diff --git a/composer.json b/composer.json index eddd3079b..1113763dc 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,7 @@ "drupal/pantheon_advanced_page_cache": "2.3.3", "drupal/paragraphs": "^1.17", "drupal/paragraphs_edit": "^3.0", + "drupal/paragraphs_modal_add": "^1.0", "drupal/password_policy": "^4.0", "drupal/pathauto": "^1.12", "drupal/permissions_filter": "^1.3", diff --git a/composer.lock b/composer.lock index 409147d34..90f600c62 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": "a1033c9e48108cc6b070b89c38e03ad9", + "content-hash": "8fbe68cb83c75e554eb61e72ff3aa861", "packages": [ { "name": "asm89/stack-cors", @@ -4755,6 +4755,51 @@ "source": "https://git.drupalcode.org/project/paragraphs_edit" } }, + { + "name": "drupal/paragraphs_modal_add", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://git.drupalcode.org/project/paragraphs_modal_add.git", + "reference": "1.0.0" + }, + "dist": { + "type": "zip", + "url": "https://ftp.drupal.org/files/projects/paragraphs_modal_add-1.0.0.zip", + "reference": "1.0.0", + "shasum": "7f4819c4168a85fae204956691afd9526acced8b" + }, + "require": { + "drupal/core": "^10 | ^11", + "drupal/paragraphs": "*" + }, + "type": "drupal-module", + "extra": { + "drupal": { + "version": "1.0.0", + "datestamp": "1744336309", + "security-coverage": { + "status": "not-covered", + "message": "Project has not opted into security advisory coverage!" + } + } + }, + "notification-url": "https://packages.drupal.org/8/downloads", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "ruby232", + "homepage": "https://www.drupal.org/user/2604126" + } + ], + "description": "Allows users to add paragraphs.", + "homepage": "https://www.drupal.org/project/paragraphs_modal_add", + "support": { + "source": "https://git.drupalcode.org/project/paragraphs_modal_add" + } + }, { "name": "drupal/password_policy", "version": "4.0.3", diff --git a/config/sync/core.entity_form_display.node.landing_page.default.yml b/config/sync/core.entity_form_display.node.landing_page.default.yml index 443f61432..cfb915a35 100644 --- a/config/sync/core.entity_form_display.node.landing_page.default.yml +++ b/config/sync/core.entity_form_display.node.landing_page.default.yml @@ -11,7 +11,7 @@ dependencies: module: - content_moderation - metatag - - paragraphs + - paragraphs_simple_edit - path id: node.landing_page.default targetEntityType: node @@ -40,7 +40,7 @@ content: use_details: true third_party_settings: { } field_paragraphs: - type: paragraphs + type: paragraphs_simple_edit_default weight: 10 region: content settings: @@ -49,14 +49,15 @@ content: edit_mode: closed closed_mode: summary autocollapse: none - closed_mode_threshold: 0 + closed_mode_threshold: '0' add_mode: dropdown form_display_mode: default default_paragraph_type: _none features: - add_above: '0' - collapse_edit_all: collapse_edit_all duplicate: duplicate + collapse_edit_all: collapse_edit_all + add_above: 0 + convert: 0 third_party_settings: { } langcode: type: language_select diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index c1968f6f3..85699f84b 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -69,6 +69,8 @@ module: page_cache: 0 pantheon_advanced_page_cache: 0 paragraphs_edit: 0 + paragraphs_modal_add: 0 + paragraphs_simple_edit: 0 password_policy_blacklist: 0 password_policy_character_types: 0 password_policy_length: 0 diff --git a/web/modules/custom/paragraphs_simple_edit/config/schema/paragraphs_simple_edit.schema.yml b/web/modules/custom/paragraphs_simple_edit/config/schema/paragraphs_simple_edit.schema.yml new file mode 100755 index 000000000..643cae82f --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/config/schema/paragraphs_simple_edit.schema.yml @@ -0,0 +1,36 @@ +field.widget.settings.paragraphs_simple_edit_default: + type: mapping + label: 'Paragraphs simple edit widget settings' + mapping: + title: + type: string + label: 'Title' + title_plural: + type: string + label: 'Title plural' + edit_mode: + type: string + label: 'Edit mode' + closed_mode: + type: string + label: 'Closed mode' + autocollapse: + type: string + label: 'Autocollapse' + closed_mode_threshold: + type: integer + label: 'Closed mode threshold' + add_mode: + type: string + label: 'Add mode' + form_display_mode: + type: string + label: 'Form display mode' + default_paragraph_type: + type: string + label: 'Default paragraph type' + features: + type: sequence + label: 'Features' + sequence: + type: string diff --git a/web/modules/custom/paragraphs_simple_edit/css/paragraphs_simple_edit.claro.css b/web/modules/custom/paragraphs_simple_edit/css/paragraphs_simple_edit.claro.css new file mode 100755 index 000000000..b5ea4c75a --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/css/paragraphs_simple_edit.claro.css @@ -0,0 +1,14 @@ +/* Claro theme automatically adds the .button--small class, which introduces + extra margin and padding. These overrides remove the unwanted spacing + applied to the link wrapper. */ +.paragraph-simple-edit--add-button.dropbutton.button--small { + margin: 0; + padding: 0; +} + +/* Claro theme applies certain admin styles (e.g., accordion, tabs) based on + class names. These overrides ensure paragraph widgets display correctly + regardless of their type. */ +.paragraph-simple-edit--add-button li { + box-shadow: none; +} diff --git a/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.info.yml b/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.info.yml new file mode 100755 index 000000000..adcce6789 --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.info.yml @@ -0,0 +1,8 @@ +name: 'Paragraphs Simple Edit' +type: module +description: 'Provides a paragraphs field widget that allows editing, adding, and deleting paragraphs on dedicated pages.' +package: Paragraphs +core_version_requirement: ^10 || ^11 +dependencies: + - paragraphs_edit:paragraphs_edit + - paragraphs_modal_add:paragraphs_modal_add diff --git a/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.libraries.yml b/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.libraries.yml new file mode 100755 index 000000000..d0716d506 --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/paragraphs_simple_edit.libraries.yml @@ -0,0 +1,5 @@ +widget.claro: + version: 1.x + css: + theme: + css/paragraphs_simple_edit.claro.css: {} diff --git a/web/modules/custom/paragraphs_simple_edit/src/Plugin/Field/FieldWidget/ParagraphsSimpleEditDefaultWidget.php b/web/modules/custom/paragraphs_simple_edit/src/Plugin/Field/FieldWidget/ParagraphsSimpleEditDefaultWidget.php new file mode 100755 index 000000000..b20f53efe --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/src/Plugin/Field/FieldWidget/ParagraphsSimpleEditDefaultWidget.php @@ -0,0 +1,336 @@ + 'none', + 'closed_mode_threshold' => 0, + 'add_mode' => 'dropdown', + ]; + + /** + * Constructs a ParagraphsSimpleEditDefaultWidget object. + * + * @param string $plugin_id + * The plugin_id for the widget. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the widget is associated. + * @param array $settings + * The widget settings. + * @param array $third_party_settings + * Any third party settings. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager + * The entity field manager. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager + * The theme manager. + * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination + * The redirect destination. + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ThemeManagerInterface $theme_manager, RedirectDestinationInterface $redirect_destination) { + $this->entityTypeManager = $entity_type_manager; + $this->themeManager = $theme_manager; + $this->redirectDestination = $redirect_destination; + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $entity_field_manager); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['third_party_settings'], + $container->get('entity_field.manager'), + $container->get('entity_type.manager'), + $container->get('theme.manager'), + $container->get('redirect.destination') + ); + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $defaults = parent::defaultSettings(); + // Set default values for unused settings. We are not removing these + // to avoid any problems when parent widget is being used. + foreach (static::$unusedSettings as $element_key => $element_value) { + $defaults[$element_key] = $element_value; + } + return $defaults; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $elements = parent::settingsForm($form, $form_state); + // Instead of removing these elements, we are just hiding those + // to avoid any problems when parent widget is being used. + foreach (static::$unusedSettings as $element_key => $element_value) { + $elements[$element_key]['#access'] = FALSE; + } + + // Form display mode is only useful when edit_mode is open. + if (!empty($elements['form_display_mode'])) { + $elements['form_display_mode']['#states'] = [ + 'visible' => [ + 'select[name="fields[' . $this->fieldDefinition->getName() . '][settings_edit_form][settings][edit_mode]"]' => ['value' => 'open'], + ], + ]; + } + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + $summary[] = $this->t('Title: @title', ['@title' => $this->getSetting('title')]); + $summary[] = $this->t('Plural title: @title_plural', [ + '@title_plural' => $this->getSetting('title_plural'), + ]); + + $edit_mode = $this->getSettingOptions('edit_mode')[$this->getSetting('edit_mode')]; + $closed_mode = $this->getSettingOptions('closed_mode')[$this->getSetting('closed_mode')]; + + $summary[] = $this->t('Edit mode: @edit_mode', ['@edit_mode' => $edit_mode]); + $summary[] = $this->t('Closed mode: @closed_mode', ['@closed_mode' => $closed_mode]); + + if ($this->getSetting('edit_mode') == 'open') { + $summary[] = $this->t('Form display mode: @form_display_mode', [ + '@form_display_mode' => $this->getSetting('form_display_mode'), + ]); + } + + if ($this->getDefaultParagraphTypeLabelName() !== NULL) { + $summary[] = $this->t('Default paragraph type: @default_paragraph_type', [ + '@default_paragraph_type' => $this->getDefaultParagraphTypeLabelName(), + ]); + } + $features_labels = array_intersect_key($this->getSettingOptions('features'), array_filter($this->getSetting('features'))); + if (!empty($features_labels)) { + $summary[] = $this->t('Features: @features', ['@features' => implode(', ', $features_labels)]); + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $element = parent::formElement($items, $delta, $element, $form, $form_state); + + /** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item */ + $item = $items[$delta]; + $paragraph = $item->entity; + if (!$paragraph || $paragraph->isNew()) { + return $element; + } + + $host = $items->getEntity(); + + if (!$host->id()) { + // Edit and Delete links require saved paragraph entities with IDs. + // For new host entities, paragraphs are created on form submission. + return $element; + } + + // Links shouldn't be there when edit mode is set to open. + $edit_mode = $this->getSetting('edit_mode'); + if ($edit_mode == 'open') { + return $element; + } + + $destination = $this->redirectDestination->getAsArray(); + + $edit_url = Url::fromRoute('paragraphs_edit.edit_form', [ + 'root_parent_type' => $host->getEntityTypeId(), + 'root_parent' => $host->id(), + 'paragraph' => $paragraph->id(), + ], + [ + 'query' => $destination, + 'attributes' => [ + 'class' => ['paragraphs-simple-edit-edit-link'], + ], + ]); + + $delete_url = Url::fromRoute('paragraphs_edit.delete_form', [ + 'root_parent_type' => $host->getEntityTypeId(), + 'root_parent' => $host->id(), + 'paragraph' => $paragraph->id(), + ], + [ + 'query' => $destination, + 'attributes' => [ + 'class' => ['paragraphs-simple-edit-delete-link'], + ], + ]); + + $element['top']['actions']['actions'] = [ + '#type' => 'dropbutton', + '#dropbutton_type' => 'extrasmall', + '#links' => [ + 'edit' => [ + 'title' => $this->t('Edit'), + 'url' => $edit_url, + ], + 'delete' => [ + 'title' => $this->t('Delete'), + 'url' => $delete_url, + ], + ], + '#weight' => 10, + ]; + + return $element; + } + + /** + * {@inheritdoc} + */ + public function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) { + $elements = parent::formMultipleElements($items, $form, $form_state); + + $field_name = $this->fieldDefinition->getName(); + + if (!isset($elements['add_more'])) { + return $elements; + } + + $host = $items->getEntity(); + $bundle = $host->bundle(); + + if (!$host->id()) { + $host_bundle_label = NULL; + $bundle_entity_type = $host->getEntityType()->getBundleEntityType(); + if ($bundle_entity_type) { + $host_bundle = $this->entityTypeManager->getStorage($bundle_entity_type)->load($bundle); + $host_bundle_label = $host_bundle ? $host_bundle->label() : $bundle; + } + else { + // Entity type does not have bundles. + $host_bundle_label = $host->getEntityType()->getLabel(); + } + // For new entities, we will just add a text. + $elements['add_more'] = [ + '#markup' => $this->t('Save the @bundle first to add new @title_plural.', [ + '@bundle' => $host_bundle_label, + '@title_plural' => $this->getSetting('title_plural'), + ]), + ]; + return $elements; + } + + $bundle_fields = $this->entityFieldManager + ->getFieldDefinitions($host->getEntityTypeId(), $bundle); + + if (!isset($bundle_fields[$field_name])) { + return $elements; + } + + $target_bundles = $this->getAccessibleOptions(); + + $destination = $this->redirectDestination->getAsArray(); + + $add_links = []; + foreach ($target_bundles as $bundle => $bundle_label) { + $paragraph_type = $this->entityTypeManager + ->getStorage('paragraphs_type') + ->load($bundle); + // Additional check to make sure paragraph type exists. + if (!$paragraph_type) { + continue; + } + + $add_links[$bundle] = [ + 'title' => $this->t('Add @type', ['@type' => $paragraph_type->label()]), + 'url' => Url::fromRoute('paragraphs_modal_add.add_form', [ + 'root_parent_type' => $host->getEntityTypeId(), + 'root_parent' => $host->id(), + 'parent_field_name' => $field_name, + 'paragraphs_type' => $bundle, + ], + [ + 'attributes' => [ + 'class' => ['paragraphs-simple-edit-add-link'], + ], + 'query' => $destination, + ]), + ]; + } + + if (empty($add_links)) { + return $elements; + } + + $elements['add_more'] = [ + '#type' => 'dropbutton', + '#dropbutton_type' => 'extrasmall', + '#links' => $add_links, + '#attributes' => [ + 'class' => ['paragraph-simple-edit--add-button'], + ], + ]; + + // Add css for claro theme to fix styling for add button. + if ($this->themeManager->getActiveTheme()->getName() == 'claro') { + $elements['add_more']['#attached']['library'][] = 'paragraphs_simple_edit/widget.claro'; + } + + return $elements; + } + +} diff --git a/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditLinksTest.php b/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditLinksTest.php new file mode 100644 index 000000000..9c92cae76 --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditLinksTest.php @@ -0,0 +1,164 @@ +loginAsAdmin(); + // Add two Paragraph types. + $this->addParagraphsType('btext'); + $this->addParagraphsType('dtext'); + + $content_type = 'paragraphed_test'; + $paragraph_field_name = 'paragraphs'; + + $this->addParagraphedContentType($content_type, $paragraph_field_name); + + // Enter to the field config since the weight is set through the form. + $this->drupalGet('admin/structure/types/manage/' . $content_type . '/fields/node.' . $content_type . '.' . $paragraph_field_name); + $this->submitForm([], 'Save settings'); + + $settings = ['edit_mode' => 'closed']; + $this->setSimpleEditWidget('node', $content_type, $paragraph_field_name, $settings); + + $node = $this->createNode(['type' => $content_type]); + + $this->assertAddLinks(['Add btext', 'Add dtext'], $node); + + $this->addParagraphsType('atext'); + $this->assertAddLinks(['Add btext', 'Add dtext', 'Add atext'], $node); + + $this->setParagraphsTypeWeight($content_type, 'dtext', 2, $paragraph_field_name); + $this->assertAddLinks(['Add dtext', 'Add btext', 'Add atext'], $node); + + $this->setAllowedParagraphsTypes($content_type, ['dtext', 'atext'], TRUE, $paragraph_field_name); + $this->assertAddLinks(['Add dtext', 'Add atext'], $node); + + $this->setParagraphsTypeWeight($content_type, 'atext', 1, $paragraph_field_name); + $this->assertAddLinks(['Add atext', 'Add dtext'], $node); + + $this->setAllowedParagraphsTypes($content_type, ['atext', 'dtext', 'btext'], TRUE, $paragraph_field_name); + $this->assertAddLinks(['Add atext', 'Add dtext', 'Add btext'], $node); + + // Create paragraphs & add to the node. + foreach (['atext', 'btext'] as $type) { + $this->addParagraphToEntity($type, $node, $paragraph_field_name); + } + $this->assertActionLinks(2, $node); + + $this->addParagraphToEntity('dtext', $node, $paragraph_field_name); + $this->assertActionLinks(3, $node); + } + + /** + * Tests the widget empty text for node. + */ + public function testWidgetEmptyTextForNode() { + $this->loginAsAdmin(); + // Add two Paragraph types. + $this->addParagraphsType('btext'); + $this->addParagraphsType('dtext'); + + $content_type = 'paragraphed_test'; + $paragraph_field_name = 'paragraphs'; + + $this->addParagraphedContentType($content_type, $paragraph_field_name); + + // Enter to the field config since the weight is set through the form. + $this->drupalGet('admin/structure/types/manage/' . $content_type . '/fields/node.' . $content_type . '.' . $paragraph_field_name); + $this->submitForm([], 'Save settings'); + + $settings = ['edit_mode' => 'closed']; + $this->setSimpleEditWidget('node', $content_type, $paragraph_field_name, $settings); + + // Go to node add page. + $this->drupalGet('node/add/' . $content_type); + + $this->assertSession()->pageTextContains('Save the ' . $content_type . ' first to add new Paragraphs.'); + + // Change paragraphs title text. + $settings = [ + 'title' => 'Item', + 'title_plural' => 'Items', + ]; + $this->setParagraphsWidgetSettings($content_type, $paragraph_field_name, $settings); + + // Go to node add page. + $this->drupalGet('node/add/' . $content_type); + + $this->assertSession()->pageTextContains('Save the ' . $content_type . ' first to add new Items.'); + } + + /** + * Tests the widget empty text for user. + */ + public function testWidgetEmptyTextForUser() { + $this->loginAsAdmin(); + // Add two Paragraph types. + $this->addParagraphsType('btext'); + $this->addParagraphsType('dtext'); + + $paragraph_field_name = 'paragraphs'; + + // Add paragraphs field to user. + $this->addParagraphsField('user', $paragraph_field_name, 'user'); + + // Enter to the field config since the weight is set through the form. + $this->drupalGet('admin/config/people/accounts/fields/user.user.' . $paragraph_field_name); + $this->submitForm([], 'Save settings'); + + $settings = ['edit_mode' => 'closed']; + $this->setSimpleEditWidget('user', 'user', $paragraph_field_name, $settings); + + // Go to user add page. + $this->drupalGet('admin/people/create'); + + $this->assertSession()->pageTextContains('Save the User first to add new Paragraphs.'); + + // Change paragraphs title text. + $settings = [ + 'title' => 'Item', + 'title_plural' => 'Items', + ]; + $this->setParagraphsWidgetSettings('user', $paragraph_field_name, $settings, NULL, 'user'); + + // Go to user add page. + $this->drupalGet('admin/people/create'); + + $this->assertSession()->pageTextContains('Save the User first to add new Items.'); + } + + /** + * Asserts order and quantity of add links. + */ + protected function assertAddLinks(array $expected_links, ContentEntityInterface $node) { + $this->drupalGet('node/' . $node->id() . '/edit'); + $links = $this->xpath('//a[@class="paragraphs-simple-edit-add-link"]'); + // Check if the buttons are in the same order as the given array. + foreach ($links as $key => $link) { + $this->assertEquals($link->getText(), $expected_links[$key]); + } + $this->assertEquals(count($links), count($expected_links), 'The amount of drop down links matches with the given array'); + } + + /** + * Asserts quantity of edit/delete links. + */ + protected function assertActionLinks(int $count, ContentEntityInterface $node) { + $this->drupalGet('node/' . $node->id() . '/edit'); + $edit_links = $this->xpath('//a[@class="paragraphs-simple-edit-edit-link"]'); + $this->assertEquals(count($edit_links), $count, 'The amount of edit links matches with the given count'); + $delete_links = $this->xpath('//a[@class="paragraphs-simple-edit-delete-link"]'); + $this->assertEquals(count($delete_links), $count, 'The amount of delete links matches with the given count'); + } + +} diff --git a/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditTestBase.php b/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditTestBase.php new file mode 100644 index 000000000..3eb72cf17 --- /dev/null +++ b/web/modules/custom/paragraphs_simple_edit/tests/src/Functional/WidgetSimpleEdit/ParagraphsSimpleEditTestBase.php @@ -0,0 +1,65 @@ +admin_permissions[] = 'administer user fields'; + $this->admin_permissions[] = 'administer users'; + } + + /** + * Create & add paragraph to the entity. + */ + protected function addParagraphToEntity(string $type, ContentEntityInterface $entity, string $paragraph_field_name) { + $paragraph = Paragraph::create([ + 'type' => $type, + ]); + $paragraph->setParentEntity($entity, $paragraph_field_name); + $paragraph->save(); + // Save the entity as well. + $entity->{$paragraph_field_name}->appendItem($paragraph); + $entity->save(); + } + + /** + * Sets the Paragraphs widget to simple edit. + */ + protected function setSimpleEditWidget(string $entity_type, string $bundle, string $paragraph_field_name, array $settings = []) { + $form_display = EntityFormDisplay::load($entity_type . '.' . $bundle . '.default') + ->setComponent($paragraph_field_name, [ + 'type' => 'paragraphs_simple_edit_default', + 'settings' => $settings, + ]); + $form_display->save(); + } + +}