diff --git a/sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt b/sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt index a3a0d75b4..5fea31ee3 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt +++ b/sites/all/modules/contrib/admin/admin_toolbar/CHANGELOG.txt @@ -1,3 +1,21 @@ +Admin Toolbar 8.x-1.25, 2018-11-22 +---------------------------------- +Changes since 8.x-1.24: + +- #3012102 by tikaszvince, Jody Lynn, adriancid, grahl, jigarius: Provider + property missing on link definition. +- #3010451 by epowelljr, adriancid: Improve the admin_toolbar module + description. +- #2958415 by adriancid, romainj, Prashant.c, harshita29: Replace usages of the + deprecated drupal_set_message() function. +- #2996485 by idebr, adriancid: Remove unused function + admin_toolbar_tools_get_links(). +- #2989281 by JKerschner, adriancid: Consistently use $entityTypeManager. +- #2985106 by Eli-T, adriancid, eme: Add ability to hide Drupal.org links + exposed by admin_toolbar_tools from certain users. +- #2938884 by Spurlos, bdanin, romainj, purdy_nc, adriancid: A non-existent + route breaks the site. + Admin Toolbar 8.x-1.24, 2018-05-28 ---------------------------------- Changes since 8.x-1.23: diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml index a8b87bf02..2e6857e41 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.info.yml @@ -1,5 +1,5 @@ name: Admin Toolbar -description: Provides a drop-down menu interface to the site Toolbar. +description: Provides an improved drop-down menu interface to the site Toolbar. package: Administration type: module @@ -8,8 +8,8 @@ type: module dependencies: - drupal:toolbar -# Information added by Drupal.org packaging script on 2018-05-28 -version: '8.x-1.24' +# Information added by Drupal.org packaging script on 2019-02-03 +version: '8.x-1.26' core: '8.x' project: 'admin_toolbar' -datestamp: 1527522484 +datestamp: 1549218484 diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.install b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.install new file mode 100644 index 000000000..bd80e9a06 --- /dev/null +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar.install @@ -0,0 +1,16 @@ +rebuild(); +} diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml index 46edfd8a4..7e743e26c 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_links_access_filter/admin_toolbar_links_access_filter.info.yml @@ -8,8 +8,8 @@ type: module dependencies: - admin_toolbar:admin_toolbar -# Information added by Drupal.org packaging script on 2018-05-28 -version: '8.x-1.24' +# Information added by Drupal.org packaging script on 2019-02-03 +version: '8.x-1.26' core: '8.x' project: 'admin_toolbar' -datestamp: 1527522484 +datestamp: 1549218484 diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml index a42c0ccbe..d94726473 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.info.yml @@ -7,10 +7,10 @@ type: module dependencies: - admin_toolbar:admin_toolbar - - drupal:system (>=8.3) + - drupal:system (>=8.5) -# Information added by Drupal.org packaging script on 2018-05-28 -version: '8.x-1.24' +# Information added by Drupal.org packaging script on 2019-02-03 +version: '8.x-1.26' core: '8.x' project: 'admin_toolbar' -datestamp: 1527522484 +datestamp: 1549218484 diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.links.menu.yml b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.links.menu.yml index fc7df7f1c..e39dbe940 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.links.menu.yml +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.links.menu.yml @@ -74,24 +74,3 @@ admin_toolbar_tools.flush_rendercache: route_name: admin_toolbar_tools.flush_rendercache parent: admin_toolbar_tools.flush menu_name: admin - -admin_toolbar_tools.drupalorg: - title: 'Drupal.org' - weight: -5 - url: https://www.drupal.org - parent: admin_toolbar_tools.help - menu_name: admin - -admin_toolbar_tools.listchanges: - title: 'Change records for Drupal core' - weight: -6 - url: https://www.drupal.org/list-changes - parent: admin_toolbar_tools.drupalorg - menu_name: admin - -admin_toolbar_tools.doc: - title: 'D8 API documentation' - weight: -5 - url: https://api.drupal.org/api/drupal/8 - parent: admin_toolbar_tools.drupalorg - menu_name: admin diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module index 1db436a1d..cd56261ec 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/admin_toolbar_tools.module @@ -48,13 +48,26 @@ function admin_toolbar_tools_help($route_name, RouteMatchInterface $route_match) * Implements hook_menu_links_discovered_alter(). */ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { + $languageManager = \Drupal::languageManager(); + $config_override_language = $languageManager->getConfigOverrideLanguage(); + $languageManager->setConfigOverrideLanguage($languageManager->getLanguage('en')); + $moduleHandler = \Drupal::moduleHandler(); $entityTypeManager = \Drupal::entityTypeManager(); $routeProvider = \Drupal::service('router.route_provider'); - $routes = []; - foreach ($routeProvider->getAllRoutes() as $route_name => $route) { - $routes[] = $route_name; - } + + /** + * Determine if a route exists by name. + * + * @param $routeName + * The name of the route to check. + * + * @return bool + * Whether a route with that route name exists. + */ + $routeExists = function ($routeName) use ($routeProvider) { + return (count($routeProvider->getRoutesByNames([$routeName])) === 1); + }; $entityTypes = $entityTypeManager->getDefinitions(); $content_entities = []; @@ -71,14 +84,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('views')) { $links['admin_toolbar_tools.flush_views'] = [ 'title' => t('Flush views cache'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'admin_toolbar_tools.flush_views', 'menu_name' => 'admin', 'parent' => 'admin_toolbar_tools.flush', ]; // Adding a menu link to Files. - if ($moduleHandler->moduleExists('file') && in_array('view.files.page_1', $routes)) { + if ($moduleHandler->moduleExists('file') && $routeExists('view.files.page_1')) { $links['admin_toolbar_tools.view.files'] = [ 'title' => t('Files'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'view.files.page_1', 'menu_name' => 'admin', 'parent' => 'system.admin_content', @@ -93,21 +108,23 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { foreach ($entityTypeManager->getStorage($content_entity_bundle)->loadMultiple() as $machine_name => $bundle) { // Normally, the edit form for the bundle would be its root link. $content_entity_bundle_root = NULL; - if (in_array('entity.' . $content_entity_bundle . '.overview_form', $routes)) { + if ($routeExists('entity.' . $content_entity_bundle . '.overview_form')) { // Some bundles have an overview/list form that make a better root link. $content_entity_bundle_root = 'entity.' . $content_entity_bundle . '.overview_form.' . $machine_name; $links[$content_entity_bundle_root] = [ 'title' => t($bundle->label()), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.' . $content_entity_bundle . '.overview_form', 'menu_name' => 'admin', 'parent' => 'entity.' . $content_entity_bundle . '.collection', 'route_parameters' => [$content_entity_bundle => $machine_name], ]; } - if (in_array('entity.' . $content_entity_bundle . '.edit_form', $routes)) { + if ($routeExists('entity.' . $content_entity_bundle . '.edit_form')) { $key = 'entity.' . $content_entity_bundle . '.edit_form.' . $machine_name; $links[$key] = [ 'title' => t($bundle->label()), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.' . $content_entity_bundle . '.edit_form', 'menu_name' => 'admin', 'parent' => 'entity.' . $content_entity_bundle . '.collection', @@ -122,9 +139,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { } } if ($moduleHandler->moduleExists('field_ui')) { - if (in_array('entity.' . $content_entity . '.field_ui_fields', $routes)) { + if ($routeExists('entity.' . $content_entity . '.field_ui_fields')) { $links['entity.' . $content_entity . '.field_ui_fields' . $machine_name] = [ 'title' => t('Manage fields'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.' . $content_entity . '.field_ui_fields', 'menu_name' => 'admin', 'parent' => $content_entity_bundle_root, @@ -132,9 +150,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { 'weight' => 1, ]; } - if (in_array('entity.entity_form_display.' . $content_entity . '.default', $routes)) { + if ($routeExists('entity.entity_form_display.' . $content_entity . '.default')) { $links['entity.entity_form_display.' . $content_entity . '.default' . $machine_name] = [ 'title' => t('Manage form display'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.entity_form_display.' . $content_entity . '.default', 'menu_name' => 'admin', 'parent' => $content_entity_bundle_root, @@ -142,9 +161,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { 'weight' => 2, ]; } - if (in_array('entity.entity_view_display.' . $content_entity . '.default', $routes)) { + if ($routeExists('entity.entity_view_display.' . $content_entity . '.default')) { $links['entity.entity_view_display.' . $content_entity . '.default.' . $machine_name] = [ 'title' => t('Manage display'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.entity_view_display.' . $content_entity . '.default', 'menu_name' => 'admin', 'parent' => $content_entity_bundle_root, @@ -153,9 +173,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; } } - if ($moduleHandler->moduleExists('devel') && in_array('entity.' . $content_entity_bundle . '.devel_load', $routes)) { + if ($moduleHandler->moduleExists('devel') && $routeExists('entity.' . $content_entity_bundle . '.devel_load')) { $links['entity.' . $content_entity_bundle . '.devel_load.' . $machine_name] = [ 'title' => t('Devel'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.' . $content_entity_bundle . '.devel_load', 'menu_name' => 'admin', 'parent' => $content_entity_bundle_root, @@ -163,9 +184,10 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { 'weight' => 4, ]; } - if (in_array('entity.' . $content_entity_bundle . '.delete_form', $routes)) { + if ($routeExists('entity.' . $content_entity_bundle . '.delete_form')) { $links['entity.' . $content_entity_bundle . '.delete_form.' . $machine_name] = [ 'title' => t('Delete'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.' . $content_entity_bundle . '.delete_form', 'menu_name' => 'admin', 'parent' => $content_entity_bundle_root, @@ -179,30 +201,35 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { // Add user links. $links['user.admin_create'] = [ 'title' => t('Add a new user'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'user.admin_create', 'menu_name' => 'admin', 'parent' => 'entity.user.collection', ]; $links['user.admin_permissions'] = [ 'title' => t('Permissions'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'user.admin_permissions', 'menu_name' => 'admin', 'parent' => 'entity.user.collection', ]; $links['entity.user_role.collection'] = [ 'title' => t('Roles'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user_role.collection', 'menu_name' => 'admin', 'parent' => 'entity.user.collection', ]; $links['admin_toolbar_tools.user.logout'] = [ 'title' => t('Logout'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'user.logout', 'parent' => 'admin_toolbar_tools.help', 'weight' => 10, ]; $links['user.role_add'] = [ 'title' => t('Add a new role'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'user.role_add', 'menu_name' => 'admin', 'parent' => 'entity.user_role.collection', @@ -211,18 +238,21 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('field_ui')) { $links['entity.user.field_ui_fields_'] = [ 'title' => t('Manage fields'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user.field_ui_fields', 'menu_name' => 'admin', 'parent' => 'entity.user.admin_form', ]; $links['entity.entity_form_display.user.default_'] = [ 'title' => t('Manage form display'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.entity_form_display.user.default', 'menu_name' => 'admin', 'parent' => 'entity.user.admin_form', ]; $links['entity.entity_view_display.user.default_'] = [ 'title' => t('Manage display'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.entity_view_display.user.default', 'menu_name' => 'admin', 'parent' => 'entity.user.admin_form', @@ -231,6 +261,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { foreach (user_roles() as $role) { $links['entity.user_role.edit_form.' . $role->id()] = [ 'title' => t($role->label()), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user_role.edit_form', 'menu_name' => 'admin', 'parent' => 'entity.user_role.collection', @@ -238,6 +269,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['entity.user_role.edit_permissions_form.' . $role->id()] = [ 'title' => t('Edit permissions'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user_role.edit_permissions_form', 'menu_name' => 'admin', 'parent' => 'entity.user_role.edit_form.' . $role->id(), @@ -245,6 +277,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['entity.user_role.delete_form.' . $role->id()] = [ 'title' => t('Delete'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user_role.delete_form', 'menu_name' => 'admin', 'parent' => 'entity.user_role.edit_form.' . $role->id(), @@ -253,6 +286,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('devel')) { $links['entity.user_role.devel_load.' . $role->id()] = [ 'title' => t('Devel'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.user_role.devel_load', 'menu_name' => 'admin', 'parent' => 'entity.user_role.edit_form.' . $role->id(), @@ -266,6 +300,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { $links['admin_toolbar_tools.add_content']['parent'] = 'system.admin_content'; $links['node.type_add'] = [ 'title' => t('Add content type'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'node.type_add', 'menu_name' => 'admin', 'parent' => 'entity.node_type.collection', @@ -275,6 +310,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { foreach ($entityTypeManager->getStorage('node_type')->loadMultiple() as $type) { $links['node.add.' . $type->id()] = [ 'title' => t($type->label()), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'node.add', 'parent' => 'admin_toolbar_tools.add_content', 'route_parameters' => ['node_type' => $type->id()], @@ -285,12 +321,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('field_ui')) { $links['field_ui.entity_form_mode_add'] = [ 'title' => t('Add new form mode'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'field_ui.entity_form_mode_add', 'menu_name' => 'admin', 'parent' => 'entity.entity_form_mode.collection', ]; $links['field_ui.entity_view_mode_add'] = [ 'title' => t('Add new view mode'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'field_ui.entity_view_mode_add', 'menu_name' => 'admin', 'parent' => 'entity.entity_view_mode.collection', @@ -300,6 +338,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('taxonomy')) { $links['entity.taxonomy_vocabulary.add_form'] = [ 'title' => t('Add vocabulary'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.taxonomy_vocabulary.add_form', 'menu_name' => 'admin', 'parent' => 'entity.taxonomy_vocabulary.collection', @@ -310,6 +349,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('menu_ui')) { $links['entity.menu.add_form'] = [ 'title' => t('Add menu'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.menu.add_form', 'menu_name' => 'admin', 'parent' => 'entity.menu.collection', @@ -319,6 +359,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { foreach (menu_ui_get_menus() as $machine_name => $label) { $links['entity.menu.edit_form.' . $machine_name] = [ 'title' => t($label), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.menu.edit_form', 'menu_name' => 'admin', 'parent' => 'entity.menu.collection', @@ -326,14 +367,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['entity.menu.delete_form.' . $machine_name] = [ 'title' => t('Delete'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.menu.delete_form', 'menu_name' => 'admin', 'parent' => 'entity.menu.edit_form.' . $machine_name, 'route_parameters' => ['menu' => $machine_name], ]; - if ($moduleHandler->moduleExists('devel')) { + if ($moduleHandler->moduleExists('devel') && $routeExists('entity.menu.devel_load')) { $links['entity.menu.devel_load.' . $machine_name] = [ 'title' => t('Devel'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.menu.devel_load', 'menu_name' => 'admin', 'parent' => 'entity.menu.edit_form.' . $machine_name, @@ -342,6 +385,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { } $links['entity.menu.add_link_form.' . $machine_name] = [ 'title' => t('Add link'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.menu.add_link_form', 'menu_name' => 'admin', 'parent' => 'entity.menu.edit_form.' . $machine_name, @@ -354,6 +398,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('block_content')) { $links['block_content.add_page'] = [ 'title' => t('Add custom block'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'block_content.add_page', 'menu_name' => 'admin', 'parent' => 'block.admin_display', @@ -361,12 +406,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['entity.block_content.collection'] = [ 'title' => t('Custom block library'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.block_content.collection', 'menu_name' => 'admin', 'parent' => 'block.admin_display', ]; $links['entity.block_content_type.collection'] = [ 'title' => t('Types'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.block_content_type.collection', 'menu_name' => 'admin', 'parent' => 'block.admin_display', @@ -377,6 +424,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('contact')) { $links['contact.form_add'] = [ 'title' => t('Add contact form'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'contact.form_add', 'menu_name' => 'admin', 'parent' => 'entity.contact_form.collection', @@ -388,12 +436,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('update')) { $links['update.module_update'] = [ 'title' => t('Update'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'update.module_update', 'menu_name' => 'admin', 'parent' => 'system.modules_list', ]; $links['update.module_install'] = [ 'title' => t('Install new module'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'update.module_install', 'menu_name' => 'admin', 'parent' => 'system.modules_list', @@ -404,6 +454,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('devel')) { $links['admin_development'] = [ 'title' => t('Development'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'system.admin_config_development', 'menu_name' => 'admin', 'parent' => 'admin_toolbar_tools.help', @@ -411,6 +462,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['admin_toolbar_tools.devel.admin_settings'] = [ 'title' => t('Devel settings'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.admin_settings', 'menu_name' => 'admin', 'parent' => 'admin_development', @@ -419,6 +471,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('webprofiler')) { $links['admin_toolbar_tools.devel.webprofiler'] = [ 'title' => t('Web Profiler settings'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'webprofiler.settings', 'menu_name' => 'admin', 'parent' => 'admin_development', @@ -426,53 +479,55 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { } $links['admin_toolbar_tools.devel.configs_list'] = [ 'title' => t('Config editor'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.configs_list', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.reinstall'] = [ 'title' => t('Reinstall modules'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.reinstall', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.menu_rebuild'] = [ 'title' => t('Rebuild menu'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.menu_rebuild', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.state_system_page'] = [ 'title' => t('State editor'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.state_system_page', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.theme_registry'] = [ 'title' => t('Theme registry'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.theme_registry', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.entity_info_page'] = [ 'title' => t('Entity Info'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.entity_info_page', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; - $links['admin_toolbar_tools.devel.execute_php'] = [ - 'title' => t('Execute PHP Code'), - 'route_name' => 'devel.execute_php', - 'menu_name' => 'admin', - 'parent' => 'admin_development', - ]; $links['admin_toolbar_tools.devel.session'] = [ 'title' => t('Session viewer'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.session', 'menu_name' => 'admin', 'parent' => 'admin_development', ]; $links['admin_toolbar_tools.devel.elements_page'] = [ 'title' => t('Form API field types'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.elements_page', 'menu_name' => 'admin', 'parent' => 'admin_development', @@ -480,16 +535,35 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { // Menu link for the Toolbar module. $links['admin_toolbar_tools.toolbar.settings'] = [ 'title' => t('Toolbar settings'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'devel.toolbar.settings_form', 'menu_name' => 'admin', 'parent' => 'devel.admin_settings', ]; } - + // If module Devel PHP is enabled. + if ($moduleHandler->moduleExists('devel_php') && $routeExists('devel_php.execute_php')) { + $links['admin_toolbar_tools.devel_php.execute_php'] = [ + 'title' => t('Execute PHP Code'), + 'route_name' => 'devel_php.execute_php', + 'menu_name' => 'admin', + 'parent' => 'admin_development', + ]; + } + elseif ($moduleHandler->moduleExists('devel') && $routeExists('devel.execute_php')) { + $links['admin_toolbar_tools.devel.execute_php'] = [ + 'title' => t('Execute PHP Code'), + 'provider' => 'admin_toolbar_tools', + 'route_name' => 'devel.execute_php', + 'menu_name' => 'admin', + 'parent' => 'admin_development', + ]; + } // If module Views Ui enabled. if ($moduleHandler->moduleExists('views_ui')) { $links['admin_toolbar_tools.views_ui.add'] = [ 'title' => t('Add new view'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'views_ui.add', 'menu_name' => 'admin', 'parent' => 'entity.view.collection', @@ -497,6 +571,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['admin_toolbar_tools.views_ui.field_list'] = [ 'title' => t('Used in views'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'views_ui.reports_fields', 'menu_name' => 'admin', 'parent' => 'entity.field_storage_config.collection', @@ -505,6 +580,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { $links['admin_toolbar_tools.system.theme_settings'] = [ 'title' => t('Settings'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'system.theme_settings', 'menu_name' => 'admin', 'parent' => 'system.themes_page', @@ -513,6 +589,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('webprofiler')) { $links['admin_toolbar_tools.devel.webprofiler'] = [ 'title' => t('Webprofiler settings'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'webprofiler.settings', 'menu_name' => 'admin', 'parent' => 'admin_development', @@ -522,12 +599,14 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('update')) { $links['update.theme_install_'] = [ 'title' => t('Install new theme'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'update.theme_install', 'menu_name' => 'admin', 'parent' => 'system.themes_page', ]; $links['update.theme_update_'] = [ 'title' => t('Update'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'update.theme_update', 'menu_name' => 'admin', 'parent' => 'system.themes_page', @@ -537,6 +616,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { foreach ($installed_themes as $key_theme => $label_theme) { $links['system.theme_settings_theme.' . $key_theme] = [ 'title' => t($label_theme), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'system.theme_settings_theme', 'menu_name' => 'admin', 'parent' => 'system.theme_settings_', @@ -551,6 +631,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('language')) { $links['admin_toolbar_tools.language.negotiation'] = [ 'title' => t('Detection and selection'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'language.negotiation', 'menu_name' => 'admin', 'parent' => 'entity.configurable_language.collection', @@ -561,14 +642,16 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('media')) { $links['admin_toolbar_tools.add_media'] = [ 'title' => t('Add media'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.media.add_page', 'menu_name' => 'admin', 'parent' => 'system.admin_content', ]; // Add node links for each media type. - foreach (\Drupal::entityTypeManager()->getStorage('media_type')->loadMultiple() as $type) { + foreach ($entityTypeManager->getStorage('media_type')->loadMultiple() as $type) { $links['media.add.' . $type->id()] = [ 'title' => t($type->label()), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'entity.media.add_form', 'parent' => 'admin_toolbar_tools.add_media', 'route_parameters' => ['media_type' => $type->id()], @@ -580,6 +663,7 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { if ($moduleHandler->moduleExists('config')) { $links['admin_toolbar_tools.config.import'] = [ 'title' => t('Import'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'config.import_full', 'menu_name' => 'admin', 'parent' => 'config.sync', @@ -587,12 +671,15 @@ function admin_toolbar_tools_menu_links_discovered_alter(&$links) { ]; $links['admin_toolbar_tools.config.export'] = [ 'title' => t('Export'), + 'provider' => 'admin_toolbar_tools', 'route_name' => 'config.export_full', 'menu_name' => 'admin', 'parent' => 'config.sync', 'weight' => 2, ]; } + + $languageManager->setConfigOverrideLanguage($config_override_language); } /** @@ -613,20 +700,3 @@ function admin_toolbar_tools_installed_themes() { return $themes_installed; } - -/** - * Get all links related to entity. - * - * @param string $entity_type_id - * The machine name for the entity type. - * - * @return array - * The links for the entity type. - */ -function admin_toolbar_tools_get_links($entity_type_id) { - $entity = \Drupal::entityTypeManager()->getDefinition($entity_type_id); - // Get all links related to entity. - $links = $entity->getLinkTemplates(); - - return $links; -} diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/composer.json b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/composer.json index 49414fc2b..41366480f 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/composer.json +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/composer.json @@ -34,6 +34,6 @@ }, "require": { "drupal/admin_toolbar": "^1", - "drupal/core": "~8.3" + "drupal/core": "~8.5" } } diff --git a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/src/Controller/ToolbarController.php b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/src/Controller/ToolbarController.php index 8b74436c8..a8758628a 100644 --- a/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/src/Controller/ToolbarController.php +++ b/sites/all/modules/contrib/admin/admin_toolbar/admin_toolbar_tools/src/Controller/ToolbarController.php @@ -162,8 +162,8 @@ class ToolbarController extends ControllerBase { * Flushes all caches. */ public function flushAll() { + $this->messenger()->addMessage($this->t('All caches cleared.')); drupal_flush_all_caches(); - drupal_set_message($this->t('All caches cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -173,7 +173,7 @@ class ToolbarController extends ControllerBase { public function flushJsCss() { $this->state() ->set('system.css_js_query_string', base_convert($this->time->getCurrentTime(), 10, 36)); - drupal_set_message($this->t('CSS and JavaScript cache cleared.')); + $this->messenger()->addMessage($this->t('CSS and JavaScript cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -182,7 +182,7 @@ class ToolbarController extends ControllerBase { */ public function flushPlugins() { $this->pluginCacheClearer->clearCachedDefinitions(); - drupal_set_message($this->t('Plugins cache cleared.')); + $this->messenger()->addMessage($this->t('Plugins cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -191,7 +191,7 @@ class ToolbarController extends ControllerBase { */ public function flushStatic() { drupal_static_reset(); - drupal_set_message($this->t('Static cache cleared.')); + $this->messenger()->addMessage($this->t('Static cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -204,7 +204,7 @@ class ToolbarController extends ControllerBase { $this->contextualLinkManager->clearCachedDefinitions(); $this->localTaskLinkManager->clearCachedDefinitions(); $this->localActionLinkManager->clearCachedDefinitions(); - drupal_set_message($this->t('Routing and links cache cleared.')); + $this->messenger()->addMessage($this->t('Routing and links cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -213,7 +213,7 @@ class ToolbarController extends ControllerBase { */ public function flushViews() { views_invalidate_cache(); - drupal_set_message($this->t('Views cache cleared.')); + $this->messenger()->addMessage($this->t('Views cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -224,7 +224,7 @@ class ToolbarController extends ControllerBase { // @todo Update once Drupal 8.6 will be released. // @see https://www.drupal.org/node/2908461 PhpStorageFactory::get('twig')->deleteAll(); - drupal_set_message($this->t('Twig cache cleared.')); + $this->messenger()->addMessage($this->t('Twig cache cleared.')); return new RedirectResponse($this->reloadPage()); } @@ -233,7 +233,7 @@ class ToolbarController extends ControllerBase { */ public function runCron() { $this->cron->run(); - drupal_set_message($this->t('Cron ran successfully.')); + $this->messenger()->addMessage($this->t('Cron ran successfully.')); return new RedirectResponse($this->reloadPage()); } @@ -242,7 +242,7 @@ class ToolbarController extends ControllerBase { */ public function cacheRender() { $this->cacheRender->invalidateAll(); - drupal_set_message($this->t('Render cache cleared.')); + $this->messenger()->addMessage($this->t('Render cache cleared.')); return new RedirectResponse($this->reloadPage()); } diff --git a/sites/all/modules/contrib/admin/config_update/config_update.info.yml b/sites/all/modules/contrib/admin/config_update/config_update.info.yml index a26c33f74..fbd94845c 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update.info.yml +++ b/sites/all/modules/contrib/admin/config_update/config_update.info.yml @@ -5,8 +5,8 @@ description: 'Provides basic revert and update functionality for other modules' dependencies: - drupal:config -# Information added by Drupal.org packaging script on 2017-12-05 -version: '8.x-1.5' +# Information added by Drupal.org packaging script on 2018-12-17 +version: '8.x-1.6' core: '8.x' project: 'config_update' -datestamp: 1512514387 +datestamp: 1545090489 diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc index fd8cfb220..e4d77bd41 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.drush.inc @@ -285,8 +285,13 @@ function drush_config_update_ui_config_revert($name) { // The revert command needs the type and the unprefixed name. $type = $lister->getTypeNameByConfigName($name); + // The lister gives NULL if simple configuration, but the reverter expects + // 'system.simple' so we convert it. + if ($type === NULL) { + $type = 'system.simple'; + } $shortname = $name; - if ($type && $type != 'system.simple') { + if ($type != 'system.simple') { $definition = $manager->getDefinition($type); $prefix = $definition->getConfigPrefix() . '.'; if (strpos($name, $prefix) === 0) { diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml index a65420a2f..6d109d477 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.info.yml @@ -7,8 +7,8 @@ dependencies: - config_update:config_update - drupal:config -# Information added by Drupal.org packaging script on 2017-12-05 -version: '8.x-1.5' +# Information added by Drupal.org packaging script on 2018-12-17 +version: '8.x-1.6' core: '8.x' project: 'config_update' -datestamp: 1512514387 +datestamp: 1545090489 diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module index 9e1ac6fbf..e871058e4 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/config_update_ui.module @@ -35,3 +35,20 @@ function config_update_ui_help($route_name, RouteMatchInterface $route_match) { return '

' . t('Most configuration is organized into a hierarchy of settings; at a minimum, it is a one-level hierarchy where each setting has a name and a value, and the hierarchy comes in when some of the settings have multiple components.') . '

' . t('Configuration items are normalized and formatted before computing differences. The normalization step alphabetizes the components at each level of the hierarchy, and removes a few components whose differences should be ignored, such as the UUID. The formatting step shows the full hierarchy of each configuration value with :: separators for the hierarchy levels, and a : separator between the lowest-level setting name and the value, so that in a line-by-line diff you can always see which values are actually different. Green lines with + signs have been added, and yellow lines with - signs have been removed.') . '

' . t('Note that differences are considering the base configuration, without overrides from your settings.php file, or translations.') . '

'; } } + +/** + * Implements hook_menu_links_discovered_alter(). + */ +function config_update_ui_menu_links_discovered_alter(&$links) { + if (\Drupal::moduleHandler()->moduleExists('admin_toolbar_tools')) { + // Add the Updates report link to the Tools menu, but only if the + // Admin Toolbar Extra Tools module is present. + $links['admin_toolbar_tools.config.update'] = [ + 'title' => t('Updates report'), + 'route_name' => 'config_update_ui.report', + 'menu_name' => 'admin', + 'parent' => 'config.sync', + 'weight' => 3, + ]; + } +} diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php index e8206b581..c11e9ba94 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigDeleteConfirmForm.php @@ -137,7 +137,7 @@ class ConfigDeleteConfirmForm extends ConfirmFormBase { public function submitForm(array &$form, FormStateInterface $form_state) { $this->configRevert->delete($this->type, $this->name); - drupal_set_message($this->t('The configuration was deleted.')); + $this->messenger()->addMessage($this->t('The configuration %item has been deleted.', ['%item' => $this->name])); $form_state->setRedirectUrl($this->getCancelUrl()); } diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigImportConfirmForm.php b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigImportConfirmForm.php index 20790ed73..2d064f6e2 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigImportConfirmForm.php +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigImportConfirmForm.php @@ -139,7 +139,7 @@ class ConfigImportConfirmForm extends ConfirmFormBase { public function submitForm(array &$form, FormStateInterface $form_state) { $this->configRevert->import($this->type, $this->name); - drupal_set_message($this->t('The configuration was imported from its source.')); + $this->messenger()->addMessage($this->t('The configuration %item has been imported from its source.', ['%item' => $this->name])); $form_state->setRedirectUrl($this->getCancelUrl()); } diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php index 9699ba3a0..8785d0fc5 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Form/ConfigRevertConfirmForm.php @@ -139,7 +139,7 @@ class ConfigRevertConfirmForm extends ConfirmFormBase { public function submitForm(array &$form, FormStateInterface $form_state) { $this->configRevert->revert($this->type, $this->name); - drupal_set_message($this->t('The configuration was reverted to its source.')); + $this->messenger()->addMessage($this->t('The configuration %item has been reverted to its source.', ['%item' => $this->name])); $form_state->setRedirectUrl($this->getCancelUrl()); } diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigProfileOverridesTest.php b/sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigProfileOverridesTest.php similarity index 81% rename from sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigProfileOverridesTest.php rename to sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigProfileOverridesTest.php index 299ae9c3b..f2ced76e1 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigProfileOverridesTest.php +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigProfileOverridesTest.php @@ -1,15 +1,15 @@ adminUser = $this->drupalCreateUser([ + $this->adminUser = $this->createUser([ 'access administration pages', 'administer themes', 'view config updates report', @@ -68,7 +68,8 @@ class ConfigProfileOverridesTest extends WebTestBase { // that system.theme is not shown (it should not be missing, or added, // or overridden). $this->drupalGet('admin/config/development/configuration/report/type/system.simple'); - $this->assertNoRaw('system.theme'); + $session = $this->assertSession(); + $session->responseNotContains('system.theme'); // Go to the Appearance page and change the theme to whatever is currently // disabled. Return to the report and verify that system.theme is there, @@ -76,22 +77,25 @@ class ConfigProfileOverridesTest extends WebTestBase { $this->drupalGet('admin/appearance'); $this->clickLink('Install and set as default'); $this->drupalGet('admin/config/development/configuration/report/type/system.simple'); - $this->assertText('system.theme'); + $session = $this->assertSession(); + $session->pageTextContains('system.theme'); // Look at the differences for system.theme and verify it's against // the standard profile version, not default version. The line for // default should show bartik as the source; if it's against the system // version, the word bartik would not be there. $this->drupalGet('admin/config/development/configuration/report/diff/system.simple/system.theme'); - $this->assertText('bartik'); + $session = $this->assertSession(); + $session->pageTextContains('bartik'); // Revert and verify that it reverted to the profile version, not the // system module version. $this->drupalGet('admin/config/development/configuration/report/revert/system.simple/system.theme'); $this->drupalPostForm(NULL, [], 'Revert'); $this->drupalGet('admin/config/development/configuration/single/export/system.simple/system.theme'); - $this->assertText('admin: seven'); - $this->assertText('default: bartik'); + $session = $this->assertSession(); + $session->pageTextContains('admin: seven'); + $session->pageTextContains('default: bartik'); } } diff --git a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigUpdateTest.php b/sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigUpdateTest.php similarity index 70% rename from sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigUpdateTest.php rename to sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigUpdateTest.php index 1ac7aefba..4f2c943d1 100644 --- a/sites/all/modules/contrib/admin/config_update/config_update_ui/src/Tests/ConfigUpdateTest.php +++ b/sites/all/modules/contrib/admin/config_update/config_update_ui/tests/src/Functional/ConfigUpdateTest.php @@ -1,15 +1,15 @@ adminUser = $this->drupalCreateUser([ + $this->adminUser = $this->createUser([ 'access administration pages', 'administer search', 'view config updates report', @@ -60,8 +60,8 @@ class ConfigUpdateTest extends WebTestBase { $this->drupalLogin($this->adminUser); // Make sure local tasks and page title are showing. - $this->drupalPlaceBlock('local_tasks_block'); - $this->drupalPlaceBlock('page_title_block'); + $this->placeBlock('local_tasks_block'); + $this->placeBlock('page_title_block'); // Load the Drush include file so that its functions can be tested, plus // the Drush testing include file. @@ -103,13 +103,15 @@ class ConfigUpdateTest extends WebTestBase { $this->assertReport('Testing profile', [], [], [], $inactive, ['added']); // The locale.settings line should show that the Testing profile is the // provider. - $this->assertText('Testing profile'); + $session = $this->assertSession(); + $session->pageTextContains('Testing profile'); $this->assertDrushReports('profile', '', [], [], [], array_keys($inactive)); // Verify that the user search page cannot be imported (because it already // exists). $this->drupalGet('admin/config/development/configuration/report/import/search_page/user_search'); - $this->assertResponse(404); + $session = $this->assertSession(); + $session->statusCodeEquals(404); // Delete the user search page from the search UI and verify report for // both the search page config type and user module. @@ -121,7 +123,8 @@ class ConfigUpdateTest extends WebTestBase { $this->assertReport('Search page', [], [], [], $inactive); // The search.page.user_search line should show that the User module is the // provider. - $this->assertText('User module'); + $session = $this->assertSession(); + $session->pageTextContains('User module'); $this->assertDrushReports('type', 'search_page', [], [], [], array_keys($inactive)); $this->drupalGet('admin/config/development/configuration/report/module/user'); @@ -138,17 +141,20 @@ class ConfigUpdateTest extends WebTestBase { // Verify that the user search page cannot be reverted (because it does // not already exist). $this->drupalGet('admin/config/development/configuration/report/revert/search_page/user_search'); - $this->assertResponse(404); + $session = $this->assertSession(); + $session->statusCodeEquals(404); // Verify that the delete URL doesn't work either. $this->drupalGet('admin/config/development/configuration/report/delete/search_page/user_search'); - $this->assertResponse(404); + $session = $this->assertSession(); + $session->statusCodeEquals(404); // Use the import link to get it back. Do this from the search page // report to make sure we are importing the right config. $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->clickLink('Import from source'); $this->drupalPostForm(NULL, [], 'Import'); - $this->assertText('The configuration was imported'); + $session = $this->assertSession(); + $session->pageTextContains('has been imported'); $this->assertNoReport(); $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->assertReport('Search page', [], [], [], []); @@ -156,8 +162,9 @@ class ConfigUpdateTest extends WebTestBase { // Verify that after import, there is no config hash generated. $this->drupalGet('admin/config/development/configuration/single/export/search_page/user_search'); - $this->assertText('id: user_search'); - $this->assertNoText('default_config_hash:'); + $session = $this->assertSession(); + $session->pageTextContains('id: user_search'); + $session->pageTextNotContains('default_config_hash:'); // Test importing again, this time using the Drush import command. $this->drupalGet('admin/config/search/pages'); @@ -184,10 +191,11 @@ class ConfigUpdateTest extends WebTestBase { // Test the show differences link. $this->clickLink('Show differences'); - $this->assertText('Content'); - $this->assertText('New label'); - $this->assertText('node'); - $this->assertText('new_path'); + $session = $this->assertSession(); + $session->pageTextContains('Content'); + $session->pageTextContains('New label'); + $session->pageTextContains('node'); + $session->pageTextContains('new_path'); // Test the show differences Drush command. $output = drush_config_update_ui_config_diff('search.page.node_search'); @@ -203,36 +211,39 @@ class ConfigUpdateTest extends WebTestBase { // Test the export link. $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->clickLink('Export'); - $this->assertText('Here is your configuration:'); - $this->assertText('id: node_search'); - $this->assertText('New label'); - $this->assertText('path: new_path'); - $this->assertText('search.page.node_search.yml'); + $session = $this->assertSession(); + $session->pageTextContains('Here is your configuration:'); + $session->pageTextContains('id: node_search'); + $session->pageTextContains('New label'); + $session->pageTextContains('path: new_path'); + $session->pageTextContains('search.page.node_search.yml'); - // Grab the uuid and hash lines for the next test. - $text = $this->getTextContent(); + // Grab the uuid and hash lines from the exported config for the next test. + $text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml()); $matches = []; preg_match('|^.*uuid:.*$|m', $text, $matches); - $uuid_line = $matches[0]; + $uuid_line = trim($matches[0]); preg_match('|^.*default_config_hash:.*$|m', $text, $matches); - $hash_line = $matches[0]; + $hash_line = trim($matches[0]); // Test reverting. $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->clickLink('Revert to source'); - $this->assertText('Are you sure you want to revert'); - $this->assertText('Search page'); - $this->assertText('node_search'); - $this->assertText('Customizations will be lost. This action cannot be undone'); + $session = $this->assertSession(); + $session->pageTextContains('Are you sure you want to revert'); + $session->pageTextContains('Search page'); + $session->pageTextContains('node_search'); + $session->pageTextContains('Customizations will be lost. This action cannot be undone'); $this->drupalPostForm(NULL, [], 'Revert'); $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->assertReport('Search page', [], [], [], []); // Verify that the uuid and hash keys were retained in the revert. $this->drupalGet('admin/config/development/configuration/single/export/search_page/node_search'); - $this->assertText('id: node_search'); - $this->assertText($uuid_line); - $this->assertText($hash_line); + $session = $this->assertSession(); + $session->pageTextContains('id: node_search'); + $session->pageTextContains($uuid_line); + $session->pageTextContains($hash_line); // Test reverting again, this time using Drush single revert command. $this->drupalGet('admin/config/search/pages'); @@ -278,19 +289,22 @@ class ConfigUpdateTest extends WebTestBase { // Test the export link. $this->clickLink('Export'); - $this->assertText('Here is your configuration:'); - $this->assertText('id: test'); - $this->assertText('label: test'); - $this->assertText('path: test'); - $this->assertText('search.page.test.yml'); + $session = $this->assertSession(); + $session->pageTextContains('Here is your configuration:'); + $session->pageTextContains('id: test'); + $session->pageTextContains('label: test'); + $session->pageTextContains('path: test'); + $session->pageTextContains('search.page.test.yml'); // Test the delete link. $this->drupalGet('admin/config/development/configuration/report/type/search_page'); $this->clickLink('Delete'); - $this->assertText('Are you sure'); - $this->assertText('cannot be undone'); + $session = $this->assertSession(); + $session->pageTextContains('Are you sure'); + $session->pageTextContains('cannot be undone'); $this->drupalPostForm(NULL, [], 'Delete'); - $this->assertText('The configuration was deleted'); + $session = $this->assertSession(); + $session->pageTextContains('has been deleted'); // And verify the report again. $this->drupalGet('admin/config/development/configuration/report/type/search_page'); @@ -306,18 +320,20 @@ class ConfigUpdateTest extends WebTestBase { $this->assertReport('Search module', [], [], $changed, [], ['added']); $this->clickLink('Show differences'); - $this->assertText('Config difference for Simple configuration search.settings'); - $this->assertText('index::minimum_word_size'); - $this->assertText('4'); + $session = $this->assertSession(); + $session->pageTextContains('Config difference for Simple configuration search.settings'); + $session->pageTextContains('index::minimum_word_size'); + $session->pageTextContains('4'); $this->drupalGet('admin/config/development/configuration/report/module/search'); $this->clickLink('Export'); - $this->assertText('minimum_word_size: 4'); + $session = $this->assertSession(); + $session->pageTextContains('minimum_word_size: 4'); // Grab the hash line for the next test. - $text = $this->getTextContent(); + $text = strip_tags($this->getSession()->getPage()->find('css', 'textarea')->getHtml()); $matches = []; preg_match('|^.*default_config_hash:.*$|m', $text, $matches); - $hash_line = $matches[0]; + $hash_line = trim($matches[0]); $this->drupalGet('admin/config/development/configuration/report/module/search'); $this->clickLink('Revert to source'); @@ -325,7 +341,8 @@ class ConfigUpdateTest extends WebTestBase { // Verify that the hash was retained in the revert. $this->drupalGet('admin/config/development/configuration/single/export/system.simple/search.settings'); - $this->assertText($hash_line); + $session = $this->assertSession(); + $session->pageTextContains($hash_line); $this->drupalGet('admin/config/development/configuration/report/module/search'); $this->assertReport('Search module', [], [], [], [], ['added']); @@ -338,6 +355,10 @@ class ConfigUpdateTest extends WebTestBase { $changed = ['filter.format.plain_text' => 'New label']; $this->drupalGet('admin/config/development/configuration/report/type/filter_format'); $this->assertReport('Text format', [], [], $changed, []); + + // Verify that we can revert non-entity configuration in Drush. Issue: + // https://www.drupal.org/project/config_update/issues/2935395 + drush_config_update_ui_config_revert('system.date'); } /** @@ -359,62 +380,63 @@ class ConfigUpdateTest extends WebTestBase { * Array of report sections to skip checking. */ protected function assertReport($title, array $missing, array $added, array $changed, array $inactive, array $skip = []) { - $this->assertText('Configuration updates report for ' . $title); - $this->assertText('Generate new report'); + $session = $this->assertSession(); + $session->pageTextContains('Configuration updates report for ' . $title); + $session->pageTextContains('Generate new report'); if (!in_array('missing', $skip)) { - $this->assertText('Missing configuration items'); + $session->pageTextContains('Missing configuration items'); if (count($missing)) { foreach ($missing as $name => $label) { - $this->assertText($name); - $this->assertText($label); + $session->pageTextContains($name); + $session->pageTextContains($label); } - $this->assertNoText('None: all provided configuration items are in your active configuration.'); + $session->pageTextNotContains('None: all provided configuration items are in your active configuration.'); } else { - $this->assertText('None: all provided configuration items are in your active configuration.'); + $session->pageTextContains('None: all provided configuration items are in your active configuration.'); } } if (!in_array('inactive', $skip)) { - $this->assertText('Inactive optional items'); + $session->pageTextContains('Inactive optional items'); if (count($inactive)) { foreach ($inactive as $name => $label) { - $this->assertText($name); - $this->assertText($label); + $session->pageTextContains($name); + $session->pageTextContains($label); } - $this->assertNoText('None: all optional configuration items are in your active configuration.'); + $session->pageTextNotContains('None: all optional configuration items are in your active configuration.'); } else { - $this->assertText('None: all optional configuration items are in your active configuration.'); + $session->pageTextContains('None: all optional configuration items are in your active configuration.'); } } if (!in_array('added', $skip)) { - $this->assertText('Added configuration items'); + $session->pageTextContains('Added configuration items'); if (count($added)) { foreach ($added as $name => $label) { - $this->assertText($name); - $this->assertText($label); + $session->pageTextContains($name); + $session->pageTextContains($label); } - $this->assertNoText('None: all active configuration items of this type were provided by modules, themes, or install profile.'); + $session->pageTextNotContains('None: all active configuration items of this type were provided by modules, themes, or install profile.'); } else { - $this->assertText('None: all active configuration items of this type were provided by modules, themes, or install profile.'); + $session->pageTextContains('None: all active configuration items of this type were provided by modules, themes, or install profile.'); } } if (!in_array('changed', $skip)) { - $this->assertText('Changed configuration items'); + $session->pageTextContains('Changed configuration items'); if (count($changed)) { foreach ($changed as $name => $label) { - $this->assertText($name); - $this->assertText($label); + $session->pageTextContains($name); + $session->pageTextContains($label); } - $this->assertNoText('None: no active configuration items differ from their current provided versions.'); + $session->pageTextNotContains('None: no active configuration items differ from their current provided versions.'); } else { - $this->assertText('None: no active configuration items differ from their current provided versions.'); + $session->pageTextContains('None: no active configuration items differ from their current provided versions.'); } } } @@ -440,7 +462,7 @@ class ConfigUpdateTest extends WebTestBase { protected function assertDrushReports($type, $name, array $missing, array $added, array $changed, array $inactive, array $skip = []) { if (!in_array('missing', $skip)) { $output = drush_config_update_ui_config_missing_report($type, $name); - $this->assertEqual(count($output), count($missing), 'Drush missing report has correct number of items'); + $this->assertEquals(count($output), count($missing), 'Drush missing report has correct number of items'); if (count($missing)) { foreach ($missing as $item) { $this->assertTrue(in_array($item, $output), "Item $item is in the Drush missing report"); @@ -450,7 +472,7 @@ class ConfigUpdateTest extends WebTestBase { if (!in_array('added', $skip) && $type == 'type') { $output = drush_config_update_ui_config_added_report($name); - $this->assertEqual(count($output), count($added), 'Drush added report has correct number of items'); + $this->assertEquals(count($output), count($added), 'Drush added report has correct number of items'); if (count($added)) { foreach ($added as $item) { $this->assertTrue(in_array($item, $output), "Item $item is in the Drush added report"); @@ -460,7 +482,7 @@ class ConfigUpdateTest extends WebTestBase { if (!in_array('changed', $skip)) { $output = drush_config_update_ui_config_different_report($type, $name); - $this->assertEqual(count($output), count($changed), 'Drush changed report has correct number of items'); + $this->assertEquals(count($output), count($changed), 'Drush changed report has correct number of items'); if (count($changed)) { foreach ($changed as $item) { $this->assertTrue(in_array($item, $output), "Item $item is in the Drush changed report"); @@ -470,7 +492,7 @@ class ConfigUpdateTest extends WebTestBase { if (!in_array('inactive', $skip)) { $output = drush_config_update_ui_config_inactive_report($type, $name); - $this->assertEqual(count($output), count($inactive), 'Drush inactive report has correct number of items'); + $this->assertEquals(count($output), count($inactive), 'Drush inactive report has correct number of items'); if (count($inactive)) { foreach ($inactive as $item) { $this->assertTrue(in_array($item, $output), "Item $item is in the Drush inactive report"); @@ -485,37 +507,38 @@ class ConfigUpdateTest extends WebTestBase { * Assumes you are already on the report form page. */ protected function assertNoReport() { - $this->assertText('Report type'); - $this->assertText('Full report'); - $this->assertText('Single configuration type'); - $this->assertText('Single module'); - $this->assertText('Single theme'); - $this->assertText('Installation profile'); - $this->assertText('Updates report'); - $this->assertNoText('Missing configuration items'); - $this->assertNoText('Added configuration items'); - $this->assertNoText('Changed configuration items'); - $this->assertNoText('Unchanged configuration items'); + $session = $this->assertSession(); + $session->pageTextContains('Report type'); + $session->pageTextContains('Full report'); + $session->pageTextContains('Single configuration type'); + $session->pageTextContains('Single module'); + $session->pageTextContains('Single theme'); + $session->pageTextContains('Installation profile'); + $session->pageTextContains('Updates report'); + $session->pageTextNotContains('Missing configuration items'); + $session->pageTextNotContains('Added configuration items'); + $session->pageTextNotContains('Changed configuration items'); + $session->pageTextNotContains('Unchanged configuration items'); // Verify that certain report links are shown or not shown. For extensions, // only extensions that have configuration should be shown. // Modules. - $this->assertLink('Search'); - $this->assertLink('Field'); - $this->assertNoLink('Configuration Update Base'); - $this->assertNoLink('Configuration Update Reports'); + $session->linkExists('Search'); + $session->linkExists('Field'); + $session->linkNotExists('Configuration Update Base'); + $session->linkNotExists('Configuration Update Reports'); // Themes. - $this->assertNoLink('Stark'); - $this->assertNoLink('Classy'); + $session->linkNotExists('Stark'); + $session->linkNotExists('Classy'); // Profiles. - $this->assertLink('Testing'); + $session->linkExists('Testing'); // Configuration types. - $this->assertLink('Everything'); - $this->assertLink('Simple configuration'); - $this->assertLink('Search page'); + $session->linkExists('Everything'); + $session->linkExists('Simple configuration'); + $session->linkExists('Search page'); } } diff --git a/sites/all/modules/contrib/admin/config_update/drupalci.yml b/sites/all/modules/contrib/admin/config_update/drupalci.yml new file mode 100644 index 000000000..59a556b5e --- /dev/null +++ b/sites/all/modules/contrib/admin/config_update/drupalci.yml @@ -0,0 +1,37 @@ +# This is the DrupalCI testbot build file for Configuration Update Manager. +# Learn to make one for your own drupal.org project: +# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing +build: + assessment: + validate_codebase: + phplint: + phpcs: + # phpcs will use core's specified version of Coder. + sniff-all-files: true + halt-on-fail: true + testing: + # run_tests task is executed several times in order of performance speeds. + # halt-on-fail can be set on the run_tests tasks in order to fail fast. + # suppress-deprecations is false in order to be alerted to usages of + # deprecated code. + run_tests.phpunit: + types: 'PHPUnit-Unit' + testgroups: '--all' + suppress-deprecations: false + halt-on-fail: false + run_tests.kernel: + types: 'PHPUnit-Kernel' + testgroups: '--all' + suppress-deprecations: false + halt-on-fail: false + run_tests.functional: + types: 'PHPUnit-Functional' + testgroups: '--all' + suppress-deprecations: false + halt-on-fail: false + run_tests.javascript: + concurrency: 15 + types: 'PHPUnit-FunctionalJavascript' + testgroups: '--all' + suppress-deprecations: false + halt-on-fail: false diff --git a/sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php b/sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php index dbda8df23..ce161ba56 100644 --- a/sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php +++ b/sites/all/modules/contrib/admin/config_update/src/ConfigDiffer.php @@ -5,6 +5,7 @@ namespace Drupal\config_update; use Drupal\Component\Diff\Diff; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Core\Serialization\Yaml; /** * Provides methods related to config differences. @@ -14,7 +15,7 @@ class ConfigDiffer implements ConfigDiffInterface { use StringTranslationTrait; /** - * List of elements to ignore when comparing config. + * List of elements to ignore on top level when comparing config. * * @var string[] * @@ -46,7 +47,7 @@ class ConfigDiffer implements ConfigDiffInterface { * @param \Drupal\Core\StringTranslation\TranslationInterface $translation * String translation service. * @param string[] $ignore - * Config components to ignore. + * Config components to ignore at the top level. * @param string $hierarchy_prefix * Prefix to use in diffs for array hierarchy. * @param string $value_prefix @@ -62,9 +63,9 @@ class ConfigDiffer implements ConfigDiffInterface { /** * Normalizes config for comparison. * - * Recursively removes elements in the ignore list from configuration, - * as well as empty array values, and sorts at each level by array key, so - * that config from different storage can be compared meaningfully. + * Removes elements in the ignore list from the top level of configuration, + * and at each level of the array, removes empty arrays and sorts by array + * key, so that config from different storage can be compared meaningfully. * * @param array|null $config * Configuration array to normalize. @@ -80,27 +81,40 @@ class ConfigDiffer implements ConfigDiffInterface { return []; } - // Remove "ignore" elements. + // Remove "ignore" elements, only at the top level. foreach ($this->ignore as $element) { unset($config[$element]); } - // Recursively normalize remaining elements, if they are arrays. - foreach ($config as $key => $value) { + // Recursively normalize and return. + return $this->normalizeArray($config); + } + + /** + * Recursively sorts an array by key, and removes empty arrays. + * + * @param array $array + * An array to normalize. + * + * @return array + * An array that is sorted by key, at each level of the array, with empty + * arrays removed. + */ + protected function normalizeArray(array $array) { + foreach ($array as $key => $value) { if (is_array($value)) { - $new = $this->normalize($value); + $new = $this->normalizeArray($value); if (count($new)) { - $config[$key] = $new; + $array[$key] = $new; } else { - unset($config[$key]); + unset($array[$key]); } } } - // Sort and return. - ksort($config); - return $config; + ksort($array); + return $array; } /** @@ -157,11 +171,8 @@ class ConfigDiffer implements ConfigDiffInterface { $lines[] = $line; } } - elseif (is_null($value)) { - $lines[] = $section_prefix . $this->valuePrefix . $this->t('(NULL)'); - } else { - $lines[] = $section_prefix . $this->valuePrefix . $value; + $lines[] = $section_prefix . $this->valuePrefix . Yaml::encode($value); } } diff --git a/sites/all/modules/contrib/admin/config_update/src/ConfigLister.php b/sites/all/modules/contrib/admin/config_update/src/ConfigLister.php index 4c0bf9a14..06219cce9 100644 --- a/sites/all/modules/contrib/admin/config_update/src/ConfigLister.php +++ b/sites/all/modules/contrib/admin/config_update/src/ConfigLister.php @@ -89,7 +89,7 @@ class ConfigLister implements ConfigListInterface { // Calculate and return the list. foreach ($this->entityManager->getDefinitions() as $entity_type => $definition) { - if ($definition->isSubclassOf('Drupal\Core\Config\Entity\ConfigEntityInterface')) { + if ($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface')) { $this->definitions[$entity_type] = $definition; $prefix = $definition->getConfigPrefix(); $this->typesByPrefix[$prefix] = $entity_type; diff --git a/sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php b/sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php index 18e03fa40..c0ef26d60 100644 --- a/sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php +++ b/sites/all/modules/contrib/admin/config_update/src/ConfigRevertInterface.php @@ -72,7 +72,7 @@ interface ConfigRevertInterface { * The name of the config item, without the prefix. * * @return array - * The configuration value. + * The configuration value, or FALSE if it is not found. */ public function getFromActive($type, $name); diff --git a/sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php b/sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php index 51e255ab6..e47103c8b 100644 --- a/sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php +++ b/sites/all/modules/contrib/admin/config_update/src/ConfigReverter.php @@ -87,11 +87,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { * {@inheritdoc} */ public function import($type, $name) { - // Read the config from the file. + // Read the config from the file. Note: Do not call getFromExtension() here + // because we need $full_name below. $full_name = $this->getFullName($type, $name); - $value = $this->extensionConfigStorage->read($full_name); - if (!$value) { - $value = $this->extensionOptionalConfigStorage->read($full_name); + $value = FALSE; + if ($full_name) { + $value = $this->extensionConfigStorage->read($full_name); + if (!$value) { + $value = $this->extensionOptionalConfigStorage->read($full_name); + } } if (!$value) { return FALSE; @@ -118,36 +122,44 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { * {@inheritdoc} */ public function revert($type, $name) { - // Read the config from the file. + // Read the config from the file. Note: Do not call getFromExtension() here + // because we need $full_name below. + $value = FALSE; $full_name = $this->getFullName($type, $name); - $value = $this->extensionConfigStorage->read($full_name); - if (!$value) { - $value = $this->extensionOptionalConfigStorage->read($full_name); + if ($full_name) { + $value = $this->extensionConfigStorage->read($full_name); + if (!$value) { + $value = $this->extensionOptionalConfigStorage->read($full_name); + } } if (!$value) { return FALSE; } + // Make sure the configuration exists currently in active storage. + if (!$this->activeConfigStorage->read($full_name)) { + return FALSE; + } + + // Load the current config and replace the value, retaining the config + // hash (which is part of the _core config key's value). if ($type == 'system.simple') { - // Load the current config and replace the value, retaining the config - // hash (which is part of the _core config key's value). $config = $this->configFactory->getEditable($full_name); $core = $config->get('_core'); - $config->setData($value); - $config->set('_core', $core); - $config->save(); + $config + ->setData($value) + ->set('_core', $core) + ->save(); } else { - // Load the current config entity and replace the value. Note that - // the uuid and _core/hash values are retained for entity-based config, - // since updateFromStorageRecord() only updates values that are part of - // the passed-in array, and doesn't remove or alter other values. $definition = $this->entityManager->getDefinition($type); $id_key = $definition->getKey('id'); $id = $value[$id_key]; $entity_storage = $this->entityManager->getStorage($type); $entity = $entity_storage->load($id); + $core = $entity->get('_core'); $entity = $entity_storage->updateFromStorageRecord($entity, $value); + $entity->set('_core', $core); $entity->save(); } @@ -162,11 +174,15 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { * {@inheritdoc} */ public function delete($type, $name) { + $config = FALSE; $full_name = $this->getFullName($type, $name); - if (!$full_name) { - return FALSE; + if ($full_name) { + // Make sure the configuration exists currently in active storage. + if (!$this->activeConfigStorage->read($full_name)) { + return FALSE; + } + $config = $this->configFactory->getEditable($full_name); } - $config = $this->configFactory->getEditable($full_name); if (!$config) { return FALSE; } @@ -183,17 +199,23 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { */ public function getFromActive($type, $name) { $full_name = $this->getFullName($type, $name); - return $this->activeConfigStorage->read($full_name); + if ($full_name) { + return $this->activeConfigStorage->read($full_name); + } + return FALSE; } /** * {@inheritdoc} */ public function getFromExtension($type, $name) { + $value = FALSE; $full_name = $this->getFullName($type, $name); - $value = $this->extensionConfigStorage->read($full_name); - if (!$value) { - $value = $this->extensionOptionalConfigStorage->read($full_name); + if ($full_name) { + $value = $this->extensionConfigStorage->read($full_name); + if (!$value) { + $value = $this->extensionOptionalConfigStorage->read($full_name); + } } return $value; } @@ -207,7 +229,7 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { * The config name, without prefix. * * @return string - * The config item's full name. + * The config item's full name, or FALSE if there is an error. */ protected function getFullName($type, $name) { if ($type == 'system.simple' || !$type) { @@ -215,8 +237,13 @@ class ConfigReverter implements ConfigRevertInterface, ConfigDeleteInterface { } $definition = $this->entityManager->getDefinition($type); - $prefix = $definition->getConfigPrefix() . '.'; - return $prefix . $name; + if ($definition) { + $prefix = $definition->getConfigPrefix() . '.'; + return $prefix . $name; + } + else { + return FALSE; + } } } diff --git a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigDifferTest.php b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigDifferTest.php index 9b2ad5746..085291091 100644 --- a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigDifferTest.php +++ b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigDifferTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\config_update\Unit; use Drupal\config_update\ConfigDiffer; -use Drupal\Tests\UnitTestCase; /** * Tests the \Drupal\config_update\ConfigDiffer class. @@ -12,14 +11,7 @@ use Drupal\Tests\UnitTestCase; * * @coversDefaultClass \Drupal\config_update\ConfigDiffer */ -class ConfigDifferTest extends UnitTestCase { - - /** - * The mock translation object. - * - * @var \Drupal\Core\StringTranslation\TranslationInterface - */ - protected $stringTranslation; +class ConfigDifferTest extends ConfigUpdateUnitTestBase { /** * The config differ to test. @@ -32,12 +24,7 @@ class ConfigDifferTest extends UnitTestCase { * {@inheritdoc} */ protected function setUp() { - $this->stringTranslation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock(); - $this->stringTranslation - ->method('t') - ->will($this->returnArgument(0)); - - $this->configDiffer = new ConfigDiffer($this->stringTranslation); + $this->configDiffer = new ConfigDiffer($this->getTranslationMock()); } /** @@ -59,13 +46,14 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => FALSE, + 'empty' => [], ], ]; - return [ [$base, $base, TRUE], - // Add _core, omit uuid. Should match. + // Add _core, omit uuid at top level. Should match, as both are removed + // in normalization process. [ $base, [ @@ -75,27 +63,30 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => FALSE, + 'empty' => [], ], ], TRUE, ], - // Change order. Should match. + // Change order in top and deep level. Should match. [ $base, [ - 'a' => 'a', 'uuid' => 'bar', 'b' => 0, + 'a' => 'a', 'c' => [ - 'd' => TRUE, 'e' => FALSE, + 'empty' => [], + 'd' => TRUE, ], ], TRUE, ], - // Change order and add _core in deeper level. Should match. + // Add _core in deeper level. Should not match, as this is removed + // only at the top level during normalization. [ $base, [ @@ -103,12 +94,31 @@ class ConfigDifferTest extends UnitTestCase { 'a' => 'a', 'b' => 0, 'c' => [ - 'e' => FALSE, - '_core' => 'foo', + '_core' => 'do-not-use-this-key', 'd' => TRUE, + 'e' => FALSE, + 'empty' => [], ], ], - TRUE, + FALSE, + ], + + // Add uuid in deeper level. Should not match, as this is removed + // only at the top level during normalization. + [ + $base, + [ + 'uuid' => 'bar', + 'a' => 'a', + 'b' => 0, + 'c' => [ + 'd' => TRUE, + 'e' => FALSE, + 'uuid' => 'important', + 'empty' => [], + ], + ], + FALSE, ], // Omit a component. Should not match. @@ -120,6 +130,7 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => FALSE, + 'empty' => [], ], ], FALSE, @@ -135,6 +146,7 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => FALSE, + 'empty' => [], ], 'f' => 'f', ], @@ -152,6 +164,24 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => FALSE, + 'empty' => [], + ], + ], + FALSE, + ], + + // 0 should not match NULL. + [ + $base, + [ + '_core' => 'foo', + 'uuid' => 'bar', + 'a' => 'a', + 'b' => NULL, + 'c' => [ + 'd' => TRUE, + 'e' => FALSE, + 'empty' => [], ], ], FALSE, @@ -168,6 +198,7 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => TRUE, 'e' => 'e', + 'empty' => [], ], ], FALSE, @@ -184,10 +215,29 @@ class ConfigDifferTest extends UnitTestCase { 'c' => [ 'd' => 'd', 'e' => FALSE, + 'empty' => [], ], ], FALSE, ], + + // Add an empty array at top, and remove at lower level. Should still + // match. + [ + $base, + [ + '_core' => 'foo', + 'uuid' => 'bar', + 'a' => 'a', + 'b' => 0, + 'c' => [ + 'd' => TRUE, + 'e' => FALSE, + ], + 'empty_two' => [], + ], + TRUE, + ], ]; } @@ -201,6 +251,7 @@ class ConfigDifferTest extends UnitTestCase { 'id_to_remove' => 'test.remove.id', 'type' => 'old_type', 'true_value' => TRUE, + 'null_value' => NULL, 'nested_array' => [ 'flat_array' => [ 'value2', @@ -216,6 +267,7 @@ class ConfigDifferTest extends UnitTestCase { 'id' => 'test.config.id', 'type' => 'new_type', 'true_value' => FALSE, + 'null_value' => FALSE, 'nested_array' => [ 'flat_array' => [ 'value2', @@ -284,12 +336,14 @@ class ConfigDifferTest extends UnitTestCase { 'orig' => [ 'nested_array::flat_array::1 : value1', 'nested_array::flat_array::2 : value3', - 'true_value : 1', + 'null_value : null', + 'true_value : true', 'type : old_type', ], 'closing' => [ 'nested_array::flat_array::1 : value3', - 'true_value : ', + 'null_value : false', + 'true_value : false', 'type : new_type', ], ], diff --git a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigListerTest.php b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigListerTest.php index b2a749335..668e8b929 100644 --- a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigListerTest.php +++ b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigListerTest.php @@ -2,169 +2,73 @@ namespace Drupal\Tests\config_update\Unit; -use Drupal\config_update\ConfigLister; -use Drupal\Tests\UnitTestCase; - /** - * Tests the \Drupal\config_update\ConfigLister class. + * Tests the \Drupal\config_update\ConfigListerWithProviders class. + * + * The methods from \Drupal\config_update\ConfigLister are also tested. * * @group config_update * - * @coversDefaultClass \Drupal\config_update\ConfigLister + * @coversDefaultClass \Drupal\config_update\ConfigListerWithProviders */ -class ConfigListerTest extends UnitTestCase { +class ConfigListerTest extends ConfigUpdateUnitTestBase { /** * The config lister to test. * - * @var \Drupal\config_update\ConfigLister + * @var \Drupal\config_update\ConfigListerWithProviders */ protected $configLister; /** - * The mocked entity definition information. + * List of configuration by provider in the mocks. * - * @var string[] + * This is an array whose keys are provider names, and whose values are + * each an array containing the provider type, the name of one config item + * mocked to be in config/install, and one in config/optional. + * + * @var array */ - protected $entityDefinitionInformation; + protected $configProviderList = [ + 'foo_module' => ['module', 'foo.barbaz.one', 'foo.barbaz.two'], + 'foo_theme' => ['theme', 'foo.bar.one', 'foo.bar.two'], + 'standard' => ['profile', 'baz.bar.one', 'baz.bar.two'], + ]; /** * {@inheritdoc} */ protected function setUp() { - $this->configLister = new ConfigLister($this->getEntityManagerMock(), $this->getConfigStorageMock('active'), $this->getConfigStorageMock('extension'), $this->getConfigStorageMock('optional')); + $lister = $this->getMockBuilder('Drupal\config_update\ConfigListerWithProviders') + ->setConstructorArgs([ + $this->getEntityManagerMock(), + $this->getConfigStorageMock('active'), + $this->getConfigStorageMock('extension'), + $this->getConfigStorageMock('optional'), + $this->getModuleHandlerMock(), + $this->getThemeHandlerMock(), + ]) + ->setMethods(['listProvidedItems', 'getProfileName']) + ->getMock(); + + $lister->method('getProfileName') + ->willReturn('standard'); + + $map = []; + foreach ($this->configProviderList as $provider => $info) { + // Info has: [type, install storage item, optional storage item]. + // Map needs: [type, provider name, isOptional, [config items]]. + $map[] = [$info[0], $provider, FALSE, [$info[1]]]; + $map[] = [$info[0], $provider, TRUE, [$info[2]]]; + } + $lister->method('listProvidedItems') + ->will($this->returnValueMap($map)); + + $this->configLister = $lister; } /** - * Creates a mock entity manager for the test. - */ - protected function getEntityManagerMock() { - // Make a list of fake entity definitions. Make sure they are not sorted, - // to test that the methods sort them. Also make sure there are a couple - // with prefixes that are subsets of each other. - $this->entityDefinitionInformation = [ - ['prefix' => 'foo.bar', 'type' => 'foo'], - ['prefix' => 'foo.barbaz', 'type' => 'bar'], - ['prefix' => 'baz.foo', 'type' => 'baz'], - ]; - - $definitions = []; - foreach ($this->entityDefinitionInformation as $info) { - $def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock(); - $def - ->expects($this->any()) - ->method('getConfigPrefix') - ->willReturn($info['prefix']); - $def - ->expects($this->any()) - ->method('isSubclassOf') - ->willReturn(TRUE); - - $def->getConfigPrefix(); - - $definitions[$info['type']] = $def; - } - - $manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock(); - $manager - ->method('getDefinitions') - ->willReturn($definitions); - - return($manager); - } - - /** - * Creates a mock config storage object for the test. - * - * @param string $type - * Type of storage object to return: 'active', 'extension', or 'optional'. - */ - protected function getConfigStorageMock($type) { - if ($type == 'active') { - $storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock(); - - // The only use of the read() method on active storage is - // with the core.extension config, to get the profile name. - $storage - ->method('read') - ->willReturn(['profile' => 'standard']); - - $map = [ - ['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']], - ['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']], - ['baz.foo'], [], - ['', - [ - 'foo.bar.one', - 'foo.bar.two', - 'foo.bar.three', - 'foo.barbaz.four', - 'foo.barbaz.five', - 'foo.barbaz.six', - 'something.else', - 'another.one', - ], - ], - ]; - - $storage - ->method('listAll') - ->will($this->returnValueMap($map)); - } - elseif ($type == 'extension') { - $storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock(); - $storage - ->method('getComponentNames') - ->willReturn([ - 'foo.bar.one' => 'ignored', - 'foo.bar.two' => 'ignored', - 'foo.bar.seven' => 'ignored', - 'foo.barnot.three' => 'ignored', - 'something.else' => 'ignored', - ]); - - $map = [ - ['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']], - ['baz.foo'], [], - ['', - [ - 'foo.bar.one', - 'foo.bar.two', - 'foo.bar.seven', - 'foo.barbaz.four', - 'foo.barnot.three', - 'something.else', - ], - ], - ]; - - $storage - ->method('listAll') - ->will($this->returnValueMap($map)); - } - else { - $storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock(); - $storage - ->method('getComponentNames') - ->willReturn([ - 'foo.barbaz.four' => 'ignored', - ]); - - $map = [ - ['foo.bar'], [], - ['foo.barbaz', ['foo.barbaz.four']], - ['', ['foo.barbaz.four']], - ]; - $storage - ->method('listAll') - ->will($this->returnValueMap($map)); - } - return $storage; - - } - - /** - * @covers \Drupal\config_update\ConfigLister::listConfig + * @covers \Drupal\config_update\ConfigListerWithProviders::listConfig * @dataProvider listConfigProvider */ public function testListConfig($a, $b, $expected) { @@ -176,12 +80,8 @@ class ConfigListerTest extends UnitTestCase { */ public function listConfigProvider() { return [ - // Arguments are $list_type, $name. - // We cannot really test the extension types here, because they rely - // on the going out to the file system to find out what config objects - // are there. This is too complex to mock. It is tested in the tests for - // the report output in the config_update_ui module tests. Anyway, we - // can test the other types. + // Arguments are $list_type, $name, and return value is that list of + // configuration in active, extension, and optional storage. ['type', 'system.all', [ [ @@ -220,11 +120,59 @@ class ConfigListerTest extends UnitTestCase { ], ], ['type', 'unknown.type', [[], [], []]], + ['profile', 'dummy', + [ + [ + 'foo.bar.one', + 'foo.bar.two', + 'foo.bar.three', + 'foo.barbaz.four', + 'foo.barbaz.five', + 'foo.barbaz.six', + 'something.else', + 'another.one', + ], + ['baz.bar.one'], + ['baz.bar.two'], + ], + ], + ['module', 'foo_module', + [ + [ + 'foo.bar.one', + 'foo.bar.two', + 'foo.bar.three', + 'foo.barbaz.four', + 'foo.barbaz.five', + 'foo.barbaz.six', + 'something.else', + 'another.one', + ], + ['foo.barbaz.one'], + ['foo.barbaz.two'], + ], + ], + ['theme', 'foo_theme', + [ + [ + 'foo.bar.one', + 'foo.bar.two', + 'foo.bar.three', + 'foo.barbaz.four', + 'foo.barbaz.five', + 'foo.barbaz.six', + 'something.else', + 'another.one', + ], + ['foo.bar.one'], + ['foo.bar.two'], + ], + ], ]; } /** - * @covers \Drupal\config_update\ConfigLister::getType + * @covers \Drupal\config_update\ConfigListerWithProviders::getType */ public function testGetType() { $return = $this->configLister->getType('not_in_list'); @@ -237,7 +185,7 @@ class ConfigListerTest extends UnitTestCase { } /** - * @covers \Drupal\config_update\ConfigLister::getTypeByPrefix + * @covers \Drupal\config_update\ConfigListerWithProviders::getTypeByPrefix */ public function testGetTypeByPrefix() { $return = $this->configLister->getTypeByPrefix('not_in_list'); @@ -250,7 +198,7 @@ class ConfigListerTest extends UnitTestCase { } /** - * @covers \Drupal\config_update\ConfigLister::getTypeNameByConfigName + * @covers \Drupal\config_update\ConfigListerWithProviders::getTypeNameByConfigName */ public function testGetTypeNameByConfigName() { $return = $this->configLister->getTypeNameByConfigName('not_in_list'); @@ -262,4 +210,84 @@ class ConfigListerTest extends UnitTestCase { } } + /** + * @covers \Drupal\config_update\ConfigListerWithProviders::listTypes + */ + public function testListTypes() { + $return = $this->configLister->listTypes(); + // Should return an array in sorted order, of just the config entities + // that $this->getEntityManagerMock() set up. + $expected = ['bar' => 'foo.barbaz', 'baz' => 'baz.foo', 'foo' => 'foo.bar']; + $this->assertEquals(array_keys($return), array_keys($expected)); + foreach ($return as $key => $definition) { + $this->assertTrue($definition->entityClassImplements('Drupal\Core\Config\Entity\ConfigEntityInterface')); + $this->assertEquals($definition->getConfigPrefix(), $expected[$key]); + } + } + + /** + * @covers \Drupal\config_update\ConfigListerWithProviders::listProviders + */ + public function testListProviders() { + // This method's return value is not sorted in any particular way. + $return = $this->configLister->listProviders(); + $expected = []; + foreach ($this->configProviderList as $provider => $info) { + // Info has: [type, install storage item, optional storage item]. + // Expected needs: key is item name, value is [type, provider name]. + $expected[$info[1]] = [$info[0], $provider]; + $expected[$info[2]] = [$info[0], $provider]; + } + ksort($return); + ksort($expected); + $this->assertEquals($return, $expected); + } + + /** + * @covers \Drupal\config_update\ConfigListerWithProviders::getConfigProvider + * @dataProvider getConfigProviderProvider + */ + public function testGetConfigProvider($a, $expected) { + $this->assertEquals($expected, $this->configLister->getConfigProvider($a)); + } + + /** + * Data provider for self:testGetConfigProvider(). + */ + public function getConfigProviderProvider() { + $values = []; + foreach ($this->configProviderList as $provider => $info) { + // Info has: [type, install storage item, optional storage item]. + // Values needs: [item, [type, provider name]]. + $values[] = [$info[1], [$info[0], $provider]]; + $values[] = [$info[2], [$info[0], $provider]]; + } + $values[] = ['not.a.config.item', NULL]; + return $values; + } + + /** + * @covers \Drupal\config_update\ConfigListerWithProviders::providerHasConfig + * @dataProvider providerHasConfigProvider + */ + public function testProviderHasConfig($a, $b, $expected) { + $this->assertEquals($expected, $this->configLister->providerHasConfig($a, $b)); + } + + /** + * Data provider for self:testProviderHasConfig(). + */ + public function providerHasConfigProvider() { + $values = []; + foreach ($this->configProviderList as $provider => $info) { + // Info has: [type, install storage item, optional storage item]. + // Values needs: [type, provider name, TRUE] for valid providers, + // change the last to FALSE for invalid providers. + $values[] = [$info[0], $provider, TRUE]; + $values[] = [$info[0], $provider . '_suffix', FALSE]; + } + $values[] = ['invalid_type', 'foo_module', FALSE]; + return $values; + } + } diff --git a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigReverterTest.php b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigReverterTest.php new file mode 100644 index 000000000..6c35cd72c --- /dev/null +++ b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigReverterTest.php @@ -0,0 +1,356 @@ +configReverter = new ConfigReverter( + $this->getEntityManagerMock(), + $this->getConfigStorageMock('active'), + $this->getConfigStorageMock('extension'), + $this->getConfigStorageMock('optional'), + $this->getConfigFactoryMock(), + $this->getEventDispatcherMock()); + } + + /** + * @covers \Drupal\config_update\ConfigReverter::getFromActive + * @dataProvider getFromActiveProvider + */ + public function testGetFromActive($a, $b, $expected) { + $this->assertEquals($expected, $this->configReverter->getFromActive($a, $b)); + } + + /** + * Data provider for self:testGetFromActive(). + */ + public function getFromActiveProvider() { + return [ + // Arguments are $type, $name, and return value is the config. + // Some config items that are already prefixed. + ['', 'foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']], + ['system.simple', 'foo.bar.one', + ['foo.bar.one' => 'active', 'id' => 'one'], + ], + // Config item with a defined entity definition prefix. Entity type 'foo' + // has prefix 'foo.bar'. + ['foo', 'one', ['foo.bar.one' => 'active', 'id' => 'one']], + // Unknown type. This should not generate a call into the config read, + // so should not return the known value. + ['unknown', 'foo.bar.one', FALSE], + // Missing configuration. Config mock is configured to return FALSE for + // this particular config name. + ['system.simple', 'missing', FALSE], + ]; + } + + /** + * @covers \Drupal\config_update\ConfigReverter::getFromExtension + * @dataProvider getFromExtensionProvider + */ + public function testGetFromExtension($a, $b, $expected) { + $this->assertEquals($expected, $this->configReverter->getFromExtension($a, $b)); + } + + /** + * Data provider for self:testGetFromExtension(). + */ + public function getFromExtensionProvider() { + return [ + // Arguments are $type, $name, and return value is the config. + // Some config items that are already prefixed, and exist in the mock + // extension storage. + ['', 'in.extension', ['in.extension' => 'extension']], + ['system.simple', 'in.extension', ['in.extension' => 'extension']], + // Config item with a defined entity definition prefix. Entity type 'foo' + // has prefix 'foo.bar'. + ['foo', 'one', ['foo.bar.one' => 'extension', 'id' => 'one']], + // One that exists in both extension and optional storage. + ['system.simple', 'in.both', ['in.both' => 'extension']], + // One that exists only in optional storage. + ['system.simple', 'in.optional', ['in.optional' => 'optional']], + // Unknown type. This should not generate a call into the config read, + // so should not return the known value. + ['unknown', 'in.extension', FALSE], + // Missing configuration. Storage mock is configured to return FALSE for + // this particular config name. + ['system.simple', 'missing2', FALSE], + ]; + } + + /** + * @covers \Drupal\config_update\ConfigReverter::import + * @dataProvider importProvider + */ + public function testImport($type, $name, $config_name, $expected, $config_before, $config_after) { + // Clear dispatch log and set pre-config. + $this->dispatchedEvents = []; + if ($config_name) { + $this->configStorage[$config_name] = $config_before; + } + $save_config = $this->configStorage; + + // Call the importer and test the Boolean result. + $result = $this->configReverter->import($type, $name); + $this->assertEquals($result, $expected); + + if ($result) { + // Verify that the config is correct after import, and logging worked. + $this->assertEquals($this->configStorage[$config_name], $config_after); + $this->assertEquals(count($this->dispatchedEvents), 1); + $this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::IMPORT); + } + else { + // Verify that the config didn't change and no events were logged. + $this->assertEquals($this->configStorage, $save_config); + $this->assertEquals(count($this->dispatchedEvents), 0); + } + } + + /** + * Data provider for self:testImport(). + */ + public function importProvider() { + return [ + // Elements: type, name, config name, return value, + // config to set up before, config expected after. See also + // getFromExtensionProvider(). + [ + 'system.simple', + 'in.extension', + 'in.extension', + TRUE, + ['in.extension' => 'before'], + ['in.extension' => 'extension', '_core' => 'core_for_in.extension'], + ], + [ + 'foo', + 'one', + 'foo.bar.one', + TRUE, + ['foo.bar.one' => 'before', 'id' => 'one'], + [ + 'foo.bar.one' => 'extension', + 'id' => 'one', + '_core' => 'core_for_foo.bar.one', + ], + ], + [ + 'system.simple', + 'in.both', + 'in.both', + TRUE, + ['in.both' => 'before'], + ['in.both' => 'extension', '_core' => 'core_for_in.both'], + ], + [ + 'system.simple', + 'in.optional', + 'in.optional', + TRUE, + ['in.optional' => 'before'], + ['in.optional' => 'optional', '_core' => 'core_for_in.optional'], + ], + [ + 'unknown', + 'in.extension', + FALSE, + FALSE, + FALSE, + FALSE, + ], + [ + 'system.simple', + 'missing2', + 'missing2', + FALSE, + FALSE, + FALSE, + ], + ]; + } + + /** + * @covers \Drupal\config_update\ConfigReverter::revert + * @dataProvider revertProvider + */ + public function testRevert($type, $name, $config_name, $expected, $config_before, $config_after) { + // Clear dispatch log and set pre-config. + $this->dispatchedEvents = []; + if ($config_name) { + $this->configStorage[$config_name] = $config_before; + } + $save_config = $this->configStorage; + + // Call the reverter and test the Boolean result. + $result = $this->configReverter->revert($type, $name); + $this->assertEquals($result, $expected); + + if ($result) { + // Verify that the config is correct after revert, and logging worked. + $this->assertEquals($this->configStorage[$config_name], $config_after); + $this->assertEquals(count($this->dispatchedEvents), 1); + $this->assertEquals($this->dispatchedEvents[0][0], ConfigRevertInterface::REVERT); + } + else { + // Verify that the config didn't change and no events were logged. + $this->assertEquals($this->configStorage, $save_config); + $this->assertEquals(count($this->dispatchedEvents), 0); + } + } + + /** + * Data provider for self:testRevert(). + */ + public function revertProvider() { + return [ + // Elements: type, name, config name, return value, + // config to set up before, config expected after. See also + // getFromExtensionProvider(). + [ + 'system.simple', + 'in.extension', + 'in.extension', + TRUE, + ['in.extension' => 'active'], + ['in.extension' => 'extension', '_core' => 'core_for_in.extension'], + ], + [ + 'foo', + 'one', + 'foo.bar.one', + TRUE, + ['foo.bar.one' => 'active', 'id' => 'one'], + [ + 'foo.bar.one' => 'extension', + 'id' => 'one', + '_core' => 'core_for_foo.bar.one', + ], + ], + [ + 'system.simple', + 'in.both', + 'in.both', + TRUE, + ['in.both' => 'active'], + ['in.both' => 'extension', '_core' => 'core_for_in.both'], + ], + [ + 'system.simple', + 'in.optional', + 'in.optional', + TRUE, + ['in.optional' => 'active'], + ['in.optional' => 'optional', '_core' => 'core_for_in.optional'], + ], + [ + 'unknown', + 'in.extension', + FALSE, + FALSE, + FALSE, + FALSE, + ], + // Missing from extension storage. + [ + 'system.simple', + 'missing2', + 'missing2', + FALSE, + FALSE, + FALSE, + ], + // Present in extension storage but missing from active storage. + [ + 'system.simple', + 'another', + 'another', + FALSE, + FALSE, + FALSE, + ], + ]; + } + + /** + * @covers \Drupal\config_update\ConfigReverter::delete + * @dataProvider deleteProvider + */ + public function testDelete($type, $name, $config_name, $expected) { + // Clear dispatch log. + $this->dispatchedEvents = []; + $save_config = $this->configStorage; + + // Call the deleteer and test the Boolean result. + $result = $this->configReverter->delete($type, $name); + $this->assertEquals($result, $expected); + + if ($result) { + // Verify that the config is missing after delete, and logging worked. + $this->assertTrue(!isset($this->configStorage[$config_name])); + $this->assertEquals(count($this->dispatchedEvents), 1); + $this->assertEquals($this->dispatchedEvents[0][0], ConfigDeleteInterface::DELETE); + } + else { + // Verify that the config didn't change and no events were logged. + $this->assertEquals($this->configStorage, $save_config); + $this->assertEquals(count($this->dispatchedEvents), 0); + } + } + + /** + * Data provider for self:testDelete(). + */ + public function deleteProvider() { + return [ + // Elements: type, name, config name, return value. + [ + 'system.simple', + 'in.extension', + 'in.extension', + TRUE, + ], + [ + 'foo', + 'one', + 'foo.bar.one', + TRUE, + ], + [ + 'unknown', + 'in.extension', + FALSE, + FALSE, + ], + [ + 'system.simple', + 'missing2', + 'missing2', + FALSE, + ], + ]; + } + +} diff --git a/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigUpdateUnitTestBase.php b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigUpdateUnitTestBase.php new file mode 100644 index 000000000..519474484 --- /dev/null +++ b/sites/all/modules/contrib/admin/config_update/tests/src/Unit/ConfigUpdateUnitTestBase.php @@ -0,0 +1,547 @@ + 'foo.bar', 'type' => 'foo'], + ['prefix' => 'foo.barbaz', 'type' => 'bar'], + ['prefix' => 'baz.foo', 'type' => 'baz'], + ]; + + /** + * Creates a mock entity manager for the test. + * + * @see ConfigUpdateUnitTestBase::entityDefinitionInformation + */ + protected function getEntityManagerMock() { + $definitions = []; + $map = []; + foreach ($this->entityDefinitionInformation as $info) { + $def = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityTypeInterface')->getMock(); + $def + ->expects($this->any()) + ->method('getConfigPrefix') + ->willReturn($info['prefix']); + $def + ->expects($this->any()) + ->method('entityClassImplements') + ->willReturn(TRUE); + + $def + ->method('getKey') + ->willReturn('id'); + + $def->getConfigPrefix(); + + $definitions[$info['type']] = $def; + $map[] = [$info['type'], FALSE, $def]; + $map[] = [$info['type'], TRUE, $def]; + } + + // Add in a content entity definition, which shouldn't be recognized by the + // config lister class. + $def = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityTypeInterface')->getMock(); + $def + ->expects($this->any()) + ->method('entityClassImplements') + ->willReturn(FALSE); + $definitions['content_entity'] = $def; + + $manager = $this->getMockBuilder('Drupal\Core\Entity\EntityTypeManagerInterface')->getMock(); + $manager + ->method('getDefinitions') + ->willReturn($definitions); + + $manager + ->method('getDefinition') + ->will($this->returnValueMap($map)); + + $manager + ->method('getStorage') + ->will($this->returnCallback([$this, 'mockGetStorage'])); + + return $manager; + } + + /** + * Mocks the getStorage() method for the entity manager. + */ + public function mockGetStorage($entity_type) { + // Figure out the config prefix for this entity type. + $prefix = ''; + foreach ($this->entityDefinitionInformation as $info) { + if ($info['type'] == $entity_type) { + $prefix = $info['prefix']; + } + } + + // This is used in ConfigReverter::import(). Although it is supposed to + // be entity storage, we'll use our mock config object instead. + return new MockConfig('', $prefix, $this); + } + + /** + * Array of active configuration information for mocking. + * + * Array structure: Each element is an array whose first element is a + * provider name, and second is an array of config items it provides. + * + * @var array + * + * @see ConfigUpdateUnitTestBase::getConfigStorageMock() + */ + protected $configStorageActiveInfo = [ + ['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.three']], + ['foo.barbaz', ['foo.barbaz.four', 'foo.barbaz.five', 'foo.barbaz.six']], + ['baz.foo', []], + ['', + [ + 'foo.bar.one', + 'foo.bar.two', + 'foo.bar.three', + 'foo.barbaz.four', + 'foo.barbaz.five', + 'foo.barbaz.six', + 'something.else', + 'another.one', + ], + ], + ]; + + /** + * Array of extension configuration information for mocking. + * + * Array structure: Each element is an array whose first element is a + * provider name, and second is an array of config items it provides. + * + * @var array + * + * @see ConfigUpdateUnitTestBase::getConfigStorageMock() + */ + protected $configStorageExtensionInfo = [ + ['foo.bar', ['foo.bar.one', 'foo.bar.two', 'foo.bar.seven']], + ['baz.foo', []], + // This next item is assumed to be element 2 of the array. If not, you + // will need to change ConfigUpdateUnitTestBase::getConfigStorageMock(). + ['', + [ + 'foo.bar.one', + 'foo.bar.two', + 'foo.bar.seven', + 'foo.barbaz.four', + 'foo.barnot.three', + 'something.else', + ], + ], + ]; + + /** + * Array of optional configuration information for mocking. + * + * Array structure: Each element is an array whose first element is a + * provider name, and second is an array of config items it provides. + * + * @var array + * + * @see ConfigUpdateUnitTestBase::getConfigStorageMock() + */ + protected $configStorageOptionalInfo = [ + ['foo.bar', []], + ['foo.barbaz', ['foo.barbaz.four']], + // This next item is assumed to be element 2 of the array. If not, you + // will need to change ConfigUpdateUnitTestBase::getConfigStorageMock(). + ['', ['foo.barbaz.four']], + ]; + + /** + * Creates a mock config storage object for the test. + * + * @param string $type + * Type of storage object to return: 'active', 'extension', or 'optional'. + * In active storage, the read() method is mocked to assume you are reading + * core.extension to get the profile name, so it returns that information. + * For extension and optional storage, the getComponentNames() method is + * mocked, and for all storages, the listAll() method is mocked. + * + * @see ConfigUpdateUnitTestBase::configStorageActiveInfo + * @see ConfigUpdateUnitTestBase::configStorageExtensionInfo + * @see ConfigUpdateUnitTestBase::configStorageOptionalInfo + */ + protected function getConfigStorageMock($type) { + if ($type == 'active') { + $storage = $this->getMockBuilder('Drupal\Core\Config\StorageInterface')->getMock(); + + // Various tests assume various values of configuration that need to be + // read from active storage. + $map = [ + ['core.extension', ['profile' => 'standard']], + ['foo.bar.one', ['foo.bar.one' => 'active', 'id' => 'one']], + ['missing', FALSE], + ['in.extension', + ['in.extension' => 'active', '_core' => 'core_for_in.extension'], + ], + ['in.both', ['in.both' => 'active']], + ['in.optional', ['in.optional' => 'active']], + ]; + $storage + ->method('read') + ->will($this->returnValueMap($map)); + + $storage + ->method('listAll') + ->will($this->returnValueMap($this->configStorageActiveInfo)); + } + elseif ($type == 'extension') { + $storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock(); + + $value = []; + foreach ($this->configStorageExtensionInfo[2][1] as $item) { + $value[$item] = 'ignored'; + } + $storage + ->method('getComponentNames') + ->willReturn($value); + + $storage + ->method('listAll') + ->will($this->returnValueMap($this->configStorageExtensionInfo)); + $map = [ + ['in.extension', ['in.extension' => 'extension']], + ['in.both', ['in.both' => 'extension']], + ['in.optional', FALSE], + ['foo.bar.one', ['foo.bar.one' => 'extension', 'id' => 'one']], + ['another', ['another' => 'extension', 'id' => 'one']], + ['missing2', FALSE], + ]; + $storage + ->method('read') + ->will($this->returnValueMap($map)); + + } + else { + $storage = $this->getMockBuilder('Drupal\Core\Config\ExtensionInstallStorage')->disableOriginalConstructor()->getMock(); + + $value = []; + foreach ($this->configStorageOptionalInfo[2][1] as $item) { + $value[$item] = 'ignored'; + } + $storage + ->method('getComponentNames') + ->willReturn($value); + + $storage + ->method('listAll') + ->will($this->returnValueMap($this->configStorageOptionalInfo)); + + $map = [ + ['in.optional', ['in.optional' => 'optional']], + ['in.both', ['in.both' => 'optional']], + ['missing2', FALSE], + ]; + $storage + ->method('read') + ->will($this->returnValueMap($map)); + } + return $storage; + + } + + /** + * Creates a mock module handler for the test. + */ + protected function getModuleHandlerMock() { + $manager = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandlerInterface')->getMock(); + $manager->method('getModuleList') + ->willReturn(['foo_module' => '', 'standard' => '']); + + return $manager; + } + + /** + * Creates a mock theme handler for the test. + */ + protected function getThemeHandlerMock() { + $manager = $this->getMockBuilder('Drupal\Core\Extension\ThemeHandlerInterface')->getMock(); + $manager->method('listInfo') + ->willReturn(['foo_theme' => '']); + return $manager; + } + + /** + * Creates a mock string translation class for the test. + */ + protected function getTranslationMock() { + $translation = $this->getMockBuilder('Drupal\Core\StringTranslation\TranslationInterface')->getMock(); + $translation + ->method('translateString') + ->will($this->returnCallback([$this, 'mockTranslate'])); + return $translation; + } + + /** + * Mocks the translateString() method for the string translation mock object. + * + * @param \Drupal\Core\StringTranslation\TranslatableMarkup $input + * Object to translate. + * + * @return string + * The untranslated string from $input. + */ + public function mockTranslate(TranslatableMarkup $input) { + return $input->getUntranslatedString(); + } + + /** + * List of mock-dispatched events. + * + * Each element of the array is the call parameters to dispatchEvent() in + * the mocked dispatch class: name and event instance. + * + * @var array + * + * @see ConfigUpdateUnitTestBase::getEventDispatcherMock() + */ + protected $dispatchedEvents = []; + + /** + * Mocks the event dispatcher service. + * + * Stores dispatched events in ConfigUpdateUnitTestBase::dispatchedEvents. + */ + protected function getEventDispatcherMock() { + $event = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); + $event + ->method('dispatch') + ->will($this->returnCallback([$this, 'mockDispatch'])); + + return $event; + } + + /** + * Mocks event dispatch. + * + * For \Symfony\Component\EventDispatcher\EventDispatchInterface::dispatch(). + */ + public function mockDispatch($name, Event $event = NULL) { + $this->dispatchedEvents[] = [$name, $event]; + } + + /** + * Mock config storage for the mock config factory. + * + * This is actually managed by the MockConfig class in this file. + * + * @var array + */ + protected $configStorage = []; + + /** + * Gets the value of the mocked config storage. + */ + public function getConfigStorage() { + return $this->configStorage; + } + + /** + * Sets the value of the mocked config storage. + */ + public function setConfigStorage($values) { + $this->configStorage = $values; + } + + /** + * Creates a mock config factory class for the test. + */ + protected function getConfigFactoryMock() { + $config = $this->getMockBuilder('Drupal\Core\Config\ConfigFactoryInterface')->getMock(); + $config + ->method('getEditable') + ->will($this->returnCallback([$this, 'mockGetEditable'])); + + return $config; + } + + /** + * Mocks the getEditable() method for the mock config factory. + * + * @param string $name + * Name of the config object to get an editable object for. + * + * @return MockConfig + * Editable mock config object. + */ + public function mockGetEditable($name) { + return new MockConfig($name, '', $this); + } + +} + +/** + * Mock class for mutable configuration, config entity, and entity storage. + */ +class MockConfig { + + /** + * Name of the config. + * + * @var string + */ + protected $name = ''; + + /** + * Prefix for the entity type being mocked, for entity storage mocking. + * + * @var string + */ + protected $entityPrefix = ''; + + /** + * Test class this comes from. + * + * @var \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase + */ + protected $test; + + /** + * Current value of the configuration. + * + * @var array + */ + protected $value = ''; + + /** + * Constructs a mock config object. + * + * @param string $name + * Name of the config that is being mocked. Can be blank. + * @param string $entity_prefix + * Prefix for the entity type that is being mocked. Often blank. + * @param \Drupal\Tests\config_update\Unit\ConfigUpdateUnitTestBase $test + * Test class this comes from. + */ + public function __construct($name, $entity_prefix, ConfigUpdateUnitTestBase $test) { + $this->name = $name; + $this->entityPrefix = $entity_prefix; + $this->test = $test; + + $storage = $test->getConfigStorage(); + if ($name && isset($storage[$name])) { + $value = $storage[$name]; + $value['is_new'] = FALSE; + } + else { + $value['is_new'] = TRUE; + } + $value['_core'] = 'core_for_' . $name; + + $this->value = $value; + } + + /** + * Gets a component of the configuration value. + */ + public function get($key) { + return isset($this->value[$key]) ? $this->value[$key] : NULL; + } + + /** + * Sets a component of the configuration value. + */ + public function set($key, $value) { + $this->value[$key] = $value; + return $this; + } + + /** + * Sets the entire configuration value. + */ + public function setData($value) { + // Retain the _core key. + $core = isset($this->value['_core']) ? $this->value['_core'] : ''; + $this->value = $value; + if ($core) { + $this->value['_core'] = $core; + } + return $this; + } + + /** + * Saves the configuration. + */ + public function save() { + $config = $this->test->getConfigStorage(); + $config[$this->name] = $this->value; + $this->test->setConfigStorage($config); + return $this; + } + + /** + * Deletes the configuration. + */ + public function delete() { + $config = $this->test->getConfigStorage(); + unset($config[$this->name]); + $this->test->setConfigStorage($config); + return $this; + } + + /** + * Mocks the createFromStorageRecord() method from entity storage. + */ + public function createFromStorageRecord($values) { + if (!$this->entityPrefix) { + return NULL; + } + + // This is supposed to return an entity, but the only method we need is + // save(), so instead set up and return this object. + $this->name = $this->entityPrefix . '.' . $values['id']; + $this->value = $values; + $this->value['_core'] = 'core_for_' . $this->name; + return $this; + } + + /** + * Mocks the updateFromStorageRecord() method from entity storage. + */ + public function updateFromStorageRecord($object, $values) { + return $object->createFromStorageRecord($values); + } + + /** + * Mocks the load() method for entity storage. + */ + public function load($id) { + $full_name = $this->entityPrefix . '.' . $id; + $configs = $this->test->getConfigStorage(); + if (isset($configs[$full_name])) { + $this->value = $configs[$full_name]; + $this->name = $full_name; + $this->value['_core'] = 'core_for_' . $full_name; + return $this; + } + return NULL; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/.travis.yml b/sites/all/modules/contrib/admin/domain/.travis.yml index 48b918847..8e82646e4 100644 --- a/sites/all/modules/contrib/admin/domain/.travis.yml +++ b/sites/all/modules/contrib/admin/domain/.travis.yml @@ -4,21 +4,13 @@ sudo: false matrix: fast_finish: true include: - - env: DRUPAL=8.5.x - php: 5.5 - - env: DRUPAL=8.5.x - php: 5.6 - - env: DRUPAL=8.5.x + - env: DRUPAL=8.6.x php: 7.1 - - env: DRUPAL=8.5.x + - env: DRUPAL=8.6.x php: 7.2 - - env: DRUPAL=8.6.x - php: 5.5 - - env: DRUPAL=8.6.x - php: 5.6 - - env: DRUPAL=8.6.x + - env: DRUPAL=8.7.x php: 7.1 - - env: DRUPAL=8.6.x + - env: DRUPAL=8.7.x php: 7.2 addons: @@ -94,11 +86,11 @@ before_script: - until curl -s example.com:8080; do true; done > /dev/null script: - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content - - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 4 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_access + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Kernel" --php `which php` --url http://example.com:8080 domain_alias + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_config + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_content + - php core/scripts/run-tests.sh --suppress-deprecations --verbose --color --concurrency 20 --types "PHPUnit-Functional" --php `which php` --url http://example.com:8080 domain_source diff --git a/sites/all/modules/contrib/admin/domain/CHANGELOG.md b/sites/all/modules/contrib/admin/domain/CHANGELOG.md index 7c83d7ace..37be56dfc 100644 --- a/sites/all/modules/contrib/admin/domain/CHANGELOG.md +++ b/sites/all/modules/contrib/admin/domain/CHANGELOG.md @@ -16,6 +16,7 @@ Changelog 12-FEB-2018 8.x-1.0-alpha12 07-MAR-2018 8.x-1.0-alpha13 19-OCT-2018 8.x-1.0-alpha14 +21-FEB-2019 8.x-1.0-alpha15 Status ==== @@ -113,7 +114,7 @@ marked with [x] are considered complete. - [x] Check domain responses on configuration forms - [x] Remove deprecated `entity_get_form_display` - [x] Implement theme functions or twig templates where proper -- [ ] Advanced drush integration / complete labelled tasks +- [x] Advanced drush integration / complete labelled tasks - [ ] Add filter options to domain_access and domain_source views - [ ] Test cron handling - [ ] Caching strategies in DomainNegotiator diff --git a/sites/all/modules/contrib/admin/domain/README.md b/sites/all/modules/contrib/admin/domain/README.md index abd5f4c79..691d60bba 100644 --- a/sites/all/modules/contrib/admin/domain/README.md +++ b/sites/all/modules/contrib/admin/domain/README.md @@ -199,23 +199,16 @@ a beta release on Drupal.org. We would like to tackle issues in that order, but feel free to work on what motivates you. -Testing +Testing [![Build Status](https://travis-ci.com/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.com/agentrickard/domain) ==== @zerolab built a Travis definition file for automated testing! That means all pull requests will automatically run tests! -[![Build Status](https://travis-ci.org/agentrickard/domain.svg?branch=8.x-1.x)](https://travis-ci.org/agentrickard/domain) - -The module does have solid test coverage, and complete coverage is required for release. -Right now, we mostly use SimpleTest, because it is most familiar, and much of our -testing is about browser and http behavior. - If you file a pull request or patch, please (at a minimum) run the existing tests to check for failures. Writing additional tests will greatly speed completion, as I won't commit code without test coverage. -New tests should be written as Functional, Kernel, or Unit tests. Conversion patches that -move Simpletests to Functional tests are welcome. +New tests should be written in PHPUnit as Functional, Kernel, or Unit tests. Because Domain requires varying http host requests to test, we can't normally use the Drupal.org testing infrastructure. (This may change, but we are not counting on it.) @@ -227,5 +220,3 @@ point to your drupal instance. I use variants of `example.com` for local tests. in most test cases. See `DomainTestBase::domainCreateTestDomains()` for the logic. When running tests, you normally need to be on the default domain. - -If anyone is capable of building a vagrant box to simplify testing, that would be ideal. diff --git a/sites/all/modules/contrib/admin/domain/composer.json b/sites/all/modules/contrib/admin/domain/composer.json index c5d5ff1d6..1613e13c2 100644 --- a/sites/all/modules/contrib/admin/domain/composer.json +++ b/sites/all/modules/contrib/admin/domain/composer.json @@ -13,5 +13,14 @@ "issues": "http://drupal.org/project/issues/domain", "irc": "http://irc.freenode.org/drupal-contribute", "source": "http://cgit.drupalcode.org/domain" + }, + "extra": { + "drush": { + "services": { + "domain/drush.services.yml": "^9", + "domain_access/drush.services.yml": "^9", + "domain_source/drush.services.yml": "^9" + } + } } } diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.info.yml b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml index cad33c02f..190ac69b3 100644 --- a/sites/all/modules/contrib/admin/domain/domain/domain.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain/domain.info.yml @@ -9,8 +9,8 @@ dependencies: - drupal:options configure: domain.admin -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain/domain.module b/sites/all/modules/contrib/admin/domain/domain/domain.module index b2de29eaf..960674976 100644 --- a/sites/all/modules/contrib/admin/domain/domain/domain.module +++ b/sites/all/modules/contrib/admin/domain/domain/domain.module @@ -159,3 +159,19 @@ function domain_theme() { function template_preprocess_domain_nav_block(array &$variables) { $variables['items'] = $variables['items']['#items']; } + +/** + * Implements hook_hook_info(). + */ +function domain_hook_info() { + $hooks['domain_request_alter'] = [ + 'group' => 'domain', + ]; + $hooks['domain_validate_alter'] = [ + 'group' => 'domain', + ]; + $hooks['domain_references_alter'] = [ + 'group' => 'domain', + ]; + return $hooks; +} diff --git a/sites/all/modules/contrib/admin/domain/domain/drush.services.yml b/sites/all/modules/contrib/admin/domain/domain/drush.services.yml new file mode 100644 index 000000000..84d23f785 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/drush.services.yml @@ -0,0 +1,5 @@ +services: + domain.commands: + class: \Drupal\domain\Commands\DomainCommands + tags: + - { name: drush.command } diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php index c74e2ce71..0a48aec4f 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/Access/DomainAccessCheck.php @@ -75,17 +75,17 @@ class DomainAccessCheck implements AccessCheckInterface { // Is the domain allowed? // No domain, let it pass. if (empty($domain)) { - return AccessResult::allowed()->setCacheMaxAge(0); + return AccessResult::allowed()->addCacheTags(['url.site']); } // Active domain, let it pass. if ($domain->status()) { - return AccessResult::allowed()->setCacheMaxAge(0); + return AccessResult::allowed()->addCacheTags(['url.site']); } // Inactive domain, require permissions. else { $permissions = ['administer domains', 'access inactive domains']; $operator = 'OR'; - return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->setCacheMaxAge(0); + return AccessResult::allowedIfHasPermissions($account, $permissions, $operator)->addCacheTags(['url.site']); } } diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Commands/DomainCommandException.php b/sites/all/modules/contrib/admin/domain/domain/src/Commands/DomainCommandException.php new file mode 100644 index 000000000..0ab54726b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain/src/Commands/DomainCommandException.php @@ -0,0 +1,10 @@ +domainStorage()->loadMultipleSorted(); + + if (empty($domains)) { + $this->logger()->warning(dt('No domains have been created. Use "drush domain:add" to create one.')); + return new RowsOfFields([]); + } + + $keys = [ + 'weight', + 'name', + 'hostname', + 'response', + 'scheme', + 'status', + 'is_default', + 'domain_id', + 'id', + ]; + $rows = []; + /** @var \Drupal\domain\DomainInterface $domain */ + foreach ($domains as $domain) { + $row = []; + foreach ($keys as $key) { + switch($key) { + case 'response': + try { + $v = $this->checkDomain($domain); + } + catch(\GuzzleHttp\Exception\TransferException $ex) { + $v = dt('500 - Failed'); + } + catch(Exception $ex) { + $v = dt('500 - Exception'); + } + if ($v >= 200 && $v <= 299) { + $v = dt('200 - OK'); + } + elseif ($v == 500) { + $v = dt('500 - No server'); + } + break; + case 'status': + $v = $domain->get($key); + if (($options['inactive'] && $v) || ($options['active'] && !$v)) { + continue 3; // switch, for, for + } + $v = !empty($v) ? dt('Active') : dt('Inactive'); + break; + case 'is_default': + $v = $domain->get($key); + $v = !empty($v) ? dt('Default') : ''; + break; + default: + $v = $domain->get($key); + break; + } + + $row[$key] = Html::escape($v); + } + $rows[] = $row; + } + return new RowsOfFields($rows); + } + + /** + * List general information about the domains on the site. + * + * @usage drush domain:info + * + * @command domain:info + * @aliases domain-info,dinf + * + * @return \Consolidation\OutputFormatters\StructuredData\PropertyList + * @field-labels + * count: All Domains + * count_active: Active Domains + * default_id: Default Domain ID + * default_host: Default Domain hostname + * scheme: Fields in Domain entity + * domain_admin_entities: Domain admin entities + * @list-orientation true + * @format table + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function infoDomains() { + $default_domain = $this->domainStorage()->loadDefaultDomain(); + + // Load all domains: + $all_domains = $this->domainStorage()->loadMultiple(NULL); + $active_domains = []; + foreach ($all_domains as $domain) { + if ($domain->status()) { + $active_domains[] = $domain; + } + } + + $keys = [ + 'count', + 'count_active', + 'default_id', + 'default_host', + 'scheme', + ]; + $rows = []; + foreach ($keys as $key) { + $v = ''; + switch($key) { + case 'count': + $v = count($all_domains); + break; + case 'count_active': + $v = count($active_domains); + break; + case 'default_id': + $v = '-unset-'; + if ($default_domain) { + $v = $default_domain->id(); + } + break; + case 'default_host': + $v = '-unset-'; + if ($default_domain) { + $v = $default_domain->getHostname(); + } + break; + case 'scheme': + $v = implode(', ', array_keys($this->domainStorage()->loadSchema())); + break; + } + + $rows[$key] = $v; + } + + + // Display which entities are enabled for domain by checking for the fields. + $rows['domain_admin_entities'] = $this->getFieldEntities(DOMAIN_ADMIN_FIELD); + + return new PropertyList($rows); + } + + /** + * Finds entities that reference a specific field. + * + * @param $field_name + * The field name to lookup. + */ + public function getFieldEntities($field_name) { + $entity_manager = \Drupal::entityManager(); + $field_map = $entity_manager->getFieldMap(); + $domain_entities = []; + foreach($field_map as $type => $fields) { + if (array_key_exists($field_name, $fields)) { + $domain_entities[] = $type; + } + } + return implode(', ', $domain_entities); + } + + /** + * Add a new domain to the site. + * + * @param $hostname + * The domain hostname to register (e.g. example.com). + * @param $name + * The name of the site (e.g. Domain Two). + * @param array $options An associative array of optional values. + * + * @option inactive + * Set the domain to inactive status if set. + * @option scheme + * Use indicated protocol for this domain, defaults to 'https'. Options: + * - http: normal http (no SSL). + * - https: secure https (with SSL). + * - variable: match the scheme used by the request. + * @option weight + * Set the order (weight) of the domain. + * @option is_default + * Set this domain as the default domain. + * @option validate + * Force a check of the URL response before allowing registration. + * + * @usage drush domain-add example.com 'My Test Site' + * @usage drush domain-add example.com 'My Test Site' --scheme=https --inactive + * @usage drush domain-add example.com 'My Test Site' --weight=10 + * @usage drush domain-add example.com 'My Test Site' --validate + * + * @command domain:add + * @aliases domain-add + * + * @return string + * The entity id of the created domain. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function add($hostname, $name, array $options = ['weight' => null, 'scheme' => null]) { + // Validate the weight arg. + if (!empty($options['weight']) && !is_numeric($options['weight'])) { + throw new DomainCommandException( + dt('Domain weight "!weight" must be a number', + ['!weight' => !empty($options['weight']) ? $options['weight'] : '']) + ); + } + + // Validate the scheme arg. + if (!empty($options['scheme']) && + ($options['scheme'] !== 'http' && $options['scheme'] !== 'https' && $options['scheme'] !== 'variable') + ) { + throw new DomainCommandException( + dt('Scheme name "!scheme" not known', + ['!scheme' => !empty($options['scheme']) ? $options['scheme'] : '']) + ); + } + + $domains = $this->domainStorage()->loadMultipleSorted(); + $start_weight = count($domains) + 1; + $values = [ + 'hostname' => $hostname, + 'name' => $name, + 'status' => empty($options['inactive']), + 'scheme' => empty($options['scheme']) ? 'http' : $options['scheme'], + 'weight' => empty($options['weight']) ? $start_weight : $options['weight'], + 'is_default' => !empty($options['is_default']), + 'id' => $this->domainStorage()->createMachineName($hostname), + ]; + /** @var DomainInterface $domain */ + $domain = $this->domainStorage()->create($values); + + // Check for hostname validity. This is required. + $valid = $this->validateDomain($domain); + if (!empty($valid)) { + throw new DomainCommandException( + dt('Hostname is not valid. !errors', + ['!errors' => implode(" ", $valid)]) + ); + } + // Check for hostname and id uniqueness. + foreach ($domains as $existing) { + if ($hostname == $existing->getHostname()) { + throw new DomainCommandException( + dt('No domain created. Hostname is a duplicate of !hostname.', + ['!hostname' => $hostname]) + ); + } + if ($values['id'] == $existing->id()) { + throw new DomainCommandException( + dt('No domain created. Id is a duplicate of !id.', + ['!id' => $existing->id()]) + ); + } + } + + $validate_response = (bool) $options['validate']; + if ($this->createDomain($domain, $validate_response)) { + return dt('Created the !hostname with machine id !id.', ['!hostname' => $values['hostname'], '!id' => $values['id']]); + } + else { + return dt('No domain created.'); + } + } + + /** + * Delete a domain from the site. + * + * Deletes the domain from the Drupal configuration and optionally reassign + * content and/or profiles associated with the deleted domain to another. + * The domain marked as default cannot be deleted: to achieve this goal, + * mark another, possibly newly created, domain as the default domain, then + * delete the old default. + * + * The usage example descriptions are based on starting with three domains: + * - id:19476, machine: example_com, domain: example.com + * - id:29389, machine: example_org, domain: example.org (default) + * - id:91736, machine: example_net, domain: example.net + * + * @param $domain_id + * The numeric id, machine name, or hostname of the domain to delete. The + * value "all" is taken to mean delete all except the default domain. + * @param array $options + * An associative array of options whose values come from cli, aliases, + * config, etc. + * + * @usage drush domain:delete example.com + * Delete the domain example.com, assigning its content and users to + * the default domain, example.org. + * + * @usage drush domain:delete --content-assign=ignore example.com + * Delete the domain example.com, leaving its content untouched but + * assigning its users to the default domain. + * + * @usage drush domain:delete --content-assign=example_net --users-assign=example_net + * example.com Delete the domain example.com, assigning its content and + * users to the example.net domain. + * + * @usage drush domain:delete --dryrun 19476 + * Show the effects of delete the domain example.com and assigning its + * content and users to the default domain, example.org, but not doing so. + * + * @usage drush domain:delete --chatty example_net + * Verbosely Delete the domain example.net and assign its content and users + * to the default domain, example.org. + * + * @usage drush domain-delete --chatty all + * Verbosely Delete the domains example.com and example.net and assign + * their content and users to the default domain, example.org. + * + * @option chatty + * Document each step as it is performed. + * @option dryrun + * Do not do anything, but explain what would be done. Implies --chatty. + * @option users-assign + * Values "prompt", "ignore", "default", , Reassign user accounts + * associated with the the domain being deleted to the default domain, + * to the domain whose machine name is , or leave the user accounts + * alone (and so inaccessible in the normal way). The default value is + * 'prompt': ask which domain to use. + * + * @command domain:delete + * @aliases domain-delete + * + * @throws \Drupal\domain\Commands\DomainCommandException + * + * @see https://github.com/consolidation/annotated-command#option-event-hook + */ + public function delete($domain_id, $options = ['users-assign' => null, 'dryrun' => null, 'chatty' => null]) { + if (is_null($options['users-assign'])) { + $policy_users = 'prompt'; + } + + $this->is_dry_run = (bool) $options['dryrun']; + + // Get current domain list and perform validation checks. + $default_domain = $this->domainStorage()->loadDefaultDomain(); + $all_domains = $this->domainStorage()->loadMultipleSorted(NULL); + + if (empty($all_domains)) { + throw new DomainCommandException('There are no configured domains.'); + } + if (empty($domain_id)) { + throw new DomainCommandException('You must specify a domain to delete.'); + } + + // Determine which domains to be deleted. + if ($domain_id === 'all') { + $domains = $all_domains; + if (empty($domains)) { + $this->logger()->info(dt('There are no domains to delete.')); + return; + } + $really = $this->io()->confirm(dt('This action cannot be undone. Continue?:'), FALSE); + if (empty($really)) { + return; + } + // TODO: handle deletion of all domains. + } + elseif ($domain = $this->getDomainFromArgument($domain_id)) { + if ($domain->isDefault()) { + throw new DomainCommandException('The primary domain may not be deleted. + Use drush domain:default to set a new default domain.'); + } + $domains = [$domain]; + } + + if (!empty($options['users-assign'])) { + if (in_array($options['users-assign'], $this->reassignment_policies, TRUE)) { + $policy_users = $options['users-assign']; + } + } + + $delete_options = [ + 'entity_filter' => 'user', + 'policy' => $policy_users, + 'field' => DOMAIN_ADMIN_FIELD, + ]; + + if ($policy_users !== 'ignore') { + $messages[] = $this->doReassign($domain, $delete_options); + } + + // Fire any registered hooks for deletion, passing them current imput. + $handlers = $this->getCustomEventHandlers('domain-delete'); + $messages = []; + foreach ($handlers as $handler) { + $messages[] = $handler($domain, $options); + } + + $this->deleteDomain($domains, $options); + + $message = dt('Domain record !domain deleted.', ['!domain' => $domain->id()]); + if ($messages) { + $message .= "\n" . implode("\n", $messages); + } + $this->logger()->info($message); + return $message; + } + + /** + * Handles reassignment of entities to another domain. + * + * This method includes necessary UI elements if the user is prompted to + * choose a new domain. + * + * @param Drupal\domain\DomainInterface $target_domain + * The domain selected for deletion. + * @param array $delete_options + * A selection of options for deletion, defined in reassignLinkedEntities(). + */ + public function doReassign(DomainInterface $target_domain, array $delete_options) { + $policy = $delete_options['policy']; + $default_domain = $this->domainStorage()->loadDefaultDomain(); + $all_domains = $this->domainStorage()->loadMultipleSorted(NULL); + + // Perform the 'prompt' for a destination domain. + if ($policy === 'prompt') { + // Make a list of the eligible destination domains in form id -> name. + $noassign_domain = [$target_domain->id()]; + + $reassign_list = $this->filterDomains($all_domains, $noassign_domain); + $reassign_base = [ + 'ignore' => dt('Do not reassign'), + 'default' => dt('Reassign to default domain'), + ]; + $reassign_list = array_map( + function (DomainInterface $d) { + return $d->getHostname(); + }, + $reassign_list + ); + $reassign_list = array_merge($reassign_base, $reassign_list); + $policy = $this->io()->choice(dt('Reassign @type field @field data to:', ['@type' => $delete_options['entity_filter'], '@field' => $delete_options['field']]), $reassign_list); + } + elseif ($policy === 'default') { + $policy = $default_domain->id(); + } + if ($policy !== 'ignore') { + $delete_options['policy'] = $policy; + $target = [$target_domain]; + $count = $this->reassignLinkedEntities($target, $delete_options); + return dt('@count @type entities updated field @field.', ['@count' => $count, '@type' => $delete_options['entity_filter'], '@field' => $delete_options['field']]); + } + } + + /** + * Tests domains for proper response. + * + * If run from a subfolder, you must specify the --uri. + * + * @param $domain_id + * The machine name or hostname of the domain to make default. + * + * @usage drush domain-test + * @usage drush domain-test example.com + * + * @command domain:test + * @aliases domain-test + * + * @field-labels + * id: Machine name + * url: URL + * response: HTTP Response + * @default-fields id,url,response + * + * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function test($domain_id = null) { + if (is_null($domain_id)) { + $domains = $this->domainStorage()->loadMultipleSorted(); + } + else { + if ($domain = $this->getDomainFromArgument($domain_id)) { + $domains = [$domain]; + } + else { + throw new DomainCommandException(dt('Domain @domain not found.', + ['@domain' => $options['domain']])); + } + } + $keys = ['url', 'response']; + $rows = []; + foreach ($domains as $domain) { + $rows[] = [ + 'id' => $domain->id(), + 'url' => $domain->getPath(), + 'response' => $domain->getResponse(), + ]; + } + return new RowsOfFields($rows); + } + + /** + * Sets the default domain. + * + * @param $domain_id + * The machine name or hostname of the domain to make default. + * @param array $options + * An associative array of options whose values come from cli, aliases, + * config, etc. + * @option validate + * Force a check of the URL response before allowing registration. + * @usage drush domain-default www.example.com + * @usage drush domain-default example_org + * @usage drush domain-default www.example.org --validate=1 + * + * @command domain:default + * @aliases domain-default + * + * @return string + * The machine name of the default domain. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function defaultDomain($domain_id, array $options = ['validate' => null]) { + // Resolve the domain. + if (!empty($domain_id) && $domain = $this->getDomainFromArgument($domain_id)) { + $validate = ($options['validate']) ? 1 : 0; + $domain->addProperty('validate_url', $validate); + if ($error = $this->checkHTTPResponse($domain)) { + throw new DomainCommandException(dt('Unable to verify domain !domain: !error', + ['!domain' => $domain->getHostname(), '!error' => $error])); + } + else { + $domain->saveDefault(); + } + } + + // Now, ask for the current default, so we know if it worked. + $domain = $this->domainStorage()->loadDefaultDomain(); + if ($domain->status()) { + $this->logger()->info(dt('!domain set to primary domain.', + ['!domain' => $domain->getHostname()])); + } + else { + $this->logger()->warning(dt('!domain set to primary domain, but is also inactive.', + ['!domain' => $domain->getHostname()])); + } + return $domain->id(); + } + + /** + * Deactivates the domain. + * + * @param $domain_id + * The numeric id or hostname of the domain to disable. + * @usage drush domain-disable example.com + * @usage drush domain-disable 1 + * + * @command domain:disable + * @aliases domain-disable + * + * @return string + * 'disabled' if the domain is now disabled. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function disable($domain_id) { + // Resolve the domain. + if ($domain = $this->getDomainFromArgument($domain_id)) { + if ($domain->status()) { + $domain->disable(); + $this->logger()->info(dt('!domain has been disabled.', + ['!domain' => $domain->getHostname()])); + return dt('Disabled !domain.', ['!domain' => $domain->getHostname()]); + } + else { + $this->logger()->info(dt('!domain is already disabled.', + ['!domain' => $domain->getHostname()])); + return dt('!domain is already disabled.', ['!domain' => $domain->getHostname()]); + } + } + return dt('No matching domain record found.'); + } + + /** + * Activates the domain. + * + * @param $domain_id + * The numeric id or hostname of the domain to enable. + * @usage drush domain-disable example.com + * @usage drush domain-enable 1 + * + * @command domain:enable + * @aliases domain-enable + * + * @return string + * 'enabled' if the domain is now enabled. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function enable($domain_id) { + // Resolve the domain. + if ($domain = $this->getDomainFromArgument($domain_id)) { + if (!$domain->status()) { + $domain->enable(); + $this->logger()->info(dt('!domain has been enabled.', + ['!domain' => $domain->getHostname()])); + return dt('Enabled !domain.', ['!domain' => $domain->getHostname()]); + } + else { + $this->logger()->info(dt('!domain is already enabled.', + ['!domain' => $domain->getHostname()])); + return dt('!domain is already enabled.', ['!domain' => $domain->getHostname()]); + } + } + return dt('No matching domain record found.'); + } + + /** + * Changes a domain label. + * + * @param $domain_id + * The machine name or hostname of the domain to relabel. + * @param $name + * The name to use for the domain. + * @usage drush domain-name example.com Foo + * @usage drush domain-name 1 Foo + * + * @command domain:name + * @aliases domain-name + * + * @return string + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function renameDomain($domain_id, $name) { + // Resolve the domain. + if ($domain = $this->getDomainFromArgument($domain_id)) { + $domain->saveProperty('name', $name); + return dt('Renamed !domain to !name.', ['!domain' => $domain->getHostname(), '!name' => $domain->label()]); + } + return dt('No matching domain record found.'); + } + + /** + * Changes a domain scheme. + * + * @param $domain_id + * The machine name or hostname of the domain to change. + * @param $scheme + * The scheme to use for the domain: http, https, or variable. + * + * @usage drush domain-scheme example.com http + * @usage drush domain-scheme example_com https + * + * @command domain:scheme + * @aliases domain-scheme + * + * @return string + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function scheme($domain_id, $scheme = null) { + $new_scheme = null; + + // Resolve the domain. + if ($domain = $this->getDomainFromArgument($domain_id)) { + if (!empty($scheme)) { + // --set with a value + $new_scheme = $scheme; + } + else { + // Prompt for selection. + $new_scheme = $this->io()->choice(dt('Select the default http scheme:'), + [ + 'http' => 'http', + 'https' => 'https', + 'variable' => 'variable', + ]); + } + + // If we were asked to change scheme, validate the value and do so. + if (!empty($new_scheme)) { + switch ($new_scheme) { + case 'http': + $new_scheme = 'http'; + break; + + case 'https': + $new_scheme = 'https'; + break; + + case 'variable': + $new_scheme = 'variable'; + break; + + default: + throw new DomainCommandException( + dt('Scheme name "!scheme" not known', ['!scheme' => $new_scheme]) + ); + } + $domain->saveProperty('scheme', $new_scheme); + } + + // Either way, return the (new | current) scheme for this domain. + return dt('Scheme is now to "!scheme" for !domain', ['!scheme' => $domain->get('scheme'),'!domain' => $domain->id()]); + } + + // We couldn't find the domain - so fail. + throw new DomainCommandException( + dt('Domain name "!domain" not known', ['!domain' => $domain_id]) + ); + } + + /** + * Generate domains for testing. + * + * @param $primary + * The primary domain to use. This will be created and used for + * *.example.com hostnames. + * @param array $options + * An associative array of options whose values come from cli, aliases, + * config, etc. + * @option count + * The count of extra domains to generate. Default is 15. + * @option empty + * Pass empty=1 to truncate the {domain} table before creating records. + * @usage drush domain-generate example.com + * @usage drush domain-generate example.com --count=25 + * @usage drush domain-generate example.com --count=25 --empty=1 + * @usage drush gend + * @usage drush gend --count=25 + * @usage drush gend --count=25 --empty=1 + * + * @command domain:generate + * @aliases gend,domgen,domain-generate + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + public function generate($primary = 'example.com', array $options = ['count' => null, 'empty' => null]) { + // Check the number of domains to create. + $count = $options['count']; + if (is_null($count)) { + $count = 15; + } + + $domains = $this->domainStorage()->loadMultiple(NULL); + if (!empty($options['empty'])) { + $this->domainStorage()->delete($domains); + $domains = $this->domainStorage()->loadMultiple(NULL); + } + // Ensure we don't duplicate any domains. + $existing = []; + if (!empty($domains)) { + /** @var DomainInterface $domain */ + foreach ($domains as $domain) { + $existing[] = $domain->getHostname(); + } + } + // Set up one.* and so on. + $names = [ + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'foo', + 'bar', + 'baz', + ]; + // Set the creation array. + $new = [$primary]; + foreach ($names as $name) { + $new[] = $name . '.' . $primary; + } + // Include a non hostname. + $new[] = 'my' . $primary; + // Filter against existing so we can count correctly. + $prepared = []; + foreach ($new as $key => $value) { + if (!in_array($value, $existing, true)) { + $prepared[] = $value; + } + } + + // Add any test domains that have numeric prefixes. We don't expect these URLs to work, + // and mainly use these for testing the user interface. + // Test that we already have test domains. + $start = 1; + foreach ($existing as $exists) { + $name = explode('.', $exists); + if (substr_count($name[0], 'test') > 0) { + $num = (int) str_replace('test', '', $name[0]) + 1; + if ($num > $start) { + $start = $num; + } + } + } + $needed = $count - count($prepared) + $start; + for ($i = $start; $i <= $needed; $i++) { + $prepared[] = 'test' . $i . '.' . $primary; + } + // Get the initial item weight for sorting. + $start_weight = count($domains); + $prepared = array_slice($prepared, 0, $count); + $list = []; + + // Create the domains. + foreach ($prepared as $key => $item) { + $hostname = mb_strtolower($item); + $values = [ + 'name' => ($item != $primary) ? ucwords(str_replace(".$primary", '', $item)) : \Drupal::config('system.site')->get('name'), + 'hostname' => $hostname, + 'scheme' => 'http', + 'status' => 1, + 'weight' => ($item != $primary) ? $key + $start_weight + 1 : -1, + 'is_default' => 0, + 'id' => $this->domainStorage()->createMachineName($hostname), + ]; + $domain = $this->domainStorage()->create($values); + $domain->save(); + $list[] = dt('Created @domain.', ['@domain' => $domain->getHostname()]); + } + + // If nothing created, say so. + if (empty($prepared)) { + return dt('No new domains were created.'); + } + else { + return dt("Created @count new domains:\n@list", ['@count' => count($prepared), '@list' => implode("\n", $list)]); + } + } + + /** + * Gets a domain storage object or throw an exception. + * + * Note that domain can run very early in the bootstrap, so we cannot + * reliably inject this service. + * + * @return DomainStorageInterface + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + protected function domainStorage() { + if (!is_null($this->domain_storage)) { + return $this->domain_storage; + } + + try { + $this->domain_storage = \Drupal::entityTypeManager()->getStorage('domain'); + } + catch (PluginNotFoundException $e) { + throw new DomainCommandException('Unable to get domain: no storage', $e); + } + catch (InvalidPluginDefinitionException $e) { + throw new DomainCommandException('Unable to get domain: bad storage', $e); + } + + return $this->domain_storage; + } + + /** + * Loads a domain based on a string identifier. + * + * @param string $argument + * The machine name or the hostname of an existing domain. + * + * @return \Drupal\domain\DomainInterface + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + protected function getDomainFromArgument($argument) { + + // Try loading domain assuming arg is a machine name. + $domain = $this->domainStorage()->load($argument); + if (!$domain) { + // Try loading assuming it is a host name. + $domain = $this->domainStorage()->loadByHostname($argument); + } + + // domain_id (an INT) is only used internally because the Node Access + // system demands the use of numeric keys. It should never be used to load + // or identify domain records. Use the machine_name or hostname instead. + if (!$domain) { + throw new DomainCommandException( + dt('Domain record could not be found from "!a".', ['!a' => $argument]) + ); + } + + return $domain; + } + + /** + * Filters a list of domains by excluding domains appearing in a specific list. + * + * @param DomainInterface[] $domains + * List of domains. + * @param string[] $exclude + * List of domain id to exclude from the list. + * @param DomainInterface[] $initial + * Initial value of list that will be returned. + * + * @return array + */ + protected function filterDomains(array $domains, array $exclude, array $initial = []) { + foreach ($domains as $domain) { + // Exclude unwanted domains. + if (!in_array($domain->id(), $exclude, FALSE)) { + $initial[$domain->id()] = $domain; + } + } + return $initial; + } + + /** + * Checks the domain response. + * + * @param \Drupal\domain\DomainInterface $domain + * The domain to check. + * @param bool $validate_url + * True to validate this domain by performing a URL lookup; False to skip + * the checks. + * + * @return bool + * True if the domain resolves properly, or we are not checking, + * False otherwise. + */ + protected function checkHTTPResponse(DomainInterface $domain, $validate_url = FALSE) { + // Ensure the url is rebuilt. + if ($validate_url) { + $code = $this->checkDomain($domain); + + // Some sort of success: + return $code >= 200 && $code <= 299; + } + // Not validating, so all is well! + return FALSE; + } + + /** + * Helper function: check a domain is responsive and create it. + * + * @param DomainInterface $domain + * The (as yet unsaved) domain to create. + * @param bool $check_response + * Indicates that registration should not be allowed unless the server + * returns a 200 response. + * + * @return bool + * TODO: stndardize this return so we can issue good messages. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + protected function createDomain(DomainInterface $domain, $check_response = FALSE) { + if ($check_response) { + $valid = $this->checkHTTPResponse($domain, TRUE); + if (!$valid) { + throw new DomainCommandException( + dt('The server did not return a 200 response for !d. Domain creation failed. Remove the --validate flag to save this domain.', ['!d' => $domain->getHostname()]) + ); + } + } + else { + try { + $domain->save(); + } + catch (EntityStorageException $e) { + throw new DomainCommandException('Unable to save domain', $e); + } + + if ($domain->getDomainId()) { + $this->logger()->info(dt('Created @name at @domain.', + ['@name' => $domain->label(), '@domain' => $domain->getHostname()])); + return TRUE; + } + else { + $this->logger()->error(dt('The request could not be completed.')); + } + } + return FALSE; + } + + /** + * Checks a domain exists by trying to do an http request to it. + * + * @param DomainInterface $domain + * The domain to validate for syntax and uniqueness. + * + * @return int + * The server response code for the request. + * + * @see domain_validate() + */ + protected function checkDomain(DomainInterface $domain) { + /** @var \Drupal\domain\DomainValidatorInterface $validator */ + $validator = \Drupal::service('domain.validator'); + return $validator->checkResponse($domain); + } + + /** + * Validates a domain meets the standards for a hostname. + * + * @param DomainInterface $domain + * The domain to validate for syntax and uniqueness. + * @return string[] + * Array of strings indicating issues found. + * + * @see domain_validate() + */ + protected function validateDomain(DomainInterface $domain) { + /** @var \Drupal\domain\DomainValidatorInterface $validator */ + $validator = \Drupal::service('domain.validator'); + return $validator->validate($domain->getHostname()); + } + + /** + * Deletes a domain record. + * + * @param DomainInterface[] $domains + * The domain_id to delete. Pass 'all' to delete all records. + * + * @throws \Drupal\domain\Commands\DomainCommandException + * @throws \UnexpectedValueException + */ + protected function deleteDomain(array $domains) { + foreach ($domains as $domain) { + if (!$domain instanceof DomainInterface) { + throw new StorageException('deleting domains: value is not a domain'); + } + $hostname = $domain->getHostname(); + + if ($this->is_dry_run) { + $this->logger()->info(dt('DRYRUN: Domain record @domain deleted.', + ['@domain' => $hostname])); + continue; + } + + try { + $domain->delete(); + } + catch (EntityStorageException $e) { + throw new DomainCommandException(dt('Unable to delete domain: @domain', + ['@domain' => $hostname]), $e); + } + $this->logger()->info(dt('Domain record @domain deleted.', + ['@domain' => $hostname])); + } + } + + /** + * Returns a list of the entity types that are domain enabled. + * + * A domain-enabled entity is defined here as an entity type that includes + * the domain access field(s). + * + * @param string $using_field + * The specific field name to look for. + * + * @return string[] + * List of entity machine names that support domain references. + */ + protected function findDomainEnabledEntities($using_field = DOMAIN_ADMIN_FIELD) { + $this->ensureEntityFieldMap(); + $entities = []; + foreach($this->entity_field_map as $type => $fields) { + if (array_key_exists($using_field, $fields)) { + $entities[] = $type; + } + } + return $entities; + } + + /** + * Determines whether or not a given entity is domain-enabled. + * + * @param string $entity_type + * The machine name of the entity. + * @param string $field + * The name of the field to check for existence. + * + * @return bool + * True if this type of entity has a domain field. + */ + protected function entityHasDomainField($entity_type, $field = DOMAIN_ADMIN_FIELD) { + // Try to avoid repeated calls to getFieldMap(), assuming it's expensive. + $this->ensureEntityFieldMap(); + return array_key_exists($field, $this->entity_field_map[$entity_type]); + } + + /** + * Ensure the local entity field map has been defined. + * + * Asking for the entity field map cause a lot of lookup, so we lazily + * fetch it and then remember it to avoid repeated checks. + */ + protected function ensureEntityFieldMap() { + // Try to avoid repeated calls to getFieldMap() assuming it's expensive. + if (empty($this->entity_field_map)) { + $entity_manager = \Drupal::entityManager(); + $this->entity_field_map = $entity_manager->getFieldMap(); + } + } + + /** + * Enumerate entity instances of the supplied type and domain. + * + * @param string $entity_type + * The entity type name, e.g. 'node' + * @param string $domain_id + * The machine name of the domain to enumerate. + * @param string $field + * The field to manipulate in the entity, e.g. DOMAIN_ACCESS_FIELD. + * + * @return int|string[] + * List of entity IDs for the selected domain. + * @todo: should this really be a string[] of fields? + */ + protected function enumerateDomainEntities($entity_type, $domain_id, $field, $just_count = FALSE) { + if (!$this->entityHasDomainField($entity_type, $field)) { + $this->logger()->info('Entity type @entity_type does not have field @field, so none found.', + ['@entity_type'=> $entity_type, + '@field' => $field]); + return []; + } + + $efq = \Drupal::entityQuery($entity_type); + // Don't access check or we wont get all of the possible entities moved. + $efq->accessCheck(FALSE); + $efq->condition($field, $domain_id, '='); + if ($just_count) { + $efq->count(); + } + return $efq->execute(); + } + + /** + * Reassign old_domain entities, of the supplied type, to the new_domain. + * + * @param string $entity_type + * The entity type name, e.g. 'node' + * @param string $field + * The field to manipulate in the entity, e.g. DOMAIN_ADMIN_FIELD. + * @param \Drupal\domain\DomainInterface $old_domain + * The domain the entities currently belong to. It is not an error for + * entity ids to be passed in that are not in this domain, though of course + * not very useful. + * @param \Drupal\domain\DomainInterface $new_domain + * The domain the entities should now belong to: When an entity belongs to + * the old_domain, this domain replaces it. + * @param array $ids + * List of entity IDs for the selected domain and all of type $entity_type. + * + * @return int + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function reassignEntities($entity_type, $field, DomainInterface $old_domain, DomainInterface $new_domain, array $ids) { + $entity_storage = \Drupal::entityTypeManager()->getStorage($entity_type); + $entities = $entity_storage->loadMultiple($ids); + + foreach($entities as $entity) { + $changed = FALSE; + if (!$entity->hasField($field)) { + continue; + } + // Multivalue fields are used, so check each one. + foreach ($entity->get($field) as $k => $item) { + if ($item->target_id == $old_domain->id()) { + + if ($this->is_dry_run) { + $this->logger()->info(dt('DRYRUN: Update domain membership for entity @id to @new.', + [ '@id' => $entity->id(), '@new' => $new_domain->id() ])); + + // Don't set changed, so don't save either. + continue; + } + + $changed = TRUE; + $item->target_id = $new_domain->id(); + } + } + if ($changed) { + $entity->save(); + } + } + return count($entities); + } + + /** + * Return the Domain object corresponding to a policy string. + * + * @param string $policy + * In general one of 'prompt' | 'default' | 'ignore' or a domain entity + * machine name, but this function does not process 'prompt'. + * + * @return \Drupal\Core\Entity\EntityInterface|\Drupal\domain\DomainInterface|null + * @throws \Drupal\domain\Commands\DomainCommandException + */ + protected function getDomainInstanceFromPolicy($policy) { + switch($policy) { + /* Use the Default Domain machine name */ + case 'default': + $new_domain = $this->domainStorage()->loadDefaultDomain(); + break; + + /* Ask interactively for a Domain machine name */ + case 'prompt': + case 'ignore': + return NULL; + + /* Use this (specified) Domain machine name */ + default: + $new_domain = $this->domainStorage()->load($policy); + break; + } + return $new_domain; + } + + /** + * Reassign entities of the supplied type to the $policy domain. + * + * @param array $options + * [ + * 'entity_filter' => 'node', + * 'policy' => 'prompt' | 'default' | 'ignore' | {domain_id} + * 'field' => DOMAIN_ACCESS_FIELD, + * ]; + * + * @param DomainInterface[] $domains + * List of the domains to reassign content away from. + * + * @throws \Drupal\domain\Commands\DomainCommandException + */ + protected function reassignLinkedEntities($domains, array $options) { + $count = 0; + $field = $options['field']; + $entity_typenames = $this->findDomainEnabledEntities($field); + + $new_domain = $this->getDomainInstanceFromPolicy($options['policy']); + if (empty($new_domain)) { + throw new DomainCommandException('invalid destination domain'); + } + + // For each entity type... + $exceptions = FALSE; + foreach ($entity_typenames as $name) { + if (empty($options['entity_filter']) || $options['entity_filter'] === $name) { + + // For each domain being reassigned from... + foreach ($domains as $domain) { + $ids = $this->enumerateDomainEntities($name, $domain->id(), $field); + if (!empty($ids)) { + try { + if ($options['chatty']) { + $this->logger()->info('Reassigning @count @entity_name entities to @domain', + ['@entity_name'=>'', + '@count' => \count($ids), + '@domain' => $new_domain->id()]); + } + $count = $this->reassignEntities($name, $field, $domain, $new_domain, $ids); + } + catch (PluginException $e) { + $exceptions = TRUE; + $this->logger()->error('Unable to reassign content to @new_domain: plugin exception: @ex', + ['@ex' => $e->getMessage(), + '@new_domain' => $new_domain->id()]); + } + catch (EntityStorageException $e) { + $exceptions = TRUE; + $this->logger()->error('Unable to reassign content to @new_domain: storage exception: @ex', + ['@ex' => $e->getMessage(), + '@new_domain' => $new_domain->id()]); + } + } + } + } + } + if ($exceptions) { + throw new DomainCommandException('Errors encountered during reassign.'); + } + + return $count; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php index 4afc7cc21..fe051241e 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainElementManager.php @@ -59,8 +59,15 @@ class DomainElementManager implements DomainElementManagerInterface { return $form; } $fields = $this->fieldList($field_name); + $empty = FALSE; $disallowed = $this->disallowedOptions($form_state, $form[$field_name]); - $empty = empty($form[$field_name]['widget']['#options']); + if (empty($form[$field_name]['widget']['#options']) || + (count($form[$field_name]['widget']['#options']) == 1 && + isset($form[$field_name]['widget']['#options']['_none']) + ) + ) { + $empty = TRUE; + } // If the domain form element is set as a group, and the field is not // assigned to another group, then move it. See @@ -68,7 +75,10 @@ class DomainElementManager implements DomainElementManagerInterface { if (isset($form['domain']) && !isset($form[$field_name]['#group'])) { $form[$field_name]['#group'] = 'domain'; } - + // If no values and we should hide the element, do so. + if ($hide_on_disallow && $empty) { + $form[$field_name]['#access'] = FALSE; + } // Check for domains the user cannot access or the absence of any options. if (!empty($disallowed) || $empty) { // @TODO: Potentially show this information to users with permission. diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php index 4bed560f0..ef68f7c5f 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainNegotiator.php @@ -38,7 +38,7 @@ class DomainNegotiator implements DomainNegotiatorInterface { /** * The domain storage class. * - * @var \Drupal\domain\DomainStorageInterface + * @var \Drupal\domain\DomainStorageInterface|null */ protected $domainStorage; @@ -87,7 +87,6 @@ class DomainNegotiator implements DomainNegotiatorInterface { $this->requestStack = $requestStack; $this->moduleHandler = $module_handler; $this->entityTypeManager = $entity_type_manager; - $this->domainStorage = $this->entityTypeManager->getStorage('domain'); $this->configFactory = $config_factory; } @@ -98,7 +97,7 @@ class DomainNegotiator implements DomainNegotiatorInterface { // @TODO: Investigate caching methods. $this->setHttpHost($httpHost); // Try to load a direct match. - if ($domain = $this->domainStorage->loadByHostname($httpHost)) { + if ($domain = $this->domainStorage()->loadByHostname($httpHost)) { // If the load worked, set an exact match flag for the hook. $domain->setMatchType(self::DOMAIN_MATCH_EXACT); } @@ -107,16 +106,10 @@ class DomainNegotiator implements DomainNegotiatorInterface { else { $values = ['hostname' => $httpHost]; /** @var \Drupal\domain\DomainInterface $domain */ - $domain = $this->domainStorage->create($values); + $domain = $this->domainStorage()->create($values); $domain->setMatchType(self::DOMAIN_MATCH_NONE); } - // Make sure all modules are loaded and can alter the found domains. - // See https://www.drupal.org/node/2896434#comment-12267208. - $this->moduleHandler->reload(); - foreach ($this->moduleHandler->getImplementations('domain_request_alter') as $module) { - $this->moduleHandler->load($module); - } // Now check with modules (like Domain Alias) that register alternate // lookup systems with the main module. $this->moduleHandler->alter('domain_request', $domain); @@ -126,7 +119,7 @@ class DomainNegotiator implements DomainNegotiatorInterface { $this->setActiveDomain($domain); } // Fallback to default domain if no match. - elseif ($domain = $this->domainStorage->loadDefaultDomain()) { + elseif ($domain = $this->domainStorage()->loadDefaultDomain()) { $this->moduleHandler->alter('domain_request', $domain); $domain->setMatchType(self::DOMAIN_MATCH_NONE); if (!empty($domain->id())) { @@ -180,7 +173,7 @@ class DomainNegotiator implements DomainNegotiatorInterface { $httpHost = $_SERVER['HTTP_HOST']; } $hostname = !empty($httpHost) ? $httpHost : 'localhost'; - return $this->domainStorage->prepareHostname($hostname); + return $this->domainStorage()->prepareHostname($hostname); } /** @@ -202,13 +195,13 @@ class DomainNegotiator implements DomainNegotiatorInterface { */ public function isRegisteredDomain($hostname) { // Direct hostname match always passes. - if ($domain = $this->domainStorage->loadByHostname($hostname)) { + if ($domain = $this->domainStorage()->loadByHostname($hostname)) { return TRUE; } // Check for registered alias matches. $values = ['hostname' => $hostname]; /** @var \Drupal\domain\DomainInterface $domain */ - $domain = $this->domainStorage->create($values); + $domain = $this->domainStorage()->create($values); $domain->setMatchType(self::DOMAIN_MATCH_NONE); // Now check with modules (like Domain Alias) that register alternate @@ -222,4 +215,17 @@ class DomainNegotiator implements DomainNegotiatorInterface { return FALSE; } + /** + * Retrieves the domain storage handler. + * + * @return \Drupal\domain\DomainStorageInterface + * The domain storage handler. + */ + protected function domainStorage() { + if (!$this->domainStorage) { + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + } + return $this->domainStorage; + } + } diff --git a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php index 8b570b6e5..5d68100af 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/DomainValidator.php @@ -132,10 +132,9 @@ class DomainValidator implements DomainValidatorInterface { } // We cannot know which Guzzle Exception class will be returned; be generic. catch (RequestException $e) { - watchdog_exception('domain', $e); // File a general server failure. $domain->setResponse(500); - return; + return $domain->getResponse(); } // Expected result (i.e. no exception thrown.) $domain->setResponse($request->getStatusCode()); diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Entity/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Entity/Domain.php index 2fbb6e8d1..7f5167147 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/Entity/Domain.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/Entity/Domain.php @@ -343,8 +343,8 @@ class Domain extends ConfigEntityBase implements DomainInterface { $default->is_default = FALSE; $default->save(); } - // Ensures we have a proper domain_id. - if ($this->isNew()) { + // Ensures we have a proper domain_id but does not erase existing ones. + if ($this->isNew() && empty($this->getDomainId())) { $this->createDomainId(); } // Prevent duplicate hostname. diff --git a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php index 965911af5..7347401b4 100644 --- a/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php +++ b/sites/all/modules/contrib/admin/domain/domain/src/Plugin/Condition/Domain.php @@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * id = "domain", * label = @Translation("Domain"), * context = { - * "entity:domain" = @ContextDefinition("entity:domain", label = @Translation("Domain"), required = TRUE) + * "entity:domain" = @ContextDefinition("entity:domain", label = @Translation("Domain"), required = FALSE) * } * ) */ @@ -73,7 +73,12 @@ class Domain extends ConditionPluginBase implements ContainerFactoryPluginInterf ], ], ]; - return parent::buildConfigurationForm($form, $form_state); + $form = parent::buildConfigurationForm($form, $form_state); + if (isset($form['context_mapping']['entity:domain']['#title'])) { + $form['context_mapping']['entity:domain']['#title'] = $this->t('Select the Domain condition'); + $form['context_mapping']['entity:domain']['#description'] = $this->t('This value must be set to "Active domain" for the context to work.'); + } + return $form; } /** diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_config_schema_test/domain_config_schema_test.info.yml b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_config_schema_test/domain_config_schema_test.info.yml index 0fb379830..eb1f32218 100644 --- a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_config_schema_test/domain_config_schema_test.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_config_schema_test/domain_config_schema_test.info.yml @@ -9,8 +9,8 @@ hidden: TRUE dependencies: - domain -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml b/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml index 61308a6a4..c7a2793bc 100644 --- a/sites/all/modules/contrib/admin/domain/domain/tests/modules/domain_test/domain_test.info.yml +++ b/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 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml index 01269c9cc..b7e3f245f 100644 --- a/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_access/domain_access.info.yml @@ -8,8 +8,8 @@ dependencies: - drupal:node - domain:domain -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_access/drush.services.yml b/sites/all/modules/contrib/admin/domain/domain_access/drush.services.yml new file mode 100644 index 000000000..e7541c0ec --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/drush.services.yml @@ -0,0 +1,5 @@ +services: + domain_access.commands: + class: \Drupal\domain_access\Commands\DomainAccessCommands + tags: + - { name: drush.command } diff --git a/sites/all/modules/contrib/admin/domain/domain_access/src/Commands/DomainAccessCommands.php b/sites/all/modules/contrib/admin/domain/domain_access/src/Commands/DomainAccessCommands.php new file mode 100644 index 000000000..fe408cb3f --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_access/src/Commands/DomainAccessCommands.php @@ -0,0 +1,79 @@ +getFieldEntities(DOMAIN_ACCESS_FIELD); + + return $result; + } + +/** + * @hook option domain:delete + */ + public function deleteOptions(Command $command, AnnotationData $annotationData) { + $command->addOption( + 'content-assign', + '', + InputOption::VALUE_OPTIONAL, + 'Reassign content for Domain Access', + null + ); + } + +/** + * @hook on-event domain-delete + */ + public function domainAccessDomainDelete($target_domain, $options) { + // Run our own deletion routine here. + if (is_null($options['content-assign'])) { + $policy_content = 'prompt'; + } + if (!empty($options['content-assign'])) { + if (in_array($options['content-assign'], $this->reassignment_policies, TRUE)) { + $policy_content = $options['content-assign']; + } + } + + $delete_options = [ + 'entity_filter' => 'node', + 'policy' => $policy_content, + 'field' => DOMAIN_ACCESS_FIELD, + ]; + + return $this->doReassign($target_domain, $delete_options); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml index be931b476..b9635a950 100644 --- a/sites/all/modules/contrib/admin/domain/domain_access/tests/modules/domain_access_test/domain_access_test.info.yml +++ b/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 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml index 69441e904..b6adc6d03 100644 --- a/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_alias/domain_alias.info.yml @@ -7,8 +7,8 @@ package: Domain dependencies: - domain:domain -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml index 0419af371..dae75bca8 100644 --- a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.info.yml @@ -7,8 +7,8 @@ package: Domain dependencies: - domain:domain -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.services.yml b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.services.yml index 1c8ecb791..1e1029f93 100644 --- a/sites/all/modules/contrib/admin/domain/domain_config/domain_config.services.yml +++ b/sites/all/modules/contrib/admin/domain/domain_config/domain_config.services.yml @@ -3,7 +3,7 @@ services: class: Drupal\domain_config\DomainConfigOverrider tags: - { name: config.factory.override, priority: -150} - arguments: ['@config.storage'] + arguments: ['@config.storage', '@module_handler'] domain_config.library.discovery.collector: decorates: library.discovery.collector diff --git a/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigOverrider.php b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigOverrider.php index a712eeadf..97f3ec09e 100644 --- a/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigOverrider.php +++ b/sites/all/modules/contrib/admin/domain/domain_config/src/DomainConfigOverrider.php @@ -6,6 +6,7 @@ use Drupal\domain\DomainInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; /** * Domain-specific config overrides. @@ -29,6 +30,13 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface { */ protected $storage; + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * The domain context of the request. * @@ -64,9 +72,12 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface { * * @param \Drupal\Core\Config\StorageInterface $storage * The configuration storage engine. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. */ - public function __construct(StorageInterface $storage) { + public function __construct(StorageInterface $storage, ModuleHandlerInterface $module_handler) { $this->storage = $storage; + $this->moduleHandler = $module_handler; } /** @@ -77,11 +88,16 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface { static $lookups; // Key should be a known length, so hash. $key = md5(implode(':', $names)); - if (isset($lookups[$key])) { return $lookups[$key]; } + // Set the context of the override request. + if (empty($this->contextSet)) { + $this->initiateContext(); + } + + // Prepare our overrides. $overrides = []; // loadOverrides() runs on config entities, which means that if we try // to run this routine on our own data, then we end up in an infinite loop. @@ -92,9 +108,6 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface { $lookups[$key] = $overrides; return $overrides; } - if (empty($this->contextSet)) { - $this->initiateContext(); - } if (!empty($this->domain)) { foreach ($names as $name) { $config_name = $this->getDomainConfigName($name, $this->domain); @@ -198,7 +211,11 @@ class DomainConfigOverrider implements ConfigFactoryOverrideInterface { // run. if (empty($this->domain)) { $this->domain = $this->domainNegotiator->getActiveDomain(TRUE); + // Ensure the module hook cache is set properly. + // See https://www.drupal.org/project/domain/issues/3025541 + $this->moduleHandler->resetImplementations(); } + } } diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.info.yml new file mode 100644 index 000000000..86b70de9b --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.info.yml @@ -0,0 +1,24 @@ +name: "Domain config hook test" +description: "Support module for domain config testing." +type: module +package: Testing +# version: VERSION +# core: 8.x +hidden: TRUE + +dependencies: [] + +# This module represents several services that could be provided by multiple modules in the Drupal community. +# The following are not playing nice together: +# - A page cache policy service that uses the config factory. +# - A module that implements hook_module_implements. +# - A random hook that hook_module_implements wishes to control. +# - The domain_config module (and domain negotiator service, I believe). + +# When this module is functioning correctly, when a user logs in, there will not be a state key set. + +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' +core: '8.x' +project: 'domain' +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.module b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.module new file mode 100644 index 000000000..dfbfcf56e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.module @@ -0,0 +1,23 @@ +set('domain_config_test__user_login', TRUE); +} + +/** + * Implements hook_module_implements_alter(). + */ +function domain_config_hook_test_module_implements_alter(&$implementations, $hook) { + if ($hook == 'user_login') { + // Turn off the domain_config_hook_test's hook_user_login (above). + unset($implementations['domain_config_hook_test']); + } +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.services.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.services.yml new file mode 100644 index 000000000..8cb02a0d8 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/domain_config_hook_test.services.yml @@ -0,0 +1,7 @@ +services: + domain_config_service.page_cache_request_policy: + class: Drupal\domain_config_hook_test\PageCache\RequestPolicy\PageCacheRequestPolicy + arguments: ['@config.factory'] + tags: + - { name: page_cache_request_policy } + diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/src/PageCache/RequestPolicy/PageCacheRequestPolicy.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/src/PageCache/RequestPolicy/PageCacheRequestPolicy.php new file mode 100644 index 000000000..6d0cc4b4d --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_hook_test/src/PageCache/RequestPolicy/PageCacheRequestPolicy.php @@ -0,0 +1,47 @@ +configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function check(Request $request) { + // This line is important. You have to use this service for it to fail. + $this->configFactory + ->get('system.site'); + + return NULL; + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml index 3b0859de0..0ba18b603 100644 --- a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_middleware_test/domain_config_middleware_test.info.yml +++ b/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 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml index ec624cc98..0c1f3bbf0 100644 --- a/sites/all/modules/contrib/admin/domain/domain_config/tests/modules/domain_config_test/domain_config_test.info.yml +++ b/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 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHookProblemTest.php b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHookProblemTest.php new file mode 100644 index 000000000..c6fb7a11e --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_config/tests/src/Functional/DomainConfigHookProblemTest.php @@ -0,0 +1,30 @@ +drupalGet('user/login'); + $user = $this->drupalCreateUser([]); + $edit = ['name' => $user->getUserName(), 'pass' => $user->passRaw]; + $this->drupalPostForm(NULL, $edit, t('Log in')); + + $test = \Drupal::state()->get('domain_config_test__user_login', NULL); + // When this test passes, it means domain_config_hook_test_user_login was + // not run. + $this->assertNull($test, 'The hook_user_login state message is set.'); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml index ac0df89c8..1af7da3f7 100644 --- a/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_content/domain_content.info.yml @@ -11,8 +11,8 @@ dependencies: - domain:domain - domain:domain_access -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php index cd69c7a90..385cc3531 100644 --- a/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php +++ b/sites/all/modules/contrib/admin/domain/domain_content/src/Controller/DomainContentController.php @@ -13,7 +13,7 @@ use Drupal\Core\Url; class DomainContentController extends ControllerBase { /** - * Builds the lost of domains and relevant entities. + * Builds the list of domains and relevant entities. * * @param array $options * A list of variables required to build editor or content pages. diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml index 5ba7f787f..1ba6c6fd4 100644 --- a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.info.yml @@ -8,8 +8,8 @@ dependencies: - drupal:node - domain:domain -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module index 8e4a12573..0e1f37a76 100644 --- a/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module +++ b/sites/all/modules/contrib/admin/domain/domain_source/domain_source.module @@ -286,3 +286,17 @@ function domain_source_presave_generate(EntityInterface $entity) { } } } + + +/** + * Implements hook_hook_info(). + */ +function domain_source_hook_info() { + $hooks['domain_source_alter'] = [ + 'group' => 'domain_source', + ]; + $hooks['domain_source_path_alter'] = [ + 'group' => 'domain_source', + ]; + return $hooks; +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/drush.services.yml b/sites/all/modules/contrib/admin/domain/domain_source/drush.services.yml new file mode 100644 index 000000000..6c115c943 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/drush.services.yml @@ -0,0 +1,5 @@ +services: + domain_source.commands: + class: \Drupal\domain_source\Commands\DomainSourceCommands + tags: + - { name: drush.command } diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/Commands/DomainSourceCommands.php b/sites/all/modules/contrib/admin/domain/domain_source/src/Commands/DomainSourceCommands.php new file mode 100644 index 000000000..452f0efd3 --- /dev/null +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/Commands/DomainSourceCommands.php @@ -0,0 +1,79 @@ +getFieldEntities(DOMAIN_SOURCE_FIELD); + + return $result; + } + + /** + * @hook option domain:delete + */ + public function deleteOptions(Command $command, AnnotationData $annotationData) { + $command->addOption( + 'source-assign', + '', + InputOption::VALUE_OPTIONAL, + 'Reassign content for Domain Source', + null + ); + } + + /** + * @hook on-event domain-delete + */ + public function domainSourceDomainDelete($target_domain, $options) { + // Run our own deletion routine here. + if (is_null($options['content-assign'])) { + $policy_content = 'prompt'; + } + if (!empty($options['content-assign'])) { + if (in_array($options['content-assign'], $this->reassignment_policies, TRUE)) { + $policy_content = $options['content-assign']; + } + } + + $delete_options = [ + 'entity_filter' => 'node', + 'policy' => $policy_content, + 'field' => DOMAIN_SOURCE_FIELD, + ]; + + return $this->doReassign($target_domain, $delete_options); + } + +} diff --git a/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php index 06fba8c10..799ececd4 100644 --- a/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php +++ b/sites/all/modules/contrib/admin/domain/domain_source/src/HttpKernel/DomainSourcePathProcessor.php @@ -76,7 +76,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface { /** * The domain storage. * - * @var \Drupal\domain\DomainStorageInterface + * @var \Drupal\domain\DomainStorageInterface|null */ protected $domainStorage; @@ -100,7 +100,6 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface { $this->entityTypeManager = $entity_type_manager; $this->aliasManager = $alias_manager; $this->configFactory = $config_factory; - $this->domainStorage = $entity_type_manager->getStorage('domain'); } /** @@ -159,7 +158,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface { $target_id = domain_source_get($entity); } if (!empty($target_id)) { - $source = $this->domainStorage->load($target_id); + $source = $this->domainStorage()->load($target_id); } $options['entity'] = $entity; $options['entity_type'] = $entity->getEntityTypeId(); @@ -169,7 +168,7 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface { else { if (isset($options['domain_target_id'])) { $target_id = $options['domain_target_id']; - $source = $this->domainStorage->load($target_id); + $source = $this->domainStorage()->load($target_id); } $this->moduleHandler->alter('domain_source_path', $source, $path, $options); } @@ -279,4 +278,17 @@ class DomainSourcePathProcessor implements OutboundPathProcessorInterface { return $this->activeDomain; } + /** + * Retrieves the domain storage handler. + * + * @return \Drupal\domain\DomainStorageInterface + * The domain storage handler. + */ + protected function domainStorage() { + if (!$this->domainStorage) { + $this->domainStorage = $this->entityTypeManager->getStorage('domain'); + } + return $this->domainStorage; + } + } diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/modules/domain_source_test/domain_source_test.info.yml b/sites/all/modules/contrib/admin/domain/domain_source/tests/modules/domain_source_test/domain_source_test.info.yml index 2705e4c5e..b30a708ef 100644 --- a/sites/all/modules/contrib/admin/domain/domain_source/tests/modules/domain_source_test/domain_source_test.info.yml +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/modules/domain_source_test/domain_source_test.info.yml @@ -14,8 +14,8 @@ dependencies: - serialization - user -# Information added by Drupal.org packaging script on 2018-11-14 -version: '8.x-1.0-alpha14+9-dev' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-1.0-alpha15' core: '8.x' project: 'domain' -datestamp: 1542230887 +datestamp: 1550763190 diff --git a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php index 208a32d07..981c2cbb9 100644 --- a/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php +++ b/sites/all/modules/contrib/admin/domain/domain_source/tests/src/Functional/DomainSourceElementTest.php @@ -141,4 +141,44 @@ class DomainSourceElementTest extends DomainTestBase { $this->assert(strpos($url, 'node/' . $nid . '/edit') === FALSE, 'Form submitted.'); } + /** + * Test for https://www.drupal.org/project/domain/issues/3010256. + */ + public function testAnonForm() { + // Editor with no domain permissions should not see the element. + $editor = $this->drupalCreateUser([ + 'create article content', + ]); + $this->drupalLogin($editor); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + $locator = DOMAIN_SOURCE_FIELD; + $this->assertSession()->fieldNotExists($locator); + + // Editor with domain permissions should see the element once they + // are assigned to domains. + $editor2 = $this->drupalCreateUser([ + 'create article content', + 'publish to any assigned domain', + ]); + $this->drupalLogin($editor2); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + $locator = DOMAIN_SOURCE_FIELD; + $this->assertSession()->fieldNotExists($locator); + + // Domain assignment. + $ids = ['example_com', 'one_example_com']; + $this->addDomainsToEntity('user', $editor2->id(), $ids, DOMAIN_ACCESS_FIELD); + + $this->drupalGet('node/add/article'); + $this->assertSession()->statusCodeEquals(200); + + $this->assertSession()->fieldExists($locator); + } + } diff --git a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings-circular_dependency-2930391-7.patch b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings-circular_dependency-2930391-7.patch deleted file mode 100644 index 57ae8f93f..000000000 --- a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings-circular_dependency-2930391-7.patch +++ /dev/null @@ -1,58 +0,0 @@ -diff --git a/domain_site_settings.services.yml b/domain_site_settings.services.yml -index 65e9113..6ebd87d 100644 ---- a/domain_site_settings.services.yml -+++ b/domain_site_settings.services.yml -@@ -1,6 +1,6 @@ - services: - domain_site_settings.overrider: - class: \Drupal\domain_site_settings\Configuration\DomainConfigOverride -- arguments: ['@domain.negotiator', '@config.factory'] -+ arguments: ['@config.factory'] - tags: - - {name: config.factory.override, priority: 5} -diff --git a/src/Configuration/DomainConfigOverride.php b/src/Configuration/DomainConfigOverride.php -index b2571b4..506feef 100644 ---- a/src/Configuration/DomainConfigOverride.php -+++ b/src/Configuration/DomainConfigOverride.php -@@ -16,13 +16,6 @@ use Drupal\Core\Config\ConfigFactoryInterface; - */ - class DomainConfigOverride implements ConfigFactoryOverrideInterface { - -- /** -- * The Domain negotiator. -- * -- * @var \Drupal\domain\DomainNegotiatorInterface -- */ -- protected $negotiator; -- - /** - * The config factory. - * -@@ -33,16 +26,11 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { - /** - * Constructs a DomainSourcePathProcessor object. - * -- * The domain loader. -- * @param \Drupal\domain\DomainNegotiatorInterface $negotiator -- * The domain negotiator. - * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory - * The module handler service. - */ - public function __construct( -- DomainNegotiatorInterface $negotiator, - ConfigFactoryInterface $config_factory) { -- $this->negotiator = $negotiator; - $this->configFactory = $config_factory; - } - -@@ -59,7 +47,9 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { - public function loadOverrides($names) { - $overrides = array(); - if (in_array('system.site', $names)) { -- $domain = $this->negotiator->getActiveDomain(); -+ /* @var \Drupal\domain\DomainNegotiator $negotiator */ -+ $negotiator = \Drupal::service('domain.negotiator'); -+ $domain = $negotiator->getActiveDomain(); - if (!empty($domain)) { - $domain_key = $domain->id(); - $configFactory = $this->configFactory->get('domain_site_settings.domainconfigsettings'); diff --git a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.info.yml b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.info.yml index 634d764e2..cc1b1a98c 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.info.yml +++ b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.info.yml @@ -2,15 +2,15 @@ name: Domain Site Settings type: module description: 'Basic Site Setting for Domains.' dependencies: - - domain - - domain_config + - domain:domain + - domain:domain_config package: Domain # core: 8.x configure: domain_site_settings.list -# Information added by Drupal.org packaging script on 2017-04-04 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-09-23 +version: '8.x-1.3' core: '8.x' project: 'domain_site_settings' -datestamp: 1491339784 +datestamp: 1537684984 diff --git a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.links.menu.yml b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.links.menu.yml index c3b1a52a2..80d81683b 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.links.menu.yml +++ b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.links.menu.yml @@ -4,4 +4,3 @@ domain_site_settings.menu_domain_list: description: 'Change domains site name, email address, slogan, default front page, and error pages.' parent: domain.admin weight: 10 - diff --git a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.services.yml b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.services.yml index 6ebd87db9..65e9113fb 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.services.yml +++ b/sites/all/modules/contrib/admin/domain_site_settings/domain_site_settings.services.yml @@ -1,6 +1,6 @@ services: domain_site_settings.overrider: class: \Drupal\domain_site_settings\Configuration\DomainConfigOverride - arguments: ['@config.factory'] + arguments: ['@domain.negotiator', '@config.factory'] tags: - {name: config.factory.override, priority: 5} diff --git a/sites/all/modules/contrib/admin/domain_site_settings/src/Configuration/DomainConfigOverride.php b/sites/all/modules/contrib/admin/domain_site_settings/src/Configuration/DomainConfigOverride.php index 506feefb4..272e0527b 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/src/Configuration/DomainConfigOverride.php +++ b/sites/all/modules/contrib/admin/domain_site_settings/src/Configuration/DomainConfigOverride.php @@ -5,7 +5,6 @@ namespace Drupal\domain_site_settings\Configuration; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryOverrideInterface; use Drupal\Core\Config\StorageInterface; -use Drupal\domain\DomainLoaderInterface; use Drupal\domain\DomainNegotiatorInterface; use Drupal\Core\Config\ConfigFactoryInterface; @@ -16,6 +15,13 @@ use Drupal\Core\Config\ConfigFactoryInterface; */ class DomainConfigOverride implements ConfigFactoryOverrideInterface { + /** + * The Domain negotiator. + * + * @var \Drupal\domain\DomainNegotiatorInterface + */ + protected $negotiator; + /** * The config factory. * @@ -26,11 +32,15 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { /** * Constructs a DomainSourcePathProcessor object. * + * @param \Drupal\domain\DomainNegotiatorInterface $negotiator + * The domain negotiator. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The module handler service. */ public function __construct( + DomainNegotiatorInterface $negotiator, ConfigFactoryInterface $config_factory) { + $this->negotiator = $negotiator; $this->configFactory = $config_factory; } @@ -44,12 +54,10 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { * An array keyed by configuration name of override data. Override data * contains a nested array structure of overrides. */ - public function loadOverrides($names) { - $overrides = array(); + public function loadOverrides($names = []) { + $overrides = []; if (in_array('system.site', $names)) { - /* @var \Drupal\domain\DomainNegotiator $negotiator */ - $negotiator = \Drupal::service('domain.negotiator'); - $domain = $negotiator->getActiveDomain(); + $domain = $this->negotiator->getActiveDomain(); if (!empty($domain)) { $domain_key = $domain->id(); $configFactory = $this->configFactory->get('domain_site_settings.domainconfigsettings'); @@ -60,6 +68,8 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { $site_403 = $configFactory->get($domain_key . '.site_403'); $site_404 = $configFactory->get($domain_key . '.site_404'); $site_front = $configFactory->get($domain_key . '.site_frontpage'); + $front = ($site_front !== \NULL) ? $site_front : '/node'; + // Create the new settings array to override the configuration. $overrides['system.site'] = [ 'name' => $site_name, @@ -68,7 +78,8 @@ class DomainConfigOverride implements ConfigFactoryOverrideInterface { 'page' => [ '403' => $site_403, '404' => $site_404, - 'front' => ($site_front !== NULL) ? $site_front : '/node'] + 'front' => $front, + ], ]; } } diff --git a/sites/all/modules/contrib/admin/domain_site_settings/src/Controller/DomainSiteSettingsController.php b/sites/all/modules/contrib/admin/domain_site_settings/src/Controller/DomainSiteSettingsController.php index f7d5649fa..b5a3d512c 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/src/Controller/DomainSiteSettingsController.php +++ b/sites/all/modules/contrib/admin/domain_site_settings/src/Controller/DomainSiteSettingsController.php @@ -25,7 +25,7 @@ class DomainSiteSettingsController extends ControllerBase { /** * Construct function. * - * @param DomainLoader $domain_loader + * @param \Drupal\domain\DomainLoader $domain_loader * Load the domain records. */ public function __construct( @@ -37,7 +37,7 @@ class DomainSiteSettingsController extends ControllerBase { /** * Create function return static domain loader configuration. * - * @param ContainerInterface $container + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container * Load the ContainerInterface. * * @return \static @@ -62,22 +62,22 @@ class DomainSiteSettingsController extends ControllerBase { foreach ($domains as $domain) { $row = [ $domain->label(), - $domain->id(), - Link::fromTextAndUrl($this->t('Edit'), Url::fromRoute('domain_site_settings.config_form', array('domain_id' => $domain->id()))), + $domain->getCanonical(), + Link::fromTextAndUrl($this->t('Edit'), Url::fromRoute('domain_site_settings.config_form', ['domain_id' => $domain->id()])), ]; $rows[] = $row; } // Build a render array which will be themed as a table. - $build['pager_example'] = array( + $build['pager_example'] = [ '#rows' => $rows, - '#header' => array( + '#header' => [ $this->t('Name'), $this->t('Hostname'), - $this->t('Edit Settings') - ), + $this->t('Edit Settings'), + ], '#type' => 'table', '#empty' => $this->t('No domain record found.'), - ); + ]; return $build; } diff --git a/sites/all/modules/contrib/admin/domain_site_settings/src/Form/DomainConfigSettingsForm.php b/sites/all/modules/contrib/admin/domain_site_settings/src/Form/DomainConfigSettingsForm.php index 97eedabc5..21b429035 100644 --- a/sites/all/modules/contrib/admin/domain_site_settings/src/Form/DomainConfigSettingsForm.php +++ b/sites/all/modules/contrib/admin/domain_site_settings/src/Form/DomainConfigSettingsForm.php @@ -98,67 +98,64 @@ class DomainConfigSettingsForm extends ConfigFormBase { $site_mail = $config->get($domain_id . '.site_mail'); } - $form['site_information'] = array( + $form['site_information'] = [ '#type' => 'details', '#title' => $this->t('Site details'), '#open' => TRUE, - ); - $form['site_information']['site_name'] = array( + ]; + $form['site_information']['site_name'] = [ '#type' => 'textfield', '#title' => $this->t('Site name'), '#default_value' => ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_name') : $site_config->get('name'), '#required' => TRUE, - ); - $form['site_information']['site_slogan'] = array( + ]; + $form['site_information']['site_slogan'] = [ '#type' => 'textfield', '#title' => $this->t('Slogan'), '#default_value' => ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_slogan') : $site_config->get('slogan'), '#description' => $this->t("How this is used depends on your site's theme."), - ); - $form['site_information']['site_mail'] = array( + ]; + $form['site_information']['site_mail'] = [ '#type' => 'email', '#title' => $this->t('Email address'), '#default_value' => $site_mail, '#description' => $this->t("The From address in automated emails sent during registration and new password requests, and other notifications. (Use an address ending in your site's domain to help prevent this email being flagged as spam.)"), '#required' => TRUE, - ); - - $form['front_page'] = array( + ]; + $form['front_page'] = [ '#type' => 'details', '#title' => $this->t('Front page'), '#open' => TRUE, - ); + ]; $front_page = $site_config->get('page.front') != '/user/login' ? $this->aliasManager->getAliasByPath($site_config->get('page.front')) : ''; - $front_page = ($config->get($domain_id) != NULL) ? $config->get($domain_id . '.site_frontpage') : $front_page; - - $form['front_page']['site_frontpage'] = array( + $form['front_page']['site_frontpage'] = [ '#type' => 'textfield', '#title' => $this->t('Default front page'), '#default_value' => $front_page, '#size' => 40, '#description' => $this->t('Optionally, specify a relative URL to display as the front page. Leave blank to display the default front page.'), '#field_prefix' => $this->requestContext->getCompleteBaseUrl(), - ); - $form['error_page'] = array( + ]; + $form['error_page'] = [ '#type' => 'details', '#title' => $this->t('Error pages'), '#open' => TRUE, - ); - $form['error_page']['site_403'] = array( + ]; + $form['error_page']['site_403'] = [ '#type' => 'textfield', '#title' => $this->t('Default 403 (access denied) page'), '#default_value' => ($config->get($domain_id) !== NULL) ? $config->get($domain_id . '.site_403') : $site_config->get('page.403'), '#size' => 40, '#description' => $this->t('This page is displayed when the requested document is denied to the current user. Leave blank to display a generic "access denied" page.'), - ); - $form['error_page']['site_404'] = array( + ]; + $form['error_page']['site_404'] = [ '#type' => 'textfield', '#title' => $this->t('Default 404 (not found) page'), '#default_value' => ($config->get($domain_id) !== NULL) ? $config->get($domain_id . '.site_404') : $site_config->get('page.404'), '#size' => 40, '#description' => $this->t('This page is displayed when no other content matches the requested document. Leave blank to display a generic "page not found" page.'), - ); + ]; $form['domain_id'] = [ '#type' => 'hidden', '#title' => $this->t('Domain ID'), @@ -185,7 +182,7 @@ class DomainConfigSettingsForm extends ConfigFormBase { $form_state->setErrorByName('site_frontpage', $this->t("The path '%path' has to start with a slash.", ['%path' => $form_state->getValue('site_frontpage')])); } if (!$this->pathValidator->isValid($form_state->getValue('site_frontpage'))) { - $form_state->setErrorByName('site_frontpage', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_frontpage')))); + $form_state->setErrorByName('site_frontpage', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_frontpage')])); } // Get the normal paths of both error pages. if (!$form_state->isValueEmpty('site_403')) { @@ -202,11 +199,11 @@ class DomainConfigSettingsForm extends ConfigFormBase { } // Validate 403 error path. if (!$form_state->isValueEmpty('site_403') && !$this->pathValidator->isValid($form_state->getValue('site_403'))) { - $form_state->setErrorByName('site_403', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_403')))); + $form_state->setErrorByName('site_403', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_403')])); } // Validate 404 error path. if (!$form_state->isValueEmpty('site_404') && !$this->pathValidator->isValid($form_state->getValue('site_404'))) { - $form_state->setErrorByName('site_404', $this->t("The path '%path' is either invalid or you do not have access to it.", array('%path' => $form_state->getValue('site_404')))); + $form_state->setErrorByName('site_404', $this->t("The path '%path' is either invalid or you do not have access to it.", ['%path' => $form_state->getValue('site_404')])); } parent::validateForm($form, $form_state); diff --git a/sites/all/modules/contrib/admin/matomo/README.txt b/sites/all/modules/contrib/admin/matomo/README.txt index dec0ab4b8..9d5dd11a2 100644 --- a/sites/all/modules/contrib/admin/matomo/README.txt +++ b/sites/all/modules/contrib/admin/matomo/README.txt @@ -1,6 +1,6 @@ Module: Matomo Analytics -Author: Alexander Hass +Author: Alexander Hass Description @@ -46,7 +46,7 @@ user with 'Administer Matomo' permission. Like the blocks visibility settings in Drupal core, there is a choice for "Add if the following PHP code returns TRUE." Sample PHP snippets that can be used in this textarea can be found on the handbook page "Overview-approach to -block visibility" at http://drupal.org/node/64135. +block visibility" at https://drupal.org/node/64135. Custom variables ================= @@ -65,7 +65,7 @@ Value: [current-user:matomo-role-ids] Scope: Visitor More details about custom variables can be found in the Matomo API documentation -at http://matomo.org/docs/javascript-tracking/#toc-custom-variables. +at https://matomo.org/docs/javascript-tracking/#toc-custom-variables. Advanced Settings @@ -80,7 +80,7 @@ file locally. Known issues ============ -Drupal requirements (http://drupal.org/requirements) tell you to configure +Drupal requirements (https://drupal.org/requirements) tell you to configure PHP with "session.save_handler = user", but your Matomo installation may not work with this configuration and gives you a server error 500. diff --git a/sites/all/modules/contrib/admin/matomo/composer.json b/sites/all/modules/contrib/admin/matomo/composer.json index c946a4fb7..8799b1bfe 100644 --- a/sites/all/modules/contrib/admin/matomo/composer.json +++ b/sites/all/modules/contrib/admin/matomo/composer.json @@ -15,8 +15,10 @@ ], "support": { "issues": "https://www.drupal.org/project/issues/matomo", - "source": "http://git.drupal.org/project/matomo.git" + "source": "https://git.drupal.org/project/matomo.git" }, "license": "GPL-2.0+", - "require": {} + "require": { + "drupal/core": "~8.5" + } } diff --git a/sites/all/modules/contrib/admin/matomo/matomo.info.yml b/sites/all/modules/contrib/admin/matomo/matomo.info.yml index 3b94f898b..4bc2bc4aa 100644 --- a/sites/all/modules/contrib/admin/matomo/matomo.info.yml +++ b/sites/all/modules/contrib/admin/matomo/matomo.info.yml @@ -4,12 +4,14 @@ description: "Adds Matomo javascript tracking code to all your site's pages." package: Statistics # core: 8.x configure: matomo.admin_settings_form +dependencies: + - drupal:system (>= 8.5) test_dependencies: - php:php - token:token -# Information added by Drupal.org packaging script on 2018-07-13 -version: '8.x-1.7' +# Information added by Drupal.org packaging script on 2019-02-08 +version: '8.x-1.9' core: '8.x' project: 'matomo' -datestamp: 1531470224 +datestamp: 1549615102 diff --git a/sites/all/modules/contrib/admin/matomo/matomo.install b/sites/all/modules/contrib/admin/matomo/matomo.install index 324242621..b3f5a0b61 100644 --- a/sites/all/modules/contrib/admin/matomo/matomo.install +++ b/sites/all/modules/contrib/admin/matomo/matomo.install @@ -17,7 +17,8 @@ function matomo_install() { $role->grantPermission('opt-in or out of matomo tracking'); $success = $role->save(); if ($success) { - drupal_set_message(t('Module %module granted %permission permission to authenticated users.', ['%module' => 'Matomo Analytics', '%permission' => t('Opt-in or out of tracking')]), 'status'); + $messenger = \Drupal::messenger(); + $messenger->addMessage(t('Module %module granted %permission permission to authenticated users.', ['%module' => 'Matomo Analytics', '%permission' => t('Opt-in or out of tracking')]), 'status'); } } diff --git a/sites/all/modules/contrib/admin/matomo/matomo.module b/sites/all/modules/contrib/admin/matomo/matomo.module index 380b66b34..2c422ff4d 100644 --- a/sites/all/modules/contrib/admin/matomo/matomo.module +++ b/sites/all/modules/contrib/admin/matomo/matomo.module @@ -7,7 +7,7 @@ * Adds the required Javascript to all your Drupal pages to allow tracking by * the Matomo statistics package. * - * @author: Alexander Hass + * @author: Alexander Hass */ use Drupal\Component\Serialization\Json; @@ -19,22 +19,18 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Site\Settings; use Drupal\Core\Url; +use Drupal\matomo\MatomoInterface; use Drupal\node\NodeInterface; use GuzzleHttp\Exception\RequestException; use Drupal\matomo\Component\Render\MatomoJavaScriptSnippet; -/** - * Define the default file extension list that should be tracked as download. - */ -define('MATOMO_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc(x|m)?|dot(x|m)?|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt(x|m)?|pot(x|m)?|pps(x|m)?|ppam|sld(x|m)?|thmx|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd|xls(x|m|b)?|xlt(x|m)|xlam|xml|z|zip'); - /** * Implements hook_help(). */ function matomo_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'matomo.admin_settings_form': - return t('Matomo Analytics is an open source (GPL license) web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. Matomo aims to be an open source alternative to Google Analytics.', [':pk_url' => 'http://www.matomo.org/']); + return t('Matomo Analytics is an open source (GPL license) web analytics software. It gives interesting reports on your website visitors, your popular pages, the search engines keywords they used, the language they speak... and so much more. Matomo aims to be an open source alternative to Google Analytics.', [':pk_url' => 'https://www.matomo.org/']); } } @@ -114,7 +110,7 @@ function matomo_page_attachments(array &$page) { 'error' => t('Error message'), ]; - foreach (drupal_get_messages(NULL, FALSE) as $type => $messages) { + foreach (\Drupal::messenger()->all(NULL, FALSE) as $type => $messages) { // Track only the selected message types. if (in_array($type, $message_types)) { foreach ($messages as $message) { @@ -198,7 +194,7 @@ function matomo_page_attachments(array &$page) { $codesnippet_after = $config->get('codesnippet.after'); // Build tracker code. - // @see http://matomo.org/docs/javascript-tracking/#toc-asynchronous-tracking + // @see https://matomo.org/docs/javascript-tracking/#toc-asynchronous-tracking $script = 'var _paq = _paq || [];'; $script .= '(function(){'; $script .= 'var u=(("https:" == document.location.protocol) ? "' . UrlHelper::filterBadProtocol($url_https) . '" : "' . UrlHelper::filterBadProtocol($url_http) . '");'; @@ -220,7 +216,7 @@ function matomo_page_attachments(array &$page) { } // Custom file download extensions. - if ($config->get('track.files') && !($config->get('track.files_extensions') == MATOMO_TRACKFILES_EXTENSIONS)) { + if ($config->get('track.files') && !($config->get('track.files_extensions') == MatomoInterface::MATOMO_TRACKFILES_EXTENSIONS)) { $script .= '_paq.push(["setDownloadExtensions", ' . Json::encode($config->get('track.files_extensions')) . ']);'; } @@ -279,7 +275,7 @@ function matomo_page_attachments(array &$page) { ]; // Disable the download & outlink tracking for specific CSS classes. // Custom code snippets with 'setIgnoreClasses' will override the value. - // @see http://developer.matomo.org/api-reference/tracking-javascript#disable-the-download-amp-outlink-tracking-for-specific-css-classes + // @see https://developer.matomo.org/api-reference/tracking-javascript#disable-the-download-amp-outlink-tracking-for-specific-css-classes $script .= '_paq.push(["setIgnoreClasses", ' . Json::encode($ignore_classes) . ']);'; // Enable download & outlink link tracking. @@ -441,11 +437,12 @@ function matomo_user_profile_form_submit($form, FormStateInterface $form_state) */ function matomo_cron() { $config = \Drupal::config('matomo.settings'); + $request_time = \Drupal::time()->getRequestTime(); // Regenerate the piwik.js every day. - if (REQUEST_TIME - \Drupal::state()->get('matomo.last_cache') >= 86400 && $config->get('cache')) { + if ($request_time - \Drupal::state()->get('matomo.last_cache') >= 86400 && $config->get('cache')) { _matomo_cache($config->get('url_http') . 'piwik.js', TRUE); - \Drupal::state()->set('matomo.last_cache', REQUEST_TIME); + \Drupal::state()->set('matomo.last_cache', $request_time); } } diff --git a/sites/all/modules/contrib/admin/matomo/migration_templates/d6_matomo_settings.yml b/sites/all/modules/contrib/admin/matomo/migrations/d6_matomo_settings.yml similarity index 100% rename from sites/all/modules/contrib/admin/matomo/migration_templates/d6_matomo_settings.yml rename to sites/all/modules/contrib/admin/matomo/migrations/d6_matomo_settings.yml diff --git a/sites/all/modules/contrib/admin/matomo/migration_templates/d6_matomo_user_settings.yml b/sites/all/modules/contrib/admin/matomo/migrations/d6_matomo_user_settings.yml similarity index 100% rename from sites/all/modules/contrib/admin/matomo/migration_templates/d6_matomo_user_settings.yml rename to sites/all/modules/contrib/admin/matomo/migrations/d6_matomo_user_settings.yml diff --git a/sites/all/modules/contrib/admin/matomo/migration_templates/d7_matomo_settings.yml b/sites/all/modules/contrib/admin/matomo/migrations/d7_matomo_settings.yml similarity index 100% rename from sites/all/modules/contrib/admin/matomo/migration_templates/d7_matomo_settings.yml rename to sites/all/modules/contrib/admin/matomo/migrations/d7_matomo_settings.yml diff --git a/sites/all/modules/contrib/admin/matomo/migration_templates/d7_matomo_user_settings.yml b/sites/all/modules/contrib/admin/matomo/migrations/d7_matomo_user_settings.yml similarity index 100% rename from sites/all/modules/contrib/admin/matomo/migration_templates/d7_matomo_user_settings.yml rename to sites/all/modules/contrib/admin/matomo/migrations/d7_matomo_user_settings.yml diff --git a/sites/all/modules/contrib/admin/matomo/src/Form/MatomoAdminSettingsForm.php b/sites/all/modules/contrib/admin/matomo/src/Form/MatomoAdminSettingsForm.php index a7a85e8ad..bd6e68722 100644 --- a/sites/all/modules/contrib/admin/matomo/src/Form/MatomoAdminSettingsForm.php +++ b/sites/all/modules/contrib/admin/matomo/src/Form/MatomoAdminSettingsForm.php @@ -4,15 +4,37 @@ namespace Drupal\matomo\Form; use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\ModuleHandler; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\matomo\MatomoInterface; use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Client; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Configure Matomo settings for this site. */ class MatomoAdminSettingsForm extends ConfigFormBase { + protected $moduleHandler; + + protected $currentUser; + + protected $httpClient; + + /** + * {@inheritdoc} + */ + public function __construct(ConfigFactoryInterface $config_factory, AccountInterface $currentUser, ModuleHandler $moduleHandler, Client $httpClient) { + parent::__construct($config_factory); + $this->currentUser = $currentUser; + $this->moduleHandler = $moduleHandler; + $this->httpClient = $httpClient; + } + /** * {@inheritdoc} */ @@ -117,7 +139,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { ]; // Page specific visibility configurations. - $account = \Drupal::currentUser(); + $account = $this->currentUser; $php_access = $account->hasPermission('use php for matomo tracking visibility'); $visibility_request_path_pages = $config->get('visibility.request_path_pages'); @@ -140,7 +162,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { ]; $description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", ['%blog' => '/blog', '%blog-wildcard' => '/blog/*', '%front' => '']); - if (\Drupal::moduleHandler()->moduleExists('php') && $php_access) { + if ($this->moduleHandler->moduleExists('php') && $php_access) { $options[] = $this->t('Pages on which this PHP code returns TRUE (experts only)'); $title = $this->t('Pages or PHP code'); $description .= ' ' . $this->t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', ['%php' => '']); @@ -207,13 +229,13 @@ class MatomoAdminSettingsForm extends ConfigFormBase { 1 => $this->t('Tracking on by default, users with %permission permission can opt out', $t_permission), 2 => $this->t('Tracking off by default, users with %permission permission can opt in', $t_permission), ], - '#default_value' => !empty($visibility_user_account_mode) ? $visibility_user_account_mode : 1, + '#default_value' => !empty($visibility_user_account_mode) ? $visibility_user_account_mode : 0, ]; $form['tracking']['user_visibility_settings']['matomo_trackuserid'] = [ '#type' => 'checkbox', '#title' => $this->t('Track User ID'), '#default_value' => $config->get('track.userid'), - '#description' => $this->t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. Learn more about the benefits of using User ID.', [':url' => 'http://matomo.org/docs/user-id/']), + '#description' => $this->t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. Learn more about the benefits of using User ID.', [':url' => 'https://matomo.org/docs/user-id/']), ]; // Link specific configurations. @@ -237,7 +259,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#title_display' => 'invisible', '#type' => 'textfield', '#default_value' => $config->get('track.files_extensions'), - '#description' => $this->t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: @extensions', ['@extensions' => MATOMO_TRACKFILES_EXTENSIONS]), + '#description' => $this->t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: @extensions', ['@extensions' => MatomoInterface::MATOMO_TRACKFILES_EXTENSIONS]), '#maxlength' => 500, '#states' => [ 'enabled' => [ @@ -251,7 +273,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { ]; $colorbox_dependencies = '
'; - $colorbox_dependencies .= $this->t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? $this->t('@module (enabled)', ['@module' => 'Colorbox']) : $this->t('@module (disabled)', ['@module' => 'Colorbox']))]); + $colorbox_dependencies .= $this->t('Requires: @module-list', ['@module-list' => ($this->moduleHandler->moduleExists('colorbox') ? $this->t('@module (enabled)', ['@module' => 'Colorbox']) : $this->t('@module (disabled)', ['@module' => 'Colorbox']))]); $colorbox_dependencies .= '
'; $form['tracking']['linktracking']['matomo_trackcolorbox'] = [ @@ -259,7 +281,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#title' => $this->t('Track content in colorbox modal dialogs'), '#description' => $this->t('Enable to track the content shown in colorbox modal windows.') . $colorbox_dependencies, '#default_value' => $config->get('track.colorbox'), - '#disabled' => (\Drupal::moduleHandler()->moduleExists('colorbox') ? FALSE : TRUE), + '#disabled' => ($this->moduleHandler->moduleExists('colorbox') ? FALSE : TRUE), ]; // Message specific configurations. @@ -288,7 +310,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { ]; $site_search_dependencies = '
'; - $site_search_dependencies .= $this->t('Requires: @module-list', ['@module-list' => (\Drupal::moduleHandler()->moduleExists('search') ? $this->t('@module (enabled)', ['@module' => 'Search']) : $this->t('@module (disabled)', ['@module' => 'Search']))]); + $site_search_dependencies .= $this->t('Requires: @module-list', ['@module-list' => ($this->moduleHandler->moduleExists('search') ? $this->t('@module (enabled)', ['@module' => 'Search']) : $this->t('@module (disabled)', ['@module' => 'Search']))]); $site_search_dependencies .= '
'; $form['tracking']['search']['matomo_site_search'] = [ @@ -296,7 +318,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#title' => $this->t('Track internal search'), '#description' => $this->t('If checked, internal search keywords are tracked.') . $site_search_dependencies, '#default_value' => $config->get('track.site_search'), - '#disabled' => (\Drupal::moduleHandler()->moduleExists('search') ? FALSE : TRUE), + '#disabled' => ($this->moduleHandler->moduleExists('search') ? FALSE : TRUE), ]; // Privacy specific configurations. @@ -308,7 +330,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { $form['tracking']['privacy']['matomo_privacy_donottrack'] = [ '#type' => 'checkbox', '#title' => $this->t('Universal web tracking opt-out'), - '#description' => $this->t('If enabled and your Matomo server receives the Do-Not-Track header from the client browser, the Matomo server will not track the user. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision.'), + '#description' => $this->t('If enabled and your Matomo server receives the Do-Not-Track header from the client browser, the Matomo server will not track the user. Compliance with Do Not Track could be purely voluntary, enforced by industry self-regulation, or mandated by state or federal law. Please accept your visitors privacy. If they have opt-out from tracking and advertising, you should accept their personal decision.', [':donottrack' => 'https://www.eff.org/issues/do-not-track']), '#default_value' => $config->get('privacy.donottrack'), ]; @@ -334,7 +356,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { // Custom variables. $form['matomo_custom_var'] = [ - '#description' => $this->t('You can add Matomos Custom Variables here. These will be added to every page that Matomo tracking code appears on. Custom variable names and values are limited to 200 characters in length. Keep the names and values as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', [':custom_var_documentation' => 'http://matomo.org/docs/custom-variables/']), + '#description' => $this->t('You can add Matomos Custom Variables here. These will be added to every page that Matomo tracking code appears on. Custom variable names and values are limited to 200 characters in length. Keep the names and values as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', [':custom_var_documentation' => 'https://matomo.org/docs/custom-variables/']), '#title' => $this->t('Custom variables'), '#tree' => TRUE, '#type' => 'details', @@ -382,7 +404,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#element_validate' => [[get_class($this), 'tokenElementValidate']], '#token_types' => ['node'], ]; - if (\Drupal::moduleHandler()->moduleExists('token')) { + if ($this->moduleHandler->moduleExists('token')) { $form['matomo_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate'; } $form['matomo_custom_var']['slots'][$i]['scope'] = [ @@ -402,7 +424,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#type' => 'item', '#description' => $this->t("You can supplement Matomos' basic IP address tracking of visitors by segmenting users based on custom variables. Make sure you will not associate (or permit any third party to associate) any data gathered from your websites (or such third parties' websites) with any personally identifying information from any source as part of your use (or such third parties' use) of the Matomo' service."), ]; - if (\Drupal::moduleHandler()->moduleExists('token')) { + if ($this->moduleHandler->moduleExists('token')) { $form['matomo_custom_var']['matomo_custom_var_token_tree'] = [ '#theme' => 'token_tree_link', '#token_types' => ['node'], @@ -424,7 +446,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { ]; // Allow for tracking of the originating node when viewing translation sets. - if (\Drupal::moduleHandler()->moduleExists('content_translation')) { + if ($this->moduleHandler->moduleExists('content_translation')) { $form['advanced']['matomo_translation_set'] = [ '#type' => 'checkbox', '#title' => $this->t('Track translation sets as one unit'), @@ -439,7 +461,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { '#type' => 'details', '#title' => $this->t('Custom JavaScript code'), '#open' => TRUE, - '#description' => $this->t('You can add custom Matomo code snippets here. These will be added to every page that Matomo appears on. Do not include the <script> tags, and always end your code with a semicolon (;).', [':snippets' => 'http://matomo.org/docs/javascript-tracking/']), + '#description' => $this->t('You can add custom Matomo code snippets here. These will be added to every page that Matomo appears on. Do not include the <script> tags, and always end your code with a semicolon (;).', [':snippets' => 'https://matomo.org/docs/javascript-tracking/']), ]; $form['advanced']['codesnippet']['matomo_codesnippet_before'] = [ '#type' => 'textarea', @@ -496,7 +518,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { $url = $form_state->getValue('matomo_url_http') . 'piwik.php'; try { - $result = \Drupal::httpClient()->get($url); + $result = $this->httpClient->get($url); if ($result->getStatusCode() != 200 && $form_state->getValue('matomo_url_skiperror') == FALSE) { $form_state->setErrorByName('matomo_url_http', $this->t('The validation of "@url" failed with error "@error" (HTTP code @code).', [ '@url' => UrlHelper::filterBadProtocol($url), @@ -517,7 +539,7 @@ class MatomoAdminSettingsForm extends ConfigFormBase { if (!empty($matomo_url_https)) { $url = $matomo_url_https . 'piwik.php'; try { - $result = \Drupal::httpClient()->get($url); + $result = $this->httpClient->get($url); if ($result->getStatusCode() != 200 && $form_state->getValue('matomo_url_skiperror') == FALSE) { $form_state->setErrorByName('matomo_url_https', $this->t('The validation of "@url" failed with error "@error" (HTTP code @code).', [ '@url' => UrlHelper::filterBadProtocol($url), @@ -714,4 +736,17 @@ class MatomoAdminSettingsForm extends ConfigFormBase { return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string); } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + // Load the service required to construct this class. + $container->get('config.factory'), + $container->get('current_user'), + $container->get('module_handler'), + $container->get('http_client') + ); + } + } diff --git a/sites/all/modules/contrib/admin/matomo/src/MatomoInterface.php b/sites/all/modules/contrib/admin/matomo/src/MatomoInterface.php new file mode 100644 index 000000000..db12818d6 --- /dev/null +++ b/sites/all/modules/contrib/admin/matomo/src/MatomoInterface.php @@ -0,0 +1,15 @@ +randomMachineName(8); // Create a node to search for. - // Create a node. $edit = []; $edit['title[0][value]'] = 'This is a test title'; $edit['body[0][value]'] = 'This test content contains ' . $search['keys'] . ' string.'; diff --git a/sites/all/modules/contrib/admin/matomo/src/Tests/MatomoStatusMessagesTest.php b/sites/all/modules/contrib/admin/matomo/src/Tests/MatomoStatusMessagesTest.php index bf3582f0c..2d4f0aa02 100644 --- a/sites/all/modules/contrib/admin/matomo/src/Tests/MatomoStatusMessagesTest.php +++ b/sites/all/modules/contrib/admin/matomo/src/Tests/MatomoStatusMessagesTest.php @@ -57,7 +57,11 @@ class MatomoStatusMessagesTest extends WebTestBase { $this->assertRaw('_paq.push(["trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testMatomoStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.'); // Enable logging of status, warnings and errors. - $this->config('matomo.settings')->set('track.messages', ['status' => 'status', 'warning' => 'warning', 'error' => 'error'])->save(); + $this->config('matomo.settings')->set('track.messages', [ + 'status' => 'status', + 'warning' => 'warning', + 'error' => 'error', + ])->save(); $this->drupalGet('matomo-test/drupal-messenger-add-message'); $this->assertRaw('_paq.push(["trackEvent", "Messages", "Status message", "Example status message."]);', '[testMatomoStatusMessages]: Example status message is enabled for tracking.'); diff --git a/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/matomo_test.info.yml b/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/matomo_test.info.yml index 5abee1009..77399b4bd 100644 --- a/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/matomo_test.info.yml +++ b/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/matomo_test.info.yml @@ -4,8 +4,8 @@ description: 'Support module for Matomo testing.' package: Testing # core: 8.x -# Information added by Drupal.org packaging script on 2018-07-13 -version: '8.x-1.7' +# Information added by Drupal.org packaging script on 2019-02-08 +version: '8.x-1.9' core: '8.x' project: 'matomo' -datestamp: 1531470224 +datestamp: 1549615102 diff --git a/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/src/Controller/MatomoTestController.php b/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/src/Controller/MatomoTestController.php index 4a0e8801d..a37b78342 100644 --- a/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/src/Controller/MatomoTestController.php +++ b/sites/all/modules/contrib/admin/matomo/tests/modules/matomo_test/src/Controller/MatomoTestController.php @@ -17,10 +17,10 @@ class MatomoTestController extends ControllerBase { */ public function drupalAddMessageTest() { // Set some messages. - drupal_set_message($this->t('Example status message.'), 'status'); - drupal_set_message($this->t('Example warning message.'), 'warning'); - drupal_set_message($this->t('Example error message.'), 'error'); - drupal_set_message($this->t('Example error message with html tags and link.'), 'error'); + $this->messenger()->addMessage($this->t('Example status message.'), 'status'); + $this->messenger()->addMessage($this->t('Example warning message.'), 'warning'); + $this->messenger()->addMessage($this->t('Example error message.'), 'error'); + $this->messenger()->addMessage($this->t('Example error message with html tags and link.'), 'error'); return []; } diff --git a/sites/all/modules/contrib/admin/path_alias_xt/path_alias_xt.info.yml b/sites/all/modules/contrib/admin/path_alias_xt/path_alias_xt.info.yml index 24feddf9d..51fca7b7e 100644 --- a/sites/all/modules/contrib/admin/path_alias_xt/path_alias_xt.info.yml +++ b/sites/all/modules/contrib/admin/path_alias_xt/path_alias_xt.info.yml @@ -1,13 +1,16 @@ name: Extended Path Aliases -type: module description: 'Automatically extend path aliases to include tabs, like about-us/edit for node/123/edit.
Allow these aliases to be entered in page specification wild-cards, about-us*, e.g for block visibility.' -# core: 8.x -configure: path_alias_xt.settings_form -dependencies: - - path -# Information added by Drupal.org packaging script on 2017-07-14 +type: module +# core: 8.x + +dependencies: + - drupal:path + +configure: path_alias_xt.settings_form + +# Information added by Drupal.org packaging script on 2019-01-30 version: '8.x-1.x-dev' core: '8.x' project: 'path_alias_xt' -datestamp: 1500044946 +datestamp: 1548876484 diff --git a/sites/all/modules/contrib/admin/path_alias_xt/src/PathAliasXtProcessorAlias.php b/sites/all/modules/contrib/admin/path_alias_xt/src/PathAliasXtProcessorAlias.php index cf1ea4c39..2d263a65f 100644 --- a/sites/all/modules/contrib/admin/path_alias_xt/src/PathAliasXtProcessorAlias.php +++ b/sites/all/modules/contrib/admin/path_alias_xt/src/PathAliasXtProcessorAlias.php @@ -57,7 +57,7 @@ class PathAliasXtProcessorAlias extends PathProcessorAlias { // Validate the path. // Injecting the service threw ServiceCircularReferenceException. - if (\Drupal::service('path.validator')->isValid($return_path)) { + if (\Drupal::service('path.validator')->getUrlIfValidWithoutAccessCheck($return_path)) { return $return_path; } } diff --git a/sites/all/modules/contrib/admin/redirect/README.txt b/sites/all/modules/contrib/admin/redirect/README.txt index 3ea2f179a..5ae8276ab 100644 --- a/sites/all/modules/contrib/admin/redirect/README.txt +++ b/sites/all/modules/contrib/admin/redirect/README.txt @@ -1,3 +1,61 @@ +CONTENTS OF THIS FILE +--------------------- -This is the new module home for a unified redirection API (also replaces + * Introduction + * Requirements + * Installation + * Configuration + * Maintainers + + +INTRODUCTION +------------ + +The Redirect module provides a unified redirection API (also replaces path_redirect and globalredirect). + + + * For a full description of the module visit: + https://www.drupal.org/project/redirect + + * To submit bug reports and feature suggestions, or to track changes visit: + https://www.drupal.org/project/issues/redirect + + +REQUIREMENTS +------------ + +This module requires no modules outside of Drupal core. + + +INSTALLATION +------------ + + * Install the Redirect module as you would normally install a contributed + Drupal module. Visit https://www.drupal.org/node/1897420 for further + information. + + +CONFIGURATION +------------- + + 1. Navigate to Administration > Extend and enable the module. + 2. Navigate to Administration > Configuration > Search and Metadata > URL + redirects for configuration. + 3. Select "Add redirect" and in the "Path" field add the old path. + 4. In the "To" field, start typing the title of a piece of content to select + it. You can also enter an internal path such as /node/add or an external + URL such as http://example.com. Enter to link to the front page. + 5. Select the Redirect status: 300 Multiple Choices, 301 Moved Permanently, + 302 Found, 303 See Other, 304 Not Modified, 305 Use Proxy, or 307 + Temporary Redirect. Save. + 6. Once a redirect has been added, it will be listed in the URL Redirects + vertical tab group on the content's edit page. + + +MAINTAINERS +----------- + +Supporting organization for 8.x-1.x port: + + * MD Systems - https://www.drupal.org/md-systems diff --git a/sites/all/modules/contrib/admin/redirect/config/install/views.view.redirect.yml b/sites/all/modules/contrib/admin/redirect/config/install/views.view.redirect.yml index 42555ccc3..6a1c65f55 100644 --- a/sites/all/modules/contrib/admin/redirect/config/install/views.view.redirect.yml +++ b/sites/all/modules/contrib/admin/redirect/config/install/views.view.redirect.yml @@ -38,7 +38,7 @@ display: exposed_form: type: basic options: - submit_button: Apply + submit_button: Filter reset_button: false reset_button_label: Reset exposed_sorts_label: 'Sort by' diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/redirect_404.info.yml b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/redirect_404.info.yml index b1b96997b..1fbcea416 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/redirect_404.info.yml +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/redirect_404.info.yml @@ -7,8 +7,8 @@ dependencies: - redirect - views -# Information added by Drupal.org packaging script on 2018-05-04 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-10-16 +version: '8.x-1.3' core: '8.x' project: 'redirect' -datestamp: 1525419807 +datestamp: 1539682690 diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/EventSubscriber/Redirect404Subscriber.php b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/EventSubscriber/Redirect404Subscriber.php index 7a54e86db..f7cd1416a 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/EventSubscriber/Redirect404Subscriber.php +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/EventSubscriber/Redirect404Subscriber.php @@ -2,7 +2,6 @@ namespace Drupal\redirect_404\EventSubscriber; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Path\PathMatcherInterface; @@ -106,11 +105,11 @@ class Redirect404Subscriber implements EventSubscriberInterface { $path = $this->currentPath->getPath(); // Ignore paths specified in the redirect settings. - if ($pages = Unicode::strtolower($this->config->get('pages'))) { + if ($pages = mb_strtolower($this->config->get('pages'))) { // Do not trim a trailing slash if that is the complete path. $path_to_match = $path === '/' ? $path : rtrim($path, '/'); - if ($this->pathMatcher->matchPath(Unicode::strtolower($path_to_match), $pages)) { + if ($this->pathMatcher->matchPath(mb_strtolower($path_to_match), $pages)) { return; } } diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/SqlRedirectNotFoundStorage.php b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/SqlRedirectNotFoundStorage.php index 8b425e9ba..98003ebae 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/SqlRedirectNotFoundStorage.php +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/SqlRedirectNotFoundStorage.php @@ -51,7 +51,7 @@ class SqlRedirectNotFoundStorage implements RedirectNotFoundStorageInterface { * {@inheritdoc} */ public function logRequest($path, $langcode) { - if (Unicode::strlen($path) > static::MAX_PATH_LENGTH) { + if (mb_strlen($path) > static::MAX_PATH_LENGTH) { // Don't attempt to log paths that would result in an exception. There is // no point in logging truncated paths, as they cannot be used to build a // new redirect. diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php index c7657447c..89a14d2dc 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php @@ -2,6 +2,7 @@ namespace Drupal\redirect_404\Tests; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\language\Entity\ConfigurableLanguage; @@ -88,14 +89,14 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase { // Check if we generate correct Add redirect url and if the form is // pre-filled. $destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath(); - $options = [ - 'query' => [ - 'source' => 'testing', - 'language' => 'fr', - 'destination' => $destination, - ] + $expected_query = [ + 'destination' => $destination, + 'language' => 'fr', + 'source' => 'testing', ]; - $this->assertUrl('admin/config/search/redirect/add', $options); + $parsed_url = UrlHelper::parse($this->getUrl()); + $this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']); + $this->assertEqual($expected_query, $parsed_url['query']); $this->assertFieldByName('redirect_source[0][path]', 'testing'); $this->assertOptionSelected('edit-language-0-value', 'fr'); // Save the redirect. @@ -177,14 +178,14 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase { // Assign a redirect to 'testing1'. $this->clickLink('Add redirect'); - $options = [ - 'query' => [ - 'source' => 'testing1', - 'language' => 'en', - 'destination' => $destination, - ] + $expected_query = [ + 'destination' => $destination, + 'language' => 'en', + 'source' => 'testing1', ]; - $this->assertUrl('admin/config/search/redirect/add', $options); + $parsed_url = UrlHelper::parse($this->getUrl()); + $this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']); + $this->assertEqual($expected_query, $parsed_url['query']); $this->assertFieldByName('redirect_source[0][path]', 'testing1'); $this->assertOptionSelected('edit-language-0-value', 'en'); $edit = ['redirect_redirect[0][uri]' => '/node']; diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUITest.php b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUITest.php index 44b664459..088cbba47 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUITest.php +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_404/src/Tests/Fix404RedirectUITest.php @@ -2,6 +2,7 @@ namespace Drupal\redirect_404\Tests; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; /** @@ -26,14 +27,14 @@ class Fix404RedirectUITest extends Redirect404TestBase { // Check if we generate correct Add redirect url and if the form is // pre-filled. $destination = Url::fromRoute('redirect_404.fix_404')->getInternalPath(); - $options = [ - 'query' => [ - 'source' => 'non-existing0', - 'language' => 'en', - 'destination' => $destination, - ] + $expected_query = [ + 'destination' => $destination, + 'language' => 'en', + 'source' => 'non-existing0', ]; - $this->assertUrl('admin/config/search/redirect/add', $options); + $parsed_url = UrlHelper::parse($this->getUrl()); + $this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']); + $this->assertEqual($expected_query, $parsed_url['query']); $this->assertFieldByName('redirect_source[0][path]', 'non-existing0'); // Save the redirect. $edit = ['redirect_redirect[0][uri]' => '/node']; @@ -97,14 +98,14 @@ class Fix404RedirectUITest extends Redirect404TestBase { // Assign a redirect to 'non-existing2'. $this->clickLink('Add redirect'); - $options = [ - 'query' => [ - 'source' => 'non-existing2', - 'language' => 'en', - 'destination' => $destination, - ] + $expected_query = [ + 'source' => 'non-existing2', + 'language' => 'en', + 'destination' => $destination, ]; - $this->assertUrl('admin/config/search/redirect/add', $options); + $parsed_url = UrlHelper::parse($this->getUrl()); + $this->assertEqual(Url::fromRoute('redirect.add')->setAbsolute()->toString(), $parsed_url['path']); + $this->assertEqual($expected_query, $parsed_url['query']); $this->assertFieldByName('redirect_source[0][path]', 'non-existing2'); $this->drupalPostForm(NULL, $edit, t('Save')); $this->assertUrl('admin/config/search/redirect/404'); diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/redirect_domain.info.yml b/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/redirect_domain.info.yml index a9c7ce006..aeb66d453 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/redirect_domain.info.yml +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/redirect_domain.info.yml @@ -6,8 +6,8 @@ description: 'Allows users to redirect between domains.' dependencies: - redirect -# Information added by Drupal.org packaging script on 2018-05-04 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-10-16 +version: '8.x-1.3' core: '8.x' project: 'redirect' -datestamp: 1525419807 +datestamp: 1539682690 diff --git a/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/src/Form/RedirectDomainForm.php b/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/src/Form/RedirectDomainForm.php index ec7b20f69..1bf5b9a87 100644 --- a/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/src/Form/RedirectDomainForm.php +++ b/sites/all/modules/contrib/admin/redirect/modules/redirect_domain/src/Form/RedirectDomainForm.php @@ -133,7 +133,7 @@ class RedirectDomainForm extends ConfigFormBase { if ($redirects = $form_state->getValue('redirects')) { foreach ($redirects as $redirect) { if (strpos($redirect['from'], '://') !== FALSE) { - $form_state->setErrorByName('redirects', t('No protocol should be included in the redirect domain.')); + $form_state->setErrorByName('redirects', $this->t('No protocol should be included in the redirect domain.')); } } } @@ -160,6 +160,6 @@ class RedirectDomainForm extends ConfigFormBase { } $domain_config->set('domain_redirects', $domain_redirects); $domain_config->save(); - drupal_set_message(t('The domain redirects have been saved.')); + drupal_set_message($this->t('The domain redirects have been saved.')); } } diff --git a/sites/all/modules/contrib/admin/redirect/redirect.info.yml b/sites/all/modules/contrib/admin/redirect/redirect.info.yml index 3472dab6e..a1e6eb15d 100644 --- a/sites/all/modules/contrib/admin/redirect/redirect.info.yml +++ b/sites/all/modules/contrib/admin/redirect/redirect.info.yml @@ -8,8 +8,8 @@ dependencies: - drupal:link - drupal:views -# Information added by Drupal.org packaging script on 2018-05-04 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-10-16 +version: '8.x-1.3' core: '8.x' project: 'redirect' -datestamp: 1525419807 +datestamp: 1539682690 diff --git a/sites/all/modules/contrib/admin/redirect/src/Entity/Redirect.php b/sites/all/modules/contrib/admin/redirect/src/Entity/Redirect.php index ae54ec576..cb5d4c56d 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Entity/Redirect.php +++ b/sites/all/modules/contrib/admin/redirect/src/Entity/Redirect.php @@ -3,7 +3,6 @@ namespace Drupal\redirect\Entity; use Drupal\Component\Utility\Crypt; -use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityStorageInterface; @@ -25,7 +24,7 @@ use Drupal\link\LinkItemInterface; * "delete" = "Drupal\redirect\Form\RedirectDeleteForm", * "edit" = "Drupal\redirect\Form\RedirectForm" * }, - * "views_data" = "Drupal\views\EntityViewsData", + * "views_data" = "Drupal\redirect\RedirectViewsData", * "storage_schema" = "\Drupal\redirect\RedirectStorageSchema" * }, * base_table = "redirect", @@ -62,7 +61,7 @@ class Redirect extends ContentEntityBase { */ public static function generateHash($source_path, array $source_query, $language) { $hash = array( - 'source' => Unicode::strtolower($source_path), + 'source' => mb_strtolower($source_path), 'language' => $language, ); @@ -86,7 +85,9 @@ class Redirect extends ContentEntityBase { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage_controller) { - $this->set('hash', Redirect::generateHash($this->redirect_source->path, (array) $this->redirect_source->query, $this->language()->getId())); + // Get the language code directly from the field as language() might not + // be up to date if the language was just changed. + $this->set('hash', Redirect::generateHash($this->redirect_source->path, (array) $this->redirect_source->query, $this->get('language')->value)); } /** @@ -205,7 +206,9 @@ class Redirect extends ContentEntityBase { */ public function setRedirect($url, array $query = array(), array $options = array()) { $uri = $url . ($query ? '?' . UrlHelper::buildQuery($query) : ''); - $this->redirect_redirect->set(0, ['uri' => 'internal:/' . ltrim($uri, '/'), 'options' => $options]); + $external = UrlHelper::isValid($url, TRUE); + $uri = ($external ? $url : 'internal:/' . ltrim($uri, '/')); + $this->redirect_redirect->set(0, ['uri' => $uri, 'options' => $options]); } /** diff --git a/sites/all/modules/contrib/admin/redirect/src/EventSubscriber/RedirectRequestSubscriber.php b/sites/all/modules/contrib/admin/redirect/src/EventSubscriber/RedirectRequestSubscriber.php index 3161ccd42..87095deff 100644 --- a/sites/all/modules/contrib/admin/redirect/src/EventSubscriber/RedirectRequestSubscriber.php +++ b/sites/all/modules/contrib/admin/redirect/src/EventSubscriber/RedirectRequestSubscriber.php @@ -8,7 +8,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Core\Path\AliasManager; +use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; use Drupal\Core\Routing\TrustedRedirectResponse; use Drupal\Core\Url; @@ -81,7 +81,7 @@ class RedirectRequestSubscriber implements EventSubscriberInterface { * The language manager service. * @param \Drupal\Core\Config\ConfigFactoryInterface $config * The config. - * @param \Drupal\Core\Path\AliasManager $alias_manager + * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager * The alias manager service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler service. @@ -92,7 +92,7 @@ class RedirectRequestSubscriber implements EventSubscriberInterface { * @param \Symfony\Component\Routing\RequestContext * Request context. */ - public function __construct(RedirectRepository $redirect_repository, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config, AliasManager $alias_manager, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, RedirectChecker $checker, RequestContext $context, InboundPathProcessorInterface $path_processor) { + public function __construct(RedirectRepository $redirect_repository, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, RedirectChecker $checker, RequestContext $context, InboundPathProcessorInterface $path_processor) { $this->redirectRepository = $redirect_repository; $this->languageManager = $language_manager; $this->config = $config->get('redirect.settings'); @@ -126,9 +126,19 @@ class RedirectRequestSubscriber implements EventSubscriberInterface { // Get URL info and process it to be used for hash generation. parse_str($request->getQueryString(), $request_query); - // Do the inbound processing so that for example language prefixes are - // removed. - $path = $this->pathProcessor->processInbound($request->getPathInfo(), $request); + if (strpos($request->getPathInfo(), '/system/files/') === 0 && !$request->query->has('file')) { + // Private files paths are split by the inbound path processor and the + // relative file path is moved to the 'file' query string parameter. This + // is because the route system does not allow an arbitrary amount of + // parameters. We preserve the path as is returned by the request object. + // @see \Drupal\system\PathProcessor\PathProcessorFiles::processInbound() + $path = $request->getPathInfo(); + } + else { + // Do the inbound processing so that for example language prefixes are + // removed. + $path = $this->pathProcessor->processInbound($request->getPathInfo(), $request); + } $path = trim($path, '/'); $this->context->fromRequest($request); diff --git a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteForm.php b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteForm.php index ae3c2bcb7..eeee993d9 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteForm.php +++ b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteForm.php @@ -34,7 +34,7 @@ class RedirectDeleteForm extends ContentEntityConfirmFormBase { */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->entity->delete(); - drupal_set_message(t('The redirect %redirect has been deleted.', array('%redirect' => $this->entity->getRedirectUrl()->toString()))); + drupal_set_message($this->t('The redirect %redirect has been deleted.', array('%redirect' => $this->entity->getRedirectUrl()->toString()))); $form_state->setRedirect('redirect.list'); } diff --git a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteMultipleForm.php b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteMultipleForm.php index 9e1ed222e..79c79467c 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteMultipleForm.php +++ b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectDeleteMultipleForm.php @@ -101,7 +101,7 @@ class RedirectDeleteMultipleForm extends ConfirmFormBase { * {@inheritdoc} */ public function getConfirmText() { - return t('Delete'); + return $this->t('Delete'); } /** diff --git a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectForm.php b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectForm.php index 573fa99d6..702e99158 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectForm.php +++ b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectForm.php @@ -50,7 +50,7 @@ class RedirectForm extends ContentEntityForm { $redirect->setRedirect($redirect_url, $redirect_query, $redirect_options); } catch (MatchingRouteNotFoundException $e) { - drupal_set_message(t('Invalid redirect URL %url provided.', array('%url' => $redirect_url)), 'warning'); + drupal_set_message($this->t('Invalid redirect URL %url provided.', array('%url' => $redirect_url)), 'warning'); } } @@ -71,15 +71,15 @@ class RedirectForm extends ContentEntityForm { foreach (\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE) as $langcode => $language) { $form['language']['widget'][0]['value']['#options'][$langcode] = $language->getName(); } - $form['language']['widget'][0]['value']['#options'][LanguageInterface::LANGCODE_NOT_SPECIFIED] = t('- All languages -'); + $form['language']['widget'][0]['value']['#options'][LanguageInterface::LANGCODE_NOT_SPECIFIED] = $this->t('- All languages -'); } $default_code = $redirect->getStatusCode() ? $redirect->getStatusCode() : \Drupal::config('redirect.settings')->get('default_status_code'); $form['status_code'] = array( '#type' => 'select', - '#title' => t('Redirect status'), - '#description' => t('You can find more information about HTTP redirect status codes at @status-codes.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')), + '#title' => $this->t('Redirect status'), + '#description' => $this->t('You can find more information about HTTP redirect status codes at @status-codes.', array('@status-codes' => 'http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection')), '#default_value' => $default_code, '#options' => redirect_status_code_options(), ); @@ -96,13 +96,13 @@ class RedirectForm extends ContentEntityForm { $redirect = $form_state->getValue(array('redirect_redirect', 0)); if ($source['path'] == '') { - $form_state->setErrorByName('redirect_source', t('It is not allowed to create a redirect from the front page.')); + $form_state->setErrorByName('redirect_source', $this->t('It is not allowed to create a redirect from the front page.')); } if (strpos($source['path'], '#') !== FALSE) { - $form_state->setErrorByName('redirect_source', t('The anchor fragments are not allowed.')); + $form_state->setErrorByName('redirect_source', $this->t('The anchor fragments are not allowed.')); } if (strpos($source['path'], '/') === 0) { - $form_state->setErrorByName('redirect_source', t('The url to redirect from should not start with a forward slash (/).')); + $form_state->setErrorByName('redirect_source', $this->t('The url to redirect from should not start with a forward slash (/).')); } try { @@ -113,7 +113,7 @@ class RedirectForm extends ContentEntityForm { // a valid route. Otherwise the validation will fail on the redirect path // being an invalid route. if ($source_url->toString() == $redirect_url->toString()) { - $form_state->setErrorByName('redirect_redirect', t('You are attempting to redirect the page to itself. This will result in an infinite loop.')); + $form_state->setErrorByName('redirect_redirect', $this->t('You are attempting to redirect the page to itself. This will result in an infinite loop.')); } } catch (\InvalidArgumentException $e) { @@ -133,7 +133,7 @@ class RedirectForm extends ContentEntityForm { if (!empty($redirects)) { $redirect = array_shift($redirects); if ($this->entity->isNew() || $redirect->id() != $this->entity->id()) { - $form_state->setErrorByName('redirect_source', t('The source path %source is already being redirected. Do you want to edit the existing redirect?', + $form_state->setErrorByName('redirect_source', $this->t('The source path %source is already being redirected. Do you want to edit the existing redirect?', array( '%source' => $source['path'], '@edit-page' => $redirect->url('edit-form')))); @@ -146,7 +146,7 @@ class RedirectForm extends ContentEntityForm { */ public function save(array $form, FormStateInterface $form_state) { $this->entity->save(); - drupal_set_message(t('The redirect has been saved.')); + drupal_set_message($this->t('The redirect has been saved.')); $form_state->setRedirect('redirect.list'); } } diff --git a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectSettingsForm.php b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectSettingsForm.php index f2b7d1b2f..029973dcc 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Form/RedirectSettingsForm.php +++ b/sites/all/modules/contrib/admin/redirect/src/Form/RedirectSettingsForm.php @@ -58,7 +58,7 @@ class RedirectSettingsForm extends ConfigFormBase { $form['globals']['redirect_route_normalizer_enabled'] = array( '#type' => 'checkbox', '#title' => $this->t('Enforce clean and canonical URLs.'), - '#description' => $this->t('Enabling this will automatically redirect to the canonical URL of any page. That includes redirecting to an alias if existing, removing trainling slashes, ensure the language prefix is set and similar clean-up.'), + '#description' => $this->t('Enabling this will automatically redirect to the canonical URL of any page. That includes redirecting to an alias if existing, removing trailing slashes, ensure the language prefix is set and similar clean-up.'), '#default_value' => $config->get('route_normalizer_enabled'), ); $form['globals']['redirect_ignore_admin_path'] = array( @@ -87,7 +87,7 @@ class RedirectSettingsForm extends ConfigFormBase { } } $config->save(); - drupal_set_message(t('Configuration was saved.')); + drupal_set_message($this->t('Configuration was saved.')); } } diff --git a/sites/all/modules/contrib/admin/redirect/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php b/sites/all/modules/contrib/admin/redirect/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php index 729abe45a..0a4341116 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php +++ b/sites/all/modules/contrib/admin/redirect/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php @@ -64,7 +64,7 @@ class RedirectSourceWidget extends WidgetBase { // determine if we have a valid path. try { \Drupal::service('router')->match('/' . $form_state->getValue(array('redirect_source', 0, 'path'))); - $element['status_box'][]['#markup'] = '
' . t('The source path %path is likely a valid path. It is preferred to create URL aliases for existing paths rather than redirects.', + $element['status_box'][]['#markup'] = '
' . $this->t('The source path %path is likely a valid path. It is preferred to create URL aliases for existing paths rather than redirects.', array('%path' => $source_path, '@url-alias' => Url::fromRoute('path.admin_add')->toString())) . '
'; } catch (ResourceNotFoundException $e) { @@ -80,7 +80,7 @@ class RedirectSourceWidget extends WidgetBase { $redirects = $repository->findBySourcePath($path); if (!empty($redirects)) { $redirect = array_shift($redirects); - $element['status_box'][]['#markup'] = '
' . t('The base source path %source is already being redirected. Do you want to edit the existing redirect?', array('%source' => $source_path, '@edit-page' => $redirect->url('edit-form'))) . '
'; + $element['status_box'][]['#markup'] = '
' . $this->t('The base source path %source is already being redirected. Do you want to edit the existing redirect?', array('%source' => $source_path, '@edit-page' => $redirect->url('edit-form'))) . '
'; } } } diff --git a/sites/all/modules/contrib/admin/redirect/src/RedirectRepository.php b/sites/all/modules/contrib/admin/redirect/src/RedirectRepository.php index f8a1cea9a..236c927f6 100644 --- a/sites/all/modules/contrib/admin/redirect/src/RedirectRepository.php +++ b/sites/all/modules/contrib/admin/redirect/src/RedirectRepository.php @@ -112,10 +112,12 @@ class RedirectRepository { */ protected function findByRedirect(Redirect $redirect, $language) { $uri = $redirect->getRedirectUrl(); - $baseUrl = \Drupal::request()->getBaseUrl(); - $path = ltrim(substr($uri->toString(), strlen($baseUrl)), '/'); + $base_url = \Drupal::request()->getBaseUrl(); + $generated_url = $uri->toString(TRUE); + $path = ltrim(substr($generated_url->getGeneratedUrl(), strlen($base_url)), '/'); $query = $uri->getOption('query') ?: []; - return $this->findMatchingRedirect($path, $query, $language); + $return_value = $this->findMatchingRedirect($path, $query, $language); + return $return_value ? $return_value->addCacheableDependency($generated_url) : $return_value; } /** diff --git a/sites/all/modules/contrib/admin/redirect/src/RedirectViewsData.php b/sites/all/modules/contrib/admin/redirect/src/RedirectViewsData.php new file mode 100644 index 000000000..7e33ea51f --- /dev/null +++ b/sites/all/modules/contrib/admin/redirect/src/RedirectViewsData.php @@ -0,0 +1,25 @@ +assertEqual($headers[0][':status'], $expected_ending_status); } + + /** + * @inheritdoc} + */ + protected function drupalHead($path, array $options = [], array $headers = []) { + // Always just use getAbsolutePath() so that generating the link does not + // alter special requests. + $url = $this->getAbsoluteUrl($path); + $out = $this->curlExec([CURLOPT_NOBODY => TRUE, CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers]); + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + if ($this->dumpHeaders) { + $this->verbose('GET request to: ' . $path . + '
Ending URL: ' . $this->getUrl() . + '
Headers:
' . Html::escape(var_export(array_map('trim', $this->headers), TRUE)) . '
'); + } + + return $out; + } } diff --git a/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUILanguageTest.php b/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUILanguageTest.php index 6df6e1200..6d9a72791 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUILanguageTest.php +++ b/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUILanguageTest.php @@ -91,4 +91,35 @@ class RedirectUILanguageTest extends RedirectUITest { $this->assertRedirect('es/langpath', '/es/user', 'HTTP/1.1 301 Moved Permanently'); } + /** + * Test editing the redirect language. + */ + public function testEditRedirectLanguage() { + $this->drupalLogin($this->adminUser); + + // Add a redirect for english. + $this->drupalPostForm('admin/config/search/redirect/add', array( + 'redirect_source[0][path]' => 'langpath', + 'redirect_redirect[0][uri]' => '/user', + 'language[0][value]' => 'en', + ), t('Save')); + + // Check redirect for english. + $this->assertRedirect('langpath', '/user', 'HTTP/1.1 301 Moved Permanently'); + + // Check that redirect for Germany is not working. + $this->assertRedirect('de/langpath', NULL, 'HTTP/1.1 404 Not Found'); + + // Edit the redirect and change the language. + $this->drupalGet('admin/config/search/redirect'); + $this->clickLink('Edit'); + $this->drupalPostForm(NULL, ['language[0][value]' => 'de'], t('Save')); + + // Check redirect for english is NOT working now. + $this->assertRedirect('langpath', NULL, 'HTTP/1.1 404 Not Found'); + + // Check that redirect for Germany is now working. + $this->assertRedirect('de/langpath', '/de/user', 'HTTP/1.1 301 Moved Permanently'); + } + } diff --git a/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUITest.php b/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUITest.php index 8031afc87..67b066614 100644 --- a/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUITest.php +++ b/sites/all/modules/contrib/admin/redirect/src/Tests/RedirectUITest.php @@ -2,7 +2,6 @@ namespace Drupal\redirect\Tests; -use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Language\Language; use Drupal\Core\Logger\RfcLogLevel; @@ -357,7 +356,7 @@ class RedirectUITest extends WebTestBase { $vocabulary = entity_create('taxonomy_vocabulary', array( 'name' => $this->randomMachineName(), 'description' => $this->randomMachineName(), - 'vid' => Unicode::strtolower($this->randomMachineName()), + 'vid' => mb_strtolower($this->randomMachineName()), 'langcode' => Language::LANGCODE_NOT_SPECIFIED, 'weight' => mt_rand(0, 10), )); diff --git a/sites/all/modules/contrib/admin/redirect/tests/src/Kernel/RedirectAPITest.php b/sites/all/modules/contrib/admin/redirect/tests/src/Kernel/RedirectAPITest.php index f57e66bc0..6d982f7c1 100644 --- a/sites/all/modules/contrib/admin/redirect/tests/src/Kernel/RedirectAPITest.php +++ b/sites/all/modules/contrib/admin/redirect/tests/src/Kernel/RedirectAPITest.php @@ -125,6 +125,19 @@ class RedirectAPITest extends KernelTestBase { $this->fail('Failed to find a redirect by source path with query string.'); } + // Add a redirect to an external URL. + $external_redirect = $this->controller->create(); + $external_redirect->setSource('google'); + $external_redirect->setRedirect('https://google.com'); + $external_redirect->save(); + $found = $repository->findMatchingRedirect('google'); + if (!empty($found)) { + $this->assertEqual($found->getRedirectUrl()->toString(), 'https://google.com'); + } + else { + $this->fail('Failed to find a redirect for google.'); + } + // Hashes should be case-insensitive since the source paths are. /** @var \Drupal\redirect\Entity\Redirect $redirect */ $redirect = $this->controller->create(); diff --git a/sites/all/modules/contrib/admin/redirect/tests/src/Unit/RedirectRequestSubscriberTest.php b/sites/all/modules/contrib/admin/redirect/tests/src/Unit/RedirectRequestSubscriberTest.php index 4982bdb6e..e664e95ba 100644 --- a/sites/all/modules/contrib/admin/redirect/tests/src/Unit/RedirectRequestSubscriberTest.php +++ b/sites/all/modules/contrib/admin/redirect/tests/src/Unit/RedirectRequestSubscriberTest.php @@ -105,6 +105,7 @@ class RedirectRequestSubscriberTest extends UnitTestCase { return [ ['non-existing', ['key' => 'val'], '/test-path', ['dummy' => 'value']], ['non-existing/', ['key' => 'val'], '/test-path', ['dummy' => 'value']], + ['system/files/file.txt', [], '/test-path', []], ]; } @@ -134,9 +135,6 @@ class RedirectRequestSubscriberTest extends UnitTestCase { $checker->expects($this->any()) ->method('canRedirect') ->will($this->returnValue(TRUE)); - $checker->expects($this->any()) - ->method('isLoop') - ->will($this->returnValue(FALSE)); $context = $this->getMock('Symfony\Component\Routing\RequestContext'); @@ -146,7 +144,17 @@ class RedirectRequestSubscriberTest extends UnitTestCase { $inbound_path_processor->expects($this->any()) ->method('processInbound') ->with($request->getPathInfo(), $request) - ->will($this->returnValue($request->getPathInfo())); + ->willReturnCallback(function ($path, Request $request) { + if (strpos($path, '/system/files/') === 0 && !$request->query->has('file')) { + // Private files paths are split by the inbound path processor and the + // relative file path is moved to the 'file' query string parameter. + // This is because the route system does not allow an arbitrary amount + // of parameters. + // @see \Drupal\system\PathProcessor\PathProcessorFiles::processInbound() + $path = '/system/files'; + } + return $path; + }); $alias_manager = $this->getMockBuilder('Drupal\Core\Path\AliasManager') ->disableOriginalConstructor() @@ -189,9 +197,20 @@ class RedirectRequestSubscriberTest extends UnitTestCase { ->disableOriginalConstructor() ->getMock(); - $repository->expects($this->any()) - ->method($method) - ->will($this->returnValue($redirect)); + if ($method === 'findMatchingRedirect') { + $repository->expects($this->any()) + ->method($method) + ->willReturnCallback(function ($source_path) use ($redirect) { + // No redirect with source path 'system/files' exists. The stored + // redirect has 'system/files/file.txt' as source path. + return $source_path === 'system/files' ? NULL : $redirect; + }); + } + else { + $repository->expects($this->any()) + ->method($method) + ->will($this->returnValue($redirect)); + } return $repository; } diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php index 73749f70c..d9048d74e 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessRoleForm.php @@ -44,10 +44,10 @@ class WorkflowAccessRoleForm extends WorkflowConfigTransitionFormBase { */ public function buildHeader() { $header = [ - 'label_new' => t('State'), - 'view' => t('Roles who can view posts in this state'), - 'update' => t('Roles who can edit posts in this state'), - 'delete' => t('Roles who can delete posts in this state'), + 'label_new' => $this->t('State'), + 'view' => $this->t('Roles who can view posts in this state'), + 'update' => $this->t('Roles who can edit posts in this state'), + 'delete' => $this->t('Roles who can delete posts in this state'), ]; return $header; } @@ -93,7 +93,7 @@ class WorkflowAccessRoleForm extends WorkflowConfigTransitionFormBase { $row['label_new'] = [ '#type' => 'value', - '#markup' => t('@label', ['@label' => $state->label()]), + '#markup' => $this->t('@label', ['@label' => $state->label()]), ]; $row['view'] = [ '#type' => 'checkboxes', diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php index 16f7fd356..74925f3ab 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/src/Form/WorkflowAccessSettingsForm.php @@ -34,7 +34,7 @@ class WorkflowAccessSettingsForm extends ConfigFormBase { $form['workflow_access'] = [ '#type' => 'details', '#open' => TRUE, - '#title' => t('Workflow Access Settings'), + '#title' => $this->t('Workflow Access Settings'), ]; $form['workflow_access']['#tree'] = TRUE; @@ -42,11 +42,13 @@ class WorkflowAccessSettingsForm extends ConfigFormBase { $form['workflow_access']['workflow_access_priority'] = [ '#type' => 'weight', '#delta' => 10, - '#title' => t('Workflow Access Priority'), + '#title' => $this->t('Workflow Access Priority'), '#default_value' => $weight, - '#description' => t('This sets the node access priority. Changing this - setting can be dangerous. If there is any doubt, leave it at 0. - Read the manual.', [':url' => $url]), + '#description' => $this->t( + 'This sets the node access priority. Changing this setting can be + dangerous. If there is any doubt, leave it at 0. + Read the manual.', + [':url' => $url]), ]; return parent::buildForm($form, $form_state); diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml index efd289620..91d3f3283 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.info.yml @@ -1,13 +1,14 @@ -name: 'Workflow access' +name: Workflow Access +type: module description: 'Content access control based on workflows and roles. Depends on the node_access system, so only works for entities of type ''node''.' -dependencies: - - workflow package: Workflow # core: 8.x -type: module +dependencies: + - workflow:workflow +configure: entity.workflow_type.collection -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module index c4f619ea3..5c44ecbef 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_access/workflow_access.module @@ -22,30 +22,34 @@ function workflow_access_help($route_name, RouteMatchInterface $route_match) { switch ($route_name) { case 'entity.workflow_type.access_form': $url = \Drupal\Core\Url::fromRoute('workflow.access.settings'); - $output .= t("This page lets you refine the permissions per role and per - workflow state. Although the workflow module allows you to add multiple - workflows to per entity type, Workflow Access supports only one - workflow per entity type." + $output .= t( + 'This page lets you refine the permissions per role and per + workflow state. Although the workflow module allows you to add multiple + workflows to per entity type, Workflow Access supports only one + workflow per entity type.' ); $output .= "
"; - $output .= t("WARNING: Use of the 'Edit any', 'Edit own', and even 'View - published content' permissions for the content type may override these - access settings. You may disable those permissions or - alter the priority of + $output .= t( + "WARNING: Use of the 'Edit any', 'Edit own', and even 'View + published content' permissions for the content type may override these + access settings. You may disable those permissions or + alter the priority of the Workflow access module.", [':url' => $url->toString()] ); if (\Drupal::moduleHandler()->moduleExists('og')) { $output .= '
'; - $output .= t('WARNING: Organic Groups (OG) is present and may interfere - with these settings.'); + $output .= t( + 'WARNING: Organic Groups (OG) is present and may interfere + with these settings.' + ); //$output .= ' '; //$url = \Drupal\Core\Url::fromUri('admin/config/group/settings'); // @todo D8: FIXME when OG module is ported. //$output .= t("In particular, if Strict node access // permissions is enabled, since this may override Workflow access // settings.", [':url' => $url]); - $output .= t("In particular, if Strict node access - permissions is enabled, since this may override Workflow access - settings."); + $output .= t( + 'In particular, if Strict node access permissions is enabled, + since this may override Workflow access settings.'); } break; @@ -126,16 +130,11 @@ function workflow_access_get_role_gid($rid) { // $roles_gids = $config->get('content_access_roles_gids'); // return $roles_gids[$role]; + // Return a weight, avoiding negative values by starting with 100. + // For 'Author', no role exists. /* @var $role Role */ $role = \Drupal\user\Entity\Role::load($rid); - $weight = 100; // Avoid negative values. - if ($role) { - $weight += $role->getWeight(); - } - else { - // For Author, no role exists. - $weight -= 20; - } + $weight = $role ? 100 + $role->getWeight() : 100 - 20; return $weight; } @@ -148,86 +147,86 @@ function workflow_access_get_role_gid($rid) { */ function workflow_access_node_access_records(\Drupal\node\NodeInterface $node) { $grants = []; - $workflow_transitions_added = FALSE; - - //if (!$node->isPublished()) { - // // @todo: return; - //} - - $entity_type = $node->getEntityTypeId(); - // Only relevant for content with Workflow. - if (!isset($node->workflow_transitions)) { - $node->workflow_transitions = []; - // Sometimes, a node is saved without going through workflow_transition_form. - // E.g., - // - when saving a node with workflow_node programmatically with node_save(); - // - when changing a state on a node view page/history tab; - // - when rebuilding permissions via batch for Workflow Fields. - // In that case, we need to create the workflow_transitions ourselves to - // calculate the grants. - foreach (workflow_get_workflow_field_names($node, $entity_type) as $field_name) { - $old_sid = $new_sid = $node->$field_name->value; - - // Create a dummy transition, just to set $node->workflow_transitions. - if ($old_sid) { - /* @var $transition WorkflowTransitionInterface */ - $transition = WorkflowTransition::create([$old_sid, 'field_name' => $field_name]); - $transition->setTargetEntity($node); - $transition->setValues($new_sid, $node->getOwnerId(), \Drupal::time()->getRequestTime(), ''); - - // Store the transition, so it can be easily fetched later on. - // Store in an array, to prepare for multiple workflow_fields per entity. - // This is a.o. used in hook_entity_update to trigger 'transition post'. - $node->workflow_transitions[$field_name] = $transition; - $workflow_transitions_added = TRUE; - } - } + if (!$workflow_field_names = workflow_get_workflow_field_names($node, $node->getEntityTypeId())) { + return $grants; } - // Get 'author' of this entity. - // - Some entities (e.g., taxonomy_term) do not have a uid. - // But then again: node_access is only for nodes... - $uid = ($node->getOwnerId()) ? (int) $node->getOwnerId() : 0; + // Create grants for each translation of the node. $priority = workflow_access_get_setting('workflow_access_priority'); + foreach ($node->getTranslationLanguages() as $langcode => $language) { + $translation = $node->getTranslation($langcode); - $count = 0; - foreach ($node->workflow_transitions as $transition) { - // @todo: add support for +1 workflows per entity. - if ($count++ == 1) { - continue; + // @todo: how to handle not published entities? + // if (!$translation->isPublished()) { + // return; + // } + + // Get 'author' of this entity. Some entities (e.g., taxonomy_term) + // do not have a uid. But then again: node_access is only for nodes... + $uid = ($translation->getOwnerId()) ? (int) $translation->getOwnerId() : 0; + + $workflow_transitions = []; + if (isset($translation->workflow_transitions)) { + $workflow_transitions = $translation->workflow_transitions; + } + else { + // Sometimes, a node is saved without going through workflow_transition_form. + // E.g., + // - when saving a node with workflow_node programmatically with node_save(); + // - when changing a state on a node view page/history tab; + // - when rebuilding permissions via batch for Workflow Fields. + // In that case, we need to create the workflow_transitions ourselves to + // calculate the grants. + foreach ($workflow_field_names as $field_name) { + // Create a dummy transition, just to set $workflow_transitions. + $old_sid = $new_sid = $translation->{$field_name}->value; + if ($old_sid) { + /* @var $transition WorkflowTransitionInterface */ + $transition = WorkflowTransition::create([$old_sid, 'field_name' => $field_name]); + $transition->setTargetEntity($translation); + $transition->setValues($new_sid, $translation->getOwnerId(), \Drupal::time()->getRequestTime(), ''); + + $workflow_transitions[$field_name] = $transition; + } + } } - $field_name = $transition->getFieldName(); - if ($current_sid = workflow_node_current_state($node, $field_name)) { - foreach (workflow_access_get_workflow_access_by_sid($current_sid) as $rid => $grant) { - $realm = ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) ? 'workflow_access_owner' : 'workflow_access'; - $gid = ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) ? $uid : workflow_access_get_role_gid($rid); + $count_workflow_fields = 0; + foreach ($workflow_transitions as $field_name => $transition) { + // @todo: add support for +1 workflows per entity. + if (++$count_workflow_fields > 1) { + continue; + } - // Anonymous ($uid == 0) author is not allowed for role 'author' (== -1). + if (!$current_sid = workflow_node_current_state($translation, $field_name)) { + continue; + } + + foreach (workflow_access_get_workflow_access_by_sid($current_sid) as $rid => $grant) { + // Anonymous ($uid == 0) author is not allowed for role 'Author' (== -1). // Both logically (Anonymous having more rights then authenticated) - // and technically: $gid must be a positive int. + // and technically ($gid must be a positive integer). if ($uid == 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) { continue; } $grants[] = [ - 'realm' => $realm, - 'gid' => $gid, + 'realm' => ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) + ? 'workflow_access_owner' : 'workflow_access', + 'gid' => ($uid > 0 && $rid == WORKFLOW_ROLE_AUTHOR_RID) + ? $uid : workflow_access_get_role_gid($rid), 'grant_view' => (int) $grant['grant_view'], 'grant_update' => (int) $grant['grant_update'], 'grant_delete' => (int) $grant['grant_delete'], 'priority' => $priority, - // 'langcode' => $node->language(), + 'langcode' => $langcode, 'field_name' => $field_name, // Just for analysis and info. ]; } } } - if ($workflow_transitions_added == TRUE) { - unset($node->workflow_transitions); - } return $grants; } @@ -241,12 +240,16 @@ function workflow_access_node_access_explain($row) { static $interpretations = []; switch ($row->realm) { case 'workflow_access_owner': - $interpretations[$row->gid] = t('Workflow access: author of the content may access'); + $interpretations[$row->gid] = t( + 'Workflow access: author of the content may access' + ); break; case 'workflow_access': $roles = user_roles(); - $interpretations[$row->gid] = t('Workflow access: %role may access', ['%role' => $roles[$row->gid]]); + $interpretations[$row->gid] = t( + 'Workflow access: %role may access', ['%role' => $roles[$row->gid]] + ); break; } return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL); diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php b/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php index 3529b0bad..09629fb18 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/src/Form/WorkflowCleanupSettingsForm.php @@ -52,9 +52,11 @@ class WorkflowCleanupSettingsForm extends FormBase { $form['no_workflow'] = [ '#type' => 'details', - '#title' => t('Orphaned States'), + '#title' => $this->t('Orphaned States'), '#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE. - '#description' => t('These states no longer belong to an existing workflow.'), + '#description' => $this->t( + 'These states no longer belong to an existing workflow.' + ), '#tree' => TRUE, ]; foreach ($orphans as $sid => $state) { @@ -67,9 +69,11 @@ class WorkflowCleanupSettingsForm extends FormBase { $form['inactive'] = [ '#type' => 'details', - '#title' => t('Inactive (Deleted) States'), + '#title' => $this->t('Inactive (Deleted) States'), '#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE. - '#description' => t('These states belong to a workflow, but have been marked inactive (deleted).'), + '#description' => $this->t( + 'These states belong to a workflow, but have been marked inactive (deleted).' + ), '#tree' => TRUE, ]; foreach ($inactive as $sid => $state) { @@ -82,7 +86,7 @@ class WorkflowCleanupSettingsForm extends FormBase { $form['submit'] = [ '#type' => 'submit', - '#value' => t('Delete selected states'), + '#value' => $this->t('Delete selected states'), ]; return $form; diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml index 013457fcf..bcbb2100a 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_cleanup/workflow_cleanup.info.yml @@ -4,10 +4,11 @@ description: 'Cleans up Workflow cruft. (Do not enable in production sites.)' package: Workflow # core: 8.x dependencies: - - workflow + - workflow:workflow configure: workflow.cleanup.settings -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' + +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml index 289111d6f..8237c4f96 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_devel/workflow_devel.info.yml @@ -1,14 +1,13 @@ -name: 'Workflow Devel' +name: Workflow Devel +type: module description: 'Tools for developing Workflow add-ons. Calls every hook in workflow.api.php and generates a user message on each call. (Do not enable in production sites.)' package: Workflow -type: module # core: 8.x - dependencies: - - workflow + - workflow:workflow -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml index 5ce571094..7af4fb227 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_field/workflowfield.info.yml @@ -1,12 +1,13 @@ -name: 'Workflow Field' +name: Workflow Field +type: module description: 'A residual submodule. No need to enable this module for installations with workflow 8.x-1.0-beta6 and later.' package: Workflow -type: module # core: 8.x hidden: TRUE -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' + +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml index 0abc72732..1ac46c676 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_operations/workflow_operations.info.yml @@ -1,14 +1,13 @@ -name: 'Workflow operations' -description: "Adds lesser-used Transition permissions (edit, revert, delete permissions)." -package: Workflow +name: Workflow Operations type: module +description: 'Adds lesser-used Transition permissions (edit, revert, delete permissions).' +package: Workflow # core: 8.x - dependencies: - - workflow + - workflow:workflow -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php b/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php index 14eadde74..bf1395cfa 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/src/Controller/WorkflowStateListBuilder.php @@ -14,8 +14,6 @@ use Drupal\workflow\Entity\WorkflowState; */ class WorkflowStateListBuilder extends DraggableListBuilder { - // @TODO D8-port WorkflowStateListBuilder: enable machine_name: interactive WorkflowState element. - /** * Load the Transitions, and filter for Workflow type. * {@inheritdoc} @@ -23,9 +21,7 @@ class WorkflowStateListBuilder extends DraggableListBuilder { public function load() { $entities = []; - // Get the Workflow from the page. if (!$workflow = workflow_url_get_workflow()) { - // @todo: Generate error message. return $entities; } @@ -46,7 +42,6 @@ class WorkflowStateListBuilder extends DraggableListBuilder { */ public function getFormId() { return 'workflow_state_form'; - //return parent::getFormId(); } /** @@ -82,13 +77,11 @@ class WorkflowStateListBuilder extends DraggableListBuilder { public function buildRow(EntityInterface $entity) { $row = []; - // Get the Workflow from the page. - /** @var $workflow \Drupal\workflow\Entity\Workflow */ if (!$workflow = workflow_url_get_workflow()) { - workflow_debug( __FILE__, __FUNCTION__, __LINE__); // @todo D8-port: still test this snippet. + return $row; } - $wid = $url_wid = $workflow->id(); + $wid = $url_wid = $workflow->id(); /** @var $entity \Drupal\workflow\Entity\WorkflowState */ $state = $entity; $sid = $state->id(); @@ -101,10 +94,8 @@ class WorkflowStateListBuilder extends DraggableListBuilder { $state_options += workflow_get_workflow_state_names($wid, FALSE); // Make it impossible to reassign to the same state that is disabled. - if ($state->isCreationState() || !$sid || !$state->isActive()) { - $current_state_options = []; - } - else { + $current_state_options = []; + if ($state->isActive() && !$state->isCreationState() && $sid) { $current_state = [$sid => $state_options[$sid]]; $current_state_options = array_diff($state_options, $current_state); } @@ -126,6 +117,7 @@ class WorkflowStateListBuilder extends DraggableListBuilder { '#maxlength' => 255, '#default_value' => $label, '#title' => NULL, // This hides the red 'required' asterisk. + '#disabled' => !$state->isActive(), // '#required' => TRUE, ]; $row['id'] = [ @@ -137,12 +129,12 @@ class WorkflowStateListBuilder extends DraggableListBuilder { '#default_value' => $state->id(), // N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned. '#required' => FALSE, - // @TODO D8-port: enable machine_name: interactive WorkflowState element. + // @TODO D8-port: enable machine_name as interactive WorkflowState element. '#machine_name' => [ 'exists' => [$this, 'exists'], // Local helper function, at the bottom of this class. -// 'source' => ['label_new'], + // 'source' => ['label_new'], 'source' => ['states', $state->id(), 'label_new'], -// 'replace_pattern' =>'([^a-z0-9_]+)|(^custom$)', + // 'replace_pattern' =>'([^a-z0-9_]+)|(^custom$)', 'replace_pattern' => '[^a-z0-9_()]+', // Added '()' characters from exclusion list since creation state has it. 'error' => $this->t('The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores.'), ], @@ -154,6 +146,7 @@ class WorkflowStateListBuilder extends DraggableListBuilder { $row['status'] = [ '#type' => 'checkbox', '#default_value' => $state->isActive(), + '#disabled' => $state->isCreationState() || !$sid, ]; // The new value of states that are inactivated. $row['reassign'] = [ @@ -166,21 +159,11 @@ class WorkflowStateListBuilder extends DraggableListBuilder { '#markup' => $count, ]; - // Disable some properties for Creation state. - if ($state->isCreationState()) { - $row['status']['#disabled'] = TRUE; - $row['reassign']['#type'] = 'hidden'; - $row['reassign']['#disabled'] = TRUE; - } // New state and disabled states cannot be reassigned. - if (!$sid || !$state->isActive() || ($count == 0) ) { + if (!$state->isActive() || $state->isCreationState() || !$sid || !$count) { $row['reassign']['#type'] = 'hidden'; $row['reassign']['#disabled'] = TRUE; } - // Disabled states cannot be renamed (and is a visual clue, too). - if (!$state->isActive()) { - $row['label_new']['#disabled'] = TRUE; - } return $row + parent::buildRow($entity); } @@ -189,12 +172,10 @@ class WorkflowStateListBuilder extends DraggableListBuilder { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - // Get the Workflow from the page. - /** @var $workflow \Drupal\workflow\Entity\Workflow */ + if (!$workflow = workflow_url_get_workflow()) { return $form; } - $wid = $workflow->getWorkflowId(); $form = parent::buildForm($form, $form_state); // Add a sticky header. @@ -202,21 +183,35 @@ class WorkflowStateListBuilder extends DraggableListBuilder { '#sticky' => TRUE, ]; + $wid = $workflow->id(); // Build select options for reassigning states. // We put a blank state first for validation. $state_options = workflow_get_workflow_state_names($wid, FALSE); // Is this the last state available? $form['#last_mohican'] = (count($state_options) == 1); + $form['entities']['#prefix'] = '
'; + $form['entities']['#suffix'] = '
'; // Create a placeholder WorkflowState (It must NOT be saved to DB). Add it to the item list. - $sid = ''; - $placeholder = $workflow->createState($sid, FALSE); - $placeholder->set('label', ''); - $this->entities['placeholder'] = $placeholder; - $form['entities']['placeholder'] = $this->buildRow($placeholder); - + if ($form_state->getTriggeringElement()['#name'] === 'add_state') { + $sid = ''; + $placeholder = $workflow->createState($sid, FALSE); + $placeholder->set('label', ''); + $this->entities['placeholder'] = $placeholder; + $form['entities']['placeholder'] = $this->buildRow($placeholder); + } // Rename 'submit' button. $form['actions']['submit']['#value'] = t('Save'); + // Add 'Add State' button. + $form['actions']['add_state'] = [ + '#name' => 'add_state', + '#type' => 'submit', + '#value' => t('Add State'), + '#ajax' => [ + 'callback' => '::addStateCallback', + 'wrapper' => 'states_table_wrapper', + ] + ]; return $form; } @@ -230,8 +225,6 @@ class WorkflowStateListBuilder extends DraggableListBuilder { /** @var $state \Drupal\workflow\Entity\WorkflowState */ $state = $entity; - // $workflow = $state->getWorkflow(); - /** * Allow modules to insert their own workflow operations to the list. */ @@ -251,43 +244,24 @@ class WorkflowStateListBuilder extends DraggableListBuilder { * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { - + // @TODO D8-port WorkflowStateListBuilder: enable machine_name as interactive WorkflowState element. foreach ($form_state->getValue($this->entitiesKey) as $sid => $value) { - if (isset($this->entities[$sid])) { - /** @var $state WorkflowState */ - $state = $this->entities[$sid]; + /** @var $state WorkflowState */ + $state = isset($this->entities[$sid]) ? $this->entities[$sid] : NULL; - // Does user want to deactivate the state (reassign current content)? - if ($sid && $value['status'] == 0 && $state->isActive()) { - workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); // @todo D8-port: still test this snippet. - $args = ['%state' => $state->label()]; - // Does that state have content in it? - if ($value['count'] > 0 && empty($value['reassign'])) { - if ($form['#last_mohican']) { - $message = 'Since you are deleting the last available - workflow state in this workflow, all content items - which are in that state will have their workflow state - removed.'; - drupal_set_message($this->t($message, $args), 'warning'); - } - else { - $message = 'The %state state has content; you must - reassign the content to another state.'; - $form_state->setErrorByName("states'][$sid]['reassign'", $this->t($message, $args)); - } - } + // State is de-activated (reassigning current content). + if ($state && $state->isActive() && !$value['status']) { + $args = ['%state' => $state->label()]; + // Does that state have content in it? + if (!$form['#last_mohican'] && $value['count'] > 0 && empty($value['reassign'])) { + $message = 'The %state state has content; you must + reassign the content to another state.'; + $form_state->setErrorByName("states'][$sid]['reassign'", $this->t($message, $args)); } - - // Create the machine_name for new states. - // N.B.: Keep machine_name in WorkflowState and ~ListBuilder aligned. - if ($value['label_new'] && !$value['id']) { - //$message = 'Machine name is required.'; - //$form_state->setErrorByName('machine_name', $this->t($message)); - } - } } - return; + + return []; } /** @@ -297,12 +271,9 @@ class WorkflowStateListBuilder extends DraggableListBuilder { * The WorkflowState entities are always saved. */ public function submitForm(array &$form, FormStateInterface $form_state) { - // parent::submitForm($form, $form_state); - // Get the Workflow from the page. - /** @var $workflow \Drupal\workflow\Entity\Workflow */ if (!$workflow = workflow_url_get_workflow()) { - return ; + return []; } // The default min_weight is -10. Work with it. @@ -320,7 +291,7 @@ class WorkflowStateListBuilder extends DraggableListBuilder { } // Does user want to deactivate the state (reassign current content)? - if ($sid && $value['status'] == 0 && $state->isActive()) { + if ($state && $state->isActive() && !$value['status'] && $sid) { $new_sid = $value['reassign']; $new_state = WorkflowState::load($new_sid); @@ -335,6 +306,11 @@ class WorkflowStateListBuilder extends DraggableListBuilder { $new_sid = NULL; // Do not reassign to new state. $message = 'Removing workflow states from content in the %workflow.'; drupal_set_message($this->t($message, $args)); + $message = 'Since you have deleted the last available + workflow state in this workflow, all content items + which with this %workflow workflow have their workflow state + removed.'; + drupal_set_message($this->t($message, $args), 'warning'); } else { // Prepare the state delete function. @@ -354,7 +330,7 @@ class WorkflowStateListBuilder extends DraggableListBuilder { $max_weight = max($max_weight, $state->get($this->weightKey)); // Is this a new state? - if ($sid == 'placeholder' && empty(!$value['label_new'])) { + if ($sid === 'placeholder' && empty(!$value['label_new'])) { // New state, add it. $state->set('id', $value['id']); // Set a proper weight to the new state. @@ -370,18 +346,42 @@ class WorkflowStateListBuilder extends DraggableListBuilder { $state->set('label', $value['label_new']); $state->set('status', $value['status']); - $state->save(); + try { + $state->save(); + } catch(\Exception $e) { + $message = 'ID %id already exists'; + $args = ['%id' => $state->id()]; + return drupal_set_message($this->t($message, $args), 'error'); + } } - } - drupal_set_message(t('The Workflow states have been updated.')); - return; + if ($form_state->getTriggeringElement()['#name'] === 'add_state') { + // Unset previous input in placeholder and rebuild the form. + $input = $form_state->getUserInput(); + unset($input['entities']['placeholder']['label_new']); + $form_state->setUserInput($input); + $form_state->setRebuild(); + } + + return drupal_set_message($this->t('The Workflow states have been updated.')); } /** - * Validate duplicate machine names. Function registered in 'machine_name' - * form element. + * 'Add State' button callback. + * + * @param array $form + * @param FormStateInterface $form_state + * + * @return + */ + public function addStateCallback(array &$form, FormStateInterface $form_state) { + return $form['entities']; + } + + /** + * Validate duplicate machine names. + * Function registered in 'machine_name' form element. * * @param string $name * @param array $element @@ -402,5 +402,4 @@ class WorkflowStateListBuilder extends DraggableListBuilder { } return FALSE; } - } diff --git a/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml b/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml index 5f8b6a55e..3acd00b65 100644 --- a/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml +++ b/sites/all/modules/contrib/admin/workflow/modules/workflow_ui/workflow_ui.info.yml @@ -1,17 +1,14 @@ -name: 'Workflow UI' +name: Workflow UI +type: module description: 'Administrative UI for Workflow.' package: Workflow -type: module # core: 8.x -# version: VERSION - +dependencies: + - workflow:workflow configure: entity.workflow_type.collection -dependencies: - - workflow - -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php b/sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php index 672bb714a..a2b7459e3 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/Workflow.php @@ -162,7 +162,7 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface { } else { // Delete associated state (also deletes any associated transitions). - foreach ($this->getStates($all = TRUE) as $state) { + foreach ($this->getStates(TRUE) as $state) { $state->deactivate(''); $state->delete(); } @@ -179,7 +179,7 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface { $is_valid = TRUE; // Don't allow Workflow without states. There should always be a creation state. - $states = $this->getStates($all = FALSE); + $states = $this->getStates(FALSE); if (count($states) < 1) { // That's all, so let's remind them to create some states. $message = t('Workflow %workflow has no states defined, so it cannot be assigned to content yet.', @@ -211,7 +211,7 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface { public function isDeletable() { // May not be deleted if assigned to a Field. - foreach ($fields = _workflow_info_fields() as $field_info) { + foreach (_workflow_info_fields() as $field_info) { if ($field_info->getSetting('workflow_type') == $this->id()) { return FALSE; } @@ -238,7 +238,8 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface { /* @var $state WorkflowState */ $state = WorkflowState::load($sid); if (!$state || $wid != $state->getWorkflowId()) { - $state = WorkflowState::create($values = ['id' => $sid, 'wid' => $wid]); + $values = ['id' => $sid, 'wid' => $wid]; + $state = WorkflowState::create($values); if ($save) { $state->save(); } @@ -256,7 +257,7 @@ class Workflow extends ConfigEntityBase implements WorkflowInterface { public function getCreationState() { // First, find it. if (!$this->creation_state) { - foreach ($this->getStates($all = TRUE) as $state) { + foreach ($this->getStates(TRUE) as $state) { if ($state->isCreationState()) { $this->creation_state = $state; $this->creation_sid = $state->id(); diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php index a21a76415..50b9b58cc 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowConfigTransition.php @@ -151,7 +151,6 @@ class WorkflowConfigTransition extends ConfigEntityBase implements WorkflowConfi public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) { // Sort the entities using the entity class's sort() method. // See \Drupal\Core\Config\Entity\ConfigEntityBase::sort(). - /** @var WorkflowTransitionInterface $a */ /** @var WorkflowTransitionInterface $b */ if (!$a->getFromSid() || !$b->getFromSid()) { diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php index 8ddf7f117..06493beb5 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowManager.php @@ -139,14 +139,14 @@ class WorkflowManager implements WorkflowManagerInterface { foreach (workflow_get_workflow_field_names($entity) as $field_name) { // Transition is created in widget or WorkflowTransitionForm. /** @var $transition WorkflowTransitionInterface */ - $transition = $entity->$field_name->__get('workflow_transition'); + $transition = $entity->{$field_name}->__get('workflow_transition'); if (!$transition) { // We come from creating/editing an entity via entity_form, with core widget or hidden Workflow widget. // @todo D8: from an Edit form with hidden widget. /** @noinspection PhpUndefinedFieldInspection */ if ($entity->original) { // Editing a Node with hidden Widget. State change not possible, so bail out. -// $entity->$field_name->value = $entity->original->$field_name->value; +// $entity->{$field_name}->value = $entity->original->{$field_name}->value; // continue; } @@ -154,7 +154,7 @@ class WorkflowManager implements WorkflowManagerInterface { // @todo D8: Test Creating a non-Node Entity with hidden Workflow widget. $comment = ''; $old_sid = WorkflowManager::getPreviousStateId($entity, $field_name); - $new_sid = $entity->$field_name->value; + $new_sid = $entity->{$field_name}->value; $field_info = FieldStorageConfig::loadByName($entity->getEntityTypeId(), $field_name); if ((!$new_sid) && $wid = $field_info->getSetting('workflow_type')) { /** @var Workflow $workflow */ @@ -170,7 +170,13 @@ class WorkflowManager implements WorkflowManagerInterface { // but upon insert, the old version didn't have an ID, yet. $transition->setTargetEntity($entity); - if ($transition->isScheduled()) { + if ($transition->isExecuted()) { + // Sometimes (due to Rules, or extra programming) it can happen that + // a Transition is executed twice in a call. This check avoids that + // situation, that generates message "Transition is executed twice". + $executed = TRUE; + } + elseif ($transition->isScheduled()) { $executed = $transition->save(); // Returns a positive integer. } elseif ($entity->getEntityTypeId() == 'comment') { @@ -284,19 +290,25 @@ class WorkflowManager implements WorkflowManagerInterface { public static function cancelUser($edit, AccountInterface $account, $method) { switch ($method) { - case 'user_cancel_block': // Disable the account and keep its content. - case 'user_cancel_block_unpublish': // Disable the account and unpublish its content. + case 'user_cancel_block': + // Disable the account and keep its content. + case 'user_cancel_block_unpublish': + // Disable the account and unpublish its content. // Do nothing. break; - case 'user_cancel_reassign': // Delete the account and make its content belong to the Anonymous user. - case 'user_cancel_delete': // Delete the account and its content. - - // Update tables for deleted account, move account to user 0 (anon.) - // ALERT: This may cause previously non-Anonymous posts to suddenly - // be accessible to Anonymous. + case 'user_cancel_reassign': + // Delete the account and make its content belong to the Anonymous user. + case 'user_cancel_delete': + // Delete the account and its content. + /** + * Update tables for deleted account, move account to user 0 (anon.) + * ALERT: This may cause previously non-Anonymous posts to suddenly + * be accessible to Anonymous. + */ /** - * Given a user id, re-assign history to the new user account. Called by user_delete(). + * Given a user id, re-assign history to the new user account. + * Called by user_delete(). */ $uid = $account->id(); $new_uid = 0; @@ -386,13 +398,13 @@ class WorkflowManager implements WorkflowManagerInterface { // If $field_name is not known, yet, determine it. $field_name = ($field_name) ? $field_name : workflow_get_field_name($entity, $field_name); // If $field_name is found, get more details. - if (!$field_name || !isset($entity->$field_name)) { + if (!$field_name || !isset($entity->{$field_name})) { // Return the initial value. return $sid; } // Normal situation: get the value. - $sid = $entity->$field_name->value; + $sid = $entity->{$field_name}->value; // Entity is new or in preview or there is no current state. Use previous state. // (E.g., content was created before adding workflow.) diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php index 0f70a2500..bb708fc40 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowScheduledTransition.php @@ -78,7 +78,7 @@ class WorkflowScheduledTransition extends WorkflowTransition { // this function is disabled for now. // @todo: this function is only called in the WorkflowTransitionForm, not in the Widget. // @todo: repair https://www.drupal.org/node/2896650 - + // // The following is from return parent::validate(); $this->validated = TRUE; //$violations = $this->getTypedData()->validate(); @@ -148,13 +148,6 @@ class WorkflowScheduledTransition extends WorkflowTransition { return $result; } - /** - * {@inheritdoc} - */ -// public static function loadMultiple(array $ids = NULL) { -// return parent::loadMultiple($ids); -// } - /** * {@inheritdoc} */ @@ -207,7 +200,6 @@ class WorkflowScheduledTransition extends WorkflowTransition { return $transitions; } - /** * Property functions. */ diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php index 35653f7e5..165eb58b4 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowState.php @@ -6,8 +6,8 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\Entity; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Session\AccountInterface; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\workflow\WorkflowTypeAttributeTrait; use Drupal\workflow\WorkflowURLRouteParametersTrait; @@ -248,13 +248,12 @@ class WorkflowState extends ConfigEntityBase { if ($new_sid) { // A candidate for the batch API. // @TODO: Future updates should seriously consider setting this with batch. - $user = \Drupal::currentUser(); // We can use global, since deactivate() is a UI-only function. $comment = t('Previous state deleted'); foreach (_workflow_info_fields() as $field_info) { $entity_type_id = $field_info->getTargetEntityTypeId(); - $field_name = $field_info->getName(); // @todo: overwrites key? + $field_name = $field_info->getName(); $result = []; // CommentForm's are not re-parented upon Deactivate WorkflowState. @@ -458,7 +457,6 @@ class WorkflowState extends ConfigEntityBase { // unset($transitions[$transition->id()]); // } // } - return $transitions; } @@ -542,7 +540,7 @@ class WorkflowState extends ConfigEntityBase { $count = 0; $sid = $this->id(); - foreach ($fields = _workflow_info_fields() as $field_info) { + foreach (_workflow_info_fields() as $field_info) { $field_name = $field_info->getName(); $query = \Drupal::entityQuery($field_info->getTargetEntityTypeId()); // @see #2285983 for using SQLite on D7. diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php index 9cbd54a3f..8c28242ce 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowStorage.php @@ -73,35 +73,4 @@ class WorkflowStorage extends ConfigEntityStorage { // return $events; } - /** - * {@inheritdoc} - */ - public function doLoadMultiple(array $ids = NULL) { - $return = parent::doLoadMultiple($ids); - - return $return; - } - - /** - * {@inheritdoc} - */ - public function save(EntityInterface $entity) { - $return = parent::save($entity); - -// // Update the state of registered events. -// // @todo Should we trigger a container rebuild here as well? Might be a bit -// // expensive on every save? -// $this->stateService->set('rules.registered_events', $this->getRegisteredEvents()); - - return $return; - } - - /** - * {@inheritdoc} - */ - public function delete(array $entities) { - $return = parent::delete($entities); - return $return; - } - } diff --git a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php index 2563d6f4c..7be052cd8 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php +++ b/sites/all/modules/contrib/admin/workflow/src/Entity/WorkflowTransition.php @@ -186,9 +186,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition * {@inheritdoc} */ public function save() { - // return parent::save(); - - // Set Target Entity to be used by Rules. + // Set Target Entity, to be used by Rules. $reference = $this->get('entity_id')->first(); $reference->set('entity', $this->getTargetEntity()); @@ -215,12 +213,10 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition if ($this->isEmpty()) { return SAVED_UPDATED; } - // Update the transition, if already exists. if ($this->id()) { return parent::save(); } - // Insert the transition. Make sure it hasn't already been inserted. // @todo: Allow a scheduled transition per revision. // @todo: Allow a state per language version (langcode). @@ -230,19 +226,9 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition $found_transition->getToSid() == $this->getToSid()) { return SAVED_UPDATED; } - else { - return parent::save(); - } - + return parent::save(); } - /** - * {@inheritdoc} - */ -// public static function loadMultiple(array $ids = NULL) { -// return parent::loadMultiple($ids); -// } - /** * {@inheritdoc} */ @@ -329,7 +315,6 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition /** * Check if anything has changed in this transition. - * @todo: check for attached fields. * * @return bool */ @@ -340,7 +325,14 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition if ($this->getComment()) { return FALSE; } - // @todo: check for attached fields. + $fields = WorkflowManager::getAttachedFields('workflow_transition', $this->bundle()); + /** @var \Drupal\Core\Field\Entity\BaseFieldOverride $field */ + foreach ($fields as $field_name => $field) { + if (!$this->{$field_name}->isEmpty()) { + return FALSE; + } + } + return true; } @@ -454,13 +446,11 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition } $force = $this->isForced(); - // @todo D8-port: figure out usage of $entity->workflow_transitions[$field_name] - /* - // Store the transition, so it can be easily fetched later on. - // Store in an array, to prepare for multiple workflow_fields per entity. - // This is a.o. used in hook_entity_update to trigger 'transition post'. - $entity->workflow_transitions[$field_name] = $this; - */ + // Store the transition(s), so it can be easily fetched later on. + // This is a.o. used in: + // - hook_entity_update to trigger 'transition post'. + // - hook workflow_access_node_access_records + $entity->workflow_transitions[$field_name] = $this; if (!$this->isValid()) { return $from_sid; // <-- exit !!! @@ -506,7 +496,8 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition } else { // The transition is allowed, but not scheduled. - // Let other modules modify the comment. The transition (in context) contains all relevant data. + // Let other modules modify the comment. + // The transition (in context) contains all relevant data. $context = ['transition' => $this]; \Drupal::moduleHandler()->alter('workflow_comment', $comment, $context); $this->setComment($comment); @@ -532,7 +523,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition // Notify modules that transition has occurred. // Action triggers should take place in response to this callback, not the 'transaction pre'. - + // //\Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]); // We have a problem here with Rules, Trigger, etc. when invoking // 'transition post': the entity has not been saved, yet. we are still @@ -595,8 +586,8 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition // N.B. Align the following functions: // - WorkflowDefaultWidget::massageFormValues(); // - WorkflowManager::executeTransition(). - $entity->$field_name->workflow_transition = $this; - $entity->$field_name->value = $this->getToSid(); + $entity->{$field_name}->workflow_transition = $this; + $entity->{$field_name}->value = $this->getToSid(); $entity->save(); } @@ -607,7 +598,6 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition public function post_execute($force = FALSE) { // @todo D8-port: This function post_execute() is not yet used. workflow_debug(__FILE__, __FUNCTION__, __LINE__); // @todo D8-port: Test this snippet. - if (!$this->isEmpty()) { $user = $this->getOwner(); \Drupal::moduleHandler()->invokeAll('workflow', ['transition post', $this, $user]); @@ -654,7 +644,7 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition return $this->entity; } // @todo D8: the following line only returns Node, not Term. - // return $this->entity = $this->get('entity_id')->entity; + /* return $this->entity = $this->get('entity_id')->entity; */ $entity_type = $this->getTargetEntityTypeId(); if ($id = $this->getTargetEntityId()) { @@ -773,10 +763,10 @@ class WorkflowTransition extends ContentEntityBase implements WorkflowTransition * {@inheritdoc} */ public function schedule($schedule = TRUE) { -// // We do a tricky thing here. The id of the entity is altered, so -// // all functions of another subclass are called. -// $this->entityTypeId = ($schedule) ? 'workflow_scheduled_transition' : 'workflow_transition'; - + // We do a tricky thing here. The id of the entity is altered, so + // all functions of another subclass are called. + // $this->entityTypeId = ($schedule) ? 'workflow_scheduled_transition' : 'workflow_transition'; + // $this->is_scheduled = $schedule; return $this; } diff --git a/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php b/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php index d63cb79b4..e190fe3a0 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php +++ b/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTransitionForm.php @@ -28,7 +28,7 @@ class WorkflowTransitionForm extends ContentEntityForm { // when multiple fields or entities are shown on 1 page. // Test this f.i. by checking the'scheduled' box. It wil // not unfold. - $form_id = parent::getFormId(); + // $form_id = parent::getFormId(); /** @var $transition \Drupal\workflow\Entity\WorkflowTransitionInterface */ $transition = $this->entity; @@ -122,7 +122,7 @@ class WorkflowTransitionForm extends ContentEntityForm { $actions = parent::actions($form, $form_state); // A default button is provided by core. Override it. - $actions['submit']['#value'] = t('Update workflow'); + $actions['submit']['#value'] = $this->t('Update workflow'); $actions['submit']['#attributes'] = ['class' => ['form-save-default-button']]; if (!_workflow_use_action_buttons()) { @@ -159,6 +159,7 @@ class WorkflowTransitionForm extends ContentEntityForm { /** * {@inheritdoc} */ + /** @noinspection PhpMissingParentCallCommonInspection */ public function buildEntity(array $form, FormStateInterface $form_state) { /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ $entity = clone $this->entity; @@ -186,6 +187,7 @@ class WorkflowTransitionForm extends ContentEntityForm { * * This is called from submitForm(). */ + /** @noinspection PhpMissingParentCallCommonInspection */ public function save(array $form, FormStateInterface $form_state) { // Execute transition and update the attached entity. /** @var WorkflowTransitionInterface $transition */ diff --git a/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php b/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php index db93357fa..2bfbe6db6 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php +++ b/sites/all/modules/contrib/admin/workflow/src/Form/WorkflowTypeForm.php @@ -15,15 +15,20 @@ class WorkflowTypeForm extends EntityForm { * {@inheritdoc} */ public function form(array $form, FormStateInterface $form_state) { - $noyes = [0 => t('No'), 1 => t('Yes')]; - $fieldset_options = [0 => t('No fieldset'), 1 => t('Collapsible fieldset'), 2 => t('Collapsed fieldset')]; + $fieldset_options = [ + 0 => $this->t('No fieldset'), + 1 => $this->t('Collapsible fieldset'), + 2 => $this->t('Collapsed fieldset') + ]; /* @var $workflow Workflow */ $workflow = $this->entity; $form['label'] = [ '#type' => 'textfield', - '#description' => t('The human-readable label of the workflow. This will be used as a label when - the workflow status is shown during editing of content.'), + '#description' => $this->t( + 'The human-readable label of the workflow.This will be used as a label + when the workflow status is shown during editing of content.' + ), '#title' => $this->t('Label'), '#default_value' => $this->entity->label(), '#required' => TRUE, @@ -31,68 +36,85 @@ class WorkflowTypeForm extends EntityForm { $form['id'] = [ '#type' => 'machine_name', - '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), + '#description' => $this->t( + 'A unique machine-readable name. Can only contain lowercase letters, + numbers, and underscores.' + ), '#disabled' => !$this->entity->isNew(), '#default_value' => $this->entity->id(), '#machine_name' => [ 'exists' => [$this, 'exists'], 'replace_pattern' => '([^a-z0-9_]+)|(^custom$)', - 'error' => $this->t('The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".'), + 'error' => $this->t( + 'The machine-readable name must be unique, and can only contain + lowercase letters, numbers, and underscores. + Additionally, it can not be the reserved word "custom".' + ), ], ]; $form['permissions'] = [ '#type' => 'details', - '#title' => t('Workflow permissions'), + '#title' => $this->t('Workflow permissions'), '#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE. - '#description' => t("To enable further Workflow functionality, go to the - /admin/people/permissions page and select any roles that should have - access to below and other functionalities."), + '#description' => $this->t( + 'To enable further Workflow functionality, go to the + /admin/people/permissions page and select any roles that should + have access to below and other functionality.'), ]; $form['permissions']['transition_execute'] = [ '#type' => 'item', - '#title' => t('Participate in workflow (create, execute transitions)'), - '#markup' => t("You can determine which roles are enabled on the - 'Workflow Transitions & roles' configuration page. Use this to enable - only the relevant roles. Some sites have too many roles to show on - the configuration page."), + '#title' => $this->t( + 'Participate in workflow (create, execute transitions)' + ), + '#markup' => $this->t( + "You can determine which roles are enabled on the + 'Workflow Transitions & roles' configuration page. Use this to enable + only the relevant roles. Some sites have too many roles to show on + the configuration page." + ), ]; $form['permissions']['transition_schedule'] = [ '#type' => 'item', - '#title' => t('Schedule state transition'), - '#markup' => t("Workflow transitions may be scheduled to a moment in the - future. Soon after the desired moment, the transition is executed by - Cron. This may be hidden by settings in widgets, formatters or permissions." + '#title' => $this->t('Schedule state transition'), + '#markup' => $this->t( + 'Workflow transitions may be scheduled to a moment + in the future. Soon after the desired moment, the transition is + executed by Cron. This may be hidden by settings in widgets, + formatters or permissions.' ), ]; $form['permissions']['history_tab'] = [ '#type' => 'item', - '#title' => t('Access Workflow history tab'), - '#markup' => t("You can determine if a tab 'Workflow history' is - shown on the entity view page, which gives access to the History of - the workflow. - If you have multiple workflows per bundle, better disable this feature, - and use, clone & adapt the Views display 'Workflow history per Entity'."), + '#title' => $this->t('Access Workflow history tab'), + '#markup' => $this->t( + "You can determine if a tab 'Workflow history' is shown on the entity + view page, which gives access to the History of the workflow. + If you have multiple workflows per bundle, better disable this + feature, and use, clone & adapt the Views display + 'Workflow history per Entity'." + ), ]; $form['basic'] = [ '#type' => 'details', - '#title' => t('Workflow form settings'), - // '#description' => t('Lorem ipsum.'), + '#title' => $this->t('Workflow form settings'), '#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE. ]; $form['basic']['fieldset'] = [ '#type' => 'select', '#options' => $fieldset_options, - '#title' => t('Show the form in a fieldset?'), - '#default_value' => isset($workflow->options['fieldset']) ? $workflow->options['fieldset'] : 0, + '#title' => $this->t('Show the form in a fieldset?'), + '#default_value' => isset($workflow->options['fieldset']) + ? $workflow->options['fieldset'] : 0, ]; $form['basic']['options'] = [ '#type' => 'select', - '#title' => t('How to show the available states'), + '#title' => $this->t('How to show the available states'), '#required' => FALSE, - '#default_value' => isset($workflow->options['options']) ? $workflow->options['options'] : 'radios', + '#default_value' => isset($workflow->options['options']) + ? $workflow->options['options'] : 'radios', // '#multiple' => TRUE / FALSE, '#options' => [ // These options are taken from options.module @@ -101,28 +123,33 @@ class WorkflowTypeForm extends EntityForm { 'buttons' => 'Action buttons', 'dropbutton' => 'Drop button', ], - '#description' => t("The Widget shows all available states. Decide which - is the best way to show them." + '#description' => $this->t( + 'The Widget shows all available states. + Decide which is the best way to show them.' ), ]; $form['basic']['name_as_title'] = [ '#type' => 'checkbox', '#attributes' => ['class' => ['container-inline']], - '#title' => t('Use the workflow name as the title of the workflow form'), - '#default_value' => isset($workflow->options['name_as_title']) ? $workflow->options['name_as_title'] : 0, - '#description' => t( + '#title' => $this->t( + 'Use the workflow name as the title of the workflow form' + ), + '#default_value' => isset($workflow->options['name_as_title']) + ? $workflow->options['name_as_title'] : 0, + '#description' => $this->t( 'The workflow section of the editing form is in its own fieldset. - Checking the box will add the workflow name as the title of workflow - section of the editing form.' + Checking the box will add the workflow name as the title of workflow + section of the editing form.' ), ]; $form['basic']['schedule_timezone'] = [ '#type' => 'checkbox', - '#title' => t('Show a timezone when scheduling a transition.'), + '#title' => $this->t('Show a timezone when scheduling a transition.'), '#required' => FALSE, - '#default_value' => isset($workflow->options['schedule_timezone']) ? $workflow->options['schedule_timezone'] : 1, + '#default_value' => isset($workflow->options['schedule_timezone']) + ? $workflow->options['schedule_timezone'] : 1, ]; $form['basic']['comment_log_node'] = [ @@ -130,30 +157,34 @@ class WorkflowTypeForm extends EntityForm { '#required' => FALSE, '#options' => [ // Use 0/1/2 to stay compatible with previous checkbox. - 0 => t('hidden'), - 1 => t('optional'), - 2 => t('required'), + 0 => $this->t('hidden'), + 1 => $this->t('optional'), + 2 => $this->t('required'), ], '#attributes' => ['class' => ['container-inline']], - '#title' => t('How to show the Comment subfield'), - '#default_value' => isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1, - '#description' => t( - 'A Comment area can be shown on the Workflow Transition form so that the person - making a state change can record reasons for doing so. The comment is - then included in the content\'s workflow history.' + '#title' => $this->t('How to show the Comment sub-field'), + '#default_value' => isset($workflow->options['comment_log_node']) + ? $workflow->options['comment_log_node'] : 1, + '#description' => $this->t( + 'A Comment area can be shown on the Workflow Transition form so that + the person making a state change can record reasons for doing so. + The comment is then included in the content\'s workflow history.' ), ]; $form['watchdog'] = [ '#type' => 'details', - '#title' => t('Watchdog'), - '#description' => t('Informational watchdog messages can be logged when a transition is executed (state of a node is changed).'), + '#title' => $this->t('Watchdog'), + '#description' => $this->t( + 'Informational watchdog messages can be logged when a transition is + executed (state of a node is changed).' + ), '#open' => TRUE, // Controls the HTML5 'open' attribute. Defaults to FALSE. ]; $form['watchdog']['watchdog_log'] = [ '#type' => 'checkbox', - '#title' => t('Log watchdog messages upon state change'), + '#title' => $this->t('Log watchdog messages upon state change'), '#default_value' => isset($workflow->options['watchdog_log']) ? $workflow->options['watchdog_log'] : 0, '#description' => '', ]; @@ -197,9 +228,12 @@ class WorkflowTypeForm extends EntityForm { $args = [ '%label' => $entity->label(), '%action' => $action, - 'link' => $entity->toLink(t('Edit'))->toString(), + 'link' => $entity->toLink($this->t('Edit'))->toString(), ]; - drupal_set_message($this->t('Workflow %label has been %action. Please maintain the permissions, states and transitions.', $args)); + drupal_set_message($this->t( + 'Workflow %label has been %action. Please maintain the permissions, + states and transitions.', $args) + ); $this->logger('workflow')->notice('Workflow %label has been %action.', $args); if ($status == SAVED_NEW) { diff --git a/sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php b/sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php index 74c197fee..45f03c2c1 100644 --- a/sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php +++ b/sites/all/modules/contrib/admin/workflow/src/Plugin/Field/FieldWidget/WorkflowDefaultWidget.php @@ -30,7 +30,7 @@ class WorkflowDefaultWidget extends WidgetBase { return [ /* 'workflow_default' => array( - 'label' => t('Workflow'), + 'label' => $this->t('Workflow'), 'field types' => array('workflow'), 'settings' => array( 'fieldset' => 0, @@ -112,7 +112,7 @@ class WorkflowDefaultWidget extends WidgetBase { // $form = $this->element($form, $form_state, $transition); //$element['workflow_transition'] = array( // '#type' => 'workflow_transition', - // '#title' => t('Workflow transition'), + // '#title' => $this->t('Workflow transition'), // '#default_value' => $transition, // ); @@ -170,7 +170,7 @@ class WorkflowDefaultWidget extends WidgetBase { // Try to execute the transition. Return $from_sid when error. if (!$transition) { // This should not be possible (perhaps when testing/developing). - drupal_set_message(t('Error: the transition from %from_sid to %to_sid could not be generated.'), 'error'); + drupal_set_message($this->t('Error: the transition from %from_sid to %to_sid could not be generated.'), 'error'); // The current value is still the previous state. $to_sid = $from_sid = 0; } diff --git a/sites/all/modules/contrib/admin/workflow/workflow.field.inc b/sites/all/modules/contrib/admin/workflow/workflow.field.inc index 872d85f04..ba0a6e845 100644 --- a/sites/all/modules/contrib/admin/workflow/workflow.field.inc +++ b/sites/all/modules/contrib/admin/workflow/workflow.field.inc @@ -16,7 +16,10 @@ use Drupal\Core\Form\FormStateInterface; * @todo: perhaps this is core functionality, but these values are only saved * when the site builder explicitly save the instance settings. :-( */ -function workflow_form_field_storage_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { +function workflow_form_field_storage_config_edit_form_alter(&$form, + FormStateInterface $form_state, + /** @noinspection PhpUnusedParameterInspection */ $form_id) { + /** @noinspection PhpUndefinedMethodInspection */ $field_name = $form_state->getFormObject()->getEntity()->getType(); if ($field_name == 'workflow') { @@ -32,7 +35,10 @@ function workflow_form_field_storage_config_edit_form_alter(&$form, FormStateInt /** * Implements hook_form_FORM_ID_alter(). */ -function workflow_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) { +function workflow_form_field_config_edit_form_alter(&$form, + FormStateInterface $form_state, + /** @noinspection PhpUnusedParameterInspection */ $form_id) { + /** @noinspection PhpUndefinedMethodInspection */ $field_name = $form_state->getFormObject()->getEntity()->getType(); if ($field_name == 'workflow') { @@ -102,15 +108,15 @@ function workflow_state_formatter($entity, $field_name, $current_sid = '') { } // Clone the entity and restore old value, in case you want to show an // executed transition. - if ($entity->$field_name->value != $current_sid) { + if ($entity->{$field_name}->value != $current_sid) { $entity = clone $entity; - $entity->$field_name->value = $current_sid; + $entity->{$field_name}->value = $current_sid; } // Generate a renderable array for the field. Use default language determination ($langcode = NULL). // First, add the 'current value' formatter for this field. // $list_display = $instance['display']['default']; $list_display['type'] = 'list_default'; - $element = $entity->$field_name->view($list_display); + $element = $entity->{$field_name}->view($list_display); // @todo D8: make weight better (even better: hook_field_extra_fields). // Make sure the current value is before the form. (which has weight = 0.005) @@ -121,7 +127,7 @@ function workflow_state_formatter($entity, $field_name, $current_sid = '') { /** * This function is a residue for installations that have v8.x-1.0 - * The setting for options_allowed_values is still in the Field confuguration + * The setting for options_allowed_values is still in the Field configuration * Saving the field data again does not remove the settings. * So, we copy code from options_allowed_values(). * diff --git a/sites/all/modules/contrib/admin/workflow/workflow.info.yml b/sites/all/modules/contrib/admin/workflow/workflow.info.yml index 62c98945c..cc92fca74 100644 --- a/sites/all/modules/contrib/admin/workflow/workflow.info.yml +++ b/sites/all/modules/contrib/admin/workflow/workflow.info.yml @@ -1,17 +1,16 @@ -name: 'Workflow' +name: Workflow +type: module description: 'Defines a field type with Workflows, containing customizable state transitions.' package: Workflow -type: module # core: 8.x -# version: VERSION dependencies: - - field - - options - - user + - drupal:field + - drupal:options + - drupal:user -# Information added by Drupal.org packaging script on 2018-09-04 -version: '8.x-1.1+18-dev' +# Information added by Drupal.org packaging script on 2019-01-29 +version: '8.x-1.1+35-dev' core: '8.x' project: 'workflow' -datestamp: 1536080308 +datestamp: 1548801486 diff --git a/sites/all/modules/contrib/admin/workflow/workflow.module b/sites/all/modules/contrib/admin/workflow/workflow.module index 5f8ccd7ec..9c5285800 100644 --- a/sites/all/modules/contrib/admin/workflow/workflow.module +++ b/sites/all/modules/contrib/admin/workflow/workflow.module @@ -14,30 +14,40 @@ define('WORKFLOW_CREATION_STATE_NAME', 'creation'); /** * Role ID for anonymous users. */ -// #2657072 brackets are added later to indicate a special role, and distinguish from frequently used 'author' role. + +// #2657072 brackets are added later to indicate a special role, +// and distinguish from frequently used 'author' role. define('WORKFLOW_ROLE_AUTHOR_NAME', 'Author'); define('WORKFLOW_ROLE_AUTHOR_RID', 'workflow_author'); +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Url; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\user\Entity\User; use Drupal\workflow\Entity\Workflow; use Drupal\workflow\Entity\WorkflowManager; use Drupal\workflow\Entity\WorkflowScheduledTransition; use Drupal\workflow\Entity\WorkflowState; use Drupal\workflow\Entity\WorkflowTransition; +use Drupal\workflow\Entity\WorkflowTransitionInterface; -module_load_include('inc', 'workflow', 'workflow.form'); -module_load_include('inc', 'workflow', 'workflow.field'); +\Drupal::moduleHandler()->loadInclude('workflow', 'inc', 'workflow.form'); +\Drupal::moduleHandler()->loadInclude('workflow', 'inc', 'workflow.field'); /********************************************************************** - * * Info hooks. - * */ /** * Implements hook_help(). * - * @param $route_name + * @param string $route_name + * A route. + * * @return string + * The html output. */ function workflow_help($route_name) { $output = ''; @@ -64,9 +74,7 @@ function workflow_hook_info() { } /********************************************************************** - * * CRUD hooks. - * */ /** @@ -76,21 +84,25 @@ function workflow_hook_info() { * ALERT: This may cause previously non-Anonymous posts to suddenly * be accessible to Anonymous. * - * @see hook_user_cancel() - * * @param $edit - * @param $account - * @param $method + * @param \Drupal\Core\Session\AccountInterface $account + * An account object. + * @param string $method + * + * @see hook_user_cancel() */ -function workflow_user_cancel($edit, $account, $method) { + +function workflow_user_cancel($edit, AccountInterface $account, $method) { WorkflowManager::cancelUser($edit, $account, $method); } /** * Implements hook_user_delete(). - * @todo: hook_user_delete does not exist. hook_ENTITY_TYPE_delete? * - * @param $account + * @param \Drupal\Core\Session\AccountInterface $account + * An account object. + * + * @todo: hook_user_delete does not exist. hook_ENTITY_TYPE_delete? */ function workflow_user_delete($account) { WorkflowManager::deleteUser($account); @@ -103,8 +115,9 @@ function workflow_user_delete($account) { * The technical name for the Workflow entity is 'workflow_type'. * * @param \Drupal\Core\Entity\EntityInterface $entity + * An Entity. */ -function workflow_workflow_type_insert(\Drupal\Core\Entity\EntityInterface $entity) { +function workflow_workflow_type_insert(EntityInterface $entity) { WorkflowManager::participateUserRoles($entity); } @@ -112,19 +125,21 @@ function workflow_workflow_type_insert(\Drupal\Core\Entity\EntityInterface $enti * Implements hook_entity_insert(). * * @param \Drupal\Core\Entity\EntityInterface $entity + * An Entity. */ -function workflow_entity_insert(\Drupal\Core\Entity\EntityInterface $entity) { +function workflow_entity_insert(EntityInterface $entity) { // Execute updates in hook_presave() to revert executions, // Execute inserts in hook_insert, to have the Entity ID determined. - _workflow_execute_transitions($entity); + _workflow_execute_transitions($entity); } /** * Implements hook_entity_presave(). * * @param \Drupal\Core\Entity\EntityInterface $entity + * An EntityInterface object. */ -function workflow_entity_presave(\Drupal\Core\Entity\EntityInterface $entity) { +function workflow_entity_presave(EntityInterface $entity) { if (!$entity->isNew()) { // Avoid a double call by hook_entity_presave and hook_entity_insert. _workflow_execute_transitions($entity); @@ -132,14 +147,15 @@ function workflow_entity_presave(\Drupal\Core\Entity\EntityInterface $entity) { } /** - * Execute transitions. if prohibited, restore original field value. + * Execute transitions. if prohibited, restore original field value. * - insert: use hook_insert(), to have the Entity ID determined when saving transitions. * - update: use hook_presave() to revert executions, * - so, do not use hook_update(). * * @param \Drupal\Core\Entity\EntityInterface $entity + * An Entity. */ -function _workflow_execute_transitions(\Drupal\Core\Entity\EntityInterface $entity) { +function _workflow_execute_transitions(EntityInterface $entity) { // Avoid this hook on workflow objects. if (WorkflowManager::isWorkflowEntityType($entity->getEntityTypeId())) { return; @@ -155,9 +171,10 @@ function _workflow_execute_transitions(\Drupal\Core\Entity\EntityInterface $enti * Delete the corresponding workflow table records. * * @param \Drupal\Core\Entity\EntityInterface $entity + * An Entity. */ -function workflow_entity_delete(\Drupal\Core\Entity\EntityInterface $entity) { - // @todo D8: test with multiple workflows. +function workflow_entity_delete(EntityInterface $entity) { + // @todo: test with multiple workflows. if (get_class($entity) == 'Drupal\field\Entity\FieldConfig' || get_class($entity) == 'Drupal\field\Entity\FieldStorageConfig') { // A Workflow Field is removed from an entity. @@ -204,14 +221,15 @@ function workflow_cron() { */ /** - * @deprecated D8: @see WorkflowManager::executeTransition(). - * * @param \Drupal\workflow\Entity\WorkflowTransitionInterface $transition + * A WorkflowTransition. * @param bool $force + * Indicator if the transition must be forces. * * @return string + * A string. */ -function workflow_execute_transition(Drupal\workflow\Entity\WorkflowTransitionInterface $transition, $force = FALSE) { +function workflow_execute_transition(WorkflowTransitionInterface $transition, $force = FALSE) { // Execute transition and update the attached entity. return $transition->executeAndUpdateEntity($force); } @@ -226,13 +244,13 @@ function workflow_execute_transition(Drupal\workflow\Entity\WorkflowTransitionIn * - user_role * - workflow * - workflow_state - * - sid + * - sid. */ /** * Retrieves the names of roles matching specified conditions. * - * deprecated D8: workflow_get_roles --> workflow_get_user_role_names + * Deprecated D8: workflow_get_roles --> workflow_get_user_role_names. * * Usage: * D7: $roles = workflow_get_user_role_names('participate in workflow'); @@ -284,7 +302,6 @@ function workflow_get_workflow_state_names($wid = '', $grouped = FALSE) { // @see callback_allowed_values_function() // @see options_allowed_values() - // Get the (user-dependent) options. // Since this function is only used in UI, it is save to use the global $user. $user = workflow_current_user(); @@ -316,6 +333,7 @@ function workflow_get_workflow_state_names($wid = '', $grouped = FALSE) { * * @param bool $required * Indicates if the resulting list contains a options value. + * * @return array * An array of $wid => workflow->label(). */ @@ -338,13 +356,19 @@ function workflow_get_workflow_names($required = TRUE) { /** * Gets an Options list of field names. * - * @param null $entity + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. * @param string $entity_type + * An entity_type. * @param string $entity_bundle + * An entity. * @param string $field_name + * A field name. + * * @return array + * An list of field names. */ -function workflow_get_workflow_field_names($entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') { +function workflow_get_workflow_field_names(EntityInterface $entity = NULL, $entity_type = '', $entity_bundle = '', $field_name = '') { $result = []; foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle, $field_name) as $definition) { $field_name2 = $definition->getName(); @@ -355,12 +379,13 @@ function workflow_get_workflow_field_names($entity = NULL, $entity_type = '', $e /** * Helper function, to get the label of a given State Id. + * deprecated: workflow_get_sid_label() --> workflow_get_sid_name() * - * @param $sid + * @param string $sid + * A State ID. * * @return string - * - * deprecated: workflow_get_sid_label() --> workflow_get_sid_name() + * An translated label. */ function workflow_get_sid_name($sid) { @@ -387,8 +412,9 @@ function workflow_get_sid_name($sid) { * The field name. If given, will be passed as return value. * * @return string + * The found Field name. */ -function workflow_get_field_name(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') { +function workflow_get_field_name(EntityInterface $entity, $field_name = '') { if (!$entity) { // $entity may be empty on Entity Add page. return ''; @@ -411,23 +437,28 @@ function workflow_get_field_name(\Drupal\Core\Entity\EntityInterface $entity, $f * We use UserInterface to check permissions. * * @param \Drupal\Core\Session\AccountInterface|null $account + * An Account. * * @return \Drupal\user\UserInterface + * A User. */ -function workflow_current_user(\Drupal\Core\Session\AccountInterface $account = NULL) { +function workflow_current_user(AccountInterface $account = NULL) { $account = ($account) ? $account : \Drupal::currentUser(); - return \Drupal\user\Entity\User::load($account->id()); + return User::load($account->id()); } /** * Gets the current state ID of a given entity. * * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. * @param string $field_name + * A Field name. * - * @return string $current_sid + * @return string + * The current State ID. */ -function workflow_node_current_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') { +function workflow_node_current_state(EntityInterface $entity, $field_name = '') { return WorkflowManager::getCurrentStateId($entity, $field_name); } @@ -435,28 +466,30 @@ function workflow_node_current_state(\Drupal\Core\Entity\EntityInterface $entity * Gets the previous state ID of a given entity. * * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity. * @param string $field_name + * A field_name. * - * @return string $previous_sid + * @return string + * The previous State ID. */ -function workflow_node_previous_state(\Drupal\Core\Entity\EntityInterface $entity, $field_name = '') { +function workflow_node_previous_state(EntityInterface $entity, $field_name = '') { return WorkflowManager::getPreviousStateId($entity, $field_name); } /** - * Get a specific workflow, given an entity type. Only one workflow is possible per node type. + * Get a specific workflow, given an entity type. + * Only one workflow is possible per node type. + * Caveat: gives undefined results with multiple workflows per entity. + * @todo: support multiple workflows per entity. * * @param string $entity_bundle * An entity bundle. * @param string $entity_type * An entity type. This is passed when also the Field API must be checked. * - * @return Workflow + * @return \Drupal\workflow\Entity\Workflow * A Workflow object, or NULL if no workflow is retrieved. - * - * Caveat: gives undefined results with multiple workflows per entity. - * - * @todo: support multiple workflows per entity. */ function workflow_get_workflows_by_type($entity_bundle, $entity_type) { static $map = []; @@ -480,6 +513,13 @@ function workflow_get_workflows_by_type($entity_bundle, $entity_type) { return $map[$entity_type][$entity_bundle]; } +/** + * Finds the Workflow fields on a given Entity type. + * + * @return mixed + * + * @see EntityFieldManager::getFieldMapByFieldType + */ function workflow_get_workflow_fields_by_entity_type() { return \Drupal::service('entity_field.manager')->getFieldMapByFieldType('workflow'); } @@ -517,25 +557,26 @@ function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle foreach ($data as $f_name => $value) { if (!$entity_bundle || isset($value['bundles'][$entity_bundle])) { if (!$field_name || ($field_name == $f_name)) { - // Do not use the field_name as ID, but the unique . - // since you cannot share the same field on multiple entity_types (unlike D7). - $field_config = \Drupal\field\Entity\FieldStorageConfig::loadByName($e_type, $f_name); + // Do not use the field_name as ID, but the + // unique . since you cannot share the + // same field on multiple entity_types (unlike D7). + $field_config = FieldStorageConfig::loadByName($e_type, $f_name); if ($field_config) { $field_info[$field_config->id()] = $field_config; } else { - // The field is a base/extra field not a configurable Field via Field UI. - + // The field is a base/extra field. + // not a configurable Field via Field UI. // Re-fetch the field definitions, with extra data. $field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($e_type, $entity_bundle); - // @todo ?? Loop over bundles? + // @todo: ?? Loop over bundles? /** @var \Drupal\Core\Field\BaseFieldDefinition $field_config */ $field_config = $field_definitions[$f_name]; if ($field_config) { $field_info[$field_config->getUniqueStorageIdentifier()] = $field_config; } else { - // @todo ?? Loop over bundles? + // @todo: ?? Loop over bundles? } } } @@ -551,12 +592,15 @@ function _workflow_info_fields($entity = NULL, $entity_type = '', $entity_bundle * * This is a hack. It should be solved by using $route_match. * - * @param \Drupal\Core\Entity\EntityInterface|null $entity + * @param \Drupal\Core\Entity\EntityInterface $entity + * An optional entity. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * A route. * * @return \Drupal\Core\Entity\EntityInterface + * Entity from the route. */ -function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = NULL, \Drupal\Core\Routing\RouteMatchInterface $route_match = NULL) { +function workflow_url_get_entity(EntityInterface $entity = NULL, RouteMatchInterface $route_match = NULL) { if ($entity) { return $entity; } @@ -567,7 +611,7 @@ function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = N $entities = []; foreach ($route_match->getParameters() as $param) { - if ($param instanceof \Drupal\Core\Entity\EntityInterface) { + if ($param instanceof EntityInterface) { $entities[] = $param; } } @@ -581,7 +625,7 @@ function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = N // On workflow tab, we'd get an id. // This is an indicator that the route is mal-configured. workflow_debug(__FILE__, __FUNCTION__, __LINE__, 'route declaration is not optimal.'); - // return $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($value); + /* Return $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($value); */ return NULL; } @@ -593,7 +637,8 @@ function workflow_url_get_entity(\Drupal\Core\Entity\EntityInterface $entity = N * * For now only used for ../{entity_id}/workflow history tab. * - * @return string|null $field_name + * @return string|null + * Return $field_name */ function workflow_url_get_field_name() { return workflow_url_get_parameter('field_name'); @@ -603,9 +648,10 @@ function workflow_url_get_field_name() { * Helper function to get the entity from a route. * * @return mixed|string + * Return $operation */ function workflow_url_get_operation() { - $url = \Drupal\Core\Url::fromRoute(''); + $url = Url::fromRoute(''); // The last part of the path is the operation: edit, workflow, devel. $url_parts = explode('/', $url->toString()); $operation = array_pop($url_parts); @@ -619,30 +665,35 @@ function workflow_url_get_operation() { /** * Helper function to get arbitrary parameter from a route. * - * @param $parameter + * @param string $parameter * The requested parameter. - * @return string $field_name + * + * @return string + * field_name */ function workflow_url_get_parameter($parameter) { return \Drupal::routeMatch()->getParameter($parameter); - // return \Drupal::request()->get($parameter); + // Return \Drupal::request()->get($parameter); } /** * Helper function to determine Workflow from Workflow UI URL. - * @return Workflow + * + * @return \Drupal\workflow\Entity\Workflow + * Workflow Object. */ function workflow_url_get_workflow() { /** @var $workflows \Drupal\workflow\Entity\Workflow[] */ static $workflows = []; $wid = workflow_url_get_parameter('workflow_type'); - if (is_object($wid)) { // $wid is a Workflow object. + if (is_object($wid)) { + // $wid is a Workflow object. return $wid; } - // $wid is a string if (!isset($workflows[$wid])) { + // $wid is a string. $workflows[$wid] = $wid ? Workflow::load($wid) : NULL; } return $workflows[$wid]; @@ -654,6 +705,7 @@ function workflow_url_get_workflow() { * Used in file workflow_ui.routing.yml. * * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * the page title. */ function workflow_url_get_title() { $label = ''; @@ -673,9 +725,12 @@ function workflow_url_get_title() { * Helper function to determine Workflow from Workflow UI URL. * * @param string $url + * URL. + * * @return mixed + * the Workflow type. */ -function workflow_url_get_form_type($url = '' ) { +function workflow_url_get_form_type($url = '') { // For some reason, $_SERVER is not allowed as default. $url = ($url == '') ? $_SERVER['REQUEST_URI'] : $url; @@ -687,18 +742,19 @@ function workflow_url_get_form_type($url = '' ) { /** * Helper function for D8-port: Get some info on screen. - * @see workflow_devel-module - * - * Usage: - * workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); // @todo D8-port: still test this snippet. * * @param string $class_name * @param string $function_name - * @param string $line + * @param string $line_nr * @param string $value1 * @param string $value2 + * + * @see workflow_devel-module + * + * Usage: + * workflow_debug( __FILE__, __FUNCTION__, __LINE__, '', ''); */ -function workflow_debug($class_name, $function_name, $line = '', $value1 = '', $value2 = '') { +function workflow_debug($class_name, $function_name, $line_nr = '', $value1 = '', $value2 = '') { $debug_switch = FALSE; // $debug_switch = TRUE; @@ -706,8 +762,8 @@ function workflow_debug($class_name, $function_name, $line = '', $value1 = '', $ return; } - $class_name_elements = explode( "\\", $class_name); - $output = 'Testing... function ' . end($class_name_elements) . '::' . $function_name . '/' . $line; + $class_name_elements = explode("\\", $class_name); + $output = 'Testing... function ' . end($class_name_elements) . '::' . $function_name . '/' . $line_nr; if ($value1) { $output .= ' = ' . $value1; } diff --git a/sites/all/modules/contrib/dev/config_filter/config_filter.info.yml b/sites/all/modules/contrib/dev/config_filter/config_filter.info.yml index 1ecd75772..308003b3b 100644 --- a/sites/all/modules/contrib/dev/config_filter/config_filter.info.yml +++ b/sites/all/modules/contrib/dev/config_filter/config_filter.info.yml @@ -4,8 +4,8 @@ description: Config Filter allows other modules to interact with a ConfigStorage # core: 8.x package: Config -# Information added by Drupal.org packaging script on 2018-04-05 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-11-14 +version: '8.x-1.4' core: '8.x' project: 'config_filter' -datestamp: 1522943894 +datestamp: 1542184984 diff --git a/sites/all/modules/contrib/dev/config_filter/src/Config/FilteredStorage.php b/sites/all/modules/contrib/dev/config_filter/src/Config/FilteredStorage.php index 9559df324..4f012cf17 100644 --- a/sites/all/modules/contrib/dev/config_filter/src/Config/FilteredStorage.php +++ b/sites/all/modules/contrib/dev/config_filter/src/Config/FilteredStorage.php @@ -85,7 +85,7 @@ class FilteredStorage implements FilteredStorageInterface { $data = $filter->filterReadMultiple($names, $data); } ksort($data); - return $data; + return array_filter($data); } /** @@ -224,7 +224,9 @@ class FilteredStorage implements FilteredStorageInterface { foreach ($this->filters as $filter) { $collections = $filter->filterGetAllCollectionNames($collections); } + $collections = array_unique($collections); sort($collections); + return $collections; } diff --git a/sites/all/modules/contrib/dev/config_filter/src/Tests/FilteredStorageTest.php b/sites/all/modules/contrib/dev/config_filter/src/Tests/FilteredStorageTest.php index fb1a2d47c..75dfa4723 100644 --- a/sites/all/modules/contrib/dev/config_filter/src/Tests/FilteredStorageTest.php +++ b/sites/all/modules/contrib/dev/config_filter/src/Tests/FilteredStorageTest.php @@ -114,6 +114,21 @@ class FilteredStorageTest extends CachedStorageTest { $this->assertEquals($filteredCollection, $storageC->getCollectionName()); } + /** + * Test collection names from FilteredStorage::getAllCollectionNames(). + */ + public function testGetAllCollectionNamesFilter() { + $source = $this->prophesize(StorageInterface::class); + $source->getAllCollectionNames()->willReturn(['a', 'b']); + + $filter = $this->prophesizeFilter(); + $filter->filterGetAllCollectionNames(['a', 'b'])->willReturn(['b', 'b', 'c']); + + $storage = new FilteredStorage($source->reveal(), [$filter->reveal()]); + + $this->assertEquals(['b', 'c'], $storage->getAllCollectionNames()); + } + /** * Test the read methods invokes the correct filter methods. * @@ -174,6 +189,27 @@ class FilteredStorageTest extends CachedStorageTest { // @codingStandardsIgnoreEnd } + /** + * Test that when a filter removes config on a readMultiple it is not set. + */ + public function testReadMultipleWithEmptyResults() { + $names = [$this->randomString(), $this->randomString()]; + $source = $this->prophesize(StorageInterface::class); + $data = [$this->randomArray(), $this->randomArray()]; + $source->readMultiple($names)->willReturn($data); + $source = $source->reveal(); + + foreach ([0, [], NULL] as $none) { + $filtered = $data; + $filtered[1] = $none; + $filter = $this->prophesizeFilter(); + $filter->filterReadMultiple($names, $data)->willReturn($filtered); + + $storage = new FilteredStorage($source, [$filter->reveal()]); + $this->assertEquals([$data[0]], $storage->readMultiple($names)); + } + } + /** * Test the write method invokes the filterWrite in filters. * diff --git a/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_split_test/config_filter_split_test.info.yml b/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_split_test/config_filter_split_test.info.yml index e91f96830..69f4f5a5a 100644 --- a/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_split_test/config_filter_split_test.info.yml +++ b/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_split_test/config_filter_split_test.info.yml @@ -5,8 +5,8 @@ package: Testing dependencies: - config_filter:config_filter -# Information added by Drupal.org packaging script on 2018-04-05 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-11-14 +version: '8.x-1.4' core: '8.x' project: 'config_filter' -datestamp: 1522943894 +datestamp: 1542184984 diff --git a/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_test/config_filter_test.info.yml b/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_test/config_filter_test.info.yml index 7b18d8891..0c97ec25d 100644 --- a/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_test/config_filter_test.info.yml +++ b/sites/all/modules/contrib/dev/config_filter/tests/modules/config_filter_test/config_filter_test.info.yml @@ -5,8 +5,8 @@ package: Testing dependencies: - config_filter:config_filter -# Information added by Drupal.org packaging script on 2018-04-05 -version: '8.x-1.2' +# Information added by Drupal.org packaging script on 2018-11-14 +version: '8.x-1.4' core: '8.x' project: 'config_filter' -datestamp: 1522943894 +datestamp: 1542184984 diff --git a/sites/all/modules/contrib/dev/ctools/ctools.info.yml b/sites/all/modules/contrib/dev/ctools/ctools.info.yml index 3b54b6ab9..01f59c94d 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.info.yml +++ b/sites/all/modules/contrib/dev/ctools/ctools.info.yml @@ -1,11 +1,13 @@ -name: Chaos tools +name: Chaos Tools type: module description: 'Provides a number of utility and helper APIs for Drupal developers and site builders.' package: Chaos tool suite # core: 8.x +dependencies: + - drupal:system (>=8.5) -# Information added by Drupal.org packaging script on 2017-04-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' core: '8.x' project: 'ctools' -datestamp: 1493401747 +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/ctools.services.yml b/sites/all/modules/contrib/dev/ctools/ctools.services.yml index eaa38b81e..59f91f92a 100644 --- a/sites/all/modules/contrib/dev/ctools/ctools.services.yml +++ b/sites/all/modules/contrib/dev/ctools/ctools.services.yml @@ -4,17 +4,17 @@ services: arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory'] ctools.wizard.entity.form: class: Drupal\ctools\Controller\WizardEntityFormController - arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory', '@entity.manager'] + arguments: ['@controller_resolver', '@form_builder', '@ctools.wizard.factory', '@entity_type.manager'] ctools.wizard_enhancer: class: Drupal\ctools\Routing\Enhancer\WizardEnhancer tags: - { name: route_enhancer } ctools.wizard.factory: class: Drupal\ctools\Wizard\WizardFactory - arguments: ['@form_builder', '@event_dispatcher'] + arguments: ['@form_builder', '@event_dispatcher', '@renderer'] ctools.paramconverter.tempstore: class: Drupal\ctools\ParamConverter\TempstoreConverter - arguments: ['@user.shared_tempstore', '@entity_type.manager'] + arguments: ['@tempstore.shared', '@entity_type.manager'] tags: - { name: paramconverter } ctools.typed_data.resolver: @@ -22,7 +22,7 @@ services: arguments: ['@typed_data_manager', '@string_translation'] ctools.access: class: Drupal\ctools\Access\TempstoreAccess - arguments: ['@user.shared_tempstore'] + arguments: ['@tempstore.shared'] tags: - { name: access_check, applies_to: _ctools_access } plugin.manager.ctools.relationship: @@ -33,6 +33,6 @@ services: arguments: ['@entity.repository'] ctools.serializable.tempstore.factory: class: Drupal\ctools\SerializableTempstoreFactory - arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%user.tempstore.expire%'] + arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%tempstore.expire%', '@current_user'] tags: - { name: backend_overridable } diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.info.yml index e9fdf7c74..e5cb62e95 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.info.yml +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.info.yml @@ -1,14 +1,14 @@ -name: Chaos tools blocks +name: Chaos Tools Blocks type: module description: 'Provides improvements to blocks that will one day be added to Drupal core.' package: Chaos tool suite (Experimental) # version: 3.x # core: 8.x dependencies: - - ctools + - ctools:ctools -# Information added by Drupal.org packaging script on 2017-04-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' core: '8.x' project: 'ctools' -datestamp: 1493401747 +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.module b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.module new file mode 100644 index 000000000..12c80348e --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/ctools_block.module @@ -0,0 +1,16 @@ +moduleExists('layout_builder')){ + foreach ($definitions AS $label => $definition) { + if($definition['provider'] == 'ctools_block') { + unset($definitions[$label]); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php index 14cf6ac21..e86ce0101 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Block/EntityField.php @@ -5,7 +5,6 @@ namespace Drupal\ctools_block\Plugin\Block; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Access\AccessResult; use Drupal\Core\Block\BlockBase; -use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; @@ -189,7 +188,7 @@ class EntityField extends BlockBase implements ContextAwarePluginInterface, Cont return [ 'formatter' => [ 'label' => 'above', - 'type' => $field_type_definition['default_formatter'] ?: '', + 'type' => isset($field_type_definition['default_formatter']) ? $field_type_definition['default_formatter'] : '', 'settings' => [], 'third_party_settings' => [], 'weight' => 0, diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php index ac5b5d176..7413605fb 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/src/Plugin/Deriver/EntityFieldDeriver.php @@ -14,9 +14,9 @@ class EntityFieldDeriver extends EntityDeriverBase { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { - $entity_type_labels = $this->entityManager->getEntityTypeLabels(); - foreach ($this->entityManager->getFieldMap() as $entity_type_id => $entity_field_map) { - foreach ($this->entityManager->getFieldStorageDefinitions($entity_type_id) as $field_storage_definition) { + $entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels(); + foreach ($this->entityFieldManager->getFieldMap() as $entity_type_id => $entity_field_map) { + foreach ($this->entityFieldManager->getFieldStorageDefinitions($entity_type_id) as $field_storage_definition) { $field_name = $field_storage_definition->getName(); // The blocks are based on fields. However, we are looping through field @@ -36,7 +36,7 @@ class EntityFieldDeriver extends EntityDeriverBase { else { // We take the field label used on the first bundle. $first_bundle = reset($field_info['bundles']); - $bundle_field_definitions = $this->entityManager->getFieldDefinitions($entity_type_id, $first_bundle); + $bundle_field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $first_bundle); // The field storage config may exist, but it's possible that no // fields are actually using it. If that's the case, skip to the next diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml index 17bf6d513..82daed53d 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_block/tests/modules/ctools_block_field_test/ctools_block_field_test.info.yml @@ -1,4 +1,4 @@ -name: 'Chaos tools blocks test' +name: 'Chaos Tools blocks test' type: module description: 'Support module for Chaos tools blocks tests.' # core: 8.x @@ -13,8 +13,8 @@ dependencies: - user features: true -# Information added by Drupal.org packaging script on 2017-04-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' core: '8.x' project: 'ctools' -datestamp: 1493401747 +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml new file mode 100644 index 000000000..8e7d0d53d --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.info.yml @@ -0,0 +1,11 @@ +name: 'Entity Mask' +# core: 8.x +type: module +description: 'Allows an entity type to borrow the fields and display configuration of another entity type.' +# version: VERSION + +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' +core: '8.x' +project: 'ctools' +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.module b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.module new file mode 100644 index 000000000..063a8d3cc --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/ctools_entity_mask.module @@ -0,0 +1,189 @@ +getDefinitions() as $id => $entity_type) { + $mask = $entity_type->get('mask'); + + if ($mask && isset($display_modes[$mask])) { + $display_modes[$id] = $display_modes[$mask]; + } + } +} + +/** + * Implements hook_entity_view_mode_info_alter(). + */ +function ctools_entity_mask_entity_view_mode_info_alter(&$view_modes) { + ctools_entity_mask_copy_display_modes($view_modes); +} + +/** + * Implements hook_entity_form_mode_info_alter(). + */ +function ctools_entity_mask_entity_form_mode_info_alter(&$form_modes) { + ctools_entity_mask_copy_display_modes($form_modes); +} + +/** + * Implements hook_entity_type_alter(). + */ +function ctools_entity_mask_entity_type_alter(array &$entity_types) { + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + foreach ($entity_types as $entity_type) { + // Mask entities should use our specialized storage handler, which simulates + // a save but does not write anything to the database. + if ($entity_type->get('mask') && $entity_type->getStorageClass() == SqlContentEntityStorage::class) { + $entity_type->setStorageClass(MaskContentEntityStorage::class); + // Mask entities should not maintain any tables. + $entity_type->set('base_table', NULL); + $entity_type->set('revision_table', NULL); + $entity_type->set('data_table', NULL); + $entity_type->set('revision_data_table', NULL); + + // Nor should they be exposed to Field UI. + $entity_type->set('field_ui_base_route', NULL); + } + } +} + +/** + * Copies all components from a display for a masked entity type. + * + * If the given display is for a mask entity type, the corresponding display for + * the masked entity type is loaded and all of its components are copied into + * the given display. If no corresponding display exists for the masked entity + * type, the default display will be loaded and used. + * + * @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display + * The display for the mask entity type. + */ +function ctools_entity_mask_copy_display(EntityDisplayInterface $display) { + $mask = \Drupal::entityTypeManager() + ->getDefinition($display->getTargetEntityTypeId()) + ->get('mask'); + + // If the target entity type is not masking another entity type, there is + // nothing to do here. + if (empty($mask)) { + return; + } + + // Try to load the corresponding entity display for the masked entity type, + // in descending order of preference. + $bundle = $display->getTargetBundle(); + $displays = $display::loadMultiple([ + $mask . '.' . $bundle . '.' . $display->getMode(), + $mask . '.' . $bundle . '.default', + ]); + + // Nothing to do if there is no display we can borrow components from. + if (empty($displays)) { + return; + } + foreach (reset($displays)->getComponents() as $key => $component) { + $display->setComponent($key, $component); + } +} + +/** + * Implements hook_ENTITY_TYPE_create(). + */ +function ctools_entity_mask_entity_view_display_create(EntityViewDisplayInterface $display) { + ctools_entity_mask_copy_display($display); +} + +/** + * Implements hook_ENTITY_TYPE_create(). + */ +function ctools_entity_mask_entity_form_display_create(EntityFormDisplayInterface $display) { + ctools_entity_mask_copy_display($display); +} + +/** + * Implements hook_entity_view_display_alter(). + */ +function ctools_entity_mask_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) { + ctools_entity_mask_copy_display($display); +} + +/** + * Implements hook_entity_form_display_alter(). + */ +function ctools_entity_mask_entity_form_display_alter(EntityFormDisplayInterface $form_display, array $context) { + ctools_entity_mask_copy_display($form_display); +} + +/** + * Implements hook_entity_bundle_field_info(). + */ +function ctools_entity_mask_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle) { + $info = []; + + $mask = $entity_type->get('mask'); + // Nothing to do if the entity type is not masking another entity type. + if (empty($mask)) { + return $info; + } + + $storage_info = ctools_entity_mask_entity_field_storage_info($entity_type); + + /** @var \Drupal\field\FieldConfigInterface[] $fields */ + $fields = \Drupal::entityTypeManager() + ->getStorage('field_config') + ->loadByProperties([ + 'entity_type' => $mask, + 'bundle' => $bundle, + ]); + + foreach ($fields as $field) { + $field_name = $field->getName(); + + $info[$field_name] = $field + ->createDuplicate() + ->set('entity_type', $mask) + ->set('fieldStorage', $storage_info[$field_name]); + } + return $info; +} + +/** + * Implements hook_entity_field_storage_info(). + */ +function ctools_entity_mask_entity_field_storage_info(EntityTypeInterface $entity_type) { + $info = []; + + $mask = $entity_type->get('mask'); + // Nothing to do if the entity type is not masking another entity type. + if (empty($mask)) { + return $info; + } + + /** @var \Drupal\field\FieldStorageConfigInterface[] $fields */ + $fields = \Drupal::entityTypeManager() + ->getStorage('field_storage_config') + ->loadByProperties([ + 'entity_type' => $mask, + ]); + + foreach ($fields as $field) { + $field_name = $field->getName(); + $info[$field_name] = $field->createDuplicate()->set('entity_type', $mask); + } + return $info; +} diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php new file mode 100644 index 000000000..0c9151107 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/src/MaskContentEntityStorage.php @@ -0,0 +1,81 @@ +uuid(); + } + +} diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml new file mode 100644 index 000000000..f01ced96b --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/block_content.type.basic.yml @@ -0,0 +1,7 @@ +langcode: en +status: true +dependencies: { } +id: basic +label: 'Basic block' +revision: 0 +description: 'A basic block contains a title and a body.' diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml new file mode 100644 index 000000000..4baffd4f8 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_form_display.block_content.basic.default.yml @@ -0,0 +1,52 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + - field.field.block_content.basic.field_image + - field.field.block_content.basic.field_link + - image.style.thumbnail + module: + - image + - link + - text +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + type: text_textarea_with_summary + weight: 1 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + region: content + field_image: + type: image_image + weight: 3 + settings: + preview_image_style: thumbnail + progress_indicator: throbber + third_party_settings: { } + region: content + field_link: + weight: 2 + settings: + placeholder_url: '' + placeholder_title: '' + third_party_settings: { } + type: link_default + region: content + info: + type: string_textfield + weight: 0 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + region: content +hidden: { } diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml new file mode 100644 index 000000000..6b554554e --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/core.entity_view_display.block_content.basic.default.yml @@ -0,0 +1,46 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + - field.field.block_content.basic.field_image + - field.field.block_content.basic.field_link + module: + - image + - link + - text +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + type: text_default + weight: 0 + label: hidden + settings: { } + third_party_settings: { } + region: content + field_image: + weight: 2 + label: above + settings: + image_style: '' + image_link: '' + third_party_settings: { } + type: image + region: content + field_link: + weight: 1 + label: above + settings: + trim_length: 80 + url_only: false + url_plain: false + rel: '' + target: '' + third_party_settings: { } + type: link + region: content +hidden: { } diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml new file mode 100644 index 000000000..89118eff0 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.body.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.body + module: + - text +id: block_content.basic.body +field_name: body +entity_type: block_content +bundle: basic +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: false +field_type: text_with_summary diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml new file mode 100644 index 000000000..3e11e9639 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_image.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.field_image + module: + - image +id: block_content.basic.field_image +field_name: field_image +entity_type: block_content +bundle: basic +label: Image +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + alt_field_required: true + title_field: false + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml new file mode 100644 index 000000000..d79e9ff02 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.field.block_content.basic.field_link.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.field_link + module: + - link +id: block_content.basic.field_link +field_name: field_link +entity_type: block_content +bundle: basic +label: Link +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + link_type: 17 + title: 1 +field_type: link diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml new file mode 100644 index 000000000..6e0ba2464 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_image.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - block_content + - file + - image +id: block_content.field_image +field_name: field_image +entity_type: block_content +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml new file mode 100644 index 000000000..bddf13e1c --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/config/install/field.storage.block_content.field_link.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - block_content + - link +id: block_content.field_link +field_name: field_link +entity_type: block_content +type: link +settings: { } +module: link +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml new file mode 100644 index 000000000..46be42c73 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.info.yml @@ -0,0 +1,14 @@ +name: 'Entity Mask test' +# core: 8.x +type: module +dependencies: + - block_content + - ctools_entity_mask + - image + - text + +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' +core: '8.x' +project: 'ctools' +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml new file mode 100644 index 000000000..85d2c8d3b --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/entity_mask_test.routing.yml @@ -0,0 +1,9 @@ +fake_block_content.add_form: + path: '/fake-block/add/{block_content_type}' + defaults: + _controller: '\Drupal\block_content\Controller\BlockContentController::addForm' + _title_callback: 'Drupal\block_content\Controller\BlockContentController::getAddFormTitle' + options: + _admin_route: TRUE + requirements: + _permission: 'administer blocks' diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php new file mode 100644 index 000000000..754466848 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/modules/entity_mask_test/src/Entity/BlockContent.php @@ -0,0 +1,70 @@ +drupalCreateUser(['administer blocks']); + $this->drupalLogin($account); + } + + /** + * Tests that the form display for a masked entity replicates its source. + */ + public function testFormDisplay() { + $assert = $this->assertSession(); + + $this->drupalGet('/fake-block/add/basic'); + $assert->statusCodeEquals(200); + $assert->fieldExists('Body'); + $assert->fieldExists('Link'); + $assert->fieldExists('Image'); + } + + /** + * Tests that the view display for a masked entity replicates its source. + */ + public function testViewDisplay() { + // Generate a random image for the image field, since that can potentially + // be tricky. + $image_uri = uniqid('public://') . '.png'; + $image_uri = $this->getRandomGenerator()->image($image_uri, '100x100', '200x200'); + $image = File::create(['uri' => $image_uri]); + $image->save(); + + $body = 'Qui animated corpse, cricket bat max brucks terribilem incessu zomby.'; + $link = 'https://www.drupal.org/project/ctools'; + + $block = BlockContent::create([ + 'type' => 'basic', + 'body' => $body, + 'field_link' => $link, + 'field_image' => $image, + ]); + $block->save(); + + // Ensure that the entity is intact after serialization and deserialization, + // since that may prove to be a common storage mechanism for mask entities. + $block = serialize($block); + $block = unserialize($block); + + $this->assertSame($body, $block->body->value); + $this->assertSame($link, $block->field_link->uri); + $this->assertSame($image_uri, $block->field_image->entity->getFileUri()); + + $build = \Drupal::entityTypeManager() + ->getViewBuilder('fake_block_content') + ->view($block); + + // If the fields are not in the renderable array, something has gone awry. + $this->assertArrayHasKey('body', $build); + $this->assertArrayHasKey('field_link', $build); + $this->assertArrayHasKey('field_image', $build); + + // Render the block and check the output too, just to be sure. + $rendered = \Drupal::service('renderer')->renderRoot($build); + $rendered = (string) $rendered; + + $this->assertContains($block->body->value, $rendered); + $this->assertContains($block->field_link->uri, $rendered); + + $image_url = $block->field_image->entity->getFileUri(); + $image_url = file_create_url($image_url); + // file_create_url() will include the host and port, but the rendered output + // won't include those. + $image_url = file_url_transform_relative($image_url); + $this->assertContains($image_url, $rendered); + } + +} diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php new file mode 100644 index 000000000..273a1a457 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_entity_mask/tests/src/Kernel/EntityMaskTest.php @@ -0,0 +1,216 @@ +installConfig(['block_content', 'entity_mask_test']); + $this->installEntitySchema('fake_block_content'); + } + + /** + * Tests that fields are correctly masked. + */ + public function testFields() { + $block = BlockContent::create([ + 'type' => 'basic', + ]); + + $this->assertTrue($block->hasField('body')); + $this->assertTrue($block->hasField('field_link')); + $this->assertTrue($block->hasField('field_image')); + } + + /** + * Tests that entity view displays are correctly masked. + */ + public function testViewDisplays() { + $view_modes = $this->container + ->get('entity_display.repository') + ->getAllViewModes(); + $this->assertSame($view_modes['block_content'], $view_modes['fake_block_content']); + + $display = entity_get_display('fake_block_content', 'basic', 'default'); + $this->assertTrue($display->isNew()); + + $components = $display->getComponents(); + $this->assertArrayHasKey('body', $components); + $this->assertArrayHasKey('field_link', $components); + $this->assertArrayHasKey('field_image', $components); + } + + /** + * Tests that entity form displays are correctly masked. + */ + public function testFormDisplays() { + EntityFormMode::create([ + 'id' => 'block_content.foobar', + 'label' => $this->randomString(), + 'targetEntityType' => 'block_content', + ])->save(); + + $form_modes = $this->container + ->get('entity_display.repository') + ->getAllFormModes(); + $this->assertSame($form_modes['block_content'], $form_modes['fake_block_content']); + + $display = entity_get_form_display('fake_block_content', 'basic', 'default'); + $this->assertTrue($display->isNew()); + + $components = $display->getComponents(); + $this->assertArrayHasKey('body', $components); + $this->assertArrayHasKey('field_link', $components); + $this->assertArrayHasKey('field_image', $components); + } + + /** + * Tests that mask entity types define no tables. + */ + public function testNoTables() { + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + $entity_type = $this->container + ->get('entity_type.manager') + ->getDefinition('fake_block_content'); + + $this->assertNull($entity_type->getBaseTable()); + $this->assertNull($entity_type->getDataTable()); + $this->assertNull($entity_type->getRevisionTable()); + $this->assertNull($entity_type->getRevisionDataTable()); + } + + /** + * Tests that mask entity types are not exposed to Field UI. + */ + public function testNotExposedToFieldUI() { + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */ + $entity_type = $this->container + ->get('entity_type.manager') + ->getDefinition('fake_block_content'); + + $this->assertNull($entity_type->get('field_ui_base_route')); + } + + /** + * Asserts that a mask entity can be serialized and de-serialized coherently. + * + * @depends testFields + */ + public function testSerialization() { + $body = $this->getRandomGenerator()->paragraphs(2); + $link = 'https://www.drupal.org/project/ctools'; + + /** @var \Drupal\Core\Entity\EntityInterface $block */ + $block = BlockContent::create([ + 'type' => 'basic', + 'body' => $body, + 'field_link' => $link, + ]); + + $block = serialize($block); + $block = unserialize($block); + + $this->assertSame($body, $block->body->value); + $this->assertSame($link, $block->field_link->uri); + } + + /** + * Tests that mask entities' isNew() method behaves consistently. + */ + public function testIsNew() { + $block = BlockContent::create(['type' => 'basic']); + $this->assertTrue($block->isNew()); + $block->save(); + $this->assertFalse($block->isNew()); + } + + /** + * Tests that mask entities' id() method returns the UUID. + */ + public function testId() { + $block = BlockContent::create(['type' => 'basic']); + $this->assertSame($block->id(), $block->uuid()); + $block->save(); + $this->assertSame($block->id(), $block->uuid()); + } + + /** + * Tests that mask entities cannot be loaded. + * + * @depends testId + */ + public function testLoad() { + $block = BlockContent::create(['type' => 'basic']); + $block->save(); + + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = $this->container->get('entity_type.manager')->getStorage('fake_block_content'); + + $id = $block->id(); + $this->assertNull($storage->load($id)); + $this->assertEmpty($storage->loadMultiple([$id])); + } + + /** + * Tests that deleting a mask entity doesn't throw an exception or anything. + */ + public function testDelete() { + $block = BlockContent::create(['type' => 'basic']); + $block->save(); + $block->delete(); + } + + /** + * Tests that mask entities have field data after save. + * + * @depends testFields + * @depends testNoTables + */ + public function testSave() { + $body = $this->getRandomGenerator()->paragraphs(2); + $link = 'https://www.drupal.org/project/ctools'; + + /** @var \Drupal\Core\Entity\EntityInterface $block */ + $block = BlockContent::create([ + 'type' => 'basic', + 'body' => $body, + 'field_link' => $link, + ]); + + // Ensure that the field values are preserved after save... + $this->assertSame($body, $block->body->value); + $this->assertSame($link, $block->field_link->uri); + } + +} diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/ctools_views.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/ctools_views.info.yml index 2d66b5b53..9f3898ee1 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/ctools_views.info.yml +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/ctools_views.info.yml @@ -1,15 +1,15 @@ -name: Chaos tools Views +name: Chaos Tools Views type: module description: 'A set of improvements to the core Views code that allows for greater control over Blocks.' package: Chaos tool suite (Experimental) # version: 3.x # core: 8.x dependencies: - - block - - views + - drupal:block + - drupal:views -# Information added by Drupal.org packaging script on 2017-04-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' core: '8.x' project: 'ctools' -datestamp: 1493401747 +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Plugin/Display/Block.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Plugin/Display/Block.php index e2d2a7c4d..7ebc1b1fd 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Plugin/Display/Block.php +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Plugin/Display/Block.php @@ -2,11 +2,9 @@ namespace Drupal\ctools_views\Plugin\Display; -use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; use Drupal\views\Plugin\Block\ViewsBlock; use Drupal\views\Plugin\views\display\Block as CoreBlock; -use Drupal\views\Plugin\views\filter\InOperator; /** * Provides a Block display plugin that allows for greater control over Views @@ -52,10 +50,14 @@ class Block extends CoreBlock { $options['configure_sorts'] = $this->t('Configure sorts'); $form['allow']['#options'] = $options; // Update the items_per_page if set. - $defaults = array_filter($form['allow']['#default_value']); - if (isset($defaults['items_per_page'])) { - $defaults['items_per_page'] = 'items_per_page'; + $defaults = []; + if (!empty($form['allow']['#default_value'])) { + $defaults = array_filter($form['allow']['#default_value']); + if (!empty($defaults['items_per_page'])) { + $defaults['items_per_page'] = 'items_per_page'; + } } + $form['allow']['#default_value'] = $defaults; } @@ -157,7 +159,7 @@ class Block extends CoreBlock { if (!empty($allow_settings['sort_fields'])) { $form['override']['order_fields'][$field_name]['#attributes']['class'][] = 'draggable'; } - $form['override']['order_fields'][$field_name]['#weight'] = !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : ''; + $form['override']['order_fields'][$field_name]['#weight'] = !empty($block_configuration['fields'][$field_name]['weight']) ? $block_configuration['fields'][$field_name]['weight'] : 0; if (!empty($allow_settings['hide_fields'])) { $form['override']['order_fields'][$field_name]['hide'] = [ '#type' => 'checkbox', diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php index 85201046a..559f38ba9 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/src/Tests/CToolsViewsBasicViewBlockTest.php @@ -41,11 +41,11 @@ class CToolsViewsBasicViewBlockTest extends UITestBase { /** * @inheritdoc */ - protected function setUp() { - parent::setUp(); + protected function setUp($import_test_views = TRUE) { + parent::setUp($import_test_views); ViewTestData::createTestViews(get_class($this), array('ctools_views_test_views')); - $this->storage = $this->container->get('entity.manager')->getStorage('block'); + $this->storage = $this->container->get('entity_type.manager')->getStorage('block'); } /** diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml index 092f98c37..d476c0518 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/ctools_views_test_views.info.yml @@ -13,8 +13,8 @@ dependencies: - node - taxonomy -# Information added by Drupal.org packaging script on 2017-04-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-3.2' core: '8.x' project: 'ctools' -datestamp: 1493401747 +datestamp: 1550728390 diff --git a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml index c2ece3c05..fbdfb3e9c 100644 --- a/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml +++ b/sites/all/modules/contrib/dev/ctools/modules/ctools_views/tests/modules/ctools_views_test_views/test_views/views.view.ctools_views_entity_test.yml @@ -108,7 +108,7 @@ display: field_api_classes: false filters: status: - value: true + value: '1' table: node_field_data field: status plugin_id: boolean @@ -173,7 +173,7 @@ display: filter_groups: false filters: status: - value: true + value: '1' table: node_field_data field: status plugin_id: boolean @@ -266,7 +266,7 @@ display: filter_groups: false filters: status: - value: true + value: '1' table: node_field_data field: status plugin_id: boolean @@ -361,7 +361,7 @@ display: filter_groups: false filters: status: - value: true + value: '1' table: node_field_data field: status plugin_id: boolean @@ -463,7 +463,7 @@ display: filter_groups: false filters: status: - value: true + value: '1' table: node_field_data field: status plugin_id: boolean diff --git a/sites/all/modules/contrib/dev/ctools/plugins/access/node.inc b/sites/all/modules/contrib/dev/ctools/plugins/access/node.inc new file mode 100644 index 000000000..07d500025 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/plugins/access/node.inc @@ -0,0 +1,6 @@ +entityManager = $manager; + $this->entityTypeManager = $entity_type_manager; } /** @@ -41,7 +41,7 @@ class WizardEntityFormController extends WizardFormController { protected function getFormArgument(RouteMatchInterface $route_match) { $form_arg = $route_match->getRouteObject()->getDefault('_entity_wizard'); list($entity_type_id, $operation) = explode('.', $form_arg); - $definition = $this->entityManager->getDefinition($entity_type_id); + $definition = $this->entityTypeManager->getDefinition($entity_type_id); $handlers = $definition->getHandlerClasses(); if (empty($handlers['wizard'][$operation])) { throw new \Exception(sprintf('Unsupported wizard operation %s', $operation)); diff --git a/sites/all/modules/contrib/dev/ctools/src/Controller/WizardFormController.php b/sites/all/modules/contrib/dev/ctools/src/Controller/WizardFormController.php index e4492a4c9..d7eb9b85c 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Controller/WizardFormController.php +++ b/sites/all/modules/contrib/dev/ctools/src/Controller/WizardFormController.php @@ -6,7 +6,6 @@ use Drupal\Core\Controller\ControllerResolverInterface; use Drupal\Core\Controller\FormController; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\ctools\Wizard\FormWizardInterface; use Drupal\ctools\Wizard\WizardFactoryInterface; use Symfony\Component\HttpFoundation\Request; @@ -25,7 +24,7 @@ class WizardFormController extends FormController { /** * Tempstore Factory for keeping track of values in each step of the wizard. * - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; diff --git a/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvent.php b/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvent.php new file mode 100644 index 000000000..35df32dd3 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvent.php @@ -0,0 +1,56 @@ +block = $block; + $this->variant = $variant; + } + + /** + * Gets the block plugin. + * + * @return \Drupal\Core\Block\BlockPluginInterface + */ + public function getBlock() { + return $this->block; + } + + /** + * Gets the variant plugin. + * + * @return \Drupal\ctools\Plugin\BlockVariantInterface + */ + public function getVariant() { + return $this->variant; + } + +} diff --git a/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvents.php b/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvents.php new file mode 100644 index 000000000..ea4ec364a --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/src/Event/BlockVariantEvents.php @@ -0,0 +1,49 @@ +get('user.shared_tempstore'), $container->get('plugin.manager.condition')); + return new static($container->get('tempstore.shared'), $container->get('plugin.manager.condition')); } function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $manager) { @@ -132,7 +132,8 @@ abstract class ConditionConfigure extends FormBase { $response = new AjaxResponse(); $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name); list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values); - $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters))); + $url = Url::fromRoute($route_name, $route_parameters); + $response->addCommand(new RedirectCommand($url->toString())); $response->addCommand(new CloseModalDialogCommand()); return $response; } diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ConditionDelete.php b/sites/all/modules/contrib/dev/ctools/src/Form/ConditionDelete.php index d2303bc86..0655934d9 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ConditionDelete.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ConditionDelete.php @@ -8,13 +8,13 @@ use Drupal\Core\Form\ConfirmFormHelper; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\ctools\ConstraintConditionInterface; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ConditionDelete extends ConfirmFormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -42,7 +42,7 @@ abstract class ConditionDelete extends ConfirmFormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore'), $container->get('plugin.manager.condition')); + return new static($container->get('tempstore.shared'), $container->get('plugin.manager.condition')); } function __construct(SharedTempStoreFactory $tempstore, PluginManagerInterface $manager) { diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ContextConfigure.php b/sites/all/modules/contrib/dev/ctools/src/Form/ContextConfigure.php index 142f18abb..f5f1c57d5 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ContextConfigure.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ContextConfigure.php @@ -2,11 +2,9 @@ namespace Drupal\ctools\Form; - use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\CloseModalDialogCommand; use Drupal\Core\Ajax\RedirectCommand; -use Drupal\Core\Entity\Entity; use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; @@ -14,23 +12,31 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Plugin\Context\ContextInterface; use Drupal\Core\Url; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; abstract class ContextConfigure extends FormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; + /** + * Object EntityTypeManager. + * + * @var Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + /** * @var string */ protected $tempstore_id; /** - * @var string; + * @var string */ protected $machine_name; @@ -38,11 +44,15 @@ abstract class ContextConfigure extends FormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore')); + return new static( + $container->get('tempstore.shared'), + $container->get('entity_type.manager') + ); } - function __construct(SharedTempStoreFactory $tempstore) { + function __construct(SharedTempStoreFactory $tempstore, EntityTypeManagerInterface $entity_type_manager) { $this->tempstore = $tempstore; + $this->entityTypeManager = $entity_type_manager; } /** @@ -167,7 +177,7 @@ abstract class ContextConfigure extends FormBase { if (strpos($context_definition->getDataType(), 'entity:') === 0) { list(, $entity_type) = explode(':', $context_definition->getDataType()); if (is_numeric($form_state->getValue('context_value'))) { - $value = \Drupal::entityTypeManager()->getStorage($entity_type)->load($form_state->getValue('context_value')); + $value = $this->entityTypeManager->getStorage($entity_type)->load($form_state->getValue('context_value')); } } // No loading required for non-entity values. diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ContextDelete.php b/sites/all/modules/contrib/dev/ctools/src/Form/ContextDelete.php index 06e0794a8..ec1406b80 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ContextDelete.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ContextDelete.php @@ -2,10 +2,9 @@ namespace Drupal\ctools\Form; - use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\ConfirmFormBase; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -14,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ContextDelete extends ConfirmFormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -36,7 +35,7 @@ abstract class ContextDelete extends ConfirmFormBase { protected $context_id; public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore')); + return new static($container->get('tempstore.shared')); } public function __construct(SharedTempStoreFactory $tempstore) { diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ManageConditions.php b/sites/all/modules/contrib/dev/ctools/src/Form/ManageConditions.php index ee506a89d..7e4bcb648 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ManageConditions.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ManageConditions.php @@ -2,7 +2,6 @@ namespace Drupal\ctools\Form; - use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Component\Serialization\Json; use Drupal\Core\Ajax\AjaxResponse; @@ -12,6 +11,7 @@ use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Form\FormBuilder; abstract class ManageConditions extends FormBase { @@ -20,17 +20,28 @@ abstract class ManageConditions extends FormBase { */ protected $manager; + /** + * The builder of form. + * + * @var \Drupal\Core\Form\FormBuilder + */ + protected $formBuilder; + /** * @var string */ protected $machine_name; public static function create(ContainerInterface $container) { - return new static($container->get('plugin.manager.condition')); + return new static( + $container->get('plugin.manager.condition'), + $container->get('form_builder') + ); } - function __construct(PluginManagerInterface $manager) { + function __construct(PluginManagerInterface $manager, FormBuilder $form_builder) { $this->manager = $manager; + $this->formBuilder = $form_builder; } /** @@ -91,11 +102,18 @@ abstract class ManageConditions extends FormBase { public function add(array &$form, FormStateInterface $form_state) { $condition = $form_state->getValue('conditions'); - $content = \Drupal::formBuilder()->getForm($this->getConditionClass(), $condition, $this->getTempstoreId(), $this->machine_name); + $content = $this->formBuilder->getForm($this->getConditionClass(), $condition, $this->getTempstoreId(), $this->machine_name); $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $cached_values = $form_state->getTemporaryValue('wizard'); list(, $route_parameters) = $this->getOperationsRouteInfo($cached_values, $this->machine_name, $form_state->getValue('conditions')); - $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]); + $route_name = $this->getAddRoute($cached_values); + $route_options = [ + 'query' => [ + FormBuilderInterface::AJAX_FORM_REQUEST => TRUE, + ], + ]; + $url = Url::fromRoute($route_name, $route_parameters, $route_options); + $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString(); $response = new AjaxResponse(); $response->addCommand(new OpenModalDialogCommand($this->t('Configure Required Context'), $content, array('width' => '700'))); return $response; @@ -186,7 +204,7 @@ abstract class ManageConditions extends FormBase { * Document the route name and parameters for edit/delete context operations. * * The route name returned from this method is used as a "base" to which - * ".edit" and ".delete" are appeneded in the getOperations() method. + * ".edit" and ".delete" are appended in the getOperations() method. * Subclassing '\Drupal\ctools\Form\ConditionConfigure' and * '\Drupal\ctools\Form\ConditionDelete' should set you up for using this * approach quite seamlessly. diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ManageContext.php b/sites/all/modules/contrib/dev/ctools/src/Form/ManageContext.php index 4ac3b8438..9b9d9cf8f 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ManageContext.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ManageContext.php @@ -10,6 +10,7 @@ use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\TypedData\TypedDataManagerInterface; use Drupal\Core\Url; +use Drupal\ctools\TypedDataResolver; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ManageContext extends FormBase { @@ -35,6 +36,13 @@ abstract class ManageContext extends FormBase { */ protected $formBuilder; + /** + * The typed data resolver. + * + * @var \Drupal\ctools\TypedDataResolver + */ + protected $typedDataResolver; + /** * An array of property types that are eligible as relationships. * @@ -49,13 +57,6 @@ abstract class ManageContext extends FormBase { */ protected $relationships = TRUE; - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static($container->get('typed_data_manager'), $container->get('form_builder')); - } - /** * ManageContext constructor. * @@ -63,12 +64,25 @@ abstract class ManageContext extends FormBase { * The typed data manager. * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The form builder. + * @param \Drupal\ctools\TypedDataResolver $ctools_typed_data_resolver + * The typed data resolver. */ - public function __construct(TypedDataManagerInterface $typed_data_manager, FormBuilderInterface $form_builder) { + public function __construct(TypedDataManagerInterface $typed_data_manager, FormBuilderInterface $form_builder, TypedDataResolver $ctools_typed_data_resolver) { $this->typedDataManager = $typed_data_manager; $this->formBuilder = $form_builder; + $this->typedDataResolver = $ctools_typed_data_resolver; } + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('typed_data_manager'), + $container->get('form_builder'), + $container->get('ctools.typed_data.resolver') + ); + } /** * {@inheritdoc} @@ -160,7 +174,14 @@ abstract class ManageContext extends FormBase { $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $cached_values = $form_state->getTemporaryValue('wizard'); list(, $route_parameters) = $this->getContextOperationsRouteInfo($cached_values, $this->machine_name, $context); - $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getContextAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]); + $route_name = $this->getContextAddRoute($cached_values); + $route_options = [ + 'query' => [ + FormBuilderInterface::AJAX_FORM_REQUEST => TRUE, + ], + ]; + $url = Url::fromRoute($route_name, $route_parameters, $route_options); + $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString(); $response = new AjaxResponse(); $response->addCommand(new OpenModalDialogCommand($this->t('Add new context'), $content, array('width' => '700'))); return $response; @@ -172,7 +193,14 @@ abstract class ManageContext extends FormBase { $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $cached_values = $form_state->getTemporaryValue('wizard'); list(, $route_parameters) = $this->getRelationshipOperationsRouteInfo($cached_values, $this->machine_name, $relationship); - $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getRelationshipAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]); + $route_name = $this->getRelationshipAddRoute($cached_values); + $route_options = [ + 'query' => [ + FormBuilderInterface::AJAX_FORM_REQUEST => TRUE, + ], + ]; + $url = Url::fromRoute($route_name, $route_parameters, $route_options); + $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString(); $response = new AjaxResponse(); $response->addCommand(new OpenModalDialogCommand($this->t('Configure Relationship'), $content, array('width' => '700'))); return $response; @@ -180,7 +208,7 @@ abstract class ManageContext extends FormBase { protected function getAvailableRelationships($cached_values) { /** @var \Drupal\ctools\TypedDataResolver $resolver */ - $resolver = \Drupal::service('ctools.typed_data.resolver'); + $resolver = $this->typedDataResolver; return $resolver->getTokensForContexts($this->getContexts($cached_values)); } diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ManageResolverRelationships.php b/sites/all/modules/contrib/dev/ctools/src/Form/ManageResolverRelationships.php index 7dfff5982..bb6bcfb2e 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ManageResolverRelationships.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ManageResolverRelationships.php @@ -9,6 +9,8 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\ctools\TypedDataResolver; +use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ManageResolverRelationships extends FormBase { @@ -24,6 +26,43 @@ abstract class ManageResolverRelationships extends FormBase { */ protected $property_types = []; + /** + * The typed data resolver. + * + * @var \Drupal\ctools\TypedDataResolver + */ + protected $typedDataResolver; + + /** + * The form builder. + * + * @var \Drupal\Core\Form\FormBuilder + */ + protected $formBuilder; + + /** + * Constructs a new ManageResolverRelationships object. + * + * @param \Drupal\ctools\TypedDataResolver $ctools_typed_data_resolver + * The typed data resolver. + * @param \Drupal\Core\Form\FormBuilderInterface $form_builder + * The form builder. + */ + public function __construct(TypedDataResolver $ctools_typed_data_resolver, FormBuilderInterface $form_builder) { + $this->typedDataResolver = $ctools_typed_data_resolver; + $this->formBuilder = $form_builder; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('ctools.typed_data.resolver'), + $container->get('form_builder') + ); + } + /** * {@inheritdoc} */ @@ -80,11 +119,18 @@ abstract class ManageResolverRelationships extends FormBase { public function addRelationship(array &$form, FormStateInterface $form_state) { $relationship = $form_state->getValue('relationships'); - $content = \Drupal::formBuilder()->getForm($this->getContextClass(), $relationship, $this->getTempstoreId(), $this->machine_name); + $content = $this->formBuilder->getForm($this->getContextClass(), $relationship, $this->getTempstoreId(), $this->machine_name); $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $cached_values = $form_state->getTemporaryValue('wizard'); list(, $route_parameters) = $this->getRelationshipOperationsRouteInfo($cached_values, $this->machine_name, $relationship); - $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $this->url($this->getAddRoute($cached_values), $route_parameters, ['query' => [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]]); + $route_name = $this->getAddRoute($cached_values); + $route_options = [ + 'query' => [ + FormBuilderInterface::AJAX_FORM_REQUEST => TRUE, + ], + ]; + $url = Url::fromRoute($route_name, $route_parameters, $route_options); + $content['submit']['#attached']['drupalSettings']['ajax'][$content['submit']['#id']]['url'] = $url->toString(); $response = new AjaxResponse(); $response->addCommand(new OpenModalDialogCommand($this->t('Configure Relationship'), $content, array('width' => '700'))); return $response; @@ -92,7 +138,7 @@ abstract class ManageResolverRelationships extends FormBase { protected function getAvailableRelationships($cached_values) { /** @var \Drupal\ctools\TypedDataResolver $resolver */ - $resolver = \Drupal::service('ctools.typed_data.resolver'); + $resolver = $this->typedDataResolver; return $resolver->getTokensForContexts($this->getContexts($cached_values)); } diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/RelationshipConfigure.php b/sites/all/modules/contrib/dev/ctools/src/Form/RelationshipConfigure.php index 4f13f46e8..167ee4cc4 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/RelationshipConfigure.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/RelationshipConfigure.php @@ -8,13 +8,14 @@ use Drupal\Core\Ajax\RedirectCommand; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\ctools\TypedDataResolver; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class RelationshipConfigure extends FormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -37,7 +38,7 @@ abstract class RelationshipConfigure extends FormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore'), $container->get('ctools.typed_data.resolver')); + return new static($container->get('tempstore.shared'), $container->get('ctools.typed_data.resolver')); } public function __construct(SharedTempStoreFactory $tempstore, TypedDataResolver $resolver) { @@ -111,7 +112,8 @@ abstract class RelationshipConfigure extends FormBase { $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name); list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values); $response = new AjaxResponse(); - $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters))); + $url = Url::fromRoute($route_name, $route_parameters); + $response->addCommand(new RedirectCommand($url->toString())); $response->addCommand(new CloseModalDialogCommand()); return $response; } diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContext.php b/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContext.php index 76b5ec2da..07ae6be67 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContext.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContext.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Form\FormBuilder; abstract class RequiredContext extends FormBase { @@ -17,6 +18,13 @@ abstract class RequiredContext extends FormBase { */ protected $typedDataManager; + /** + * The builder of form. + * + * @var \Drupal\Core\Form\FormBuilder + */ + protected $formBuilder; + /** * @var string */ @@ -26,11 +34,15 @@ abstract class RequiredContext extends FormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('typed_data_manager')); + return new static( + $container->get('typed_data_manager'), + $container->get('form_builder') + ); } - public function __construct(PluginManagerInterface $typed_data_manager) { + public function __construct(PluginManagerInterface $typed_data_manager, FormBuilder $form_builder) { $this->typedDataManager = $typed_data_manager; + $this->formBuilder = $form_builder; } /** @@ -98,7 +110,7 @@ abstract class RequiredContext extends FormBase { */ public function add(array &$form, FormStateInterface $form_state) { $context = $form_state->getValue('contexts'); - $content = \Drupal::formBuilder()->getForm($this->getContextClass(), $context, $this->getTempstoreId(), $this->machine_name); + $content = $this->formBuilder->getForm($this->getContextClass(), $context, $this->getTempstoreId(), $this->machine_name); $content['#attached']['library'][] = 'core/drupal.dialog.ajax'; $response = new AjaxResponse(); $response->addCommand(new OpenModalDialogCommand($this->t('Configure Required Context'), $content, array('width' => '700'))); @@ -183,7 +195,7 @@ abstract class RequiredContext extends FormBase { * Document the route name and parameters for edit/delete context operations. * * The route name returned from this method is used as a "base" to which - * ".edit" and ".delete" are appeneded in the getOperations() method. + * ".edit" and ".delete" are appended in the getOperations() method. * Subclassing '\Drupal\ctools\Form\ContextConfigure' and * '\Drupal\ctools\Form\RequiredContextDelete' should set you up for using * this approach quite seamlessly. diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContextDelete.php b/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContextDelete.php index e04429a98..e65ce80f2 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContextDelete.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/RequiredContextDelete.php @@ -6,7 +6,7 @@ use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\ConfirmFormHelper; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -15,7 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; abstract class RequiredContextDelete extends ConfirmFormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -38,11 +38,11 @@ abstract class RequiredContextDelete extends ConfirmFormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore')); + return new static($container->get('tempstore.shared')); } /** - * @param \Drupal\user\SharedTempStoreFactory $tempstore + * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore */ function __construct(SharedTempStoreFactory $tempstore) { $this->tempstore = $tempstore; diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipConfigure.php b/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipConfigure.php index 00234cd9a..5ec43d5f9 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipConfigure.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipConfigure.php @@ -2,19 +2,19 @@ namespace Drupal\ctools\Form; - use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\CloseModalDialogCommand; use Drupal\Core\Ajax\RedirectCommand; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ResolverRelationshipConfigure extends FormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -32,7 +32,7 @@ abstract class ResolverRelationshipConfigure extends FormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore')); + return new static($container->get('tempstore.shared')); } function __construct(SharedTempStoreFactory $tempstore) { @@ -141,7 +141,8 @@ abstract class ResolverRelationshipConfigure extends FormBase { $response = new AjaxResponse(); $cached_values = $this->tempstore->get($this->tempstore_id)->get($this->machine_name); list($route_name, $route_parameters) = $this->getParentRouteInfo($cached_values); - $response->addCommand(new RedirectCommand($this->url($route_name, $route_parameters))); + $url = Url::fromRoute($route_name, $route_parameters); + $response->addCommand(new RedirectCommand($url->toString())); $response->addCommand(new CloseModalDialogCommand()); return $response; } diff --git a/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipDelete.php b/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipDelete.php index 4dfde2856..7195e3cf8 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipDelete.php +++ b/sites/all/modules/contrib/dev/ctools/src/Form/ResolverRelationshipDelete.php @@ -2,18 +2,17 @@ namespace Drupal\ctools\Form; - use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\ConfirmFormHelper; use Drupal\Core\Form\FormStateInterface; use Drupal\ctools\TypedDataResolver; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; abstract class ResolverRelationshipDelete extends ConfirmFormBase { /** - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -41,14 +40,14 @@ abstract class ResolverRelationshipDelete extends ConfirmFormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore'), $container->get('ctools.typed_data.resolver')); + return new static($container->get('tempstore.shared'), $container->get('ctools.typed_data.resolver')); } /** - * @param \Drupal\user\SharedTempStoreFactory $tempstore + * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore * The shared tempstore. * @param \Drupal\ctools\TypedDataResolver $resolver - * The the typed data resolver. + * The typed data resolver. */ public function __construct(SharedTempStoreFactory $tempstore, TypedDataResolver $resolver) { $this->tempstore = $tempstore; diff --git a/sites/all/modules/contrib/dev/ctools/src/ParamConverter/TempstoreConverter.php b/sites/all/modules/contrib/dev/ctools/src/ParamConverter/TempstoreConverter.php index 6458d90e2..939bd87d6 100644 --- a/sites/all/modules/contrib/dev/ctools/src/ParamConverter/TempstoreConverter.php +++ b/sites/all/modules/contrib/dev/ctools/src/ParamConverter/TempstoreConverter.php @@ -3,10 +3,9 @@ namespace Drupal\ctools\ParamConverter; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\ParamConverter\ParamConverterInterface; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\Routing\Route; /** @@ -83,7 +82,7 @@ class TempstoreConverter implements ParamConverterInterface { /** * The tempstore factory. * - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -97,7 +96,7 @@ class TempstoreConverter implements ParamConverterInterface { /** * Constructs a TempstoreConverter. * - * @param \Drupal\user\SharedTempStoreFactory $tempstore + * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore */ public function __construct(SharedTempStoreFactory $tempstore, EntityTypeManagerInterface $entity_type_manager) { $this->tempstore = $tempstore; diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/Block/EntityView.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/Block/EntityView.php index 51342d010..3db471cf2 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/Block/EntityView.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/Block/EntityView.php @@ -4,7 +4,8 @@ namespace Drupal\ctools\Plugin\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Cache\CacheableMetadata; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContextAwarePluginInterface; @@ -21,11 +22,18 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class EntityView extends BlockBase implements ContextAwarePluginInterface, ContainerFactoryPluginInterface { /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; + + /** + * The entity display repository. + * + * @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface + */ + protected $entityDisplayRepository; /** * Constructs a new EntityView. @@ -36,13 +44,16 @@ class EntityView extends BlockBase implements ContextAwarePluginInterface, Conta * The plugin ID for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository + * The entity display repository. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityDisplayRepositoryInterface $entity_display_repository) { parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->entityManager = $entity_manager; + $this->entityTypeManager = $entity_type_manager; + $this->entityDisplayRepository = $entity_display_repository; } /** @@ -53,7 +64,8 @@ class EntityView extends BlockBase implements ContextAwarePluginInterface, Conta $configuration, $plugin_id, $plugin_definition, - $container->get('entity.manager') + $container->get('entity_type.manager'), + $container->get('entity_display.repository') ); } @@ -72,7 +84,7 @@ class EntityView extends BlockBase implements ContextAwarePluginInterface, Conta public function blockForm($form, FormStateInterface $form_state) { $form['view_mode'] = [ '#type' => 'select', - '#options' => $this->entityManager->getViewModeOptions($this->getDerivativeId()), + '#options' => $this->entityDisplayRepository->getViewModeOptions($this->getDerivativeId()), '#title' => $this->t('View mode'), '#default_value' => $this->configuration['view_mode'], ]; @@ -93,7 +105,7 @@ class EntityView extends BlockBase implements ContextAwarePluginInterface, Conta /** @var $entity \Drupal\Core\Entity\EntityInterface */ $entity = $this->getContextValue('entity'); - $view_builder = $this->entityManager->getViewBuilder($entity->getEntityTypeId()); + $view_builder = $this->entityTypeManager->getViewBuilder($entity->getEntityTypeId()); $build = $view_builder->view($entity, $this->configuration['view_mode']); CacheableMetadata::createFromObject($this->getContext('entity')) diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/BlockVariantTrait.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/BlockVariantTrait.php index fa0187073..f368f2c75 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/BlockVariantTrait.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/BlockVariantTrait.php @@ -2,6 +2,9 @@ namespace Drupal\ctools\Plugin; +use Drupal\ctools\Event\BlockVariantEvent; +use Drupal\ctools\Event\BlockVariantEvents; + /** * Provides methods for \Drupal\ctools\Plugin\BlockVariantInterface. */ @@ -21,6 +24,13 @@ trait BlockVariantTrait { */ protected $blockPluginCollection; + /** + * The event dispatcher. + * + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected $eventDispatcher; + /** * @see \Drupal\ctools\Plugin\BlockVariantInterface::getRegionNames() */ @@ -39,6 +49,12 @@ trait BlockVariantTrait { public function addBlock(array $configuration) { $configuration['uuid'] = $this->uuidGenerator()->generate(); $this->getBlockCollection()->addInstanceId($configuration['uuid'], $configuration); + + $block = $this->getBlock($configuration['uuid']); + // Allow modules to react to the change. + $event = new BlockVariantEvent($block, $this); + $this->eventDispatcher()->dispatch(BlockVariantEvents::ADD_BLOCK, $event); + return $configuration['uuid']; } @@ -46,7 +62,13 @@ trait BlockVariantTrait { * @see \Drupal\ctools\Plugin\BlockVariantInterface::removeBlock() */ public function removeBlock($block_id) { + $block = $this->getBlock($block_id); $this->getBlockCollection()->removeInstanceId($block_id); + + // Allow modules to react to the change. + $event = new BlockVariantEvent($block, $this); + $this->eventDispatcher()->dispatch(BlockVariantEvents::DELETE_BLOCK, $event); + return $this; } @@ -54,8 +76,14 @@ trait BlockVariantTrait { * @see \Drupal\ctools\Plugin\BlockVariantInterface::updateBlock() */ public function updateBlock($block_id, array $configuration) { - $existing_configuration = $this->getBlock($block_id)->getConfiguration(); + $block = $this->getBlock($block_id); + $existing_configuration = $block->getConfiguration(); $this->getBlockCollection()->setInstanceConfiguration($block_id, $configuration + $existing_configuration); + + // Allow modules to react to the change. + $event = new BlockVariantEvent($block, $this); + $this->eventDispatcher()->dispatch(BlockVariantEvents::UPDATE_BLOCK, $event); + return $this; } @@ -112,6 +140,18 @@ trait BlockVariantTrait { return $this->blockPluginCollection; } + /** + * Gets the event dispatcher. + * + * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface + */ + protected function eventDispatcher() { + if (!$this->eventDispatcher) { + $this->eventDispatcher = \Drupal::service('event_dispatcher'); + } + return $this->eventDispatcher; + } + /** * Returns the UUID generator. * diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityBundle.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityBundle.php index 073ab4a89..1adf70580 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityBundle.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityBundle.php @@ -13,7 +13,7 @@ class EntityBundle extends EntityDeriverBase { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->hasKey('bundle')) { $this->derivatives[$entity_type_id] = $base_plugin_definition; $this->derivatives[$entity_type_id]['label'] = $this->getEntityBundleLabel($entity_type); @@ -43,7 +43,7 @@ class EntityBundle extends EntityDeriverBase { $fallback = $entity_type->getLabel(); if ($bundle_entity_type = $entity_type->getBundleEntityType()) { // This is a better fallback. - $fallback = $this->entityManager->getDefinition($bundle_entity_type)->getLabel(); + $fallback = $this->entityTypeManager->getDefinition($bundle_entity_type)->getLabel(); } return $this->t('@label bundle', ['@label' => $fallback]); diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityDeriverBase.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityDeriverBase.php index 1adec9965..9fc99baa2 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityDeriverBase.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityDeriverBase.php @@ -4,7 +4,9 @@ namespace Drupal\ctools\Plugin\Deriver; use Drupal\Component\Plugin\Derivative\DeriverBase; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityTypeRepositoryInterface; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; @@ -18,23 +20,43 @@ abstract class EntityDeriverBase extends DeriverBase implements ContainerDeriver use StringTranslationTrait; /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; + + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + + /** + * The entity type repository. + * + * @var \Drupal\Core\Entity\EntityTypeRepositoryInterface + */ + protected $entityTypeRepository; /** * Constructs new EntityViewDeriver. * - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation * The string translation service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. + * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository + * The entity type repository. */ - public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) { - $this->entityManager = $entity_manager; + public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, EntityFieldManagerInterface $entity_field_manager, EntityTypeRepositoryInterface $entity_type_repository) { + $this->entityTypeManager = $entity_type_manager; $this->stringTranslation = $string_translation; + $this->entityFieldManager = $entity_field_manager; + $this->entityTypeRepository = $entity_type_repository; } /** @@ -42,8 +64,10 @@ abstract class EntityDeriverBase extends DeriverBase implements ContainerDeriver */ public static function create(ContainerInterface $container, $base_plugin_id) { return new static( - $container->get('entity.manager'), - $container->get('string_translation') + $container->get('entity_type.manager'), + $container->get('string_translation'), + $container->get('entity_field.manager'), + $container->get('entity_type.repository') ); } diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityViewDeriver.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityViewDeriver.php index f8f189c5f..0e2c725e8 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityViewDeriver.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/Deriver/EntityViewDeriver.php @@ -13,7 +13,7 @@ class EntityViewDeriver extends EntityDeriverBase { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { + foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { if ($entity_type->hasViewBuilderClass()) { $this->derivatives[$entity_type_id] = $base_plugin_definition; $this->derivatives[$entity_type_id]['admin_label'] = $this->t('Entity view (@label)', ['@label' => $entity_type->getLabel()]); diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php index 32c6e86fd..eb8615da6 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/DisplayVariant/BlockDisplayVariant.php @@ -4,13 +4,11 @@ namespace Drupal\ctools\Plugin\DisplayVariant; use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Block\BlockManager; -use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\Condition\ConditionManager; use Drupal\Core\Display\VariantBase; use Drupal\Core\Display\ContextAwareVariantInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; -use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Utility\Token; use Drupal\ctools\Form\AjaxFormTrait; @@ -21,7 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a base class for a display variant that simply contains blocks. */ -abstract class BlockDisplayVariant extends VariantBase implements ContextAwareVariantInterface, ContainerFactoryPluginInterface, BlockVariantInterface, RefinableCacheableDependencyInterface { +abstract class BlockDisplayVariant extends VariantBase implements ContextAwareVariantInterface, ContainerFactoryPluginInterface, BlockVariantInterface { use AjaxFormTrait; use BlockVariantTrait; diff --git a/sites/all/modules/contrib/dev/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php b/sites/all/modules/contrib/dev/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php index ce96841d3..bee44c509 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php +++ b/sites/all/modules/contrib/dev/ctools/src/Plugin/Relationship/TypedDataEntityRelationship.php @@ -1,6 +1,7 @@ hasDefault('_controller') && ($route->hasDefault('_wizard') || $route->hasDefault('_entity_wizard')); - } +class WizardEnhancer implements EnhancerInterface { /** * {@inheritdoc} */ public function enhance(array $defaults, Request $request) { + $route = $defaults[RouteObjectInterface::ROUTE_OBJECT]; + if (!$this->isApplicable($route)) { + return $defaults; + } + if (!empty($defaults['_wizard'])) { $defaults['_controller'] = 'ctools.wizard.form:getContentResult'; } if (!empty($defaults['_entity_wizard'])) { $defaults['_controller'] = 'ctools.wizard.entity.form:getContentResult'; } + return $defaults; } + /** + * Returns if current route use ctools default parameters. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check. + * + * @return bool + * TRUE if the route use one of ctools route default parameters or FALSE. + */ + public function isApplicable(Route $route) { + return !$route->hasDefault('_controller') && ($route->hasDefault('_wizard') || $route->hasDefault('_entity_wizard')); + } + } diff --git a/sites/all/modules/contrib/dev/ctools/src/SerializableTempstore.php b/sites/all/modules/contrib/dev/ctools/src/SerializableTempstore.php index 65567edc5..2fbac0b70 100644 --- a/sites/all/modules/contrib/dev/ctools/src/SerializableTempstore.php +++ b/sites/all/modules/contrib/dev/ctools/src/SerializableTempstore.php @@ -3,7 +3,7 @@ namespace Drupal\ctools; use Drupal\Core\DependencyInjection\DependencySerializationTrait; -use Drupal\user\SharedTempStore; +use Drupal\Core\TempStore\SharedTempStore; /** * An extension of the SharedTempStore system for serialized data. diff --git a/sites/all/modules/contrib/dev/ctools/src/SerializableTempstoreFactory.php b/sites/all/modules/contrib/dev/ctools/src/SerializableTempstoreFactory.php index 51cf02b46..c3e283b9d 100644 --- a/sites/all/modules/contrib/dev/ctools/src/SerializableTempstoreFactory.php +++ b/sites/all/modules/contrib/dev/ctools/src/SerializableTempstoreFactory.php @@ -2,25 +2,55 @@ namespace Drupal\ctools; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; +use Drupal\Core\Lock\LockBackendInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\Core\TempStore\SharedTempStoreFactory; +use Symfony\Component\HttpFoundation\RequestStack; /** * A factory for creating SerializableTempStore objects. */ class SerializableTempstoreFactory extends SharedTempStoreFactory { + /** + * The current logged user. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + + /** + * Constructs a Drupal\Core\TempStore\SharedTempStoreFactory object. + * + * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $storage_factory + * The key/value store factory. + * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend + * The lock object used for this data. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + * @param int $expire + * The time to live for items, in seconds. + * @param \Drupal\Core\Session\AccountProxyInterface|null $current_user + * The current logged user. + */ + public function __construct(KeyValueExpirableFactoryInterface $storage_factory, LockBackendInterface $lock_backend, RequestStack $request_stack, $expire = 604800, AccountProxyInterface $current_user = NULL) { + parent::__construct($storage_factory, $lock_backend, $request_stack, $expire); + $this->currentUser = $current_user ?: \Drupal::currentUser(); + } + /** * {@inheritdoc} */ - function get($collection, $owner = NULL) { + public function get($collection, $owner = NULL) { // Use the currently authenticated user ID or the active user ID unless the // owner is overridden. if (!isset($owner)) { - $owner = \Drupal::currentUser()->id() ?: session_id(); + $owner = $this->currentUser->id() ?: session_id(); } // Store the data for this collection in the database. - $storage = $this->storageFactory->get("user.shared_tempstore.$collection"); + $storage = $this->storageFactory->get("tempstore.shared.$collection"); return new SerializableTempstore($storage, $this->lockBackend, $owner, $this->requestStack, $this->expire); } diff --git a/sites/all/modules/contrib/dev/ctools/src/Testing/EntityCreationTrait.php b/sites/all/modules/contrib/dev/ctools/src/Testing/EntityCreationTrait.php index 358f8a17a..6de483d60 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Testing/EntityCreationTrait.php +++ b/sites/all/modules/contrib/dev/ctools/src/Testing/EntityCreationTrait.php @@ -2,7 +2,6 @@ namespace Drupal\ctools\Testing; - use Drupal\Component\Render\FormattableMarkup; trait EntityCreationTrait { diff --git a/sites/all/modules/contrib/dev/ctools/src/Tests/Wizard/CToolsWizardTest.php b/sites/all/modules/contrib/dev/ctools/src/Tests/Wizard/CToolsWizardTest.php index c9b91eada..d459dfc38 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Tests/Wizard/CToolsWizardTest.php +++ b/sites/all/modules/contrib/dev/ctools/src/Tests/Wizard/CToolsWizardTest.php @@ -1,43 +1,44 @@ drupalGet('ctools/wizard'); - $this->assertText('Form One'); + $this->assertSession()->pageTextContains('Form One'); $this->dumpHeaders = TRUE; // Check that $operations['one']['values'] worked. - $this->assertText('Xylophone'); + $this->assertSession()->pageTextContains('Xylophone'); // Submit first step in the wizard. $edit = [ 'one' => 'test', ]; $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next')); // Redirected to the second step. - $this->assertText('Form Two'); - $this->assertText('Dynamic value submitted: Xylophone'); + $this->assertSession()->pageTextContains('Form Two'); + $this->assertSession()->pageTextContains('Dynamic value submitted: Xylophone'); // Check that $operations['two']['values'] worked. - $this->assertText('Zebra'); + $this->assertSession()->pageTextContains('Zebra'); // Hit previous to make sure our form value are preserved. $this->drupalPostForm(NULL, [], $this->t('Previous')); // Check the known form values. - $this->assertFieldByName('one', 'test'); - $this->assertText('Xylophone'); + $this->assertSession()->fieldValueEquals('one', 'test'); + $this->assertSession()->pageTextContains('Xylophone'); // Goto next step again and finish this wizard. $this->drupalPostForm(NULL, [], $this->t('Next')); $edit = [ @@ -45,44 +46,50 @@ class CToolsWizardTest extends WebTestBase { ]; $this->drupalPostForm(NULL, $edit, $this->t('Finish')); // Check that the wizard finished properly. - $this->assertText('Value One: test'); - $this->assertText('Value Two: Second test'); + $this->assertSession()->pageTextContains('Value One: test'); + $this->assertSession()->pageTextContains('Value Two: Second test'); } - function testStepValidateAndSubmit() { + /** + * Test wizard validate and submit. + */ + public function testStepValidateAndSubmit() { $this->drupalGet('ctools/wizard'); - $this->assertText('Form One'); + $this->assertSession()->pageTextContains('Form One'); // Submit first step in the wizard. $edit = [ 'one' => 'wrong', ]; $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next')); // We're still on the first form and the error is present. - $this->assertText('Form One'); - $this->assertText('Cannot set the value to "wrong".'); + $this->assertSession()->pageTextContains('Form One'); + $this->assertSession()->pageTextContains('Cannot set the value to "wrong".'); // Try again with the magic value. $edit = [ 'one' => 'magic', ]; $this->drupalPostForm('ctools/wizard', $edit, $this->t('Next')); // Redirected to the second step. - $this->assertText('Form Two'); + $this->assertSession()->pageTextContains('Form Two'); $edit = [ 'two' => 'Second test', ]; $this->drupalPostForm(NULL, $edit, $this->t('Finish')); // Check that the magic value triggered our submit callback. - $this->assertText('Value One: Abraham'); - $this->assertText('Value Two: Second test'); + $this->assertSession()->pageTextContains('Value One: Abraham'); + $this->assertSession()->pageTextContains('Value Two: Second test'); } - function testEntityWizard() { + /** + * Test wizard entity config update. + */ + public function testEntityWizard() { $this->drupalLogin($this->drupalCreateUser(['administer site configuration'])); // Start adding a new config entity. $this->drupalGet('admin/structure/ctools_wizard_test_config_entity/add'); - $this->assertText('Example entity'); - $this->assertNoText('Existing entity'); + $this->assertSession()->pageTextContains('Example entity'); + $this->assertSession()->pageTextNotContains('Existing entity'); // Submit the general step. $edit = [ @@ -104,33 +111,32 @@ class CToolsWizardTest extends WebTestBase { $this->drupalPostForm(NULL, $edit, $this->t('Finish')); // Now we should be looking at the list of entities. - $this->assertUrl('admin/structure/ctools_wizard_test_config_entity'); - $this->assertText('Test Config Entity 123'); + $this->assertSession()->addressEquals('admin/structure/ctools_wizard_test_config_entity'); + $this->assertSession()->pageTextContains('Test Config Entity 123'); // Edit the entity again and make sure the values are what we expect. $this->clickLink(t('Edit')); - $this->assertText('Existing entity'); - $this->assertFieldByName('label', 'Test Config Entity 123'); + $this->assertSession()->pageTextContains('Existing entity'); + $this->assertSession()->fieldValueEquals('label', 'Test Config Entity 123'); $this->clickLink(t('Form One')); - $this->assertFieldByName('one', 'The first bit'); + $this->assertSession()->fieldValueEquals('one', 'The first bit'); $previous = $this->getUrl(); $this->clickLink(t('Show on dialog')); - $this->assertRaw('Value from one: The first bit'); + $this->assertSession()->responseContains('Value from one: The first bit'); $this->drupalGet($previous); // Change the value for 'one'. $this->drupalPostForm(NULL, ['one' => 'New value'], $this->t('Next')); - $this->assertFieldByName('two', 'The second bit'); + $this->assertSession()->fieldValueEquals('two', 'The second bit'); $this->drupalPostForm(NULL, [], $this->t('Next')); // Make sure we get the additional step because the entity exists. - $this->assertText('This step only shows if the entity is already existing!'); + $this->assertSession()->pageTextContains('This step only shows if the entity is already existing!'); $this->drupalPostForm(NULL, [], $this->t('Finish')); // Edit the entity again and make sure the change stuck. - $this->assertUrl('admin/structure/ctools_wizard_test_config_entity'); + $this->assertSession()->addressEquals('admin/structure/ctools_wizard_test_config_entity'); $this->clickLink(t('Edit')); - $this->drupalPostForm(NULL, [], $this->t('Next')); - $this->assertFieldByName('one', 'New value'); + $this->clickLink(t('Form One')); + $this->assertSession()->fieldValueEquals('one', 'New value'); } } - diff --git a/sites/all/modules/contrib/dev/ctools/src/TypedDataResolver.php b/sites/all/modules/contrib/dev/ctools/src/TypedDataResolver.php index 737ab3ce1..f06ec419c 100644 --- a/sites/all/modules/contrib/dev/ctools/src/TypedDataResolver.php +++ b/sites/all/modules/contrib/dev/ctools/src/TypedDataResolver.php @@ -2,11 +2,8 @@ namespace Drupal\ctools; - -use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; -use Drupal\Core\Plugin\Context\ContextDefinitionInterface; use Drupal\Core\Plugin\Context\ContextInterface; use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\TypedData\ComplexDataDefinitionInterface; diff --git a/sites/all/modules/contrib/dev/ctools/src/Wizard/EntityFormWizardBase.php b/sites/all/modules/contrib/dev/ctools/src/Wizard/EntityFormWizardBase.php index 1647f734a..9cac6854b 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Wizard/EntityFormWizardBase.php +++ b/sites/all/modules/contrib/dev/ctools/src/Wizard/EntityFormWizardBase.php @@ -2,14 +2,13 @@ namespace Drupal\ctools\Wizard; - use Drupal\Core\DependencyInjection\ClassResolverInterface; -use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\ctools\Event\WizardEvent; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -18,14 +17,14 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; abstract class EntityFormWizardBase extends FormWizardBase implements EntityFormWizardInterface { /** - * The entity manager. + * The entity type manager. * - * @var \Drupal\Core\Entity\EntityManagerInterface + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityManager; + protected $entityTypeManager; /** - * @param \Drupal\user\SharedTempStoreFactory $tempstore + * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore * Tempstore Factory for keeping track of values in each step of the * wizard. * @param \Drupal\Core\Form\FormBuilderInterface $builder @@ -34,8 +33,8 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm * The class resolver. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. - * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager - * The entity manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. * @param $tempstore_id * The shared temp store factory collection name. * @param null $machine_name @@ -43,8 +42,8 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm * @param null $step * The current active step of the wizard. */ - public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterface $builder, ClassResolverInterface $class_resolver, EventDispatcherInterface $event_dispatcher, EntityManagerInterface $entity_manager, RouteMatchInterface $route_match, $tempstore_id, $machine_name = NULL, $step = NULL) { - $this->entityManager = $entity_manager; + public function __construct(SharedTempStoreFactory $tempstore, FormBuilderInterface $builder, ClassResolverInterface $class_resolver, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, RouteMatchInterface $route_match, $tempstore_id, $machine_name = NULL, $step = NULL) { + $this->entityTypeManager = $entity_type_manager; parent::__construct($tempstore, $builder, $class_resolver, $event_dispatcher, $route_match, $tempstore_id, $machine_name, $step); } @@ -53,11 +52,14 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm */ public static function getParameters() { return [ - 'tempstore' => \Drupal::service('user.shared_tempstore'), + 'tempstore' => \Drupal::service('tempstore.shared'), 'builder' => \Drupal::service('form_builder'), 'class_resolver' => \Drupal::service('class_resolver'), 'event_dispatcher' => \Drupal::service('event_dispatcher'), + // Keep the deprecated entity manager service as a parameter as well for + // BC, so that subclasses still work. 'entity_manager' => \Drupal::service('entity.manager'), + 'entity_type_manager' => \Drupal::service('entity_type.manager'), ]; } @@ -65,7 +67,7 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm * {@inheritdoc} */ public function initValues() { - $storage = $this->entityManager->getStorage($this->getEntityType()); + $storage = $this->entityTypeManager->getStorage($this->getEntityType()); if ($this->getMachineName()) { $values = $this->getTempstore()->get($this->getMachineName()); if (!$values) { @@ -89,24 +91,25 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm */ public function finish(array &$form, FormStateInterface $form_state) { $cached_values = $form_state->getTemporaryValue('wizard'); - /** @var $entity \Drupal\Core\Entity\EntityInterface */ + /** @var \Drupal\Core\Entity\EntityInterface $entity */ $entity = $cached_values[$this->getEntityType()]; $entity->set('id', $cached_values['id']); $entity->set('label', $cached_values['label']); $status = $entity->save(); - $definition = $this->entityManager->getDefinition($this->getEntityType()); - if ($status) { - drupal_set_message($this->t('Saved the %label @entity_type.', array( - '%label' => $entity->label(), - '@entity_type' => $definition->getLabel(), - ))); + + $arguments = [ + '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), + '%label' => $entity->label(), + ]; + if ($status === SAVED_UPDATED) { + $this->messenger()->addMessage($this->t('The @entity-type %label has been updated.', $arguments)); + $this->logger($entity->getEntityType()->getProvider())->notice('Updated @entity-type %label.', $arguments); } - else { - drupal_set_message($this->t('The %label @entity_type was not saved.', array( - '%label' => $entity->label(), - '@entity_type' => $definition->getLabel(), - ))); + elseif ($status === SAVED_NEW) { + $this->messenger()->addMessage($this->t('The @entity-type %label has been added.', $arguments)); + $this->logger($entity->getEntityType()->getProvider())->notice('Added @entity-type %label.', $arguments); } + $form_state->setRedirectUrl($entity->toUrl('collection')); parent::finish($form, $form_state); } @@ -122,7 +125,7 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm protected function customizeForm(array $form, FormStateInterface $form_state) { $form = parent::customizeForm($form, $form_state); if ($this->machine_name) { - $entity = $this->entityManager->getStorage($this->getEntityType()) + $entity = $this->entityTypeManager->getStorage($this->getEntityType()) ->load($this->machine_name); } else { @@ -145,7 +148,7 @@ abstract class EntityFormWizardBase extends FormWizardBase implements EntityForm $default_operation = reset($operations); if ($operation['form'] == $default_operation['form']) { // Get the plugin definition of this entity. - $definition = $this->entityManager->getDefinition($this->getEntityType()); + $definition = $this->entityTypeManager->getDefinition($this->getEntityType()); // Create id and label form elements. $form['name'] = array( '#type' => 'fieldset', diff --git a/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardBase.php b/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardBase.php index e537ef804..df8048e6c 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardBase.php +++ b/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardBase.php @@ -13,7 +13,8 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; use Drupal\ctools\Ajax\OpenModalWizardCommand; use Drupal\ctools\Event\WizardEvent; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -24,7 +25,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { /** * Tempstore Factory for keeping track of values in each step of the wizard. * - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -71,7 +72,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { protected $step; /** - * @param \Drupal\user\SharedTempStoreFactory $tempstore + * @param \Drupal\Core\TempStore\SharedTempStoreFactory $tempstore * Tempstore Factory for keeping track of values in each step of the * wizard. * @param \Drupal\Core\Form\FormBuilderInterface $builder @@ -103,7 +104,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { */ public static function getParameters() { return [ - 'tempstore' => \Drupal::service('user.shared_tempstore'), + 'tempstore' => \Drupal::service('tempstore.shared'), 'builder' => \Drupal::service('form_builder'), 'class_resolver' => \Drupal::service('class_resolver'), 'event_dispatcher' => \Drupal::service('event_dispatcher'), @@ -131,7 +132,8 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { * {@inheritdoc} */ public function getTempstore() { - return $this->tempstore->get($this->getTempstoreId()); + $tempstore = $this->tempstore->get($this->getTempstoreId()); + return $tempstore; } /** @@ -393,7 +395,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { $actions['submit']['#ajax'] = [ 'callback' => '::ajaxSubmit', 'url' => Url::fromRoute($this->getRouteName(), $parameters), - 'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], + 'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], ]; } @@ -419,7 +421,7 @@ abstract class FormWizardBase extends FormBase implements FormWizardInterface { $actions['previous']['#ajax'] = [ 'callback' => '::ajaxPrevious', 'url' => Url::fromRoute($this->getRouteName(), $parameters), - 'options' => ['query' => \Drupal::request()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], + 'options' => ['query' => $this->getRequest()->query->all() + [FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]], ]; } } diff --git a/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardInterface.php b/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardInterface.php index 54043c3ba..53ccc48fc 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardInterface.php +++ b/sites/all/modules/contrib/dev/ctools/src/Wizard/FormWizardInterface.php @@ -39,7 +39,7 @@ interface FormWizardInterface extends FormInterface { /** * The active SharedTempStore for this wizard. * - * @return \Drupal\user\SharedTempStore + * @return \Drupal\Core\TempStore\SharedTempStore */ public function getTempstore(); diff --git a/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactory.php b/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactory.php index 27522d066..4edfe8a4d 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactory.php +++ b/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactory.php @@ -5,6 +5,7 @@ namespace Drupal\ctools\Wizard; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormState; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Drupal\Core\Render\Renderer; class WizardFactory implements WizardFactoryInterface { @@ -23,14 +24,26 @@ class WizardFactory implements WizardFactoryInterface { protected $dispatcher; /** + * The object renderer. + * + * @var \Drupal\Core\Render\Renderer + */ + protected $renderer; + + /** + * The construct method. + * * @param \Drupal\Core\Form\FormBuilderInterface $form_builder * The form builder. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. + * @param \Drupal\Core\Render\Renderer $renderer + * The object renderer. */ - public function __construct(FormBuilderInterface $form_builder, EventDispatcherInterface $event_dispatcher) { + public function __construct(FormBuilderInterface $form_builder, EventDispatcherInterface $event_dispatcher, Renderer $renderer) { $this->builder = $form_builder; $this->dispatcher = $event_dispatcher; + $this->renderer = $renderer; } /** @@ -42,9 +55,8 @@ class WizardFactory implements WizardFactoryInterface { if ($ajax) { $form['#attached']['library'][] = 'core/drupal.dialog.ajax'; - $status_messages = array('#type' => 'status_messages'); - // @todo properly inject the renderer. Core should really be doing this work. - if ($messages = \Drupal::service('renderer')->renderRoot($status_messages)) { + $status_messages = ['#type' => 'status_messages']; + if ($messages = $this->renderer->renderRoot($status_messages)) { if (!empty($form['#prefix'])) { // Form prefix is expected to be a string. Prepend the messages to // that string. diff --git a/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactoryInterface.php b/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactoryInterface.php index 34b00a8f8..d9b3c8e71 100644 --- a/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactoryInterface.php +++ b/sites/all/modules/contrib/dev/ctools/src/Wizard/WizardFactoryInterface.php @@ -1,4 +1,5 @@ getLabel($entity); + $row['label'] = $entity->label(); $row['id'] = $entity->id(); // You probably want a few more properties here... return $row + parent::buildRow($entity); diff --git a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php index 487751d92..035034ca4 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php +++ b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityDeleteForm.php @@ -36,8 +36,7 @@ class ExampleConfigEntityDeleteForm extends EntityConfirmFormBase { */ public function submitForm(array &$form, FormStateInterface $form_state) { $this->entity->delete(); - - drupal_set_message( + $this->messenger()->addMessage( $this->t('content @type: deleted @label.', [ '@type' => $this->entity->bundle(), diff --git a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php index 2ba871c28..d82b80c4c 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php +++ b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/ExampleConfigEntityExternalForm.php @@ -2,18 +2,20 @@ namespace Drupal\ctools_wizard_test\Form; - use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\user\SharedTempStoreFactory; +use Drupal\Core\TempStore\SharedTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; +/** + * Example form config entity. + */ class ExampleConfigEntityExternalForm extends FormBase { /** * Tempstore factory. * - * @var \Drupal\user\SharedTempStoreFactory + * @var \Drupal\Core\TempStore\SharedTempStoreFactory */ protected $tempstore; @@ -21,8 +23,9 @@ class ExampleConfigEntityExternalForm extends FormBase { * Constructs a new ExampleConfigEntityExternalForm. * * @param \Drupal\ctools_wizard_test\Form\SharedTempStoreFactory $tempstore + * Creates a shared temporary storage for a collection. */ - function __construct(SharedTempStoreFactory $tempstore) { + public function __construct(SharedTempStoreFactory $tempstore) { $this->tempstore = $tempstore; } @@ -30,7 +33,7 @@ class ExampleConfigEntityExternalForm extends FormBase { * {@inheritdoc} */ public static function create(ContainerInterface $container) { - return new static($container->get('user.shared_tempstore')); + return new static($container->get('tempstore.shared')); } /** @@ -63,4 +66,3 @@ class ExampleConfigEntityExternalForm extends FormBase { } } - diff --git a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php index 037505637..6ee5d67ef 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php +++ b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Form/OneForm.php @@ -64,8 +64,7 @@ class OneForm extends FormBase { $cached_values[$key] = $form_state->getValue($key); } $form_state->setTemporaryValue('wizard', $cached_values); - - drupal_set_message($this->t('Dynamic value submitted: @value', ['@value' => $cached_values['dynamic']]));; + $this->messenger()->addMessage($this->t('Dynamic value submitted: @value', ['@value' => $cached_values['dynamic']])); } } diff --git a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php index 539df331d..49a3ce597 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php +++ b/sites/all/modules/contrib/dev/ctools/tests/modules/ctools_wizard_test/src/Wizard/WizardTest.php @@ -74,8 +74,8 @@ class WizardTest extends FormWizardBase { */ public function finish(array &$form, FormStateInterface $form_state) { $cached_values = $form_state->getTemporaryValue('wizard'); - drupal_set_message($this->t('Value One: @one', ['@one' => $cached_values['one']])); - drupal_set_message($this->t('Value Two: @two', ['@two' => $cached_values['two']])); + $this->messenger()->addMessage($this->t('Value One: @one', ['@one' => $cached_values['one']])); + $this->messenger()->addMessage($this->t('Value Two: @two', ['@two' => $cached_values['two']])); parent::finish($form, $form_state); } diff --git a/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/BlockDisplayVariantTest.php b/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/BlockDisplayVariantTest.php new file mode 100644 index 000000000..01e462665 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/BlockDisplayVariantTest.php @@ -0,0 +1,53 @@ +prophesize(EventDispatcherInterface::class); + // Swap in a mock event dispatcher so we can spy on method calls. + $this->container->set('event_dispatcher', $event_dispatcher->reveal()); + + $variant = BlockDisplayVariant::create( + $this->container, + [], + 'foobar', + [] + ); + // Set up the expected calls to the event dispatcher. + $event = Argument::type(BlockVariantEvent::class); + + $event_dispatcher->dispatch(BlockVariantEvents::ADD_BLOCK, $event) + ->shouldBeCalled(); + $event_dispatcher->dispatch(BlockVariantEvents::UPDATE_BLOCK, $event) + ->shouldBeCalled(); + $event_dispatcher->dispatch(BlockVariantEvents::DELETE_BLOCK, $event) + ->shouldBeCalled(); + + $block_id = $variant->addBlock(['id' => 'system_powered_by_block']); + $variant->updateBlock($block_id, []); + $variant->removeBlock($block_id); + } + +} diff --git a/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/RelationshipManagerTest.php b/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/RelationshipManagerTest.php index 94ca6d9c0..909c52c68 100644 --- a/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/RelationshipManagerTest.php +++ b/sites/all/modules/contrib/dev/ctools/tests/src/Kernel/RelationshipManagerTest.php @@ -6,7 +6,7 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; /** - * @coversDefaultClass \Drupal\ctools\Plugin\RelationshipManagerInterface + * @coversDefaultClass \Drupal\ctools\Plugin\RelationshipManager * @group CTools */ class RelationshipManagerTest extends RelationshipsTestBase { diff --git a/sites/all/modules/contrib/dev/ctools/views_content/views_content.admin.inc b/sites/all/modules/contrib/dev/ctools/views_content/views_content.admin.inc new file mode 100644 index 000000000..589cf42b6 --- /dev/null +++ b/sites/all/modules/contrib/dev/ctools/views_content/views_content.admin.inc @@ -0,0 +1,6 @@ +=8.5.0) + - drupal:system (>=8.6.0) -# Information added by Drupal.org packaging script on 2018-06-08 -version: '8.x-1.0-beta4' +# Information added by Drupal.org packaging script on 2018-10-11 +version: '8.x-1.0-rc1' core: '8.x' project: 'entity' -datestamp: 1528452194 +datestamp: 1539272605 diff --git a/sites/all/modules/contrib/dev/entity/entity.module b/sites/all/modules/contrib/dev/entity/entity.module index c15e3f7ed..546f32278 100644 --- a/sites/all/modules/contrib/dev/entity/entity.module +++ b/sites/all/modules/contrib/dev/entity/entity.module @@ -5,8 +5,14 @@ * Provides expanded entity APIs. */ +use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\entity\BundlePlugin\BundlePluginHandler; +use Drupal\entity\QueryAccess\EntityQueryAlter; +use Drupal\entity\QueryAccess\ViewsQueryAlter; +use Drupal\views\Plugin\views\query\QueryPluginBase; +use Drupal\views\Plugin\views\query\Sql; +use Drupal\views\ViewExecutable; /** * Gets the entity types which use bundle plugins. @@ -86,3 +92,29 @@ function entity_module_preuninstall($module) { \Drupal::service('entity.bundle_plugin_installer')->uninstallBundles($entity_type, [$module]); } } + +/** + * Implements hook_query_TAG_alter(). + */ +function entity_query_entity_query_alter(SelectInterface $query) { + $entity_type_id = $query->getMetaData('entity_type'); + if ($query->hasTag($entity_type_id . '_access')) { + $entity_type_manager = \Drupal::entityTypeManager(); + $entity_type = $entity_type_manager->getDefinition($entity_type_id); + + \Drupal::service('class_resolver') + ->getInstanceFromDefinition(EntityQueryAlter::class) + ->alter($query, $entity_type); + } +} + +/** + * Implements hook_views_query_alter(). + */ +function entity_views_query_alter(ViewExecutable $view, QueryPluginBase $query) { + if ($query instanceof Sql) { + \Drupal::service('class_resolver') + ->getInstanceFromDefinition(ViewsQueryAlter::class) + ->alter($query, $view); + } +} diff --git a/sites/all/modules/contrib/dev/entity/entity.services.yml b/sites/all/modules/contrib/dev/entity/entity.services.yml index c4e5f83f5..3e690d122 100644 --- a/sites/all/modules/contrib/dev/entity/entity.services.yml +++ b/sites/all/modules/contrib/dev/entity/entity.services.yml @@ -1,10 +1,4 @@ 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'] diff --git a/sites/all/modules/contrib/dev/entity/src/Access/EntityDeleteMultipleAccessCheck.php b/sites/all/modules/contrib/dev/entity/src/Access/EntityDeleteMultipleAccessCheck.php deleted file mode 100644 index a04cda68c..000000000 --- a/sites/all/modules/contrib/dev/entity/src/Access/EntityDeleteMultipleAccessCheck.php +++ /dev/null @@ -1,87 +0,0 @@ -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(); - } - -} diff --git a/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php b/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php index 4e1573c2a..799af140b 100644 --- a/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php +++ b/sites/all/modules/contrib/dev/entity/src/Access/EntityRevisionRouteAccessChecker.php @@ -26,7 +26,7 @@ class EntityRevisionRouteAccessChecker implements AccessInterface { * * @var array */ - protected $accessCache = array(); + protected $accessCache = []; /** * The currently active route match object. diff --git a/sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php b/sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php index d583db337..70e791e47 100644 --- a/sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php +++ b/sites/all/modules/contrib/dev/entity/src/EntityPermissionProvider.php @@ -13,8 +13,8 @@ use Drupal\Core\Entity\EntityTypeInterface; * Provided permissions: * - administer $entity_type * - access $entity_type overview - * - view ($bundle) $entity_type * - view own unpublished $entity_type + * - view ($bundle) $entity_type * - update (own|any) ($bundle) $entity_type * - delete (own|any) ($bundle) $entity_type * - create $bundle $entity_type diff --git a/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php b/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php index aed711dfc..2a7dbc2df 100644 --- a/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php +++ b/sites/all/modules/contrib/dev/entity/src/Form/DeleteMultipleForm.php @@ -2,322 +2,13 @@ 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; +use Drupal\Core\Entity\Form\DeleteMultipleForm as CoreDeleteMultipleForm; + +@trigger_error('\Drupal\entity\Form\DeleteMultipleForm has been deprecated in favor of \Drupal\Core\Entity\Form\DeleteMultipleForm. Use that instead.'); /** * Provides an entities deletion confirmation form. + * + * @deprecated Use \Drupal\Core\Entity\Form\DeleteMultipleForm instead. */ -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(''); - } - } - - /** - * {@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) - The following @entity_type translations will be deleted:', - [ - '@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."); - } - -} +class DeleteMultipleForm extends CoreDeleteMultipleForm {} diff --git a/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php b/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php index c94c53a0c..f4a4a843c 100644 --- a/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php +++ b/sites/all/modules/contrib/dev/entity/src/Form/RevisionRevertForm.php @@ -119,10 +119,17 @@ class RevisionRevertForm extends ConfirmFormBase { $original_revision_timestamp = $this->revision->getRevisionCreationTime(); $this->revision->setRevisionLogMessage($this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)])); - drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)])); + $this->messenger()->addStatus(t('@type %title has been reverted to the revision from %revision-date.', [ + '@type' => $this->getBundleLabel($this->revision), + '%title' => $this->revision->label(), + '%revision-date' => $this->dateFormatter->format($original_revision_timestamp), + ])); } else { - drupal_set_message(t('@type %title has been reverted', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label()])); + $this->messenger()->addStatus(t('@type %title has been reverted', [ + '@type' => $this->getBundleLabel($this->revision), + '%title' => $this->revision->label(), + ])); } $this->revision->save(); diff --git a/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php b/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php index 6f9ae461b..6e7d8c7f4 100644 --- a/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php +++ b/sites/all/modules/contrib/dev/entity/src/Form/RevisionableContentEntityForm.php @@ -11,15 +11,14 @@ use Drupal\Core\Form\FormStateInterface; /** * Extends the base entity form with revision support in the UI. * - * @deprecated in favor of \Drupal\Core\Entity\ContentEntityForm. Use that - * instead. + * @deprecated Use \Drupal\Core\Entity\ContentEntityForm instead. */ class RevisionableContentEntityForm extends ContentEntityForm { /** * The entity being used by this form. * - * @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\entity\Revision\EntityRevisionLogInterface + * @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface */ protected $entity; @@ -49,7 +48,8 @@ class RevisionableContentEntityForm extends ContentEntityForm { * The bundle entity, or NULL if there is none. */ protected function getBundleEntity() { - if ($bundle_key = $this->entity->getEntityType()->getKey('bundle')) { + if ($this->entity->getEntityType()->getBundleEntityType()) { + $bundle_key = $this->entity->getEntityType()->getKey('bundle'); return $this->entity->{$bundle_key}->referencedEntities()[0]; } return NULL; diff --git a/sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php b/sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php index b0103709d..4ddd554f6 100644 --- a/sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php +++ b/sites/all/modules/contrib/dev/entity/src/Menu/EntityCollectionLocalActionProvider.php @@ -30,6 +30,12 @@ class EntityCollectionLocalActionProvider implements EntityLocalActionProviderIn /* @see \Drupal\entity\Menu\EntityAddLocalAction::getTitle() */ 'title' => 'Add ' . $entity_type->getSingularLabel(), 'route_name' => $route_name, + 'options' => [ + // Redirect back to the collection after form submission. + 'query' => [ + 'destination' => $entity_type->getLinkTemplate('collection'), + ], + ], 'appears_on' => ["entity.$entity_type_id.collection"], 'class' => EntityAddLocalAction::class, ]; diff --git a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php index 51c9d39c1..68a8bc524 100644 --- a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php +++ b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/DeleteAction.php @@ -2,96 +2,19 @@ namespace Drupal\entity\Plugin\Action; -use Drupal\Core\Action\ActionBase; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\Core\TempStore\PrivateTempStoreFactory; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Action\Plugin\Action\DeleteAction as CoreDeleteAction; + +@trigger_error('\Drupal\entity\Plugin\Action\DeleteAction has been deprecated in favor of \Drupal\Core\Action\Plugin\Action\DeleteAction. Use that instead.'); /** * Redirects to an entity deletion form. * + * @deprecated Use "entity:delete_action" instead. + * * @Action( * id = "entity_delete_action", * label = @Translation("Delete entity"), * deriver = "Drupal\entity\Plugin\Action\Derivative\DeleteActionDeriver", * ) */ -class DeleteAction extends ActionBase implements ContainerFactoryPluginInterface { - - /** - * The tempstore object. - * - * @var \Drupal\Core\TempStore\SharedTempStore - */ - protected $tempStore; - - /** - * The current user. - * - * @var \Drupal\Core\Session\AccountInterface - */ - protected $currentUser; - - /** - * Constructs a new DeleteAction 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\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - * @param \Drupal\Core\Session\AccountInterface $current_user - * Current user. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { - $this->currentUser = $current_user; - $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); - - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('tempstore.private'), - $container->get('current_user') - ); - } - - /** - * {@inheritdoc} - */ - public function executeMultiple(array $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() . ':' . $this->getPluginDefinition()['type'], $selection); - } - - /** - * {@inheritdoc} - */ - public function execute($object = NULL) { - $this->executeMultiple([$object]); - } - - /** - * {@inheritdoc} - */ - public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { - return $object->access('delete', $account, $return_as_object); - } - -} +class DeleteAction extends CoreDeleteAction {} diff --git a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php index 1f7d76b8d..05bd28204 100644 --- a/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php +++ b/sites/all/modules/contrib/dev/entity/src/Plugin/Action/Derivative/DeleteActionDeriver.php @@ -4,13 +4,14 @@ namespace Drupal\entity\Plugin\Action\Derivative; use Drupal\Component\Plugin\Derivative\DeriverBase; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; -use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a delete action for each content entity type. + * + * @deprecated */ class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterface { @@ -46,7 +47,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->getSingularLabel()]); + $definition['label'] = t('Delete @entity_type (Deprecated)', ['@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; @@ -69,7 +70,10 @@ class DeleteActionDeriver extends DeriverBase implements ContainerDeriverInterfa protected function getParticipatingEntityTypes() { $entity_types = $this->entityTypeManager->getDefinitions(); $entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) { - return $entity_type->entityClassImplements(ContentEntityInterface::class) && $entity_type->hasLinkTemplate('delete-multiple-form'); + // Core requires a "delete-multiple-confirm" form to be declared as well, + // if it's missing, it's safe to assume that the entity type is still + // relying on previous Entity API contrib behavior. + return $entity_type->hasLinkTemplate('delete-multiple-form') && !$entity_type->hasHandlerClass('form', 'delete-multiple-confirm'); }); return $entity_types; diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/Condition.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/Condition.php new file mode 100644 index 000000000..ef1f630c3 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/Condition.php @@ -0,0 +1,113 @@ +', '<', '<=', '>', '>=', 'BETWEEN', 'NOT BETWEEN', + 'IN', 'NOT IN', 'IS NULL', 'IS NOT NULL', + ]; + + /** + * The field. + * + * @var string + */ + protected $field; + + /** + * The value. + * + * @var mixed + */ + protected $value; + + /** + * The operator. + * + * @var string + */ + protected $operator; + + /** + * Constructs a new Condition object. + * + * @param string $field + * The field, with an optional column name. E.g: 'uid', 'address.locality'. + * @param mixed $value + * The value. + * @param string $operator + * The operator. + * Possible values: =, <>, <, <=, >, >=, BETWEEN, NOT BETWEEN, + * IN, NOT IN, IS NULL, IS NOT NULL. + */ + public function __construct($field, $value, $operator = NULL) { + // Provide a default based on the data type of the value. + if (!isset($operator)) { + $operator = is_array($value) ? 'IN' : '='; + } + // Validate the selected operator. + if (!in_array($operator, self::$supportedOperators)) { + throw new \InvalidArgumentException(sprintf('Unrecognized operator "%s".', $operator)); + } + + $this->field = $field; + $this->value = $value; + $this->operator = $operator; + } + + /** + * {@inheritdoc} + */ + public function getField() { + return $this->field; + } + + /** + * {@inheritdoc} + */ + public function getValue() { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getOperator() { + return $this->operator; + } + + /** + * Gets the string representation of the condition. + * + * Used for debugging purposes. + * + * @return string + * The string representation of the condition. + */ + public function __toString() { + if (in_array($this->operator, ['IS NULL', 'IS NOT NULL'])) { + return "{$this->field} {$this->operator}"; + } + else { + if (is_array($this->value)) { + $value = "['" . implode("', '", $this->value) . "']"; + } + else { + $value = "'" . $this->value . "'"; + } + + return "{$this->field} {$this->operator} $value"; + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/ConditionGroup.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/ConditionGroup.php new file mode 100644 index 000000000..094feb109 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/ConditionGroup.php @@ -0,0 +1,227 @@ +addCondition('type', ['article', 'page']); + * $condition_group->addCondition('uid', '1'); + * + * // Filter by node type or status. + * $condition_group = new ConditionGroup('OR'); + * $condition_group->addCondition('type', ['article', 'page']); + * $condition_group->addCondition('status', '1', '<>'); + * + * // Nested condition groups: node type AND (uid OR status). + * $condition_group = new ConditionGroup(); + * $condition_group->addCondition('type', ['article', 'page']); + * $condition_group->addCondition((new ConditionGroup('OR')) + * ->addCondition('uid', 1) + * ->addCondition('status', '1') + * ); + * @endcode + */ +final class ConditionGroup implements \Countable, RefinableCacheableDependencyInterface { + + use RefinableCacheableDependencyTrait; + + /** + * The conjunction. + * + * @var string + */ + protected $conjunction; + + /** + * The conditions. + * + * @var \Drupal\entity\QueryAccess\Condition[]|\Drupal\entity\QueryAccess\ConditionGroup[] + */ + protected $conditions = []; + + /** + * Whether the condition group is always FALSE. + * + * @var bool + */ + protected $alwaysFalse = FALSE; + + /** + * Constructs a new ConditionGroup object. + * + * @param string $conjunction + * The conjunction. + */ + public function __construct($conjunction = 'AND') { + $this->conjunction = $conjunction; + } + + /** + * Gets the conjunction. + * + * @return string + * The conjunction. Possible values: AND, OR. + */ + public function getConjunction() { + return $this->conjunction; + } + + /** + * Gets all conditions and nested condition groups. + * + * @return \Drupal\entity\QueryAccess\Condition[]|\Drupal\entity\QueryAccess\ConditionGroup[] + * The conditions, where each one is either a Condition or a nested + * ConditionGroup. Returned by reference, to allow callers to replace + * or remove conditions. + */ + public function &getConditions() { + return $this->conditions; + } + + /** + * Adds a condition. + * + * @param string|\Drupal\entity\QueryAccess\ConditionGroup $field + * Either a condition group (for nested AND/OR conditions), or a + * field name with an optional column name. E.g: 'uid', 'address.locality'. + * @param mixed $value + * The value. + * @param string $operator + * The operator. + * Possible values: =, <>, <, <=, >, >=, BETWEEN, NOT BETWEEN, + * IN, NOT IN, IS NULL, IS NOT NULL. + * + * @return $this + */ + public function addCondition($field, $value = NULL, $operator = NULL) { + if ($field instanceof ConditionGroup) { + if ($field->count() === 1) { + // The condition group only has a single condition, merge it. + $this->conditions[] = reset($field->getConditions()); + $this->addCacheTags($field->getCacheTags()); + $this->addCacheContexts($field->getCacheContexts()); + $this->mergeCacheMaxAge($field->getCacheMaxAge()); + } + elseif ($field->count() > 1) { + $this->conditions[] = $field; + } + } + else { + $this->conditions[] = new Condition($field, $value, $operator); + } + + return $this; + } + + /** + * Gets whether the condition group is always FALSE. + * + * Used when the user doesn't have access to any entities, to ensure that a + * query returns no results. + * + * @return bool + * Whether the condition group is always FALSE. + */ + public function isAlwaysFalse() { + return $this->alwaysFalse; + } + + /** + * Sets whether the condition group should always be FALSE. + * + * @param bool $always_false + * Whether the condition group should always be FALSE. + * + * @return $this + */ + public function alwaysFalse($always_false = TRUE) { + $this->alwaysFalse = $always_false; + return $this; + } + + /** + * Clones the contained conditions when the condition group is cloned. + */ + public function __clone() { + foreach ($this->conditions as $i => $condition) { + $this->conditions[$i] = clone $condition; + } + } + + /** + * Gets the string representation of the condition group. + * + * @return string + * The string representation of the condition group. + */ + public function __toString() { + // Special case for a single, nested condition group: + if (count($this->conditions) == 1) { + return (string) reset($this->conditions); + } + $lines = []; + foreach ($this->conditions as $condition) { + $lines[] = str_replace("\n", "\n ", (string) $condition); + } + return $lines ? "(\n " . implode("\n {$this->conjunction}\n ", $lines) . "\n)" : ''; + } + + /** + * {@inheritdoc} + */ + public function count() { + return count($this->conditions); + } + + /** + * {@inheritdoc} + */ + public function getCacheTags() { + $tags = $this->cacheTags; + foreach ($this->conditions as $condition) { + if ($condition instanceof ConditionGroup) { + $tags = array_merge($tags, $condition->getCacheTags()); + } + } + return Cache::mergeTags($tags, []); + } + + /** + * {@inheritdoc} + */ + public function getCacheContexts() { + $cache_contexts = $this->cacheContexts; + foreach ($this->conditions as $condition) { + if ($condition instanceof ConditionGroup) { + $cache_contexts = array_merge($cache_contexts, $condition->getCacheContexts()); + } + } + return Cache::mergeContexts($cache_contexts); + } + + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + $max_age = $this->cacheMaxAge; + foreach ($this->conditions as $condition) { + if ($condition instanceof ConditionGroup) { + $max_age = Cache::mergeMaxAges($max_age, $condition->getCacheMaxAge()); + } + } + return $max_age; + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/EntityQueryAlter.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/EntityQueryAlter.php new file mode 100644 index 000000000..25c7395c8 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/EntityQueryAlter.php @@ -0,0 +1,181 @@ +entityFieldManager = $entity_field_manager; + $this->entityTypeManager = $entity_type_manager; + $this->renderer = $renderer; + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity_field.manager'), + $container->get('entity_type.manager'), + $container->get('renderer'), + $container->get('request_stack') + ); + } + + /** + * Alters the select query for the given entity type. + * + * @param \Drupal\Core\Database\Query\SelectInterface $query + * The select query. + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + */ + public function alter(SelectInterface $query, EntityTypeInterface $entity_type) { + if (!$entity_type->hasHandlerClass('query_access')) { + return; + } + $entity_type_id = $entity_type->id(); + $storage = $this->entityTypeManager->getStorage($entity_type_id); + if (!$storage instanceof SqlContentEntityStorage) { + return; + } + + /** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */ + $query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access'); + $conditions = $query_access->getConditions('view'); + if ($conditions->isAlwaysFalse()) { + $query->where('1 = 0'); + } + elseif (count($conditions)) { + $sql_conditions = $this->mapConditions($conditions, $query); + $query->condition($sql_conditions); + } + + $this->applyCacheability(CacheableMetadata::createFromObject($conditions)); + } + + /** + * Maps an entity type's access conditions to SQL conditions. + * + * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions + * The access conditions. + * @param \Drupal\Core\Database\Query\SelectInterface $query + * The SQL query. + * @param bool $nested_inside_or + * Whether the access conditions are nested inside an OR condition. + * + * @return \Drupal\Core\Database\Query\ConditionInterface + * The SQL conditions. + */ + protected function mapConditions(ConditionGroup $conditions, SelectInterface $query, $nested_inside_or = FALSE) { + $sql_condition = $query->conditionGroupFactory($conditions->getConjunction()); + $tables = new Tables($query); + $nested_inside_or = $nested_inside_or || $conditions->getConjunction() == 'OR'; + foreach ($conditions->getConditions() as $condition) { + if ($condition instanceof ConditionGroup) { + $nested_sql_conditions = $this->mapConditions($condition, $query, $nested_inside_or); + $sql_condition->condition($nested_sql_conditions); + } + else { + // Access conditions don't specify a langcode. + $langcode = NULL; + $type = $nested_inside_or || $condition->getOperator() === 'IS NULL' ? 'LEFT' : 'INNER'; + $sql_field = $tables->addField($condition->getField(), $type, $langcode); + $value = $condition->getValue(); + $operator = $condition->getOperator(); + // Using LIKE/NOT LIKE ensures a case insensitive comparison. + // @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition(). + $case_sensitive = $tables->isFieldCaseSensitive($condition->getField()); + $operator_map = [ + '=' => 'LIKE', + '<>' => 'NOT LIKE', + ]; + if (!$case_sensitive && isset($operator_map[$operator])) { + $operator = $operator_map[$operator]; + $value = $query->escapeLike($value); + } + + $sql_condition->condition($sql_field, $value, $operator); + } + } + + return $sql_condition; + } + + /** + * Applies the cacheablity metadata to the current request. + * + * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata + * The cacheability metadata. + */ + protected function applyCacheability(CacheableMetadata $cacheable_metadata) { + $request = $this->requestStack->getCurrentRequest(); + if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) { + $build = []; + $cacheable_metadata->applyTo($build); + $this->renderer->render($build); + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessEvent.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessEvent.php new file mode 100644 index 000000000..979010bfb --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessEvent.php @@ -0,0 +1,92 @@ +conditions = $conditions; + $this->operation = $operation; + $this->account = $account; + } + + /** + * Gets the conditions. + * + * If $conditions->isAlwaysFalse() is TRUE, the user doesn't have access to + * any entities, and the query is expected to return no results. + * This can be reversed by calling $conditions->alwaysFalse(FALSE). + * + * If $conditions->isAlwaysFalse() is FALSE, and the condition group is + * empty (count is 0), the user has full access, and the query doesn't + * need to be restricted. + * + * @return \Drupal\entity\QueryAccess\ConditionGroup + * The conditions. + */ + public function getConditions() { + return $this->conditions; + } + + /** + * Gets the operation. + * + * @return string + * The operation. Usually one of "view", "update" or "delete". + */ + public function getOperation() { + return $this->operation; + } + + /** + * Gets the user for which to restrict access. + * + * @return \Drupal\Core\Session\AccountInterface + * The user. + */ + public function getAccount() { + return $this->account; + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandler.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandler.php new file mode 100644 index 000000000..f9ec51c4c --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandler.php @@ -0,0 +1,27 @@ +buildEntityConditions($operation, $account); + } + + return parent::buildEntityOwnerConditions($operation, $account); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerBase.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerBase.php new file mode 100644 index 000000000..aaa131539 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerBase.php @@ -0,0 +1,268 @@ +entityType = $entity_type; + $this->bundleInfo = $bundle_info; + $this->eventDispatcher = $event_dispatcher; + $this->currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity_type.bundle.info'), + $container->get('event_dispatcher'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function getConditions($operation, AccountInterface $account = NULL) { + $account = $account ?: $this->currentUser; + $entity_type_id = $this->entityType->id(); + $conditions = $this->buildConditions($operation, $account); + + // Allow other modules to modify the conditions before they are used. + $event = new QueryAccessEvent($conditions, $operation, $account); + $this->eventDispatcher->dispatch("entity.query_access.{$entity_type_id}", $event); + + return $conditions; + } + + /** + * Builds the conditions for the given operation and user. + * + * @param string $operation + * The access operation. Usually one of "view", "update" or "delete". + * @param \Drupal\Core\Session\AccountInterface $account + * The user for which to restrict access. + * + * @return \Drupal\entity\QueryAccess\ConditionGroup + * The conditions. + */ + public function buildConditions($operation, AccountInterface $account) { + $entity_type_id = $this->entityType->id(); + $has_owner = $this->entityType->entityClassImplements(EntityOwnerInterface::class); + + if ($account->hasPermission("administer {$entity_type_id}")) { + // The user has full access to all operations, no conditions needed. + $conditions = new ConditionGroup('OR'); + $conditions->addCacheContexts(['user.permissions']); + return $conditions; + } + + if ($has_owner) { + $entity_conditions = $this->buildEntityOwnerConditions($operation, $account); + } + else { + $entity_conditions = $this->buildEntityConditions($operation, $account); + } + + $conditions = NULL; + if ($operation == 'view' && $this->entityType->entityClassImplements(EntityPublishedInterface::class)) { + $uid_key = $this->entityType->getKey('uid'); + $published_key = $this->entityType->getKey('published'); + $published_conditions = NULL; + $unpublished_conditions = NULL; + + if ($entity_conditions) { + // Restrict the existing conditions to published entities only. + $published_conditions = new ConditionGroup('AND'); + $published_conditions->addCacheContexts(['user.permissions']); + $published_conditions->addCondition($entity_conditions); + $published_conditions->addCondition($published_key, '1'); + } + if ($has_owner && $account->hasPermission("view own unpublished $entity_type_id")) { + $unpublished_conditions = new ConditionGroup('AND'); + $unpublished_conditions->addCacheContexts(['user']); + $unpublished_conditions->addCondition($uid_key, $account->id()); + $unpublished_conditions->addCondition($published_key, '0'); + } + + if ($published_conditions && $unpublished_conditions) { + $conditions = new ConditionGroup('OR'); + $conditions->addCondition($published_conditions); + $conditions->addCondition($unpublished_conditions); + } + elseif ($published_conditions) { + $conditions = $published_conditions; + } + elseif ($unpublished_conditions) { + $conditions = $unpublished_conditions; + } + } + else { + $conditions = $entity_conditions; + } + + if (!$conditions) { + // The user doesn't have access to any entities. + // Falsify the query to ensure no results are returned. + $conditions = new ConditionGroup('OR'); + $conditions->addCacheContexts(['user.permissions']); + $conditions->alwaysFalse(); + } + + return $conditions; + } + + /** + * Builds the conditions for entities that have an owner. + * + * @param string $operation + * The access operation. Usually one of "view", "update" or "delete". + * @param \Drupal\Core\Session\AccountInterface $account + * The user for which to restrict access. + * + * @return \Drupal\entity\QueryAccess\ConditionGroup|null + * The conditions, or NULL if the user doesn't have access to any entity. + */ + protected function buildEntityOwnerConditions($operation, AccountInterface $account) { + $entity_type_id = $this->entityType->id(); + $uid_key = $this->entityType->getKey('uid'); + $bundle_key = $this->entityType->getKey('bundle'); + + $conditions = new ConditionGroup('OR'); + $conditions->addCacheContexts(['user.permissions']); + // Any $entity_type permission. + if ($account->hasPermission("$operation any $entity_type_id")) { + // The user has full access, no conditions needed. + return $conditions; + } + + // Own $entity_type permission. + if ($account->hasPermission("$operation own $entity_type_id")) { + $conditions->addCacheContexts(['user']); + $conditions->addCondition($uid_key, $account->id()); + } + + $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id)); + $bundles_with_any_permission = []; + $bundles_with_own_permission = []; + foreach ($bundles as $bundle) { + if ($account->hasPermission("$operation any $bundle $entity_type_id")) { + $bundles_with_any_permission[] = $bundle; + } + if ($account->hasPermission("$operation own $bundle $entity_type_id")) { + $bundles_with_own_permission[] = $bundle; + } + } + // Any $bundle permission. + if ($bundles_with_any_permission) { + $conditions->addCondition($bundle_key, $bundles_with_any_permission); + } + // Own $bundle permission. + if ($bundles_with_own_permission) { + $conditions->addCacheContexts(['user']); + $conditions->addCondition((new ConditionGroup('AND')) + ->addCondition($uid_key, $account->id()) + ->addCondition($bundle_key, $bundles_with_own_permission) + ); + } + + return $conditions->count() ? $conditions : NULL; + } + + /** + * Builds the conditions for entities that do not have an owner. + * + * @param string $operation + * The access operation. Usually one of "view", "update" or "delete". + * @param \Drupal\Core\Session\AccountInterface $account + * The user for which to restrict access. + * + * @return \Drupal\entity\QueryAccess\ConditionGroup|null + * The conditions, or NULL if the user doesn't have access to any entity. + */ + protected function buildEntityConditions($operation, AccountInterface $account) { + $entity_type_id = $this->entityType->id(); + $bundle_key = $this->entityType->getKey('bundle'); + + $conditions = new ConditionGroup('OR'); + $conditions->addCacheContexts(['user.permissions']); + // The $entity_type permission. + if ($account->hasPermission("$operation $entity_type_id")) { + // The user has full access, no conditions needed. + return $conditions; + } + + $bundles = array_keys($this->bundleInfo->getBundleInfo($entity_type_id)); + $bundles_with_any_permission = []; + foreach ($bundles as $bundle) { + if ($account->hasPermission("$operation $bundle $entity_type_id")) { + $bundles_with_any_permission[] = $bundle; + } + } + // The $bundle permission. + if ($bundles_with_any_permission) { + $conditions->addCondition($bundle_key, $bundles_with_any_permission); + } + + return $conditions->count() ? $conditions : NULL; + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerInterface.php b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerInterface.php new file mode 100644 index 000000000..15d455a62 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/src/QueryAccess/QueryAccessHandlerInterface.php @@ -0,0 +1,41 @@ +connection = $connection; + $this->entityFieldManager = $entity_field_manager; + $this->entityTypeManager = $entity_type_manager; + $this->renderer = $renderer; + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('database'), + $container->get('entity_field.manager'), + $container->get('entity_type.manager'), + $container->get('renderer'), + $container->get('request_stack') + ); + } + + /** + * Alters the given views query. + * + * @param \Drupal\views\Plugin\views\query\Sql $query + * The views query. + * @param \Drupal\views\ViewExecutable $view + * The view. + */ + public function alter(Sql $query, ViewExecutable $view) { + $table_info = $query->getEntityTableInfo(); + $base_table = reset($table_info); + if (empty($base_table['entity_type']) || $base_table['relationship_id'] != 'none') { + return; + } + $entity_type_id = $base_table['entity_type']; + $entity_type = $this->entityTypeManager->getDefinition($entity_type_id); + if (!$entity_type->hasHandlerClass('query_access')) { + return; + } + $storage = $this->entityTypeManager->getStorage($entity_type_id); + if (!$storage instanceof SqlContentEntityStorage) { + return; + } + + /** @var \Drupal\entity\QueryAccess\QueryAccessHandlerInterface $query_access */ + $query_access = $this->entityTypeManager->getHandler($entity_type_id, 'query_access'); + $conditions = $query_access->getConditions('view'); + if ($conditions->isAlwaysFalse()) { + $query->addWhereExpression(0, '1 = 0'); + } + elseif (count($conditions)) { + // Store the data table, in case mapConditions() needs to join it in. + $base_table['data_table'] = $entity_type->getDataTable(); + $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id); + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ + $table_mapping = $storage->getTableMapping(); + $sql_conditions = $this->mapConditions($conditions, $query, $base_table, $field_storage_definitions, $table_mapping); + $query->addWhere(0, $sql_conditions); + } + + $this->applyCacheability(CacheableMetadata::createFromObject($conditions)); + } + + /** + * Maps an entity type's access conditions to views SQL conditions. + * + * @param \Drupal\entity\QueryAccess\ConditionGroup $conditions + * The access conditions. + * @param \Drupal\views\Plugin\views\query\Sql $query + * The views query. + * @param array $base_table + * The base table information. + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $field_storage_definitions + * The field storage definitions. + * @param \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping + * The table mapping. + * + * @return \Drupal\Core\Database\Query\ConditionInterface + * The SQL conditions. + */ + protected function mapConditions(ConditionGroup $conditions, Sql $query, array $base_table, array $field_storage_definitions, DefaultTableMapping $table_mapping) { + $sql_condition = new SqlCondition($conditions->getConjunction()); + foreach ($conditions->getConditions() as $condition) { + if ($condition instanceof ConditionGroup) { + $nested_sql_conditions = $this->mapConditions($condition, $query, $base_table, $field_storage_definitions, $table_mapping); + $sql_condition->condition($nested_sql_conditions); + } + else { + $field = $condition->getField(); + $property_name = NULL; + if (strpos($field, '.') !== FALSE) { + list($field, $property_name) = explode('.', $field); + } + // Skip unknown fields. + if (!isset($field_storage_definitions[$field])) { + continue; + } + $field_storage_definition = $field_storage_definitions[$field]; + if (!$property_name) { + $property_name = $field_storage_definition->getMainPropertyName(); + } + + $column = $table_mapping->getFieldColumnName($field_storage_definition, $property_name); + if ($table_mapping->requiresDedicatedTableStorage($field_storage_definitions[$field])) { + if ($base_table['revision']) { + $dedicated_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition); + } + else { + $dedicated_table = $table_mapping->getDedicatedDataTableName($field_storage_definition); + } + // Views defaults to LEFT JOIN. For simplicity, we don't try to + // use an INNER JOIN when it's safe to do so (AND conjunctions). + $alias = $query->ensureTable($dedicated_table); + } + elseif ($base_table['revision'] && !$field_storage_definition->isRevisionable()) { + // Workaround for #2652652, which causes $query->ensureTable() + // to not work in this case, due to a missing relationship. + if ($data_table = $query->getTableInfo($base_table['data_table'])) { + $alias = $data_table['alias']; + } + else { + $configuration = [ + 'type' => 'INNER', + 'table' => $base_table['data_table'], + 'field' => 'id', + 'left_table' => $base_table['alias'], + 'left_field' => 'id', + ]; + /** @var \Drupal\Views\Plugin\views\join\JoinPluginBase $join */ + $join = Views::pluginManager('join')->createInstance('standard', $configuration); + $alias = $query->addRelationship($base_table['data_table'], $join, $data_table); + } + } + else { + $alias = $base_table['alias']; + } + + $value = $condition->getValue(); + $operator = $condition->getOperator(); + // Using LIKE/NOT LIKE ensures a case insensitive comparison. + // @see \Drupal\Core\Entity\Query\Sql\Condition::translateCondition(). + $property_definitions = $field_storage_definition->getPropertyDefinitions(); + $case_sensitive = $property_definitions[$property_name]->getSetting('case_sensitive'); + $operator_map = [ + '=' => 'LIKE', + '<>' => 'NOT LIKE', + ]; + if (!$case_sensitive && isset($operator_map[$operator])) { + $operator = $operator_map[$operator]; + $value = $this->connection->escapeLike($value); + } + + $sql_condition->condition("$alias.$column", $value, $operator); + } + } + + return $sql_condition; + } + + /** + * Applies the cacheablity metadata to the current request. + * + * @param \Drupal\Core\Cache\CacheableMetadata $cacheable_metadata + * The cacheability metadata. + */ + protected function applyCacheability(CacheableMetadata $cacheable_metadata) { + $request = $this->requestStack->getCurrentRequest(); + if ($request->isMethodCacheable() && $this->renderer->hasRenderContext()) { + $build = []; + $cacheable_metadata->applyTo($build); + $this->renderer->render($build); + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php b/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php index b8dced03f..7fb95481a 100644 --- a/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php +++ b/sites/all/modules/contrib/dev/entity/src/Routing/DeleteMultipleRouteProvider.php @@ -9,6 +9,10 @@ use Symfony\Component\Routing\RouteCollection; /** * Provides the HTML route for deleting multiple entities. + * + * @deprecated Since Drupal 8.6.x the core DefaultHtmlRouteProvider provides + * the route for any entity type with a "delete-multiple-form" link template + * and a "delete-multiple-confirm" form. */ class DeleteMultipleRouteProvider implements EntityRouteProviderInterface { @@ -34,7 +38,10 @@ class DeleteMultipleRouteProvider implements EntityRouteProviderInterface { * The generated route, if available. */ protected function deleteMultipleFormRoute(EntityTypeInterface $entity_type) { - if ($entity_type->hasLinkTemplate('delete-multiple-form')) { + // Core requires a "delete-multiple-confirm" form to be declared as well, + // if it's missing, it's safe to assume that the entity type is still + // relying on previous Entity API contrib behavior. + if ($entity_type->hasLinkTemplate('delete-multiple-form') && !$entity_type->hasHandlerClass('form', 'delete-multiple-confirm')) { $route = new Route($entity_type->getLinkTemplate('delete-multiple-form')); $route->setDefault('_form', '\Drupal\entity\Form\DeleteMultipleForm'); $route->setDefault('entity_type_id', $entity_type->id()); diff --git a/sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php b/sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php index 9a2fdf01b..1b25b2ad2 100644 --- a/sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php +++ b/sites/all/modules/contrib/dev/entity/src/UncacheableEntityPermissionProvider.php @@ -14,9 +14,8 @@ use Drupal\user\EntityOwnerInterface; * Provided permissions: * - administer $entity_type * - access $entity_type overview - * - view any ($bundle) $entity_type - * - view own ($bundle) $entity_type * - view own unpublished $entity_type + * - view (own|any) ($bundle) $entity_type * - update (own|any) ($bundle) $entity_type * - delete (own|any) ($bundle) $entity_type * - create $bundle $entity_type @@ -69,8 +68,8 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase { ]; } else { - $permissions["view any {$entity_type_id}"] = [ - 'title' => $this->t('View any @type', [ + $permissions["view {$entity_type_id}"] = [ + 'title' => $this->t('View @type', [ '@type' => $plural_label, ]), ]; @@ -95,18 +94,25 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase { $has_owner = $entity_type->entityClassImplements(EntityOwnerInterface::class); $plural_label = $entity_type->getPluralLabel(); - $permissions["view any {$entity_type_id}"] = [ - 'title' => $this->t('View any @type', [ - '@type' => $plural_label, - ]), - ]; if ($has_owner) { + $permissions["view any {$entity_type_id}"] = [ + 'title' => $this->t('View any @type', [ + '@type' => $plural_label, + ]), + ]; $permissions["view own {$entity_type_id}"] = [ 'title' => $this->t('View own @type', [ '@type' => $plural_label, ]), ]; } + else { + $permissions["view {$entity_type_id}"] = [ + 'title' => $this->t('View @type', [ + '@type' => $plural_label, + ]), + ]; + } foreach ($bundles as $bundle_name => $bundle_info) { if ($has_owner) { @@ -124,8 +130,8 @@ class UncacheableEntityPermissionProvider extends EntityPermissionProviderBase { ]; } else { - $permissions["view any {$bundle_name} {$entity_type_id}"] = [ - 'title' => $this->t('@bundle: View any @type', [ + $permissions["view {$bundle_name} {$entity_type_id}"] = [ + 'title' => $this->t('@bundle: View @type', [ '@bundle' => $bundle_info['label'], '@type' => $plural_label, ]), diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_examples_test/entity_module_bundle_plugin_examples_test.info.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_examples_test/entity_module_bundle_plugin_examples_test.info.yml index ea21c3d13..0bc69c5c2 100644 --- a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_examples_test/entity_module_bundle_plugin_examples_test.info.yml +++ b/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 2018-06-08 -version: '8.x-1.0-beta4' +# Information added by Drupal.org packaging script on 2018-10-11 +version: '8.x-1.0-rc1' core: '8.x' project: 'entity' -datestamp: 1528452194 +datestamp: 1539272605 diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_test/entity_module_bundle_plugin_test.info.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_test/entity_module_bundle_plugin_test.info.yml index f3931409a..654a84203 100644 --- a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_bundle_plugin_test/entity_module_bundle_plugin_test.info.yml +++ b/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 2018-06-08 -version: '8.x-1.0-beta4' +# Information added by Drupal.org packaging script on 2018-10-11 +version: '8.x-1.0-rc1' core: '8.x' project: 'entity' -datestamp: 1528452194 +datestamp: 1539272605 diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.first.assigned.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.first.assigned.yml new file mode 100644 index 000000000..9e9c57738 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.first.assigned.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.entity_test_enhanced.assigned +id: entity_test_enhanced.first.assigned +field_name: assigned +entity_type: entity_test_enhanced +bundle: first +label: Assigned +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.second.assigned.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.second.assigned.yml new file mode 100644 index 000000000..fb259e3f6 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.field.entity_test_enhanced.second.assigned.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.entity_test_enhanced.assigned +id: entity_test_enhanced.second.assigned +field_name: assigned +entity_type: entity_test_enhanced +bundle: second +label: Assigned +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.storage.entity_test_enhanced.assigned.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.storage.entity_test_enhanced.assigned.yml new file mode 100644 index 000000000..3669b5181 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/install/field.storage.entity_test_enhanced.assigned.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - entity_module_test +id: entity_test_enhanced.assigned +field_name: assigned +entity_type: entity_test_enhanced +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: true +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced.yml new file mode 100644 index 000000000..ab0ecf500 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced.yml @@ -0,0 +1,170 @@ +langcode: en +status: true +dependencies: + module: + - entity_module_test +id: entity_test_enhanced +label: 'Enhanced entities' +module: views +description: '' +tag: '' +base_table: entity_test_enhanced_field_data +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + name: + table: entity_test_enhanced_field_data + field: name + id: name + entity_type: null + entity_field: name + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: { } + sorts: + id: + id: id + table: entity_test_enhanced_field_data + field: id + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: entity_test_enhanced + entity_field: id + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_revisions.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_revisions.yml new file mode 100644 index 000000000..98def8a6e --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_revisions.yml @@ -0,0 +1,170 @@ +langcode: en +status: true +dependencies: + module: + - entity_module_test +id: entity_test_enhanced_revisions +label: 'Enhanced entities (Revisions)' +module: views +description: '' +tag: '' +base_table: entity_test_enhanced_field_revision +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + name: + table: entity_test_enhanced_field_revision + field: name + id: name + entity_type: null + entity_field: name + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: { } + sorts: + vid: + id: vid + table: entity_test_enhanced_field_revision + field: vid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: entity_test_enhanced + entity_field: vid + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner.yml new file mode 100644 index 000000000..fcee646c2 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner.yml @@ -0,0 +1,170 @@ +langcode: en +status: true +dependencies: + module: + - entity_module_test +id: entity_test_enhanced_with_owner +label: 'Enhanced entities with an owner' +module: views +description: '' +tag: '' +base_table: entity_test_enhanced_with_owner_field_data +base_field: id +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + name: + table: entity_test_enhanced_with_owner_field_data + field: name + id: name + entity_type: null + entity_field: name + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: { } + sorts: + id: + id: id + table: entity_test_enhanced_with_owner_field_data + field: id + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: entity_test_enhanced_with_owner + entity_field: id + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner_revisions.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner_revisions.yml new file mode 100644 index 000000000..6e3c7e933 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/optional/views.view.entity_test_enhanced_with_owner_revisions.yml @@ -0,0 +1,170 @@ +langcode: en +status: true +dependencies: + module: + - entity_module_test +id: entity_test_enhanced_with_owner_revisions +label: 'Enhanced entities with an owner (Revisions)' +module: views +description: '' +tag: '' +base_table: entity_test_enhanced_with_owner_field_revision +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: none + options: { } + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + name: + table: entity_test_enhanced_with_owner_field_revision + field: name + id: name + entity_type: null + entity_field: name + plugin_id: field + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: { } + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: { } + sorts: + vid: + id: vid + table: entity_test_enhanced_with_owner_field_revision + field: vid + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + entity_type: entity_test_enhanced_with_owner + entity_field: vid + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + tags: { } diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml deleted file mode 100644 index eb1c5a089..000000000 --- a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml +++ /dev/null @@ -1,16 +0,0 @@ -entity_module_test.entity_test_enhanced_bundle.*: - type: config_entity - label: 'Entity test with enhancements - Bundle' - mapping: - id: - type: string - label: 'Type' - label: - type: label - label: 'Label' - description: - type: text - label: 'Description' - new_revision: - type: boolean - label: 'New revision' diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml index 65fd30379..3446a3e0b 100644 --- a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.info.yml @@ -2,9 +2,11 @@ name: Entity test type: module package: Testing # core: 8.x +dependencies: + - field -# Information added by Drupal.org packaging script on 2018-06-08 -version: '8.x-1.0-beta4' +# Information added by Drupal.org packaging script on 2018-10-11 +version: '8.x-1.0-rc1' core: '8.x' project: 'entity' -datestamp: 1528452194 +datestamp: 1539272605 diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.module b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.module new file mode 100644 index 000000000..bcd14929a --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/entity_module_test.module @@ -0,0 +1,15 @@ +setLabel('Name') diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php deleted file mode 100644 index 4b3a14bc9..000000000 --- a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php +++ /dev/null @@ -1,84 +0,0 @@ -description; - } - - /** - * {@inheritdoc} - */ - public function setDescription($description) { - $this->description = $description; - return $this; - } - - /** - * {@inheritdoc} - */ - public function shouldCreateNewRevision() { - return $this->new_revision; - } - -} diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityWithOwner.php b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityWithOwner.php new file mode 100644 index 000000000..214fb21bf --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/Entity/EnhancedEntityWithOwner.php @@ -0,0 +1,145 @@ +get('user_id')->entity; + } + + /** + * {@inheritdoc} + */ + public function getOwnerId() { + return $this->get('user_id')->target_id; + } + + /** + * {@inheritdoc} + */ + public function setOwnerId($uid) { + $this->set('user_id', $uid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setOwner(UserInterface $account) { + $this->set('user_id', $account->id()); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + $fields += static::publishedBaseFieldDefinitions($entity_type); + + $fields['name'] = BaseFieldDefinition::create('string') + ->setLabel('Name') + ->setRevisionable(TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'string', + 'weight' => -5, + ]); + + $fields['user_id'] = BaseFieldDefinition::create('entity_reference') + ->setLabel(t('User ID')) + ->setDescription(t('The ID of the associated user.')) + ->setSetting('target_type', 'user') + ->setSetting('handler', 'default') + // Default EntityTest entities to have the root user as the owner, to + // simplify testing. + ->setDefaultValue([0 => ['target_id' => 1]]) + ->setTranslatable(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'entity_reference_autocomplete', + 'weight' => -1, + 'settings' => [ + 'match_operator' => 'CONTAINS', + 'size' => '60', + 'placeholder' => '', + ], + ]); + + return $fields; + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php new file mode 100644 index 000000000..5f7ffb607 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/modules/entity_module_test/src/EventSubscriber/QueryAccessSubscriber.php @@ -0,0 +1,58 @@ + 'onQueryAccess', + ]; + } + + /** + * Modifies the access conditions based on the current user. + * + * This is just a convenient example for testing. A real subscriber would + * ignore the account and extend the conditions to cover additional factors, + * such as a custom entity field. + * + * @param \Drupal\entity\QueryAccess\QueryAccessEvent $event + * The event. + */ + public function onQueryAccess(QueryAccessEvent $event) { + $conditions = $event->getConditions(); + $email = $event->getAccount()->getEmail(); + + if ($email == 'user1@example.com') { + // This user should not have access to any entities. + $conditions->alwaysFalse(); + } + elseif ($email == 'user2@example.com') { + // This user should have access to entities with the IDs 1, 2, and 3. + // The query access handler might have already set ->alwaysFalse() + // due to the user not having any other access, so we make sure + // to undo it with $conditions->alwaysFalse(TRUE). + $conditions->alwaysFalse(FALSE); + $conditions->addCondition('id', ['1', '2', '3']); + } + elseif ($email == 'user3@example.com') { + // This user should only have access to entities assigned to "marketing", + // or unassigned entities. + $conditions->alwaysFalse(FALSE); + $conditions->addCondition((new ConditionGroup('OR')) + ->addCondition('assigned', NULL, 'IS NULL') + // Confirm that explicitly specifying the property name works. + ->addCondition('assigned.value', 'marketing') + ); + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php index 5160ed402..2e7772b2a 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/CollectionRouteAccessTest.php @@ -3,7 +3,6 @@ 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; @@ -33,11 +32,6 @@ class CollectionRouteAccessTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ])->save(); - $this->placeBlock('local_tasks_block'); $this->placeBlock('system_breadcrumb_block'); } diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php index 1dc6931c6..aaf70c60e 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/DeleteMultipleFormTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\entity\Functional; use Drupal\entity_module_test\Entity\EnhancedEntity; -use Drupal\entity_module_test\Entity\EnhancedEntityBundle; use Drupal\Tests\BrowserTestBase; /** @@ -35,10 +34,6 @@ class DeleteMultipleFormTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ])->save(); $this->account = $this->drupalCreateUser(['administer entity_test_enhanced']); $this->drupalLogin($this->account); } diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php index 4fb4faabe..ca4e1192e 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/Menu/EntityLocalActionTest.php @@ -37,7 +37,7 @@ class EntityLocalActionTest extends BrowserTestBase { */ public function testCollectionLocalAction() { $this->drupalGet('/entity_test_enhanced'); - $this->assertSession()->linkByHrefExists('/entity_test_enhanced/add'); + $this->assertSession()->linkByHrefExists('/entity_test_enhanced/add?destination=/entity_test_enhanced'); $this->assertSession()->linkExists('Add enhanced entity'); } diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php index c581cf0e5..c40a8303b 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Functional/RevisionRouteAccessTest.php @@ -3,7 +3,6 @@ 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; @@ -40,11 +39,6 @@ class RevisionRouteAccessTest extends BrowserTestBase { protected function setUp() { parent::setUp(); - EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ])->save(); - $this->placeBlock('local_tasks_block'); $this->placeBlock('system_breadcrumb_block'); diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/BundleEntityAccessControlHandlerTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/BundleEntityAccessControlHandlerTest.php deleted file mode 100644 index 9ba0412ca..000000000 --- a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/BundleEntityAccessControlHandlerTest.php +++ /dev/null @@ -1,56 +0,0 @@ -installEntitySchema('entity_test_enhanced'); - } - - /** - * Tests the "view label" access checking. - */ - public function testAccess() { - $bundle = EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ]); - $bundle->save(); - - // The default user has no permissions related to entity_test_enhanced. - $this->assertFalse($bundle->access('view label')); - - $permissions = [ - 'administer entity_test_enhanced', - 'view entity_test_enhanced', - 'view default entity_test_enhanced', - ]; - foreach ($permissions as $permission) { - $account = $this->createUser([], [$permission]); - $this->assertTrue($bundle->access('view label', $account)); - } - } - -} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php index 3d138d6f5..1f2f35a0c 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/DeleteActionTest.php @@ -4,7 +4,6 @@ namespace Drupal\Tests\entity\Kernel; use Drupal\entity\Plugin\Action\DeleteAction; use Drupal\entity_module_test\Entity\EnhancedEntity; -use Drupal\entity_module_test\Entity\EnhancedEntityBundle; use Drupal\system\Entity\Action; use Drupal\user\Entity\User; use Drupal\KernelTests\KernelTestBase; @@ -40,12 +39,6 @@ class DeleteActionTest extends KernelTestBase { $this->installEntitySchema('entity_test_enhanced'); $this->installSchema('system', ['key_value_expire', 'sequences']); - $bundle = EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ]); - $bundle->save(); - $this->user = User::create([ 'name' => 'username', 'status' => 1, diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/ConditionGroupTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/ConditionGroupTest.php new file mode 100644 index 000000000..34dc09cea --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/ConditionGroupTest.php @@ -0,0 +1,106 @@ +addCondition('uid', '2'); + $this->assertEquals('AND', $condition_group->getConjunction()); + $expected_conditions = [ + new Condition('uid', '2'), + ]; + $this->assertEquals($expected_conditions, $condition_group->getConditions()); + $this->assertEquals(1, $condition_group->count()); + $this->assertEquals("uid = '2'", $condition_group->__toString()); + + $condition_group = new ConditionGroup('OR'); + $condition_group->addCondition('type', ['article', 'page']); + $condition_group->addCondition('status', '1', '<>'); + $this->assertEquals('OR', $condition_group->getConjunction()); + $expected_conditions = [ + new Condition('type', ['article', 'page']), + new Condition('status', '1', '<>'), + ]; + $expected_lines = [ + "(", + " type IN ['article', 'page']", + " OR", + " status <> '1'", + ")", + ]; + $this->assertEquals($expected_conditions, $condition_group->getConditions()); + $this->assertEquals(2, $condition_group->count()); + $this->assertEquals(implode("\n", $expected_lines), $condition_group->__toString()); + + // Nested condition group with a single condition. + $condition_group = new ConditionGroup(); + $condition_group->addCondition('type', ['article', 'page']); + $condition_group->addCondition((new ConditionGroup('AND')) + ->addCondition('status', '1') + ); + $expected_conditions = [ + new Condition('type', ['article', 'page']), + new Condition('status', '1'), + ]; + $expected_lines = [ + "(", + " type IN ['article', 'page']", + " AND", + " status = '1'", + ")", + ]; + $this->assertEquals($expected_conditions, $condition_group->getConditions()); + $this->assertEquals('AND', $condition_group->getConjunction()); + $this->assertEquals(2, $condition_group->count()); + $this->assertEquals(implode("\n", $expected_lines), $condition_group->__toString()); + + // Nested condition group with multiple conditions. + $condition_group = new ConditionGroup(); + $condition_group->addCondition('type', ['article', 'page']); + $nested_condition_group = new ConditionGroup('OR'); + $nested_condition_group->addCondition('uid', '1'); + $nested_condition_group->addCondition('status', '1'); + $condition_group->addCondition($nested_condition_group); + $expected_conditions = [ + new Condition('type', ['article', 'page']), + $nested_condition_group, + ]; + $expected_lines = [ + "(", + " type IN ['article', 'page']", + " AND", + " (", + " uid = '1'", + " OR", + " status = '1'", + " )", + ")", + ]; + $this->assertEquals($expected_conditions, $condition_group->getConditions()); + $this->assertEquals('AND', $condition_group->getConjunction()); + $this->assertEquals(2, $condition_group->count()); + $this->assertEquals(implode("\n", $expected_lines), $condition_group->__toString()); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php new file mode 100644 index 000000000..8ecef3b17 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessEventTest.php @@ -0,0 +1,62 @@ +installEntitySchema('entity_test_enhanced'); + + // Create uid: 1 here so that it's skipped in test cases. + $admin_user = $this->createUser(); + + $entity_type_manager = $this->container->get('entity_type.manager'); + $entity_type = $entity_type_manager->getDefinition('entity_test_enhanced'); + $this->handler = QueryAccessHandler::createInstance($this->container, $entity_type); + } + + /** + * Tests the event. + */ + public function testEvent() { + // By default, the first user should have full access, and the second + // user should have no access. The QueryAccessSubscriber flips that. + $first_user = $this->createUser(['mail' => 'user1@example.com'], ['administer entity_test_enhanced']); + $second_user = $this->createUser(['mail' => 'user2@example.com']); + + $conditions = $this->handler->getConditions('view', $first_user); + $this->assertTrue($conditions->isAlwaysFalse()); + + $conditions = $this->handler->getConditions('view', $second_user); + $this->assertFalse($conditions->isAlwaysFalse()); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessHandlerTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessHandlerTest.php new file mode 100644 index 000000000..6ac3e6c15 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessHandlerTest.php @@ -0,0 +1,137 @@ +installEntitySchema('entity_test_enhanced'); + + // Create uid: 1 here so that it's skipped in test cases. + $admin_user = $this->createUser(); + + $entity_type_manager = $this->container->get('entity_type.manager'); + $entity_type = $entity_type_manager->getDefinition('entity_test_enhanced'); + $this->handler = QueryAccessHandler::createInstance($this->container, $entity_type); + } + + /** + * @covers ::getConditions + */ + public function testNoAccess() { + foreach (['view', 'update', 'delete'] as $operation) { + $user = $this->createUser([], ['access content']); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertTrue($conditions->isAlwaysFalse()); + } + } + + /** + * @covers ::getConditions + */ + public function testAdmin() { + foreach (['view', 'update', 'delete'] as $operation) { + $user = $this->createUser([], ['administer entity_test_enhanced']); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + } + + /** + * @covers ::getConditions + */ + public function testView() { + // Entity type permission. + $user = $this->createUser([], ['view entity_test_enhanced']); + $conditions = $this->handler->getConditions('view', $user); + $expected_conditions = [ + new Condition('status', '1'), + ]; + $this->assertEquals(1, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Bundle permission. + $user = $this->createUser([], ['view first entity_test_enhanced']); + $conditions = $this->handler->getConditions('view', $user); + $expected_conditions = [ + new Condition('type', ['first']), + new Condition('status', '1'), + ]; + $this->assertEquals('AND', $conditions->getConjunction()); + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + + /** + * @covers ::getConditions + */ + public function testUpdateDelete() { + foreach (['update', 'delete'] as $operation) { + // Entity type permission. + $user = $this->createUser([], ["$operation entity_test_enhanced"]); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Bundle permission. + $user = $this->createUser([], [ + "$operation first entity_test_enhanced", + "$operation second entity_test_enhanced", + ]); + $conditions = $this->handler->getConditions($operation, $user); + $expected_conditions = [ + new Condition('type', ['first', 'second']), + ]; + $this->assertEquals('OR', $conditions->getConjunction()); + $this->assertEquals(1, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessTest.php new file mode 100644 index 000000000..068e62af5 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/QueryAccessTest.php @@ -0,0 +1,343 @@ +installEntitySchema('entity_test_enhanced'); + $this->installConfig(['entity_module_test']); + + // Create uid: 1 here so that it's skipped in test cases. + $admin_user = $this->createUser(); + + $first_entity = EnhancedEntity::create([ + 'type' => 'first', + 'label' => 'First', + 'status' => 1, + ]); + $first_entity->save(); + + $first_entity->set('name', 'First!'); + $first_entity->set('status', 0); + $first_entity->setNewRevision(TRUE); + $first_entity->save(); + + $second_entity = EnhancedEntity::create([ + 'type' => 'first', + 'label' => 'Second', + 'status' => 0, + ]); + $second_entity->save(); + + $second_entity->set('name', 'Second!'); + $second_entity->set('status', 1); + $second_entity->setNewRevision(TRUE); + $second_entity->save(); + + $third_entity = EnhancedEntity::create([ + 'type' => 'second', + 'label' => 'Third', + 'status' => 1, + ]); + $third_entity->save(); + + $third_entity->set('name', 'Third!'); + $third_entity->setNewRevision(TRUE); + $third_entity->save(); + + $this->entities = [$first_entity, $second_entity, $third_entity]; + $this->storage = \Drupal::entityTypeManager()->getStorage('entity_test_enhanced'); + } + + /** + * Tests EntityQuery filtering. + */ + public function testEntityQuery() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced']); + \Drupal::currentUser()->setAccount($admin_user); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[0]->id(), + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->execute(); + $this->assertEmpty($result); + + // View (published-only). + $user = $this->createUser([], ['view entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + + // View $bundle (published-only). + $user = $this->createUser([], ['view first entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[1]->id(), + ], array_values($result)); + } + + /** + * Tests EntityQuery filtering when all revisions are queried. + */ + public function testEntityQueryWithRevisions() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced']); + \Drupal::currentUser()->setAccount($admin_user); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '2' => $this->entities[0]->id(), + '3' => $this->entities[1]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->execute(); + $this->assertEmpty($result); + + // View (published-only). + $user = $this->createUser([], ['view entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + + // View $bundle (published-only). + $user = $this->createUser([], ['view first entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '4' => $this->entities[1]->id(), + ], $result); + } + + /** + * Tests Views filtering. + */ + public function testViews() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced']); + \Drupal::currentUser()->setAccount($admin_user); + + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[0]->id()], + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, []); + + // View (published-only). + $user = $this->createUser([], ['view entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + + // View $bundle (published-only). + $user = $this->createUser([], ['view first entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[1]->id()], + ], ['id' => 'id']); + } + + /** + * Tests Views filtering when all revisions are queried. + */ + public function testViewsWithRevisions() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced']); + \Drupal::currentUser()->setAccount($admin_user); + + $view = Views::getView('entity_test_enhanced_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '2', 'id' => $this->entities[0]->id()], + ['vid' => '3', 'id' => $this->entities[1]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, []); + + // View (published-only). + $user = $this->createUser([], ['view entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + + // View $bundle (published-only). + $user = $this->createUser([], ['view first entity_test_enhanced']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ], ['vid' => 'vid']); + } + + /** + * Tests filtering based on a configurable field. + * + * QueryAccessSubscriber adds a condition that ensures that the field value + * is either empty or matches "marketing". + * + * @see \Drupal\entity_module_test\EventSubscriber\QueryAccessSubscriber + */ + public function testConfigurableField() { + $this->entities[0]->set('assigned', 'marketing'); + $this->entities[0]->save(); + // The field is case sensitive, so the third entity should be ignored. + $this->entities[2]->set('assigned', 'MarKeTing'); + $this->entities[2]->save(); + $user = $this->createUser([ + 'mail' => 'user3@example.com', + ], ['access content']); + \Drupal::currentUser()->setAccount($user); + + // EntityQuery. + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[0]->id(), + $this->entities[1]->id(), + ], array_values($result)); + + // EntityQuery with revisions. + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '2' => $this->entities[0]->id(), + '3' => $this->entities[1]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + ], $result); + + // View. + $view = Views::getView('entity_test_enhanced'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[0]->id()], + ['id' => $this->entities[1]->id()], + ], ['id' => 'id']); + + // View with revisions. + $view = Views::getView('entity_test_enhanced_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '2', 'id' => $this->entities[0]->id()], + ['vid' => '3', 'id' => $this->entities[1]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessHandlerTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessHandlerTest.php new file mode 100644 index 000000000..761a1f7a6 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessHandlerTest.php @@ -0,0 +1,203 @@ +installEntitySchema('entity_test_enhanced_with_owner'); + + // Create uid: 1 here so that it's skipped in test cases. + $admin_user = $this->createUser(); + + $entity_type_manager = $this->container->get('entity_type.manager'); + $entity_type = $entity_type_manager->getDefinition('entity_test_enhanced_with_owner'); + $this->handler = UncacheableQueryAccessHandler::createInstance($this->container, $entity_type); + } + + /** + * @covers ::getConditions + */ + public function testNoAccess() { + foreach (['view', 'update', 'delete'] as $operation) { + $user = $this->createUser([], ['access content']); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertTrue($conditions->isAlwaysFalse()); + } + } + + /** + * @covers ::getConditions + */ + public function testAdmin() { + foreach (['view', 'update', 'delete'] as $operation) { + $user = $this->createUser([], ['administer entity_test_enhanced_with_owner']); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + } + + /** + * @covers ::getConditions + */ + public function testView() { + // Any permission. + $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']); + $conditions = $this->handler->getConditions('view', $user); + $expected_conditions = [ + new Condition('status', '1'), + ]; + $this->assertEquals(1, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Own permission. + $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']); + $conditions = $this->handler->getConditions('view', $user); + $expected_conditions = [ + new Condition('user_id', $user->id()), + new Condition('status', '1'), + ]; + $this->assertEquals('AND', $conditions->getConjunction()); + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Any permission for the first bundle, own permission for the second. + $user = $this->createUser([], [ + 'view any first entity_test_enhanced_with_owner', + 'view own second entity_test_enhanced_with_owner', + ]); + $conditions = $this->handler->getConditions('view', $user); + $expected_conditions = [ + (new ConditionGroup('OR')) + ->addCacheContexts(['user', 'user.permissions']) + ->addCondition('type', ['first']) + ->addCondition((new ConditionGroup('AND')) + ->addCondition('user_id', $user->id()) + ->addCondition('type', ['second']) + ), + new Condition('status', '1'), + ]; + $this->assertEquals('AND', $conditions->getConjunction()); + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // View own unpublished permission. + $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']); + $conditions = $this->handler->buildConditions('view', $user); + $expected_conditions = [ + new Condition('user_id', $user->id()), + new Condition('status', '0'), + ]; + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Both view any and view own unpublished permissions. + $user = $this->createUser([], [ + 'view any entity_test_enhanced_with_owner', + 'view own unpublished entity_test_enhanced_with_owner', + ]); + $conditions = $this->handler->buildConditions('view', $user); + $expected_conditions = [ + new Condition('status', '1'), + (new ConditionGroup('AND')) + ->addCondition('user_id', $user->id()) + ->addCondition('status', '0') + ->addCacheContexts(['user']), + ]; + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + + /** + * @covers ::getConditions + */ + public function testUpdateDelete() { + foreach (['update', 'delete'] as $operation) { + // Any permission. + $user = $this->createUser([], ["$operation any entity_test_enhanced_with_owner"]); + $conditions = $this->handler->getConditions($operation, $user); + $this->assertEquals(0, $conditions->count()); + $this->assertEquals(['user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Own permission. + $user = $this->createUser([], ["$operation own entity_test_enhanced_with_owner"]); + $conditions = $this->handler->getConditions($operation, $user); + $expected_conditions = [ + new Condition('user_id', $user->id()), + ]; + $this->assertEquals(1, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + + // Any permission for the first bundle, own permission for the second. + $user = $this->createUser([], [ + "$operation any first entity_test_enhanced_with_owner", + "$operation own second entity_test_enhanced_with_owner", + ]); + $conditions = $this->handler->getConditions($operation, $user); + $expected_conditions = [ + new Condition('type', ['first']), + (new ConditionGroup('AND')) + ->addCondition('user_id', $user->id()) + ->addCondition('type', ['second']), + ]; + $this->assertEquals('OR', $conditions->getConjunction()); + $this->assertEquals(2, $conditions->count()); + $this->assertEquals($expected_conditions, $conditions->getConditions()); + $this->assertEquals(['user', 'user.permissions'], $conditions->getCacheContexts()); + $this->assertFalse($conditions->isAlwaysFalse()); + } + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessTest.php new file mode 100644 index 000000000..bcd0d3585 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/QueryAccess/UncacheableQueryAccessTest.php @@ -0,0 +1,511 @@ +installEntitySchema('entity_test_enhanced_with_owner'); + $this->installConfig(['entity_module_test']); + + // Create uid: 1 here so that it's skipped in test cases. + $admin_user = $this->createUser(); + + $first_entity = EnhancedEntityWithOwner::create([ + 'type' => 'first', + 'name' => 'First', + 'status' => 1, + ]); + $first_entity->save(); + + $first_entity->set('name', 'First!'); + $first_entity->set('status', 0); + $first_entity->setNewRevision(TRUE); + $first_entity->save(); + + $second_entity = EnhancedEntityWithOwner::create([ + 'type' => 'first', + 'name' => 'Second', + 'status' => 0, + ]); + $second_entity->save(); + + $second_entity->set('name', 'Second!'); + $second_entity->set('status', 1); + $second_entity->setNewRevision(TRUE); + $second_entity->save(); + + $third_entity = EnhancedEntityWithOwner::create([ + 'type' => 'second', + 'name' => 'Third', + 'status' => 1, + ]); + $third_entity->save(); + + $third_entity->set('name', 'Third!'); + $third_entity->setNewRevision(TRUE); + $third_entity->save(); + + $this->entities = [$first_entity, $second_entity, $third_entity]; + $this->storage = \Drupal::entityTypeManager()->getStorage('entity_test_enhanced_with_owner'); + } + + /** + * Tests EntityQuery filtering. + */ + public function testEntityQuery() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($admin_user); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[0]->id(), + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->execute(); + $this->assertEmpty($result); + + // View own (published-only). + $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[1]->id(), + ], array_values($result)); + + // View any (published-only). + $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + + // View own unpublished. + $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[0]->id(), + ], array_values($result)); + + // View own unpublished + view any (published-only). + $user = $this->createUser([], [ + 'view own unpublished entity_test_enhanced_with_owner', + 'view any entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[0]->id(), + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + + // View own $first_bundle + View any $second_bundle. + $user = $this->createUser([], [ + 'view own first entity_test_enhanced_with_owner', + 'view any second entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->sort('id')->execute(); + $this->assertEquals([ + $this->entities[1]->id(), + $this->entities[2]->id(), + ], array_values($result)); + } + + /** + * Tests EntityQuery filtering when all revisions are queried. + */ + public function testEntityQueryWithRevisions() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($admin_user); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '2' => $this->entities[0]->id(), + '3' => $this->entities[1]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->allRevisions()->execute(); + $this->assertEmpty($result); + + // View own (published-only). + $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + // The user_id field is not revisionable, which means that updating it + // will modify both revisions for each entity. + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '4' => $this->entities[1]->id(), + ], $result); + + // View any (published-only). + $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + + // View own unpublished. + $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '2' => $this->entities[0]->id(), + '3' => $this->entities[1]->id(), + ], $result); + + // View own unpublished + view any (published-only). + $user = $this->createUser([], [ + 'view own unpublished entity_test_enhanced_with_owner', + 'view any entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '1' => $this->entities[0]->id(), + '2' => $this->entities[0]->id(), + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + + // View own $first_bundle + View any $second_bundle. + $user = $this->createUser([], [ + 'view own first entity_test_enhanced_with_owner', + 'view any second entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $result = $this->storage->getQuery()->allRevisions()->sort('id')->execute(); + $this->assertEquals([ + '4' => $this->entities[1]->id(), + '5' => $this->entities[2]->id(), + '6' => $this->entities[2]->id(), + ], $result); + } + + /** + * Tests Views filtering. + */ + public function testViews() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($admin_user); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[0]->id()], + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, []); + + // View own (published-only). + $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[1]->id()], + ], ['id' => 'id']); + + // View any (published-only). + $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + + // View own unpublished. + $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[0]->id()], + ], ['id' => 'id']); + + // View own unpublished + view any (published-only). + $user = $this->createUser([], [ + 'view own unpublished entity_test_enhanced_with_owner', + 'view any entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[0]->id()], + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + + // View own $first_bundle + View any $second_bundle. + $user = $this->createUser([], [ + 'view own first entity_test_enhanced_with_owner', + 'view any second entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['id' => $this->entities[1]->id()], + ['id' => $this->entities[2]->id()], + ], ['id' => 'id']); + } + + /** + * Tests Views filtering when all revisions are queried. + */ + public function testViewsWithRevisions() { + // Admin permission, full access. + $admin_user = $this->createUser([], ['administer entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($admin_user); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '2', 'id' => $this->entities[0]->id()], + ['vid' => '3', 'id' => $this->entities[1]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + + // No view permissions, no access. + $user = $this->createUser([], ['access content']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, []); + + // View own (published-only). + $user = $this->createUser([], ['view own entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ], ['vid' => 'vid']); + + // View any (published-only). + $user = $this->createUser([], ['view any entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + + // View own unpublished. + $user = $this->createUser([], ['view own unpublished entity_test_enhanced_with_owner']); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '2', 'id' => $this->entities[0]->id()], + ['vid' => '3', 'id' => $this->entities[1]->id()], + ], ['vid' => 'vid']); + + // View own unpublished + view any (published-only). + $user = $this->createUser([], [ + 'view own unpublished entity_test_enhanced_with_owner', + 'view any entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[0]->set('user_id', $user->id()); + $this->entities[0]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '1', 'id' => $this->entities[0]->id()], + ['vid' => '2', 'id' => $this->entities[0]->id()], + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + + // View own $first_bundle + View any $second_bundle. + $user = $this->createUser([], [ + 'view own first entity_test_enhanced_with_owner', + 'view any second entity_test_enhanced_with_owner', + ]); + \Drupal::currentUser()->setAccount($user); + + $this->entities[1]->set('user_id', $user->id()); + $this->entities[1]->save(); + + $view = Views::getView('entity_test_enhanced_with_owner_revisions'); + $view->execute(); + $this->assertIdenticalResultset($view, [ + ['vid' => '4', 'id' => $this->entities[1]->id()], + ['vid' => '5', 'id' => $this->entities[2]->id()], + ['vid' => '6', 'id' => $this->entities[2]->id()], + ], ['vid' => 'vid']); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php index 1342848da..8d70bd2a8 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Kernel/RevisionBasicUITest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\entity\Kernel; use Drupal\entity_module_test\Entity\EnhancedEntity; -use Drupal\entity_module_test\Entity\EnhancedEntityBundle; use Drupal\KernelTests\KernelTestBase; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; @@ -30,12 +29,6 @@ class RevisionBasicUITest extends KernelTestBase { $this->installSchema('system', 'router'); $this->installConfig(['system']); - $bundle = EnhancedEntityBundle::create([ - 'id' => 'default', - 'label' => 'Default', - ]); - $bundle->save(); - \Drupal::service('router.builder')->rebuild(); } diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Unit/BundleEntityAccessControlHandlerTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Unit/BundleEntityAccessControlHandlerTest.php new file mode 100644 index 000000000..0126d4465 --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Unit/BundleEntityAccessControlHandlerTest.php @@ -0,0 +1,115 @@ +prophesize(ModuleHandlerInterface::class); + $module_handler->invokeAll(Argument::any(), Argument::any())->willReturn([]); + $cache_contexts_manager = $this->prophesize(CacheContextsManager::class); + $cache_contexts_manager->assertValidTokens(Argument::any())->willReturn(TRUE); + + $container = new ContainerBuilder(); + $container->set('module_handler', $module_handler->reveal()); + $container->set('cache_contexts_manager', $cache_contexts_manager->reveal()); + \Drupal::setContainer($container); + } + + /** + * @covers ::checkAccess + * + * @dataProvider accessProvider + */ + public function testAccess(EntityInterface $entity, $operation, $account, $allowed) { + $handler = new BundleEntityAccessControlHandler($entity->getEntityType()); + $handler->setStringTranslation($this->getStringTranslationStub()); + $result = $handler->access($entity, $operation, $account); + $this->assertEquals($allowed, $result); + } + + /** + * Data provider for testAccess(). + * + * @return array + * A list of testAccess method arguments. + */ + public function accessProvider() { + $entity_type = $this->prophesize(ContentEntityTypeInterface::class); + $entity_type->id()->willReturn('green_entity_bundle'); + $entity_type->getBundleOf()->willReturn('green_entity'); + $entity_type->getAdminPermission()->willReturn('administer green_entity'); + $entity_type = $entity_type->reveal(); + + $entity = $this->prophesize(ConfigEntityInterface::class); + $entity->getEntityType()->willReturn($entity_type); + $entity->getEntityTypeId()->willReturn('green_entity_bundle'); + $entity->id()->willReturn('default'); + $entity->uuid()->willReturn('fake uuid'); + $entity->language()->willReturn(new Language(['id' => LanguageInterface::LANGCODE_NOT_SPECIFIED])); + + // User with no access. + $user = $this->buildMockUser(1, 'access content'); + $data[] = [$entity->reveal(), 'view label', $user->reveal(), FALSE]; + + // Permissions which grant "view label" access. + $permissions = [ + 'administer green_entity', + 'view green_entity', + 'view default green_entity', + 'view own green_entity', + 'view any green_entity', + 'view own default green_entity', + 'view any default green_entity', + ]; + foreach ($permissions as $index => $permission) { + $user = $this->buildMockUser(10 + $index, $permission); + $data[] = [$entity->reveal(), 'view label', $user->reveal(), TRUE]; + } + + return $data; + } + + /** + * Builds a mock user. + * + * @param int $uid + * The user ID. + * @param string $permission + * The permission to grant. + * + * @return \Prophecy\Prophecy\ObjectProphecy + * The user mock. + */ + protected function buildMockUser($uid, $permission) { + $account = $this->prophesize(AccountInterface::class); + $account->id()->willReturn($uid); + $account->hasPermission($permission)->willReturn(TRUE); + $account->hasPermission(Argument::any())->willReturn(FALSE); + + return $account; + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Unit/QueryAccess/ConditionTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Unit/QueryAccess/ConditionTest.php new file mode 100644 index 000000000..6d73aebaa --- /dev/null +++ b/sites/all/modules/contrib/dev/entity/tests/src/Unit/QueryAccess/ConditionTest.php @@ -0,0 +1,50 @@ +assertEquals('uid', $condition->getField()); + $this->assertEquals('2', $condition->getValue()); + $this->assertEquals('=', $condition->getOperator()); + $this->assertEquals("uid = '2'", $condition->__toString()); + + $condition = new Condition('type', ['article', 'page']); + $this->assertEquals('type', $condition->getField()); + $this->assertEquals(['article', 'page'], $condition->getValue()); + $this->assertEquals('IN', $condition->getOperator()); + $this->assertEquals("type IN ['article', 'page']", $condition->__toString()); + + $condition = new Condition('title', NULL, 'IS NULL'); + $this->assertEquals('title', $condition->getField()); + $this->assertEquals(NULL, $condition->getValue()); + $this->assertEquals('IS NULL', $condition->getOperator()); + $this->assertEquals("title IS NULL", $condition->__toString()); + } + +} diff --git a/sites/all/modules/contrib/dev/entity/tests/src/Unit/UncacheableEntityPermissionProviderTest.php b/sites/all/modules/contrib/dev/entity/tests/src/Unit/UncacheableEntityPermissionProviderTest.php index a3dd02194..45cf0fd5e 100644 --- a/sites/all/modules/contrib/dev/entity/tests/src/Unit/UncacheableEntityPermissionProviderTest.php +++ b/sites/all/modules/contrib/dev/entity/tests/src/Unit/UncacheableEntityPermissionProviderTest.php @@ -81,7 +81,7 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase { 'create green_entity' => 'Create green entities', 'update green_entity' => 'Update green entities', 'delete green_entity' => 'Delete green entities', - 'view any green_entity' => 'View any green entities', + 'view green_entity' => 'View green entities', ]; $data[] = [$entity_type->reveal(), $expected_permissions]; @@ -127,9 +127,9 @@ class UncacheableEntityPermissionProviderTest extends UnitTestCase { 'create second white_entity' => 'Second: Create white entities', 'update second white_entity' => 'Second: Update white entities', 'delete second white_entity' => 'Second: Delete white entities', - 'view any white_entity' => 'View any white entities', - 'view any first white_entity' => 'First: View any white entities', - 'view any second white_entity' => 'Second: View any white entities', + 'view white_entity' => 'View white entities', + 'view first white_entity' => 'First: View white entities', + 'view second white_entity' => 'Second: View white entities', ]; $data[] = [$entity_type->reveal(), $expected_permissions]; diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/README.txt b/sites/all/modules/contrib/dev/module_missing_message_fixer/README.txt index b0029f05e..e745e6480 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/README.txt +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/README.txt @@ -11,10 +11,13 @@ CONTENTS OF THIS FILE INTRODUCTION ------------ -The Module Missing Message Fixer module displays a list of missing modules that appear in the message log after the Drupal 7.50 release and provides an interface to fix the entries. This module was designed in sn effort to help those people who didnt want to run SQL commands directly. +The Module Missing Message Fixer module displays a list of missing modules that appear in the message log after the +Drupal 7.50 release and provides an interface to fix the entries. This module was designed in sn effort to help those +people who didnt want to run SQL commands directly. * For a full description of the module, visit https://www.drupal.org/project/module_missing_message_fixer -* To submit bug reports and feature suggestions, or to track changes visit https://www.drupal.org/project/issues/module_missing_message_fixer +* To submit bug reports and feature suggestions, or to track changes +visit https://www.drupal.org/project/issues/module_missing_message_fixer REQUIREMENTS @@ -26,7 +29,9 @@ This module requires no additional modules outside of Drupal core. INSTALLATION ------------ -Install the Module Missing Message Fixer module as you would normally install a contributed Drupal module. Visit https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 for further information. +Install the Module Missing Message Fixer module as you would normally install a contributed Drupal module. Visit +https://www.drupal.org/docs/7/extending-drupal-7/installing-contributed-modules-find-import-enable-configure-drupal-7 +for further information. CONFIGURATION @@ -34,12 +39,15 @@ CONFIGURATION Through the UI interface: 1. Enable the Module Missing Message Fixer module. -2. Ensure that you have the proper permissions by navigating to Administration > People > Permissions and selecting the checkbox. -3. Navigate to Administration > Configuration > System > Missing Module Message Fixer. Select the missing modules you would like to fix and select the "Remove These Errors!" tab. The module will now go through and remove the "ghost records" from the systems table. +2. Ensure that you have the proper permissions by navigating to Administration > People > Permissions and selecting +the checkbox. +3. Navigate to Administration > Configuration > System > Missing Module Message Fixer. Select the missing modules you +would like to fix and select the "Remove These Errors!" tab. The module will now go through and remove the "ghost records" from the systems table. Through Drush: 1. $ drush en module_missing_message_fixer 2. $ drush module-missing-message-fixer-list OR $ drush mmmfl. This will generate a list of missing modules. -3. $ drush module-missing-message-fixer-fix machine_name_of_module (or --all) OR $ drush mmmff machine_name_of_module (or --all). This will fix the missing modules entities. +3. $ drush module-missing-message-fixer-fix machine_name_of_module (or --all) OR +$ drush mmmff machine_name_of_module (or --all). This will fix the missing modules entities. For more information visit https://www.drupal.org/node/2487215 diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/composer.json b/sites/all/modules/contrib/dev/module_missing_message_fixer/composer.json new file mode 100644 index 000000000..436aa647d --- /dev/null +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/composer.json @@ -0,0 +1,28 @@ +{ + "name": "drupal/module_missing_message_fixer", + "description": "This module displays a list of missing modules that appear on your site and lets you fix the entries", + "type": "drupal-module", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/module_missing_message_fixer", + "authors": [ + { + "name": "See contributors", + "homepage": "https://www.drupal.org/node/2951176/committers", + "role": "Developer" + } + ], + "minimum-stability": "dev", + "prefer-stable": true, + "support": { + "issues": "https://www.drupal.org/project/issues/module_missing_message_fixer", + "source": "http://cgit.drupalcode.org/module_missing_message_fixer" + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9" + } + } + } +} diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/drush.services.yml b/sites/all/modules/contrib/dev/module_missing_message_fixer/drush.services.yml new file mode 100644 index 000000000..64fd165d8 --- /dev/null +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/drush.services.yml @@ -0,0 +1,11 @@ +services: + module_missing_message_fixer.fix: + class: \Drupal\module_missing_message_fixer\Commands\MmmfFixCommand + arguments: ['@module_missing_message_fixer.fixer', '@database', '@config.factory'] + tags: + - { name: drush.command } + module_missing_message_fixer.list: + class: \Drupal\module_missing_message_fixer\Commands\MmmfListCommand + arguments: ['@module_missing_message_fixer.fixer'] + tags: + - { name: drush.command } diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/includes/module_missing_message_fixer.drush.inc b/sites/all/modules/contrib/dev/module_missing_message_fixer/includes/module_missing_message_fixer.drush.inc index 57f520a3b..52631eb0b 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/includes/module_missing_message_fixer.drush.inc +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/includes/module_missing_message_fixer.drush.inc @@ -1,52 +1,21 @@ 1 ? drush_format_table($rows, TRUE) : 'No Missing Modules Found!!!'; - drush_print($output); - - return NULL; -} - /** * Implements hook_drush_help(). - * - * @param string $section - * - * @return null|string */ function module_missing_message_fixer_drush_help($section) { switch ($section) { case 'module-missing-message-fixer-list': return dt("Returns a list of modules that have missing messages."); + case 'module-missing-message-fixer-fix': return dt("Fixes a specified module that has missing messages. (optional --all)"); + default: return NULL; } @@ -57,26 +26,26 @@ function module_missing_message_fixer_drush_help($section) { */ function module_missing_message_fixer_drush_command() { $items = []; - $items['module-missing-message-fixer-list'] = array( + $items['module-missing-message-fixer-list'] = [ 'description' => dt('Returns a list of modules that have missing messages.'), - 'aliases' => array( + 'aliases' => [ 'mmmfl', - ), - 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL - ); - $items['module-missing-message-fixer-fix'] = array( + ], + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + ]; + $items['module-missing-message-fixer-fix'] = [ 'description' => dt('Fixes modules that have missing messages.'), - 'aliases' => array( + 'aliases' => [ 'mmmff', - ), - 'arguments' => array( + ], + 'arguments' => [ 'name' => 'The name of the module to fix.', - ), - 'options' => array( + ], + 'options' => [ 'all' => dt('Fixes all module missing messages'), - ), - 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL - ); + ], + 'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_FULL, + ]; return $items; } @@ -86,18 +55,20 @@ function module_missing_message_fixer_drush_command() { * Displays a list of modules that have missing messages. */ function drush_module_missing_message_fixer_list() { - module_missing_message_fixer_check_modules(); + \Drupal::service('module_missing_message_fixer.fixer')->checkModules(); } /** * Drush command. * * @param string $name - * The name of the module to fix messages for. + * The name of the module to fix messages for. */ function drush_module_missing_message_fixer_fix($name = NULL) { $modules = []; - $rows = module_missing_message_fixer_check_modules(TRUE); + $rows = \Drupal::service('module_missing_message_fixer.fixer') + ->checkModules(TRUE); + if (drush_get_option('all') !== NULL) { if (!empty($rows)) { foreach ($rows as $row) { diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.info.yml b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.info.yml index 6002a22c7..f5b759105 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.info.yml +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.info.yml @@ -4,8 +4,8 @@ package: Administration # core: 8.x type: module -# Information added by Drupal.org packaging script on 2017-11-29 -version: '8.x-1.0-alpha3' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-1.0-beta2' core: '8.x' project: 'module_missing_message_fixer' -datestamp: 1511927894 +datestamp: 1546878787 diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.module b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.module index 0a6eb872e..d7fba30d1 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.module +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.module @@ -4,4 +4,3 @@ * @file * The Missing Module Message Fixer Module file. */ - diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.services.yml b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.services.yml new file mode 100644 index 000000000..a278e3cdf --- /dev/null +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/module_missing_message_fixer.services.yml @@ -0,0 +1,4 @@ +services: + module_missing_message_fixer.fixer: + class: \Drupal\module_missing_message_fixer\ModuleMissingMessageFixer + arguments: ['@database', '@messenger'] diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfFixCommand.php b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfFixCommand.php new file mode 100644 index 000000000..3b5d47216 --- /dev/null +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfFixCommand.php @@ -0,0 +1,131 @@ +fixer = $mmmf; + $this->connection = $connection; + $this->configFactory = $config_factory; + } + + /** + * Fixes the missing modules. + * + * @param string $name + * The name of the module to fix messages for. + * @param array $options + * The all flag option. + * + * @command module-missing-message-fixer:fix + * + * @option all Fixes all module missing messages. + * + * @usage drush module-missing-message-fixer:fix stuff + * Fixes the stuff module. + * @usage drush module-missing-message-fixer:fix stuff --all + * Fixes all the modules. + * + * @aliases mmmff + */ + public function fixCommand($name = NULL, array $options = ['all' => NULL]) { + $modules = []; + $rows = $this->fixer->checkModules(TRUE); + if ($options['all'] !== NULL) { + if (!empty($rows)) { + foreach ($rows as $row) { + $modules[] = $row['name']; + + // Clean up old migrate configuration. + $like = $this->connection->escapeLike($row['name'] . '.'); + $config_names = $this->connection->select('config', 'c') + ->fields('c', ['name']) + ->condition('name', $like . '%', 'LIKE') + ->execute() + ->fetchAll(); + + // Delete each config using configFactory. + foreach ($config_names as $config_name) { + $this->configFactory->getEditable($config_name->name)->delete(); + } + + // Reminds users to export config. + if (!empty($config_name)) { + $this->output()->writeln(dt("Don't forget to export your config")); + } + } + } + } + elseif ($name !== NULL) { + // If this exists in the table. + if (strpos(json_encode($rows), $name)) { + $modules[] = $name; + } + else { + $this->output()->writeln(dt('Module ' . $name . ' was not found.'), 'error'); + } + } + else { + $this->output()->writeln(dt('Missing input, provide module name or run with --all'), 'error'); + } + // Delete if there is no modules. + if (count($modules) > 0) { + $query = $this->connection->delete('key_value'); + $query->condition('collection', 'system.schema'); + $query->condition('name', $modules, 'IN'); + $query->execute(); + + if (drush_get_option('all') !== NULL) { + $this->output()->writeln(dt('All missing references have been removed.'), 'success'); + } + elseif ($name !== NULL) { + if (in_array($name, $modules, TRUE)) { + $this->output()->writeln(dt('Reference to ' . $name . ' (if found) has been removed.'), 'success'); + } + } + } + } + +} diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfListCommand.php b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfListCommand.php new file mode 100644 index 000000000..377f98db3 --- /dev/null +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Commands/MmmfListCommand.php @@ -0,0 +1,59 @@ +fixer = $mmmf; + } + + /** + * Returns a list of modules that have missing messages. + * + * @command module-missing-message-fixer:list + * + * @usage drush module-missing-message-fixer:list + * Returns a list of modules that have missing messages. + * + * @aliases mmmfl + * + * @field-labels + * name: Name + * type: Type + */ + public function listCommand() { + $rows = $this->fixer->getTableRows(); + if (count($rows) > 0) { + $table[] = $this->fixer->getTableHeader(); + foreach ($rows as $row) { + $table[] = $row; + } + return new RowsOfFields($table); + } + $this->output()->writeln('No Missing Modules Found!!!'); + } + +} diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Form/ModuleMissingMessageFixerForm.php b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Form/ModuleMissingMessageFixerForm.php index fb5224155..7d98ed1f4 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Form/ModuleMissingMessageFixerForm.php +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/Form/ModuleMissingMessageFixerForm.php @@ -1,19 +1,81 @@ connection = $connection; + $this->configFactory = $config_factory; + $this->messenger = $messenger; + $this->mmmf = $mmmf; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('database'), + $container->get('config.factory'), + $container->get('messenger'), + $container->get('module_missing_message_fixer.fixer') + ); + } + /** * {@inheritdoc} */ @@ -24,13 +86,12 @@ class ModuleMissingMessageFixerForm extends FormBase { /** * {@inheritdoc} */ - public function buildForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state) { - $form = []; + public function buildForm(array $form, FormStateInterface $form_state) { // Fancy title string. - $title = t('This list comes from the system table and is checked against the drupal_get_filename() function. See this issue for more information.', [ - '@link' => 'https://www.drupal.org/node/2487215' - ]); + $title = $this->t('This list comes from the system table and is checked against the drupal_get_filename() function. See this issue for more information.', [ + '@link' => 'https://www.drupal.org/node/2487215', + ]); // Title. $form['title'] = [ @@ -41,15 +102,15 @@ class ModuleMissingMessageFixerForm extends FormBase { // Fancy submit buttons to win this. $form['submit'] = [ '#type' => 'submit', - '#value' => t('Remove These Errors!'), + '#value' => $this->t('Remove These Errors!'), ]; // Set the tables select to make this more granular. $form['table'] = [ '#type' => 'tableselect', - '#header' => ModuleMissingMessageFixer::getTableHeader(), - '#options' => ModuleMissingMessageFixer::getTableRows(), - '#empty' => t('No Missing Modules Found!!!'), + '#header' => $this->mmmf->getTableHeader(), + '#options' => $this->mmmf->getTableRows(), + '#empty' => $this->t('No Missing Modules Found!!!'), ]; return $form; @@ -58,24 +119,36 @@ class ModuleMissingMessageFixerForm extends FormBase { /** * {@inheritdoc} */ - public function submitForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state) { $modules = []; // Go through each record and add it to the array to win. foreach ($form_state->getValue(['table']) as $module) { if ($module) { $modules[] = $module; + + // Clean up old migrate configuration. + $like = $this->connection->escapeLike($module . '.'); + $config_names = $this->connection->select('config', 'c') + ->fields('c', ['name']) + ->condition('name', $like . '%', 'LIKE') + ->execute() + ->fetchAll(); + + // Delete each config using configFactory. + foreach ($config_names as $config_name) { + $this->configFactory->getEditable($config_name->name)->delete(); + } + + // Reminds users to export config. + if (!empty($config_name)) { + $this->messenger->addWarning("Don't forget to export your config"); + } } } - /** - * @todo - * - * https://www.drupal.org/forum/support/module-development-and-code-questions/2015-09-09/how-to-remove-mymodule-configurations#comment-10820772 - */ - // Delete if there is no modules. if (count($modules) > 0) { - $query = \Drupal::database()->delete('key_value'); + $query = $this->connection->delete('key_value'); $query->condition('collection', 'system.schema'); $query->condition('name', $modules, 'IN'); $query->execute(); diff --git a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/ModuleMissingMessageFixer.php b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/ModuleMissingMessageFixer.php index 330dd8ba7..5199680fb 100644 --- a/sites/all/modules/contrib/dev/module_missing_message_fixer/src/ModuleMissingMessageFixer.php +++ b/sites/all/modules/contrib/dev/module_missing_message_fixer/src/ModuleMissingMessageFixer.php @@ -1,28 +1,97 @@ connection = $connection; + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public function create(ContainerInterface $container) { + return new static( + $container->get('database'), + $container->get('messenger') + ); + } + + /** + * Helper function to check for modules to fix. + * + * @param bool $return + * If we are to return to rows or just print the list. + * + * @return string|null + * The printed output. + */ + public function checkModules($return = FALSE) { + + if ($return) { + return $this->getTableRows(); + } + + $rows = []; + + // Use a key for the head row that is not a valid module name. + $rows['*HEAD*'] = ModuleMissingMessageFixer::getTableHeader(); + $rows += ModuleMissingMessageFixer::getTableRows(); + + // Print Table here instead of in the hook_command. + $output = count($rows) > 1 ? + drush_format_table($rows, TRUE) : 'No Missing Modules Found!!!'; + drush_print($output); + + return NULL; + } + /** * Set the table headers for the ui and drush. * * @return string[] * Format: $[$column_key] = $cell */ - public static function getTableHeader() { - return array( + public function getTableHeader() { + return [ 'name' => 'Name', 'type' => 'Type', - ); + ]; } - /** * Produces one table row for each missing module. * @@ -31,14 +100,12 @@ class ModuleMissingMessageFixer { * @return array[] * Format: $[$extension_name][$column_key] = $cell */ - public static function getTableRows() { + public function getTableRows() { // Initalize vars. $rows = []; - $i = 0; - $db = \Drupal::database(); // Grab all the modules in the system table. - $results = $db->select('key_value', 'k') + $results = $this->connection->select('key_value', 'k') ->fields('k', ['name']) ->condition('collection', 'system.schema') ->execute() @@ -57,19 +124,19 @@ class ModuleMissingMessageFixer { if ($filename === NULL) { // Report this module in the table. - $rows[$record->name] = array( + $rows[$record->name] = [ 'name' => $record->name, 'type' => 'module', - ); + ]; continue; } $message = NULL; - $replacements = array( + $replacements = [ '@name' => $record->name, '@type' => 'module', '@file' => $filename, - ); + ]; if (!file_exists($filename)) { // This case is unexpected, because drupal_get_filename() should take care // of it already. @@ -96,13 +163,13 @@ class ModuleMissingMessageFixer { if ($message !== NULL) { // This case should never occur. - drupal_set_message( + $this->messenger->addWarning( t($message, $replacements), - 'warning', FALSE); } } return $rows; } + } diff --git a/sites/all/modules/contrib/dev/redis/.travis.yml b/sites/all/modules/contrib/dev/redis/.travis.yml index 400d14326..bb194fa15 100644 --- a/sites/all/modules/contrib/dev/redis/.travis.yml +++ b/sites/all/modules/contrib/dev/redis/.travis.yml @@ -59,8 +59,8 @@ env: - DRUPAL_TI_BEHAT_DRIVER="phantomjs" - DRUPAL_TI_BEHAT_BROWSER="firefox" - # Use Drupal 8.3.x to run tests. - - DRUPAL_TI_CORE_BRANCH="8.5.x" + # Set Drupal version in which to run tests. + - DRUPAL_TI_CORE_BRANCH="8.6.x" # PHPUnit specific commandline arguments. - DRUPAL_TI_PHPUNIT_ARGS="--verbose --debug" diff --git a/sites/all/modules/contrib/dev/redis/redis.info.yml b/sites/all/modules/contrib/dev/redis/redis.info.yml index 06a5bbcb6..64991bd8d 100644 --- a/sites/all/modules/contrib/dev/redis/redis.info.yml +++ b/sites/all/modules/contrib/dev/redis/redis.info.yml @@ -5,8 +5,8 @@ type: module # core: 8.x configure: redis.admin_display -# Information added by Drupal.org packaging script on 2018-05-30 -version: '8.x-1.0' +# Information added by Drupal.org packaging script on 2018-11-07 +version: '8.x-1.1' core: '8.x' project: 'redis' -datestamp: 1527699489 +datestamp: 1541600592 diff --git a/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php index 2f53e62ab..593875a8d 100644 --- a/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php +++ b/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php @@ -104,10 +104,7 @@ class PhpRedis extends LockBackendAbstract { $key = $this->getKey($name); $value = $this->client->get($key); - // In Drupal 7, this method treated the lock as available if the ID did - // match. The database backend and test expects it to return FALSE in that - // case, updated accordingly. - return FALSE === $value; + return $value === FALSE || $value === NULL; } /** diff --git a/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php b/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php index aa67ca143..01d183062 100644 --- a/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php +++ b/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php @@ -98,10 +98,7 @@ class Predis extends LockBackendAbstract { $key = $this->getKey($name); $value = $this->client->get($key); - // In Drupal 7, this method treated the lock as available if the ID did - // match. The database backend and test expects it to return FALSE in that - // case, updated accordingly. - return FALSE === $value; + return $value === FALSE || $value === NULL; } public function release($name) { diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php index d74a05ee2..14f38157e 100644 --- a/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php +++ b/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php @@ -36,11 +36,12 @@ class RedisLockFunctionalTest extends LockFunctionalTest { chmod($filename, 0666); $contents = file_get_contents($filename); $redis_interface = self::getRedisInterfaceEnv(); - $contents .= "\n\n" . '$settings[\'container_yamls\'][] = \'modules/redis/example.services.yml\';'; + $module_path = drupal_get_path('module', 'redis'); + $contents .= "\n\n" . "\$settings['container_yamls'][] = '$module_path/example.services.yml';"; $contents .= "\n\n" . '$settings["redis.connection"]["interface"] = \'' . $redis_interface . '\';'; file_put_contents($filename, $contents); $settings = Settings::getAll(); - $settings['container_yamls'][] = 'modules/redis/example.services.yml'; + $settings['container_yamls'][] = $module_path . '/example.services.yml'; $settings['redis.connection']['interface'] = $redis_interface; new Settings($settings); OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $filename); @@ -53,4 +54,36 @@ class RedisLockFunctionalTest extends LockFunctionalTest { $db_schema->dropTable('semaphore'); } + /** + * {@inheritdoc} + */ + public function testLockAcquire() { + $redis_interface = self::getRedisInterfaceEnv(); + $lock = $this->container->get('lock'); + $this->assertInstanceOf('\Drupal\redis\Lock\\' . $redis_interface, $lock); + + // Verify that a lock that has never been acquired is marked as available. + // @todo Remove this line when #3002640 lands. + // @see https://www.drupal.org/project/drupal/issues/3002640 + $this->assertTrue($lock->lockMayBeAvailable('system_test_lock_acquire')); + + parent::testLockAcquire(); + } + + /** + * {@inheritdoc} + */ + public function testPersistentLock() { + $redis_interface = self::getRedisInterfaceEnv(); + $persistent_lock = $this->container->get('lock.persistent'); + $this->assertInstanceOf('\Drupal\redis\PersistentLock\\' . $redis_interface, $persistent_lock); + + // Verify that a lock that has never been acquired is marked as available. + // @todo Remove this line when #3002640 lands. + // @see https://www.drupal.org/project/drupal/issues/3002640 + $this->assertTrue($persistent_lock->lockMayBeAvailable('lock1')); + + parent::testPersistentLock(); + } + } diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisLockTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisLockTest.php new file mode 100644 index 000000000..f3a9a8e8a --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisLockTest.php @@ -0,0 +1,66 @@ +register('lock', LockBackendInterface::class) + ->setFactory([new Reference('redis.lock.factory'), 'get']); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->lock = $this->container->get('lock'); + } + + /** + * {@inheritdoc} + */ + public function testBackendLockRelease() { + $redis_interface = self::getRedisInterfaceEnv(); + // Verify that the correct lock backend is being instantiated by the + // factory. + $this->assertInstanceOf('\Drupal\redis\Lock\\' . $redis_interface, $this->lock); + + // Verify that a lock that has never been acquired is marked as available. + // @todo Remove this line when #3002640 lands. + // @see https://www.drupal.org/project/drupal/issues/3002640 + $this->assertTrue($this->lock->lockMayBeAvailable('lock_a')); + + parent::testBackendLockRelease(); + } + +} diff --git a/sites/all/modules/contrib/dev/token/src/Controller/TokenTreeController.php b/sites/all/modules/contrib/dev/token/src/Controller/TokenTreeController.php index f233ab234..3edf40229 100644 --- a/sites/all/modules/contrib/dev/token/src/Controller/TokenTreeController.php +++ b/sites/all/modules/contrib/dev/token/src/Controller/TokenTreeController.php @@ -50,6 +50,17 @@ class TokenTreeController extends ControllerBase { $build['#cache']['contexts'][] = 'url.query_args:options'; $build['#title'] = $this->t('Available tokens'); + // If this is an AJAX/modal request, add a wrapping div to the contents so + // that Drupal.behaviors.tokenTree and Drupal.behaviors.tokenAttach can + // stil find the elements they need to. + // @see https://www.drupal.org/project/token/issues/2994671 + // @see https://www.drupal.org/node/2940704 + // @see http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ + if ($request->isXmlHttpRequest()) { + $build['#prefix'] = '
'; + $build['#suffix'] = '
'; + } + return $build; } diff --git a/sites/all/modules/contrib/dev/token/tests/modules/token_module_test/token_module_test.info.yml b/sites/all/modules/contrib/dev/token/tests/modules/token_module_test/token_module_test.info.yml index 134c18ce5..037918b02 100644 --- a/sites/all/modules/contrib/dev/token/tests/modules/token_module_test/token_module_test.info.yml +++ b/sites/all/modules/contrib/dev/token/tests/modules/token_module_test/token_module_test.info.yml @@ -5,8 +5,8 @@ package: Testing # core: 8.x hidden: TRUE -# Information added by Drupal.org packaging script on 2018-09-08 -version: '8.x-1.4' +# Information added by Drupal.org packaging script on 2018-09-21 +version: '8.x-1.5' core: '8.x' project: 'token' -datestamp: 1536408485 +datestamp: 1537557488 diff --git a/sites/all/modules/contrib/dev/token/tests/modules/token_user_picture/token_user_picture.info.yml b/sites/all/modules/contrib/dev/token/tests/modules/token_user_picture/token_user_picture.info.yml index 8c168d25a..131f85028 100644 --- a/sites/all/modules/contrib/dev/token/tests/modules/token_user_picture/token_user_picture.info.yml +++ b/sites/all/modules/contrib/dev/token/tests/modules/token_user_picture/token_user_picture.info.yml @@ -7,8 +7,8 @@ hidden: TRUE dependencies: - image -# Information added by Drupal.org packaging script on 2018-09-08 -version: '8.x-1.4' +# Information added by Drupal.org packaging script on 2018-09-21 +version: '8.x-1.5' core: '8.x' project: 'token' -datestamp: 1536408485 +datestamp: 1537557488 diff --git a/sites/all/modules/contrib/dev/token/token.info.yml b/sites/all/modules/contrib/dev/token/token.info.yml index 8d93be36f..4919185b7 100644 --- a/sites/all/modules/contrib/dev/token/token.info.yml +++ b/sites/all/modules/contrib/dev/token/token.info.yml @@ -5,8 +5,8 @@ description: Provides a user interface for the Token API and some missing core t dependencies: - drupal:system (>= 8.5) -# Information added by Drupal.org packaging script on 2018-09-08 -version: '8.x-1.4' +# Information added by Drupal.org packaging script on 2018-09-21 +version: '8.x-1.5' core: '8.x' project: 'token' -datestamp: 1536408485 +datestamp: 1537557488 diff --git a/sites/all/modules/contrib/fields/audiofield/README.md b/sites/all/modules/contrib/fields/audiofield/README.md index 40fc01926..1b95eb2e8 100644 --- a/sites/all/modules/contrib/fields/audiofield/README.md +++ b/sites/all/modules/contrib/fields/audiofield/README.md @@ -23,8 +23,9 @@ of additional files. You can check the current status of installation at /admin/reports/status or read below for further details on how to install each player. -To install all external audio player libraries at once, you can run the drush -command drush audiofield-download +To install all external audio player libraries at once, you can run the Drush +command drush audiofield-download (Drush 8) or drush audiofield:download (Drush +9). Audio.js Audio Player Audio.js requires installation of the audio.js library located at @@ -32,7 +33,8 @@ Audio.js Audio Player subfolder titled "audiojs" should be placed in /libraries/audiojs such that the file audio.min.js is found at /libraries/audiojs/. - Install through drush using command drush audiofield-download audiojs. + Install through drush using command drush audiofield-download audiojs + (Drush 8) or drush audiofield:download audiojs (Drush 9). Audio.js does not offer any theme options, but can be fully styled by custom CSS. @@ -43,7 +45,8 @@ jPlayer Audio Player in /libraries/jplayer. It should be installed such that the file jquery.jplayer.min.js can be found at /libraries/jplayer/dist/jplayer/. - Install through drush using command drush audiofield-download jplayer. + Install through drush using command drush audiofield-download jplayer + (Drush 8) or drush audiofield:download jplayer (Drush 9). jPlayer comes bundled with multiple skins by default. You can install additional skins by placing them in /libraries/jplayer/dist/skin/ and @@ -60,7 +63,8 @@ MediaElement Audio Player in /libraries/mediaelement such that the file mediaelement-and-player.min.js is found at /libraries/mediaelement/build/. - Install through drush using command drush audiofield-download mediaelement. + Install through drush using command drush audiofield-download mediaelement + (Drush 8) or drush audiofield:download mediaelement (Drush 9). Projekktor Audio Player Projekktor requires installation of the Projekktor library located at @@ -73,7 +77,8 @@ Projekktor Audio Player projekktor-1.3.09.min.js at /libraries/projekktor/. This file will not exist unless the distribution has been properly compiled. - Install through drush using command drush audiofield-download projekktor. + Install through drush using command drush audiofield-download projekktor + (Drush 8) or drush audiofield:download projekktor (Drush 9). SoundManager Audio Player WordPress Audio Player requires installation of the SoundManager library @@ -82,7 +87,8 @@ SoundManager Audio Player that the file soundmanager2-nodebug-jsmin.js can be found at /libraries/soundmanager/script/. - Install through drush using command drush audiofield-download soundmanager. + Install through drush using command drush audiofield-download soundmanager + (Drush 8) or drush audiofield:download soundmanager (Drush 9). SoundManager is a highly configurable library. However, handling and allowing for all of the possible configurations and implementations of SoundManager is @@ -101,7 +107,8 @@ Wavesurfer Audio Player but there are small feature differences between the two to be aware of. The drush command will install the latest version. - Install through drush using command drush audiofield-download wavesurfer. + Install through drush using command drush audiofield-download waveplayer + (Drush 8) or drush audiofield:download waveplayer (Drush 9). WordPress Audio Player WordPress Audio Player requires installation of the Standalone version of @@ -109,7 +116,8 @@ WordPress Audio Player These files should be downloaded and placed in /libraries/wordpress-audio such that the file audio-player.js can be found at /libraries/wordpress-audio. - Install through drush using command drush audiofield-download wordpress. + Install through drush using command drush audiofield-download wordpress + (Drush 8) or drush audiofield:download wordpress (Drush 9). WordPress Audio Player only supports a single skin, but does support a single audio player for multiple files. However, the multi-file version of diff --git a/sites/all/modules/contrib/fields/audiofield/audiofield.info.yml b/sites/all/modules/contrib/fields/audiofield/audiofield.info.yml index ce0527c39..df1c3b5f0 100644 --- a/sites/all/modules/contrib/fields/audiofield/audiofield.info.yml +++ b/sites/all/modules/contrib/fields/audiofield/audiofield.info.yml @@ -5,8 +5,8 @@ type: module package: Field types # core: '8.x' -# Information added by Drupal.org packaging script on 2017-12-27 -version: '8.x-1.6' +# Information added by Drupal.org packaging script on 2018-11-26 +version: '8.x-1.8' core: '8.x' project: 'audiofield' -datestamp: 1514414887 +datestamp: 1543249984 diff --git a/sites/all/modules/contrib/fields/audiofield/composer.json b/sites/all/modules/contrib/fields/audiofield/composer.json index 240f5dbed..fbc159907 100644 --- a/sites/all/modules/contrib/fields/audiofield/composer.json +++ b/sites/all/modules/contrib/fields/audiofield/composer.json @@ -1,18 +1,25 @@ { - "name": "drupal/audiofield", - "description": "AudioField Module", - "type": "drupal-module", - "license": "GPL-2.0+", - "homepage": "https://www.drupal.org/project/audiofield", - "minimum-stability": "dev", - "authors": [ - { - "name": "Daniel Moberly", - "homepage": "https://www.drupal.org/u/danielmoberly", - "role": "Maintainer" + "name": "drupal/audiofield", + "description": "AudioField Module", + "type": "drupal-module", + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/audiofield", + "minimum-stability": "dev", + "authors": [ + { + "name": "Daniel Moberly", + "homepage": "https://www.drupal.org/u/danielmoberly", + "role": "Maintainer" + } + ], + "support": { + "issues": "https://www.drupal.org/project/issues/audiofield" + }, + "extra": { + "drush": { + "services": { + "drush.services.yml": "^9" + } + } } - ], - "support": { - "issues": "https://www.drupal.org/project/issues/audiofield" - } } diff --git a/sites/all/modules/contrib/fields/audiofield/drush.services.yml b/sites/all/modules/contrib/fields/audiofield/drush.services.yml new file mode 100644 index 000000000..ec41f7a08 --- /dev/null +++ b/sites/all/modules/contrib/fields/audiofield/drush.services.yml @@ -0,0 +1,7 @@ +services: + audiofield.commands: + class: \Drupal\audiofield\Commands\AudiofieldCommands + arguments: + - '@plugin.manager.audiofield' + tags: + - { name: drush.command } diff --git a/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.es6.js b/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.es6.js index 533f759cd..858916ff8 100644 --- a/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.es6.js +++ b/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.es6.js @@ -127,7 +127,7 @@ }, swfPath: '/libraries/jplayer/dist/jplayer', wmode: 'window', - preload: settings.lazyload, + preload: file.lazyload, keyEnabled: true, supplied: file.filetype, }, diff --git a/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.js b/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.js index bb75be6c5..c47f26493 100644 --- a/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.js +++ b/sites/all/modules/contrib/fields/audiofield/js/jplayer.builder.js @@ -90,7 +90,7 @@ swfPath: '/libraries/jplayer/dist/jplayer', wmode: 'window', keyEnabled: true, - preload: settings.lazyload, + preload: file.lazyload, supplied: file.filetype }); }); diff --git a/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.es6.js b/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.es6.js index 45e1cab5c..877a57c42 100644 --- a/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.es6.js +++ b/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.es6.js @@ -23,6 +23,18 @@ // Create waveform. const wavesurfer = WaveSurfer.create({ container: `#${$(wavecontainer).attr('id')} .waveform`, + audioRate: settings.audioRate, + autoCenter: settings.autoCenter, + barGap: settings.barGap, + barHeight: settings.barHeight, + barWidth: settings.barWidth, + cursorColor: settings.cursorColor, + cursorWidth: settings.cursorWidth, + forceDecode: settings.forceDecode, + normalize: settings.normalize, + progressColor: settings.progressColor, + responsive: settings.responsive, + waveColor: settings.waveColor, }); // Load the file. @@ -32,12 +44,12 @@ wavesurfer.setVolume(settings.volume); // Handle play/pause. - $(wavecontainer).find('.player-button.playpause').on('click', () => { + $(wavecontainer).find('.player-button.playpause').on('click', (event) => { Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); }); // Handle volume change. - $(wavecontainer).find('.volume').on('change', () => { + $(wavecontainer).find('.volume').on('change', (event) => { wavesurfer.setVolume(($(event.currentTarget).val() / 10)); }); @@ -61,6 +73,18 @@ // Create waveform. const wavesurfer = WaveSurfer.create({ container: `#${$(wavecontainer).attr('id')} .waveform`, + audioRate: settings.audioRate, + autoCenter: settings.autoCenter, + barGap: settings.barGap, + barHeight: settings.barHeight, + barWidth: settings.barWidth, + cursorColor: settings.cursorColor, + cursorWidth: settings.cursorWidth, + forceDecode: settings.forceDecode, + normalize: settings.normalize, + progressColor: settings.progressColor, + responsive: settings.responsive, + waveColor: settings.waveColor, }); // Set the default volume. @@ -77,28 +101,33 @@ wavesurfer.load(first.attr('data-src')); // Handle play/pause. - $(wavecontainer).find('.player-button.playpause').on('click', () => { + $(wavecontainer).find('.player-button.playpause').on('click', (event) => { Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); }); // Handle next/previous. - $(wavecontainer).find('.player-button.next').on('click', () => { + $(wavecontainer).find('.player-button.next').on('click', (event) => { Drupal.AudiofieldWavesurfer.Next(wavecontainer, wavesurfer); }); - $(wavecontainer).find('.player-button.previous').on('click', () => { + $(wavecontainer).find('.player-button.previous').on('click', (event) => { Drupal.AudiofieldWavesurfer.Previous(wavecontainer, wavesurfer); }); // Handle clicking track. - $(wavecontainer).find('.playlist .track').on('click', () => { - // Load the track. - Drupal.AudiofieldWavesurfer.Load(wavecontainer, wavesurfer, $(event.currentTarget)); - // Play the track. - Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); + $(wavecontainer).find('.playlist .track').on('click', (event) => { + // Check if the track is already playing. + if ($(this).hasClass('playing')) { + // Play/pause the track if it is already loaded. + Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); + } + else { + // Load the track. + Drupal.AudiofieldWavesurfer.Load(wavecontainer, wavesurfer, $(event.currentTarget)); + } }); // Handle volume change. - $(wavecontainer).find('.volume').on('change', () => { + $(wavecontainer).find('.volume').on('change', (event) => { wavesurfer.setVolume(($(event.currentTarget).val() / 10)); }); @@ -108,7 +137,7 @@ } // Handle track finishing. - wavesurfer.on('finish', () => { + wavesurfer.on('finish', (event) => { Drupal.AudiofieldWavesurfer.Next(wavecontainer, wavesurfer); }); }); @@ -147,13 +176,13 @@ */ Drupal.AudiofieldWavesurfer.Load = (wavecontainer, wavesurfer, track) => { // Load the track. - wavesurfer.on('ready', () => { - wavesurfer.play(); + wavesurfer.load(track.attr('data-src')); + wavesurfer.on('ready', (event) => { $(wavecontainer).removeClass('playing'); $(wavecontainer).addClass('playing'); $(wavecontainer).find('.player-button.playpause').html('Pause'); + wavesurfer.play(); }); - wavesurfer.load(track.attr('data-src')); // Remove playing from all other tracks. $(wavecontainer).find('.track').removeClass('playing'); // Set the class on this track. diff --git a/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.js b/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.js index 46e20a9d8..77aacc241 100644 --- a/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.js +++ b/sites/all/modules/contrib/fields/audiofield/js/wavesurfer.builder.js @@ -13,18 +13,30 @@ Drupal.AudiofieldWavesurfer.generate = function (context, file, settings) { $.each($(context).find('#' + file.id).once('generate-waveform'), function (index, wavecontainer) { var wavesurfer = WaveSurfer.create({ - container: '#' + $(wavecontainer).attr('id') + ' .waveform' + container: '#' + $(wavecontainer).attr('id') + ' .waveform', + audioRate: settings.audioRate, + autoCenter: settings.autoCenter, + barGap: settings.barGap, + barHeight: settings.barHeight, + barWidth: settings.barWidth, + cursorColor: settings.cursorColor, + cursorWidth: settings.cursorWidth, + forceDecode: settings.forceDecode, + normalize: settings.normalize, + progressColor: settings.progressColor, + responsive: settings.responsive, + waveColor: settings.waveColor }); wavesurfer.load(file.path); wavesurfer.setVolume(settings.volume); - $(wavecontainer).find('.player-button.playpause').on('click', function () { + $(wavecontainer).find('.player-button.playpause').on('click', function (event) { Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); }); - $(wavecontainer).find('.volume').on('change', function () { + $(wavecontainer).find('.volume').on('change', function (event) { wavesurfer.setVolume($(event.currentTarget).val() / 10); }); @@ -37,7 +49,19 @@ Drupal.AudiofieldWavesurfer.generatePlaylist = function (context, settings) { $.each($(context).find('#wavesurfer_playlist').once('generate-waveform'), function (index, wavecontainer) { var wavesurfer = WaveSurfer.create({ - container: '#' + $(wavecontainer).attr('id') + ' .waveform' + container: '#' + $(wavecontainer).attr('id') + ' .waveform', + audioRate: settings.audioRate, + autoCenter: settings.autoCenter, + barGap: settings.barGap, + barHeight: settings.barHeight, + barWidth: settings.barWidth, + cursorColor: settings.cursorColor, + cursorWidth: settings.cursorWidth, + forceDecode: settings.forceDecode, + normalize: settings.normalize, + progressColor: settings.progressColor, + responsive: settings.responsive, + waveColor: settings.waveColor }); wavesurfer.setVolume(settings.volume); @@ -51,24 +75,27 @@ wavesurfer.load(first.attr('data-src')); - $(wavecontainer).find('.player-button.playpause').on('click', function () { + $(wavecontainer).find('.player-button.playpause').on('click', function (event) { Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); }); - $(wavecontainer).find('.player-button.next').on('click', function () { + $(wavecontainer).find('.player-button.next').on('click', function (event) { Drupal.AudiofieldWavesurfer.Next(wavecontainer, wavesurfer); }); - $(wavecontainer).find('.player-button.previous').on('click', function () { + $(wavecontainer).find('.player-button.previous').on('click', function (event) { Drupal.AudiofieldWavesurfer.Previous(wavecontainer, wavesurfer); }); - $(wavecontainer).find('.playlist .track').on('click', function () { - Drupal.AudiofieldWavesurfer.Load(wavecontainer, wavesurfer, $(event.currentTarget)); - - Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); + $(wavecontainer).find('.playlist .track').on('click', function (event) { + if ($(this).hasClass('playing')) { + Drupal.AudiofieldWavesurfer.PlayPause(wavecontainer, wavesurfer); + } + else { + Drupal.AudiofieldWavesurfer.Load(wavecontainer, wavesurfer, $(event.currentTarget)); + } }); - $(wavecontainer).find('.volume').on('change', function () { + $(wavecontainer).find('.volume').on('change', function (event) { wavesurfer.setVolume($(event.currentTarget).val() / 10); }); @@ -76,7 +103,7 @@ wavesurfer.on('ready', wavesurfer.play.bind(wavesurfer)); } - wavesurfer.on('finish', function () { + wavesurfer.on('finish', function (event) { Drupal.AudiofieldWavesurfer.Next(wavecontainer, wavesurfer); }); }); @@ -95,13 +122,13 @@ }; Drupal.AudiofieldWavesurfer.Load = function (wavecontainer, wavesurfer, track) { - wavesurfer.on('ready', function () { - wavesurfer.play(); + wavesurfer.load(track.attr('data-src')); + wavesurfer.on('ready', function (event) { $(wavecontainer).removeClass('playing'); $(wavecontainer).addClass('playing'); $(wavecontainer).find('.player-button.playpause').html('Pause'); + wavesurfer.play(); }); - wavesurfer.load(track.attr('data-src')); $(wavecontainer).find('.track').removeClass('playing'); diff --git a/sites/all/modules/contrib/fields/audiofield/src/AudioFieldPluginBase.php b/sites/all/modules/contrib/fields/audiofield/src/AudioFieldPluginBase.php index 02e31ddc6..85d567a28 100644 --- a/sites/all/modules/contrib/fields/audiofield/src/AudioFieldPluginBase.php +++ b/sites/all/modules/contrib/fields/audiofield/src/AudioFieldPluginBase.php @@ -9,11 +9,62 @@ use Drupal\Core\Field\FieldItemListInterface; use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Random; use Drupal\Component\Plugin\PluginBase; +use Drupal\Core\Asset\LibraryDiscovery; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Messenger\Messenger; +use Drupal\Core\Logger\LoggerChannelFactoryInterface ; /** * Base class for audiofield plugins. Includes global functions. */ -abstract class AudioFieldPluginBase extends PluginBase { +abstract class AudioFieldPluginBase extends PluginBase implements ContainerFactoryPluginInterface { + + /** + * Library discovery service. + * + * @var Drupal\Core\Asset\LibraryDiscovery + */ + protected $libraryDiscovery; + + /** + * Messenger service. + * + * @var Drupal\Core\Messenger\Messenger + */ + protected $messenger; + + /** + * Messenger service. + * + * @var Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, LibraryDiscovery $library_discovery, Messenger $messenger, LoggerChannelFactoryInterface $logger_factory) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->libraryDiscovery = $library_discovery; + $this->messenger = $messenger; + $this->loggerFactory = $logger_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('library.discovery'), + $container->get('messenger'), + $container->get('logger.factory') + ); + } /** * Renders the player. @@ -68,7 +119,7 @@ abstract class AudioFieldPluginBase extends PluginBase { */ public function getPluginLibrary() { // Get the main library for this plugin. - return \Drupal::service('library.discovery')->getLibraryByName('audiofield', 'audiofield.' . $this->getPluginLibraryName()); + return $this->libraryDiscovery->getLibraryByName('audiofield', 'audiofield.' . $this->getPluginLibraryName()); } /** @@ -170,8 +221,8 @@ abstract class AudioFieldPluginBase extends PluginBase { '%command' => 'drush audiofield-update', '@status_report' => Link::createFromRoute('status report', 'system.status')->toString(), ]; - \Drupal::logger('audiofield')->warning('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data); - drupal_set_message(t('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data), 'warning'); + $this->loggerFactory->get('audiofield')->warning('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data); + $this->messenger->addWarning(t('Warning: @plugin library is out of date. You should upgrade from version @version to version @newversion. You can manually download the required version here: @download-link or you can install automatically by running the command %command. See the @status_report for more information', $message_data)); } return FALSE; } @@ -186,8 +237,8 @@ abstract class AudioFieldPluginBase extends PluginBase { '@library_name' => $this->getPluginLibraryName(), '@status_report' => Link::createFromRoute('status report', 'system.status')->toString(), ]; - \Drupal::logger('audiofield')->error('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data); - drupal_set_message(t('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data), 'error'); + $this->loggerFactory->get('audiofield')->error('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data); + $this->messenger->addWarning(t('Error: @library_name library is not currently installed! See the @status_report for more information.', $message_data)); } /** @@ -208,8 +259,8 @@ abstract class AudioFieldPluginBase extends PluginBase { '@player' => $this->getPluginLibraryName(), '%extensions' => implode(', ', $this->pluginDefinition["fileTypes"]), ]; - \Drupal::logger('audiofield')->error('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data); - drupal_set_message(t('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data), 'error'); + $this->loggerFactory->get('audiofield')->error('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data); + $this->messenger->addWarning(t('Error playing file %filename: currently selected audio player only supports the following extensions: %extensions', $message_data)); return FALSE; } return TRUE; @@ -232,8 +283,8 @@ abstract class AudioFieldPluginBase extends PluginBase { $message_data = [ '%link' => $link->toString(), ]; - \Drupal::logger('audiofield')->error('Error playing file: invalid link: %link', $message_data); - drupal_set_message(t('Error playing file: invalid link: %link', $message_data), 'error'); + $this->loggerFactory->get('audiofield')->error('Error playing file: invalid link: %link', $message_data); + $this->messenger->addWarning(t('Error playing file: invalid link: %link', $message_data)); return FALSE; } return TRUE; @@ -366,20 +417,19 @@ abstract class AudioFieldPluginBase extends PluginBase { * The source URL of an entity. */ private function getAudioSource($item) { - $source_url = ''; if ($this->getClassType($item) == 'FileItem') { // Load the associated file. $file = $this->loadFileFromItem($item); // Get the file URL. - $source_url = Url::fromUri(file_create_url($file->getFileUri())); + return Url::fromUri(file_create_url($file->getFileUri())); } // Handle Link entity. elseif ($this->getClassType($item) == 'LinkItem') { // Get the file URL. - $source_url = $item->getUrl(); + return $item->getUrl(); } - return $source_url; + return ''; } /** @@ -392,7 +442,6 @@ abstract class AudioFieldPluginBase extends PluginBase { * The description of an entity. */ private function getAudioDescription($item) { - $entity_description = ''; if ($this->getClassType($item) == 'FileItem') { // Get the file description - use the filename if it doesn't exist. $entity_description = $item->get('description')->getString(); @@ -400,19 +449,20 @@ abstract class AudioFieldPluginBase extends PluginBase { // Load the associated file. $file = $this->loadFileFromItem($item); - $entity_description = $file->getFilename(); + return $file->getFilename(); } + return $entity_description; } // Handle Link entity. elseif ($this->getClassType($item) == 'LinkItem') { // Get the file description - use the filename if it doesn't exist. $entity_description = $item->get('title')->getString(); if (empty($entity_description)) { - $entity_description = $item->getUrl()->toString(); + return $item->getUrl()->toString(); } + return $entity_description; } - - return $entity_description; + return ''; } /** @@ -476,51 +526,29 @@ abstract class AudioFieldPluginBase extends PluginBase { * A render array containing download links. */ public function createDownloadList($items, array $settings) { - $download_links = []; - - // Check if download links are turned on. - if ($settings['download_link']) { - // Loop over each item. - foreach ($items as $item) { - // Get the source URL for this item. - $source_url = $this->getAudioSource($item); - - // Get the entity description for this item. - $entity_description = $this->getAudioDescription($item); - - // Add the link. - $download_links[] = [ - '#markup' => Link::fromTextAndUrl($entity_description, $source_url)->toString(), - '#wrapper_attributes' => [ - 'class' => [ - 'audiofield-download-link', - ], - ], - ]; - } + // Check if download links are turned on and there are items. + if (!$settings['download_link'] || count($items) == 0) { + return []; } - // Render links if we have them. - $download_render_array = []; - if (count($download_links) > 0) { - $download_render_array = [ - '#theme' => 'item_list', - '#list_type' => 'ul', - '#title' => t('Download files:'), - '#wrapper_attributes' => [ - 'class' => [ - 'audiofield-downloads', - ], - ], - '#attributes' => [], - '#empty' => '', - '#items' => $download_links, + $links = []; + // Loop over each item. + foreach ($items as $item) { + // Get the source URL for this item. + $source_url = $this->getAudioSource($item); + + // Get the entity description for this item. + $entity_description = $this->getAudioDescription($item); + + // Add the link. + $links[] = [ + 'link' => Link::fromTextAndUrl($entity_description, $source_url)->toString(), ]; } return [ '#theme' => 'audiofield_download_links', - '#links' => $download_render_array, + '#links' => $links, ]; } diff --git a/sites/all/modules/contrib/fields/audiofield/src/Commands/AudiofieldCommands.php b/sites/all/modules/contrib/fields/audiofield/src/Commands/AudiofieldCommands.php new file mode 100644 index 000000000..6e0be8b02 --- /dev/null +++ b/sites/all/modules/contrib/fields/audiofield/src/Commands/AudiofieldCommands.php @@ -0,0 +1,268 @@ +playerManager = $player_manager; + } + + /** + * Downloads the suggested Audiofield libraries from their remote repos. + * + * @param string $installLibrary + * The name of the library. If omitted, all libraries will be installed. + * @param bool $print_messages + * Flag indicating if messages should be displayed. + * + * @command audiofield:download + * @aliases audiofield-download + */ + public function download($installLibrary = '', $print_messages = TRUE) { + + // Declare filesystem container. + $fs = new Filesystem(); + + // Get a list of the audiofield plugins. + $pluginList = $this->playerManager->getDefinitions(); + + // If there is an argument, check to make sure its valid. + if (!empty($installLibrary)) { + if (!isset($pluginList[$installLibrary . '_audio_player'])) { + $this->logger()->error(dt('Error: @library is not a valid Audiofield library.', [ + '@library' => $installLibrary, + ], 'error')); + return; + } + // If the argument is valid, we only want to install that plugin. + $pluginList = [$installLibrary . '_audio_player' => $pluginList[$installLibrary . '_audio_player']]; + } + + // Loop over each plugin and make sure it's library is installed. + foreach ($pluginList as $pluginName => $plugin) { + // Create an instance of this plugin. + $pluginInstance = $this->playerManager->createInstance($pluginName); + + // Only check install if there is a library for the plugin. + if (!$pluginInstance->getPluginLibrary()) { + continue; + } + + // Skip if the plugin is installed. + if ($pluginInstance->checkInstalled()) { + if ($print_messages) { + $this->logger()->notice(dt('Audiofield library for @library is already installed at @location', [ + '@library' => $pluginInstance->getPluginTitle(), + '@location' => $pluginInstance->getPluginLibraryPath(), + ], 'success')); + } + continue; + } + + // Get the library install path. + $path = DRUPAL_ROOT . $pluginInstance->getPluginLibraryPath(); + // Create the install directory if it does not exist. + if (!is_dir($path)) { + $fs->mkdir($path); + } + + // Download the file. + $client = new Client(); + $destination = tempnam(sys_get_temp_dir(), 'file.') . "tar.gz"; + try { + $client->get($pluginInstance->getPluginRemoteSource(), ['save_to' => $destination]); + } + catch (RequestException $e) { + // Remove the directory. + $fs->remove($path); + $this->logger()->error(dt('Error: unable to download @library. @exception', [ + '@library' => $pluginInstance->getPluginTitle(), + '@exception' => $e->getMessage(), + ], 'error')); + continue; + } + $fs->rename($destination, $path . '/audiofield-dl.zip'); + if (!file_exists($path . '/audiofield-dl.zip')) { + // Remove the directory where we tried to install. + $fs->remove($path); + if ($print_messages) { + $this->logger()->error(dt('Error: unable to download Audiofield library @library', [ + '@library' => $pluginInstance->getPluginTitle(), + ], 'error')); + continue; + } + } + + // Unzip the file. + $zip = new \ZipArchive(); + $res = $zip->open($path . '/audiofield-dl.zip'); + if ($res === TRUE) { + $zip->extractTo($path); + $zip->close(); + } + else { + // Remove the directory. + $fs->remove($path); + $this->logger()->error(dt('Error: unable to unzip @library.', [ + '@library' => $pluginInstance->getPluginTitle(), + ], 'error')); + continue; + } + + // Remove the downloaded zip file. + $fs->remove($path . '/audiofield-dl.zip'); + + // If the library still is not installed, we need to move files. + if (!$pluginInstance->checkInstalled()) { + // Find all folders in this directory and move their + // subdirectories up to the parent directory. + $directories = Finder::create() + ->directories() + ->depth('< 1') + ->in($path) + ->ignoreDotFiles(TRUE) + ->ignoreVCS(TRUE); + foreach ($directories as $dirName) { + $fs->mirror($dirName, $path, NULL, ['override' => TRUE]); + $fs->remove($dirName); + } + + // Projekktor source files need to be installed. + if ($pluginInstance->getPluginId() == 'projekktor_audio_player') { + drush_op('chdir', '..'); + drush_op('chdir', $path); + drush_shell_exec('npm install'); + drush_shell_exec('grunt --force'); + } + } + if ($pluginInstance->checkInstalled()) { + if ($print_messages) { + $this->logger()->notice(dt('Audiofield library for @library has been successfully installed at @location', [ + '@library' => $pluginInstance->getPluginTitle(), + '@location' => $pluginInstance->getPluginLibraryPath(), + ], 'success')); + } + } + else { + // Remove the directory where we tried to install. + $fs->remove($path); + if ($print_messages) { + $this->logger()->error(dt('Error: unable to install Audiofield library @library', [ + '@library' => $pluginInstance->getPluginTitle(), + ], 'error')); + } + } + } + } + + /** + * Updates Audiofield libraries from their remote repos if out of date. + * + * @param string $updateLibrary + * The name of the library. If omitted, all libraries will be updated. + * @param bool $print_messages + * Flag indicating if messages should be displayed. + * + * @command audiofield:update + * @aliases audiofield-update + */ + public function update($updateLibrary = '', $print_messages = TRUE) { + + // Declare filesystem container. + $fs = new Filesystem(); + + // Get a list of the audiofield plugins. + $pluginList = $this->playerManager->getDefinitions(); + + // If there is an argument, check to make sure its valid. + if (!empty($updateLibrary)) { + if (!isset($pluginList[$updateLibrary . '_audio_player'])) { + $this->logger()->error(dt('Error: @library is not a valid Audiofield library.', [ + '@library' => $updateLibrary, + ], 'error')); + return; + } + // If the argument is valid, we only want to install that plugin. + $pluginList = [$updateLibrary . '_audio_player' => $pluginList[$updateLibrary . '_audio_player']]; + } + + // Loop over each plugin and make sure it's library is installed. + foreach ($pluginList as $pluginName => $plugin) { + // Create an instance of this plugin. + $pluginInstance = $this->playerManager->createInstance($pluginName); + + // Only check install if there is a library for the plugin. + if (!$pluginInstance->getPluginLibrary()) { + continue; + } + + // Get the library install path. + $path = DRUPAL_ROOT . $pluginInstance->getPluginLibraryPath(); + + // If the library isn't installed at all we just run the install. + if (!$pluginInstance->checkInstalled(FALSE)) { + $this->download($pluginInstance->getPluginLibraryName()); + continue; + } + + // Don't updating the library if its up to date. + if ($pluginInstance->checkVersion(FALSE)) { + $this->logger()->notice(dt('Audiofield library for @library is already up to date', [ + '@library' => $pluginInstance->getPluginTitle(), + ], 'success')); + continue; + } + + // Move the current installation to the temp directory. + $fs->rename($path, file_directory_temp() . '/temp_audiofield', TRUE); + // If the directory failed to move, just delete it. + if (is_dir($path)) { + $fs->remove($path); + } + + // Run the install command now to get the latest version. + $this->download($updateLibrary, FALSE); + + // Check if library has been properly installed. + if ($pluginInstance->checkInstalled()) { + // Remove the temporary directory. + $fs->remove(file_directory_temp() . '/temp_audiofield'); + $this->logger()->notice(dt('Audiofield library for @library has been successfully updated at @location', [ + '@library' => $pluginInstance->getPluginTitle(), + '@location' => $pluginInstance->getPluginLibraryPath(), + ], 'success')); + } + else { + // Remove the directory where we tried to install. + $fs->remove($path); + $this->logger()->error(dt('Error: unable to update Audiofield library @library', [ + '@library' => $pluginInstance->getPluginTitle(), + ], 'error')); + // Restore the original install since we failed to update. + $fs->rename(file_directory_temp() . '/temp_audiofield', $path, TRUE); + } + } + } + +} diff --git a/sites/all/modules/contrib/fields/audiofield/src/Plugin/AudioPlayer/WavesurferAudioPlayer.php b/sites/all/modules/contrib/fields/audiofield/src/Plugin/AudioPlayer/WavesurferAudioPlayer.php index 5ba98bdd3..5d8224e23 100644 --- a/sites/all/modules/contrib/fields/audiofield/src/Plugin/AudioPlayer/WavesurferAudioPlayer.php +++ b/sites/all/modules/contrib/fields/audiofield/src/Plugin/AudioPlayer/WavesurferAudioPlayer.php @@ -45,6 +45,18 @@ class WavesurferAudioPlayer extends AudioFieldPluginBase { 'volume' => ($settings['audio_player_initial_volume'] / 10), 'playertype' => ($settings['audio_player_wavesurfer_combine_files'] ? 'playlist' : 'default'), 'files' => [], + 'audioRate' => $settings['audio_player_wavesurfer_audiorate'], + 'autoCenter' => $settings['audio_player_wavesurfer_autocenter'], + 'barGap' => $settings['audio_player_wavesurfer_bargap'], + 'barHeight' => $settings['audio_player_wavesurfer_barheight'], + 'barWidth' => $settings['audio_player_wavesurfer_barwidth'], + 'cursorColor' => $settings['audio_player_wavesurfer_cursorcolor'], + 'cursorWidth' => $settings['audio_player_wavesurfer_cursorwidth'], + 'forceDecode' => $settings['audio_player_wavesurfer_forcedecode'], + 'normalize' => $settings['audio_player_wavesurfer_normalize'], + 'progressColor' => $settings['audio_player_wavesurfer_progresscolor'], + 'responsive' => $settings['audio_player_wavesurfer_responsive'], + 'waveColor' => $settings['audio_player_wavesurfer_wavecolor'], 'autoplay' => $settings['audio_player_autoplay'], ]; diff --git a/sites/all/modules/contrib/fields/audiofield/src/Plugin/Field/FieldFormatter/AudioFieldFieldFormatter.php b/sites/all/modules/contrib/fields/audiofield/src/Plugin/Field/FieldFormatter/AudioFieldFieldFormatter.php index 408505378..3eba0199f 100644 --- a/sites/all/modules/contrib/fields/audiofield/src/Plugin/Field/FieldFormatter/AudioFieldFieldFormatter.php +++ b/sites/all/modules/contrib/fields/audiofield/src/Plugin/Field/FieldFormatter/AudioFieldFieldFormatter.php @@ -138,6 +138,140 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory ], ], ]; + // Settings for WaveSurfer. + $elements['audio_player_wavesurfer_audiorate'] = [ + '#type' => 'number', + '#title' => $this->t('Audio Rate'), + '#description' => $this->t("Speed at which to play audio. Lower number is slower."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_audiorate'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_autocenter'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Auto Center'), + '#description' => $this->t("If a scrollbar is present, center the waveform around the progress."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_autocenter'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_bargap'] = [ + '#type' => 'number', + '#title' => $this->t('Bar Gap'), + '#description' => $this->t("The optional spacing between bars of the wave."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_bargap'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_barheight'] = [ + '#type' => 'number', + '#title' => $this->t('Bar Height'), + '#description' => $this->t("Height of the waveform bars. Higher number than 1 will increase the waveform bar heights."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_barheight'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_barwidth'] = [ + '#type' => 'number', + '#title' => $this->t('Bar Width'), + '#description' => $this->t("If specified, the waveform will be drawn like this: ▁ ▂ ▇ ▃ ▅ ▂"), + '#default_value' => $this->getSetting('audio_player_wavesurfer_barwidth'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_cursorcolor'] = [ + '#type' => 'color', + '#title' => $this->t('Cursor Color'), + '#description' => $this->t("The fill color of the cursor indicating the playhead position."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_cursorcolor'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_cursorwidth'] = [ + '#type' => 'number', + '#title' => $this->t('Cursor Width'), + '#description' => $this->t("Width of the cursor indicating the playhead position. Measured in pixels."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_cursorwidth'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_forcedecode'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Force Decode'), + '#description' => $this->t("Force decoding of audio using web audio when zooming to get a more detailed waveform."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_forcedecode'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_normalize'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Normalize'), + '#description' => $this->t("If checked, normalize by the maximum peak instead of 1.0."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_normalize'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_progresscolor'] = [ + '#type' => 'color', + '#title' => $this->t('Progress Color'), + '#description' => $this->t("The fill color of the part of the waveform behind the cursor."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_progresscolor'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_responsive'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Responsive'), + '#description' => $this->t("If checked, resize the waveform, when the window is resized. This is debounced with a 100ms timeout by default."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_responsive'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + $elements['audio_player_wavesurfer_wavecolor'] = [ + '#type' => 'color', + '#title' => $this->t('Wave Color'), + '#description' => $this->t("The fill color of the waveform after the cursor."), + '#default_value' => $this->getSetting('audio_player_wavesurfer_wavecolor'), + '#states' => [ + 'visible' => [ + [':input[name="fields[' . $fieldname . '][settings_edit_form][settings][audio_player]"]' => ['value' => 'wavesurfer_audio_player']], + ], + ], + ]; + // Settings for WordPress. // Only show when WordPress is the selected audio player. $elements['audio_player_wordpress_combine_files'] = [ @@ -291,6 +425,42 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory $summary[] = $this->t('Combine files into single player? @combine', [ '@combine' => ($settings['audio_player_wavesurfer_combine_files'] ? 'Yes' : 'No'), ]); + $summary[] = $this->t('Audio Rate: @value', [ + '@value' => $settings['audio_player_wavesurfer_audiorate'], + ]); + $summary[] = $this->t('Auto Center? @value', [ + '@value' => ($settings['audio_player_wavesurfer_autocenter'] ? 'Yes' : 'No'), + ]); + $summary[] = $this->t('Bar Gap: @value', [ + '@value' => $settings['audio_player_wavesurfer_bargap'], + ]); + $summary[] = $this->t('Bar Height: @value', [ + '@value' => $settings['audio_player_wavesurfer_barheight'], + ]); + $summary[] = $this->t('Bar Width: @value', [ + '@value' => $settings['audio_player_wavesurfer_barwidth'], + ]); + $summary[] = $this->t('Cursor Color: ', [ + '@value' => $settings['audio_player_wavesurfer_cursorcolor'], + ]); + $summary[] = $this->t('Cursor Width: @value', [ + '@value' => $settings['audio_player_wavesurfer_cursorwidth'], + ]); + $summary[] = $this->t('Force Decode? @value', [ + '@value' => ($settings['audio_player_wavesurfer_forcedecode'] ? 'Yes' : 'No'), + ]); + $summary[] = $this->t('Normalize? @value', [ + '@value' => ($settings['audio_player_wavesurfer_normalize'] ? 'Yes' : 'No'), + ]); + $summary[] = $this->t('Progress Color: ', [ + '@value' => $settings['audio_player_wavesurfer_progresscolor'], + ]); + $summary[] = $this->t('Responsive? @value', [ + '@value' => ($settings['audio_player_wavesurfer_responsive'] ? 'Yes' : 'No'), + ]); + $summary[] = $this->t('Wave Color: ', [ + '@value' => $settings['audio_player_wavesurfer_wavecolor'], + ]); } // If this is wordpress, add those settings. elseif ($settings['audio_player'] == 'wordpress_audio_player') { @@ -384,6 +554,18 @@ class AudioFieldFieldFormatter extends FormatterBase implements ContainerFactory 'audio_player' => 'default_mp3_player', 'audio_player_jplayer_theme' => 'none', 'audio_player_wavesurfer_combine_files' => FALSE, + 'audio_player_wavesurfer_audiorate' => 1, + 'audio_player_wavesurfer_autocenter' => TRUE, + 'audio_player_wavesurfer_bargap' => 0, + 'audio_player_wavesurfer_barheight' => 1, + 'audio_player_wavesurfer_barwidth' => NULL, + 'audio_player_wavesurfer_cursorcolor' => '#333', + 'audio_player_wavesurfer_cursorwidth' => 1, + 'audio_player_wavesurfer_forcedecode' => FALSE, + 'audio_player_wavesurfer_normalize' => FALSE, + 'audio_player_wavesurfer_progresscolor' => '#555', + 'audio_player_wavesurfer_responsive' => FALSE, + 'audio_player_wavesurfer_wavecolor' => '#999', 'audio_player_wordpress_combine_files' => FALSE, 'audio_player_wordpress_animation' => TRUE, 'audio_player_soundmanager_theme' => 'default', diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audiofield-download-links.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audiofield-download-links.html.twig index cb735f0d5..cdedf5a58 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audiofield-download-links.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audiofield-download-links.html.twig @@ -4,10 +4,16 @@ * Default theme implementation for AudioField audioplayer download links. * * Available variables: - * - links: theme_item_list render array containing the download links for - * an audiofield entity. + * - links: downloa * * @ingroup themeable */ #} -{{ links }} + + + {% for file in links %} + + + + {% endfor %} +
{{ 'Download files:'|trans }}
diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer--jplayer-circle.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer--jplayer-circle.html.twig index e090f46e6..cab28df5f 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer--jplayer-circle.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer--jplayer-circle.html.twig @@ -16,24 +16,26 @@ {% if settings.audio_player_jplayer_theme == 'audiofield.jplayer.theme_jplayer_circle' %} {% for file in files %} -
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- +
-
{% endfor %} {% endif %} diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer.html.twig index 7cf2df811..91de5a9a7 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--jplayer.html.twig @@ -14,24 +14,26 @@ #}
{% for file in files %} -
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- +
-
{% endfor %}
diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--mediaelement.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--mediaelement.html.twig index f4ba9be9c..1e7844859 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--mediaelement.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--mediaelement.html.twig @@ -14,12 +14,14 @@ #}
{% for file in files %} -
- +
+
+ +
+
- {% endfor %}
diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--projekktor.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--projekktor.html.twig index 0661b099b..b179de5ff 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--projekktor.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--projekktor.html.twig @@ -14,10 +14,12 @@ #}
{% for file in files %} - - +
+ + +
{% endfor %}
diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--soundmanager--barui.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--soundmanager--barui.html.twig index b8ce611ed..8365f2ea3 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--soundmanager--barui.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer--soundmanager--barui.html.twig @@ -19,7 +19,7 @@
@@ -47,7 +47,7 @@
- menu + menu
diff --git a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer.html.twig b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer.html.twig index 821dd3f14..19477cf25 100644 --- a/sites/all/modules/contrib/fields/audiofield/templates/audioplayer.html.twig +++ b/sites/all/modules/contrib/fields/audiofield/templates/audioplayer.html.twig @@ -14,10 +14,12 @@ #}
{% for file in files %} - - +
+ + +
{% endfor %}
diff --git a/sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml b/sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml index 882a2f1cb..c6e3f1d10 100644 --- a/sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml +++ b/sites/all/modules/contrib/fields/linkit/config/schema/linkit.schema.yml @@ -45,6 +45,8 @@ linkit.matcher.entity: type: boolean substitution_type: type: string + limit: + type: integer linkit.matcher.entity:*: type: linkit.matcher.entity diff --git a/sites/all/modules/contrib/fields/linkit/linkit.info.yml b/sites/all/modules/contrib/fields/linkit/linkit.info.yml index 8fb636780..e6b1057bc 100644 --- a/sites/all/modules/contrib/fields/linkit/linkit.info.yml +++ b/sites/all/modules/contrib/fields/linkit/linkit.info.yml @@ -7,8 +7,8 @@ configure: entity.linkit_profile.collection test_dependencies: - imce -# Information added by Drupal.org packaging script on 2018-03-08 -version: '8.x-5.0-beta7' +# Information added by Drupal.org packaging script on 2018-12-28 +version: '8.x-5.0-beta8' core: '8.x' project: 'linkit' -datestamp: 1520552594 +datestamp: 1545966788 diff --git a/sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php b/sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php index 349c4342b..6892db077 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php +++ b/sites/all/modules/contrib/fields/linkit/src/Annotation/Matcher.php @@ -28,8 +28,6 @@ class Matcher extends Plugin { /** * The human-readable name of the matcher. * - * The string should be wrapped in a @Translation(). - * * @var \Drupal\Core\Annotation\Translation */ public $label; diff --git a/sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php b/sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php index 1e6e5bd17..cbb4d2ea8 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php +++ b/sites/all/modules/contrib/fields/linkit/src/Controller/AutocompleteController.php @@ -2,7 +2,6 @@ namespace Drupal\linkit\Controller; -use Drupal\Component\Utility\Unicode; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\linkit\SuggestionManager; @@ -77,7 +76,7 @@ class AutocompleteController implements ContainerInjectionInterface { $this->linkitProfile = $this->linkitProfileStorage->load($linkit_profile_id); $string = $request->query->get('q'); - $suggestionCollection = $this->suggestionManager->getSuggestions($this->linkitProfile, Unicode::strtolower($string)); + $suggestionCollection = $this->suggestionManager->getSuggestions($this->linkitProfile, mb_strtolower($string)); /* * If there are no suggestions from the matcher plugins, we have to add a diff --git a/sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php b/sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php index 5e533c514..2bb7963f6 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php +++ b/sites/all/modules/contrib/fields/linkit/src/Element/Linkit.php @@ -26,6 +26,7 @@ class Linkit extends FormElement { '#process' => [ [$class, 'processLinkitAutocomplete'], [$class, 'processGroup'], + [$class, 'processAjaxForm'], ], '#pre_render' => [ [$class, 'preRenderLinkitElement'], diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php index fdc80f077..5007d0475 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/AddForm.php @@ -120,7 +120,7 @@ class AddForm extends FormBase { ]); } else { - drupal_set_message($this->t('Added %label matcher.', ['%label' => $plugin->getLabel()])); + $this->messenger()->addMessage($this->t('Added %label matcher.', ['%label' => $plugin->getLabel()])); $form_state->setRedirect('linkit.matchers', [ 'linkit_profile' => $this->linkitProfile->id(), diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php index e09b038e0..e2f14ecb2 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/DeleteForm.php @@ -70,7 +70,7 @@ class DeleteForm extends ConfirmFormBase { public function submitForm(array &$form, FormStateInterface $form_state) { $this->linkitProfile->removeMatcher($this->linkitMatcher); - drupal_set_message($this->t('The matcher %label has been deleted.', ['%label' => $this->linkitMatcher->getLabel()])); + $this->messenger()->addMessage($this->t('The matcher %label has been deleted.', ['%label' => $this->linkitMatcher->getLabel()])); $this->logger('linkit')->notice('The matcher %label has been deleted in the @profile profile.', [ '%label' => $this->linkitMatcher->getLabel(), '@profile' => $this->linkitProfile->label(), diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php index ef32d4a95..d847d4e57 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Matcher/EditForm.php @@ -74,7 +74,7 @@ class EditForm extends FormBase { $this->linkitMatcher->submitConfigurationForm($form, $plugin_data); $this->linkitProfile->save(); - drupal_set_message($this->t('Saved %label configuration.', ['%label' => $this->linkitMatcher->getLabel()])); + $this->messenger()->addMessage($this->t('Saved %label configuration.', ['%label' => $this->linkitMatcher->getLabel()])); $this->logger('linkit')->notice('The matcher %label has been updated in the @profile profile.', [ '%label' => $this->linkitMatcher->getLabel(), '@profile' => $this->linkitProfile->label(), diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php index 10d810581..eab67d916 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/AddForm.php @@ -7,7 +7,7 @@ use Drupal\Core\Form\FormStateInterface; /** * Controller for profile addition forms. * - * @see \Drupal\linkit\Profile\FormBase + * @see \Drupal\linkit\Form\Profile\FormBase */ class AddForm extends FormBase { diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php index e3e4dfae9..6a2784de7 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/EditForm.php @@ -7,7 +7,7 @@ use Drupal\Core\Form\FormStateInterface; /** * Provides an edit form for profile. * - * @see \Drupal\linkit\Profile\FormBase + * @see \Drupal\linkit\Form\Profile\FormBase */ class EditForm extends FormBase { diff --git a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php index 7670a42a9..d11e33c0c 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php +++ b/sites/all/modules/contrib/fields/linkit/src/Form/Profile/FormBase.php @@ -67,7 +67,7 @@ abstract class FormBase extends EntityForm { $edit_link = $this->entity->toLink($this->t('Edit'), 'edit-form')->toString(); switch ($status) { case SAVED_NEW: - drupal_set_message($this->t('Created new profile %label.', ['%label' => $linkit_profile->label()])); + $this->messenger()->addMessage($this->t('Created new profile %label.', ['%label' => $linkit_profile->label()])); $this->logger('linkit')->notice('Created new profile %label.', ['%label' => $linkit_profile->label(), 'link' => $edit_link]); $form_state->setRedirect('linkit.matchers', [ 'linkit_profile' => $linkit_profile->id(), @@ -75,7 +75,7 @@ abstract class FormBase extends EntityForm { break; case SAVED_UPDATED: - drupal_set_message($this->t('Updated profile %label.', ['%label' => $linkit_profile->label()])); + $this->messenger()->addMessage($this->t('Updated profile %label.', ['%label' => $linkit_profile->label()])); $this->logger('linkit')->notice('Updated profile %label.', ['%label' => $linkit_profile->label(), 'link' => $edit_link]); $form_state->setRedirectUrl($linkit_profile->toUrl('edit-form')); break; diff --git a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php index a8a85c8f3..9d56a479b 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php +++ b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/EntityMatcher.php @@ -36,6 +36,11 @@ class EntityMatcher extends ConfigurableMatcherBase { use MatcherTokensTrait; + /** + * The default limit for matches. + */ + const DEFAULT_LIMIT = 100; + /** * The database connection. * @@ -161,6 +166,12 @@ class EntityMatcher extends ConfigurableMatcherBase { $summery[] = $this->t('Group by bundle: @bundle_grouping', [ '@bundle_grouping' => $this->configuration['group_by_bundle'] ? $this->t('Yes') : $this->t('No'), ]); + + if (!empty($this->configuration['limit'])) { + $summery[] = $this->t('Limit: @limit', [ + '@limit' => $this->configuration['limit'], + ]); + } } return $summery; @@ -175,6 +186,7 @@ class EntityMatcher extends ConfigurableMatcherBase { 'bundles' => [], 'group_by_bundle' => FALSE, 'substitution_type' => SubstitutionManagerInterface::DEFAULT_SUBSTITUTION, + 'limit' => static::DEFAULT_LIMIT, ] + parent::defaultConfiguration(); } @@ -257,6 +269,25 @@ class EntityMatcher extends ConfigurableMatcherBase { '#description' => $this->t('Configure how the selected entity should be transformed into a URL for insertion.'), ]; + $form['limit'] = [ + '#type' => 'details', + '#title' => $this->t('Limit'), + '#open' => TRUE, + ]; + + $form['limit']['limit'] = [ + '#type' => 'select', + '#options' => [ + 0 => $this->t('Unlimited'), + 20 => 20, + 50 => 50, + 100 => 100, + 200 => 200, + ], + '#title' => $this->t('Limit search results'), + '#description' => $this->t('Limit the amount of results displayed when searching.'), + '#default_value' => $this->configuration['limit'], + ]; return $form; } @@ -274,6 +305,7 @@ class EntityMatcher extends ConfigurableMatcherBase { $this->configuration['bundles'] = $form_state->getValue('bundles'); $this->configuration['group_by_bundle'] = $form_state->getValue('group_by_bundle'); $this->configuration['substitution_type'] = $form_state->getValue('substitution_type'); + $this->configuration['limit'] = $form_state->getValue('limit'); } /** @@ -353,6 +385,9 @@ class EntityMatcher extends ConfigurableMatcherBase { $query->condition($bundle_key, $this->configuration['bundles'], 'IN'); } + if ($this->configuration['limit']) { + $query->range(0, $this->configuration['limit']); + } $this->addQueryTags($query); return $query; diff --git a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php index 28252a2fd..9d0508998 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php +++ b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/FileMatcher.php @@ -190,22 +190,30 @@ class FileMatcher extends EntityMatcher { /** @var \Drupal\file\FileInterface $entity */ $file = $entity->getFileUri(); - /** @var \Drupal\Core\Image\ImageInterface $image */ - $image = \Drupal::service('image.factory')->get($file); - if ($image->isValid()) { - if ($this->configuration['images']['show_dimensions']) { - $description_array[] = $image->getWidth() . 'x' . $image->getHeight() . 'px'; - } + if ($this->configuration['images']['show_dimensions'] || $this->configuration['images']['show_thumbnail']) { + $image_factory = \Drupal::service('image.factory'); + $supported_extensions = $image_factory->getSupportedExtensions(); - if ($this->configuration['images']['show_thumbnail'] && $this->moduleHandler->moduleExists('image')) { - $image_element = [ - '#weight' => -10, - '#theme' => 'image_style', - '#style_name' => $this->configuration['images']['thumbnail_image_style'], - '#uri' => $entity->getFileUri(), - ]; + // Check if the file extension is supported by the image toolkit. + if (empty(file_validate_extensions($entity, implode(' ', $supported_extensions)))) { + /** @var \Drupal\Core\Image\ImageInterface $image */ + $image = $image_factory->get($file); + if ($image->isValid()) { + if ($this->configuration['images']['show_dimensions']) { + $description_array[] = $image->getWidth() . 'x' . $image->getHeight() . 'px'; + } - $description_array[] = (string) \Drupal::service('renderer')->render($image_element); + if ($this->configuration['images']['show_thumbnail'] && $this->moduleHandler->moduleExists('image')) { + $image_element = [ + '#weight' => -10, + '#theme' => 'image_style', + '#style_name' => $this->configuration['images']['thumbnail_image_style'], + '#uri' => $entity->getFileUri(), + ]; + + $description_array[] = (string) \Drupal::service('renderer')->render($image_element); + } + } } } diff --git a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php index 3a1424cc8..f40c7c006 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php +++ b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Matcher/NodeMatcher.php @@ -3,6 +3,7 @@ namespace Drupal\linkit\Plugin\Linkit\Matcher; use Drupal\Core\Form\FormStateInterface; +use Drupal\node\NodeInterface; /** * Provides specific linkit matchers for the node entity type. @@ -84,9 +85,27 @@ class NodeMatcher extends EntityMatcher { protected function buildEntityQuery($search_string) { $query = parent::buildEntityQuery($search_string); - $no_access = !$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants')); - if ($this->configuration['include_unpublished'] !== TRUE || $no_access) { - $query->condition('status', NODE_PUBLISHED); + if ($this->configuration['include_unpublished'] == FALSE) { + $query->condition('status', NodeInterface::PUBLISHED); + } + elseif (count($this->moduleHandler->getImplementations('node_grants')) === 0) { + if (($this->currentUser->hasPermission('bypass node access') || $this->currentUser->hasPermission('view any unpublished content'))) { + // User can see all content, no check necessary. + } + elseif ($this->currentUser->hasPermission('view own unpublished content')) { + // Users with "view own unpublished content" can see only their own. + if ($this->configuration['include_unpublished'] == TRUE) { + $or_condition = $query + ->orConditionGroup() + ->condition('status', NodeInterface::PUBLISHED) + ->condition('uid', $this->currentUser->id()); + $query->condition($or_condition); + } + } + } + else { + // All other users should only get published results. + $query->condition('status', NodeInterface::PUBLISHED); } return $query; diff --git a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Substitution/Media.php b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Substitution/Media.php index ddc1bf295..be593f8fe 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Substitution/Media.php +++ b/sites/all/modules/contrib/fields/linkit/src/Plugin/Linkit/Substitution/Media.php @@ -5,7 +5,6 @@ namespace Drupal\linkit\Plugin\Linkit\Substitution; use Drupal\Component\Plugin\PluginBase; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\GeneratedUrl; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\linkit\SubstitutionInterface; @@ -21,30 +20,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class Media extends PluginBase implements SubstitutionInterface, ContainerFactoryPluginInterface { - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManager - */ - protected $entityTypeManager; - - /** - * Constructs a Media 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\Entity\EntityTypeManager $entity_type_manager - * The entity type manager. - */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManager $entity_type_manager) { - parent::__construct($configuration, $plugin_id, $plugin_definition); - $this->entityTypeManager = $entity_type_manager; - } - /** * {@inheritdoc} */ diff --git a/sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCreationTrait.php b/sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCreationTrait.php index e662aaf14..234303568 100644 --- a/sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCreationTrait.php +++ b/sites/all/modules/contrib/fields/linkit/src/Tests/ProfileCreationTrait.php @@ -2,7 +2,6 @@ namespace Drupal\linkit\Tests; -use Drupal\Component\Utility\Unicode; use Drupal\linkit\Entity\Profile; /** @@ -29,7 +28,7 @@ trait ProfileCreationTrait { protected function createProfile(array $settings = []) { // Populate defaults array. $settings += [ - 'id' => Unicode::strtolower($this->randomMachineName()), + 'id' => mb_strtolower($this->randomMachineName()), 'label' => $this->randomMachineName(), ]; diff --git a/sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml b/sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml index d3ba29c9e..5c82ba91e 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml +++ b/sites/all/modules/contrib/fields/linkit/tests/linkit_test/linkit_test.info.yml @@ -9,8 +9,8 @@ dependencies: - drupal:field - drupal:text -# Information added by Drupal.org packaging script on 2018-03-08 -version: '8.x-5.0-beta7' +# Information added by Drupal.org packaging script on 2018-12-28 +version: '8.x-5.0-beta8' core: '8.x' project: 'linkit' -datestamp: 1520552594 +datestamp: 1545966788 diff --git a/sites/all/modules/contrib/fields/linkit/tests/src/Functional/ProfileAdminTest.php b/sites/all/modules/contrib/fields/linkit/tests/src/Functional/ProfileAdminTest.php index 3b840b9ab..86b40676c 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/src/Functional/ProfileAdminTest.php +++ b/sites/all/modules/contrib/fields/linkit/tests/src/Functional/ProfileAdminTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\linkit\Functional; -use Drupal\Component\Utility\Unicode; use Drupal\linkit\Tests\ProfileCreationTrait; /** @@ -62,8 +61,8 @@ class ProfileAdminTest extends LinkitBrowserTestBase { // Create a profile. $edit = []; - $edit['label'] = Unicode::strtolower($this->randomMachineName()); - $edit['id'] = Unicode::strtolower($this->randomMachineName()); + $edit['label'] = mb_strtolower($this->randomMachineName()); + $edit['id'] = mb_strtolower($this->randomMachineName()); $edit['description'] = $this->randomMachineName(16); $this->submitForm($edit, t('Save and manage matchers')); diff --git a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitAutocompleteTest.php b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitAutocompleteTest.php index 5c167b869..df6c2abb1 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitAutocompleteTest.php +++ b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitAutocompleteTest.php @@ -5,7 +5,6 @@ namespace Drupal\Tests\linkit\Kernel; use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Html; -use Drupal\Component\Utility\Unicode; use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTestMul; use Drupal\language\Entity\ConfigurableLanguage; @@ -153,6 +152,29 @@ class LinkitAutocompleteTest extends LinkitKernelTestBase { $this->assertEmpty(count($data), 'Autocomplete did not return any suggestions.'); } + /** + * Tests autocompletion with results limit. + */ + public function testAutocompletionWithLimit() { + /** @var \Drupal\linkit\MatcherInterface $plugin */ + $plugin = $this->matcherManager->createInstance('entity:entity_test'); + $configuration = $plugin->getConfiguration(); + $configuration['settings']['limit'] = 2; + + $this->linkitProfile->addMatcher($configuration); + $this->linkitProfile->save(); + + $entity_1 = EntityTest::create(['name' => 'foo 1']); + $entity_1->save(); + $entity_2 = EntityTest::create(['name' => 'foo 2']); + $entity_2->save(); + $entity_3 = EntityTest::create(['name' => 'foo 3']); + $entity_3->save(); + + $data = $this->getAutocompleteResult('foo'); + $this->assertTrue(count($data) == 2, 'Autocomplete returned the expected amount of suggestions.'); + } + /** * Tests autocompletion with translated entities. */ @@ -221,7 +243,7 @@ class LinkitAutocompleteTest extends LinkitKernelTestBase { protected function createProfile(array $settings = []) { // Populate defaults array. $settings += [ - 'id' => Unicode::strtolower($this->randomMachineName()), + 'id' => mb_strtolower($this->randomMachineName()), 'label' => $this->randomMachineName(), ]; diff --git a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitKernelTestBase.php b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitKernelTestBase.php index 737a0bb1f..4a8bde6fd 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitKernelTestBase.php +++ b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/LinkitKernelTestBase.php @@ -51,7 +51,7 @@ abstract class LinkitKernelTestBase extends KernelTestBase { if ($permissions) { // Create a new role and apply permissions to it. $role = Role::create([ - 'id' => strtolower($this->randomMachineName(8)), + 'id' => mb_strtolower($this->randomMachineName(8)), 'label' => $this->randomMachineName(8), ]); $role->save(); diff --git a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/NodeMatcherTest.php b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/NodeMatcherTest.php index 8783a4f97..0ab55506e 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/NodeMatcherTest.php +++ b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/NodeMatcherTest.php @@ -18,7 +18,7 @@ class NodeMatcherTest extends LinkitKernelTestBase { * * @var array */ - public static $modules = ['field', 'node']; + public static $modules = ['field', 'node', 'content_moderation', 'workflows']; /** * The matcher manager. @@ -34,6 +34,7 @@ class NodeMatcherTest extends LinkitKernelTestBase { parent::setUp(); $this->installEntitySchema('node'); + $this->installSchema('node', ['node_access']); $this->installConfig(['field', 'node']); $this->manager = $this->container->get('plugin.manager.linkit.matcher'); @@ -74,7 +75,7 @@ class NodeMatcherTest extends LinkitKernelTestBase { ]); $node->save(); - // Unpublished node. + // Unpublished nodes. $node = Node::create([ 'title' => 'Lorem unpublishd', 'type' => $type1->id(), @@ -82,6 +83,13 @@ class NodeMatcherTest extends LinkitKernelTestBase { ]); $node->save(); + $node = Node::create([ + 'title' => 'Lorem unpublishd 2', + 'type' => $type2->id(), + 'status' => FALSE, + ]); + $node->save(); + // Set the current user to someone that is not the node owner. \Drupal::currentUser()->setAccount($this->createUser([], ['access content'])); } @@ -133,7 +141,22 @@ class NodeMatcherTest extends LinkitKernelTestBase { // Test with permissions to see unpublished nodes. $suggestions = $plugin->execute('Lorem'); + $this->assertEquals(5, count($suggestions->getSuggestions()), 'Correct number of suggestions'); + + // Test with permissions to see own unpublished nodes. + \Drupal::currentUser()->setAccount($this->createUser([], ['access content', 'view own unpublished content'])); + $nodes = $this->container->get('entity_type.manager')->getStorage('node')->loadByProperties(['title' => 'Lorem unpublishd']); + $node4 = reset($nodes); + /** @var \Drupal\node\NodeInterface $node4 */ + $node4->setOwnerId(\Drupal::currentUser()->id()); + $node4->save(); + $suggestions = $plugin->execute('Lorem'); $this->assertEquals(4, count($suggestions->getSuggestions()), 'Correct number of suggestions'); + + // Test with permissions to see any unpublished nodes. + \Drupal::currentUser()->setAccount($this->createUser([], ['access content', 'view any unpublished content'])); + $suggestions = $plugin->execute('Lorem'); + $this->assertEquals(5, count($suggestions->getSuggestions()), 'Correct number of suggestions'); } /** diff --git a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/TermMatcherTest.php b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/TermMatcherTest.php index 22f2c446e..4e04d0fde 100644 --- a/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/TermMatcherTest.php +++ b/sites/all/modules/contrib/fields/linkit/tests/src/Kernel/Matchers/TermMatcherTest.php @@ -2,7 +2,6 @@ namespace Drupal\Tests\linkit\Kernel\Matchers; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Language\LanguageInterface; use Drupal\taxonomy\VocabularyInterface; use Drupal\Tests\linkit\Kernel\LinkitKernelTestBase; @@ -118,7 +117,7 @@ class TermMatcherTest extends LinkitKernelTestBase { $vocabulary = $vocabularyStorage->create([ 'name' => $name, 'description' => $name, - 'vid' => Unicode::strtolower($name), + 'vid' => mb_strtolower($name), 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, ]); $vocabulary->save(); diff --git a/sites/all/modules/contrib/migrate/migrate_plus/README.txt b/sites/all/modules/contrib/migrate/migrate_plus/README.txt index 797082e38..9738be925 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/README.txt +++ b/sites/all/modules/contrib/migrate/migrate_plus/README.txt @@ -7,8 +7,8 @@ Extensions to base API migration configuration. * A MigrationGroup configuration entity is provided, which enables migrations to be organized in groups, and to maintain shared configuration in one place. -* A MigrateEvents::PREPARE_ROW event is provided to dispatch hook_prepare_row() - invocations as events. +* A MigrateEvents::PREPARE_ROW event is provided to dispatch + hook_migrate_prepare_row() invocations as events. * A SourcePluginExtension class is provided, enabling one to define fields and IDs for a source plugin via configuration rather than requiring PHP code. diff --git a/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.destination.schema.yml b/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.destination.schema.yml index 58ad74e53..c76e59d5d 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.destination.schema.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.destination.schema.yml @@ -8,6 +8,9 @@ migrate_plus.destination.*: type: boolean label: 'Whether stubbing is allowed.' default: false + default_bundle: + type: string + label: 'The default bundle for content entity destinations.' migrate_plus.destination.config: type: migrate_destination diff --git a/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.schema.yml b/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.schema.yml index 99aef5523..33dc2e6e1 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.schema.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/config/schema/migrate_plus.schema.yml @@ -29,13 +29,13 @@ migrate_plus.migration.*: type: label label: 'Label' source: - type: migrate_plus.source.[plugin] + type: ignore label: 'Source' process: type: ignore label: 'Process' destination: - type: migrate_plus.destination.[plugin] + type: ignore label: 'Destination' migration_dependencies: type: mapping diff --git a/sites/all/modules/contrib/migrate/migrate_plus/drupalci.yml b/sites/all/modules/contrib/migrate/migrate_plus/drupalci.yml new file mode 100644 index 000000000..7286c3ba5 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/drupalci.yml @@ -0,0 +1,20 @@ +# Learn to make one for your own drupal.org project: +# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing +build: + assessment: + validate_codebase: + phplint: + container_composer: + phpcs: + # phpcs will use core's specified version of Coder. + sniff-all-files: true + halt-on-fail: true + testing: + # run_tests task is executed several times in order of performance speeds. + # halt-on-fail can be set on the run_tests tasks in order to fail fast. + # suppress-deprecations is false in order to be alerted to usages of + # deprecated code. + run_tests.standard: + types: 'Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional' + testgroups: '--all' + suppress-deprecations: false diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example.info.yml index 5ff995788..4d87e172f 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example.info.yml @@ -10,8 +10,8 @@ dependencies: - drupal:menu_ui - drupal:path -# Information added by Drupal.org packaging script on 2018-09-06 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_plus' -datestamp: 1536264189 +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.info.yml index b9dd2672c..1b9d0d402 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.info.yml @@ -11,8 +11,8 @@ dependencies: - drupal:options - drupal:taxonomy -# Information added by Drupal.org packaging script on 2018-09-06 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_plus' -datestamp: 1536264189 +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.install b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.install index 342bc2dcf..e0a5fe65f 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.install +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example/migrate_example_setup/migrate_example_setup.install @@ -316,7 +316,7 @@ function migrate_example_beer_data_node() { 'image_title', 'image_description', ]; - $query = db_insert('migrate_example_beer_node') + $query = \Drupal::database()->insert('migrate_example_beer_node') ->fields($fields); // Use high bid numbers to avoid overwriting an existing node id. $data = [ @@ -381,7 +381,7 @@ function migrate_example_beer_data_account() { 'sex', 'beers', ]; - $query = db_insert('migrate_example_beer_account') + $query = \Drupal::database()->insert('migrate_example_beer_account') ->fields($fields); $data = [ [ @@ -436,7 +436,7 @@ function migrate_example_beer_data_account() { */ function migrate_example_beer_data_comment() { $fields = ['bid', 'cid_parent', 'subject', 'body', 'name', 'mail', 'aid']; - $query = db_insert('migrate_example_beer_comment') + $query = \Drupal::database()->insert('migrate_example_beer_comment') ->fields($fields); $data = [ [99999998, NULL, 'im first', 'full body', 'alice', 'alice@example.com', 0], @@ -464,7 +464,7 @@ function migrate_example_beer_data_comment() { */ function migrate_example_beer_data_topic() { $fields = ['style', 'details', 'style_parent', 'region', 'hoppiness']; - $query = db_insert('migrate_example_beer_topic') + $query = \Drupal::database()->insert('migrate_example_beer_topic') ->fields($fields); $data = [ ['ale', 'traditional', NULL, 'Medieval British Isles', 'Medium'], @@ -488,7 +488,7 @@ function migrate_example_beer_data_topic() { */ function migrate_example_beer_data_topic_node() { $fields = ['bid', 'style']; - $query = db_insert('migrate_example_beer_topic_node') + $query = \Drupal::database()->insert('migrate_example_beer_topic_node') ->fields($fields); $data = [ [99999999, 'pilsner'], diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/config/install/migrate_plus.migration.wine_variety_list.yml.txt b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/config/install/migrate_plus.migration.wine_variety_list.yml.txt deleted file mode 100755 index ff88862b2..000000000 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/config/install/migrate_plus.migration.wine_variety_list.yml.txt +++ /dev/null @@ -1,59 +0,0 @@ -# This migration demonstrates importing from an endpoint listing other endpoints -# containing individual item data. -id: wine_variety_list -label: XML feed of varieties -migration_group: wine -migration_tags: - - advanced example -source: - # We use the XML source plugin. - plugin: xml - # Normally, this is one or more fully-qualified URLs or file paths. Because - # we can't hardcode your local URL, we provide a relative path here which - # hook_install() will rewrite to a full URL for the current site. - urls: - - /migrate_example_advanced_variety_list?_format=xml - item_url: /migrate_example_advanced_variety_list/:id?_format=xml - id_selector: /response/items - # Visit the URL above (relative to your site root) and look at it. You can see - # that is the outer element, and each item we want to import is a - # element. The item_xpath value is the xpath to use to query the - # desired elements. - item_selector: /response/variety - # Under 'fields', we list the data items to be imported. The first level keys - # are the source field names we want to populate (the names to be used as - # sources in the process configuration below). For each field we're importing, - # we provide a label (optional - this is for display in migration tools) and - # an xpath for retrieving that value. It's important to note that this xpath - # is relative to the elements retrieved by item_xpath. - fields: - category_name: - label: - selector: name - category_details: - label: - selector: details - category_parent: - label: 'Unique position identifier' - selector: parent - # Under 'ids', we identify source fields populated above which will uniquely - # identify each imported item. The 'type' makes sure the migration map table - # uses the proper schema type for stored the IDs. - ids: - category_name: - type: string -process: - vid: - plugin: default_value - default_value: migrate_example_wine_varieties - name: category_name - description: category_details - parent: - plugin: migration_lookup - migration: wine_terms - source: category_parent -destination: - plugin: entity:taxonomy_term -migration_dependencies: - require: - - wine_terms diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced.info.yml index 1d396e34b..b18ee5a45 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced.info.yml @@ -8,8 +8,8 @@ dependencies: - migrate_plus:migrate_example_advanced_setup - migrate_plus:migrate_plus -# Information added by Drupal.org packaging script on 2018-09-06 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_plus' -datestamp: 1536264189 +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.info.yml index 870586568..97334d36a 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.info.yml @@ -11,8 +11,8 @@ dependencies: - drupal:taxonomy - drupal:rest -# Information added by Drupal.org packaging script on 2018-09-06 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_plus' -datestamp: 1536264189 +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.install b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.install index be9031a6b..6fd5f7926 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.install +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_example_advanced/migrate_example_advanced_setup/migrate_example_advanced_setup.install @@ -737,7 +737,7 @@ function migrate_example_advanced_data_wine() { 'region', 'rating', ]; - $query = db_insert('migrate_example_wine') + $query = \Drupal::database()->insert('migrate_example_wine') ->fields($fields); $data = [ [ @@ -776,7 +776,7 @@ function migrate_example_advanced_data_wine() { */ function migrate_example_advanced_data_updates() { $fields = ['wineid', 'rating']; - $query = db_insert('migrate_example_advanced_updates') + $query = \Drupal::database()->insert('migrate_example_advanced_updates') ->fields($fields); $data = [ [1, 93], @@ -793,7 +793,7 @@ function migrate_example_advanced_data_updates() { */ function migrate_example_advanced_data_producer() { $fields = ['producerid', 'name', 'body', 'excerpt', 'accountid']; - $query = db_insert('migrate_example_advanced_producer') + $query = \Drupal::database()->insert('migrate_example_advanced_producer') ->fields($fields); $data = [ [1, 'Montes', 'Fine Chilean winery', 'Great!', 9], @@ -824,7 +824,7 @@ function migrate_example_advanced_data_account() { 'imageid', 'positions', ]; - $query = db_insert('migrate_example_advanced_account') + $query = \Drupal::database()->insert('migrate_example_advanced_account') ->fields($fields); $data = [ [ @@ -884,7 +884,7 @@ function migrate_example_advanced_data_account() { */ function migrate_example_advanced_data_account_updates() { $fields = ['accountid', 'sex']; - $query = db_insert('migrate_example_advanced_account_updates') + $query = \Drupal::database()->insert('migrate_example_advanced_account_updates') ->fields($fields); $data = [ [1, NULL], @@ -915,7 +915,7 @@ function migrate_example_advanced_data_comment() { 'posted', 'lastchanged', ]; - $query = db_insert('migrate_example_advanced_comment') + $query = \Drupal::database()->insert('migrate_example_advanced_comment') ->fields($fields); $data = [ [ @@ -1000,7 +1000,7 @@ function migrate_example_advanced_data_comment() { */ function migrate_example_advanced_data_comment_updates() { $fields = ['commentid', 'subject']; - $query = db_insert('migrate_example_advanced_comment_updates') + $query = \Drupal::database()->insert('migrate_example_advanced_comment_updates') ->fields($fields); $data = [ [1, 'I am first'], @@ -1027,7 +1027,7 @@ function migrate_example_advanced_data_categories() { 'details', 'ordering', ]; - $query = db_insert('migrate_example_advanced_categories') + $query = \Drupal::database()->insert('migrate_example_advanced_categories') ->fields($fields); $data = [ [ @@ -1079,7 +1079,7 @@ function migrate_example_advanced_data_categories() { */ function migrate_example_advanced_data_vintages() { $fields = ['wineid', 'vintage']; - $query = db_insert('migrate_example_advanced_vintages') + $query = \Drupal::database()->insert('migrate_example_advanced_vintages') ->fields($fields); $data = [ [1, 2006], @@ -1097,7 +1097,7 @@ function migrate_example_advanced_data_vintages() { */ function migrate_example_advanced_data_variety_updates() { $fields = ['categoryid', 'details']; - $query = db_insert('migrate_example_advanced_variety_updates') + $query = \Drupal::database()->insert('migrate_example_advanced_variety_updates') ->fields($fields); $data = [ [1, 'White wines are simpler and sweeter than red'], @@ -1120,7 +1120,7 @@ function migrate_example_advanced_data_variety_updates() { */ function migrate_example_advanced_data_category_wine() { $fields = ['wineid', 'categoryid']; - $query = db_insert('migrate_example_advanced_category_wine') + $query = \Drupal::database()->insert('migrate_example_advanced_category_wine') ->fields($fields); $data = [ [1, 12], @@ -1138,7 +1138,7 @@ function migrate_example_advanced_data_category_wine() { */ function migrate_example_advanced_data_category_producer() { $fields = ['producerid', 'categoryid']; - $query = db_insert('migrate_example_advanced_category_producer') + $query = \Drupal::database()->insert('migrate_example_advanced_category_producer') ->fields($fields); $data = [ [1, 17], @@ -1154,7 +1154,7 @@ function migrate_example_advanced_data_category_producer() { */ function migrate_example_advanced_data_files() { $fields = ['imageid', 'url', 'image_alt', 'image_title', 'wineid']; - $query = db_insert('migrate_example_advanced_files') + $query = \Drupal::database()->insert('migrate_example_advanced_files') ->fields($fields); $data = [ [ @@ -1192,7 +1192,7 @@ function migrate_example_advanced_data_files() { function migrate_example_advanced_data_blobs() { $blob = file_get_contents('core/misc/druplicon.png'); $fields = ['imageid', 'imageblob']; - $query = db_insert('migrate_example_advanced_blobs') + $query = \Drupal::database()->insert('migrate_example_advanced_blobs') ->fields($fields); $data = [ [1, $blob], @@ -1208,7 +1208,7 @@ function migrate_example_advanced_data_blobs() { */ function migrate_example_advanced_data_table_source() { $fields = ['fooid', 'field1', 'field2']; - $query = db_insert('migrate_example_advanced_table_source') + $query = \Drupal::database()->insert('migrate_example_advanced_table_source') ->fields($fields); $data = [ [3, 'Some sample data', 58], diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/README.md b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/README.md new file mode 100644 index 000000000..4736b985a --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/README.md @@ -0,0 +1,22 @@ +A demonstration of a simple import of a JSON file. + +REQUIREMENTS +============ +You need the contrib modules Migrate Plus and Migrate Tools. +To make the products.json file available for import, the file will be copied +from the artifacts folder to your sites/default/files folder. + +USAGE +===== +Enable the module, check status, import all products and rollback with Drush +drush en migrate_json_example +drush migrate-status +drush migrate-import product +drush migrate-rollback product + +See config/optional/migrate_plus.migration.product.yml for details about the +migration. + +Thanks to Jeff Geerling and Christophe for the original code: +- https://www.jeffgeerling.com/blog/2016/migrate-custom-json-feed-drupal-8-migrate-source-json +- https://colorfield.be/blog/drupal-8-json-custom-migration diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/artifacts/products.json b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/artifacts/products.json new file mode 100644 index 000000000..def2ddf29 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/artifacts/products.json @@ -0,0 +1,16 @@ +{ + "product": [ + { + "upc": "11111", + "name": "Widget", + "description": "Helpful for many things.", + "price": "14.99" + }, + { + "upc": "22222", + "name": "Sprocket", + "description": "Helpful for things needing sprockets.", + "price": "8.99" + } + ] +} diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_form_display.node.product.default.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_form_display.node.product.default.yml new file mode 100644 index 000000000..7faa1a2b2 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_form_display.node.product.default.yml @@ -0,0 +1,88 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.product.field_description + - field.field.node.product.field_price + - field.field.node.product.field_upc + - node.type.product + module: + - path +id: node.product.default +targetEntityType: node +bundle: product +mode: default +content: + created: + type: datetime_timestamp + weight: 10 + region: content + settings: { } + third_party_settings: { } + field_description: + weight: 123 + settings: + size: 60 + placeholder: '' + third_party_settings: { } + type: string_textfield + region: content + field_price: + weight: 124 + settings: + placeholder: '' + third_party_settings: { } + type: number + region: content + field_upc: + weight: 122 + settings: + placeholder: '' + third_party_settings: { } + type: number + region: content + path: + type: path + weight: 30 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 15 + region: content + third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 120 + region: content + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 16 + region: content + third_party_settings: { } + title: + type: string_textfield + weight: -5 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } +hidden: { } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_view_display.node.product.default.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_view_display.node.product.default.yml new file mode 100644 index 000000000..9e7a9f23c --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/core.entity_view_display.node.product.default.yml @@ -0,0 +1,49 @@ +langcode: en +status: true +dependencies: + config: + - field.field.node.product.field_description + - field.field.node.product.field_price + - field.field.node.product.field_upc + - node.type.product + module: + - user +id: node.product.default +targetEntityType: node +bundle: product +mode: default +content: + field_description: + weight: 103 + label: above + settings: + link_to_entity: false + third_party_settings: { } + type: string + region: content + field_price: + weight: 104 + label: above + settings: + thousand_separator: '' + decimal_separator: . + scale: 2 + prefix_suffix: true + third_party_settings: { } + type: number_decimal + region: content + field_upc: + weight: 102 + label: above + settings: + thousand_separator: '' + prefix_suffix: true + third_party_settings: { } + type: number_integer + region: content + links: + weight: 100 + settings: { } + third_party_settings: { } + region: content +hidden: { } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_description.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_description.yml new file mode 100644 index 000000000..7bbbd8419 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_description.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_description + - node.type.product +id: node.product.field_description +field_name: field_description +entity_type: node +bundle: product +label: Description +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: string diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_price.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_price.yml new file mode 100644 index 000000000..df23a84ae --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_price.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_price + - node.type.product +id: node.product.field_price +field_name: field_price +entity_type: node +bundle: product +label: Price +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + min: null + max: null + prefix: '' + suffix: '' +field_type: float diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_upc.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_upc.yml new file mode 100644 index 000000000..ef0a28ec4 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.field.node.product.field_upc.yml @@ -0,0 +1,22 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_upc + - node.type.product +id: node.product.field_upc +field_name: field_upc +entity_type: node +bundle: product +label: UPC +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + min: null + max: null + prefix: '' + suffix: '' +field_type: integer diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_description.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_description.yml new file mode 100644 index 000000000..a5c39580d --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_description.yml @@ -0,0 +1,20 @@ +langcode: en +status: true +dependencies: + module: + - node +id: node.field_description +field_name: field_description +entity_type: node +type: string +settings: + max_length: 255 + is_ascii: false + case_sensitive: false +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_price.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_price.yml new file mode 100644 index 000000000..090acad3d --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_price.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + module: + - node +id: node.field_price +field_name: field_price +entity_type: node +type: float +settings: { } +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_upc.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_upc.yml new file mode 100644 index 000000000..149b11da1 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/field.storage.node.field_upc.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node +id: node.field_upc +field_name: field_upc +entity_type: node +type: integer +settings: + unsigned: false + size: normal +module: core +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/migrate_plus.migration.product.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/migrate_plus.migration.product.yml new file mode 100644 index 000000000..166072494 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/migrate_plus.migration.product.yml @@ -0,0 +1,81 @@ +# This migration demonstrates a simple import from a JSON file. +id: product +label: JSON feed of Products +migration_group: Product +migration_tags: + - json example +source: + # We use the JSON source plugin. + plugin: url + # In this example we get data from a local file, to get data from a URL + # define http as data_fetcher_plugin. + # data_fetcher_plugin: http + data_fetcher_plugin: file + data_parser_plugin: json + # The data_parser normally limits the fields passed on to the source plugin + # to fields configured to be used as part of the migration. To support more + # dynamic migrations, the JSON data parser supports including the original + # data for the current row. Simply include the 'include_raw_data' flag set + # to `true` to enable this. This option is disabled by default to minimize + # memory footprint for migrations that do not need this capability. + # include_raw_data: true + # Flags whether to track changes to incoming data. If TRUE, we will maintain + # hashed source rows to determine whether incoming data has changed. + # track_changes: true + # Copy the example JSON file in artifacts folder to sites/default/files folder. + urls: + - 'public://migrate_json_example/products.json' + # An xpath-like selector corresponding to the items to be imported. + item_selector: product + # Under 'fields', we list the data items to be imported. The first level keys + # are the source field names we want to populate (the names to be used as + # sources in the process configuration below). For each field we're importing, + # we provide a label (optional - this is for display in migration tools) and + # an xpath for retrieving that value. It's important to note that this xpath + # is relative to the elements retrieved by item_selector. + fields: + - + name: upc + label: 'Unique product identifier' + selector: upc + - + name: name + label: 'Product name' + selector: name + - + name: description + label: 'Product description' + selector: description + - + name: price + label: 'Product price' + selector: price + # Under 'ids', we identify source fields populated above which will uniquely + # identify each imported item. The 'type' makes sure the migration map table + # uses the proper schema type for stored the IDs. + ids: + upc: + type: integer +process: + # Note that the source field names here (name, description and price) were + # defined by the 'fields' configuration for the source plugin above. + type: + plugin: default_value + default_value: product + title: name + field_upc: upc + field_description: description + field_price: price + sticky: + plugin: default_value + default_value: 0 + uid: + plugin: default_value + default_value: 0 +destination: + plugin: 'entity:node' +migration_dependencies: { } +dependencies: + enforced: + module: + - migrate_json_example diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/node.type.product.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/node.type.product.yml new file mode 100644 index 000000000..18d8499de --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/config/optional/node.type.product.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + module: + - menu_ui +third_party_settings: + menu_ui: + available_menus: + - main + parent: 'main:' +name: Product +type: product +description: '' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: true diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.info.yml new file mode 100644 index 000000000..31b9d8cde --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.info.yml @@ -0,0 +1,15 @@ +type: module +name: Migrate JSON Example +description: 'Simple JSON Migration example' +package: Examples +# core: 8.x +dependencies: + - drupal:migrate + - migrate_plus:migrate_plus + - migrate_tools:migrate_tools + +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' +core: '8.x' +project: 'migrate_plus' +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.install b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.install new file mode 100644 index 000000000..b10e368d3 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/migrate_json_example/migrate_json_example.install @@ -0,0 +1,20 @@ +=8.3) -# Information added by Drupal.org packaging script on 2018-09-06 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_plus' -datestamp: 1536264189 +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/Migration.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/Migration.php index 6359825cb..64992cd7c 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/Migration.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/Migration.php @@ -18,8 +18,23 @@ use Drupal\Core\Entity\EntityTypeInterface; * entity_keys = { * "id" = "id", * "label" = "label", - * "weight" = "weight" - * } + * "weight" = "weight", + * "status" = "status" + * }, + * config_export = { + * "id", + * "class", + * "field_plugin_method", + * "cck_plugin_method", + * "migration_tags", + * "migration_group", + * "status", + * "label", + * "source", + * "process", + * "destination", + * "migration_dependencies", + * }, * ) */ class Migration extends ConfigEntityBase implements MigrationInterface { diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/MigrationGroup.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/MigrationGroup.php index 519dbe241..96845b031 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/MigrationGroup.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Entity/MigrationGroup.php @@ -19,7 +19,15 @@ use Drupal\Core\Config\Entity\ConfigEntityBase; * entity_keys = { * "id" = "id", * "label" = "label" - * } + * }, + * config_export = { + * "id", + * "label", + * "description", + * "source_type", + * "module", + * "shared_configuration", + * }, * ) */ class MigrationGroup extends ConfigEntityBase implements MigrationGroupInterface { diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/MigrationConfigDeriver.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/MigrationConfigDeriver.php index 013544497..d66892128 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/MigrationConfigDeriver.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/MigrationConfigDeriver.php @@ -20,6 +20,10 @@ class MigrationConfigDeriver extends DeriverBase { $migrations = Migration::loadMultiple(); /** @var \Drupal\migrate_plus\Entity\MigrationInterface $migration */ foreach ($migrations as $id => $migration) { + if (!$migration->status()) { + continue; + } + $this->derivatives[$id] = $migration->toArray(); } return $this->derivatives; diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/EntityLookup.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/EntityLookup.php index 2ae0d07c1..7ed4e84d2 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/EntityLookup.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/EntityLookup.php @@ -131,8 +131,8 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn $this->entityManager = $entityManager; $this->selectionPluginManager = $selectionPluginManager; $pluginIdParts = explode(':', $this->migration->getDestinationPlugin()->getPluginId()); - $this->destinationEntityType = empty($pluginIdParts[1]) ?: $pluginIdParts[1]; - $this->destinationBundleKey = !$this->destinationEntityType ?: $this->entityManager->getDefinition($this->destinationEntityType)->getKey('bundle'); + $this->destinationEntityType = empty($pluginIdParts[1]) ? NULL : $pluginIdParts[1]; + $this->destinationBundleKey = $this->destinationEntityType ? $this->entityManager->getDefinition($this->destinationEntityType)->getKey('bundle') : NULL; } /** @@ -223,8 +223,7 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn break; default: - throw new MigrateException('Destination field type ' . - $fieldConfig->getType() . 'is not a recognized reference type.'); + throw new MigrateException(sprintf('Destination field type %s is not a recognized reference type.', $fieldConfig->getType())); } } } @@ -262,6 +261,11 @@ class EntityLookup extends ProcessPluginBase implements ContainerFactoryPluginIn $query = $this->entityManager->getStorage($this->lookupEntityType) ->getQuery() ->condition($this->lookupValueKey, $value, $multiple ? 'IN' : NULL); + // Sqlite and possibly others returns data in a non-deterministic order. + // Make it deterministic. + if ($multiple) { + $query->sort($this->lookupValueKey, 'DESC'); + } if ($this->lookupBundleKey) { $query->condition($this->lookupBundleKey, $this->lookupBundle); diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/Merge.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/Merge.php index 463595e9d..d6b32bbc0 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/Merge.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/Merge.php @@ -56,7 +56,7 @@ class Merge extends ProcessPluginBase { throw new MigrateException(sprintf('Merge process failed for destination property (%s): input is not an array.', $destination_property)); } $new_value = []; - foreach($value as $i => $item) { + foreach ($value as $i => $item) { if (!is_array($item)) { throw new MigrateException(sprintf('Merge process failed for destination property (%s): index (%s) in the source value is not an array that can be merged.', $destination_property, $i)); } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/SkipOnValue.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/SkipOnValue.php index 1a0c8f545..af18a734d 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/SkipOnValue.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/SkipOnValue.php @@ -20,10 +20,10 @@ use Drupal\migrate\Row; * - value: An single value or array of values against which the source value * should be compared. * - not_equals: (optional) If set, skipping occurs when values are not equal. - * - method: What to do if the input value is empty. Possible values: - * - row: Skips the entire row when an empty value is encountered. - * - process: Prevents further processing of the input property when the value - * is empty. + * - method: What to do if the input value equals to value given in + * configuration key value. Possible values: + * - row: Skips the entire row. + * - process: Prevents further processing of the input property * * Examples: * @@ -32,12 +32,12 @@ use Drupal\migrate\Row; * type: * plugin: skip_on_value * source: content_type - * method: row + * method: process * value: blog * @endcode * - * The above example will skip processing the input property if the content_type - * source field equals "blog". + * The above example will skip further processing of the input property if + * the content_type source field equals "blog". * * Example usage with full configuration: * @code diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/StrReplace.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/StrReplace.php index 2b033bebc..dcff096b8 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/StrReplace.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate/process/StrReplace.php @@ -59,6 +59,15 @@ use Drupal\migrate\Row; * All the rules for * @link http://php.net/manual/function.str-replace.php str_replace @endlink * apply. This means that you can provide arrays as values. + * + * Multiple values can be matched like this: + * @code + * field_text: + * plugin: str_replace + * source: text + * search: ["AT", "CH", "DK"] + * replace: ["Austria", "Switzerland", "Denmark"] + * @endcode */ class StrReplace extends ProcessPluginBase { diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_fetcher/File.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_fetcher/File.php index dfa02d3bc..33e3cae5c 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_fetcher/File.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_fetcher/File.php @@ -34,7 +34,7 @@ class File extends DataFetcherPluginBase { * {@inheritdoc} */ public function getResponse($url) { - $response = file_get_contents($url); + $response = @file_get_contents($url); if ($response === FALSE) { throw new MigrateException('file parser plugin: could not retrieve data from ' . $url); } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_parser/Json.php b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_parser/Json.php index c1220bc5f..7ef4467d0 100755 --- a/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_parser/Json.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/src/Plugin/migrate_plus/data_parser/Json.php @@ -114,8 +114,8 @@ class Json extends DataParserPluginBase implements ContainerFactoryPluginInterfa foreach ($field_selectors as $field_selector) { if (is_array($field_data) && array_key_exists($field_selector, $field_data)) { $field_data = $field_data[$field_selector]; - } - else { + } + else { $field_data = ''; } } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration.fruit_terms.yml b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration.fruit_terms.yml new file mode 100644 index 000000000..7f606d127 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration.fruit_terms.yml @@ -0,0 +1,32 @@ +langcode: en +status: true +dependencies: { } +id: fruit_terms +label: Fruit Terms +class: null +field_plugin_method: null +cck_plugin_method: null +migration_tags: { } +migration_group: default +source: + plugin: embedded_data + data_rows: + - + name: Apple + - + name: Banana + - + name: Orange + ids: + name: + type: string + constants: + vocabulary: fruit +process: + name: name + vid: constants/vocabulary +destination: + plugin: entity:taxonomy_term +migration_dependencies: + required: { } + optional: { } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration_group.default.yml b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration_group.default.yml new file mode 100644 index 000000000..0f35a2189 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/config/install/migrate_plus.migration_group.default.yml @@ -0,0 +1,9 @@ +langcode: en +status: true +dependencies: { } +id: default +label: Default +description: '' +source_type: '' +module: null +shared_configuration: null diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/migrate_plus_test.info.yml b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/migrate_plus_test.info.yml new file mode 100644 index 000000000..070d6134a --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/modules/migrate_plus_test/migrate_plus_test.info.yml @@ -0,0 +1,14 @@ +type: module +name: Migrate Plus Test +description: 'Test module to test Migrate Plus.' +package: Testing +# core: 8.x +dependencies: + - drupal:migrate (>=8.3) + - migrate_plus:migrate_plus + +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' +core: '8.x' +project: 'migrate_plus' +datestamp: 1546879116 diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Functional/LoadTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Functional/LoadTest.php index c9aceda72..d042ecf30 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Functional/LoadTest.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Functional/LoadTest.php @@ -20,7 +20,10 @@ class LoadTest extends BrowserTestBase { public static $modules = [ 'migrate_plus', 'migrate_example', + 'migrate_example_setup', 'migrate_example_advanced', + 'migrate_example_advanced_setup', + 'migrate_json_example', ]; /** diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationConfigEntityTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationConfigEntityTest.php index 354da7628..d32d2d13d 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationConfigEntityTest.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationConfigEntityTest.php @@ -2,17 +2,26 @@ namespace Drupal\Tests\migrate_plus\Kernel; -use Drupal\KernelTests\KernelTestBase; +use Drupal\Component\Plugin\Exception\PluginNotFoundException; +use Drupal\migrate\MigrateExecutable; use Drupal\migrate_plus\Entity\Migration; +use Drupal\Tests\migrate\Kernel\MigrateTestBase; /** * Test migration config entity discovery. * * @group migrate_plus */ -class MigrationConfigEntityTest extends KernelTestBase { +class MigrationConfigEntityTest extends MigrateTestBase { - public static $modules = ['migrate', 'migrate_plus']; + public static $modules = [ + 'migrate', + 'migrate_plus', + 'migrate_plus_test', + 'taxonomy', + 'text', + 'system', + ]; /** * The plugin manager. @@ -27,6 +36,9 @@ class MigrationConfigEntityTest extends KernelTestBase { protected function setUp() { parent::setUp(); $this->pluginManager = \Drupal::service('plugin.manager.migration'); + $this->installConfig('migrate_plus'); + $this->installEntitySchema('taxonomy_term'); + $this->installSchema('system', ['key_value', 'key_value_expire']); } /** @@ -35,6 +47,7 @@ class MigrationConfigEntityTest extends KernelTestBase { public function testCacheInvalidation() { $config = Migration::create([ 'id' => 'test', + 'status' => TRUE, 'label' => 'Label A', 'migration_tags' => [], 'source' => [], @@ -57,4 +70,54 @@ class MigrationConfigEntityTest extends KernelTestBase { $this->assertSame('Label B', $this->pluginManager->getDefinition('test')['label']); } + /** + * Tests migration status. + */ + public function testMigrationStatus() { + $configs = [ + [ + 'id' => 'test_active', + 'status' => TRUE, + 'label' => 'Label Active', + 'migration_tags' => [], + 'source' => [], + 'destination' => [], + 'migration_dependencies' => [], + ], + [ + 'id' => 'test_inactive', + 'status' => FALSE, + 'label' => 'Label Inactive', + 'migration_tags' => [], + 'source' => [], + 'destination' => [], + 'migration_dependencies' => [], + ], + ]; + + foreach ($configs as $config) { + Migration::create($config)->save(); + } + + $definitions = $this->pluginManager->getDefinitions(); + $this->assertCount(1, $definitions); + $this->assertArrayHasKey('test_active', $definitions); + + $this->setExpectedException(PluginNotFoundException::class, 'The "test_inactive" plugin does not exist.'); + $this->pluginManager->getDefinition('test_inactive'); + } + + /** + * Tests migration from configuration. + */ + public function testImport() { + $this->installConfig('migrate_plus_test'); + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->pluginManager->createInstance('fruit_terms'); + $id_map = $migration->getIdMap(); + $executable = new MigrateExecutable($migration, $this); + $executable->import(); + $this->assertSame(3, $id_map->importedCount()); + } + } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationGroupTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationGroupTest.php index 53d3ad467..2909891c6 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationGroupTest.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/MigrationGroupTest.php @@ -69,10 +69,9 @@ class MigrationGroupTest extends KernelTestBase { $migration->save(); $expected_config = [ - 'migration_group' => $group_id, 'label' => 'Unaffected by the group', - 'migration_tags' => ['Drupal 7'], - 'source' => [ + 'getMigrationTags' => ['Drupal 7'], + 'getSourceConfiguration' => [ 'plugin' => 'empty', 'constants' => [ 'entity_type' => 'user', @@ -80,13 +79,13 @@ class MigrationGroupTest extends KernelTestBase { 'cardinality' => '3', ], ], - 'destination' => ['plugin' => 'field_storage_config'], + 'getDestinationConfiguration' => ['plugin' => 'field_storage_config'], ]; - /** @var \Drupal\migrate\Plugin\MigrationInterface $loaded_migration */ + /** @var \Drupal\migrate_plus\Plugin\MigrationInterface $loaded_migration */ $loaded_migration = $this->container->get('plugin.manager.migration') ->createInstance('specific_migration'); - foreach ($expected_config as $key => $expected_value) { - $actual_value = $loaded_migration->get($key); + foreach ($expected_config as $method => $expected_value) { + $actual_value = call_user_func([$loaded_migration, $method]); $this->assertEquals($expected_value, $actual_value); } } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityGenerateTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityGenerateTest.php index 3d46496de..3a5291817 100644 --- a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityGenerateTest.php +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityGenerateTest.php @@ -5,12 +5,15 @@ namespace Drupal\Tests\migrate_plus\Kernel\Plugin\migrate\process; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\KernelTests\KernelTestBase; use Drupal\migrate\MigrateExecutable; use Drupal\migrate\MigrateMessageInterface; use Drupal\node\Entity\NodeType; +use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; +use Drupal\Tests\field\Traits\EntityReferenceTestTrait; /** * Tests the migration plugin. @@ -107,6 +110,18 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED ); + // Create a non-reference field. + FieldStorageConfig::create([ + 'field_name' => 'field_integer', + 'type' => 'integer', + 'entity_type' => 'node', + ])->save(); + FieldConfig::create([ + 'field_name' => 'field_integer', + 'entity_type' => 'node', + 'bundle' => $this->bundle, + ])->save(); + $this->migrationPluginManager = \Drupal::service('plugin.manager.migration'); } @@ -156,14 +171,14 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa foreach ($valueToCheck as $key => $expectedValue) { if (empty($expectedValue)) { if (!$entity->{$property}->isEmpty()) { - $this->assertTrue($entity->{$property}[0]->entity->$key->isEmpty(), "Expected value is empty but field $property.$key is not empty."); + $this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty."); } else { - $this->assertTrue($entity->{$property}->isEmpty(), "FOOBAR Expected value is empty but field $property is not empty."); + $this->assertTrue($entity->{$property}->isEmpty(), "Expected value is empty but field $property is not empty."); } } elseif ($entity->{$property}->getValue()) { - $this->assertEquals($expectedValue, $entity->{$property}[$valueID]->entity->$key->value); + $this->assertEquals($expectedValue, $entity->get($property)->offsetGet($valueID)->entity->{$key}->value); } else { $this->fail("Expected value: $expectedValue does not exist in $property."); @@ -177,7 +192,7 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa foreach ($value as $key => $expectedValue) { if (empty($expectedValue)) { if (!$entity->{$property}->isEmpty()) { - $this->assertTrue($entity->{$property}[0]->entity->$key->isEmpty(), "Expected value is empty but field $property.$key is not empty."); + $this->assertTrue($entity->{$property}[0]->entity->{$key}->isEmpty(), "Expected value is empty but field $property.$key is not empty."); } else { $this->assertTrue($entity->{$property}->isEmpty(), "BINBAZ Expected value is empty but field $property is not empty."); @@ -203,6 +218,99 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa } } + /** + * Test lookup without a reference field. + */ + public function testNonReferenceField() { + $values = [ + 'name' => 'Apples', + 'vid' => $this->vocabulary, + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]; + $this->createTestData('taxonomy_term', $values); + + // Not enough context is provided for a non reference field, so error out. + $definition = [ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + [ + 'id' => 1, + 'title' => 'content item 1', + 'term' => 'Apples', + ], + ], + 'ids' => [ + 'id' => ['type' => 'integer'], + ], + ], + 'process' => [ + 'id' => 'id', + 'type' => [ + 'plugin' => 'default_value', + 'default_value' => $this->bundle, + ], + 'title' => 'title', + 'field_integer' => [ + 'plugin' => 'entity_generate', + 'source' => 'term', + ], + ], + 'destination' => [ + 'plugin' => 'entity:node', + ], + ]; + /** @var \Drupal\migrate\Plugin\Migration $migration */ + $migration = $this->migrationPluginManager->createStubMigration($definition); + $migrationExecutable = (new MigrateExecutable($migration, $this)); + $migrationExecutable->import(); + $this->assertEquals('Destination field type integer is not a recognized reference type.', $migration->getIdMap()->getMessageIterator()->fetch()->message); + $this->assertSame(1, $migration->getIdMap()->messageCount()); + + // Enough context is provided so this should work. + $definition = [ + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => [ + [ + 'id' => 1, + 'title' => 'content item 1', + 'term' => 'Apples', + ], + ], + 'ids' => [ + 'id' => ['type' => 'integer'], + ], + ], + 'process' => [ + 'id' => 'id', + 'type' => [ + 'plugin' => 'default_value', + 'default_value' => $this->bundle, + ], + 'title' => 'title', + 'field_integer' => [ + 'plugin' => 'entity_generate', + 'source' => 'term', + 'value_key' => 'name', + 'bundle_key' => 'vid', + 'bundle' => $this->vocabulary, + 'entity_type' => 'taxonomy_term', + ], + ], + 'destination' => [ + 'plugin' => 'entity:node', + ], + ]; + /** @var \Drupal\migrate\Plugin\Migration $migration */ + $migration = $this->migrationPluginManager->createStubMigration($definition); + $migrationExecutable = (new MigrateExecutable($migration, $this)); + $migrationExecutable->import(); + $this->assertEmpty($migration->getIdMap()->messageCount()); + $term = Term::load(1); + $this->assertEquals('Apples', $term->label()); + } + /** * Provides multiple migration definitions for "transform" test. */ @@ -775,6 +883,9 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa * The storage manager to create. * @param array $values * The values to use when creating the entity. + * + * @return string|int + * The entity identifier. */ private function createTestData($storageName, array $values) { /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ @@ -783,6 +894,7 @@ class EntityGenerateTest extends KernelTestBase implements MigrateMessageInterfa ->getStorage($storageName); $entity = $storage->create($values); $entity->save(); + return $entity->id(); } } diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php new file mode 100644 index 000000000..67d403cc6 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Kernel/Plugin/migrate/process/EntityLookupTest.php @@ -0,0 +1,76 @@ +installSchema('system', ['sequences']); + $this->installEntitySchema('user'); + } + + /** + * Lookup an entity without bundles on destination key. + * + * Using user entity as destination entity without bundles as example for + * testing. + * + * @covers ::transform + */ + public function testLookupEntityWithoutBundles() { + // Create a user. + $known_user = $this->createUser([], 'lucuma'); + // Setup test migration objects. + $migration_prophecy = $this->prophesize(MigrationInterface::class); + $migrate_destination_prophecy = $this->prophesize(MigrateDestinationInterface::class); + $migrate_destination_prophecy->getPluginId()->willReturn('user'); + $migrate_destination = $migrate_destination_prophecy->reveal(); + $migration_prophecy->getDestinationPlugin()->willReturn($migrate_destination); + $migration_prophecy->getProcess()->willReturn([]); + $migration = $migration_prophecy->reveal(); + $configuration = [ + 'entity_type' => 'user', + 'value_key' => 'name', + ]; + $plugin = \Drupal::service('plugin.manager.migrate.process') + ->createInstance('entity_lookup', $configuration, $migration); + $executable = $this->prophesize(MigrateExecutableInterface::class)->reveal(); + $row = new Row(); + // Check the known user is found. + $value = $plugin->transform('lucuma', $executable, $row, 'name'); + $this->assertSame($known_user->id(), $value); + // Check an unknown user is not found. + $value = $plugin->transform('orange', $executable, $row, 'name'); + $this->assertNull($value); + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/FileTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/FileTest.php new file mode 100644 index 000000000..0e09cf77a --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/FileTest.php @@ -0,0 +1,185 @@ + 'url', + 'data_fetcher_plugin' => 'file', + 'data_parser_plugin' => 'json', + 'item_selector' => 0, + 'fields' => [], + 'ids' => [ + 'id' => [ + 'type' => 'integer', + ], + ], + ]; + + /** + * The data fetcher plugin ID being tested. + * + * @var string + */ + private $dataFetcherPluginId = 'file'; + + /** + * The data fetcher plugin definition. + * + * @var array + */ + private $pluginDefinition = [ + 'id' => 'file', + 'title' => 'File', + ]; + + /** + * Test data to populate a file with. + * + * @var string + */ + private $testData = '[ + { + "id": 1, + "name": "Joe Bloggs" + } + ]'; + + /** + * Define virtual dir where we'll be creating files in/fetching files from. + * + * @var \org\bovigo\vfs\vfsStreamDirectory + */ + private $baseDir; + + /** + * Set up test environment. + */ + public function setUp() { + $this->baseDir = vfsStream::setup(self::BASE_DIRECTORY); + } + + /** + * Test fetching a valid file. + */ + public function testFetchFile() { + $file_name = 'file.json'; + $file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name])); + $migration_config = $this->specificMigrationConfig + [ + 'urls' => [$file_path], + ]; + + $plugin = new File( + $migration_config, + $this->dataFetcherPluginId, + $this->pluginDefinition + ); + + $tree = [ + $file_name => $this->testData, + ]; + + vfsStream::create($tree, $this->baseDir); + + $expected = json_decode($this->testData, TRUE); + $retrieved = json_decode($plugin->getResponseContent($file_path), TRUE); + + $this->assertEquals($expected, $retrieved); + } + + /** + * Test fetching multiple valid files. + */ + public function testFetchMultipleFiles() { + $number_of_files = 3; + $file_paths = []; + $file_names = []; + + for ($i = 0; $i < $number_of_files; $i++) { + $file_name = 'file_' . $i . '.json'; + $file_names[] = $file_name; + $file_paths[] = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name])); + } + + $migration_config = $this->specificMigrationConfig + [ + 'urls' => $file_paths, + ]; + + $plugin = new File( + $migration_config, + $this->dataFetcherPluginId, + $this->pluginDefinition + ); + + for ($i = 0; $i < $number_of_files; $i++) { + $file_name = $file_names[$i]; + $file_path = $file_paths[$i]; + + $tree = [ + $file_name => $this->testData, + ]; + + vfsStream::create($tree, $this->baseDir); + + $expected = json_decode($this->testData); + $retrieved = json_decode($plugin->getResponseContent($file_path)); + + $this->assertEquals($expected, $retrieved); + } + } + + /** + * Test trying to fetch an unreadable file results in exception. + */ + public function testFetchUnreadableFile() { + $file_name = 'file.json'; + $file_path = vfsStream::url(implode(DIRECTORY_SEPARATOR, [self::BASE_DIRECTORY, $file_name])); + $migration_config = $this->specificMigrationConfig + [ + 'urls' => [$file_path], + ]; + + $plugin = new File( + $migration_config, + $this->dataFetcherPluginId, + $this->pluginDefinition + ); + + // Create an unreadable file. + vfsStream::newFile($file_name, 0300) + ->withContent($this->testData) + ->at($this->baseDir); + + // Trigger exception trying to read the non-readable file. + $this->setExpectedException(MigrateException::class, 'file parser plugin: could not retrieve data from vfs://migration_data/file.json'); + $plugin->getResponseContent($file_path); + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/HttpTest.php b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/HttpTest.php new file mode 100644 index 000000000..cbaec2d93 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_plus/tests/src/Unit/data_fetcher/HttpTest.php @@ -0,0 +1,242 @@ + 'url', + 'urls' => ['http://example.org/http_fetcher_test'], + 'data_fetcher_plugin' => 'http', + 'data_parser_plugin' => 'json', + 'item_selector' => 0, + 'authentication' => [ + 'plugin' => 'basic', + 'username' => 'testing', + 'password' => 'password', + ], + 'fields' => [], + 'ids' => [ + 'id' => [ + 'type' => 'integer', + ], + ], + ]; + + /** + * The data fetcher plugin ID being tested. + * + * @var string + */ + private $dataFetcherPluginId = 'http'; + + /** + * The data fetcher plugin definition. + * + * @var array + */ + private $pluginDefinition = [ + 'id' => 'http', + 'title' => 'HTTP', + ]; + + /** + * Test data to validate an HTTP response against. + * + * @var string + */ + private $testData = ' + { + "id": 1, + "name": "Joe Bloggs" + } + '; + + /** + * Mocked up Basic authentication plugin. + * + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $basicAuthenticator = NULL; + + /** + * Set up test environment. + */ + public function setUp() { + // Mock up a Basic authentication plugin that will be used in requests. + $basic_authenticator = $this->getMockBuilder(Basic::class) + ->disableOriginalConstructor() + ->getMock(); + + $basic_authenticator->method('getAuthenticationOptions') + ->will($this->returnValue([ + 'auth' => [ + 'username', + 'password', + ], + ])); + + $this->basicAuthenticator = $basic_authenticator; + } + + /** + * Test 'http' data fetcher (with auth) returns an expected response. + */ + public function testFetchHttpWithAuth() { + $migration_config = $this->migrationConfiguration + $this->specificMigrationConfig; + + $plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition); + $plugin->mockHttpClient([[200, 'application/json', $this->testData]], $this->basicAuthenticator); + + // The Guzzle mock returns an instance of StreamInterface. + // http://docs.guzzlephp.org/en/latest/psr7.html + $stream = $plugin->getResponseContent($migration_config['urls'][0]); + + $body = json_decode((string) $stream, TRUE); + + // Compare what we got back from the parser to what we expected to get. + $expected = json_decode($this->testData, TRUE); + $this->assertArrayEquals($expected, $body); + } + + /** + * Test 'http' data fetcher (without auth) returns an expected response. + */ + public function testFetchHttpNoAuth() { + $migration_config = $this->migrationConfiguration + $this->specificMigrationConfig; + unset($migration_config['authentication']); + + $plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition); + $plugin->mockHttpClient([[200, 'application/json', $this->testData]], NULL); + + $stream = $plugin->getResponseContent($migration_config['urls'][0]); + + $body = json_decode((string) $stream, TRUE); + + $expected = json_decode($this->testData, TRUE); + $this->assertArrayEquals($expected, $body); + } + + /** + * Test 'http' data fetcher (with auth) dies as expected when auth fails. + */ + public function testFetchHttpAuthFailure() { + $migration_config = $this->migrationConfiguration + $this->specificMigrationConfig; + + $plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition); + $plugin->mockHttpClient([[403, 'text/html', 'Forbidden']], $this->basicAuthenticator); + + $this->setExpectedException(MigrateException::class, 'Error message: Client error: `GET http://example.org/http_fetcher_test` resulted in a `403 Forbidden'); + $plugin->getResponseContent($migration_config['urls'][0]); + } + + /** + * Test 'http' data fetcher (with auth) dies as expected when server down. + */ + public function testFetchHttp500Error() { + $migration_config = $this->migrationConfiguration + $this->specificMigrationConfig; + + $plugin = new TestHttp($migration_config, $this->dataFetcherPluginId, $this->pluginDefinition); + $plugin->mockHttpClient([[500, 'text/html', 'Internal Server Error']], $this->basicAuthenticator); + + $this->setExpectedException(MigrateException::class, 'GET http://example.org/http_fetcher_test` resulted in a `500 Internal Server Error'); + $plugin->getResponseContent($migration_config['urls'][0]); + } + +} + +/** + * Test class to mock an HTTP request. + */ +class TestHttp extends Http { + + /** + * Mocked authenticator plugin. + * + * @var \PHPUnit_Framework_MockObject_MockObject + */ + public $authenticator = NULL; + + /** + * Mock the HttpClient, so we can control the request/response(s) etc. + * + * @param array $responses + * An array of responses (arrays), with each consisting of properties, + * ordered: response code, content-type and response body. + * @param \PHPUnit_Framework_MockObject_MockObject $authenticator + * Mocked authenticator plugin. + */ + public function mockHttpClient(array $responses, \PHPUnit_Framework_MockObject_MockObject $authenticator = NULL) { + // Set mocked authentication plugin to be used for the request auth plugin. + $this->authenticator = $authenticator; + + $handler_responses = []; + foreach ($responses as $response) { + $handler_responses[] = new Response( + $response[0], + ['Content-Type' => $response[1]], + $response[2] + ); + } + + $mock = new MockHandler($handler_responses); + $handler = HandlerStack::create($mock); + + $this->httpClient = new Client(['handler' => $handler]); + } + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition) { + // Skip calling the Http() constructor (that sets the httpClient instance + // variable via \Drupal which we don't want to do), but keep the call to its + // parent class constructor. @see https://bugs.php.net/bug.php?id=42016 + DataFetcherPluginBase::__construct($configuration, $plugin_id, $plugin_definition); + + // This is what the parent class is doing, that we need to override. + $this->httpClient = NULL; + } + + /** + * Override the parent::getAuthenticationPlugin() + * + * So we can mock the authentication plugin. + * + * @return \PHPUnit_Framework_MockObject_MockObject + * A mocked authentication plugin. + */ + public function getAuthenticationPlugin() { + if (!isset($this->authenticationPlugin)) { + $this->authenticationPlugin = $this->authenticator; + } + + return $this->authenticationPlugin; + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/composer.json b/sites/all/modules/contrib/migrate/migrate_tools/composer.json index d5c8fca8f..dfd9dbc84 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/composer.json +++ b/sites/all/modules/contrib/migrate/migrate_tools/composer.json @@ -8,13 +8,14 @@ "irc": "irc://irc.freenode.org/drupal-migrate", "source": "http://cgit.drupalcode.org/migrate_tools" }, - "license": "GPL-2.0+", + "license": "GPL-2.0-or-later", "require": { "drupal/migrate_plus": "^4" }, "require-dev": { - "drupal/coder": "^8", - "drupal/migrate_source_csv": "^2.2" + "drupal/migrate_plus": "4.x-dev", + "drupal/migrate_source_csv": "^2.2", + "drush/drush": "^9" }, "minimum-stability": "dev", "extra": { diff --git a/sites/all/modules/contrib/migrate/migrate_tools/drupalci.yml b/sites/all/modules/contrib/migrate/migrate_tools/drupalci.yml new file mode 100644 index 000000000..116512f40 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/drupalci.yml @@ -0,0 +1,25 @@ +# Learn to make one for your own drupal.org project: +# https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing +build: + assessment: + validate_codebase: + phplint: + container_composer: + phpcs: + # phpcs will use core's specified version of Coder. + sniff-all-files: true + halt-on-fail: true + testing: + # run_tests task is executed several times in order of performance speeds. + # halt-on-fail can be set on the run_tests tasks in order to fail fast. + # suppress-deprecations is false in order to be alerted to usages of + # deprecated code. + run_tests.standard: + types: 'Simpletest,PHPUnit-Unit,PHPUnit-Kernel,PHPUnit-Functional' + testgroups: '--all' + suppress-deprecations: false + run_tests.js: + types: 'PHPUnit-FunctionalJavascript' + testgroups: '--all' + suppress-deprecations: false + nightwatchjs: { } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.drush.inc b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.drush.inc index 73ddec530..16ff79b1f 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.drush.inc +++ b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.drush.inc @@ -5,13 +5,13 @@ * Command-line tools to aid performing and developing migrations. */ -use Drupal\Component\Utility\Unicode; use Drupal\migrate\Exception\RequirementsException; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\RequirementsInterface; use Drupal\migrate_plus\Entity\MigrationGroup; use Drupal\migrate_tools\DrushLogMigrateMessage; use Drupal\migrate_tools\MigrateExecutable; +use Drupal\migrate_tools\MigrateTools; /** * Implements hook_drush_command(). @@ -47,6 +47,7 @@ function migrate_tools_drush_command() { 'limit' => 'Limit on the number of items to process in each migration', 'feedback' => 'Frequency of progress messages, in items processed', 'idlist' => 'Comma-separated list of IDs to import', + 'idlist-delimiter' => 'The delimiter for records, defaults to \':\'', 'update' => ' In addition to processing unprocessed items from the source, update previously-imported items with the current data', 'force' => 'Force an operation to run, even if all dependencies are not satisfied', 'execute-dependencies' => 'Execute all dependent migrations first.', @@ -62,6 +63,7 @@ function migrate_tools_drush_command() { 'migrate-import beer_term,beer_node' => 'Import new terms and nodes', 'migrate-import beer_user --limit=2' => 'Import no more than 2 users', 'migrate-import beer_user --idlist=5' => 'Import the user record with source ID 5', + 'migrate-import beer_node_revision --idlist=1:2,2:3,3:5' => "Import the node revision record with source IDs [1,2], [2,3], and [3,5]", ], 'drupal dependencies' => ['migrate_tools'], 'aliases' => ['mi', 'mim'], @@ -74,9 +76,11 @@ function migrate_tools_drush_command() { 'group' => 'A comma-separated list of migration groups to rollback', 'tag' => 'ID of the migration tag to rollback', 'feedback' => 'Frequency of progress messages, in items processed', + 'idlist' => 'Comma-separated list of IDs to import', ], 'arguments' => [ 'migration' => 'Name of migration(s) to rollback. Delimit multiple using commas.', + 'idlist' => 'Comma-separated list of IDs to import', ], 'examples' => [ 'migrate-rollback --all' => 'Perform all migrations', @@ -84,6 +88,7 @@ function migrate_tools_drush_command() { 'migrate-rollback --tag=user' => 'Rollback all migrations with the user tag', 'migrate-rollback --group=beer --tag=user' => 'Rollback all migrations in the beer group and with the user tag', 'migrate-rollback beer_term,beer_node' => 'Rollback imported terms and nodes', + 'migrate-rollback beer_user --idlist=5' => 'Rollback imported user record with source ID 5', ], 'drupal dependencies' => ['migrate_tools'], 'aliases' => ['mr'], @@ -296,7 +301,16 @@ function _drush_migrate_tools_execute_migration(MigrationInterface $migration, $ $migration->set('requirements', []); } if (!empty($options['update'])) { - $migration->getIdMap()->prepareUpdate(); + if (empty($options['idlist'])) { + $migration->getIdMap()->prepareUpdate(); + } + else { + $source_id_values_list = MigrateTools::buildIdList($options); + $keys = array_keys($migration->getSourcePlugin()->getIds()); + foreach ($source_id_values_list as $source_id_values) { + $migration->getIdMap()->setUpdate(array_combine($keys, $source_id_values)); + } + } } $executable = new MigrateExecutable($migration, $log, $options); // Function drush_op() provides --simulate support. @@ -326,8 +340,10 @@ function drush_migrate_tools_migrate_rollback($migration_names = '') { return; } - if (drush_get_option('feedback')) { - $options['feedback'] = drush_get_option('feedback'); + foreach (['feedback', 'idlist'] as $option) { + if (drush_get_option($option)) { + $options[$option] = drush_get_option($option); + } } $log = new DrushLogMigrateMessage(); @@ -365,12 +381,15 @@ function drush_migrate_tools_migrate_stop($migration_id = '') { case MigrationInterface::STATUS_IDLE: drush_log(dt('Migration @id is idle', ['@id' => $migration_id]), 'warning'); break; + case MigrationInterface::STATUS_DISABLED: drush_log(dt('Migration @id is disabled', ['@id' => $migration_id]), 'warning'); break; + case MigrationInterface::STATUS_STOPPING: drush_log(dt('Migration @id is already stopping', ['@id' => $migration_id]), 'warning'); break; + default: $migration->interruptMigration(MigrationInterface::RESULT_STOPPED); drush_log(dt('Migration @id requested to stop', ['@id' => $migration_id]), 'success'); @@ -505,9 +524,9 @@ function drush_migrate_tools_migration_list($migration_ids = '') { } else { // Get the requested migrations. - $migration_ids = explode(',', Unicode::strtolower($migration_ids)); + $migration_ids = explode(',', mb_strtolower($migration_ids)); foreach ($plugins as $id => $migration) { - if (in_array(Unicode::strtolower($id), $migration_ids)) { + if (in_array(mb_strtolower($id), $migration_ids)) { $matched_migrations[$id] = $migration; } } @@ -539,7 +558,7 @@ function drush_migrate_tools_migration_list($migration_ids = '') { $configured_values = (array) $migration->get($property); $configured_id = (in_array($search_value, $configured_values)) ? $search_value : 'default'; if (empty($search_value) || $search_value == $configured_id) { - if (empty($migration_ids) || in_array(Unicode::strtolower($id), $migration_ids)) { + if (empty($migration_ids) || in_array(mb_strtolower($id), $migration_ids)) { $filtered_migrations[$id] = $migration; } } @@ -553,7 +572,7 @@ function drush_migrate_tools_migration_list($migration_ids = '') { // Sort the matched migrations by group. if (!empty($matched_migrations)) { foreach ($matched_migrations as $id => $migration) { - $configured_group_id = empty($migration->get('migration_group')) ? 'default' : $migration->get('migration_group'); + $configured_group_id = empty($migration->migration_group) ? 'default' : $migration->migration_group; $migrations[$configured_group_id][$id] = $migration; } } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.info.yml b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.info.yml index e3cb18da2..104563b8e 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.info.yml @@ -3,14 +3,15 @@ name: Migrate Tools description: 'Tools to assist in developing and running migrations.' package: Migration # core: 8.x +configure: entity.migration_group.list dependencies: - drupal:migrate (>=8.3) - migrate_plus:migrate_plus test_dependencies: - migrate_source_csv:migrate_source_csv (>=8.x-2.2) -# Information added by Drupal.org packaging script on 2018-08-27 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_tools' -datestamp: 1535380087 +datestamp: 1546879109 diff --git a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.routing.yml b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.routing.yml index 08f43e5b5..ca04f9663 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.routing.yml +++ b/sites/all/modules/contrib/migrate/migrate_tools/migrate_tools.routing.yml @@ -157,7 +157,7 @@ migrate_tools.messages: path: '/admin/structure/migrate/manage/{migration_group}/migrations/{migration}/messages' defaults: _controller: '\Drupal\migrate_tools\Controller\MessageController::overview' - _title: 'Messages' + _title_callback: '\Drupal\migrate_tools\Controller\MessageController::title' _migrate_group: true requirements: _permission: 'administer migrations' diff --git a/sites/all/modules/contrib/migrate/migrate_tools/phpcs.xml b/sites/all/modules/contrib/migrate/migrate_tools/phpcs.xml deleted file mode 100644 index a18054efb..000000000 --- a/sites/all/modules/contrib/migrate/migrate_tools/phpcs.xml +++ /dev/null @@ -1,207 +0,0 @@ - - - Drupal 8 coding standards - - . - - - - ./vendor/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - - 0 - - - 0 - - - - 0 - - - 0 - - - 0 - - - - - - - - 0 - - - - - - - - - - - - - - - - diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Commands/MigrateToolsCommands.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Commands/MigrateToolsCommands.php index 976b3c684..dbae1fb20 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Commands/MigrateToolsCommands.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Commands/MigrateToolsCommands.php @@ -3,7 +3,6 @@ namespace Drupal\migrate_tools\Commands; use Consolidation\OutputFormatters\StructuredData\RowsOfFields; -use Drupal\Component\Utility\Unicode; use Drupal\Core\Datetime\DateFormatter; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\KeyValueStore\KeyValueFactoryInterface; @@ -12,7 +11,9 @@ use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\MigrationPluginManager; use Drupal\migrate\Plugin\RequirementsInterface; use Drupal\migrate_tools\Drush9LogMigrateMessage; +use Drupal\migrate_tools\IdMapFilter; use Drupal\migrate_tools\MigrateExecutable; +use Drupal\migrate_tools\MigrateTools; use Drush\Commands\DrushCommands; /** @@ -89,6 +90,8 @@ class MigrateToolsCommands extends DrushCommands { * @option tag Name of the migration tag to list * @option names-only Only return names, not all the details (faster) * + * @default $options [] + * * @usage migrate:status * Retrieve status for all migrations * @usage migrate:status --group=beer @@ -118,7 +121,12 @@ class MigrateToolsCommands extends DrushCommands { * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields * Migrations status formatted as table. */ - public function status($migration_names = '', array $options = ['group' => NULL, 'tag' => NULL, 'names-only' => NULL]) { + public function status($migration_names = '', array $options = []) { + $options += [ + 'group' => NULL, + 'tag' => NULL, + 'names-only' => NULL, + ]; $names_only = $options['names-only']; $migrations = $this->migrationsList($migration_names, $options); @@ -231,12 +239,15 @@ class MigrateToolsCommands extends DrushCommands { * @option limit Limit on the number of items to process in each migration * @option feedback Frequency of progress messages, in items processed * @option idlist Comma-separated list of IDs to import + * @option idlist-delimiter The delimiter for records, defaults to ':' * @option update In addition to processing unprocessed items from the * source, update previously-imported items with the current data * @option force Force an operation to run, even if all dependencies are not * satisfied * @option execute-dependencies Execute all dependent migrations first. * + * @default $options [] + * * @usage migrate:import --all * Perform all migrations * @usage migrate:import --group=beer @@ -251,6 +262,8 @@ class MigrateToolsCommands extends DrushCommands { * Import no more than 2 users * @usage migrate:import beer_user --idlist=5 * Import the user record with source ID 5 + * @usage migrate:import beer_node_revision --idlist=1:2,2:3,3:5 + * Import the node revision record with source IDs [1,2], [2,3], and [3,5] * * @validate-module-enabled migrate_tools * @@ -259,7 +272,19 @@ class MigrateToolsCommands extends DrushCommands { * @throws \Exception * If there are not enough parameters to the command. */ - public function import($migration_names = '', array $options = ['all' => NULL, 'group' => NULL, 'tag' => NULL, 'limit' => NULL, 'feedback' => NULL, 'idlist' => NULL, 'update' => NULL, 'force' => NULL, 'execute-dependencies' => NULL]) { + public function import($migration_names = '', array $options = []) { + $options += [ + 'all' => NULL, + 'group' => NULL, + 'tag' => NULL, + 'limit' => NULL, + 'feedback' => NULL, + 'idlist' => NULL, + 'idlist-delimiter' => ':', + 'update' => NULL, + 'force' => NULL, + 'execute-dependencies' => NULL, + ]; $group_names = $options['group']; $tag_names = $options['tag']; $all = $options['all']; @@ -268,7 +293,16 @@ class MigrateToolsCommands extends DrushCommands { throw new \Exception(dt('You must specify --all, --group, --tag or one or more migration names separated by commas')); } - foreach (['limit', 'feedback', 'idlist', 'update', 'force', 'execute-dependencies'] as $option) { + $possible_options = [ + 'limit', + 'feedback', + 'idlist', + 'idlist-delimiter', + 'update', + 'force', + 'execute-dependencies', + ]; + foreach ($possible_options as $option) { if ($options[$option]) { $additional_options[$option] = $options[$option]; } @@ -303,6 +337,11 @@ class MigrateToolsCommands extends DrushCommands { * @option group A comma-separated list of migration groups to rollback * @option tag ID of the migration tag to rollback * @option feedback Frequency of progress messages, in items processed + * @option feedback Frequency of progress messages, in items processed + * @option idlist Comma-separated list of IDs to rollback + * @option idlist-delimiter The delimiter for records, defaults to ':' + * + * @default $options [] * * @usage migrate:rollback --all * Perform all migrations @@ -314,6 +353,8 @@ class MigrateToolsCommands extends DrushCommands { * Rollback all migrations in the beer group and with the user tag * @usage migrate:rollback beer_term,beer_node * Rollback imported terms and nodes + * @usage migrate:rollback beer_user --idlist=5 + * Rollback imported user record with source ID 5 * @validate-module-enabled migrate_tools * * @aliases mr, migrate-rollback @@ -321,7 +362,15 @@ class MigrateToolsCommands extends DrushCommands { * @throws \Exception * If there are not enough parameters to the command. */ - public function rollback($migration_names = '', array $options = ['all' => NULL, 'group' => NULL, 'tag' => NULL, 'feedback' => NULL]) { + public function rollback($migration_names = '', array $options = []) { + $options += [ + 'all' => NULL, + 'group' => NULL, + 'tag' => NULL, + 'feedback' => NULL, + 'idlist' => NULL, + 'idlist-delimiter' => ':', + ]; $group_names = $options['group']; $tag_names = $options['tag']; $all = $options['all']; @@ -330,8 +379,10 @@ class MigrateToolsCommands extends DrushCommands { throw new \Exception(dt('You must specify --all, --group, --tag, or one or more migration names separated by commas')); } - if ($options['feedback']) { - $additional_options['feedback'] = $options['feedback']; + foreach (['feedback', 'idlist', 'idlist-delimiter'] as $option) { + if ($options[$option]) { + $additional_options[$option] = $options[$option]; + } } $migrations = $this->migrationsList($migration_names, $options); @@ -456,6 +507,10 @@ class MigrateToolsCommands extends DrushCommands { * @command migrate:messages * * @option csv Export messages as a CSV + * @option idlist Comma-separated list of IDs to import + * @option idlist-delimiter The delimiter for records, defaults to ':' + * + * @default $options [] * * @usage migrate:messages MyNode * Show all messages for the MyNode migration @@ -473,7 +528,12 @@ class MigrateToolsCommands extends DrushCommands { * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields * Source fields of the given migration formatted as a table. */ - public function messages($migration_id, array $options = ['csv' => NULL]) { + public function messages($migration_id, array $options = []) { + $options += [ + 'csv' => NULL, + 'idlist' => NULL, + 'idlist-delimiter' => ':', + ]; /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ $migration = $this->migrationPluginManager->createInstance( $migration_id @@ -484,8 +544,8 @@ class MigrateToolsCommands extends DrushCommands { ); return NULL; } - - $map = $migration->getIdMap(); + $id_list = MigrateTools::buildIdList($options); + $map = new IdMapFilter($migration->getIdMap(), $id_list); $table = []; foreach ($map->getMessageIterator() as $row) { unset($row->msgid); @@ -561,6 +621,8 @@ class MigrateToolsCommands extends DrushCommands { * @param array $options * Command options. * + * @default $options [] + * * @return \Drupal\migrate\Plugin\MigrationInterface[][] * An array keyed by migration group, each value containing an array of * migrations or an empty array if no migrations match the input criteria. @@ -586,9 +648,9 @@ class MigrateToolsCommands extends DrushCommands { } else { // Get the requested migrations. - $migration_ids = explode(',', Unicode::strtolower($migration_ids)); + $migration_ids = explode(',', mb_strtolower($migration_ids)); foreach ($plugins as $id => $migration) { - if (in_array(Unicode::strtolower($id), $migration_ids)) { + if (in_array(mb_strtolower($id), $migration_ids)) { $matched_migrations[$id] = $migration; } } @@ -624,7 +686,7 @@ class MigrateToolsCommands extends DrushCommands { )) ? $search_value : 'default'; if (empty($search_value) || $search_value == $configured_id) { if (empty($migration_ids) || in_array( - Unicode::strtolower($id), + mb_strtolower($id), $migration_ids )) { $filtered_migrations[$id] = $migration; @@ -640,7 +702,7 @@ class MigrateToolsCommands extends DrushCommands { // Sort the matched migrations by group. if (!empty($matched_migrations)) { foreach ($matched_migrations as $id => $migration) { - $configured_group_id = empty($migration->get('migration_group')) ? 'default' : $migration->get('migration_group'); + $configured_group_id = empty($migration->migration_group) ? 'default' : $migration->migration_group; $migrations[$configured_group_id][$id] = $migration; } } @@ -660,6 +722,8 @@ class MigrateToolsCommands extends DrushCommands { * @param array $options * Additional options of the command. * + * @default $options [] + * * @throws \Exception * If some migrations failed during execution. */ @@ -686,7 +750,16 @@ class MigrateToolsCommands extends DrushCommands { $migration->set('requirements', []); } if (!empty($options['update'])) { - $migration->getIdMap()->prepareUpdate(); + if (empty($options['idlist'])) { + $migration->getIdMap()->prepareUpdate(); + } + else { + $source_id_values_list = MigrateTools::buildIdList($options); + $keys = array_keys($migration->getSourcePlugin()->getIds()); + foreach ($source_id_values_list as $source_id_values) { + $migration->getIdMap()->setUpdate(array_combine($keys, $source_id_values)); + } + } } $executable = new MigrateExecutable($migration, $this->getMigrateMessage(), $options); // drush_op() provides --simulate support. diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MessageController.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MessageController.php index fbd347c96..a08dba7c3 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MessageController.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MessageController.php @@ -141,4 +141,22 @@ class MessageController extends ControllerBase { return $build; } + /** + * Get the title of the page. + * + * @param \Drupal\migrate_plus\Entity\MigrationGroupInterface $migration_group + * The migration group. + * @param \Drupal\migrate_plus\Entity\MigrationInterface $migration + * The $migration. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup + * The translated title. + */ + public function title(MigrationGroupInterface $migration_group, MigratePlusMigrationInterface $migration) { + return $this->t( + 'Messages of %migration', + ['%migration' => $migration->label()] + ); + } + } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationController.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationController.php index 15c90795c..664c41043 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationController.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationController.php @@ -211,6 +211,9 @@ class MigrationController extends ControllerBase implements ContainerInjectionIn $row = []; $row[] = ['data' => Html::escape($destination_id)]; if (isset($process_line[0]['source'])) { + if (is_array($process_line[0]['source'])) { + $process_line[0]['source'] = implode(', ', $process_line[0]['source']); + } $row[] = ['data' => Xss::filterAdmin($process_line[0]['source'])]; } else { diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationListBuilder.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationListBuilder.php index d078764ce..94951b0e9 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationListBuilder.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Controller/MigrationListBuilder.php @@ -142,7 +142,7 @@ class MigrationListBuilder extends ConfigEntityListBuilder implements EntityHand try { /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ $migration = $this->migrationPluginManager->createInstance($migration_entity->id()); - $migration_group = $migration->get('migration_group'); + $migration_group = $migration_entity->get('migration_group'); if (!$migration_group) { $migration_group = 'default'; } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationDeleteForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationDeleteForm.php index 593046dba..d3ee346e3 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationDeleteForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationDeleteForm.php @@ -60,7 +60,7 @@ class MigrationDeleteForm extends EntityConfirmFormBase { $this->entity->delete(); // Set a message that the entity was deleted. - drupal_set_message(t('Migration %label was deleted.', [ + $this->messenger()->addStatus($this->t('Migration %label was deleted.', [ '%label' => $this->entity->label(), ])); diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationEditForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationEditForm.php index 214d0ae35..15740c60b 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationEditForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationEditForm.php @@ -29,7 +29,7 @@ class MigrationEditForm extends MigrationFormBase { */ public function actions(array $form, FormStateInterface $form_state) { $actions = parent::actions($form, $form_state); - $actions['submit']['#value'] = t('Update Migration'); + $actions['submit']['#value'] = $this->t('Update Migration'); return $actions; } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationExecuteForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationExecuteForm.php index d05b736eb..352894649 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationExecuteForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationExecuteForm.php @@ -67,24 +67,24 @@ class MigrationExecuteForm extends FormBase { // Build the 'Update options' form. $form = [ '#type' => 'fieldset', - '#title' => t('Operations'), + '#title' => $this->t('Operations'), ]; $options = [ - 'import' => t('Import'), - 'rollback' => t('Rollback'), - 'stop' => t('Stop'), - 'reset' => t('Reset'), + 'import' => $this->t('Import'), + 'rollback' => $this->t('Rollback'), + 'stop' => $this->t('Stop'), + 'reset' => $this->t('Reset'), ]; $form['operation'] = [ '#type' => 'select', - '#title' => t('Choose an operation to run'), + '#title' => $this->t('Choose an operation to run'), '#options' => $options, '#default_value' => 'import', '#required' => TRUE, ]; $form['submit'] = [ '#type' => 'submit', - '#value' => t('Execute'), + '#value' => $this->t('Execute'), ]; $definitions = []; $definitions[] = $this->t('Import: Imports all previously unprocessed records from the source, plus any records marked for update, into destination Drupal objects.'); @@ -100,26 +100,31 @@ class MigrationExecuteForm extends FormBase { $form['options'] = [ '#type' => 'fieldset', - '#title' => t('Options'), + '#title' => $this->t('Options'), '#collapsible' => TRUE, '#collapsed' => TRUE, ]; $form['options']['update'] = [ '#type' => 'checkbox', - '#title' => t('Update'), - '#description' => t('Check this box to update all previously-imported content + '#title' => $this->t('Update'), + '#description' => $this->t('Check this box to update all previously-imported content in addition to importing new content. Leave unchecked to only import new content'), ]; $form['options']['force'] = [ '#type' => 'checkbox', - '#title' => t('Ignore dependencies'), - '#description' => t('Check this box to ignore dependencies when running imports + '#title' => $this->t('Ignore dependencies'), + '#description' => $this->t('Check this box to ignore dependencies when running imports - all tasks will run whether or not their dependent tasks have completed.'), ]; - // @TODO: Limit is not working. Perhaps because of batch? See - // https://www.drupal.org/project/migrate_tools/issues/2924298. + $form['options']['limit'] = [ + '#type' => 'textfield', + '#title' => $this->t('Limit to:'), + '#size' => 10, + '#description' => $this->t('Set a limit of how many items to process for each migration task.'), + ]; + return $form; } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationFormBase.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationFormBase.php index 3ba6a0f55..f568e7e0e 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationFormBase.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationFormBase.php @@ -97,7 +97,7 @@ class MigrationFormBase extends EntityForm { foreach ($groups as $group) { $group_options[$group->id()] = $group->label(); } - if (!$migration->get('migration_group') && isset($group_options['default'])) { + if (!$migration->migration_group && isset($group_options['default'])) { $migration->set('migration_group', 'default'); } @@ -105,7 +105,7 @@ class MigrationFormBase extends EntityForm { '#type' => 'select', '#title' => $this->t('Migration Group'), '#empty_value' => '', - '#default_value' => $migration->get('migration_group'), + '#default_value' => $migration->migration_group, '#options' => $group_options, '#description' => $this->t('Assign this migration to an existing group.'), ]; @@ -150,7 +150,7 @@ class MigrationFormBase extends EntityForm { * An array of supported actions for the current entity form. */ protected function actions(array $form, FormStateInterface $form_state) { - // Get the basic actins from the base class. + // Get the basic actions from the base class. $actions = parent::actions($form, $form_state); // Change the submit button text. @@ -169,11 +169,11 @@ class MigrationFormBase extends EntityForm { if ($status == SAVED_UPDATED) { // If we edited an existing entity... - drupal_set_message($this->t('Migration %label has been updated.', ['%label' => $migration->label()])); + $this->messenger()->addStatus($this->t('Migration %label has been updated.', ['%label' => $migration->label()])); } else { // If we created a new entity... - drupal_set_message($this->t('Migration %label has been added.', ['%label' => $migration->label()])); + $this->messenger()->addStatus($this->t('Migration %label has been added.', ['%label' => $migration->label()])); } // Redirect the user back to the listing route after the save operation. diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupDeleteForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupDeleteForm.php index 98d1baf70..52f1c1214 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupDeleteForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupDeleteForm.php @@ -60,7 +60,7 @@ class MigrationGroupDeleteForm extends EntityConfirmFormBase { $this->entity->delete(); // Set a message that the entity was deleted. - drupal_set_message(t('Migration group %label was deleted.', [ + $this->messenger()->addStatus($this->t('Migration group %label was deleted.', [ '%label' => $this->entity->label(), ])); diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupEditForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupEditForm.php index 1eedc2d26..551950a01 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupEditForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupEditForm.php @@ -28,7 +28,7 @@ class MigrationGroupEditForm extends MigrationGroupFormBase { */ public function actions(array $form, FormStateInterface $form_state) { $actions = parent::actions($form, $form_state); - $actions['submit']['#value'] = t('Update Migration Group'); + $actions['submit']['#value'] = $this->t('Update Migration Group'); return $actions; } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupFormBase.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupFormBase.php index d60944a75..5fc8b289b 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupFormBase.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/MigrationGroupFormBase.php @@ -158,11 +158,11 @@ class MigrationGroupFormBase extends EntityForm { if ($status == SAVED_UPDATED) { // If we edited an existing entity... - drupal_set_message($this->t('Migration group %label has been updated.', ['%label' => $migration_group->label()])); + $this->messenger()->addStatus($this->t('Migration group %label has been updated.', ['%label' => $migration_group->label()])); } else { // If we created a new entity... - drupal_set_message($this->t('Migration group %label has been added.', ['%label' => $migration_group->label()])); + $this->messenger()->addStatus($this->t('Migration group %label has been added.', ['%label' => $migration_group->label()])); } // Redirect the user back to the listing route after the save operation. diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/SourceCsvForm.php b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/SourceCsvForm.php index 40b5dc3ba..bbf2a583f 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/Form/SourceCsvForm.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/Form/SourceCsvForm.php @@ -25,11 +25,11 @@ use Drupal\migrate\Plugin\MigrationPluginManagerInterface; * migration yml itself. * * Changes made to the column configuration, or aliases, are stored in the - * private migrate_toools private store keyed by the migration plugin id. The + * private migrate_tools private store keyed by the migration plugin id. The * data stored for each migrations consists of two arrays, the 'original' column * aliases and the 'updated' column aliases. * - * An addtional list of all changed migration id is kept in the store, in the + * An additional list of all changed migration id is kept in the store, in the * key 'migrations_changed' * * Private Store Usage: @@ -38,7 +38,6 @@ use Drupal\migrate\Plugin\MigrationPluginManagerInterface; * [migration_id]: The original and changed values for this column assignments * * Format of the source configuration saved in the store. - * @code * migration_id * original * column_index1 @@ -50,22 +49,19 @@ use Drupal\migrate\Plugin\MigrationPluginManagerInterface; * property 2 => label 2 * column_index2 * property 1 => label 1 - * @endcode * - * Example source configuration. - * @code + * Example source configuration: * custom_migration - * original - * 2 - * title => title - * 3 - * body => foo - * updated - * 8 - * title => new_title - * 9 - * body => new_body - * @endcode + * original + * 2 + * title => title + * 3 + * body => foo + * updated + * 8 + * title => new_title + * 9 + * body => new_body */ class SourceCsvForm extends FormBase { diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/IdMapFilter.php b/sites/all/modules/contrib/migrate/migrate_tools/src/IdMapFilter.php new file mode 100644 index 000000000..46518d206 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/IdMapFilter.php @@ -0,0 +1,42 @@ +idList = $id_list; + } + + /** + * {@inheritdoc} + */ + public function accept() { + // Row is included. + if (empty($this->idList) || in_array(array_values($this->getInnerIterator()->currentSource()), $this->idList)) { + return TRUE; + } + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateBatchExecutable.php b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateBatchExecutable.php index ee24f64d7..9d031c5ff 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateBatchExecutable.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateBatchExecutable.php @@ -2,15 +2,16 @@ namespace Drupal\migrate_tools; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\migrate\MigrateMessage; use Drupal\migrate\MigrateMessageInterface; -use Drupal\migrate\Plugin\Migration; use Drupal\migrate\Plugin\MigrationInterface; /** * Defines a migrate executable class for batch migrations through UI. */ class MigrateBatchExecutable extends MigrateExecutable { + use StringTranslationTrait; /** * Representing a batch import operation. @@ -98,10 +99,10 @@ class MigrateBatchExecutable extends MigrateExecutable { if (count($operations) > 0) { $batch = [ 'operations' => $operations, - 'title' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]), - 'init_message' => t('Start migrating %migrate', ['%migrate' => $this->migration->label()]), - 'progress_message' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]), - 'error_message' => t('An error occurred while migrating %migrate.', ['%migrate' => $this->migration->label()]), + 'title' => $this->t('Migrating %migrate', ['%migrate' => $this->migration->label()]), + 'init_message' => $this->t('Start migrating %migrate', ['%migrate' => $this->migration->label()]), + 'progress_message' => $this->t('Migrating %migrate', ['%migrate' => $this->migration->label()]), + 'error_message' => $this->t('An error occurred while migrating %migrate.', ['%migrate' => $this->migration->label()]), 'finished' => '\Drupal\migrate_tools\MigrateBatchExecutable::batchFinishedImport', ]; @@ -181,6 +182,12 @@ class MigrateBatchExecutable extends MigrateExecutable { $message = new MigrateMessage(); /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ $migration = \Drupal::getContainer()->get('plugin.manager.migration')->createInstance($migration_id); + + // Each batch run we need to reinitialize the counter for the migration. + if (!empty($options['limit']) && isset($context['results'][$migration->id()]['@numitems'])) { + $options['limit'] = $options['limit'] - $context['results'][$migration->id()]['@numitems']; + } + $executable = new MigrateBatchExecutable($migration, $message, $options); if (empty($context['sandbox']['total'])) { @@ -249,7 +256,7 @@ class MigrateBatchExecutable extends MigrateExecutable { foreach ($results as $migration_id => $result) { $singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'"; $plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'"; - drupal_set_message(\Drupal::translation()->formatPlural($result['@numitems'], + \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural($result['@numitems'], $singular_message, $plural_message, $result)); diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateExecutable.php b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateExecutable.php index 2282ab8cb..2930e422c 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateExecutable.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateExecutable.php @@ -2,19 +2,19 @@ namespace Drupal\migrate_tools; +use Drupal\migrate\Event\MigrateEvents; +use Drupal\migrate\Event\MigrateImportEvent; +use Drupal\migrate\Event\MigrateMapDeleteEvent; +use Drupal\migrate\Event\MigrateMapSaveEvent; use Drupal\migrate\Event\MigratePreRowSaveEvent; use Drupal\migrate\Event\MigrateRollbackEvent; use Drupal\migrate\Event\MigrateRowDeleteEvent; use Drupal\migrate\MigrateExecutable as MigrateExecutableBase; use Drupal\migrate\MigrateMessageInterface; -use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateSkipRowException; use Drupal\migrate\Plugin\MigrateIdMapInterface; -use Drupal\migrate\Event\MigrateEvents; +use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate_plus\Event\MigrateEvents as MigratePlusEvents; -use Drupal\migrate\Event\MigrateMapSaveEvent; -use Drupal\migrate\Event\MigrateMapDeleteEvent; -use Drupal\migrate\Event\MigrateImportEvent; use Drupal\migrate_plus\Event\MigratePrepareRowEvent; /** @@ -104,14 +104,7 @@ class MigrateExecutable extends MigrateExecutableBase { if (isset($options['feedback'])) { $this->feedback = $options['feedback']; } - if (isset($options['idlist'])) { - if (is_string($options['idlist'])) { - $this->idlist = explode(',', $options['idlist']); - array_walk($this->idlist, function (&$value, $key) { - $value = explode(':', $value); - }); - } - } + $this->idlist = MigrateTools::buildIdList($options); $this->listeners[MigrateEvents::MAP_SAVE] = [$this, 'onMapSave']; $this->listeners[MigrateEvents::MAP_DELETE] = [$this, 'onMapDelete']; @@ -294,6 +287,8 @@ class MigrateExecutable extends MigrateExecutableBase { * The map event. */ public function onPostRollback(MigrateRollbackEvent $event) { + $migrate_last_imported_store = \Drupal::keyValue('migrate_last_imported'); + $migrate_last_imported_store->set($event->getMigration()->id(), FALSE); $this->rollbackMessage(); $this->removeListeners(); } @@ -363,6 +358,8 @@ class MigrateExecutable extends MigrateExecutableBase { * @throws \Drupal\migrate\MigrateSkipRowException */ public function onPrepareRow(MigratePrepareRowEvent $event) { + // TODO: remove after 8.6 suppor is sunset. + // @see https://www.drupal.org/project/migrate_tools/issues/3008316 if (!empty($this->idlist)) { $row = $event->getRow(); // TODO: replace for $source_id = $row->getSourceIdValues(); @@ -378,7 +375,7 @@ class MigrateExecutable extends MigrateExecutableBase { } } if ($skip) { - throw new MigrateSkipRowException(NULL, FALSE); + throw new MigrateSkipRowException('Skipped due to idlist.', FALSE); } } if ($this->feedback && ($this->counter) && $this->counter % $this->feedback == 0) { @@ -389,7 +386,20 @@ class MigrateExecutable extends MigrateExecutableBase { if ($this->itemLimit && ($this->itemLimitCounter + 1) >= $this->itemLimit) { $event->getMigration()->interruptMigration(MigrationInterface::RESULT_COMPLETED); } + } + /** + * {@inheritdoc} + */ + protected function getSource() { + return new SourceFilter(parent::getSource(), $this->idlist); + } + + /** + * {@inheritdoc} + */ + protected function getIdMap() { + return new IdMapFilter(parent::getIdMap(), $this->idlist); } } diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateTools.php b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateTools.php new file mode 100644 index 000000000..5a7ce4c90 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/MigrateTools.php @@ -0,0 +1,34 @@ + NULL, + 'idlist-delimiter' => ':', + ]; + $id_list = []; + if ($options['idlist']) { + $id_list = explode(',', $options['idlist']); + array_walk($id_list, function (&$value) use ($options) { + $value = str_getcsv($value, $options['idlist-delimiter']); + }); + } + return $id_list; + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/src/SourceFilter.php b/sites/all/modules/contrib/migrate/migrate_tools/src/SourceFilter.php new file mode 100644 index 000000000..28a6f9d9e --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/src/SourceFilter.php @@ -0,0 +1,53 @@ +idList = $id_list; + } + + /** + * {@inheritdoc} + */ + public function accept() { + // No idlist filtering, don't filter. + if (empty($this->idList)) { + return TRUE; + } + // Some source plugins do not extend SourcePluginBase. These cannot be + // filtered so warn and return all values. + if (!$this->getInnerIterator() instanceof SourcePluginBase) { + trigger_error(sprintf('The source plugin %s is not an instance of %s. Extend from %s to support idlist filtering.', $this->getInnerIterator()->getPluginId(), SourcePluginBase::class, SourcePluginBase::class)); + return TRUE; + } + // Row is included. + if (in_array(array_values($this->getInnerIterator()->getCurrentIds()), $this->idList)) { + return TRUE; + } + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/csv_source_test/csv_source_test.info.yml b/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/csv_source_test/csv_source_test.info.yml index f16e6bbd7..989af7898 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/csv_source_test/csv_source_test.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/csv_source_test/csv_source_test.info.yml @@ -8,8 +8,8 @@ dependencies: - migrate_plus:migrate_plus - migrate_plus:migrate_source_csv -# Information added by Drupal.org packaging script on 2018-08-27 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_tools' -datestamp: 1535380087 +datestamp: 1546879109 diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/migrate_tools_test/migrate_tools_test.info.yml b/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/migrate_tools_test/migrate_tools_test.info.yml index bbeb69867..dd77f458e 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/migrate_tools_test/migrate_tools_test.info.yml +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/modules/migrate_tools_test/migrate_tools_test.info.yml @@ -7,8 +7,8 @@ dependencies: - drupal:migrate (>=8.3) - migrate_plus:migrate_plus -# Information added by Drupal.org packaging script on 2018-08-27 -version: '8.x-4.0' +# Information added by Drupal.org packaging script on 2019-01-07 +version: '8.x-4.1' core: '8.x' project: 'migrate_tools' -datestamp: 1535380087 +datestamp: 1546879109 diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/MigrateExecutionFormTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/MigrateExecutionFormTest.php index 2f29bf5ba..3bdabdc7a 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/MigrateExecutionFormTest.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/MigrateExecutionFormTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\migrate_tools\Functional; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\Tests\BrowserTestBase; @@ -11,6 +12,7 @@ use Drupal\Tests\BrowserTestBase; * @group migrate_tools */ class MigrateExecutionFormTest extends BrowserTestBase { + use StringTranslationTrait; /** * {@inheritdoc} @@ -77,21 +79,21 @@ class MigrateExecutionFormTest extends BrowserTestBase { $edit = [ 'operation' => 'import', ]; - $this->drupalPostForm($urlPath, $edit, t('Execute')); + $this->drupalPostForm($urlPath, $edit, $this->t('Execute')); $real_count = $this->vocabularyQuery->count()->execute(); $expected_count = 3; $this->assertEquals($expected_count, $real_count); $edit = [ 'operation' => 'rollback', ]; - $this->drupalPostForm($urlPath, $edit, t('Execute')); + $this->drupalPostForm($urlPath, $edit, $this->t('Execute')); $real_count = $this->vocabularyQuery->count()->execute(); $expected_count = 0; $this->assertEquals($expected_count, $real_count); $edit = [ 'operation' => 'import', ]; - $this->drupalPostForm($urlPath, $edit, t('Execute')); + $this->drupalPostForm($urlPath, $edit, $this->t('Execute')); $real_count = $this->vocabularyQuery->count()->execute(); $expected_count = 3; $this->assertEquals($expected_count, $real_count); diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/SourceCsvFormTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/SourceCsvFormTest.php index 767a9bfe0..6cbded03b 100644 --- a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/SourceCsvFormTest.php +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Functional/SourceCsvFormTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\migrate_tools\Functional; use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Tests\BrowserTestBase; use Drupal\taxonomy\Entity\Vocabulary; use Drupal\taxonomy\VocabularyInterface; @@ -16,6 +17,7 @@ use Drupal\taxonomy\VocabularyInterface; * @group migrate_tools */ class SourceCsvFormTest extends BrowserTestBase { + use StringTranslationTrait; /** * Temporary store for column assignment changes. @@ -134,7 +136,7 @@ EOD; 'edit-name' => 1, 'edit-description' => 1, ]; - $this->drupalPostForm($editUrlPath, $edit, t('Submit')); + $this->drupalPostForm($editUrlPath, $edit, $this->t('Submit')); $session->responseContains('Source properties can not share the same source column.'); $this->assertTrue($session->optionExists('edit-vid', 'description') ->isSelected()); @@ -149,7 +151,7 @@ EOD; 'edit-name' => 0, 'edit-description' => 1, ]; - $this->drupalPostForm($editUrlPath, $edit, t('Submit')); + $this->drupalPostForm($editUrlPath, $edit, $this->t('Submit')); $this->assertTrue($session->optionExists('edit-vid', 'weight') ->isSelected()); $this->assertTrue($session->optionExists('edit-name', 'vid') @@ -184,22 +186,22 @@ EOD; $edit = [ 'operation' => 'import', ]; - $this->drupalPostForm($executeUrlPath, $edit, t('Execute')); + $this->drupalPostForm($executeUrlPath, $edit, $this->t('Execute')); $session->responseContains("Processed 1 item (1 created, 0 updated, 0 failed, 0 ignored) - done with 'csv_source_test'"); // Rollback. $edit = [ 'operation' => 'rollback', ]; - $this->drupalPostForm($executeUrlPath, $edit, t('Execute')); + $this->drupalPostForm($executeUrlPath, $edit, $this->t('Execute')); - // Restore to an order that will succesfully migrate. + // Restore to an order that will successfully migrate. $edit = [ 'edit-vid' => 0, 'edit-name' => 1, 'edit-description' => 2, ]; - $this->drupalPostForm($editUrlPath, $edit, t('Submit')); + $this->drupalPostForm($editUrlPath, $edit, $this->t('Submit')); $this->assertTrue($session->optionExists('edit-vid', 'vid') ->isSelected()); $this->assertTrue($session->optionExists('edit-name', 'name') @@ -212,7 +214,7 @@ EOD; 'operation' => 'import', ]; drupal_flush_all_caches(); - $this->drupalPostForm($executeUrlPath, $edit, t('Execute')); + $this->drupalPostForm($executeUrlPath, $edit, $this->t('Execute')); $session->responseContains("Processed 4 items (4 created, 0 updated, 0 failed, 0 ignored) - done with 'csv_source_test'"); $this->assertEntity('tags', 'Tags', 'Use tags to group articles'); $this->assertEntity('forums', 'Sujet de discussion', 'Forum navigation vocabulary'); diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/DrushTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/DrushTest.php new file mode 100644 index 000000000..6929c96f5 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/DrushTest.php @@ -0,0 +1,217 @@ + NULL, + 'group' => NULL, + 'tag' => NULL, + 'limit' => NULL, + 'feedback' => NULL, + 'idlist' => NULL, + 'idlist-delimiter' => ':', + 'update' => NULL, + 'force' => NULL, + 'execute-dependencies' => NULL, + ]; + + /** + * The Migrate Tools Command drush service. + * + * @var \Drupal\migrate_tools\Commands\MigrateToolsCommands + */ + protected $commands; + + /** + * The migration plugin manager. + * + * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface + */ + protected $migrationPluginManager; + + /** + * The logger. + * + * @var \Drupal\Core\Logger\LoggerChannelInterface + */ + protected $logger; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + $this->installConfig('migrate_plus'); + $this->installConfig('migrate_tools_test'); + $this->installEntitySchema('taxonomy_term'); + $this->installSchema('system', ['key_value', 'key_value_expire']); + $this->migrationPluginManager = $this->container->get('plugin.manager.migration'); + $this->logger = $this->container->get('logger.channel.migrate_tools'); + $this->commands = new MigrateToolsCommands( + $this->migrationPluginManager, + $this->container->get('date.formatter'), + $this->container->get('entity_type.manager'), + $this->container->get('keyvalue')); + $this->commands->setLogger($this->logger); + } + + /** + * Tests drush ms. + */ + public function testStatus() { + $this->executeMigration('fruit_terms'); + /** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */ + $result = $this->commands->status('fruit_terms'); + $rows = $result->getArrayCopy(); + $this->assertSame(1, count($rows)); + $row = reset($rows); + $this->assertSame('fruit_terms', $row['id']); + $this->assertSame(3, $row['total']); + $this->assertSame(3, $row['imported']); + $this->assertSame('Idle', $row['status']); + } + + /** + * Tests drush mim. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function testImport() { + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->migrationPluginManager->createInstance('fruit_terms'); + $id_map = $migration->getIdMap(); + $this->commands->import('fruit_terms', ['idlist' => 'Apple'] + $this->importBaseOptions); + $this->assertSame(1, $id_map->importedCount()); + $this->commands->import('fruit_terms'); + $this->assertSame(3, $id_map->importedCount()); + $this->commands->import('fruit_terms', ['idlist' => 'Apple', 'update' => TRUE] + $this->importBaseOptions); + $this->assertSame(0, count($id_map->getRowsNeedingUpdate(100))); + } + + /** + * Tests drush mmsg. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function testMessages() { + $this->executeMigration('fruit_terms'); + $message = $this->getRandomGenerator()->string(16); + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->migrationPluginManager->createInstance('fruit_terms'); + $id_map = $migration->getIdMap(); + $id_map->saveMessage(['name' => 'Apple'], $message); + /** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */ + $result = $this->commands->messages('fruit_terms'); + $rows = $result->getArrayCopy(); + $this->assertSame($message, $rows[0]['message']); + } + + /** + * Tests drush mr. + */ + public function testRollback() { + $this->executeMigration('fruit_terms'); + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->migrationPluginManager->createInstance('fruit_terms'); + $id_map = $migration->getIdMap(); + $this->assertSame(3, $id_map->importedCount()); + $this->commands->rollback('fruit_terms'); + $this->assertSame(0, $id_map->importedCount()); + } + + /** + * Tests drush mrs. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function testReset() { + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->migrationPluginManager->createInstance('fruit_terms'); + $migration->setStatus(MigrationInterface::STATUS_IMPORTING); + $this->assertSame('Importing', $this->commands->status('fruit_terms')->getArrayCopy()[0]['status']); + $this->commands->resetStatus('fruit_terms'); + $this->assertSame(MigrationInterface::STATUS_IDLE, $migration->getStatus()); + + } + + /** + * Tests drush mst. + * + * @throws \Drupal\Component\Plugin\Exception\PluginException + */ + public function testStop() { + /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ + $migration = $this->migrationPluginManager->createInstance('fruit_terms'); + $migration->setStatus(MigrationInterface::STATUS_IMPORTING); + $this->commands->stop('fruit_terms'); + $this->assertSame(MigrationInterface::STATUS_STOPPING, $migration->getStatus()); + } + + /** + * Tests drush mfs. + */ + public function testFieldsSource() { + /** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */ + $result = $this->commands->fieldsSource('fruit_terms'); + $rows = $result->getArrayCopy(); + $this->assertSame(1, count($rows)); + $this->assertSame('name', $rows[0]['machine_name']); + $this->assertSame('name', $rows[0]['description']); + } + +} + +namespace Drupal\migrate_tools\Commands; + +/** + * Stub for drush_op. + * + * @param callable $callable + * The function to call. + */ +function drush_op(callable $callable) { + $args = func_get_args(); + array_shift($args); + call_user_func_array($callable, $args); +} + +/** + * Stub for dt(). + * + * @param string $text + * The text. + * + * @return string + * The text. + */ +function dt($text) { + return $text; +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateImportTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateImportTest.php new file mode 100644 index 000000000..883421bc4 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateImportTest.php @@ -0,0 +1,81 @@ +installEntitySchema('user'); + $this->installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(['taxonomy']); + } + + /** + * Tests rolling back configuration and content entities. + */ + public function testImport() { + // We use vocabularies to demonstrate importing and rolling back + // configuration entities. + $vocabulary_data_rows = [ + ['id' => '1', 'name' => 'categories', 'weight' => '2'], + ['id' => '2', 'name' => 'tags', 'weight' => '1'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $definition = [ + 'id' => 'vocabularies', + 'migration_tags' => ['Import and rollback test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $vocabulary_data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'vid' => 'id', + 'name' => 'name', + 'weight' => 'weight', + ], + 'destination' => ['plugin' => 'entity:taxonomy_vocabulary'], + ]; + + /** @var \Drupal\migrate\Plugin\MigrationInterface $vocabulary_migration */ + $vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition); + $vocabulary_id_map = $vocabulary_migration->getIdMap(); + + // Test id list import. + $executable = new MigrateExecutable($vocabulary_migration, $this, ['idlist' => 2]); + $executable->import(); + + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load(1); + $this->assertFalse($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => 1]); + $this->assertFalse($map_row); + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load(2); + $this->assertTrue($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => 2]); + $this->assertNotNull($map_row['destid1']); + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateRollbackTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateRollbackTest.php new file mode 100644 index 000000000..9bbfa47b3 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Kernel/MigrateRollbackTest.php @@ -0,0 +1,106 @@ +installEntitySchema('user'); + $this->installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(['taxonomy']); + } + + /** + * Tests rolling back configuration and content entities. + */ + public function testRollback() { + // We use vocabularies to demonstrate importing and rolling back + // configuration entities. + $vocabulary_data_rows = [ + ['id' => '1', 'name' => 'categories', 'weight' => '2'], + ['id' => '2', 'name' => 'tags', 'weight' => '1'], + ]; + $ids = ['id' => ['type' => 'integer']]; + $definition = [ + 'id' => 'vocabularies', + 'migration_tags' => ['Import and rollback test'], + 'source' => [ + 'plugin' => 'embedded_data', + 'data_rows' => $vocabulary_data_rows, + 'ids' => $ids, + ], + 'process' => [ + 'vid' => 'id', + 'name' => 'name', + 'weight' => 'weight', + ], + 'destination' => ['plugin' => 'entity:taxonomy_vocabulary'], + ]; + + /** @var \Drupal\migrate\Plugin\MigrationInterface $vocabulary_migration */ + $vocabulary_migration = \Drupal::service('plugin.manager.migration')->createStubMigration($definition); + $vocabulary_id_map = $vocabulary_migration->getIdMap(); + + // Import and validate vocabulary config entities were created. + $executable = new MigrateExecutable($vocabulary_migration, $this, []); + $executable->import(); + foreach ($vocabulary_data_rows as $row) { + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load($row['id']); + $this->assertTrue($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); + $this->assertNotNull($map_row['destid1']); + } + + // Test id list rollback. + $rollback_executable = new MigrateExecutable($vocabulary_migration, $this, ['idlist' => 1]); + $rollback_executable->rollback(); + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load(1); + $this->assertFalse($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => 1]); + $this->assertFalse($map_row); + + // TODO: remove after 8.6 is sunset. + // @see https://www.drupal.org/project/migrate_tools/issues/3008316 + include_once $this->root . '/core/includes/install.core.inc'; + $version = _install_get_version_info(\Drupal::VERSION); + if ($version['minor'] == 6) { + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load(1); + $this->assertFalse($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => 1]); + $this->assertFalse($map_row); + } + else { + /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ + $vocabulary = Vocabulary::load(2); + $this->assertTrue($vocabulary); + $map_row = $vocabulary_id_map->getRowBySource(['id' => 2]); + $this->assertNotNull($map_row['destid1']); + } + + } + +} diff --git a/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Unit/MigrateToolsTest.php b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Unit/MigrateToolsTest.php new file mode 100644 index 000000000..23f02c581 --- /dev/null +++ b/sites/all/modules/contrib/migrate/migrate_tools/tests/src/Unit/MigrateToolsTest.php @@ -0,0 +1,85 @@ +assertEquals($results, $expected); + } + + /** + * Data provider for testBuildIdList. + */ + public function dataProviderIdList() { + $cases = []; + $cases[] = [ + 'options' => [], + 'expected' => [], + ]; + $cases['single id'] = [ + 'options' => [ + 'idlist' => 123, + ], + 'expected' => [[123]], + ]; + $cases['multiple ids'] = [ + 'options' => [ + 'idlist' => '123, 456', + ], + 'expected' => [ + [123], [456], + ], + ]; + $cases['default delimiter, composite key'] = [ + 'options' => [ + 'idlist' => '123:456', + ], + 'expected' => [ + [123, 456], + ], + ]; + $cases['special delimiter, single'] = [ + 'options' => [ + 'idlist' => '123:456', + 'idlist-delimiter' => '~', + ], + 'expected' => [ + ['123:456'], + ], + ]; + $cases['special delimiter, multiple'] = [ + 'options' => [ + 'idlist' => '123:456~987:654', + 'idlist-delimiter' => '~', + ], + 'expected' => [ + ['123:456', '987:654'], + ], + ]; + $cases['space delimiter, multiple'] = [ + 'options' => [ + 'idlist' => '123:456 987:654', + 'idlist-delimiter' => ' ', + ], + 'expected' => [ + ['123:456', '987:654'], + ], + ]; + return $cases; + } + +} diff --git a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt index 66f73cb2c..51a09db73 100644 --- a/sites/all/modules/contrib/search/search_api/CHANGELOG.txt +++ b/sites/all/modules/contrib/search/search_api/CHANGELOG.txt @@ -1,3 +1,74 @@ +Search API 1.11 (2018-11-18): +----------------------------- +- #3012778 by Shashwat Purav, pamatt, drunken monkey: Fixed several doc comment + typos. +- #3011340 by pfrenssen, idimopoulos, drunken monkey: Fixed problem when + updating multiple indexes' datasource settings via "drush cim". +- #2986623 by drunken monkey, valic: Added the "Reverse entity references" + processor. +- #3009136 by drunken monkey, mkalkbrenner, borisson_: Added the + "search_api_retrieved_field_values" query option. +- #3006267 by idebr, drunken monkey: Removed conditional code for Drupal 8.5. +- #3007311 by drunken monkey: Fixed catch clause in + PostRequestIndexing::destruct(). +- #2994022 by drunken monkey, Watergate, borisson_: Fixed facets when there are + no search results in Database backend. +- #2898917 by Lennard Westerveld, RoSk0, drunken monkey: Fixed Views filters + with checkboxes and "is all of" operator. +- #3001259 by klausi, drunken monkey, borisson_: Fixed HTML escaping throughout + the module. +- #3001030 by mkalkbrenner, drunken monkey, mvantuch: Fixed excerpt not taking + Solr filters into account. +- #3004017 by idebr, drunken monkey: Fixed server/index config schemas to use + UUID field from Core definition. +- #2971619 by idebr, drunken monkey: Refactored the access check for the + "Execute pending tasks" local action. +- #2995342 by AndrewsizZ, drunken monkey, borisson_: Added stripping of + script/style HTML tags in excerpts to Highlight processor. + +Search API 1.10 (2018-09-17): +----------------------------- +- #2993787 by drunken monkey, borisson_, mkalkbrenner: Added the possibility to + boost fields in MLT queries. +- #2936631 by drunken monkey, borisson_: Added support for the "no_ui" + definition key for plugins. +- #2996936 by drunken monkey, borisson_: Added suggested packages to + composer.json. +- #2987156 by hchonov, drunken monkey, borisson_: Fixed missing dependency on + modules providing fields. +- #2994638 by drunken monkey, Grayle, borisson_: Fixed warning in aggregated + fields without values. +- #2987237 by quicksketch, drunken monkey, borisson_: Fixed requirements hook + for the Taxonomy Term Handlers module preventing updates from running. +- #2992662 by capysara, drunken monkey, borisson_: Fixed the reindexing link + when saving index processors or fields. +- #2989689 by drunken monkey, valthebald, borisson_: Fixed bug in Views cache + plugin alter hook. +- #2919676 by drunken monkey, borisson_: Fixed server entity link definitions. +- #2996278 by drunken monkey: Removed one pointless variable. +- #2987245 by drunken monkey: Fixed use of account_switcher service for + switching the currently active account. +- #2996907 by zerolab, drunken monkey: Fixed entity tracking using access + checks. +- #2993434 by jasonawant, drunken monkey: Fixed handling of comma-separated + values in Views contextual filters. +- #2983843 by drunken monkey, borisson_: Fixed suggestion of installing default + module when dependencies aren't met. +- #2979316 by drunken monkey, Sutharsan: Fixed highlighting problems with + multi-byte characters. +- #2986310 by capysara, drunken monkey: Added link to reindexing when saving + fields. +- #2986013 by tim-diels, drunken monkey, borisson_: Fixed casting of + nun-numeric strings in the database backend. +- #2983630 by chipway, borisson_, drunken monkey: Fixed format of System + dependency in search_api.info.yml. +- #2982660 by jhedstrom, drunken monkey, borisson_: Fixed indexed time of + date-only fields. +- #2986216 by drunken monkey: Added the "legacy" group to + SearchApiDbUpdate8102Test. +- #2920943 by drunken monkey: Fixed error handling for "Rendered item" + processor. + Search API 1.9 (2018-07-19): ---------------------------- - #2956012 by drunken monkey, DuaelFr: Added uncustomized view modes as options diff --git a/sites/all/modules/contrib/search/search_api/README.txt b/sites/all/modules/contrib/search/search_api/README.txt index 44b2e3d64..21198bc18 100644 --- a/sites/all/modules/contrib/search/search_api/README.txt +++ b/sites/all/modules/contrib/search/search_api/README.txt @@ -84,7 +84,7 @@ framework. - Datasources: src/Datasource - Data types: src/DataType - Displays: src/Display - - ParseModes: src/ParseMode + - Parse modes: src/ParseMode - Processors: src/Processor - Trackers: src/Tracker The display plugins are a bit of a special case there, because they aren't @@ -106,6 +106,32 @@ module's issue regarding potential module-specific changes to that policy [7]. [6] https://www.drupal.org/core/d8-bc-policy [7] https://www.drupal.org/node/2871549 +SERVER BACKEND FEATURES +----------------------- + +Server backend features are a way for other contrib modules to cleanly define +ways in which the Search API can be extended. For more information, see [8]. + +[8] https://www.drupal.org/docs/8/modules/search-api/getting-started/server-backends-and-features + +The Search API module itself currently defines one feature: + +- More Like This (search_api_mlt) + This feature can be used to retrieve a list of search results that are similar + to a given indexed item. A backend that supports this plugin has to recognize + the "search_api_mlt" query option. If present, it contains an associative + array with the following keys: + - id: The Search API item ID (consisting of the datasource ID and the + datasource-specific item ID – passing a plain entity ID will NOT work!) of + the item for which similar results should be found. + - fields: A simple array of fields which should be used for determining + similarity. Backends can choose to ignore this field. + - "field boosts": (optional) An associative array mapping fields to a numeric + "boost" value that determines how important they should be considered when + determining similarity. Backends can choose to ignore this field. + The feature can be used in the UI via the "More like this" Views contextual + filter. + MAINTAINERS ----------- Current maintainers: diff --git a/sites/all/modules/contrib/search/search_api/composer.json b/sites/all/modules/contrib/search/search_api/composer.json index 43bb2ef42..effce8170 100644 --- a/sites/all/modules/contrib/search/search_api/composer.json +++ b/sites/all/modules/contrib/search/search_api/composer.json @@ -26,6 +26,11 @@ "require-dev": { "drupal/search_api_autocomplete": "@dev" }, + "suggest": { + "drupal/facets": "Adds the ability to create faceted searches.", + "drupal/search_api_autocomplete": "Allows adding autocomplete suggestions to search fields.", + "drupal/search_api_solr": "Adds support for using Apache Solr as a backend." + }, "extra": { "drush": { "services": { diff --git a/sites/all/modules/contrib/search/search_api/config/schema/search_api.index.schema.yml b/sites/all/modules/contrib/search/search_api/config/schema/search_api.index.schema.yml index 6797a8ebc..8e519eb0f 100644 --- a/sites/all/modules/contrib/search/search_api/config/schema/search_api.index.schema.yml +++ b/sites/all/modules/contrib/search/search_api/config/schema/search_api.index.schema.yml @@ -8,9 +8,6 @@ search_api.index.*: name: type: label label: Name' - uuid: - type: string - label: 'UUID' description: type: text label: 'Description' diff --git a/sites/all/modules/contrib/search/search_api/config/schema/search_api.server.schema.yml b/sites/all/modules/contrib/search/search_api/config/schema/search_api.server.schema.yml index 62dd39cf5..99cafafcd 100644 --- a/sites/all/modules/contrib/search/search_api/config/schema/search_api.server.schema.yml +++ b/sites/all/modules/contrib/search/search_api/config/schema/search_api.server.schema.yml @@ -8,9 +8,6 @@ search_api.server.*: name: type: label label: 'Name' - uuid: - type: string - label: 'UUID' description: type: text label: 'Description' diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db.info.yml b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db.info.yml index 94ca69eba..8777d6a71 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db.info.yml +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db.info.yml @@ -6,8 +6,8 @@ package: Search dependencies: - search_api:search_api -# Information added by Drupal.org packaging script on 2018-07-19 -version: '8.x-1.9' +# Information added by Drupal.org packaging script on 2018-11-18 +version: '8.x-1.11' core: '8.x' project: 'search_api' -datestamp: 1531985591 +datestamp: 1542564486 diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml index 2dba38b37..8e10445b7 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.info.yml @@ -13,8 +13,8 @@ dependencies: - drupal:views - search_api:search_api_db -# Information added by Drupal.org packaging script on 2018-07-19 -version: '8.x-1.9' +# Information added by Drupal.org packaging script on 2018-11-18 +version: '8.x-1.11' core: '8.x' project: 'search_api' -datestamp: 1531985591 +datestamp: 1542564486 diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.install b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.install index 249d37009..ca40db3ad 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.install +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/search_api_db_defaults/search_api_db_defaults.install @@ -5,7 +5,7 @@ * Install, update and uninstall functions for the DB Search Defaults module. */ -use Drupal\node\Entity\NodeType; +use Drupal\search_api\IndexListBuilder; /** * Implements hook_requirements(). @@ -14,60 +14,12 @@ function search_api_db_defaults_requirements($phase) { $requirements = []; if ($phase == 'install') { - $node_types = NodeType::loadMultiple(); - $required_types = [ - 'article' => ['body', 'comment', 'field_tags', 'field_image'], - 'page' => ['body'], - ]; - - /** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */ - $entity_field_manager = \Drupal::service('entity_field.manager'); - - foreach ($required_types as $required_type_id => $required_fields) { - if (!isset($node_types[$required_type_id])) { - $requirements['search_api_db_defaults:' . $required_type_id] = [ - 'severity' => REQUIREMENT_ERROR, - 'description' => t('Content type @content_type not found. Database Search Defaults module could not be installed.', ['@content_type' => $required_type_id]), - ]; - } - else { - // Check if all the fields are here. - $fields = $entity_field_manager->getFieldDefinitions('node', $required_type_id); - foreach ($required_fields as $required_field) { - if (!isset($fields[$required_field])) { - $requirements['search_api_db_defaults:' . $required_type_id . ':' . $required_field] = [ - 'severity' => REQUIREMENT_ERROR, - 'description' => t('Field @field in content type @node_type not found. Database Search Defaults module could not be installed', ['@node_type' => $required_type_id, '@field' => $required_field]), - ]; - } - } - } - } - - if (\Drupal::moduleHandler()->moduleExists('search_api_db')) { - $entities_to_check = [ - 'search_api_index' => 'default_index', - 'search_api_server' => 'default_server', - 'view' => 'search_content', + $errors = IndexListBuilder::checkDefaultsModuleCanBeInstalled(); + foreach ($errors as $type => $error) { + $requirements["search_api_db_defaults:$type"] = [ + 'severity' => REQUIREMENT_ERROR, + 'description' => $error, ]; - - /** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */ - $entity_type_manager = \Drupal::service('entity_type.manager'); - foreach ($entities_to_check as $entity_type => $entity_id) { - // Find out if the entity is already in place. If so, fail to install the - // module. - $entity_storage = $entity_type_manager->getStorage($entity_type); - $entity_storage->resetCache(); - $entity = $entity_storage->load($entity_id); - - if ($entity) { - $requirements['search_api_db_defaults:defaults_exist'] = [ - 'severity' => REQUIREMENT_ERROR, - 'description' => t('It looks like the default setup provided by this module already exists on your site. Cannot re-install module.'), - ]; - break; - } - } } } diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php b/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php index 5bfb8cf59..172c9a39a 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Plugin/search_api/backend/Database.php @@ -3,7 +3,6 @@ namespace Drupal\search_api_db\Plugin\search_api\backend; use Drupal\Component\Utility\Crypt; -use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Database as CoreDatabase; @@ -1166,7 +1165,7 @@ class Database extends BackendPluginBase implements PluginFormInterface { } catch (\Exception $e) { // We just log the error, hoping we can index the other items. - $this->getLogger()->warning(Html::escape($e->getMessage())); + $this->getLogger()->warning($e->getMessage()); } } return $indexed; @@ -1405,8 +1404,8 @@ class Database extends BackendPluginBase implements PluginFormInterface { * @param mixed $value * The value to convert. * @param string $type - * The type to convert to. One of the keys from - * search_api_default_field_types(). + * The Search API type to convert to. (Has to be a type supported by this + * backend.) * @param string $original_type * The value's original type. * @param \Drupal\search_api\IndexInterface $index @@ -1471,7 +1470,6 @@ class Database extends BackendPluginBase implements PluginFormInterface { return $tokens; case 'string': - case 'uri': // For non-dates, PHP can handle this well enough. if ($original_type == 'date') { return date('c', $value); @@ -1484,9 +1482,10 @@ class Database extends BackendPluginBase implements PluginFormInterface { case 'integer': case 'date': - case 'duration': + return (int) $value; + case 'decimal': - return 0 + $value; + return (float) $value; case 'boolean': return $value ? 1 : 0; @@ -1602,16 +1601,21 @@ class Database extends BackendPluginBase implements PluginFormInterface { $results = $query->getResults(); $skip_count = $query->getOption('skip result count'); + $count = NULL; if (!$skip_count) { $count_query = $db_query->countQuery(); - $results->setResultCount($count_query->execute()->fetchField()); + $count = $count_query->execute()->fetchField(); + $results->setResultCount($count); } - if ($skip_count || $results->getResultCount()) { - if ($query->getOption('search_api_facets')) { - $results->setExtraData('search_api_facets', $this->getFacets($query, clone $db_query)); - } - + // With a "min_count" of 0, some facets can even be available if there are + // no results. + if ($query->getOption('search_api_facets')) { + $facets = $this->getFacets($query, clone $db_query, $count); + $results->setExtraData('search_api_facets', $facets); + } + // Everything else can be skipped if the count is 0. + if ($skip_count || $count) { $query_options = $query->getOptions(); if (isset($query_options['offset']) || isset($query_options['limit'])) { $offset = isset($query_options['offset']) ? $query_options['offset'] : 0; @@ -2353,16 +2357,13 @@ class Database extends BackendPluginBase implements PluginFormInterface { * The search query for which facets should be computed. * @param \Drupal\Core\Database\Query\SelectInterface $db_query * A database select query which returns all results of that search query. + * @param int|null $result_count + * (optional) The total number of results of the search query, if known. * * @return array * An array of facets, as specified by the search_api_facets feature. */ - protected function getFacets(QueryInterface $query, SelectInterface $db_query) { - $table = $this->getTemporaryResultsTable($db_query); - if (!$table) { - return []; - } - + protected function getFacets(QueryInterface $query, SelectInterface $db_query, $result_count = NULL) { $fields = $this->getFieldInfo($query->getIndex()); $ret = []; foreach ($query->getOption('search_api_facets') as $key => $facet) { @@ -2374,8 +2375,33 @@ class Database extends BackendPluginBase implements PluginFormInterface { $field = $fields[$facet['field']]; if (empty($facet['operator']) || $facet['operator'] != 'or') { - // All the AND facets can use the main query. - $select = $this->database->select($table, 't'); + // First, check whether this can even possibly have any results. + if ($result_count !== NULL && $result_count < $facet['min_count']) { + continue; + } + + // All the AND facets can use the main query. If we didn't yet create a + // temporary table for them yet, do so now. + if (!isset($table)) { + $table = $this->getTemporaryResultsTable($db_query); + } + if ($table) { + $select = $this->database->select($table, 't'); + } + else { + // If no temporary table could be created (most likely due to a + // missing permission), use a nested query instead. + $select = $this->database->select(clone $db_query, 't'); + } + // In case we didn't get the result count passed to the method, we can + // get it now. (This allows us to skip AND facets with a min_count + // higher than the result count.) + if ($result_count === NULL) { + $result_count = $select->countQuery()->execute()->fetchField(); + if ($result_count < $facet['min_count']) { + continue; + } + } } else { // For OR facets, we need to build a different base query that excludes @@ -2388,7 +2414,13 @@ class Database extends BackendPluginBase implements PluginFormInterface { unset($conditions[$i]); } } - $or_db_query = $this->createDbQuery($or_query, $fields); + try { + $or_db_query = $this->createDbQuery($or_query, $fields); + } + catch (SearchApiException $e) { + $this->logException($e, '%type while trying to create a facets query: @message in %function (line %line of %file).'); + continue; + } $select = $this->database->select($or_db_query, 't'); } diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Tests/Update/SearchApiDbUpdate8102Test.php b/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Tests/Update/SearchApiDbUpdate8102Test.php index bb0b1e4ef..25cd8d1fc 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Tests/Update/SearchApiDbUpdate8102Test.php +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/src/Tests/Update/SearchApiDbUpdate8102Test.php @@ -9,6 +9,7 @@ use Drupal\search_api_db\Tests\DatabaseTestsTrait; * Tests whether search_api_db_update_8102() works correctly. * * @group search_api + * @group legacy * * @see https://www.drupal.org/node/2884451 */ diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml b/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml index 8ca25ceda..531bb5f3f 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/search_api_db_test_autocomplete/search_api_db_test_autocomplete.info.yml @@ -8,8 +8,8 @@ dependencies: # core: 8.x hidden: true -# Information added by Drupal.org packaging script on 2018-07-19 -version: '8.x-1.9' +# Information added by Drupal.org packaging script on 2018-11-18 +version: '8.x-1.11' core: '8.x' project: 'search_api' -datestamp: 1531985591 +datestamp: 1542564486 diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php b/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php index 6d2a76602..703367a94 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_db/tests/src/Kernel/BackendTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\search_api_db\Kernel; use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Database\Database as CoreDatabase; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\search_api\Entity\Index; use Drupal\search_api\Entity\Server; use Drupal\search_api\IndexInterface; @@ -93,6 +94,7 @@ class BackendTest extends BackendTestBase { $this->regressionTest2926733(); $this->regressionTest2938646(); $this->regressionTest2925464(); + $this->regressionTest2994022(); } /** @@ -637,6 +639,54 @@ class BackendTest extends BackendTestBase { $this->indexItems($index->id()); } + /** + * Tests changing of field types. + * + * @see https://www.drupal.org/project/search_api/issues/2994022 + */ + protected function regressionTest2994022() { + $query = $this->buildSearch('nonexistent_search_term'); + $facets['category'] = [ + 'field' => 'category', + 'limit' => 0, + 'min_count' => 0, + 'missing' => FALSE, + 'operator' => 'and', + ]; + $query->setOption('search_api_facets', $facets); + $results = $query->execute(); + $this->assertResults([], $results, 'Non-existent keyword'); + $expected = [ + ['count' => 0, 'filter' => '"article_category"'], + ['count' => 0, 'filter' => '"item_category"'], + ]; + $category_facets = $results->getExtraData('search_api_facets')['category']; + usort($category_facets, [$this, 'facetCompare']); + $this->assertEquals($expected, $category_facets, 'Correct facets were returned for minimum count 0'); + + $query = $this->buildSearch('nonexistent_search_term'); + $conditions = $query->createConditionGroup('AND', ['facet:category']); + $conditions->addCondition('category', 'article_category'); + $query->addConditionGroup($conditions); + $facets['category'] = [ + 'field' => 'category', + 'limit' => 0, + 'min_count' => 0, + 'missing' => FALSE, + 'operator' => 'and', + ]; + $query->setOption('search_api_facets', $facets); + $results = $query->execute(); + $this->assertResults([], $results, 'Non-existent keyword with filter'); + $expected = [ + ['count' => 0, 'filter' => '"article_category"'], + ['count' => 0, 'filter' => '"item_category"'], + ]; + $category_facets = $results->getExtraData('search_api_facets')['category']; + usort($category_facets, [$this, 'facetCompare']); + $this->assertEquals($expected, $category_facets, 'Correct facets were returned for minimum count 0'); + } + /** * {@inheritdoc} */ @@ -723,7 +773,7 @@ class BackendTest extends BackendTestBase { $expected = [ 'search_api_db_database_search_index' => 'search_api_db_database_search_index', ]; - $this->assertEquals($expected, $tables, 'All the tables of the the Database Search module have been removed.'); + $this->assertEquals($expected, $tables, 'All the tables of the Database Search module have been removed.'); } /** @@ -850,14 +900,20 @@ class BackendTest extends BackendTestBase { // Test different input values, similar to @dataProvider (but with less // overhead). $t = 1400000000; - $f = 'Y-m-d H:i:s'; + $date_time_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT; + $date_format = DateTimeItemInterface::DATE_STORAGE_FORMAT; $test_values = [ 'null' => [NULL, NULL], 'timestamp' => [$t, $t], 'string timestamp' => ["$t", $t], 'float timestamp' => [$t + 0.12, $t], - 'date string' => [gmdate($f, $t), $t], - 'date string with timezone' => [date($f . 'P', $t), $t], + 'date string' => [gmdate($date_time_format, $t), $t], + 'date string with timezone' => [date($date_time_format . 'P', $t), $t], + 'date only' => [ + date($date_format, $t), + // Date-only fields are stored with the default time (12:00:00). + strtotime(date($date_format, $t) . 'T12:00:00+00:00'), + ], ]; // Get storage information for quickly checking the indexed value. diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.info.yml b/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.info.yml index ec79a41cc..e054bd630 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.info.yml +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.info.yml @@ -11,8 +11,8 @@ dependencies: - drupal:taxonomy - drupal:views -# Information added by Drupal.org packaging script on 2018-07-19 -version: '8.x-1.9' +# Information added by Drupal.org packaging script on 2018-11-18 +version: '8.x-1.11' core: '8.x' project: 'search_api' -datestamp: 1531985591 +datestamp: 1542564486 diff --git a/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.install b/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.install index 229a91972..fe1d28197 100644 --- a/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.install +++ b/sites/all/modules/contrib/search/search_api/modules/search_api_views_taxonomy/search_api_views_taxonomy.install @@ -10,15 +10,20 @@ use Drupal\Core\Url; /** * Implements hook_requirements(). */ -function search_api_views_taxonomy_requirements() { - $requirements['search_api_views_taxonomy'] = [ - 'title' => t('Search API Taxonomy Term Handlers'), - 'value' => t('Out-dated'), - 'description' => t('All functionality of the "Search API Taxonomy Term Handlers" module has been moved into the Search API module. You should uninstall this module.', [ - ':uninstall' => Url::fromRoute('system.modules_uninstall')->toString(), - ]), - 'severity' => REQUIREMENT_WARNING, - ]; +function search_api_views_taxonomy_requirements($phase) { + $requirements = []; + + // We don't want to prevent update.php from running. + if ($phase !== 'update') { + $requirements['search_api_views_taxonomy'] = [ + 'title' => t('Search API Taxonomy Term Handlers'), + 'value' => t('Out-dated'), + 'description' => t('All functionality of the "Search API Taxonomy Term Handlers" module has been moved into the Search API module. You should uninstall this module.', [ + ':uninstall' => Url::fromRoute('system.modules_uninstall')->toString(), + ]), + 'severity' => REQUIREMENT_WARNING, + ]; + } return $requirements; } diff --git a/sites/all/modules/contrib/search/search_api/search_api.info.yml b/sites/all/modules/contrib/search/search_api/search_api.info.yml index 018a25860..f2d747f03 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.info.yml +++ b/sites/all/modules/contrib/search/search_api/search_api.info.yml @@ -5,10 +5,10 @@ package: Search # core: 8.x configure: search_api.overview dependencies: - - system (>=8.5) + - drupal:system (>=8.5) -# Information added by Drupal.org packaging script on 2018-07-19 -version: '8.x-1.9' +# Information added by Drupal.org packaging script on 2018-11-18 +version: '8.x-1.11' core: '8.x' project: 'search_api' -datestamp: 1531985591 +datestamp: 1542564486 diff --git a/sites/all/modules/contrib/search/search_api/search_api.routing.yml b/sites/all/modules/contrib/search/search_api/search_api.routing.yml index eb6bf603a..978574262 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.routing.yml +++ b/sites/all/modules/contrib/search/search_api/search_api.routing.yml @@ -12,8 +12,7 @@ search_api.execute_tasks: _controller: '\Drupal\search_api\Controller\TaskController::executeTasks' _title: 'Execute pending tasks' requirements: - _permission: 'administer search_api' - _search_api_tasks: 'TRUE' + _custom_access: '\Drupal\search_api\Controller\TaskController::executeTasksAccess' entity.search_api_server.add_form: path: '/admin/config/search/search-api/add-server' diff --git a/sites/all/modules/contrib/search/search_api/search_api.services.yml b/sites/all/modules/contrib/search/search_api/search_api.services.yml index fc37edd1e..e32b8e6f4 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.services.yml +++ b/sites/all/modules/contrib/search/search_api/search_api.services.yml @@ -1,10 +1,4 @@ services: - access_check.search_api_tasks: - class: Drupal\search_api\Controller\ExecuteTasksAccessCheck - arguments: ['@search_api.task_manager'] - tags: - - { name: access_check, applies_to: _search_api_tasks } - logger.channel.search_api: parent: logger.channel_base arguments: ['search_api'] diff --git a/sites/all/modules/contrib/search/search_api/search_api.views.inc b/sites/all/modules/contrib/search/search_api/search_api.views.inc index 8a80996cb..674c509a1 100644 --- a/sites/all/modules/contrib/search/search_api/search_api.views.inc +++ b/sites/all/modules/contrib/search/search_api/search_api.views.inc @@ -119,7 +119,8 @@ function search_api_views_plugins_cache_alter(array &$plugins) { foreach (Index::loadMultiple() as $index) { $bases[] = 'search_api_index_' . $index->id(); } - $plugins['search_api']['base'] = $bases; + $plugins['search_api_tag']['base'] = $bases; + $plugins['search_api_time']['base'] = $bases; } /** diff --git a/sites/all/modules/contrib/search/search_api/src/Controller/ExecuteTasksAccessCheck.php b/sites/all/modules/contrib/search/search_api/src/Controller/ExecuteTasksAccessCheck.php deleted file mode 100644 index 9f2f39f3c..000000000 --- a/sites/all/modules/contrib/search/search_api/src/Controller/ExecuteTasksAccessCheck.php +++ /dev/null @@ -1,46 +0,0 @@ -tasksManager = $tasksManager; - } - - /** - * Checks access. - * - * @return \Drupal\Core\Access\AccessResultInterface - * The access result. - */ - public function access() { - // @todo Once #2722237 is fixed, see whether this can't just use the - // "search_api_task_list" cache tag instead. - if ($this->tasksManager->getTasksCount()) { - return AccessResult::allowed()->setCacheMaxAge(0); - } - return AccessResult::forbidden()->setCacheMaxAge(0); - } - -} diff --git a/sites/all/modules/contrib/search/search_api/src/Controller/TaskController.php b/sites/all/modules/contrib/search/search_api/src/Controller/TaskController.php index 06c502068..f2a6e17a9 100644 --- a/sites/all/modules/contrib/search/search_api/src/Controller/TaskController.php +++ b/sites/all/modules/contrib/search/search_api/src/Controller/TaskController.php @@ -2,7 +2,9 @@ namespace Drupal\search_api\Controller; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\search_api\Task\TaskManagerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -62,4 +64,23 @@ class TaskController extends ControllerBase { return batch_process(Url::fromRoute('search_api.overview')); } + /** + * Checks access for executing pending tasks. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function executeTasksAccess(AccountInterface $account) { + // @todo Once we depend on Drupal 8.7+, check whether this can't just use + // the "search_api_task_list" cache tag instead. (See #2722237.) + if ($this->taskManager->getTasksCount()) { + return AccessResult::allowedIfHasPermission($account, 'administer search_api') + ->setCacheMaxAge(0); + } + return AccessResult::forbidden()->setCacheMaxAge(0); + } + } diff --git a/sites/all/modules/contrib/search/search_api/src/DataType/DataTypeInterface.php b/sites/all/modules/contrib/search/search_api/src/DataType/DataTypeInterface.php index ddbd41440..2303f7f4e 100644 --- a/sites/all/modules/contrib/search/search_api/src/DataType/DataTypeInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/DataType/DataTypeInterface.php @@ -5,6 +5,7 @@ namespace Drupal\search_api\DataType; use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\search_api\Plugin\HideablePluginInterface; /** * Defines an interface for data type plugins. @@ -14,7 +15,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; * @see \Drupal\search_api\DataType\DataTypePluginBase * @see plugin_api */ -interface DataTypeInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface { +interface DataTypeInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface { /** * Returns the label of the data type. diff --git a/sites/all/modules/contrib/search/search_api/src/DataType/DataTypePluginBase.php b/sites/all/modules/contrib/search/search_api/src/DataType/DataTypePluginBase.php index c81fe019a..ebd0b3e66 100644 --- a/sites/all/modules/contrib/search/search_api/src/DataType/DataTypePluginBase.php +++ b/sites/all/modules/contrib/search/search_api/src/DataType/DataTypePluginBase.php @@ -2,7 +2,7 @@ namespace Drupal\search_api\DataType; -use Drupal\Core\Plugin\PluginBase; +use Drupal\search_api\Plugin\HideablePluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -40,7 +40,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * @see \Drupal\search_api\DataType\DataTypeInterface * @see plugin_api */ -abstract class DataTypePluginBase extends PluginBase implements DataTypeInterface { +abstract class DataTypePluginBase extends HideablePluginBase implements DataTypeInterface { /** * {@inheritdoc} diff --git a/sites/all/modules/contrib/search/search_api/src/Display/DisplayInterface.php b/sites/all/modules/contrib/search/search_api/src/Display/DisplayInterface.php index 99af31db1..c5969a38f 100644 --- a/sites/all/modules/contrib/search/search_api/src/Display/DisplayInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/Display/DisplayInterface.php @@ -6,6 +6,7 @@ use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\search_api\Plugin\HideablePluginInterface; /** * Defines an interface for display plugins. @@ -15,7 +16,7 @@ use Drupal\Component\Plugin\DependentPluginInterface; * @see \Drupal\search_api\Display\DisplayPluginBase * @see plugin_api */ -interface DisplayInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface, DependentPluginInterface { +interface DisplayInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface, DependentPluginInterface { /** * Returns the display label. diff --git a/sites/all/modules/contrib/search/search_api/src/Display/DisplayPluginBase.php b/sites/all/modules/contrib/search/search_api/src/Display/DisplayPluginBase.php index 1a22d1f15..8828cc401 100644 --- a/sites/all/modules/contrib/search/search_api/src/Display/DisplayPluginBase.php +++ b/sites/all/modules/contrib/search/search_api/src/Display/DisplayPluginBase.php @@ -4,8 +4,8 @@ namespace Drupal\search_api\Display; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Path\CurrentPathStack; -use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Url; +use Drupal\search_api\Plugin\HideablePluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -33,7 +33,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * @see \Drupal\search_api\Display\DisplayInterface * @see plugin_api */ -abstract class DisplayPluginBase extends PluginBase implements DisplayInterface { +abstract class DisplayPluginBase extends HideablePluginBase implements DisplayInterface { /** * The current path service. diff --git a/sites/all/modules/contrib/search/search_api/src/Entity/Server.php b/sites/all/modules/contrib/search/search_api/src/Entity/Server.php index 74b7e5366..61e858eae 100644 --- a/sites/all/modules/contrib/search/search_api/src/Entity/Server.php +++ b/sites/all/modules/contrib/search/search_api/src/Entity/Server.php @@ -51,12 +51,12 @@ use Drupal\search_api\Utility\Utility; * "backend_config", * }, * links = { - * "canonical" = "/admin/config/search/search-api/index/{search_api_server}", + * "canonical" = "/admin/config/search/search-api/server/{search_api_server}", * "add-form" = "/admin/config/search/search-api/add-server", - * "edit-form" = "/admin/config/search/search-api/index/{search_api_server}/edit", - * "delete-form" = "/admin/config/search/search-api/index/{search_api_server}/delete", - * "disable" = "/admin/config/search/search-api/index/{search_api_server}/disable", - * "enable" = "/admin/config/search/search-api/index/{search_api_server}/enable", + * "edit-form" = "/admin/config/search/search-api/server/{search_api_server}/edit", + * "delete-form" = "/admin/config/search/search-api/server/{search_api_server}/delete", + * "disable" = "/admin/config/search/search-api/server/{search_api_server}/disable", + * "enable" = "/admin/config/search/search-api/server/{search_api_server}/enable", * } * ) */ diff --git a/sites/all/modules/contrib/search/search_api/src/Form/IndexFieldsForm.php b/sites/all/modules/contrib/search/search_api/src/Form/IndexFieldsForm.php index cab71b055..9e17f9ae5 100644 --- a/sites/all/modules/contrib/search/search_api/src/Form/IndexFieldsForm.php +++ b/sites/all/modules/contrib/search/search_api/src/Form/IndexFieldsForm.php @@ -187,6 +187,9 @@ class IndexFieldsForm extends EntityForm { $data_types = []; foreach ($instances as $name => $type) { + if ($type->isHidden()) { + continue; + } $data_types[$name] = [ 'label' => $type->label(), 'description' => $type->getDescription(), @@ -219,7 +222,6 @@ class IndexFieldsForm extends EntityForm { * The build structure. */ protected function buildFieldsTable(array $fields) { - $types = $this->dataTypePluginManager->getInstancesOptions(); $fallback_types = $this->dataTypeHelper ->getDataTypeFallbackMapping($this->entity); @@ -236,6 +238,7 @@ class IndexFieldsForm extends EntityForm { } } + $types = []; $fulltext_types = [ [ 'value' => 'text', @@ -243,6 +246,9 @@ class IndexFieldsForm extends EntityForm { ]; // Add all data types with fallback "text" to fulltext types as well. foreach ($this->dataTypePluginManager->getInstances() as $type_id => $type) { + if (!$type->isHidden()) { + $types[$type_id] = $type->label(); + } if ($type->getFallbackType() == 'text') { $fulltext_types[] = [ 'value' => $type_id, @@ -488,7 +494,8 @@ class IndexFieldsForm extends EntityForm { $this->messenger->addStatus($this->t('The changes were successfully saved.')); if ($this->entity->isReindexing()) { - $this->messenger->addStatus($this->t('All content was scheduled for reindexing so the new settings can take effect.')); + $url = $this->entity->toUrl('canonical'); + $this->messenger->addStatus($this->t('All content was scheduled for reindexing so the new settings can take effect.', [':url' => $url->toString()])); } return SAVED_UPDATED; diff --git a/sites/all/modules/contrib/search/search_api/src/Form/IndexForm.php b/sites/all/modules/contrib/search/search_api/src/Form/IndexForm.php index 7e4e6f7c9..5615fd994 100644 --- a/sites/all/modules/contrib/search/search_api/src/Form/IndexForm.php +++ b/sites/all/modules/contrib/search/search_api/src/Form/IndexForm.php @@ -2,7 +2,6 @@ namespace Drupal\search_api\Form; -use Drupal\Component\Utility\Html; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -13,6 +12,7 @@ use Drupal\Core\Form\SubformState; use Drupal\Core\Utility\Error; use Drupal\search_api\IndexInterface; use Drupal\search_api\Utility\PluginHelperInterface; +use Drupal\search_api\Utility\Utility; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -96,7 +96,7 @@ class IndexForm extends EntityForm { ->loadMultiple(); foreach ($servers as $server_id => $server) { // @todo Special formatting for disabled servers. - $options[$server_id] = Html::escape($server->label()); + $options[$server_id] = Utility::escapeHtml($server->label()); } return $options; } @@ -185,8 +185,11 @@ class IndexForm extends EntityForm { ]; $datasource_options = []; foreach ($this->pluginHelper->createDatasourcePlugins($index) as $datasource_id => $datasource) { - $datasource_options[$datasource_id] = $datasource->label(); - $form['datasources'][$datasource_id]['#description'] = $datasource->getDescription(); + if ($datasource->isHidden()) { + continue; + } + $datasource_options[$datasource_id] = Utility::escapeHtml($datasource->label()); + $form['datasources'][$datasource_id]['#description'] = Utility::escapeHtml($datasource->getDescription()); } asort($datasource_options, SORT_NATURAL | SORT_FLAG_CASE); $form['datasources']['#options'] = $datasource_options; @@ -230,8 +233,11 @@ class IndexForm extends EntityForm { ]; $tracker_options = []; foreach ($this->pluginHelper->createTrackerPlugins($index) as $tracker_id => $tracker) { - $tracker_options[$tracker_id] = $tracker->label(); - $form['tracker'][$tracker_id]['#description'] = $tracker->getDescription(); + if ($tracker->isHidden()) { + continue; + } + $tracker_options[$tracker_id] = Utility::escapeHtml($tracker->label()); + $form['tracker'][$tracker_id]['#description'] = Utility::escapeHtml($tracker->getDescription()); } asort($tracker_options, SORT_NATURAL | SORT_FLAG_CASE); $form['tracker']['#options'] = $tracker_options; @@ -419,7 +425,7 @@ class IndexForm extends EntityForm { $form['tracker_config']['#type'] = 'details'; $form['tracker_config']['#title'] = $this->t('Configure the %plugin tracker', ['%plugin' => $tracker->label()]); - $form['tracker_config']['#description'] = Html::escape($tracker->getDescription()); + $form['tracker_config']['#description'] = Utility::escapeHtml($tracker->getDescription()); $form['tracker_config']['#open'] = $index->isNew(); // If the user changed the tracker and the new one has a config form, show diff --git a/sites/all/modules/contrib/search/search_api/src/Form/IndexProcessorsForm.php b/sites/all/modules/contrib/search/search_api/src/Form/IndexProcessorsForm.php index c8bca3e3c..bb2b7a7d2 100644 --- a/sites/all/modules/contrib/search/search_api/src/Form/IndexProcessorsForm.php +++ b/sites/all/modules/contrib/search/search_api/src/Form/IndexProcessorsForm.php @@ -336,7 +336,7 @@ class IndexProcessorsForm extends EntityForm { $save_status = parent::save($form, $form_state); $this->messenger->addStatus($this->t('The indexing workflow was successfully edited.')); if ($this->entity->isReindexing()) { - $url = $this->entity->toUrl(); + $url = $this->entity->toUrl('canonical'); $this->messenger->addStatus($this->t('All content was scheduled for reindexing so the new settings can take effect.', [':url' => $url->toString()])); } } diff --git a/sites/all/modules/contrib/search/search_api/src/Form/ServerForm.php b/sites/all/modules/contrib/search/search_api/src/Form/ServerForm.php index 4e65fec9a..eea3d372b 100644 --- a/sites/all/modules/contrib/search/search_api/src/Form/ServerForm.php +++ b/sites/all/modules/contrib/search/search_api/src/Form/ServerForm.php @@ -2,6 +2,7 @@ namespace Drupal\search_api\Form; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Form\FormStateInterface; @@ -12,6 +13,7 @@ use Drupal\Core\Url; use Drupal\Core\Utility\Error; use Drupal\search_api\Backend\BackendPluginManager; use Drupal\search_api\ServerInterface; +use Drupal\search_api\Utility\Utility; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -140,10 +142,19 @@ class ServerForm extends EntityForm { foreach ($backends as $backend_id => $definition) { $config = $backend_id === $server->getBackendId() ? $server->getBackendConfig() : []; $config['#server'] = $server; - $backend = $this->backendPluginManager - ->createInstance($backend_id, $config); - $backend_options[$backend_id] = $backend->label(); - $descriptions[$backend_id]['#description'] = $backend->getDescription(); + try { + /** @var \Drupal\search_api\Backend\BackendInterface $backend */ + $backend = $this->backendPluginManager + ->createInstance($backend_id, $config); + } + catch (PluginException $e) { + continue; + } + if ($backend->isHidden()) { + continue; + } + $backend_options[$backend_id] = Utility::escapeHtml($backend->label()); + $descriptions[$backend_id]['#description'] = Utility::escapeHtml($backend->getDescription()); } asort($backend_options, SORT_NATURAL | SORT_FLAG_CASE); if ($backend_options) { diff --git a/sites/all/modules/contrib/search/search_api/src/IndexListBuilder.php b/sites/all/modules/contrib/search/search_api/src/IndexListBuilder.php index 4898c62a6..50a324010 100644 --- a/sites/all/modules/contrib/search/search_api/src/IndexListBuilder.php +++ b/sites/all/modules/contrib/search/search_api/src/IndexListBuilder.php @@ -2,6 +2,7 @@ namespace Drupal\search_api; +use Drupal\Component\Plugin\Exception\PluginException; use Drupal\Component\Utility\Html; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Config\Entity\ConfigEntityListBuilder; @@ -9,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Url; +use Drupal\node\Entity\NodeType; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -50,6 +52,75 @@ class IndexListBuilder extends ConfigEntityListBuilder { ); } + /** + * Determines whether the "Database Search Defaults" module can be installed. + * + * @return \Drupal\Core\StringTranslation\TranslatableMarkup[] + * An array of error messages describing why the module cannot be installed, + * keyed by a short, machine name-like identifier for the kind of error. If + * the array is empty, the module can be installed. + */ + public static function checkDefaultsModuleCanBeInstalled() { + $errors = []; + + $node_types = NodeType::loadMultiple(); + $required_types = [ + 'article' => ['body', 'comment', 'field_tags', 'field_image'], + 'page' => ['body'], + ]; + + /** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */ + $entity_field_manager = \Drupal::service('entity_field.manager'); + + foreach ($required_types as $required_type_id => $required_fields) { + if (!isset($node_types[$required_type_id])) { + $errors[$required_type_id] = t('Content type @content_type not found. Database Search Defaults module could not be installed.', ['@content_type' => $required_type_id]); + } + else { + // Check if all the fields are here. + $fields = $entity_field_manager->getFieldDefinitions('node', $required_type_id); + foreach ($required_fields as $required_field) { + if (!isset($fields[$required_field])) { + $errors[$required_type_id . ':' . $required_field] = t('Field @field in content type @node_type not found. Database Search Defaults module could not be installed', [ + '@node_type' => $required_type_id, + '@field' => $required_field + ]); + } + } + } + } + + if (\Drupal::moduleHandler()->moduleExists('search_api_db')) { + $entities_to_check = [ + 'search_api_index' => 'default_index', + 'search_api_server' => 'default_server', + 'view' => 'search_content', + ]; + + /** @var \Drupal\Core\Entity\EntityTypeManager $entity_type_manager */ + $entity_type_manager = \Drupal::service('entity_type.manager'); + foreach ($entities_to_check as $entity_type => $entity_id) { + try { + // Find out if the entity is already in place. If so, fail to install + // the module. + $entity_storage = $entity_type_manager->getStorage($entity_type); + $entity_storage->resetCache(); + $entity = $entity_storage->load($entity_id); + if ($entity) { + $errors['defaults_exist'] = t('It looks like the default setup provided by this module already exists on your site. Cannot re-install module.'); + break; + } + } + catch (PluginException $e) { + // This can only happen for the view, if the Views module isn't + // installed. Ignore. + } + } + } + + return $errors; + } + /** * {@inheritdoc} */ @@ -160,7 +231,7 @@ class IndexListBuilder extends ConfigEntityListBuilder { '#type' => 'table', '#header' => $this->buildHeader(), '#rows' => [], - '#empty' => $entity_groups['lone_indexes'] ? '' : $this->t('There are no servers or indexes defined. For a quick start, we suggest you install the Database Search Defaults module.'), + '#empty' => '', '#attributes' => [ 'id' => 'search-api-entity-list', 'class' => ['search-api-entity-list'], @@ -186,6 +257,14 @@ class IndexListBuilder extends ConfigEntityListBuilder { $list['lone_indexes']['table']['#rows'][$entity->id()] = $this->buildRow($entity); } } + elseif (!$list['servers']['#rows']) { + if (static::checkDefaultsModuleCanBeInstalled() === []) { + $list['servers']['#empty'] = $this->t('There are no servers or indexes defined. For a quick start, we suggest you install the Database Search Defaults module.'); + } + else { + $list['servers']['#empty'] = $this->t('There are no servers or indexes defined.'); + } + } return $list; } diff --git a/sites/all/modules/contrib/search/search_api/src/Item/ItemInterface.php b/sites/all/modules/contrib/search/search_api/src/Item/ItemInterface.php index 7091fdacd..a3fdbb6f5 100644 --- a/sites/all/modules/contrib/search/search_api/src/Item/ItemInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/Item/ItemInterface.php @@ -253,6 +253,8 @@ interface ItemInterface extends \Traversable { * highlighting data should also include any non-highlighted field values, * to avoid having to determine which values are included and which * aren't. + * - highlighted_keys: The exact tokens that matched keys in this item's + * text values. The value is an array of strings. * However, contrib modules can define arbitrary other keys. (Usually they * should be prefixed with the module name, though.) * @param mixed $default diff --git a/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModeInterface.php b/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModeInterface.php index 718cfee7a..46b1fc3d9 100644 --- a/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModeInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModeInterface.php @@ -5,6 +5,7 @@ namespace Drupal\search_api\ParseMode; use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\search_api\Plugin\HideablePluginInterface; /** * Defines an interface for parse mode plugins. @@ -14,7 +15,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; * @see \Drupal\search_api\ParseMode\ParseModePluginBase * @see plugin_api */ -interface ParseModeInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface { +interface ParseModeInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, ContainerFactoryPluginInterface { /** * Returns the label of the parse mode. diff --git a/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModePluginBase.php b/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModePluginBase.php index 4adff63fe..6e42e81db 100644 --- a/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModePluginBase.php +++ b/sites/all/modules/contrib/search/search_api/src/ParseMode/ParseModePluginBase.php @@ -2,7 +2,7 @@ namespace Drupal\search_api\ParseMode; -use Drupal\Core\Plugin\PluginBase; +use Drupal\search_api\Plugin\HideablePluginBase; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -31,7 +31,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; * @see \Drupal\search_api\ParseMode\ParseModeInterface * @see plugin_api */ -abstract class ParseModePluginBase extends PluginBase implements ParseModeInterface { +abstract class ParseModePluginBase extends HideablePluginBase implements ParseModeInterface { /** * The default conjunction to use when parsing keywords. diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginBase.php b/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginBase.php index 12d776341..37c1e37ca 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginBase.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginBase.php @@ -2,14 +2,13 @@ namespace Drupal\search_api\Plugin; -use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Plugin\PluginDependencyTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a base class for all configurable Search API plugins. */ -abstract class ConfigurablePluginBase extends PluginBase implements ConfigurablePluginInterface { +abstract class ConfigurablePluginBase extends HideablePluginBase implements ConfigurablePluginInterface { // Normally, we'd just need \Drupal\Core\Entity\DependencyTrait here for // plugins. However, in a few cases, plugins use plugins themselves, and then diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginInterface.php b/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginInterface.php index 71f5bd30a..44d3b5af7 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/ConfigurablePluginInterface.php @@ -16,7 +16,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; * @see \Drupal\Core\Plugin\PluginFormInterface * @see \Drupal\search_api\Plugin\PluginFormTrait */ -interface ConfigurablePluginInterface extends PluginInspectionInterface, DerivativeInspectionInterface, DrupalConfigurablePluginInterface, ContainerFactoryPluginInterface { +interface ConfigurablePluginInterface extends HideablePluginInterface, PluginInspectionInterface, DerivativeInspectionInterface, DrupalConfigurablePluginInterface, ContainerFactoryPluginInterface { /** * Returns the label for use on the administration pages. diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginBase.php b/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginBase.php new file mode 100644 index 000000000..ea0154e90 --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginBase.php @@ -0,0 +1,19 @@ +pluginDefinition['no_ui']); + } + +} diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginInterface.php b/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginInterface.php new file mode 100644 index 000000000..5925118dd --- /dev/null +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/HideablePluginInterface.php @@ -0,0 +1,18 @@ +setDefaultDateTime(); + } return $date->getTimestamp(); } diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/data_type/value/TextTokenInterface.php b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/data_type/value/TextTokenInterface.php index 7b6b2dae0..257efb48d 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/data_type/value/TextTokenInterface.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/data_type/value/TextTokenInterface.php @@ -11,15 +11,15 @@ interface TextTokenInterface { * Retrieves the text value of this token. * * @return string - * The the text value of this token. + * The text value of this token. */ public function getText(); /** - * Sets the the text value of this token. + * Sets the text value of this token. * * @param string $text - * The new the text value of this token. + * The new text value of this token. * * @return $this */ diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/datasource/ContentEntity.php b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/datasource/ContentEntity.php index 88116a71d..6130fc6c8 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/datasource/ContentEntity.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/datasource/ContentEntity.php @@ -2,7 +2,6 @@ namespace Drupal\search_api\Plugin\search_api\datasource; -use Drupal\Component\Utility\Html; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; @@ -11,6 +10,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface; +use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -507,7 +507,7 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte $options = []; if (($bundles = $this->getEntityBundles())) { foreach ($bundles as $bundle => $bundle_info) { - $options[$bundle] = Html::escape($bundle_info['label']); + $options[$bundle] = Utility::escapeHtml($bundle_info['label']); } } return $options; @@ -677,6 +677,9 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte ->getStorage($this->getEntityTypeId()) ->getQuery(); + // When tracking items, we never want access checks. + $select->accessCheck(FALSE); + // We want to determine all entities of either one of the given bundles OR // one of the given languages. That means we can't just filter for $bundles // if $languages is given. Instead, we have to filter for all bundles we @@ -930,6 +933,13 @@ class ContentEntity extends DatasourcePluginBase implements EntityDatasourceInte } } + // The field might be provided by a module which is not the provider of the + // entity type, therefore we need to add a dependency on that module. + if ($property instanceof FieldStorageDefinitionInterface) { + $provider = $property->getProvider(); + $dependencies['module'][$provider] = $provider; + } + $property = $this->getFieldsHelper()->getInnerProperty($property); if ($property instanceof EntityDataDefinitionInterface) { diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AddHierarchy.php b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AddHierarchy.php index 8c165e304..80b24388d 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AddHierarchy.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AddHierarchy.php @@ -2,7 +2,6 @@ namespace Drupal\search_api\Plugin\search_api\processor; -use Drupal\Component\Utility\Html; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface; @@ -13,6 +12,7 @@ use Drupal\search_api\IndexInterface; use Drupal\search_api\Item\FieldInterface; use Drupal\search_api\Plugin\PluginFormTrait; use Drupal\search_api\Processor\ProcessorPluginBase; +use Drupal\search_api\Utility\Utility; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -151,6 +151,7 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface { */ protected function findHierarchicalProperties(EntityDataDefinitionInterface $property, $property_label) { $entity_type_id = $property->getEntityTypeId(); + $property_label = Utility::escapeHtml($property_label); $options = []; // Check properties for potential hierarchy. Check two levels down, since @@ -159,9 +160,10 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface { foreach ($this->getFieldsHelper()->getNestedProperties($property) as $name_2 => $property_2) { $property_2_label = $property_2->getLabel(); $property_2 = $this->getFieldsHelper()->getInnerProperty($property_2); + $is_reference = FALSE; if ($property_2 instanceof EntityDataDefinitionInterface) { if ($property_2->getEntityTypeId() == $entity_type_id) { - $options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label); + $is_reference = TRUE; } } elseif ($property_2 instanceof ComplexDataDefinitionInterface) { @@ -169,12 +171,16 @@ class AddHierarchy extends ProcessorPluginBase implements PluginFormInterface { $property_3 = $this->getFieldsHelper()->getInnerProperty($property_3); if ($property_3 instanceof EntityDataDefinitionInterface) { if ($property_3->getEntityTypeId() == $entity_type_id) { - $options["$entity_type_id-$name_2"] = Html::escape($property_label . ' » ' . $property_2_label); + $is_reference = TRUE; break; } } } } + if ($is_reference) { + $property_2_label = Utility::escapeHtml($property_2_label); + $options["$entity_type_id-$name_2"] = $property_label . ' » ' . $property_2_label; + } } return $options; } diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AggregatedFields.php b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AggregatedFields.php index 595c6fbea..87952a26d 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AggregatedFields.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/AggregatedFields.php @@ -94,11 +94,15 @@ class AggregatedFields extends ProcessorPluginBase { break; case 'max': - $values = [max($values)]; + if ($values) { + $values = [max($values)]; + } break; case 'min': - $values = [min($values)]; + if ($values) { + $values = [min($values)]; + } break; case 'first': diff --git a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/Highlight.php b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/Highlight.php index c94425b24..f98a5a6c0 100644 --- a/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/Highlight.php +++ b/sites/all/modules/contrib/search/search_api/src/Plugin/search_api/processor/Highlight.php @@ -279,11 +279,22 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface { foreach ($item as $values) { $text = array_merge($text, $values); } + $item_keys = $keys; + + // If the backend already did highlighting and told us the exact keys it + // found in the item's text values, we can use those for our own + // highlighting. This will help us take stemming, transliteration, etc. + // into account properly. + $highlighted_keys = $results[$item_id]->getExtraData('highlighted_keys'); + if ($highlighted_keys) { + $item_keys = array_unique(array_merge($keys, $highlighted_keys)); + } + // @todo This is pretty poor handling for the borders between different // values/fields. Better would be to pass an array and have proper // handling of this in createExcerpt(), ensuring that no snippet goes // across multiple values/fields. - $results[$item_id]->setExcerpt($this->createExcerpt(implode($this->getEllipses()[1], $text), $keys)); + $results[$item_id]->setExcerpt($this->createExcerpt(implode($this->getEllipses()[1], $text), $item_keys)); } } @@ -438,12 +449,15 @@ class Highlight extends ProcessorPluginBase implements PluginFormInterface { * created. */ protected function createExcerpt($text, array $keys) { + // Remove HTML tags + +And another foo! +END; + $fields = [ + 'entity:node/body' => [ + 'type' => 'text', + 'values' => [$body_value], + ], + ]; + + $items = $this->createItems($this->index, 1, $fields); + + $results = new ResultSet($query); + $results->setResultItems($items); + $results->setResultCount(1); + + $this->processor->postprocessSearchResults($results); + + $output = $results->getResultItems(); + $excerpt = $output[$this->itemIds[0]]->getExcerpt(); + $correct_output = '… Sentence with foo keyword. And another foo! …'; + $this->assertEquals($correct_output, $excerpt, 'Excerpt was added.'); + } + /** * Tests whether highlighting works on a longer text matching near the end. */ @@ -600,6 +654,50 @@ class HighlightTest extends UnitTestCase { $this->assertEmpty($excerpt, 'No excerpt added when disabled.'); } + /** + * Tests whether excerpt creation uses the "highlighted_keys" extra data. + */ + public function testPostprocessSearchResultsExcerptWithKeysFromBackend() { + $query = $this->createMock(QueryInterface::class); + $query->expects($this->once()) + ->method('getProcessingLevel') + ->willReturn(QueryInterface::PROCESSING_FULL); + $query->expects($this->atLeastOnce()) + ->method('getOriginalKeys') + ->will($this->returnValue(['#conjunction' => 'AND', 'congues'])); + /** @var \Drupal\search_api\Query\QueryInterface $query */ + + $field = $this->createTestField('body', 'entity:node/body'); + + $this->index->expects($this->atLeastOnce()) + ->method('getFields') + ->will($this->returnValue(['body' => $field])); + + $this->processor->setIndex($this->index); + + $body_values = [$this->getFieldBody()]; + $fields = [ + 'entity:node/body' => [ + 'type' => 'text', + 'values' => $body_values, + ], + ]; + + $items = $this->createItems($this->index, 1, $fields); + $items[$this->itemIds[0]]->setExtraData('highlighted_keys', ['congue']); + + $results = new ResultSet($query); + $results->setResultItems($items); + $results->setResultCount(1); + + $this->processor->postprocessSearchResults($results); + + $output = $results->getResultItems(); + $excerpt = $output[$this->itemIds[0]]->getExcerpt(); + $correct_output = '… tristique, ligula sit amet condimentum dapibus, lorem nunc congue velit, et dictum augue leo sodales augue. Maecenas …'; + $this->assertEquals($correct_output, $excerpt, 'Excerpt was added.'); + } + /** * Tests whether highlighting works on a longer text. */ diff --git a/sites/all/modules/contrib/taxonomy/synonyms/src/Plugin/Field/FieldWidget/EntityReferenceSynonymsAutocomplete.php b/sites/all/modules/contrib/taxonomy/synonyms/src/Plugin/Field/FieldWidget/EntityReferenceSynonymsAutocomplete.php index f99ad2814..68b0e2156 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/src/Plugin/Field/FieldWidget/EntityReferenceSynonymsAutocomplete.php +++ b/sites/all/modules/contrib/taxonomy/synonyms/src/Plugin/Field/FieldWidget/EntityReferenceSynonymsAutocomplete.php @@ -134,6 +134,10 @@ class EntityReferenceSynonymsAutocomplete extends WidgetBase implements Containe '#target_bundles' => $this->getFieldSetting('handler_settings')['target_bundles'], '#suggestion_size' => $this->getSetting('suggestion_size'), '#suggest_only_unique' => $this->getSetting('suggest_only_unique'), + // Entity reference field items are handling validation themselves via + // the 'ValidReference' constraint. + '#validate_reference' => FALSE, + '#maxlength' => 1024, '#match' => $this->getSetting('match'), '#default_value' => $default_value, ]; diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info.yml b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info.yml index 0b305730d..5916083ee 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info.yml +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms.info.yml @@ -7,8 +7,8 @@ configure: entity.synonym.overview dependencies: - system (>=8.2) -# Information added by Drupal.org packaging script on 2018-03-11 -version: '8.x-1.0-alpha1+4-dev' +# Information added by Drupal.org packaging script on 2018-10-21 +version: '8.x-1.0-alpha1+5-dev' core: '8.x' project: 'synonyms' -datestamp: 1520765586 +datestamp: 1540141684 diff --git a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info.yml b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info.yml index c3be36439..01b4e0878 100644 --- a/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info.yml +++ b/sites/all/modules/contrib/taxonomy/synonyms/synonyms_search/synonyms_search.info.yml @@ -7,8 +7,8 @@ dependencies: - synonyms - search -# Information added by Drupal.org packaging script on 2018-03-11 -version: '8.x-1.0-alpha1+4-dev' +# Information added by Drupal.org packaging script on 2018-10-21 +version: '8.x-1.0-alpha1+5-dev' core: '8.x' project: 'synonyms' -datestamp: 1520765586 +datestamp: 1540141684 diff --git a/sites/all/modules/contrib/users/email_registration/README.txt b/sites/all/modules/contrib/users/email_registration/README.txt index 68bc584cb..4021cc533 100644 --- a/sites/all/modules/contrib/users/email_registration/README.txt +++ b/sites/all/modules/contrib/users/email_registration/README.txt @@ -1,44 +1,45 @@ -Email Registration allows users to register and login with their e-mail address -instead of using a separate username in addition to the e-mail address. It will -automatically generate a username based on the e-mail address but that behavior +INTRODUCTION +------------ + +Email Registration allows users to register and login with their email address +instead of using a separate username in addition to the email address. It will +automatically generate a username based on the email address but that behavior can be overridden with a custom hook implementation in a site specific module. INSTALLATION -============ +------------ Required step: -1. Enable the module as you normally would. +Enable the module as you normally would. See: +https://www.drupal.org/docs/8/extending-drupal-8/installing-modules -Optional steps: +CONFIGURATION +------------- -2. You will probably want to change the welcome e-mail - (Administer -> User Management -> User Settings) and replace instances of - the token !username with !mailto +* You will probably want to change the welcome email (Administration + -> Configuration -> People -> Account Settings) and replace instances of the + token [user:display-name] with [user:mail]. -3. This automatically generated username is still displayed name for posts, - comments, etc. You can allow your users to change their username by - going to: (Administer -> User Management -> Access Control) and granting - the permission to "change own username" - This privilege allows a user to change their username in "My Account". +* The automatically generated username is still displayed name for posts, + comments, etc. You can allow your users to change their username by granting + the permission (Administraion -> People -> Permissions) to "change own + username". This privilege allows a user to change their username in "My + Account". -4. If a user enters an invalid email or password they will see a message: - "Sorry, unrecognized username or password. Have you forgotten your password?" +* If a user enters an invalid email or password they will see a message: + "Unrecognized username or password. Forgot your password?" That message is confusing because it mentions username when all other - language on the page mentions entering their E-mail. This can be easily + language on the page mentions entering their email. This can be easily overridden in your settings.php file with an entry like this: -$conf['locale_custom_strings_en'][''] = array( - 'Sorry, unrecognized username or password. Have you forgotten your password?' => 'Sorry, unrecognized e-mail or password. Have you forgotten your password?', -); - - - +$settings['locale_custom_strings_en'][''] = [ + 'Unrecognized username or password. Forgot your password?' => 'Unrecognized e-mail address or password. Forgot your password?', +]; BUGS, FEATURES, QUESTIONS -========================= +------------------------- Post any bugs, features or questions to the issue queue: http://drupal.org/project/issues/email_registration - diff --git a/sites/all/modules/contrib/users/email_registration/composer.json b/sites/all/modules/contrib/users/email_registration/composer.json new file mode 100644 index 000000000..63cc982d0 --- /dev/null +++ b/sites/all/modules/contrib/users/email_registration/composer.json @@ -0,0 +1,34 @@ +{ + "name": "drupal/email_registration", + "type": "drupal-module", + "description": "Allows users to register with an email address as their username.", + "license": "GPL-2.0+", + "homepage": "https://www.drupal.org/project/email_registration", + "minimum-stability": "dev", + "authors": [ + { + "name": "Greg Knaddison (greggles)", + "homepage": "https://www.drupal.org/u/greggles", + "role": "Maintainer" + }, + { + "name": "Andrey Postnikov (andypost)", + "homepage": "https://www.drupal.org/u/andypost", + "role": "Maintainer" + }, + { + "name": "Chris Herberte", + "homepage": "https://www.drupal.org/u/chris-herberte", + "role": "Maintainer" + }, + { + "name": "Moshe Weitzman (moshe weitzman)", + "homepage": "https://www.drupal.org/u/moshe-weitzman", + "role": "Maintainer" + } + ], + "support": { + "issues": "http://drupal.org/project/issues/email_registration", + "source": "http://cgit.drupalcode.org/email_registration" + } +} diff --git a/sites/all/modules/contrib/users/email_registration/config/schema/email_registration.schema.yml b/sites/all/modules/contrib/users/email_registration/config/schema/email_registration.schema.yml index a13fdd7c3..00fcc8ee1 100644 --- a/sites/all/modules/contrib/users/email_registration/config/schema/email_registration.schema.yml +++ b/sites/all/modules/contrib/users/email_registration/config/schema/email_registration.schema.yml @@ -4,4 +4,4 @@ email_registration.settings: mapping: login_with_username: type: boolean - label: 'Allow users to log in with e-mail or username.' + label: 'Allow users to log in with email address or username.' diff --git a/sites/all/modules/contrib/users/email_registration/email_registration.info.yml b/sites/all/modules/contrib/users/email_registration/email_registration.info.yml index 15295fb15..2e2003e9f 100644 --- a/sites/all/modules/contrib/users/email_registration/email_registration.info.yml +++ b/sites/all/modules/contrib/users/email_registration/email_registration.info.yml @@ -1,12 +1,12 @@ name: Email Registration type: module -description: 'Allows users to register with an e-mail address as their username.' +description: 'Allows users to register with an email address as their username.' # core: 8.x dependencies: - drupal:user -# Information added by Drupal.org packaging script on 2017-04-04 -version: '8.x-1.0-rc5' +# Information added by Drupal.org packaging script on 2018-12-21 +version: '8.x-1.0-rc6' core: '8.x' project: 'email_registration' -datestamp: 1491318185 +datestamp: 1545350889 diff --git a/sites/all/modules/contrib/users/email_registration/email_registration.install b/sites/all/modules/contrib/users/email_registration/email_registration.install index 3d411999c..8c31c7560 100644 --- a/sites/all/modules/contrib/users/email_registration/email_registration.install +++ b/sites/all/modules/contrib/users/email_registration/email_registration.install @@ -9,23 +9,23 @@ * Implements hook_requirements(). */ function email_registration_requirements() { - $requirements = array(); + $requirements = []; // @todo Get rid of variable_get() and check is this conflict still valid. if (\Drupal::moduleHandler()->moduleExists('logintoboggan') && \Drupal::config('logintoboggan.settings')->get('login_with_email')) { - $requirements['email_registration'] = array( + $requirements['email_registration'] = [ 'title' => t('Email Registration / LoginToboggan Conflict'), 'value' => t('Conflict'), - 'description' => t('There is a conflict between %email_registration and %logintoboggan. You should disable the "Allow users to login using their e-mail address" option from %logintoboggan.', array('%email_registration' => 'Email registration', '%logintoboggan' => 'Login Toboggan')), + 'description' => t('There is a conflict between %email_registration and %logintoboggan. You should disable the "Allow users to login using their email address" option from %logintoboggan.', ['%email_registration' => 'Email registration', '%logintoboggan' => 'Login Toboggan']), 'severity' => REQUIREMENT_ERROR, - ); + ]; } return $requirements; } /** - * Add an option to log in with the username as well as the e-mail address. + * Add an option to log in with the username as well as the email address. */ function email_registration_update_8100() { $config_factory = \Drupal::configFactory(); diff --git a/sites/all/modules/contrib/users/email_registration/email_registration.module b/sites/all/modules/contrib/users/email_registration/email_registration.module index e172bd2d3..71095c2ec 100644 --- a/sites/all/modules/contrib/users/email_registration/email_registration.module +++ b/sites/all/modules/contrib/users/email_registration/email_registration.module @@ -2,7 +2,7 @@ /** * @file - * Allows users to register with an e-mail address as their username. + * Allows users to register with an email address as their username. */ use Drupal\Component\Utility\Unicode; @@ -26,7 +26,7 @@ function email_registration_user_insert(UserInterface $account) { // Other modules may implement hook_email_registration_name($edit, $account) // to generate a username (return a string to be used as the username, NULL // to have email_registration generate it). - $names = Drupal::moduleHandler()->invokeAll('email_registration_name', array($account)); + $names = Drupal::moduleHandler()->invokeAll('email_registration_name', [$account]); // Remove any empty entries. $names = array_filter($names); @@ -81,7 +81,7 @@ function email_registration_unique_username($name, $uid = 0) { $database = \Drupal::database(); do { $new_name = empty($i) ? $name : $name . '_' . $i; - $found = $database->queryRange("SELECT uid from {users_field_data} WHERE uid <> :uid AND name = :name", 0, 1, array(':uid' => $uid, ':name' => $new_name))->fetchAssoc(); + $found = $database->queryRange("SELECT uid from {users_field_data} WHERE uid <> :uid AND name = :name", 0, 1, [':uid' => $uid, ':name' => $new_name])->fetchAssoc(); $i++; } while (!empty($found)); @@ -115,27 +115,31 @@ function email_registration_cleanup_username($name) { $name = ('' === $name) ? t('user') : $name; // Truncate to a reasonable size. - $name = (Unicode::strlen($name) > (USERNAME_MAX_LENGTH - 10)) ? Unicode::substr($name, 0, USERNAME_MAX_LENGTH - 11) : $name; + $name = (Unicode::strlen($name) > (UserInterface::USERNAME_MAX_LENGTH - 10)) ? Unicode::substr($name, 0, UserInterface::USERNAME_MAX_LENGTH - 11) : $name; return $name; } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_form_BASE_FORM_ID_alter(). */ -function email_registration_form_user_register_form_alter(&$form, FormStateInterface $form_state) { +function email_registration_form_user_form_alter(&$form, FormStateInterface $form_state) { $form['account']['name']['#type'] = 'value'; $form['account']['name']['#value'] = 'email_registration_' . user_password(); - $form['account']['mail']['#title'] = t('E-mail'); + $form['account']['mail']['#title'] = t('Email'); } /** * Implements hook_form_FORM_ID_alter(). */ function email_registration_form_user_pass_alter(&$form, FormStateInterface $form_state) { - $form['name']['#title'] = t('E-mail'); - // Allow client side validation of input format. - $form['name']['#type'] = 'email'; - $form['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH; + $form['name']['#title'] = t('Email'); + // Only convert textfields to email fields. This field is a hidden value when + // you are already logged in. + if ($form['name']['#type'] == 'textfield') { + // Allow client side validation of input format. + $form['name']['#type'] = 'email'; + $form['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH; + } } /** @@ -144,10 +148,10 @@ function email_registration_form_user_pass_alter(&$form, FormStateInterface $for function email_registration_form_user_login_form_alter(&$form, FormStateInterface $form_state) { $config = \Drupal::config('email_registration.settings'); $login_with_username = $config->get('login_with_username'); - $form['name']['#title'] = $login_with_username ? t('E-mail or username') : t('E-mail'); - $form['name']['#description'] = $login_with_username ? t('Enter your e-mail address or username.') : t('Enter your e-mail address.'); + $form['name']['#title'] = $login_with_username ? t('Email or username') : t('Email'); + $form['name']['#description'] = $login_with_username ? t('Enter your email address or username.') : t('Enter your email address.'); $form['name']['#element_validate'][] = 'email_registration_user_login_validate'; - $form['pass']['#description'] = t('Enter the password that accompanies your e-mail.'); + $form['pass']['#description'] = t('Enter the password that accompanies your email address.'); // Allow client side validation of input format. $form['name']['#type'] = $login_with_username ? 'textfield' : 'email'; $form['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH; @@ -170,18 +174,11 @@ function email_registration_user_login_validate($form, FormStateInterface $form_ elseif (!$config->get('login_with_username')) { $user_input = $form_state->getUserInput(); $query = isset($user_input['name']) ? ['name' => $user_input['name']] : []; - $form_state->setErrorByName('name', t('Unrecognized e-mail address or password. Forgot your password?', [':password' => Url::fromRoute('user.pass', [], ['query' => $query])->toString()])); + $form_state->setErrorByName('name', t('Unrecognized email address or password. Forgot your password?', [':password' => Url::fromRoute('user.pass', [], ['query' => $query])->toString()])); } } } -/** - * Implements hook_form_FORM_ID_alter(). - */ -function email_registration_form_user_form_alter(&$form, FormStateInterface $form_state) { - $form['account']['name']['#title'] = t('Display name'); -} - /** * Implements hook_form_FORM_ID_alter(). */ @@ -189,8 +186,8 @@ function email_registration_form_user_admin_settings_alter(&$form, FormStateInte $config = \Drupal::config('email_registration.settings'); $form['registration_cancellation']['login_with_username'] = [ '#type' => 'checkbox', - '#title' => t('Allow to log in with e-mail or username.'), - '#description' => t('Allow users to log in with either their username or their e-mail address.'), + '#title' => t('Allow log in with email address or username.'), + '#description' => t('Allow users to log in with either their email address or their username.'), '#default_value' => $config->get('login_with_username'), ]; $form['#submit'][] = 'email_registration_form_user_admin_settings_submit'; diff --git a/sites/all/modules/contrib/users/email_registration/src/Plugin/Commerce/CheckoutPane/EmailRegistrationLogin.php b/sites/all/modules/contrib/users/email_registration/src/Plugin/Commerce/CheckoutPane/EmailRegistrationLogin.php new file mode 100644 index 000000000..1bd226bfd --- /dev/null +++ b/sites/all/modules/contrib/users/email_registration/src/Plugin/Commerce/CheckoutPane/EmailRegistrationLogin.php @@ -0,0 +1,108 @@ +get('login_with_username'); + $pane_form['returning_customer']['name']['#title'] = $login_with_username ? t('Email address or username') : t('Email address'); + $pane_form['returning_customer']['name']['#description'] = $login_with_username ? t('Enter your email address or username.') : t('Enter your email address.'); + $pane_form['returning_customer']['name']['#element_validate'][] = 'email_registration_user_login_validate'; + $pane_form['returning_customer']['name']['#type'] = $login_with_username ? 'textfield' : 'email'; + $pane_form['returning_customer']['name']['#maxlength'] = Email::EMAIL_MAX_LENGTH; + $pane_form['returning_customer']['password']['#description'] = t('Enter the password that accompanies your email address.'); + $complete_form['#cache']['tags'][] = 'config:email_registration.settings'; + + $pane_form['register']['name']['#type'] = 'value'; + $pane_form['register']['name']['#value'] = 'email_registration_' . user_password(); + $pane_form['register']['mail']['#title'] = t('Email'); + + return $pane_form; + } + + /** + * {@inheritdoc} + */ + public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) { + $values = $form_state->getValue($pane_form['#parents']); + $triggering_element = $form_state->getTriggeringElement(); + if ($triggering_element['#op'] === 'login') { + $mail = $form_state->getValue([ + 'email_registration_login', + 'returning_customer', + 'name', + ]); + if (!empty($mail)) { + $config = \Drupal::config('email_registration.settings'); + if ($user = user_load_by_mail($mail)) { + $username = $user->getAccountName(); + $form_state->setValue([ + 'email_registration_login', + 'returning_customer', + 'name', + ], $username); + } + elseif (!$config->get('login_with_username')) { + $user_input = $form_state->getUserInput(); + $query = isset($user_input['email_registration_login']['returning_customer']['name']) ? ['name' => $user_input['email_registration_login']['returning_customer']['name']] : []; + $form_state->setError($pane_form['returning_customer'], t('Unrecognized email address or password. Forgot your password?', [ + ':password' => Url::fromRoute('user.pass', [], ['query' => $query]) + ->toString(), + ])); + return; + } + } + $name_element = $pane_form['returning_customer']['name']; + $password = trim($values['returning_customer']['password']); + // Generate the "reset password" url. + $query = !empty($username) ? ['name' => $username] : []; + $password_url = Url::fromRoute('user.pass', [], ['query' => $query]) + ->toString(); + + if (user_is_blocked($username)) { + $form_state->setError($name_element, $this->t('The account with email address %mail has not been activated or is blocked.', ['%mail' => $mail])); + return; + } + if (!$this->credentialsCheckFlood->isAllowedHost($this->clientIp)) { + $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', [':url' => Url::fromRoute('user.pass')])); + $this->credentialsCheckFlood->register($this->clientIp, $username); + return; + } + elseif (!$this->credentialsCheckFlood->isAllowedAccount($this->clientIp, $username)) { + $form_state->setErrorByName($name_element, $this->t('Too many failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', [':url' => Url::fromRoute('user.pass')])); + $this->credentialsCheckFlood->register($this->clientIp, $username); + return; + } + + $uid = $this->userAuth->authenticate($username, $password); + if (!$uid) { + $this->credentialsCheckFlood->register($this->clientIp, $username); + $form_state->setError($name_element, $this->t('Unrecognized email address or password. Forgot your password?', [':url' => $password_url])); + } + $form_state->set('logged_in_uid', $uid); + } + parent::validatePaneForm($pane_form, $form_state, $complete_form); + } + +} diff --git a/sites/all/modules/contrib/users/email_registration/tests/src/Functional/EmailRegistrationTestCase.php b/sites/all/modules/contrib/users/email_registration/tests/src/Functional/EmailRegistrationTestCase.php index c47115701..db9938cef 100644 --- a/sites/all/modules/contrib/users/email_registration/tests/src/Functional/EmailRegistrationTestCase.php +++ b/sites/all/modules/contrib/users/email_registration/tests/src/Functional/EmailRegistrationTestCase.php @@ -16,7 +16,7 @@ class EmailRegistrationTestCase extends BrowserTestBase { * * @var array */ - public static $modules = array('email_registration'); + public static $modules = ['email_registration']; /** * Test various behaviors for anonymous users. @@ -31,18 +31,18 @@ class EmailRegistrationTestCase extends BrowserTestBase { // Try to register a user. $name = $this->randomMachineName(); $pass = $this->randomString(10); - $register = array( + $register = [ 'mail' => $name . '@example.com', 'pass[pass1]' => $pass, 'pass[pass2]' => $pass, - ); + ]; $this->drupalPostForm('/user/register', $register, t('Create new account')); $this->drupalLogout(); - $login = array( + $login = [ 'name' => $name . '@example.com', 'pass' => $pass, - ); + ]; $this->drupalPostForm('user/login', $login, t('Log in')); // Really basic confirmation that the user was created and logged in. @@ -53,13 +53,13 @@ class EmailRegistrationTestCase extends BrowserTestBase { // Try to login with just username, should fail by default. $this->drupalGet('user/login'); - $this->assertText('Enter your e-mail address.'); - $this->assertText('E-mail'); - $this->assertNoText('E-mail or username'); - $login = array( + $this->assertText('Enter your email address.'); + $this->assertText('Email'); + $this->assertNoText('Email or username'); + $login = [ 'name' => $name, 'pass' => $pass, - ); + ]; $this->drupalPostForm('user/login', $login, t('Log in')); $error_message = $this->xpath('//div[contains(@class, "error")]'); $this->assertTrue(!empty($error_message), t('When login_with_username is false, a user cannot login with just their username.')); @@ -67,8 +67,8 @@ class EmailRegistrationTestCase extends BrowserTestBase { // Set login_with_username to TRUE and try to login with just username. $email_registration_config->set('login_with_username', TRUE)->save(); $this->drupalGet('user/login'); - $this->assertText('Enter your e-mail address or username.'); - $this->assertText('E-mail or username'); + $this->assertText('Enter your email address or username.'); + $this->assertText('Email or username'); $this->drupalPostForm('user/login', $login, t('Log in')); $this->assertRaw('' . $name . ' | Drupal', t('When login_with_username is true, a user can login with just their username.')); $this->drupalLogout(); @@ -78,11 +78,11 @@ class EmailRegistrationTestCase extends BrowserTestBase { ->save(); $name = $this->randomMachineName(); $pass = $this->randomString(10); - $register = array( + $register = [ 'mail' => $name . '@example.com', 'pass[pass1]' => $pass, 'pass[pass2]' => $pass, - ); + ]; $this->drupalPostForm('/user/register', $register, t('Create new account')); $this->assertRaw('Registration successful. You are now logged in.', t('User properly created, immediately logged in.')); @@ -95,14 +95,14 @@ class EmailRegistrationTestCase extends BrowserTestBase { $name = $this->randomMachineName(32); $pass = $this->randomString(10); - $this->createUser(array(), $name); + $this->createUser([], $name); $next_unique_name = email_registration_unique_username($name); - $register = array( + $register = [ 'mail' => $name . '@example2.com', 'pass[pass1]' => $pass, 'pass[pass2]' => $pass, - ); + ]; $this->drupalPostForm('/user/register', $register, t('Create new account')); $account = user_load_by_mail($register['mail']); $this->assertTrue($next_unique_name === $account->getAccountName()); diff --git a/sites/all/modules/contrib/views/translation_views/README.md b/sites/all/modules/contrib/views/translation_views/README.md index 0c261b0fc..2110fd249 100644 --- a/sites/all/modules/contrib/views/translation_views/README.md +++ b/sites/all/modules/contrib/views/translation_views/README.md @@ -3,7 +3,7 @@ Translation Views INTRODUCTION ------------ -Provides fields and filter for views to display information about the current row, the **source** translation, in another **target** language. You can also add translation operation links to decide if you want to add/edit the content in the target language. +Provides fields and filter for views to display information about each row, the **source** translation, in another **target** language. You can also add translation operation links to decide if you want to add/edit the content in the target language. Configurations for a demo view is imported on installation, named `Content translation jobs` which can be found at path `/translate/content`. If you want to get view for other translatable entities, you should build it yourself using our fields/filters. @@ -17,16 +17,17 @@ FEATURES - Target language: `field`,`filter`. Should in most cases be an exposed filter. Also, there is an extra filter option: *Remove rows where source language is equal to target language* -These fields/filters will display information about the current content in the selected target language: +These fields/filters will display information about the each row in the selected target language: - Translation outdated: `field`,`filter` - Translation status: `field`,`filter` - Target language equals default language: `field`,`filter`: Checks if the target language is the same as the original language of the node. - Translation changed time: `field` -- Translation source equals row language: `field`,`filter`: Checks if the source translation of the target language is the same as the language of the row +- Source translation of target language equals row language: `field`,`filter`: Checks if the source translation of the row is the same as the target language - Translation operations: `field` +- Translation moderation state: `field` **Source translation** -- Translation counter: `field`,`filter` +- Translation counter: `field`,`filter`: count translations of oirignal language. You can also configure the field to include original language in the count. REQUIREMENTS ------------ @@ -65,9 +66,12 @@ Exposed filters: Hidden filters - Source content outdated must be false: Make sure you do not translation from outdated nodes. - Target language equals default language must be false: As there is no use to check status of original language as a target language. -- The target language must be untranslated or outdated: Make sure translated and updated content disappear from the list. - The target language must be untranslated or the same as the source language of translation: This makes sure that once there is a translation in target language it will only be displayed if the source language is the correct source language. If target language is not translated you can use different language as source translation. +MODULE INTEGRATION +------------- +You can limit the target language filter list to the users registered translation skill if you're using [Local Translation](https://www.drupal.org/project/local_translation) + MAINTAINERS ----------- Developed by [vlad.dancer](https://drupal.org/u/vladdancer) diff --git a/sites/all/modules/contrib/views/translation_views/config/optional/views.view.content_translations.yml b/sites/all/modules/contrib/views/translation_views/config/optional/views.view.content_translations.yml index 5ea359213..ba341e188 100644 --- a/sites/all/modules/contrib/views/translation_views/config/optional/views.view.content_translations.yml +++ b/sites/all/modules/contrib/views/translation_views/config/optional/views.view.content_translations.yml @@ -612,7 +612,7 @@ display: identifier: translation_target_language required: true remember: true - multiple: true + multiple: false remember_roles: anonymous: anonymous authenticated: authenticated @@ -679,7 +679,7 @@ display: group_type: group admin_label: '' operator: '=' - value: 2 + value: '0' group: 1 exposed: true expose: @@ -754,7 +754,7 @@ display: group_type: group admin_label: '' operator: '=' - value: 0 + value: '0' group: 2 exposed: false expose: @@ -819,79 +819,6 @@ display: default_group_multiple: { } group_items: { } plugin_id: translation_views_source_equals_row - translation_status_2: - id: translation_status_2 - table: node_translation - field: translation_status - relationship: none - group_type: group - admin_label: '' - operator: '=' - value: 0 - group: 3 - exposed: false - expose: - operator_id: '' - label: '' - description: '' - use_operator: false - operator: '' - identifier: '' - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - entity_type: node - plugin_id: translation_views_status - translation_outdated_1: - id: translation_outdated_1 - table: node_translation_target - field: translation_outdated - relationship: none - group_type: group - admin_label: '' - operator: '=' - value: '1' - group: 3 - exposed: false - expose: - operator_id: '' - label: '' - description: '' - use_operator: false - operator: '' - identifier: '' - required: false - remember: false - multiple: false - remember_roles: - authenticated: authenticated - is_grouped: false - group_info: - label: '' - description: '' - identifier: '' - optional: true - widget: select - multiple: false - remember: false - default_group: All - default_group_multiple: { } - group_items: { } - plugin_id: boolean sorts: created: id: created diff --git a/sites/all/modules/contrib/views/translation_views/config/schema/translation_views.views.schema.yml b/sites/all/modules/contrib/views/translation_views/config/schema/translation_views.views.schema.yml index 783cadd05..74d22e5ae 100644 --- a/sites/all/modules/contrib/views/translation_views/config/schema/translation_views.views.schema.yml +++ b/sites/all/modules/contrib/views/translation_views/config/schema/translation_views.views.schema.yml @@ -4,19 +4,15 @@ views.filter.translation_views_translation_count: views.filter_value.translation_views_translation_count: type: views.filter_value.numeric - label: 'Filter rows by amount of translations (without default language)' + label: 'Filter rows by amount of translations' views.filter.translation_views_source_equals_row: type: views.filter.boolean value: 'A boolean indicating whether the translation source of target language is same as the row language' views.filter.translation_views_status: - type: views_filter - label: 'A value indicating whether the node is translated into target language' - mapping: - value: - type: integer - label: 'Value' + type: views.filter.boolean + label: 'A boolean indicating whether the node is translated into target language' views.filter.translation_views_target_language: type: views_filter @@ -28,10 +24,27 @@ views.filter.translation_views_target_language: remove: type: boolean label: 'Remove rows where source language is equal to target language' + limit: + type: boolean + label: 'Enable limiting languages list using Local Translation module' + column: + type: mapping + label: 'Column' + mapping: + from: + type: string + lable: 'Source language' + to: + type: string + lable: 'Target language' views.field.translation_views_translation_count: type: views.field.numeric - label: 'Amount of translations (without default language)' + label: 'Amount of translations' + mapping: + include_original_language: + type: boolean + label: 'Include original language in count' views.field.translation_views_operations: type: views_field @@ -48,3 +61,11 @@ views.field.translation_views_status: views.field.translation_views_target_language: type: views.field.language label: 'The target language' + +views.field.translation_moderation_state: + type: views_field + label: 'Translation moderation state' + mapping: + display_name: + type: string + label: 'Formatter' diff --git a/sites/all/modules/contrib/views/translation_views/src/EntityInfo.php b/sites/all/modules/contrib/views/translation_views/src/EntityInfo.php deleted file mode 100644 index eafb54d6d..000000000 --- a/sites/all/modules/contrib/views/translation_views/src/EntityInfo.php +++ /dev/null @@ -1,78 +0,0 @@ -entity = $entity; - $this->access = $access; - - $this->rowLanguage = $row_lang; - $this->currentLanguage = $language; - - $this->entityTypeId = $entity->getEntityTypeId(); - $this->entityType = $entity->getEntityType(); - $this->translations = $entity->getTranslationLanguages(); - $this->translatable = $entity->isTranslatable(); - } - - /** - * An wrapper to get translation access by particular entity per operation. - * - * @see \Drupal\content_translation\ContentTranslationHandlerInterface::getTranslationAccess, - * for available operations. - */ - public function getTranslationAccess($op) { - $access = $this->access->getTranslationAccess($this->entity, $op); - return $access->isAllowed(); - } - -} diff --git a/sites/all/modules/contrib/views/translation_views/src/EntityTranslationInfo.php b/sites/all/modules/contrib/views/translation_views/src/EntityTranslationInfo.php new file mode 100644 index 000000000..c4f65503a --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/src/EntityTranslationInfo.php @@ -0,0 +1,81 @@ +entity = $entity; + $this->accessHandler = $accessHandler; + + $this->sourceLanguage = $sourceLanguage; + $this->targetLanguage = $targetLanguage; + + $this->entityTypeId = $entity->getEntityTypeId(); + $this->entityType = $entity->getEntityType(); + } + + /** + * A wrapper for getting translation access per operation. + * + * @see \Drupal\content_translation\ContentTranslationHandlerInterface::getTranslationAccess, + * for available operations. + */ + public function getTranslationAccess($operation) { + $access = $this->accessHandler->getTranslationAccess($this->entity, $operation); + return $access->isAllowed(); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationCountField.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationCountField.php index 20a7af838..e8c533b65 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationCountField.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationCountField.php @@ -2,6 +2,7 @@ namespace Drupal\translation_views\Plugin\views\field; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\translation_views\TranslationCountTrait; use Drupal\views\Plugin\views\field\NumericField; @@ -57,4 +58,26 @@ class TranslationCountField extends NumericField implements ContainerFactoryPlug $this->field_alias = $query->addField($join_alias, 'count_langs', $this->realField); } + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['include_original_language'] = ['default' => FALSE]; + return $options; + } + + /** + * Provide option to include original language in count. + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + $form['include_original_language'] = [ + '#title' => $this->t('Include original language in count'), + '#description' => $this->t("Enable to also count the original language."), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['include_original_language']), + ]; + parent::buildOptionsForm($form, $form_state); + } + } diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationModerationState.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationModerationState.php new file mode 100644 index 000000000..435ba90e2 --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationModerationState.php @@ -0,0 +1,161 @@ +moderationInfo = $moderation_info; + $this->languageManager = $language_manager; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create( + ContainerInterface $container, + array $configuration, + $plugin_id, + $plugin_definition + ) { + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('content_moderation.moderation_information'), + $container->get('language_manager'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + $options = parent::defineOptions(); + $options['display_name'] = ['default' => TRUE]; + return $options; + } + + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + $default_value = 'state'; + $states = [ + 'state' => $this->t('Moderation State'), + 'name' => $this->t('Machine Name'), + ]; + if (!empty($this->options['display_name'])) { + $default_value = $this->options['display_name']; + } + $form['display_name'] = [ + '#type' => 'select', + '#title' => $this->t('Formatter'), + '#options' => $states, + '#required' => TRUE, + '#default_value' => $default_value, + ]; + parent::buildOptionsForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function query() { + + } + + /** + * {@inheritdoc} + */ + public function clickSortable() { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function render(ResultRow $values) { + if ($values->_entity->id()) { + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $entityTypeId = $values->_entity->getEntityTypeId(); + $storage = $this->entityTypeManager->getStorage($entityTypeId); + $entity = $storage->load($values->_entity->id()); + $target_langcode = $this->getTargetLangcode(); + $pending_revision_enabled = ContentTranslationManager::isPendingRevisionSupportEnabled($entityTypeId); + + if (empty($target_langcode) || $target_langcode == '***LANGUAGE_site_default***') { + $target_langcode = $this->languageManager->getCurrentLanguage()->getId(); + } + + $translation_has_revision = $storage->getLatestTranslationAffectedRevisionId($values->_entity->id(), $target_langcode); + if ($entity && $pending_revision_enabled && $target_langcode && $translation_has_revision) { + $latest_revision = $storage->loadRevision($translation_has_revision); + if ($entity && $latest_revision->hasTranslation($target_langcode)) { + $workflow = $this->moderationInfo->getWorkflowForEntity($latest_revision); + $translation = $latest_revision->getTranslation($target_langcode); + $translation_state = $translation->moderation_state->value; + + if (!empty($this->options['display_name']) && $workflow !== NULL) { + if ($this->options['display_name'] == 'state') { + $translation_label = $workflow->getTypePlugin() + ->getState($translation_state) + ->label(); + $values->{$this->field_alias} = $translation_label ? $translation_label : NULL; + } + else { + $values->{$this->field_alias} = $translation_state ? $translation_state : NULL; + } + } + } + } + } + + return parent::render($values); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationOperationsField.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationOperationsField.php index adbfa0842..1bd34d13f 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationOperationsField.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationOperationsField.php @@ -2,12 +2,18 @@ namespace Drupal\translation_views\Plugin\views\field; +use Drupal\content_translation\ContentTranslationManager; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Url; -use Drupal\translation_views\EntityInfo as HelperEntityInfo; +use Drupal\translation_views\EntityTranslationInfo; use Drupal\translation_views\TranslationViewsTargetLanguage as TargetLanguage; use Drupal\views\Plugin\views\field\EntityOperations; use Drupal\views\ResultRow; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Renders translation operations links. @@ -19,16 +25,65 @@ use Drupal\views\ResultRow; class TranslationOperationsField extends EntityOperations { use TargetLanguage; + /** + * Current user account object. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + /** + * Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManager + */ + protected $entityTypeManager; + + /** + * {@inheritdoc} + */ + public function __construct( + array $configuration, + $plugin_id, + array $plugin_definition, + EntityManagerInterface $entity_manager, + EntityTypeManager $entity_type_manager, + LanguageManagerInterface $language_manager, + AccountProxyInterface $account + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_manager, $language_manager); + $this->currentUser = $account; + $this->entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public static function create( + ContainerInterface $container, + array $configuration, + $plugin_id, + $plugin_definition + ) { + return new static( + $configuration, $plugin_id, $plugin_definition, + $container->get('entity.manager'), + $container->get('entity_type.manager'), + $container->get('language_manager'), + $container->get('current_user') + ); + } + /** * {@inheritdoc} * * Build operation links. */ public function render(ResultRow $values) { - /* @var ContentEntityInterface $entity */ - $entity = $this->getEntity($values); - $langcode_key = $this->buildSourceEntityLangcodeKey($entity); - $operations = $this->getOperations($entity, $values->{$langcode_key}); + /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity($values); + $langcode_key = $this->buildSourceEntityLangcodeKey($entity); + $source_langcode = $values->{$langcode_key}; + $operations = $this->getTranslationOperations($entity, $source_langcode); if ($this->options['destination']) { foreach ($operations as &$operation) { @@ -39,7 +94,7 @@ class TranslationOperationsField extends EntityOperations { } } $build = [ - '#type' => 'operations', + '#type' => 'operations', '#links' => $operations, ]; $build['#cache']['contexts'][] = 'url.query_args:target_language'; @@ -76,20 +131,21 @@ class TranslationOperationsField extends EntityOperations { * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The source entity to get context for decision. - * @param string $row_lang - * The target langcode of the row. + * @param string $source_langcode + * The langcode of the row. * * @return array * Operation links' render array. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Core\Entity\EntityMalformedException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ - protected function getOperations(ContentEntityInterface $entity, $row_lang) { + protected function getTranslationOperations(ContentEntityInterface $entity, $source_langcode) { $links = []; - $current = $this->getTargetLanguage() - ? $this->getTargetLanguage() - : $row_lang; + $target_langcode = $this->getTargetLangcode() + ? $this->getTargetLangcode() + : $source_langcode; /* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */ $handler = $this->getEntityManager() @@ -98,74 +154,159 @@ class TranslationOperationsField extends EntityOperations { // Construct special object to store common properties, // it will be used by all builder functions, // just as "trait" but for methods. - $entity_info = new HelperEntityInfo( + $translation_info = new EntityTranslationInfo( $entity, $handler, - $this->languageManager->getLanguage($current), - $this->languageManager->getLanguage($row_lang) + $this->languageManager->getLanguage($target_langcode), + $this->languageManager->getLanguage($source_langcode) ); - $is_source = $entity->getUntranslated()->language()->getId() === $entity_info->rowLanguage->getId(); + $is_default = static::isDefaultTranslation($translation_info); // Build edit & delete link. - if (array_key_exists($current, $entity_info->translations)) { + if (array_key_exists($target_langcode, $entity->getTranslationLanguages())) { // If the user is allowed to edit the entity we point the edit link to // the entity form, otherwise if we are not dealing with the original // language we point the link to the translation form. - if ($entity->access('update', NULL, TRUE)->isAllowed() - && $entity_info->entityType->hasLinkTemplate('edit-form') + if ($entity->access('update', NULL) + && $translation_info->entityType->hasLinkTemplate('edit-form') ) { - $links += $this->buildEditLink($entity_info, 'entity'); + $links += $this->buildEditLink($translation_info, 'entity'); } - elseif (!$is_source && $entity_info->getTranslationAccess('update')) { - $links += $this->buildEditLink($entity_info, 'translation'); + elseif (!$is_default + && $translation_info->getTranslationAccess('update') + || $this->checkForOperationTranslationPermission('update') + ) { + $links += $this->buildEditLink($translation_info, 'translation'); } // Build delete link. - if (!$is_source) { - if ($entity->access('delete') - && $entity_info->entityType->hasLinkTemplate('delete-form') - ) { - $links += $this->buildDeleteLink($entity_info, 'entity'); - } - elseif ($entity_info->getTranslationAccess('update')) { - $links += $this->buildDeleteLink($entity_info, 'translation'); - } + if ($entity->access('delete') + && $translation_info->entityType->hasLinkTemplate('delete-form') + ) { + $links += $this->buildDeleteLink($translation_info, 'entity'); + } + elseif (!$is_default + && $translation_info->getTranslationAccess('delete') + || $this->checkForOperationTranslationPermission('delete') + ) { + $links += $this->buildDeleteLink($translation_info, 'translation'); + } + } + // Check if there are pending revisions. + elseif ($this->pendingRevisionExist($translation_info)) { + // If the user is allowed to edit the entity we point the edit link to + // the entity form, otherwise if we are not dealing with the original + // language we point the link to the translation form. + if ($entity->access('update', NULL) + && $translation_info->entityType->hasLinkTemplate('edit-form') + ) { + $links += $this->buildEditLink($translation_info, 'entity'); + } + elseif (!$is_default + && $translation_info->getTranslationAccess('update') + || $this->checkForOperationTranslationPermission('update') + ) { + $links += $this->buildEditLink($translation_info, 'translation'); } } // Build add link. - elseif (!empty($current) - && $entity_info->translatable - && $entity_info->getTranslationAccess('create') + elseif (!empty($target_langcode) + && $translation_info->entity->isTranslatable() + && $translation_info->getTranslationAccess('create') ) { // No such translation. - $links += $this->buildAddLink($entity_info); + $links += $this->buildAddLink($translation_info); } - return $links; + return $links; + } + + /** + * Check if the current user has appropriate translation permission. + * + * @param string $operation + * Operation name. + * + * @return bool + * TRUE - if user permitted to perform specified operation, FALSE otherwise. + */ + protected function checkForOperationTranslationPermission($operation) { + if (!in_array($operation, ['create', 'update', 'delete'], TRUE)) { + return FALSE; + } + return $this->currentUser + ->hasPermission("$operation content translations"); + } + + /** + * Check if target translation is the default translation. + * + * @param \Drupal\translation_views\EntityInfo $translation_info + * Entity info object. + * + * @return bool + * Checking result. + */ + protected static function isDefaultTranslation(EntityTranslationInfo $translation_info) { + $default_langcode = $translation_info->entity + ->getUntranslated() + ->language() + ->getId(); + $target_langcode = $translation_info->targetLanguage->getId(); + return $default_langcode === $target_langcode; + } + + /** + * Check if pending revision exist for this translation. + * + * @param \Drupal\translation_views\EntityInfo $translation_info + * Entity info object. + * + * @return bool + * TRUE - if pending revision exist, FALSE otherwise. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + protected function pendingRevisionExist(EntityTranslationInfo $translation_info) { + $pending_revision_enabled = ContentTranslationManager::isPendingRevisionSupportEnabled($translation_info->entityTypeId); + if ($this->moduleHandler->moduleExists('content_moderation') && $pending_revision_enabled) { + /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ + $storage = $this->entityTypeManager->getStorage($translation_info->entityTypeId); + $entity = $storage->load($translation_info->entity->id()); + $translation_has_revision = $storage->getLatestTranslationAffectedRevisionId( + $entity->id(), + $translation_info->targetLanguage->getId() + ); + if ($translation_has_revision) { + return TRUE; + } + } + return FALSE; } /** * Add link builder. */ - protected function buildAddLink(HelperEntityInfo $entity_info) { + protected function buildAddLink(EntityTranslationInfo $translation_info) { $links = []; $add_url = new Url( - "entity.{$entity_info->entityTypeId}.content_translation_add", + "entity.{$translation_info->entityTypeId}.content_translation_add", [ - 'source' => $entity_info->rowLanguage->getId(), - 'target' => $entity_info->currentLanguage->getId(), - $entity_info->entityTypeId => $entity_info->entity->id(), + 'source' => $translation_info->sourceLanguage->getId(), + 'target' => $translation_info->targetLanguage->getId(), + $translation_info->entityTypeId => $translation_info->entity->id(), ], [ - 'language' => $entity_info->currentLanguage, + 'language' => $translation_info->targetLanguage, ] ); $links['add'] = [ 'title' => $this->t('Add'), - 'url' => $add_url, + 'url' => $add_url, ]; return $links; } @@ -173,7 +314,7 @@ class TranslationOperationsField extends EntityOperations { /** * Delete link builder. */ - protected function buildDeleteLink(HelperEntityInfo $entity_info, $type = FALSE) { + protected function buildDeleteLink(EntityTranslationInfo $translation_info, $type = FALSE) { $links = []; if (!$type) { @@ -182,22 +323,22 @@ class TranslationOperationsField extends EntityOperations { if ($type == 'entity') { $links['delete'] = [ - 'title' => $this->t('Delete'), - 'url' => $entity_info->entity->urlInfo('delete-form'), - 'language' => $entity_info->rowLanguage, + 'title' => $this->t('Delete'), + 'url' => $translation_info->entity->urlInfo('delete-form'), + 'language' => $translation_info->targetLanguage, ]; } elseif ($type == 'translation') { $links['delete'] = [ 'title' => $this->t('Delete'), - 'url' => new Url( - "entity.{$entity_info->entityTypeId}.content_translation_delete", + 'url' => new Url( + "entity.{$translation_info->entityTypeId}.content_translation_delete", [ - 'language' => $entity_info->rowLanguage->getId(), - $entity_info->entityTypeId => $entity_info->entity->id(), + 'language' => $translation_info->targetLanguage->getId(), + $translation_info->entityTypeId => $translation_info->entity->id(), ], [ - 'language' => $entity_info->rowLanguage, + 'language' => $translation_info->targetLanguage, ] ), ]; @@ -210,22 +351,22 @@ class TranslationOperationsField extends EntityOperations { * * @throws \Drupal\Core\Entity\EntityMalformedException */ - protected function buildEditLink(HelperEntityInfo $entity_info, $type = FALSE) { + protected function buildEditLink(EntityTranslationInfo $translation_info, $type = FALSE) { $links = []; if ($type == 'entity') { - $links['edit']['url'] = $entity_info->entity->toUrl('edit-form'); - $links['edit']['language'] = $entity_info->rowLanguage; + $links['edit']['url'] = $translation_info->entity->toUrl('edit-form'); + $links['edit']['language'] = $translation_info->targetLanguage; } elseif ($type == 'translation') { $links['edit']['url'] = new Url( - "entity.{$entity_info->entityTypeId}.content_translation_edit", + "entity.{$translation_info->entityTypeId}.content_translation_edit", [ - 'language' => $entity_info->rowLanguage->getId(), - $entity_info->entityTypeId => $entity_info->entity->id(), + 'language' => $translation_info->targetLanguage->getId(), + $translation_info->entityTypeId => $translation_info->entity->id(), ], [ - 'language' => $entity_info->rowLanguage, + 'language' => $translation_info->targetLanguage, ] ); ; diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationSourceLangcodeEqualsRowLangcodeField.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationSourceLangcodeEqualsRowLangcodeField.php index b9d025c7f..38b04882b 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationSourceLangcodeEqualsRowLangcodeField.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationSourceLangcodeEqualsRowLangcodeField.php @@ -41,7 +41,7 @@ class TranslationSourceLangcodeEqualsRowLangcodeField extends Boolean { $this->query->addField( NULL, - "IF($table_alias.content_translation_source = $base_table.langcode, 1, 0)", + "IF(($table_alias.content_translation_source = $base_table.langcode) OR ($table_alias.default_langcode = 1), 1, 0)", 'source_lang_eq_row' ); } diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationTargetLanguageField.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationTargetLanguageField.php index 468a63b9d..298a269db 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationTargetLanguageField.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/field/TranslationTargetLanguageField.php @@ -57,13 +57,13 @@ class TranslationTargetLanguageField extends LanguageField implements ContainerF * instead from ResultRow. */ public function render(ResultRow $values) { - $target_lang = $this->getTargetLanguage(); + $target_langcode = $this->getTargetLangcode(); $languages = $this->options['native_language'] ? $this->languageManager->getNativeLanguages() : $this->languageManager->getLanguages(); - $build['#markup'] = $languages[$target_lang]->getName(); + $build['#markup'] = $languages[$target_langcode]->getName(); $build['#cache']['contexts'][] = 'url.query_args:' . self::$targetExposedKey; return $build; diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationSourceLangcodeEqualsRowLangcodeFilter.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationSourceLangcodeEqualsRowLangcodeFilter.php index 0ad6e7369..f7fdb1bd0 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationSourceLangcodeEqualsRowLangcodeFilter.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationSourceLangcodeEqualsRowLangcodeFilter.php @@ -22,7 +22,7 @@ class TranslationSourceLangcodeEqualsRowLangcodeFilter extends BooleanOperator { $this->query->addWhereExpression( $this->options['group'], - "IF($table_alias.content_translation_source $this->operator $base_table.langcode, 1, 0) = :value", [ + "IF(($table_alias.content_translation_source $this->operator $base_table.langcode) OR ($table_alias.default_langcode $this->operator 1), 1, 0) = :value", [ ':value' => $this->value, ] ); diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationStatusFilter.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationStatusFilter.php index 7fcaca632..3059968f4 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationStatusFilter.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationStatusFilter.php @@ -2,8 +2,7 @@ namespace Drupal\translation_views\Plugin\views\filter; -use Drupal\Core\Form\FormStateInterface; -use Drupal\views\Plugin\views\filter\FilterPluginBase; +use Drupal\views\Plugin\views\filter\BooleanOperator; /** * Provides filtering by translation status. @@ -12,85 +11,13 @@ use Drupal\views\Plugin\views\filter\FilterPluginBase; * * @ViewsFilter("translation_views_status") */ -class TranslationStatusFilter extends FilterPluginBase { - - const VALUE_UNTRANSLATED = 0; - const VALUE_TRANSLATED = 1; - const VALUE_BOTH = 2; +class TranslationStatusFilter extends BooleanOperator { /** * {@inheritdoc} */ - public function buildExposeForm(&$form, FormStateInterface $form_state) { - parent::buildExposeForm($form, $form_state); - - unset($form['expose']['multiple']); - unset($form['expose']['required']); - } - - /** - * {@inheritdoc} - */ - protected function defineOptions() { - $options = parent::defineOptions(); - $options['value']['default'] = FALSE; - - return $options; - } - - /** - * {@inheritdoc} - * - * Remove "all" option because we don't need it. - */ - public function buildExposedForm(&$form, FormStateInterface $form_state) { - parent::buildExposedForm($form, $form_state); - - if (!empty($this->options['expose']['identifier'])) { - $value_id = $this->options['expose']['identifier']; - } - else { - $value_id = 'translation_views_status'; - } - - unset($form[$value_id]['#options']['All']); - } - - /** - * {@inheritdoc} - */ - protected function valueForm(&$form, FormStateInterface $form_state) { - parent::valueForm($form, $form_state); - - $exposed = $form_state->get('exposed'); - - if (!empty($this->options['exposed'])) { - $identifier = $this->options['expose']['identifier']; - $user_input = $form_state->getUserInput(); - - // We need set exposed input to our "BOTH" value, - // when there is no selected value by user yet, - // usually when there is no any changes in exposed filters. - if ($exposed && !isset($user_input[$identifier])) { - $user_input = $form_state->getUserInput(); - $user_input[$identifier] = self::VALUE_BOTH; - - $form_state->setUserInput($user_input); - $this->view->setExposedInput($user_input); - } - } - - $form['value'] = [ - '#type' => 'select', - '#title' => $this->t('Status'), - '#options' => [ - self::VALUE_BOTH => t('Both'), - self::VALUE_UNTRANSLATED => t('Not translated'), - self::VALUE_TRANSLATED => t('Translated'), - ], - '#multiple' => FALSE, - '#default_value' => $this->value, - ]; + public function getValueOptions() { + $this->valueOptions = [1 => $this->t('Translated'), 0 => $this->t('Not translated')]; } /** @@ -98,33 +25,24 @@ class TranslationStatusFilter extends FilterPluginBase { */ public function query() { $table_alias = $this->ensureMyTable(); + $status = $this->value; - $status = is_array($this->value) - ? (int) reset($this->value) - : $this->value; - - // We don't want to build and run query, - // when user specified both statuses. - if ($status == self::VALUE_BOTH) { - return FALSE; + if ($status == 0) { + $operation = '='; } - - if ($status == self::VALUE_UNTRANSLATED) { - $op = '='; - } - // Then self::VALUE_TRANSLATED is the case. + // Then status translated is the case. else { // Mysql FIND_IN_SET func will return position of element, // when no element was found then it returns 0, // so we use "> 0" as condition to filter out untranslated rows. - $op = '>'; + $operation = '>'; $status = 0; } /* @var \Drupal\views\Plugin\views\query\Sql */ $this->query->addWhereExpression( $this->options['group'], - "FIND_IN_SET(:langcode, $table_alias.langs) $op :status", [ + "FIND_IN_SET(:langcode, $table_alias.langs) $operation :status", [ ':langcode' => '***TRANSLATION_VIEWS_TARGET_LANG***', ':status' => $status, ] diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationTargetLanguageFilter.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationTargetLanguageFilter.php index 1bcc7b4f9..3844ac630 100644 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationTargetLanguageFilter.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/filter/TranslationTargetLanguageFilter.php @@ -2,6 +2,7 @@ namespace Drupal\translation_views\Plugin\views\filter; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -21,22 +22,40 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class TranslationTargetLanguageFilter extends FilterPluginBase implements ContainerFactoryPluginInterface { use TargetLanguage; + /** + * Flag about module 'local_translation_content' existence. + * + * @var bool + */ + protected $localTranslation = FALSE; + /** + * Local Translation skills service. + * + * @var \Drupal\local_translation\Services\LocalTranslationUserSkills|null + */ + protected $skills = NULL; + /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, - $container->get('language_manager') + $container->get('language_manager'), + $container->get('module_handler') ); } /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, ModuleHandlerInterface $handler) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->languageManager = $language_manager; + $this->localTranslation = $handler->moduleExists('local_translation_content'); + if ($this->localTranslation && \Drupal::hasService('local_translation.user_skills')) { + $this->skills = \Drupal::service('local_translation.user_skills'); + } } /** @@ -48,12 +67,16 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai unset($form['expose']['multiple']); unset($form['expose']['required']); + if ($this->localTranslation) { + // We need to force this option to allow users to use only the languages, + // specified as the user's translation skills. + $form['expose']['reduce']['#default_value'] = TRUE; + $form['expose']['reduce']['#disabled'] = TRUE; + } + $form['expose']['identifier'] = [ '#type' => 'hidden', - '#value' => $this->options['expose']['identifier'], - '#title' => $this->t('Filter identifier'), - '#size' => 40, - '#description' => $this->t('This will appear in the URL after the ? to identify this filter. Cannot be blank. Only letters, digits and the dot ("."), hyphen ("-"), underscore ("_"), and tilde ("~") characters are allowed.'), + '#value' => static::$targetExposedKey, ]; } @@ -62,13 +85,76 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai */ protected function defineOptions() { $options = parent::defineOptions(); - $options['expose']['identifier'] = static::$targetExposedKey; + + $options['expose']['contains']['label'] = [ + 'default' => $this->t('Target language'), + ]; + + $options['expose']['contains']['identifier'] = [ + 'default' => static::$targetExposedKey, + ]; + $options['value']['default'] = ''; - $options['remove']['default'] = FALSE; + $options['remove']['default'] = TRUE; + $options['exposed']['default'] = TRUE; + + if ($this->localTranslation) { + $options['limit'] = ['default' => FALSE]; + $options['column'] = ['default' => ['from' => '', 'to' => 'to']]; + } return $options; } + /** + * {@inheritdoc} + */ + public function buildOptionsForm(&$form, FormStateInterface $form_state) { + parent::buildOptionsForm($form, $form_state); + if ($this->localTranslation) { + // Remove the values list - we will handle them on a background basis. + // Only if limited option is checked. + $form['value']['#states'] = [ + 'visible' => [ + 'input[name="options[limit]"]' => ['checked' => FALSE], + ], + ]; + // Build values list independently in order to see all the options, + // while switching "limit" option without necessity to reload the form. + $form['value']['#options'] = $this->listLanguages( + LanguageInterface::STATE_ALL + | LanguageInterface::STATE_SITE_DEFAULT + | PluginBase::INCLUDE_NEGOTIATED + ); + + $end = $form['clear_markup_end']; + unset($form['clear_markup_end']); + $form['limit'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Limit target languages by translation skills'), + '#required' => FALSE, + '#default_value' => $this->options['limit'], + ]; + $form['column'] = [ + '#type' => 'checkboxes', + '#options' => [ + 'from' => $this->t('Source languages'), + 'to' => $this->t('Target languages'), + ], + '#title' => $this->t('Languages by translation skills'), + '#required' => FALSE, + '#default_value' => $this->options['column'], + '#states' => [ + 'visible' => [ + 'input[name="options[limit]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['clear_markup_end'] = $end; + $form['value']['#prefix'] = '
'; + } + } + /** * {@inheritdoc} */ @@ -77,26 +163,48 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai $exposed = $form_state->get('exposed'); + $language_options = $this->buildLanguageOptions(); + $default_langcode = $this->languageManager->getDefaultLanguage()->getId(); + if (!empty($this->options['exposed'])) { $identifier = $this->options['expose']['identifier']; $user_input = $form_state->getUserInput(); // We need set exposed input when there is no selected value by user yet. - if ($exposed && !isset($user_input[$identifier])) { - $value = PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT; - $this->setExposedValue($identifier, $value, $form_state); + if ($exposed) { + if (!isset($this->options['limit'])) { + $this->options['limit'] = FALSE; + } + if (!$this->options['limit']) { + if (!isset($user_input[$identifier]) + || (isset($user_input[$identifier]) && $user_input[$identifier] === $default_langcode) + ) { + $this->setExposedValue($identifier, PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT, $form_state); + } + } + elseif (isset($user_input[$identifier]) && !isset($language_options[$user_input[$identifier]])) { + $this->setExposedValue($identifier, array_keys($language_options)[0], $form_state); + $this->value = array_keys($language_options)[0]; + } } } + $this->always_required = TRUE; + $form['value'] = [ '#type' => 'select', '#title' => $this->t('Target language'), '#options' => $this->buildLanguageOptions(), '#multiple' => FALSE, - '#weight' => 0, + '#required' => TRUE, '#default_value' => $this->value, ]; + $form['expose']['identifier'] = [ + '#type' => 'hidden', + '#value' => static::$targetExposedKey, + ]; + if (!$exposed) { $form['remove'] = [ '#type' => 'checkbox', @@ -113,16 +221,55 @@ class TranslationTargetLanguageFilter extends FilterPluginBase implements Contai * Options are based on configurable languages or site default one. */ protected function buildLanguageOptions() { + $options = []; + if ($this->localTranslation && $this->options['limit']) { + $skills = $this->skills->getSkills(NULL, TRUE); + // Handle column options. + foreach ($this->options['column'] as $name => $column) { + if (!empty($column)) { + foreach ($skills as $langs) { + $this->processColumnOption($langs, $name, $options); + } + } + } + return $options; + } $site_default = $this->languageManager->getDefaultLanguage(); $options = $this->listLanguages(LanguageInterface::STATE_CONFIGURABLE); if (isset($options[$site_default->getId()])) { unset($options[$site_default->getId()]); } - return [PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT => $site_default->getName()] + $options; } + /** + * Process column options. + * + * @param array $languages + * Languages array. + * @param string $column + * Column name. + */ + protected function processColumnOption(array $languages, $column, &$options) { + $key = "language_$column"; + if (isset($languages[$key])) { + $key = $languages[$key]; + $options[$key] = $this->languageManager + ->getLanguage($key) + ->getName(); + } + } + + /** + * {@inheritdoc} + */ + protected function listLanguages($flags = LanguageInterface::STATE_ALL, array $current_values = NULL) { + return array_map(function ($language) { + return (string) $language; + }, parent::listLanguages($flags, $current_values)); + } + /** * {@inheritdoc} */ diff --git a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/join/TranslationLanguageJoin.php b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/join/TranslationLanguageJoin.php index de5f533a5..3f16b895d 100755 --- a/sites/all/modules/contrib/views/translation_views/src/Plugin/views/join/TranslationLanguageJoin.php +++ b/sites/all/modules/contrib/views/translation_views/src/Plugin/views/join/TranslationLanguageJoin.php @@ -29,7 +29,7 @@ class TranslationLanguageJoin extends JoinPluginBase implements ContainerFactory public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->database = $database; - $this->eid = $this->configuration['entity_pk']; + $this->eid = $this->configuration['entity_id']; } /** @@ -53,14 +53,14 @@ class TranslationLanguageJoin extends JoinPluginBase implements ContainerFactory if (!empty($this->configuration['langcodes_as_count'])) { $query->addExpression("COUNT(efd.langcode)", 'count_langs'); + if (isset($this->configuration['include_original_language']) + && $this->configuration['include_original_language'] == FALSE) { + $query->where('efd.default_langcode != 1'); + } } else { $query->addExpression("GROUP_CONCAT(efd.langcode separator ',')", 'langs'); } - - if (!empty($this->configuration['exclude_default_langcode'])) { - $query->where('efd.default_langcode != 1'); - } $query->groupBy('efd.' . $this->eid); $this->configuration['table formula'] = $query; diff --git a/sites/all/modules/contrib/views/translation_views/src/TranslationCountTrait.php b/sites/all/modules/contrib/views/translation_views/src/TranslationCountTrait.php index 276f6c2ad..0bc0d12e6 100644 --- a/sites/all/modules/contrib/views/translation_views/src/TranslationCountTrait.php +++ b/sites/all/modules/contrib/views/translation_views/src/TranslationCountTrait.php @@ -34,9 +34,9 @@ trait TranslationCountTrait { 'field' => $keys['id'], 'table' => $query_base_table, 'left_table' => $query_base_table, - 'exclude_default_langcode' => TRUE, + 'include_original_language' => !empty($this->options['include_original_language']), 'langcodes_as_count' => TRUE, - 'entity_pk' => $keys['id'], + 'entity_id' => $keys['id'], ]; $tableAlias = $query->ensureTable($query_base_table, $this->relationship); diff --git a/sites/all/modules/contrib/views/translation_views/src/TranslationViewsFieldDefinitions.php b/sites/all/modules/contrib/views/translation_views/src/TranslationViewsFieldDefinitions.php index 18fea1e7d..2462fee17 100644 --- a/sites/all/modules/contrib/views/translation_views/src/TranslationViewsFieldDefinitions.php +++ b/sites/all/modules/contrib/views/translation_views/src/TranslationViewsFieldDefinitions.php @@ -10,7 +10,7 @@ namespace Drupal\translation_views; final class TranslationViewsFieldDefinitions { /** - * Ttranslation status field definition. + * Translation status field definition. */ public static function buildStatusField() { return [ @@ -34,7 +34,7 @@ final class TranslationViewsFieldDefinitions { } /** - * Translations count field definition. + * Translation count field definition. */ public static function buildCountField() { return [ @@ -44,11 +44,11 @@ final class TranslationViewsFieldDefinitions { 'title' => t('Translation counter'), 'field' => [ 'id' => 'translation_views_translation_count', - 'help' => t('Amount of translations (without default language).'), + 'help' => t('Amount of translations.'), ], 'filter' => [ 'id' => 'translation_views_translation_count', - 'help' => t('Filter rows by amount of translations (without default language).'), + 'help' => t('Filter rows by amount of translations.'), ], ], ]; @@ -93,7 +93,7 @@ final class TranslationViewsFieldDefinitions { $field['translation_source']['filter']['id'] = 'translation_views_source_equals_row'; $field['translation_source']['field']['help'] = t('A boolean indicating whether the translation source of target language is same as the row language'); $field['translation_source']['filter']['help'] = t('A boolean indicating whether the translation source of target language is same as the row language'); - $field['translation_source']['title'] = t('Translation source equals row language'); + $field['translation_source']['title'] = t('Source translation of target language equals row language'); return $field; } @@ -144,4 +144,21 @@ final class TranslationViewsFieldDefinitions { return $field; } + /** + * Translation moderation state field definition. + */ + public static function buildModerationStateField() { + return [ + 'translation_moderation_state' => [ + 'title' => t('Translation moderation state'), + 'field' => [ + 'id' => 'translation_moderation_state', + 'property' => 'translation_moderation_state', + 'field_name' => t('Translation moderation state'), + 'help' => t('Moderation state of the translation.'), + ], + ], + ]; + } + } diff --git a/sites/all/modules/contrib/views/translation_views/src/TranslationViewsTargetLanguage.php b/sites/all/modules/contrib/views/translation_views/src/TranslationViewsTargetLanguage.php index 035a6c6c8..ac0e9db83 100644 --- a/sites/all/modules/contrib/views/translation_views/src/TranslationViewsTargetLanguage.php +++ b/sites/all/modules/contrib/views/translation_views/src/TranslationViewsTargetLanguage.php @@ -20,18 +20,24 @@ trait TranslationViewsTargetLanguage { * @var \Drupal\Core\Language\LanguageManagerInterface */ protected $languageManager; - + /** + * Target exposed key. + * + * @var string + */ public static $targetExposedKey = 'translation_target_language'; /** * Get target language from exposed input. * * @return string - * Current selected target langcode + * The selected target langcode. */ - protected function getTargetLanguage() { + protected function getTargetLangcode() { $inputs = $this->view->getExposedInput(); - $langcode = $inputs[self::$targetExposedKey]; + $langcode = isset($inputs[self::$targetExposedKey]) + ? $inputs[self::$targetExposedKey] + : PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT; return $langcode == PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT ? $this->languageManager->getDefaultLanguage()->getId() diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/config/install/views.view.test_local_translation_content_integration.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/config/install/views.view.test_local_translation_content_integration.yml new file mode 100644 index 000000000..d4d912824 --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/config/install/views.view.test_local_translation_content_integration.yml @@ -0,0 +1,253 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + module: + - node + - translation_views + - user +id: test_local_translation_content_integration +label: 'Test local translation content integration' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: none + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: none + options: + offset: 0 + style: + type: default + row: + type: 'entity:node' + options: + relationship: none + view_mode: teaser + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: language + settings: + link_to_entity: false + native_language: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: langcode + plugin_id: field + filters: + translation_target_language: + id: translation_target_language + table: node_translation + field: translation_target_language + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '***LANGUAGE_site_default***' + group: 1 + exposed: true + expose: + operator_id: '' + label: 'Target language' + description: '' + use_operator: false + operator: '' + identifier: translation_target_language + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + anonymous: '0' + administrator: '0' + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + remove: true + limit: false + column: + from: '' + to: 'to' + entity_type: node + plugin_id: translation_views_target_language + sorts: { } + title: 'Test local translation content filter' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test-local-translation-content-filter + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/translation_views_local_translation_test.info.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/translation_views_local_translation_test.info.yml new file mode 100644 index 000000000..b272afabe --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_local_translation_test/translation_views_local_translation_test.info.yml @@ -0,0 +1,19 @@ +name: 'Translation views Local Translation integration test' +type: module +description: '' +package: Testing +# core: 8.x +dependencies: + - drupal:node + - drupal:views + - drupal:translation_views + - drupal:content_translation + - drupal:language + - local_translation:local_translation + - local_translation:local_translation_content + +# Information added by Drupal.org packaging script on 2019-02-07 +version: '8.x-1.0-alpha5' +core: '8.x' +project: 'translation_views' +datestamp: 1549581183 diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.comment_translation.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.comment_translation.yml index d1400ce16..1e8727d73 100644 --- a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.comment_translation.yml +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.comment_translation.yml @@ -358,7 +358,7 @@ display: relationship: none group_type: group admin_label: '' - label: 'Target language equals default language' + label: 'Source translation of target language equals row language' exclude: false alter: alter_text: false @@ -514,6 +514,7 @@ display: format_plural_string: !!binary MQNAY291bnQ= prefix: '' suffix: '' + include_original_language: false entity_type: comment plugin_id: translation_views_translation_count translation_outdated: @@ -577,7 +578,7 @@ display: relationship: none group_type: group admin_label: '' - label: 'Translation source equals row language' + label: 'Source translation of target language equals row language' exclude: false alter: alter_text: false @@ -881,7 +882,7 @@ display: group_type: group admin_label: '' operator: '=' - value: 2 + value: All group: 1 exposed: true expose: diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.content_moderation_integration_test.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.content_moderation_integration_test.yml new file mode 100644 index 000000000..feb3d2b76 --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.content_moderation_integration_test.yml @@ -0,0 +1,166 @@ +uuid: 9f3298b4-2171-4aac-8684-01bdc45909c8 +langcode: en +status: true +dependencies: + config: + - node.type.article + module: + - node + - translation_views + - user +id: content_moderation_integration_test +label: 'Content Moderation Integration test' +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: some + options: + items_per_page: 10 + offset: 0 + style: + type: default + row: + type: fields + fields: + translation_moderation_state: + id: translation_moderation_state + table: node_translation + field: translation_moderation_state + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + display_name: state + entity_type: node + plugin_id: translation_moderation_state + filters: + type: + id: type + table: node_field_data + field: type + value: + article: article + entity_type: node + entity_field: type + plugin_id: bundle + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + entity_type: node + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + title: 'Content Moderation Integration test' + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: content-moderation-integration-test + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.test_operations_links.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.test_operations_links.yml new file mode 100644 index 000000000..2e60e9ec8 --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.test_operations_links.yml @@ -0,0 +1,260 @@ +uuid: 0df3cfda-b782-4b0d-9810-3ca90a1638cc +langcode: en +status: true +dependencies: + config: + - node.type.article + module: + - node + - translation_views + - user +id: test_operations_links +label: test_operations_links +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + row: + type: fields + fields: + langcode: + id: langcode + table: node_field_data + field: langcode + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: language + settings: + link_to_entity: false + native_language: false + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + entity_type: node + entity_field: langcode + plugin_id: field + translation_operations: + id: translation_operations + table: node_translation + field: translation_operations + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + destination: false + entity_type: node + plugin_id: translation_views_operations + filters: + status: + value: '1' + table: node_field_data + field: status + plugin_id: boolean + entity_type: node + entity_field: status + id: status + expose: + operator: '' + group: 1 + type: + id: type + table: node_field_data + field: type + value: + article: article + entity_type: node + entity_field: type + plugin_id: bundle + sorts: + created: + id: created + table: node_field_data + field: created + order: DESC + entity_type: node + entity_field: created + plugin_id: date + relationship: none + group_type: group + admin_label: '' + exposed: false + expose: + label: '' + granularity: second + title: Test + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } + page_1: + display_plugin: page + id: page_1 + display_title: Page + position: 1 + display_options: + display_extenders: { } + path: test_operations_links + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.translation_views_all_filters_and_fields.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.translation_views_all_filters_and_fields.yml index f0d134208..2372c5aac 100644 --- a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.translation_views_all_filters_and_fields.yml +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/views.view.translation_views_all_filters_and_fields.yml @@ -322,7 +322,7 @@ display: relationship: none group_type: group admin_label: '' - label: 'Target language equals default language' + label: 'Source translation of target language equals row language' exclude: false alter: alter_text: false @@ -478,6 +478,7 @@ display: format_plural_string: !!binary MQNAY291bnQ= prefix: '' suffix: '' + include_original_language: false entity_type: node plugin_id: translation_views_translation_count translation_outdated: @@ -541,7 +542,7 @@ display: relationship: none group_type: group admin_label: '' - label: 'Translation source equals row language' + label: 'Source translation of target language equals row language' exclude: false alter: alter_text: false @@ -836,7 +837,7 @@ display: group_type: group admin_label: '' operator: '=' - value: 2 + value: All group: 1 exposed: true expose: diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/workflows.workflow.editorial.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/workflows.workflow.editorial.yml new file mode 100644 index 000000000..91a0026da --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/test_views/workflows.workflow.editorial.yml @@ -0,0 +1,63 @@ +langcode: en +status: true +dependencies: + config: + - node.type.article + module: + - content_moderation +id: editorial +label: Editorial +type: content_moderation +type_settings: + states: + archived: + label: Archived + weight: 5 + published: false + default_revision: true + draft: + label: Draft + published: false + default_revision: false + weight: -5 + published: + label: Published + published: true + default_revision: true + weight: 0 + transitions: + archive: + label: Archive + from: + - published + to: archived + weight: 2 + archived_draft: + label: 'Restore to Draft' + from: + - archived + to: draft + weight: 3 + archived_published: + label: Restore + from: + - archived + to: published + weight: 4 + create_new_draft: + label: 'Create New Draft' + to: draft + weight: 0 + from: + - draft + - published + publish: + label: Publish + to: published + weight: 1 + from: + - draft + - published + entity_types: + node: + - article diff --git a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/translation_views_test_views.info.yml b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/translation_views_test_views.info.yml index 9e1013a12..3cc541c7b 100644 --- a/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/translation_views_test_views.info.yml +++ b/sites/all/modules/contrib/views/translation_views/tests/modules/translation_views_test_views/translation_views_test_views.info.yml @@ -9,9 +9,11 @@ dependencies: - drupal:translation_views - drupal:content_translation - drupal:language + - drupal:content_moderation + - drupal:workflows -# Information added by Drupal.org packaging script on 2018-05-23 -version: '8.x-1.0-alpha2' +# Information added by Drupal.org packaging script on 2019-02-07 +version: '8.x-1.0-alpha5' core: '8.x' project: 'translation_views' -datestamp: 1527090186 +datestamp: 1549581183 diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/CommentFullViewFiltersFieldsTest.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/CommentFullViewFiltersFieldsTest.php index ac81e4ba8..8f1df71f5 100644 --- a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/CommentFullViewFiltersFieldsTest.php +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/CommentFullViewFiltersFieldsTest.php @@ -116,7 +116,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase { 'langcode' => $default_params[0], 'translation_target_language' => $default_params[1], 'translation_outdated' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => $orderColumn, 'sort' => $sort, ], @@ -234,7 +234,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase { $this->assertTextInRow(1, 'translation-changed', ':'); $this->assertTextInRow(1, 'translation-count', '0'); $this->assertTextInRow(1, 'translation-outdated', 'No'); - $this->assertTextInRow(1, 'translation-source', 'No'); + $this->assertTextInRow(1, 'translation-source', 'Yes'); $this->assertTextInRow(1, 'translation-status', 'Translated'); $this->assertTextInRow(1, 'translation-operations', 'Edit'); } @@ -347,7 +347,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase { 2 => 'Yes', ]); - // Translation source equals row language. + // Source translation of target language equals row language. $this->assertOrder('translation_source', 'asc', [ 'All', '***LANGUAGE_site_default***', @@ -357,7 +357,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase { 2 => 'No', 3 => 'No', 4 => 'No', - 5 => 'No', + 5 => 'Yes', 6 => 'Yes', ]); @@ -370,7 +370,7 @@ class CommentFullViewFiltersFieldsTest extends ViewTestBase { 5 => 'No', 4 => 'No', 3 => 'No', - 2 => 'No', + 2 => 'Yes', 1 => 'Yes', ]); diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentFullViewFiltersFieldsTest.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentFullViewFiltersFieldsTest.php index b8d406236..bfeac3074 100644 --- a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentFullViewFiltersFieldsTest.php +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentFullViewFiltersFieldsTest.php @@ -116,7 +116,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => '***LANGUAGE_site_default***', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -131,7 +131,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'de', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -146,7 +146,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'fr', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -161,7 +161,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => 'de', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -176,7 +176,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => 'fr', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -191,7 +191,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 1, + 'translation_status' => '1', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -206,7 +206,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 0, + 'translation_status' => '0', ], ]); $this->assertSession()->statusCodeEquals(200); @@ -227,7 +227,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'title', 'sort' => 'asc', ], @@ -247,7 +247,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'title', 'sort' => 'desc', ], @@ -268,7 +268,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'langcode', 'sort' => 'asc', ], @@ -288,7 +288,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'langcode', 'sort' => 'desc', ], @@ -309,7 +309,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_default', 'sort' => 'asc', ], @@ -329,7 +329,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_default', 'sort' => 'desc', ], @@ -350,7 +350,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_changed', 'sort' => 'asc', ], @@ -370,7 +370,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_changed', 'sort' => 'desc', ], @@ -391,7 +391,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_count', 'sort' => 'asc', ], @@ -411,7 +411,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_count', 'sort' => 'desc', ], @@ -432,7 +432,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_status', 'sort' => 'asc', ], @@ -452,7 +452,7 @@ class ContentFullViewFiltersFieldsTest extends ViewTestBase { 'content_translation_source' => 'All', 'translation_target_language' => '***LANGUAGE_site_default***', 'translation_default' => 'All', - 'translation_status' => 2, + 'translation_status' => 'All', 'order' => 'translation_status', 'sort' => 'desc', ], diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentModerationIntegration.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentModerationIntegration.php new file mode 100644 index 000000000..25eb6a364 --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/ContentModerationIntegration.php @@ -0,0 +1,103 @@ +drupalLogin($this->rootUser); + // Import test views. + ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']); + // Create additional language. + ConfigurableLanguage::createFromLangcode('af')->save(); + + // Enable translation for article nodes. + $this->drupalPostForm('admin/config/regional/content-language', [ + "entity_types[node]" => 1, + "settings[node][article][translatable]" => 1, + "settings[node][article][settings][language][language_alterable]" => 1, + ], 'Save configuration'); + // Flush definitions caches. + \Drupal::entityTypeManager()->clearCachedDefinitions(); + + // Enable moderation state for article nodes. + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article'); + $workflow->save(); + // Logout. + $this->drupalLogout(); + } + + /** + * Test view's field "Translation Moderation State". + */ + public function testTranslationModerationFieldViews() { + // Login as a root user. + $this->drupalLogin($this->rootUser); + // Create node for test. + $node = $this->createNode(['type' => 'article']); + + // Ensure we have moderation state "Draft" by default + // in the newly created node. + $this->assertTrue($node->hasField('moderation_state')); + $this->assertFalse($node->get('moderation_state')->isEmpty()); + $this->assertEquals('draft', $node->get('moderation_state')->first()->getString()); + + // Go to the testing view's page. + $this->drupalGet('/content-moderation-integration-test'); + $this->assertSession()->statusCodeEquals(200); + // Check that created node has "Draft" moderation state by default. + $this->assertSession()->elementTextContains( + 'css', + '.views-field-translation-moderation-state span', + 'Draft' + ); + + // Change moderation state to "Published". + $node->set('moderation_state', 'published')->save(); + + // Reload the testing view's page. + $this->drupalGet('/content-moderation-integration-test'); + $this->assertSession()->statusCodeEquals(200); + // Check that the moderation state value has been changed in view's field. + $this->assertSession()->elementTextContains( + 'css', + '.views-field-translation-moderation-state span', + 'Published' + ); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/LocalTranslationContentIntegrationTest.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/LocalTranslationContentIntegrationTest.php new file mode 100644 index 000000000..15b09c49a --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/LocalTranslationContentIntegrationTest.php @@ -0,0 +1,221 @@ +setUpTest(); + } + + /** + * Additional steps for tests set up. + */ + protected function setUpTest() { + $this->drupalLogin($this->rootUser); + $this->skills = $this->container->get('local_translation.user_skills'); + $this->createLanguages(); + $this->enableTranslation('node', 'article'); + $this->drupalLogout(); + } + + /** + * Get array of all testing languages. + * + * @return array + * All testing langcodes array. + */ + private static function getAllTestingLanguages() { + return array_merge(static::$registeredSkills, static::$unregisteredSkills); + } + + /** + * Change language settings for entity types. + * + * @param string $category + * Entity category (e.g. node). + * @param string $subcategory + * Entity subcategory (e.g. article). + */ + protected function enableTranslation($category, $subcategory) { + $this->drupalPostForm('admin/config/regional/content-language', [ + "entity_types[$category]" => 1, + "settings[$category][$subcategory][translatable]" => 1, + "settings[$category][$subcategory][settings][language][language_alterable]" => 1, + ], 'Save configuration'); + \Drupal::entityTypeManager()->clearCachedDefinitions(); + } + + /** + * Register translation skills for testing. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + protected function registerTestSkills() { + $this->skills->addSkill(static::$registeredSkills); + foreach (static::$registeredSkills as $skill) { + $this->assertTrue($this->skills->userHasSkill($skill)); + } + } + + /** + * Create additional languages for testing. + */ + protected function createLanguages() { + try { + foreach (static::getAllTestingLanguages() as $language) { + if ($language === $this->defaultLanguage) { + continue; + } + $this->assertEquals(1, ConfigurableLanguage::createFromLangcode($language)->save()); + } + } + catch (EntityStorageException $e) { + $this->fail('Additional languages have not been created'); + } + } + + /** + * Simply check that all required modules have been installed. + */ + public function testDependencyInstallation() { + $this->assertTrue($this->container->get('module_handler') + ->moduleExists('local_translation')); + $this->assertTrue($this->container->get('module_handler') + ->moduleExists('local_translation_content')); + $this->assertTrue($this->container->has('local_translation.user_skills')); + } + + /** + * Test local translation content integration for target language filter. + */ + public function testLocalTranslationLanguageFilterInView() { + $this->drupalLogin($this->rootUser); + $this->registerTestSkills(); + for ($i = 1; $i <= 10; $i++) { + Node::create([ + 'type' => 'article', + 'title' => $this->randomString(), + 'langcode' => static::$registeredSkills[0], + ]) + ->addTranslation(static::$registeredSkills[1], ['title' => $this->randomString()]) + ->save(); + } + + $this->drupalGet('/test-local-translation-content-filter'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->statusCodeNotEquals(404); + + // Find langcode field element. + $langcode_field = $this->getSession() + ->getPage() + ->findField('translation_target_language'); + $this->assertNotNull($langcode_field); + + // Get all existing options of the langcode filter dropdown. + $options = $langcode_field->findAll('xpath', '//option'); + $this->assertNotNull($options); + + // Prepare array of options' values. + $language_options = array_map(function ($option) { + return $option->getAttribute('value') ?: $option->getText(); + }, $options); + + $this->assertCount(4, $language_options); + $this->assertContains('***LANGUAGE_site_default***', $language_options); + $this->assertContains('fr', $language_options); + $this->assertContains('de', $language_options); + $this->assertContains('sq', $language_options); + + $this->drupalGet('/admin/structure/views/nojs/handler/test_local_translation_content_integration/page_1/filter/translation_target_language'); + // Check for the default state of the options. + $this->assertSession()->checkboxNotChecked('options[limit]'); + $this->assertSession()->checkboxNotChecked('options[column][from]'); + $this->assertSession()->checkboxChecked('options[column][to]'); + // Update options. + $this->drupalPostForm(NULL, [ + 'options[limit]' => 1, + 'options[column][from]' => 1, + 'options[column][to]' => 1, + ], 'Apply'); + $this->click('input[value="Save"]'); + + $this->drupalGet('/test-local-translation-content-filter'); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->statusCodeNotEquals(404); + + // Find langcode field element. + $langcode_field = $this->getSession() + ->getPage() + ->findField('translation_target_language'); + $this->assertNotNull($langcode_field); + + // Get all existing options of the langcode filter dropdown. + $options = $langcode_field->findAll('xpath', '//option'); + $this->assertNotNull($options); + + // Prepare array of options' values. + $language_options = array_map(function ($option) { + return $option->getAttribute('value') ?: $option->getText(); + }, $options); + + $this->assertCount(2, $language_options); + $this->assertContains('en', $language_options); + $this->assertContains('fr', $language_options); + $this->assertNotContains('de', $language_options); + $this->assertNotContains('sq', $language_options); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationTargetLanguageTest.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationTargetLanguageTest.php new file mode 100644 index 000000000..82a502b0c --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationTargetLanguageTest.php @@ -0,0 +1,211 @@ +adminUser = $this->createUser([], 'test_admin', TRUE); + $this->drupalLogin($this->adminUser); + + $this->defaultLangcode = \Drupal::languageManager() + ->getDefaultLanguage() + ->getId(); + + // Set up testing views. + ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']); + try { + $this->setUpLanguages(); + } + catch (EntityStorageException $e) { + $this->verbose($e->getMessage()); + } + // Enable translation for Article nodes. + $this->enableTranslation('node', 'article'); + + // Create testing node. + $this->drupalPostForm('node/add/article', [ + 'title[0][value]' => $this->randomString(), + ], 'Save'); + + $this->drupalLogout(); + } + + /** + * Set up languages. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + private function setUpLanguages() { + foreach (self::$langcodes as $langcode) { + ConfigurableLanguage::createFromLangcode($langcode)->save(); + } + } + + /** + * Change language settings for entity types. + * + * @param string $category + * Entity category (e.g. node). + * @param string $subcategory + * Entity subcategory (e.g. article). + */ + private function enableTranslation($category, $subcategory) { + $this->drupalPostForm('admin/config/regional/content-language', [ + "entity_types[$category]" => 1, + "settings[$category][$subcategory][translatable]" => 1, + "settings[$category][$subcategory][settings][language][language_alterable]" => 1, + ], 'Save configuration'); + \Drupal::entityTypeManager()->clearCachedDefinitions(); + } + + /** + * Translate node all specified languages. + */ + private function translateNode() { + $node = Node::load(1); + foreach (self::$langcodes as $langcode) { + if (!$node->hasTranslation($langcode)) { + $this->assertFalse($node->hasTranslation($langcode)); + $node->addTranslation($langcode, ['title' => $this->randomMachineName()])->save(); + $this->assertTrue($node->hasTranslation($langcode)); + } + } + } + + /** + * Generate random target language ID from available list. + * + * @return string + * Language ID. + */ + private function generateTargetLanguage() { + $target_language = self::$langcodes[mt_rand(0, 4)]; + $this->assertNotNull($target_language); + return $target_language; + } + + /** + * Assert that the response HTTP code is 200. + * + * @throws \Behat\Mink\Exception\ExpectationException + */ + private function assertResponseOk() { + $this->assertSession()->statusCodeEquals(200); + } + + /** + * Test translation operations target language. + * + * @throws \Behat\Mink\Exception\ElementHtmlException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function testTranslationOperationsTargetLanguage() { + $base_selector = 'table > tbody > tr:nth-child(1) .views-field-translation-operations ul li'; + $this->drupalLogin($this->adminUser); + + // Check for "Add" links target languages. + $target_language = $this->generateTargetLanguage(); + $this->drupalGet('translation-views-all-filters-and-fields', [ + 'query' => [ + 'content_translation_source' => 'All', + 'translation_target_language' => $target_language, + 'translation_default' => 'All', + 'translation_status' => 'All', + ], + ]); + $this->assertResponseOk(); + $expected_create_link = "/$target_language/node/1/translations/add/{$this->defaultLangcode}/$target_language"; + $this->assertSession() + ->elementAttributeContains( + 'css', + "{$base_selector} a", + 'href', + $expected_create_link + ); + // Translate nodes. + $this->translateNode(); + // Check for "Edit" and "Delete" links target languages. + $target_language = $this->generateTargetLanguage(); + $this->drupalGet('translation-views-all-filters-and-fields', [ + 'query' => [ + 'content_translation_source' => 'All', + 'translation_target_language' => $target_language, + 'translation_default' => 'All', + 'translation_status' => 'All', + ], + ]); + $this->assertResponseOk(); + $this->assertSession() + ->elementAttributeContains( + 'css', + "{$base_selector}.edit a", + 'href', + "/$target_language/node/1/edit" + ); + $this->assertSession() + ->elementAttributeContains( + 'css', + "{$base_selector}.delete a", + 'href', + "/$target_language/node/1/delete" + ); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationsFieldPermissionsTest.php b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationsFieldPermissionsTest.php new file mode 100644 index 000000000..c6b15c0dd --- /dev/null +++ b/sites/all/modules/contrib/views/translation_views/tests/src/Functional/TranslationOperationsFieldPermissionsTest.php @@ -0,0 +1,285 @@ +setUpUsers(); + $this->drupalLogin($this->adminUser); + + // Set up testing views. + ViewTestData::createTestViews(get_class($this), ['translation_views_test_views']); + try { + $this->setUpLanguages(); + } + catch (EntityStorageException $e) { + $this->verbose($e->getMessage()); + } + // Enable translation for Article nodes. + $this->enableTranslation('node', 'article'); + + // Create testing node. + $this->drupalPostForm('node/add/article', [ + 'title[0][value]' => $this->randomString(), + ], 'Save'); + + $this->drupalLogout(); + } + + /** + * Set up users with different sets of permissions. + */ + private function setUpUsers() { + $this->adminUser = $this->createUser([], 'test_admin', TRUE); + $this->userCreate = $this->createUser(['create content translations']); + $this->userUpdate = $this->createUser(['update content translations']); + $this->userDelete = $this->createUser(['delete content translations']); + } + + /** + * Set up languages. + * + * @throws \Drupal\Core\Entity\EntityStorageException + */ + private function setUpLanguages() { + foreach (self::$langcodes as $langcode) { + ConfigurableLanguage::createFromLangcode($langcode)->save(); + } + } + + /** + * Checks that text is in specific row. + * + * @param int $row_number + * Table row order number. + * @param string $css_class + * Part of the css class of required field. + * @param string $text + * Text that should be found in the element. + * + * @throws \Behat\Mink\Exception\ElementTextException + */ + private function assertTextInRow($row_number, $css_class, $text) { + $this->assertSession() + ->elementTextContains( + 'css', + ".view-content > div:nth-child({$row_number}) .views-field-{$css_class}", + $text + ); + } + + /** + * Change language settings for entity types. + * + * @param string $category + * Entity category (e.g. node). + * @param string $subcategory + * Entity subcategory (e.g. article). + */ + private function enableTranslation($category, $subcategory) { + $this->drupalPostForm('admin/config/regional/content-language', [ + "entity_types[$category]" => 1, + "settings[$category][$subcategory][translatable]" => 1, + "settings[$category][$subcategory][settings][language][language_alterable]" => 1, + ], 'Save configuration'); + \Drupal::entityTypeManager()->clearCachedDefinitions(); + } + + /** + * Go to the /translate/content page. + * + * @throws \Behat\Mink\Exception\ExpectationException + */ + private function goToTestingView() { + $this->drupalGet('/test_operations_links'); + $this->assertSession()->statusCodeEquals(200); + } + + /** + * Translate node all specified languages. + */ + private function translateNode() { + $node = Node::load(1); + foreach (self::$langcodes as $langcode) { + if (!$node->hasTranslation($langcode)) { + $node->addTranslation($langcode, ['title' => $this->randomMachineName()]) + ->save(); + } + } + } + + /** + * Add specific set of permissions to the "authenticated" role. + * + * @param array $permissions + * Permissions array. + */ + private function addPermissionsForAuthUser(array $permissions = []) { + if (!empty($permissions)) { + $role = Role::load(Role::AUTHENTICATED_ID); + if ($role instanceof Role) { + $this->grantPermissions($role, $permissions); + } + } + } + + /** + * Test translation operation create permissions. + * + * @throws \Behat\Mink\Exception\ElementTextException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function testTranslationOperationsCreatePermissions() { + $default_language = \Drupal::languageManager()->getDefaultLanguage(); + $target_language = static::$langcodes[mt_rand(0, 4)]; + $this->assertNotNull($target_language); + $this->assertNotNull($default_language); + + $this->drupalLogin($this->userCreate); + + $this->assertTrue($this->userCreate->hasPermission('create content translations')); + $this->assertFalse($this->userCreate->hasPermission('translate any entity')); + + $this->drupalGet('/translate/content', [ + 'query' => [ + 'langcode' => $default_language->getId(), + 'translation_target_language' => $target_language, + 'translation_outdated' => 'All', + 'translation_status' => 'All', + ], + ]); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->elementNotExists( + 'css', + 'table > tbody > tr:nth-child(1) .views-field-translation-operations ul li a' + ); + + $this->addPermissionsForAuthUser(['translate any entity']); + $this->assertTrue($this->userCreate->hasPermission('translate any entity')); + + $this->drupalGet('/translate/content', [ + 'query' => [ + 'langcode' => $default_language->getId(), + 'translation_target_language' => $target_language, + 'translation_outdated' => 'All', + 'translation_status' => 'All', + ], + ]); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession() + ->elementTextContains( + 'css', + "table > tbody > tr:nth-child(1) .views-field-translation-operations ul li a", + 'Add' + ); + } + + /** + * Test translation operation update permissions. + * + * @throws \Behat\Mink\Exception\ElementTextException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function testTranslationOperationsUpdatePermissions() { + $this->translateNode(); + $this->assertTrue($this->userUpdate->hasPermission('update content translations')); + $this->assertFalse($this->userUpdate->hasPermission('translate any entity')); + $this->drupalLogin($this->userUpdate); + $this->goToTestingView(); + $this->assertTextInRow( + 1, + 'translation-operations ul li a', + 'Edit' + ); + } + + /** + * Test translation operation delete permissions. + * + * @throws \Behat\Mink\Exception\ElementTextException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function testTranslationOperationsDeletePermissions() { + $this->translateNode(); + $this->assertTrue($this->userDelete->hasPermission('delete content translations')); + $this->assertFalse($this->userDelete->hasPermission('translate any entity')); + $this->drupalLogin($this->userDelete); + $this->goToTestingView(); + $this->assertTextInRow( + 1, + 'translation-operations ul li a', + 'Delete' + ); + } + +} diff --git a/sites/all/modules/contrib/views/translation_views/translation_views.info.yml b/sites/all/modules/contrib/views/translation_views/translation_views.info.yml index 17ad7a092..974166190 100644 --- a/sites/all/modules/contrib/views/translation_views/translation_views.info.yml +++ b/sites/all/modules/contrib/views/translation_views/translation_views.info.yml @@ -7,8 +7,11 @@ dependencies: - drupal:content_translation - drupal:views -# Information added by Drupal.org packaging script on 2018-05-23 -version: '8.x-1.0-alpha2' +test_dependencies: + - local_translation:local_translation + +# Information added by Drupal.org packaging script on 2019-02-07 +version: '8.x-1.0-alpha5' core: '8.x' project: 'translation_views' -datestamp: 1527090186 +datestamp: 1549581183 diff --git a/sites/all/modules/contrib/views/translation_views/translation_views.module b/sites/all/modules/contrib/views/translation_views/translation_views.module index 0cc2baaeb..d5b30f376 100644 --- a/sites/all/modules/contrib/views/translation_views/translation_views.module +++ b/sites/all/modules/contrib/views/translation_views/translation_views.module @@ -4,8 +4,9 @@ * @file * Provide hooks for translation_views module. * - * Basicaly this file only used as workaround for this issue: - * https://www.drupal.org/node/2325463. + * Basicaly this file is only used as workaround for these issues: + * https://www.drupal.org/project/drupal/issues/2954032 + * https://www.drupal.org/project/drupal/issues/2944278 */ use Drupal\Core\Database\Query\AlterableInterface; @@ -41,12 +42,12 @@ function translation_views_form_alter(&$form, FormStateInterface $form_state) { } $entity = $form_object->getEntity(); - $op = $form_object->getOperation(); + $operations = $form_object->getOperation(); if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && count($entity->getTranslationLanguages()) > 1 - && in_array($op, ['edit', 'add', 'default'], TRUE) + && in_array($operations, ['edit', 'add', 'default'], TRUE) ) { $form['source_langcode']['submit']['#submit'][] = 'translation_views_content_form_fix_destination'; } @@ -66,9 +67,7 @@ function translation_views_content_form_fix_destination($form, FormStateInterfac $options = []; // Remove destination parameter from request, - // to allow "Change" button works like desired, - // see similar issue here: https://www.drupal.org/node/2325463. - // @todo: Create core issue for the content_translation module. + // to allow "Change" button works like desired if ($query->has('destination')) { $options['query']['destination'] = $query->get('destination'); $query->remove('destination'); diff --git a/sites/all/modules/contrib/views/translation_views/translation_views.views.inc b/sites/all/modules/contrib/views/translation_views/translation_views.views.inc index 60185506c..f1b91e84b 100755 --- a/sites/all/modules/contrib/views/translation_views/translation_views.views.inc +++ b/sites/all/modules/contrib/views/translation_views/translation_views.views.inc @@ -2,7 +2,7 @@ /** * @file - * Provide views extra filters/fields for translator needs. + * Provide views fields/filters for content translations. * * @ingroup views_module_handlers */ @@ -50,7 +50,7 @@ function translation_views_views_data_alter(array &$data) { $translations_table =& $data[$key]; $translations_table_target =& $data[$target_key]; - $pk = $entity_type->getKey('id'); + $entity_id = $entity_type->getKey('id'); $translations_table['table'] = [ 'group' => $group, @@ -58,10 +58,10 @@ function translation_views_views_data_alter(array &$data) { 'join' => [ $field_data_table => [ 'join_id' => 'translation_views_language_join', - 'entity_pk' => $pk, + 'entity_id' => $entity_id, 'type' => 'LEFT', - 'left_field' => $pk, - 'field' => $pk, + 'left_field' => $entity_id, + 'field' => $entity_id, 'table' => $field_data_table, ], ], @@ -72,13 +72,17 @@ function translation_views_views_data_alter(array &$data) { $translations_table += FieldDefinitions::buildOpLinksField(); $translations_table += FieldDefinitions::buildCountField(); + $module_handler = \Drupal::service('module_handler'); + if ($module_handler->moduleExists('content_moderation')) { + $translations_table += FieldDefinitions::buildModerationStateField(); + } $translations_table_target['table'] = [ 'group' => $group, 'join' => [ $field_data_table => [ 'type' => 'LEFT', - 'left_field' => $pk, - 'field' => $pk, + 'left_field' => $entity_id, + 'field' => $entity_id, 'table' => $field_data_table, 'extra' => [ [ @@ -99,9 +103,11 @@ function translation_views_views_data_alter(array &$data) { $translations_table_target += FieldDefinitions::buildDefaultLangcodeField( $data[$field_data_table]['default_langcode'] ); - $translations_table_target += FieldDefinitions::buildChangedField( - $data[$field_data_table]['changed'] - ); + if (isset($data[$field_data_table]['changed'])) { + $translations_table_target += FieldDefinitions::buildChangedField( + $data[$field_data_table]['changed'] + ); + } } } } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml b/sites/all/modules/contrib/views/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml index 546ae53e5..0fae1cbc7 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml +++ b/sites/all/modules/contrib/views/views_bulk_operations/config/schema/views_bulk_operations.views.schema.yml @@ -23,3 +23,6 @@ views.field.views_bulk_operations_bulk_form: preconfiguration: type: ignore label: 'Preliminary configuration array' + clear_on_exposed: + type: boolean + label: 'Clear on exposed' diff --git a/sites/all/modules/contrib/views/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml b/sites/all/modules/contrib/views/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml index 562a3de87..06947af3d 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml +++ b/sites/all/modules/contrib/views/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml @@ -6,8 +6,8 @@ package: 'Views Bulk Operations' dependencies: - drupal:views_bulk_operations -# Information added by Drupal.org packaging script on 2018-07-02 -version: '8.x-2.4' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-2.5' core: '8.x' project: 'views_bulk_operations' -datestamp: 1530516826 +datestamp: 1550740388 diff --git a/sites/all/modules/contrib/views/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml b/sites/all/modules/contrib/views/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml index 4eff3573e..c8c57af49 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml +++ b/sites/all/modules/contrib/views/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml @@ -6,8 +6,8 @@ package: 'Examples' dependencies: - drupal:views_bulk_operations -# Information added by Drupal.org packaging script on 2018-07-02 -version: '8.x-2.4' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-2.5' core: '8.x' project: 'views_bulk_operations' -datestamp: 1530516826 +datestamp: 1550740388 diff --git a/sites/all/modules/contrib/views/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php b/sites/all/modules/contrib/views/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php index 3908916dd..8f87b0629 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php +++ b/sites/all/modules/contrib/views/views_bulk_operations/src/Controller/ViewsBulkOperationsController.php @@ -9,6 +9,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionProcessorInterface; use Drupal\user\PrivateTempStoreFactory; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\Ajax\AjaxResponse; @@ -75,7 +76,12 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI $this->deleteTempstoreData(); $this->actionProcessor->executeProcessing($view_data); - return batch_process($view_data['redirect_url']); + if ($view_data['batch']) { + return batch_process($view_data['redirect_url']); + } + else { + return new RedirectResponse($view_data['redirect_url']->setAbsolute()->toString()); + } } /** diff --git a/sites/all/modules/contrib/views/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php b/sites/all/modules/contrib/views/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php index 66d893f3c..13087d299 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php +++ b/sites/all/modules/contrib/views/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php @@ -215,6 +215,12 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe 'redirect_url' => Url::createFromRequest(clone $this->requestStack->getCurrentRequest()), 'exposed_input' => $this->view->getExposedInput(), ]; + $query = $variable['redirect_url']->getOption('query'); + if (!$query) { + $query = []; + } + $query += $variable['exposed_input']; + $variable['redirect_url']->setOption('query', $query); // Create tempstore data object if it doesn't exist. if (!is_array($this->tempStoreData)) { @@ -624,7 +630,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe $action_id = $form_state->getValue('action'); if (!empty($action_id)) { $action = $this->actions[$action_id]; - if ($this->isConfigurable($action)) { + if ($this->isActionConfigurable($action)) { $actionObject = $this->actionManager->createInstance($action_id); $form['header'][$this->options['id']]['configuration'] += $actionObject->buildConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state); $form['header'][$this->options['id']]['configuration']['#config_included'] = TRUE; @@ -778,7 +784,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe $this->tempStoreData['list'] = []; } - $configurable = $this->isConfigurable($action); + $configurable = $this->isActionConfigurable($action); // Get configuration if using AJAX. if ($configurable && empty($this->options['form_step'])) { @@ -794,6 +800,11 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe } // Routing - determine redirect route. + + // Set default redirection due to issue #2952498. + // TODO: remove the next line when core cause is eliminated. + $redirect_route = 'views_bulk_operations.execute_batch'; + if ($this->options['form_step'] && $configurable) { $redirect_route = 'views_bulk_operations.execute_configurable'; } @@ -887,7 +898,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe /** * Check if an action is configurable. */ - protected function isConfigurable($action) { + protected function isActionConfigurable($action) { return (in_array('Drupal\Core\Plugin\PluginFormInterface', class_implements($action['class']), TRUE) || method_exists($action['class'], 'buildConfigurationForm')); } diff --git a/sites/all/modules/contrib/views/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml b/sites/all/modules/contrib/views/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml index b990f7966..a8b750f94 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml +++ b/sites/all/modules/contrib/views/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml @@ -7,8 +7,8 @@ dependencies: - drupal:views_bulk_operations - drupal:node -# Information added by Drupal.org packaging script on 2018-07-02 -version: '8.x-2.4' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-2.5' core: '8.x' project: 'views_bulk_operations' -datestamp: 1530516826 +datestamp: 1550740388 diff --git a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info.yml b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info.yml index f78de2e72..06015357a 100644 --- a/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info.yml +++ b/sites/all/modules/contrib/views/views_bulk_operations/views_bulk_operations.info.yml @@ -6,8 +6,8 @@ package: 'Views' dependencies: - drupal:views (>=8.4) -# Information added by Drupal.org packaging script on 2018-07-02 -version: '8.x-2.4' +# Information added by Drupal.org packaging script on 2019-02-21 +version: '8.x-2.5' core: '8.x' project: 'views_bulk_operations' -datestamp: 1530516826 +datestamp: 1550740388 diff --git a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.info.yml b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.info.yml index 6ec1d58aa..db3dce3b2 100644 --- a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.info.yml +++ b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.info.yml @@ -6,6 +6,13 @@ base theme: seven libraries: - adminimal_theme/global-styling +libraries-override: + system/base: + css: + component: + /core/themes/stable/css/system/components/system-status-counter.css: /core/themes/seven/css/components/system-status-counter.css + /core/themes/stable/css/system/components/system-status-report-counters.css: /core/themes/seven/css/components/system-status-report-counters.css + /core/themes/stable/css/system/components/system-status-report-general-info.css: /core/themes/seven/css/components/system-status-report-general-info.css regions: header: 'Header' @@ -20,8 +27,8 @@ regions: regions_hidden: - sidebar_first -# Information added by Drupal.org packaging script on 2016-11-30 -version: '8.x-1.3' +# Information added by Drupal.org packaging script on 2019-01-20 +version: '8.x-1.4' core: '8.x' project: 'adminimal_theme' -datestamp: 1480507087 +datestamp: 1547996584 diff --git a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.libraries.yml b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.libraries.yml index 5e6824866..4f38fbcda 100644 --- a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.libraries.yml +++ b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.libraries.yml @@ -3,3 +3,9 @@ global-styling: css: base: css/adminimal.css: {} + +custom-styling: + version: VERSION + css: + theme: + public://adminimal-custom.css: {} diff --git a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.theme b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.theme index 089ce6fbb..ee7f3b1e1 100644 --- a/sites/all/themes/contrib/adminimal_theme/adminimal_theme.theme +++ b/sites/all/themes/contrib/adminimal_theme/adminimal_theme.theme @@ -5,15 +5,102 @@ * Functions to support theming in the Adminimal theme. */ -use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; /** * Implements hook_preprocess_HOOK() for HTML document templates. */ function adminimal_theme_preprocess_html(&$variables) { - // Add adminimal class to the body. $variables['attributes']['class'][] = 'adminimal'; + // Add library with custom CSS. + if (theme_get_setting('custom_css')) { + $variables['#attached']['library'][] = 'adminimal_theme/custom-styling'; + } +} + +/** + * Implements hook_form_system_theme_settings_alter(). + */ +function adminimal_theme_form_system_theme_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id = NULL) { + // Work-around for a core bug affecting admin themes. See issue #943212. + if (isset($form_id)) { + return; + } + + // Get adminimal theme path. + global $base_url; + $adminimal_path = drupal_get_path('theme', 'adminimal_theme'); + $old_css_path = $adminimal_path . '/css/custom.css'; + $custom_css_path = 'public://adminimal-custom.css'; + $custom_css_dir = str_replace($base_url . '/', "", file_create_url($custom_css_path)); + $custom_css_url = file_create_url($custom_css_path); + + // Try to create the adminimal-custom.css file automatically. + if (!file_exists($custom_css_path)) { + + // Try to migrate from the old css. + if (file_exists($old_css_path)) { + file_unmanaged_copy($old_css_path, $custom_css_path, FILE_EXISTS_ERROR); + } + // Else create a new blank css file. + else { + file_unmanaged_save_data("", $custom_css_path, FILE_EXISTS_ERROR); + } + + } + + // Notify user to remove his old css file. + if (file_exists($old_css_path)) { + drupal_set_message(t('Please delete the old @css_location file, as its no longer used.', ['@css_location file' => $old_css_path]), 'warning'); + } + + $form['adminimal_custom'] = [ + '#type' => 'fieldset', + '#title' => t('Adminimal Customization'), + '#weight' => -10, + ]; + + $form['adminimal_custom']['custom_css'] = [ + '#type' => 'checkbox', + '#title' => t('Use "adminimal-custom.css"'), + '#description' => t('Include adminimal-custom.css file to override or add custom css code without subthememing/hacking Adminimal Theme.'), + '#default_value' => theme_get_setting('custom_css'), + ]; + + $form['adminimal_custom']['adminimal_custom_check'] = [ + '#type' => 'fieldset', + '#title' => t('Custom CSS file check'), + '#weight' => 50, + '#states' => [ + // Hide the settings when the cancel notify checkbox is disabled. + 'visible' => [ + ':input[name="custom_css"]' => ['checked' => TRUE], + ], + ], + ]; + + if (file_exists($custom_css_path)) { + $form['adminimal_custom']['adminimal_custom_check']['custom_css_description'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['messages', 'messages--status'], + ], + 'message' => [ + '#markup' => t('Custom CSS file Found in: @css', ['@css' => $custom_css_dir]), + ], + ]; + } + else { + $form['adminimal_custom']['adminimal_custom_check']['custom_css_not_found'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['messages', 'messages--error'], + ], + 'message' => [ + '#markup' => t('Custom CSS file not found. You must create the @css file manually.', ['@css' => $custom_css_dir]), + ], + ]; + } } diff --git a/sites/all/themes/contrib/adminimal_theme/composer.json b/sites/all/themes/contrib/adminimal_theme/composer.json new file mode 100644 index 000000000..e5c2b6515 --- /dev/null +++ b/sites/all/themes/contrib/adminimal_theme/composer.json @@ -0,0 +1,10 @@ +{ + "name": "drupal/adminimal_theme", + "description": "Drupal administration theme with modern minimalist design.", + "type": "drupal-theme", + "homepage": "https://www.drupal.org/project/adminimal_theme", + "support": { + "issues": "https://www.drupal.org/project/issues/adminimal_theme" + }, + "license": "GPL-2.0+" +} diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_breadcrumbs.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_breadcrumbs.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_breadcrumbs.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_breadcrumbs.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_content.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_content.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_content.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_content.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_local_actions.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_local_actions.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_local_actions.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_local_actions.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_login.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_login.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_login.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_login.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_messages.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_messages.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_messages.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_messages.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_page_title.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_page_title.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_page_title.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_page_title.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_primary_local_tasks.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_primary_local_tasks.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_primary_local_tasks.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_primary_local_tasks.yml diff --git a/sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_secondary_local_tasks.yml b/sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_secondary_local_tasks.yml similarity index 100% rename from sites/all/themes/contrib/adminimal_theme/config/install/block.block.adminimal_theme_secondary_local_tasks.yml rename to sites/all/themes/contrib/adminimal_theme/config/optional/block.block.adminimal_theme_secondary_local_tasks.yml diff --git a/sites/all/themes/contrib/adminimal_theme/css/adminimal.css b/sites/all/themes/contrib/adminimal_theme/css/adminimal.css index ff8147841..c514df1f2 100644 --- a/sites/all/themes/contrib/adminimal_theme/css/adminimal.css +++ b/sites/all/themes/contrib/adminimal_theme/css/adminimal.css @@ -35,12 +35,11 @@ body.adminimal { .adminimal h1, .adminimal .heading-a { - font-weight: 300; - margin-bottom: 19px; - margin-bottom: 1rem; - font-size: 2.35em; - line-height: 1.2em; color: #fff; + font-size: 2.35em; + font-weight: 300; + line-height: 1.2em; + margin-bottom: 1rem; } .adminimal .content-header { @@ -48,24 +47,18 @@ body.adminimal { padding: 1rem 0 0; } -.adminimal.toolbar-tray-open.toolbar-fixed.toolbar-horizontal { - padding-top: 4rem !important; -} - .adminimal thead th { border: none; color: #909090; - text-transform: none; - font-weight: 500; font-size: 0.91rem; + font-weight: 500; + text-transform: none; } .adminimal thead th.is-active { background: #fff; + border: 1px solid #e6e4df; border-bottom: 1px solid #fff; - border-top: 1px solid #E6E4DF; - border-left: 1px solid #E6E4DF; - border-right: 1px solid #E6E4DF; } .adminimal th.is-active > a, @@ -83,7 +76,7 @@ body.adminimal { } .adminimal thead > tr { - border-bottom: 1px solid #E6E4DF; + border-bottom: 1px solid #e6e4df; } .adminimal .tabs__tab { @@ -92,7 +85,6 @@ body.adminimal { .adminimal .tabs.primary { margin: 0; - margin: 0; } .adminimal .tabs.primary .tabs__tab.is-active { @@ -109,7 +101,7 @@ body.adminimal { [dir="rtl"] .is-collapse-enabled .tabs__trigger { border-left: 1px solid transparent; - border-radius: 0 0 0 0; + border-radius: 0; } .adminimal .is-open .tabs__tab.is-active { @@ -176,9 +168,9 @@ body.adminimal { } .adminimal .vertical-tabs { + background: #f2f2f0; border-radius: 0; border: 1px solid #b5b5b5; - background: #f2f2f0; } .adminimal .vertical-tabs__panes { @@ -190,29 +182,37 @@ body.adminimal { } .adminimal .vertical-tabs__menu-item a { - border: 0 none; + border: none; transition: all, 0.2s; } .adminimal .vertical-tabs__menu-item a:hover, .adminimal .vertical-tabs__menu-item a:focus { - background: #D2E7F4; + background: #d2e7f4; } .adminimal .vertical-tabs__menu-item.is-selected a:hover, .adminimal .vertical-tabs__menu-item.is-selected a:focus { - background: #FCFCFA; + background: #fcfcfa; } .adminimal .vertical-tabs__menu-item.is-selected { - border: 0 none; + border: none; box-shadow: none; } -.adminimal .button { - border-radius: 0; +.adminimal .button:not(.media-library-item__remove) { background-image: none; - background-color: #F9F9F9; +} + +.adminimal .button:not(.media-library-item__remove):hover, +.adminimal .button:not(.media-library-item__remove):focus { + background-image: none; +} + +.adminimal .button { + background-color: #f9f9f9; + border-radius: 0; text-shadow: none; transition: all, 0.2s; } @@ -221,7 +221,6 @@ body.adminimal { .adminimal .button:focus { background-color: #FFFFFF; border-color: #008ee6; - background-image: none; } .adminimal .button-action:before { @@ -255,22 +254,22 @@ body.adminimal { } .adminimal .action-links .button--primary { - color: #327EBD; background: #fff; - font-size: 14px; - padding: 0.33rem 1.33rem; border: 2px solid #327ebd; - font-family: sans-serif; border-radius: 999px; + color: #327ebd; + font-family: sans-serif; + font-size: 14px; font-weight: 600; + padding: 0.4rem 1.33rem; } .adminimal .action-links .button--primary:hover, .adminimal .action-links .button--primary:focus { - color: #fff; - box-shadow: none; + background-color: #327ebd; background-image: none; - background-color: #327EBD; + box-shadow: none; + color: #fff; } .adminimal .action-links .button--primary:focus { @@ -279,11 +278,10 @@ body.adminimal { } .adminimal .button--small { + background: #fff; border-color: #cacaca; border-radius: 0.2rem; - color: #b1b1b1; font-weight: 500; - background: #fff; } .adminimal .button--danger { @@ -311,10 +309,10 @@ body.adminimal { background: url(../images/caret-down.svg) no-repeat 98% 64% #fcfcfa; } .adminimal select:focus { - border-color: #68A3CF; - outline: 2px solid #C1E0FF; - outline-offset: 0px; + border-color: #68a3cf; color: #000; + outline: 2px solid #C1E0FF; + outline-offset: 0; } } @@ -330,8 +328,8 @@ body.adminimal { .js .adminimal .dropbutton-widget .dropbutton-action input, .js .adminimal .dropbutton-wrapper .dropbutton-widget .dropbutton-toggle button { border-radius: 0; - font-weight: 500; box-shadow: none; + font-weight: 500; } .js .adminimal .dropbutton-wrapper .dropbutton-widget .dropbutton-action a:hover, @@ -345,7 +343,7 @@ body.adminimal { .js .adminimal .form-actions .dropbutton-wrapper .dropbutton-widget .dropbutton-toggle button, .adminimal .form-actions .dropbutton .secondary-action { - border-color: #FFFFFF; + border-color: #ffffff; } .js .adminimal .dropbutton-multiple .dropbutton-widget { @@ -419,7 +417,7 @@ body.adminimal { .js .adminimal .dropbutton-wrapper .dropbutton-widget .dropbutton-toggle button:hover, .js .adminimal .dropbutton-wrapper .dropbutton-widget .dropbutton-toggle button:focus { background-image:none; - background-color: #FFFFFF; + background-color: #ffffff; } .adminimal .link.tabledrag-toggle-weight { @@ -439,8 +437,8 @@ body.adminimal { } .adminimal .toolbar .toolbar-bar { + background-color: #2d2d2d; box-shadow: none; - background-color: #2D2D2D; } .adminimal .toolbar .toolbar-menu { @@ -453,7 +451,7 @@ body.adminimal { } .adminimal .toolbar-tray a { - color: #BBBBBB; + color: #bbbbbb; } .adminimal .toolbar .toolbar-bar .toolbar-tab > .toolbar-item { @@ -461,12 +459,12 @@ body.adminimal { } .adminimal .toolbar .toolbar-bar .toolbar-item { - color: #BEBEBE; + color: #bebebe; } .adminimal .toolbar .toolbar-bar .toolbar-tab > .toolbar-item:hover { + background-color: #2d2d2d; background-image: none; - background-color: #2D2D2D; color: #fff; text-decoration: none; } @@ -488,8 +486,8 @@ body.adminimal { .adminimal .toolbar .toolbar-tray .menu-item--active-trail > .toolbar-box a, .adminimal .toolbar .toolbar-tray a.is-active { color: #bbb; - text-decoration: none; font-weight: 500; + text-decoration: none; } .adminimal .toolbar .toolbar-tray-vertical .menu-item + .menu-item { @@ -543,7 +541,7 @@ body.adminimal { } .adminimal .toolbar .toolbar-tray-vertical .toolbar-menu a { - color: #BBB; + color: #bbb; } .adminimal .toolbar .toolbar-tray-vertical .toolbar-menu a:hover { @@ -558,97 +556,114 @@ body.adminimal { border: none; } -/* Style checkbox and radio buttons in Chrome + Safari + Opera */ +/* Style checkbox and radio buttons */ +input[type=checkbox], +input[type=radio], +input[type=checkbox]#edit-delete { + display: inline-block; + position: relative; + top: 3px; + margin-right: 0.25em; + padding: 0; + background: #fff; + border: 1px solid #b5b5b5; + width: 16px !important; + height: 16px !important; + -webkit-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: background 0.5s ease; + outline: none; +} + +.adminimal-theme .location .form-item input.form-checkbox { + display: inline-block; + width: 16px; +} + +input[type=checkbox]#edit-delete { + margin-bottom: 0; +} + +input[type=radio] { + border-radius: 50%; +} + +input[type=checkbox]:hover, +input[type=checkbox]:focus, +input[type=radio]:hover, +input[type=radio]:focus { + border-color: #0074bd; + cursor: pointer; +} + +input[type=checkbox]#edit-delete:hover, +input[type=checkbox]#edit-delete:focus { + border-color: #d01616; + cursor: pointer; +} + +input[type=checkbox]:checked, +input[type=checkbox]:disabled:checked, +input[type=checkbox].error:checked, +input[type=checkbox].error:focus:checked, +input[type=radio]:checked, +input[type=radio]:disabled:checked, +input[type=radio].error:checked, +input[type=radio].error:focus:checked { + background: #0074bd; + box-shadow: inset 0 0 0 3px #fff; +} + +input[type=checkbox]#edit-delete:checked, +input[type=checkbox]#edit-delete:disabled:checked { + background: #D01616; + box-shadow: inset 0 0 0 3px #fff; +} + +input[type=checkbox]:disabled, +input[type=radio]:disabled { + opacity: .5; + background: #e2e2e2; +} + +input[type=checkbox]#edit-delete:disabled { + opacity: .5; + background: #e88181; +} + +input[type=checkbox]:disabled:hover { + border-color: #b5b5b5; + cursor: default; +} + +input[type=checkbox]:disabled:hover, +input[type=radio]:disabled:hover { + border-color: #b5b5b5; + cursor: default; +} + +/* in Chrome + Safari + Opera + Mozilla */ @supports (-webkit-appearance:none) { - input[type=checkbox], - input[type=radio], - input[type=checkbox]#edit-delete { - -webkit-appearance:none; - display: inline-block; - position: relative; - top: 3px; - margin-right: 0.25em; - padding: 0; - background: #fff; - border: 1px solid #B5B5B5; - width: 16px !important; - height: 16px !important; - -webkit-transition: all 0.5s ease; - -o-transition: all 0.5s ease; - transition: background 0.5s ease; - outline: none; + input[type="checkbox"], + input[type="radio"], + input[type="checkbox"]#edit-delete { + -webkit-appearance: none; } +} - .adminimal-theme .location .form-item input.form-checkbox { - width: 16px; - display: inline-block; +/* in Mozilla */ +@supports (-moz-appearance:none) { + input[type="checkbox"], + input[type="radio"], + input[type="checkbox"]#edit-delete { + -moz-appearance:none; } - - input[type=checkbox]#edit-delete { - margin-bottom: 0; - } - - input[type=radio] { - border-radius: 50%; - } - - input[type=checkbox]:hover, - input[type=checkbox]:focus, - input[type=radio]:hover, - input[type=radio]:focus { - border-color: #0074BD; - cursor: pointer; - } - - input[type=checkbox]#edit-delete:hover, - input[type=checkbox]#edit-delete:focus { - border-color: #D01616; - cursor: pointer; - } - - input[type=checkbox]:checked, - input[type=checkbox]:disabled:checked, - input[type=radio]:checked, - input[type=radio]:disabled:checked{ - background: #0074BD; - box-shadow: inset 0px 0px 0px 3px #fff; - } - - input[type=checkbox]#edit-delete:checked, - input[type=checkbox]#edit-delete:disabled:checked { - background: #D01616; - box-shadow: inset 0px 0px 0px 3px #fff; - } - - input[type=checkbox]:disabled, - input[type=radio]:disabled { - opacity: .5; - background: #E2E2E2; - } - - input[type=checkbox]#edit-delete:disabled { - opacity: .5; - background: #E88181; - } - - input[type=checkbox]:disabled:hover { - border-color: #B5B5B5; - cursor: default; - } - - input[type=checkbox]:disabled:hover, - input[type=radio]:disabled:hover { - border-color: #B5B5B5; - cursor: default; - /*cursor: not-allowed;*/ - } - } .adminimal .dropbutton-single .dropbutton-action a { border-radius: 0 !important; background-image: none; - background: #F5F5F2; + background: #f5f5f2; } .adminimal .ui-dialog .ui-dialog-titlebar { @@ -687,7 +702,7 @@ body.adminimal { .adminimal .messages { margin: 1rem 0 1rem 0; - border-width: 1px 0px 1px 0; + border-width: 1px 0 1px 0; border-radius: 0; background-color: transparent; box-shadow: none; @@ -696,26 +711,26 @@ body.adminimal { } .adminimal .messages--error { - border-color: #E32700; + border-color: #e32700; } .adminimal .messages--warning { - border-color: #E29700; + border-color: #e29700; } .adminimal .messages--status { - border-color: #73B355; + border-color: #73b355; } .adminimal .cke_top, .adminimal .cke_bottom { - background: #F7F7F7; + background: #f7f7f7; background-image: none; box-shadow: none; } .adminimal .filter-wrapper { - background: #F7F7F7; + background: #f7f7f7; } .adminimal .cke_ltr .cke_button:first-child, @@ -767,7 +782,7 @@ body.adminimal { /* Main menu icons end. */ .adminimal.path-batch .layout-container { - padding-top: 1.3rem; + padding-top: 1.3rem; } /* Module: Views */ @@ -806,3 +821,24 @@ body.adminimal { .adminimal .views-ui-dialog .views-override { background-color: #f6f6f6; } + +.adminimal .field-multiple-table .form-item { + margin: 0.75em 0; +} + +.node-preview-container .form-type-select { + float: right; +} + +.node-preview-container label { + color: #fff; +} + +.content-header .page-title .field--name-title { + display: block; + margin-top: 1.2rem; +} + +.adminimal .media-library-item__remove.button { + border-radius: 20px; +} \ No newline at end of file