diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9827bbcc107cb2cb64a914840198bcdbb1b103b1..16d95c79169930691cbbc2ea4ab07ed2e3ea70c2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,31 +1,43 @@ parameters: ignoreErrors: - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:assertWaitOnAjaxRequest\\(\\)\\.$#" + message: '#^Unsafe usage of new static\(\)\.$#' + identifier: new.static + count: 1 + path: src/Plugin/Field/FieldWidget/AsyncMetatagFirehose.php + + - + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:assertWaitOnAjaxRequest\(\)\.$#' + identifier: method.notFound count: 1 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForButton\\(\\)\\.$#" - count: 2 + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:waitForButton\(\)\.$#' + identifier: method.notFound + count: 3 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForElement\\(\\)\\.$#" - count: 2 + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:waitForElement\(\)\.$#' + identifier: method.notFound + count: 4 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForField\\(\\)\\.$#" + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:waitForField\(\)\.$#' + identifier: method.notFound count: 2 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForLink\\(\\)\\.$#" - count: 1 + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:waitForLink\(\)\.$#' + identifier: method.notFound + count: 2 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php - - message: "#^Call to an undefined method Drupal\\\\Tests\\\\WebAssert\\:\\:waitForText\\(\\)\\.$#" - count: 3 + message: '#^Call to an undefined method Drupal\\Tests\\WebAssert\:\:waitForText\(\)\.$#' + identifier: method.notFound + count: 4 path: tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php diff --git a/src/Plugin/Field/FieldWidget/AsyncMetatagFirehose.php b/src/Plugin/Field/FieldWidget/AsyncMetatagFirehose.php index 6d326fae3ad9d493be4c373ba2e687fb8cf64d42..14342fa85e553d45cae1cac88791d845d5d9001a 100644 --- a/src/Plugin/Field/FieldWidget/AsyncMetatagFirehose.php +++ b/src/Plugin/Field/FieldWidget/AsyncMetatagFirehose.php @@ -4,10 +4,17 @@ namespace Drupal\metatag_async_widget\Plugin\Field\FieldWidget; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; +use Drupal\metatag\MetatagManagerInterface; +use Drupal\metatag\MetatagTagPluginManager; use Drupal\metatag\Plugin\Field\FieldWidget\MetatagFirehose; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Asynchronous widget for the Metatag field. @@ -23,6 +30,46 @@ use Drupal\metatag\Plugin\Field\FieldWidget\MetatagFirehose; */ class AsyncMetatagFirehose extends MetatagFirehose { + /** + * Instance of Entity Type Manager service. + */ + protected EntityTypeManagerInterface $entityTypeManager; + + /** + * {@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('metatag.manager'), + $container->get('plugin.manager.metatag.tag'), + $container->get('config.factory'), + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function __construct( + $plugin_id, + $plugin_definition, + FieldDefinitionInterface $field_definition, + array $settings, + array $third_party_settings, + MetatagManagerInterface $manager, + MetatagTagPluginManager $plugin_manager, + ConfigFactoryInterface $config_factory, + EntityTypeManagerInterface $entity_type_manager, + ) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings, $manager, $plugin_manager, $config_factory); + $this->entityTypeManager = $entity_type_manager; + } + /** * Ajax callback for the "Customize meta tags" button. */ @@ -65,7 +112,13 @@ class AsyncMetatagFirehose extends MetatagFirehose { /** * {@inheritdoc} */ - public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array { + public function formElement( + FieldItemListInterface $items, + $delta, + array $element, + array &$form, + FormStateInterface $form_state, + ): array { if ($form_state->get('metatag_async_widget_customize_meta_tags')) { $element += parent::formElement($items, $delta, $element, $form, $form_state); // Open the meta tags group upon selection. @@ -109,7 +162,81 @@ class AsyncMetatagFirehose extends MetatagFirehose { } } + // Add current entity node as hidden field for saving purposes. + $entity = $items->getEntity(); + $element['metatag-async-widget-entity-id'] = [ + '#type' => 'hidden', + '#name' => 'metatag-async-widget-entity-id', + '#value' => $entity->isNew() ? 'new' : $entity->id(), + ]; + + $element['metatag-async-widget-entity-language'] = [ + '#type' => 'hidden', + '#name' => 'metatag-async-widget-entity-language', + '#value' => $entity->language()->getId(), + ]; + + $element['metatag-async-widget-entity-type'] = [ + '#type' => 'hidden', + '#name' => 'metatag-async-widget-entity-type', + '#value' => $entity->getEntityTypeId(), + ]; + return $element; } + /** + * Apply default entity values to the metatag field if no async edit was requested. + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state): array { + $unchangedValues = array_filter($values, function ($value) { + return isset($value['metatag_async_widget_customize_meta_tags']) || !isset($value['basic']) || empty($value); + }); + + foreach ($unchangedValues as $key => &$value) { + $entityType = $value['metatag-async-widget-entity-type']; + $entityID = $value['metatag-async-widget-entity-id']; + $entityLanguage = $value['metatag-async-widget-entity-language']; + unset( + $value['metatag-async-widget-entity-id'], + $value['metatag-async-widget-entity-type'], + $value['metatag-async-widget-entity-language'] + ); + + if ($entityID === 'new') { + continue; + } + try { + $storage = $this->entityTypeManager->getStorage($entityType); + } + catch (\Exception) { + continue; + } + + $entity = $storage->load($entityID); + if (!$entity instanceof ContentEntityInterface) { + continue; + } + + // Check if currently handled entity is in a different language than the + // original entity. + if ($entity->language()->getId() !== $entityLanguage && $entity->hasTranslation($entityLanguage)) { + $entity = $entity->getTranslation($entityLanguage); + } + + // Get current field value from the entity. + if ($originalValues = $entity->get($this->fieldDefinition->getName())->getValue()) { + $value = $originalValues[$key]['value']; + } + } + + // The rest of the values that don't have the + // metatag_async_widget_customize_meta_tags value needs to be parsed by + // parent::messageFormValues. + $customizedValues = array_diff_key($values, $unchangedValues); + $customizedValues = parent::massageFormValues($customizedValues, $form, $form_state); + + return array_merge($unchangedValues, $customizedValues); + } + } diff --git a/tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php b/tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php index 61e925135d1ead3ba0305ee6a829aa117182831b..5e87cf502a78141bacbb3dc569419e7faca5a417 100644 --- a/tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php +++ b/tests/src/FunctionalJavascript/MetatagAsyncWidgetTest.php @@ -175,6 +175,21 @@ class MetatagAsyncWidgetTest extends WebDriverTestBase { $assert->waitForButton('Customize meta tags')->click(); $assert->waitForText('Configure the meta tags below.'); $assert->fieldValueEquals('field_meta_tags[0][basic][abstract]', $abstract); + + // Test that saving the node without changing metatags does not lose the + // information. + $this->drupalGet('node/1/edit'); + $assert->fieldNotExists('field_meta_tags[0][basic][abstract]'); + $assert->waitForElement('xpath', $metatag_details_xpath)->click(); + $page->pressButton('Save'); + sleep(1); + + $assert->waitForLink('Edit')->click(); + $assert->fieldNotExists('field_meta_tags[0][basic][abstract]'); + $assert->waitForElement('xpath', $metatag_details_xpath)->click(); + $assert->waitForButton('Customize meta tags')->click(); + $assert->waitForText('Configure the meta tags below.'); + $assert->fieldValueEquals('field_meta_tags[0][basic][abstract]', $abstract); } }