From 0455e2be7f481b3a26a2b95b9221aab4a861df8c Mon Sep 17 00:00:00 2001 From: Brian Canini <canini.16@osu.edu> Date: Mon, 29 Jun 2020 14:25:21 -0400 Subject: [PATCH] Updating drupal/field_group (3.0.0 => 3.1.0) --- composer.json | 2 +- composer.lock | 29 +- vendor/composer/installed.json | 29 +- web/modules/field_group/composer.json | 11 +- .../field_group_migrate.info.yml | 8 +- .../migrate/destination/d7/FieldGroup.php | 4 +- web/modules/field_group/field_group.info.yml | 9 +- web/modules/field_group/field_group.install | 50 +++ web/modules/field_group/field_group.module | 94 ++---- .../formatters/tabs/horizontal-tabs.css | 1 + .../formatters/tabs/horizontal-tabs.js | 10 +- web/modules/field_group/includes/field_ui.inc | 1 - .../field_group/src/Element/VerticalTabs.php | 3 +- .../src/FieldGroupFormatterBase.php | 6 +- .../src/Form/FieldGroupAddForm.php | 4 +- .../src/Form/FieldGroupDeleteForm.php | 4 +- .../field_group/src/FormatterHelper.php | 96 +++++- .../field_group_test.info.yml | 8 +- .../src/Functional/EntityDisplayTest.php | 40 +-- .../src/Functional/FieldGroupTestTrait.php | 8 +- .../HorizontalTabsLabelsTest.php | 288 ++++++++++++++++++ 21 files changed, 550 insertions(+), 155 deletions(-) create mode 100644 web/modules/field_group/tests/src/FunctionalJavascript/HorizontalTabsLabelsTest.php diff --git a/composer.json b/composer.json index aabb08e27f..76d7240f55 100644 --- a/composer.json +++ b/composer.json @@ -120,7 +120,7 @@ "drupal/entity_reference_revisions": "1.8", "drupal/externalauth": "1.1", "drupal/features": "3.8", - "drupal/field_group": "3.0", + "drupal/field_group": "3.1", "drupal/field_permissions": "1.0", "drupal/file_browser": "1.1", "drupal/focal_point": "1.4", diff --git a/composer.lock b/composer.lock index cd907bc32a..6d90d94e17 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d3787aaafd1ba3db8d0785fee363f8b", + "content-hash": "2efebe1e5a975379bafab0314e809f18", "packages": [ { "name": "alchemy/zippy", @@ -4813,29 +4813,29 @@ }, { "name": "drupal/field_group", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/field_group.git", - "reference": "8.x-3.0" + "reference": "8.x-3.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/field_group-8.x-3.0.zip", - "reference": "8.x-3.0", - "shasum": "8d87cdc4abc417aa4d411bffcaeb0a3ef1afa497" + "url": "https://ftp.drupal.org/files/projects/field_group-8.x-3.1.zip", + "reference": "8.x-3.1", + "shasum": "8a719eaea594f0ba874172831cb28da93c66b77a" }, "require": { - "drupal/core": "^8 || ^9" + "drupal/core": "^8.8 || ^9" + }, + "require-dev": { + "drupal/jquery_ui_accordion": "^1.0" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-3.x": "3.x-dev" - }, "drupal": { - "version": "8.x-3.0", - "datestamp": "1580250787", + "version": "8.x-3.1", + "datestamp": "1591772567", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4844,7 +4844,7 @@ }, "notification-url": "https://packages.drupal.org/8/downloads", "license": [ - "GPL-2.0+" + "GPL-2.0-or-later" ], "authors": [ { @@ -4871,7 +4871,8 @@ "description": "Provides the field_group module.", "homepage": "https://www.drupal.org/project/field_group", "support": { - "source": "https://git.drupalcode.org/project/field_group" + "source": "https://git.drupalcode.org/project/field_group", + "issues": "https://www.drupal.org/project/issues/field_group" } }, { diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 9d55d91d2d..6c0478828a 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -4955,30 +4955,30 @@ }, { "name": "drupal/field_group", - "version": "3.0.0", - "version_normalized": "3.0.0.0", + "version": "3.1.0", + "version_normalized": "3.1.0.0", "source": { "type": "git", "url": "https://git.drupalcode.org/project/field_group.git", - "reference": "8.x-3.0" + "reference": "8.x-3.1" }, "dist": { "type": "zip", - "url": "https://ftp.drupal.org/files/projects/field_group-8.x-3.0.zip", - "reference": "8.x-3.0", - "shasum": "8d87cdc4abc417aa4d411bffcaeb0a3ef1afa497" + "url": "https://ftp.drupal.org/files/projects/field_group-8.x-3.1.zip", + "reference": "8.x-3.1", + "shasum": "8a719eaea594f0ba874172831cb28da93c66b77a" }, "require": { - "drupal/core": "^8 || ^9" + "drupal/core": "^8.8 || ^9" + }, + "require-dev": { + "drupal/jquery_ui_accordion": "^1.0" }, "type": "drupal-module", "extra": { - "branch-alias": { - "dev-3.x": "3.x-dev" - }, "drupal": { - "version": "8.x-3.0", - "datestamp": "1580250787", + "version": "8.x-3.1", + "datestamp": "1591772567", "security-coverage": { "status": "covered", "message": "Covered by Drupal's security advisory policy" @@ -4988,7 +4988,7 @@ "installation-source": "dist", "notification-url": "https://packages.drupal.org/8/downloads", "license": [ - "GPL-2.0+" + "GPL-2.0-or-later" ], "authors": [ { @@ -5015,7 +5015,8 @@ "description": "Provides the field_group module.", "homepage": "https://www.drupal.org/project/field_group", "support": { - "source": "https://git.drupalcode.org/project/field_group" + "source": "https://git.drupalcode.org/project/field_group", + "issues": "https://www.drupal.org/project/issues/field_group" } }, { diff --git a/web/modules/field_group/composer.json b/web/modules/field_group/composer.json index ac893ea517..47b16e766f 100644 --- a/web/modules/field_group/composer.json +++ b/web/modules/field_group/composer.json @@ -2,9 +2,16 @@ "name": "drupal/field_group", "description": "Provides the field_group module.", "type": "drupal-module", - "license": "GPL-2.0+", + "license": "GPL-2.0-or-later", "minimum-stability": "dev", "require": { - "drupal/core": "^8 || ^9" + "drupal/core": "^8.8 || ^9" + }, + "require-dev": { + "drupal/jquery_ui_accordion": "^1.0" + }, + "support": { + "issues": "https://www.drupal.org/project/issues/field_group", + "source": "https://git.drupalcode.org/project/field_group" } } diff --git a/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml b/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml index f1a0fb93f4..de95c532dd 100644 --- a/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml +++ b/web/modules/field_group/contrib/field_group_migrate/field_group_migrate.info.yml @@ -2,11 +2,11 @@ name: 'Field Group Migrate' type: module description: 'Provides the ability to migrate field groups from D6/D7 to D8.' package: Migration -core: 8.x +core_version_requirement: ^8.8 || ^9 dependencies: - field_group:field_group -# Information added by Drupal.org packaging script on 2020-01-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2020-06-10 +version: '8.x-3.1' project: 'field_group' -datestamp: 1580250789 +datestamp: 1591772570 diff --git a/web/modules/field_group/contrib/field_group_migrate/src/Plugin/migrate/destination/d7/FieldGroup.php b/web/modules/field_group/contrib/field_group_migrate/src/Plugin/migrate/destination/d7/FieldGroup.php index 828715ffd8..64b6c4cd17 100644 --- a/web/modules/field_group/contrib/field_group_migrate/src/Plugin/migrate/destination/d7/FieldGroup.php +++ b/web/modules/field_group/contrib/field_group_migrate/src/Plugin/migrate/destination/d7/FieldGroup.php @@ -88,8 +88,8 @@ public function fields(MigrationInterface $migration = NULL) { * The entity display object. */ protected function getEntity($entity_type, $bundle, $mode, $type) { - $function = $type == 'entity_form_display' ? 'entity_get_form_display' : 'entity_get_display'; - return $function($entity_type, $bundle, $mode); + $function = $type == 'entity_form_display' ? 'getFormDisplay' : 'getViewDisplay'; + return \Drupal::service('entity_display.repository')->$function($entity_type, $bundle, $mode); } } diff --git a/web/modules/field_group/field_group.info.yml b/web/modules/field_group/field_group.info.yml index 42618f0d09..0e4b01a222 100644 --- a/web/modules/field_group/field_group.info.yml +++ b/web/modules/field_group/field_group.info.yml @@ -2,12 +2,11 @@ name: 'Field Group' type: module description: 'Provides the ability to group your fields on both form and display.' package : Fields -core: 8.x -core_version_requirement: ^8 || ^9 +core_version_requirement: ^8.8 || ^9 dependencies: - drupal:field -# Information added by Drupal.org packaging script on 2020-01-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2020-06-10 +version: '8.x-3.1' project: 'field_group' -datestamp: 1580250789 +datestamp: 1591772570 diff --git a/web/modules/field_group/field_group.install b/web/modules/field_group/field_group.install index 659cf67a2f..b247a47a05 100644 --- a/web/modules/field_group/field_group.install +++ b/web/modules/field_group/field_group.install @@ -5,9 +5,59 @@ * Update hooks for the Field Group module. */ +/** + * Implements hook_requirements(). + */ +function field_group_requirements($phase) { + $requirements = []; + + if ($phase == 'runtime') { + // Check jQuery UI Accordion module for D9. + if (version_compare(\Drupal::VERSION, 9) > 0) { + if (!\Drupal::moduleHandler()->moduleExists('jquery_ui_accordion')) { + $requirements['field_group_jquery_ui_accordion'] = [ + 'title' => t('Field Group'), + 'value' => t('jQuery UI Accordion not enabled'), + 'description' => t('If you want to use the Field Group accordion formatter, you will need to install the <a href=":link" target="_blank">jQuery UI Accordion</a> module.', [':link' => 'https://www.drupal.org/project/jquery_ui_accordion']), + 'severity' => REQUIREMENT_WARNING, + ]; + } + else { + $requirements['field_group_jquery_ui_accordion'] = [ + 'title' => t('Field Group'), + 'description' => t('The jQuery UI Accordion module is installed'), + 'severity' => REQUIREMENT_INFO, + ]; + } + } + } + + return $requirements; +} + /** * Removed in favor of hook_post_update script. */ function field_group_update_8301() { // @see field_group_post_update_0001(). } + +/** + * Install the 'jquery_ui_accordion' module if it exists. + */ +function field_group_update_8302() { + try { + // Enables the jQuery UI accordion module if it exists. + if (\Drupal::service('extension.list.module') + ->getName('jquery_ui_accordion')) { + \Drupal::service('module_installer') + ->install(['jquery_ui_accordion'], FALSE); + return t('The "jquery_ui_accordion" module has been installed.'); + } + } + catch (\Exception $e) { + return + t('If you want to use the Field Group accordion formatter, you will need to install the <a href=":link" target="_blank">jQuery UI Accordion</a> module.', + [':link' => 'https://www.drupal.org/project/jquery_ui_accordion']); + } +} diff --git a/web/modules/field_group/field_group.module b/web/modules/field_group/field_group.module index 2e794568cf..006ae6f6e3 100644 --- a/web/modules/field_group/field_group.module +++ b/web/modules/field_group/field_group.module @@ -16,6 +16,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\field_group\Element\VerticalTabs; +use Drupal\field_group\FormatterHelper; /** * Implements hook_help(). @@ -32,6 +33,24 @@ function field_group_help($route_name, RouteMatchInterface $route_match) { } } +/** + * Implements hook_library_info_alter(). + */ +function field_group_library_info_alter(&$libraries, $extension) { + // Swap jQuery.ui library if available. + // See https://www.drupal.org/project/field_group/issues/3109552 for more + // background on the logic. + if (version_compare(\Drupal::VERSION, 9) > 0 && $extension == 'field_group') { + if (\Drupal::moduleHandler()->moduleExists('jquery_ui_accordion')) { + $libraries['formatter.accordion']['dependencies'] = ['jquery_ui_accordion/accordion']; + } + else { + $libraries['formatter.accordion']['js'] = []; + $libraries['formatter.accordion']['dependencies'] = []; + } + } +} + /** * Implements hook_theme_registry_alter(). */ @@ -235,7 +254,7 @@ function field_group_form_alter(array &$form, FormStateInterface $form_state) { ]; field_group_attach_groups($form, $context); - $form['#process'][] = 'field_group_form_process'; + $form['#process'][] = [FormatterHelper::class, 'formProcess']; } } @@ -256,7 +275,7 @@ function field_group_inline_entity_form_entity_form_alter(&$entity_form, FormSta ]; field_group_attach_groups($entity_form, $context); - field_group_form_process($entity_form); + FormatterHelper::formProcess($entity_form, $form_state); } /** @@ -277,7 +296,7 @@ function field_group_form_layout_builder_update_block_alter(&$form, FormStateInt ]; field_group_attach_groups($form['settings']['block_form'], $context); - $form['settings']['block_form']['#process'][] = 'field_group_form_process'; + $form['settings']['block_form']['#process'][] = [FormatterHelper::class, 'formProcess']; } /** @@ -317,7 +336,7 @@ function field_group_entity_view_alter(&$build, EntityInterface $entity, EntityD // If DS is enabled, no pre render is needed (DS adds fieldgroup preprocessing). if (!$ds_enabled) { - $build['#pre_render'][] = 'field_group_entity_view_pre_render'; + $build['#pre_render'][] = [FormatterHelper::class, 'entityViewPrender']; } } } @@ -349,72 +368,7 @@ function field_group_form_pre_render(array $element) { * @return array */ function field_group_form_process(array &$element, FormStateInterface $form_state = NULL, array &$form = []) { - if (empty($element['#field_group_form_process'])) { - $element['#field_group_form_process'] = TRUE; - if (empty($element['#fieldgroups'])) { - return $element; - } - - // Create all groups and keep a flat list of references to these groups. - $group_references = []; - foreach ($element['#fieldgroups'] as $group_name => $group) { - if (!isset($element[$group_name])) { - $element[$group_name] = []; - } - - $group_parents = $element['#array_parents']; - $group_parents[] = empty($group->parent_name) ? $group->region : $group->parent_name; - $group_references[$group_name] = &$element[$group_name]; - $element[$group_name]['#group'] = implode('][', $group_parents); - - // Use array parents to set the group name. This will cover multilevel forms (eg paragraphs). - $parents = $element['#array_parents']; - $parents[] = $group_name; - $element[$group_name]['#parents'] = $parents; - $group_children_parent_group = implode('][', $parents); - foreach ($group->children as $child) { - if (!empty($element[$child]['#field_group_ignore'])) { - continue; - } - $element[$child]['#group'] = $group_children_parent_group; - } - } - - foreach ($element['#fieldgroups'] as $group_name => $group) { - $field_group_element = &$element[$group_name]; - - // Let modules define their wrapping element. - // Note that the group element has no properties, only elements. - foreach (Drupal::moduleHandler()->getImplementations('field_group_form_process') as $module) { - // The intention here is to have the opportunity to alter the - // elements, as defined in hook_field_group_formatter_info. - // Note, implement $element by reference! - $function = $module . '_field_group_form_process'; - $function($field_group_element, $group, $element); - } - - // Allow others to alter the pre_render. - Drupal::moduleHandler()->alter('field_group_form_process', $field_group_element, $group, $element); - } - - // Allow others to alter the complete processed build. - Drupal::moduleHandler()->alter('field_group_form_process_build', $element, $form_state, $form); - } - - return $element; -} - -/** - * Pre render callback for rendering groups on entities without theme hook. - * - * @param array $element - * Entity being rendered. - * - * @return array - */ -function field_group_entity_view_pre_render(array $element) { - field_group_build_entity_groups($element, 'view'); - return $element; + return FormatterHelper::formProcess($element, $form_state, $form); } /** diff --git a/web/modules/field_group/formatters/tabs/horizontal-tabs.css b/web/modules/field_group/formatters/tabs/horizontal-tabs.css index b28d5c9dea..5aa784f928 100644 --- a/web/modules/field_group/formatters/tabs/horizontal-tabs.css +++ b/web/modules/field_group/formatters/tabs/horizontal-tabs.css @@ -31,6 +31,7 @@ padding: 0 1em; border: 0; background-color: unset; + box-shadow: unset; } .horizontal-tabs-pane > summary { diff --git a/web/modules/field_group/formatters/tabs/horizontal-tabs.js b/web/modules/field_group/formatters/tabs/horizontal-tabs.js index ca0dc04ca5..865da55329 100644 --- a/web/modules/field_group/formatters/tabs/horizontal-tabs.js +++ b/web/modules/field_group/formatters/tabs/horizontal-tabs.js @@ -1,4 +1,4 @@ -(function ($) { +(function ($, Drupal) { 'use strict'; @@ -56,9 +56,9 @@ summaryElement = $this.find('> summary'); } - var summary = summaryElement.clone().children().remove().end().text(); + var summaryText = summaryElement.clone().children().remove().end().text().trim() || summaryElement.find('> span:first-child').text().trim(); var horizontal_tab = new Drupal.horizontalTab({ - title: $.trim(summary), + title: summaryText, details: $this }); horizontal_tab.item.addClass('horizontal-tab-button-' + i); @@ -178,7 +178,6 @@ // Display the tab. this.item.removeClass('horizontal-tab-hidden'); this.item.show(); - alert('show'); // Update .first marker for items. We need recurse from parent to retain the // actual DOM element order as jQuery implements sortOrder, but not as public @@ -201,7 +200,6 @@ // Hide this tab. this.item.addClass('horizontal-tab-hidden'); this.item.hide(); - alert('hide'); // Update .first marker for items. We need recurse from parent to retain the // actual DOM element order as jQuery implements sortOrder, but not as public @@ -253,4 +251,4 @@ return tab; }; -})(jQuery, Modernizr); +})(jQuery, Drupal); diff --git a/web/modules/field_group/includes/field_ui.inc b/web/modules/field_group/includes/field_ui.inc index 0a14e6b771..5c53ff1a74 100644 --- a/web/modules/field_group/includes/field_ui.inc +++ b/web/modules/field_group/includes/field_ui.inc @@ -167,7 +167,6 @@ function field_group_field_ui_display_form_alter(&$form, FormStateInterface $for $settings = field_group_format_settings_form($group, $form, $form_state); $id = strtr($name, '_', '-'); - $js_rows_data[$id] = ['type' => 'group', 'name' => $name]; // A group cannot be selected as its own parent. $parent_options = $table['#parent_options']; $region = isset($group->region) && in_array($group->region, $params->available_regions) ? $group->region : $params->default_region; diff --git a/web/modules/field_group/src/Element/VerticalTabs.php b/web/modules/field_group/src/Element/VerticalTabs.php index 598e24cf47..c8a212d209 100644 --- a/web/modules/field_group/src/Element/VerticalTabs.php +++ b/web/modules/field_group/src/Element/VerticalTabs.php @@ -4,11 +4,12 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\RenderCallbackInterface; /** * Provides extra processing and pre rendering on the vertical tabs. */ -class VerticalTabs { +class VerticalTabs implements RenderCallbackInterface { /** * Pre render the group to support #group parameter. diff --git a/web/modules/field_group/src/FieldGroupFormatterBase.php b/web/modules/field_group/src/FieldGroupFormatterBase.php index 0230b9ac3b..b3297a637a 100644 --- a/web/modules/field_group/src/FieldGroupFormatterBase.php +++ b/web/modules/field_group/src/FieldGroupFormatterBase.php @@ -90,13 +90,13 @@ public function settingsForm() { $form = []; $form['label'] = [ '#type' => 'textfield', - '#title' => t('Field group label'), + '#title' => $this->t('Field group label'), '#default_value' => $this->label, '#weight' => -5, ]; $form['id'] = [ - '#title' => t('ID'), + '#title' => $this->t('ID'), '#type' => 'textfield', '#default_value' => $this->getSetting('id'), '#weight' => 10, @@ -104,7 +104,7 @@ public function settingsForm() { ]; $form['classes'] = [ - '#title' => t('Extra CSS classes'), + '#title' => $this->t('Extra CSS classes'), '#type' => 'textfield', '#default_value' => $this->getSetting('classes'), '#weight' => 11, diff --git a/web/modules/field_group/src/Form/FieldGroupAddForm.php b/web/modules/field_group/src/Form/FieldGroupAddForm.php index a69b5904be..7b89fb3aba 100644 --- a/web/modules/field_group/src/Form/FieldGroupAddForm.php +++ b/web/modules/field_group/src/Form/FieldGroupAddForm.php @@ -212,7 +212,7 @@ public function buildConfigurationForm(array &$form, FormStateInterface $form_st $group->bundle = $this->bundle; $group->mode = $this->mode; - $manager = \Drupal::service('plugin.manager.field_group.formatters'); + $manager = $this->fieldGroupFormatterPluginManager; $plugin = $manager->getInstance([ 'format_type' => $form_state->getValue('group_formatter'), 'configuration' => [ @@ -285,7 +285,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Store new group information for any additional submit handlers. $groups_added = $form_state->get('groups_added'); $groups_added['_add_new_group'] = $new_group->group_name; - $this->messenger->addMessage(t('New group %label successfully created.', ['%label' => $new_group->label])); + $this->messenger->addMessage($this->t('New group %label successfully created.', ['%label' => $new_group->label])); $form_state->setRedirectUrl(FieldgroupUi::getFieldUiRoute($new_group)); \Drupal::cache()->invalidate('field_groups'); diff --git a/web/modules/field_group/src/Form/FieldGroupDeleteForm.php b/web/modules/field_group/src/Form/FieldGroupDeleteForm.php index fafb76f1f1..6814525f96 100644 --- a/web/modules/field_group/src/Form/FieldGroupDeleteForm.php +++ b/web/modules/field_group/src/Form/FieldGroupDeleteForm.php @@ -82,7 +82,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { field_group_delete_field_group($this->fieldGroup); - $this->messenger->addMessage(t('The group %group has been deleted from the %type content type.', ['%group' => t($this->fieldGroup->label), '%type' => $bundle_label])); + $this->messenger->addMessage($this->t('The group %group has been deleted from the %type content type.', ['%group' => $this->fieldGroup->label, '%type' => $bundle_label])); // Redirect. $form_state->setRedirectUrl($this->getCancelUrl()); @@ -93,7 +93,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { * {@inheritdoc} */ public function getQuestion() { - return $this->t('Are you sure you want to delete the group %group?', ['%group' => t($this->fieldGroup->label)]); + return $this->t('Are you sure you want to delete the group %group?', ['%group' => $this->fieldGroup->label]); } /** diff --git a/web/modules/field_group/src/FormatterHelper.php b/web/modules/field_group/src/FormatterHelper.php index 436d7d71ca..4b84072158 100644 --- a/web/modules/field_group/src/FormatterHelper.php +++ b/web/modules/field_group/src/FormatterHelper.php @@ -3,11 +3,13 @@ namespace Drupal\field_group; use Drupal; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Security\TrustedCallbackInterface; /** * Static methods for fieldgroup formatters. */ -class FormatterHelper { +class FormatterHelper implements TrustedCallbackInterface { /** * Return an array of field_group_formatter options. @@ -18,7 +20,7 @@ public static function formatterOptions($type) { if (!isset($options)) { $options = []; - $manager = Drupal::service('plugin.manager.field_group.formatters'); + $manager = \Drupal::service('plugin.manager.field_group.formatters'); $formatters = $manager->getDefinitions(); foreach ($formatters as $formatter) { @@ -31,4 +33,94 @@ public static function formatterOptions($type) { return $options; } + /** + * Pre render callback for rendering groups on entities without theme hook. + * + * @param array $element + * Entity being rendered. + * + * @return array + */ + public static function entityViewPrender(array $element) { + field_group_build_entity_groups($element, 'view'); + return $element; + } + + /** + * Process callback for field groups. + * + * @param array $element + * Form that is being processed. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param array $form + * The complete form structure. + * + * @return array + */ + public static function formProcess(array &$element, FormStateInterface $form_state = NULL, array &$form = []) { + if (empty($element['#field_group_form_process'])) { + $element['#field_group_form_process'] = TRUE; + if (empty($element['#fieldgroups'])) { + return $element; + } + + // Create all groups and keep a flat list of references to these groups. + $group_references = []; + foreach ($element['#fieldgroups'] as $group_name => $group) { + if (!isset($element[$group_name])) { + $element[$group_name] = []; + } + + $group_parents = $element['#array_parents']; + $group_parents[] = empty($group->parent_name) ? $group->region : $group->parent_name; + $group_references[$group_name] = &$element[$group_name]; + $element[$group_name]['#group'] = implode('][', $group_parents); + + // Use array parents to set the group name. This will cover multilevel forms (eg paragraphs). + $parents = $element['#array_parents']; + $parents[] = $group_name; + $element[$group_name]['#parents'] = $parents; + $group_children_parent_group = implode('][', $parents); + foreach ($group->children as $child) { + if (!empty($element[$child]['#field_group_ignore'])) { + continue; + } + $element[$child]['#group'] = $group_children_parent_group; + } + } + + foreach ($element['#fieldgroups'] as $group_name => $group) { + $field_group_element = &$element[$group_name]; + + // Let modules define their wrapping element. + // Note that the group element has no properties, only elements. + foreach (Drupal::moduleHandler()->getImplementations('field_group_form_process') as $module) { + // The intention here is to have the opportunity to alter the + // elements, as defined in hook_field_group_formatter_info. + // Note, implement $element by reference! + $function = $module . '_field_group_form_process'; + $function($field_group_element, $group, $element); + } + + // Allow others to alter the pre_render. + Drupal::moduleHandler()->alter('field_group_form_process', $field_group_element, $group, $element); + } + + // Allow others to alter the complete processed build. + Drupal::moduleHandler()->alter('field_group_form_process_build', $element, $form_state, $form); + } + + return $element; + } + + + /** + * {@inheritdoc} + */ + public static function trustedCallbacks() { + return ['entityViewPrender', 'formProcess']; + } + + } diff --git a/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml b/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml index 279040ed8e..b5e79b5971 100644 --- a/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml +++ b/web/modules/field_group/tests/modules/field_group_test/field_group_test.info.yml @@ -1,11 +1,11 @@ name: 'Field Group Test' description: 'Test module for Field Group' -core: 8.x +core_version_requirement: ^8.8 || ^9 package: 'Fields' type: module hidden: TRUE -# Information added by Drupal.org packaging script on 2020-01-28 -version: '8.x-3.0' +# Information added by Drupal.org packaging script on 2020-06-10 +version: '8.x-3.1' project: 'field_group' -datestamp: 1580250789 +datestamp: 1591772570 diff --git a/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php b/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php index 7eda8fa638..5f0060ab37 100644 --- a/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php +++ b/web/modules/field_group/tests/src/Functional/EntityDisplayTest.php @@ -23,7 +23,7 @@ class EntityDisplayTest extends BrowserTestBase { 'field_test', 'field_ui', 'field_group', - 'field_group_test', + 'field_group_test' ]; /** @@ -157,8 +157,8 @@ public function testHtmlElement() { $this->drupalGet('node/' . $this->node->id()); // Test group ids and classes. - $this->assertTrue($this->xpath("//div[contains(@id, 'wrapper-id')]"), 'Wrapper id set on wrapper div'); - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class')]"), 'Test class set on wrapper div, class="' . $group->group_name . ' test-class'); + $this->assertCount(1, $this->xpath("//div[contains(@id, 'wrapper-id')]"), 'Wrapper id set on wrapper div'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class')]"), 'Test class set on wrapper div, class="' . $group->group_name . ' test-class'); // Test group label. $this->assertSession()->responseNotContains('<h3><span>' . $data['label'] . '</span></h3>'); @@ -178,8 +178,8 @@ public function testHtmlElement() { field_group_group_save($group); $this->drupalGet('node/' . $this->node->id()); - $this->assertTrue($this->xpath("//div[contains(@class, 'speed-fast')]"), 'Speed class is set'); - $this->assertTrue($this->xpath("//div[contains(@class, 'effect-blink')]"), 'Effect class is set'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'speed-fast')]"), 'Speed class is set'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'effect-blink')]"), 'Effect class is set'); } /** @@ -205,8 +205,8 @@ public function testFieldset() { $this->drupalGet('node/' . $this->node->id()); // Test group ids and classes. - $this->assertTrue($this->xpath("//fieldset[contains(@id, 'fieldset-id')]"), 'Correct id set on the fieldset'); - $this->assertTrue($this->xpath("//fieldset[contains(@class, 'test-class')]"), 'Test class set on the fieldset'); + $this->assertCount(1, $this->xpath("//fieldset[contains(@id, 'fieldset-id')]"), 'Correct id set on the fieldset'); + $this->assertCount(1, $this->xpath("//fieldset[contains(@class, 'test-class')]"), 'Test class set on the fieldset'); } /** @@ -264,16 +264,16 @@ public function testTabs() { $this->drupalGet('node/' . $this->node->id()); // Test properties. - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]"), 'Test class set on tabs wrapper'); - $this->assertTrue($this->xpath("//details[contains(@class, 'test-class-2')]"), 'Test class set on second tab'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class-wrapper')]"), 'Test class set on tabs wrapper'); + $this->assertCount(1, $this->xpath("//details[contains(@class, 'test-class-2')]"), 'Test class set on second tab'); $this->assertSession()->responseContains('<div class="details-description">description of second tab</div>'); // Test if correctly nested. - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class')]"), 'First tab is displayed as child of the wrapper.'); - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class-2')]"), 'Second tab is displayed as child of the wrapper.'); + $this->assertCount(2, $this->xpath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class')]"), 'First tab is displayed as child of the wrapper.'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class-wrapper')]//details[contains(@class, 'test-class-2')]"), 'Second tab is displayed as child of the wrapper.'); // Test if it's a vertical tab. - $this->assertTrue($this->xpath('//div[@data-vertical-tabs-panes=""]'), 'Tabs are shown vertical.'); + $this->assertCount(1, $this->xpath('//div[@data-vertical-tabs-panes=""]'), 'Tabs are shown vertical.'); // Switch to horizontal. $tabs_group->format_settings['direction'] = 'horizontal'; @@ -282,7 +282,7 @@ public function testTabs() { $this->drupalGet('node/' . $this->node->id()); // Test if it's a horizontal tab. - $this->assertTrue($this->xpath('//div[@data-horizontal-tabs-panes=""]'), 'Tabs are shown horizontal.'); + $this->assertCount(1, $this->xpath('//div[@data-horizontal-tabs-panes=""]'), 'Tabs are shown horizontal.'); } /** @@ -338,15 +338,15 @@ public function testAccordion() { $this->drupalGet('node/' . $this->node->id()); // Test properties. - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]"), 'Test class set on tabs wrapper'); - $this->assertTrue($this->xpath("//div[contains(@class, 'effect-bounceslide')]"), 'Correct effect is set on the accordion'); - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class')]"), 'Accordion item with test-class is shown'); - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-2')]"), 'Accordion item with test-class-2 is shown'); - $this->assertTrue($this->xpath("//h3[contains(@class, 'field-group-accordion-active')]"), 'Accordion item 2 was set active'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class-wrapper')]"), 'Test class set on tabs wrapper'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'effect-bounceslide')]"), 'Correct effect is set on the accordion'); + $this->assertCount(3, $this->xpath("//div[contains(@class, 'test-class')]"), 'Accordion item with test-class is shown'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class-2')]"), 'Accordion item with test-class-2 is shown'); + $this->assertCount(1, $this->xpath("//h3[contains(@class, 'field-group-accordion-active')]"), 'Accordion item 2 was set active'); // Test if correctly nested. - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]"), 'First item is displayed as child of the wrapper.'); - $this->assertTrue($this->xpath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class-2')]"), 'Second item is displayed as child of the wrapper.'); + $this->assertCount(2, $this->xpath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]"), 'First item is displayed as child of the wrapper.'); + $this->assertCount(1, $this->xpath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class-2')]"), 'Second item is displayed as child of the wrapper.'); } } diff --git a/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php b/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php index 563ec92cd1..a4c2d77765 100644 --- a/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php +++ b/web/modules/field_group/tests/src/Functional/FieldGroupTestTrait.php @@ -23,7 +23,7 @@ trait FieldGroupTestTrait { * @param array $data * Data for the field group. * - * @return \stdClass + * @return object * An object that represents the field group. */ protected function createGroup($entity_type, $bundle, $context, $mode, array $data) { @@ -34,7 +34,11 @@ protected function createGroup($entity_type, $bundle, $context, $mode, array $da $data['format_settings'] += Drupal::service('plugin.manager.field_group.formatters')->getDefaultSettings($data['format_type'], $context); - $group_name = 'group_' . mb_strtolower($this->randomMachineName()); + $group_name_without_prefix = isset($data['group_name']) && is_string($data['group_name']) + ? preg_replace('/^group_/', '', $data['group_name']) + : mb_strtolower($this->randomMachineName()); + + $group_name = 'group_' . $group_name_without_prefix; $field_group = (object) [ 'group_name' => $group_name, diff --git a/web/modules/field_group/tests/src/FunctionalJavascript/HorizontalTabsLabelsTest.php b/web/modules/field_group/tests/src/FunctionalJavascript/HorizontalTabsLabelsTest.php new file mode 100644 index 0000000000..8c6701fc17 --- /dev/null +++ b/web/modules/field_group/tests/src/FunctionalJavascript/HorizontalTabsLabelsTest.php @@ -0,0 +1,288 @@ +<?php + +namespace Drupal\Tests\field_group\FunctionalJavascript; + +use Drupal\Core\Extension\Exception\UnknownExtensionException; +use Drupal\Core\Extension\ThemeInstallerInterface; +use Drupal\Core\Url; +use Drupal\field\FieldStorageConfigInterface; +use Drupal\FunctionalJavascriptTests\WebDriverTestBase; +use Drupal\Tests\field_group\Functional\FieldGroupTestTrait; + +/** + * Tests horizontal tabs labels. + * + * @group field_group + */ +class HorizontalTabsLabelsTest extends WebDriverTestBase { + + use FieldGroupTestTrait; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = [ + 'block', + 'field_group', + 'node', + 'user', + ]; + + /** + * The themes to test with. + * + * @var string[] + */ + protected $themeList = [ + 'bartik', + 'claro', + 'classy', + 'seven', + 'stable', + 'stable9', + 'stark', + ]; + + /** + * The themes that are shipped with block configurations. + * + * @var string[] + */ + protected $themesWithBlocks = [ + 'claro', + ]; + + /** + * The webassert session. + * + * @var \Drupal\Tests\WebAssert + */ + protected $assertSession; + + /** + * The page element. + * + * @var \Behat\Mink\Element\DocumentElement + */ + protected $page; + + /** + * The node type used for testing. + * + * @var \Drupal\node\NodeTypeInterface + */ + protected $testNodeType; + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + + $this->assertSession = $this->assertSession(); + $this->page = $this->getSession()->getPage(); + $this->testNodeType = $this->drupalCreateContentType([ + 'type' => 'test_node_bundle', + 'name' => 'Test Node Type', + ]); + + // Add an extra field to the test content type. + $entity_type_manager = $this->container->get('entity_type.manager'); + $field_storage = $entity_type_manager + ->getStorage('field_storage_config') + ->create([ + 'type' => 'string', + 'field_name' => 'test_label', + 'entity_type' => 'node', + ]); + assert($field_storage instanceof FieldStorageConfigInterface); + $field_storage->save(); + + $entity_type_manager->getStorage('field_config') + ->create([ + 'label' => 'Test label', + 'field_storage' => $field_storage, + 'bundle' => $this->testNodeType->id(), + ]) + ->save(); + + $tab1 = [ + 'label' => 'Tab1', + 'group_name' => 'group_tab1', + 'weight' => '1', + 'children' => [ + 0 => 'test_label', + ], + 'format_type' => 'tab', + 'format_settings' => [ + 'label' => 'Tab1', + 'formatter' => 'open', + ], + ]; + $this->createGroup('node', $this->testNodeType->id(), 'form', 'default', $tab1); + $this->createGroup('node', $this->testNodeType->id(), 'view', 'default', $tab1); + + $tab2 = [ + 'label' => 'Tab2', + 'group_name' => 'group_tab2', + 'weight' => '2', + 'children' => [ + 0 => 'body', + ], + 'format_type' => 'tab', + 'format_settings' => [ + 'label' => 'Tab2', + 'formatter' => 'closed', + ], + ]; + $this->createGroup('node', $this->testNodeType->id(), 'form', 'default', $tab2); + $this->createGroup('node', $this->testNodeType->id(), 'view', 'default', $tab2); + + $horizontal_tabs = [ + 'label' => 'Horizontal tabs', + 'group_name' => 'group_horizontal_tabs', + 'weight' => '-5', + 'children' => [ + 'group_tab1', + 'group_tab2', + ], + 'format_type' => 'tabs', + 'format_settings' => [ + 'direction' => 'horizontal', + 'label' => 'Horizontal tabs', + ], + ]; + $this->createGroup('node', $this->testNodeType->id(), 'form', 'default', $horizontal_tabs); + $this->createGroup('node', $this->testNodeType->id(), 'view', 'default', $horizontal_tabs); + + $entity_type_manager->getStorage('entity_form_display') + ->load(implode('.', [ + 'node', + $this->testNodeType->id(), + 'default', + ])) + ->setComponent('test_label', ['weight' => '1']) + ->save(); + + $entity_type_manager->getStorage('entity_view_display') + ->load(implode('.', [ + 'node', + $this->testNodeType->id(), + 'default', + ])) + ->setComponent('test_label', ['weight' => '1']) + ->save(); + } + + /** + * Tests horizontal tabs labels. + * + * @dataProvider providerTestHorizontalTabsLabels + */ + public function testHorizontalTabsLabels(string $theme_name) { + if ($theme_name !== $this->defaultTheme) { + $theme_installer = \Drupal::service('theme_installer'); + assert($theme_installer instanceof ThemeInstallerInterface); + try { + $theme_installer->install([$theme_name], TRUE); + } + catch (UnknownExtensionException $ex) { + // Themes might be missing, e.g Drupal 8.x does not have stable9 theme. + $this->pass("The $theme_name theme does not exist in the current test environment."); + return; + } + \Drupal::configFactory() + ->getEditable('system.theme') + ->set('default', $theme_name) + ->set('admin', $theme_name) + ->save(); + } + + if (!in_array($theme_name, $this->themesWithBlocks, TRUE)) { + $this->drupalPlaceBlock('page_title_block', [ + 'region' => 'content', + ]); + $this->drupalPlaceBlock('local_tasks_block', [ + 'region' => 'content', + 'weight' => 1, + ]); + $this->drupalPlaceBlock('local_actions_block', [ + 'region' => 'content', + 'weight' => 2, + ]); + $this->drupalPlaceBlock('system_main_block', [ + 'region' => 'content', + 'weight' => 3, + ]); + } + + $this->drupalLogin($this->rootUser); + + // Actual test: check the node edit page. Tab1 and Tab2 should be present. + $this->drupalGet(Url::fromRoute('node.add', [ + 'node_type' => $this->testNodeType->id(), + ])); + $this->assertHorizontalTabsLabels(); + + // Create a node. + $this->page->fillField('title[0][value]', 'Field Group Horizontal Tabs Test Node'); + $this->page->fillField('Test label', 'Test label'); + $this->assertNotNull($tab2 = $this->page->find('css', '.js .field-group-tabs-wrapper a[href="#edit-group-tab2"]')); + $tab2->click(); + $this->assertSession->waitForElementVisible('css', '[name="body[0][value]"]'); + $this->page->fillField('body[0][value]', 'Donec laoreet imperdiet.'); + $this->page->findButton('edit-submit')->click(); + $this->assertSession->waitForElement('css', 'html.js [data-drupal-messages]'); + $status_message = $this->page->find('css', 'html.js [data-drupal-messages]'); + $this->assertStringContainsString("{$this->testNodeType->label()} Field Group Horizontal Tabs Test Node has been created.", $status_message->getText()); + + // Check the node. + $this->drupalGet(Url::fromRoute('entity.node.canonical', [ + 'node' => '1', + ])); + $this->assertHorizontalTabsLabels(); + + $this->drupalLogout(); + + // Retest the node with anonymous user. + $this->drupalGet(Url::fromRoute('entity.node.canonical', [ + 'node' => '1', + ])); + $this->assertHorizontalTabsLabels(); + } + + /** + * Asserts the horizontal tabs labels. + */ + protected function assertHorizontalTabsLabels() { + $this->assertSession->waitForElement('css', '.js .field-group-tabs-wrapper a[href="#edit-group-tab1"]'); + $this->assertSession->waitForElement('css', '.js .field-group-tabs-wrapper a[href="#edit-group-tab2"]'); + $this->assertNotNull($tab1 = $this->page->find('css', '.js .field-group-tabs-wrapper a[href="#edit-group-tab1"]')); + $this->assertStringContainsString('Tab1', $tab1->getText()); + $this->assertNotNull($tab2 = $this->page->find('css', '.js .field-group-tabs-wrapper a[href="#edit-group-tab2"]')); + $this->assertStringContainsString('Tab2', $tab2->getText()); + } + + /** + * Data provider for testHorizontalTabsLabels. + * + * @return string[][][] + * The test cases with the theme machine names. + */ + public function providerTestHorizontalTabsLabels() { + return array_reduce($this->themeList, function (array $carry, string $theme_name) { + $carry[$theme_name] = [ + 'theme_name' => $theme_name, + ]; + return $carry; + }, []); + } + +} -- GitLab