diff --git a/src/Form/EntityEmbedDialog.php b/src/Form/EntityEmbedDialog.php index cc9daaf..e15affa 100644 --- a/src/Form/EntityEmbedDialog.php +++ b/src/Form/EntityEmbedDialog.php @@ -16,6 +16,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\editor\Ajax\EditorDialogSave; use Drupal\editor\EditorInterface; use Drupal\embed\EmbedButtonInterface; @@ -87,6 +88,20 @@ class EntityEmbedDialog extends FormBase { */ protected $entityBrowserSettings = []; + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * Indicates whether the current widget instance is in translation. + * + * @var bool + */ + protected $isTranslating; + /** * Constructs a EntityEmbedDialog object. * @@ -102,14 +117,17 @@ class EntityEmbedDialog extends FormBase { * The entity field manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. */ - public function __construct(EntityEmbedDisplayManager $entity_embed_display_manager, FormBuilderInterface $form_builder, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, EntityFieldManagerInterface $entity_field_manager, ModuleHandlerInterface $module_handler) { + public function __construct(EntityEmbedDisplayManager $entity_embed_display_manager, FormBuilderInterface $form_builder, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, EntityFieldManagerInterface $entity_field_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) { $this->entityEmbedDisplayManager = $entity_embed_display_manager; $this->formBuilder = $form_builder; $this->entityTypeManager = $entity_type_manager; $this->eventDispatcher = $event_dispatcher; $this->entityFieldManager = $entity_field_manager; $this->moduleHandler = $module_handler; + $this->languageManager = $language_manager; } /** @@ -194,9 +212,16 @@ class EntityEmbedDialog extends FormBase { $form_state->set('entity_element', $entity_element); $entity = $this->loadEntityByAttributes($entity_element); $form_state->set('entity', $entity ?: NULL); + $form_state->set('langcode', $this->languageManager->getCurrentLanguage()->getId()); + + if ($entity instanceof EntityInterface) { + $this->initIsTranslating($form_state, $entity); + $entity = $this->prepareEntity($entity, $form_state); + $form_state->set('entity', $entity); + } if (!$form_state->get('step')) { - // If an entity has been selected, then always skip to the embed options. + // If an entity has been selected, then skip to the embed or edit options. if ($form_state->get('entity')) { $form_state->set('step', 'embed'); } @@ -223,6 +248,9 @@ class EntityEmbedDialog extends FormBase { elseif ($form_state->get('step') == 'embed') { $form = $this->buildEmbedStep($form, $form_state); } + elseif ($form_state->get('step') == 'entity_edit') { + $form = $this->buildEntityEditStep($form, $form_state); + } return $form; } @@ -431,6 +459,21 @@ class EntityEmbedDialog extends FormBase { '#title' => $this->t('Selected entity'), '#markup' => $entity_label, ]; + + $inline_entity_edit_enabled = $embed_button->getTypeSetting('inline_entity_edit_enabled'); + if ($inline_entity_edit_enabled && $this->moduleHandler->moduleExists('inline_entity_form')) { + // Provide a link to switch to the entity edit step. + $form['entity_edit'] = [ + '#type' => 'button', + '#executes_submit_callback' => FALSE, + '#value' => $this->t('Edit'), + '#ajax' => [ + 'callback' => '::submitAndShowEntityEdit', + 'event' => 'click', + ], + ]; + } + $form['attributes']['data-entity-type'] = [ '#type' => 'hidden', '#value' => $entity_element['data-entity-type'], @@ -558,6 +601,121 @@ class EntityEmbedDialog extends FormBase { return $form; } + /** + * Form constructor for the entity embedding step. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. + */ + public function buildEntityEditStep(array $form, FormStateInterface $form_state) { + // Entity element is calculated on every AJAX request/submit. + // See ::buildForm(). + $entity_element = $form_state->get('entity_element'); + + /** @var \Drupal\Core\Entity\EntityInterface $entity */ + $entity = $form_state->get('entity'); + + $form['#title'] = $this->t('Edit @type', array('@type' => $entity->getEntityType()->getSingularLabel())); + + if ($this->moduleHandler->moduleExists('inline_entity_form')) { + // @TODO: Make form_mode configurable? + $form['inline_entity_form'] = [ + '#type' => 'inline_entity_form', + '#entity_type' => $entity->getEntityType()->id(), + '#bundle' => $entity->bundle(), + '#language' => $entity->language()->getId(), + '#default_value' => $entity, + '#op' => 'edit', + '#form_mode' => 'default', + '#save_entity' => TRUE, + ]; + + $form['attributes']['data-entity-type'] = array( + '#type' => 'hidden', + '#value' => $entity_element['data-entity-type'], + ); + $form['attributes']['data-entity-uuid'] = array( + '#type' => 'hidden', + '#value' => $entity_element['data-entity-uuid'], + ); + + // TODO: Also set data-entity-embed-display-settings. + $form['attributes']['data-entity-embed-display'] = array( + '#type' => 'hidden', + '#default_value' => $entity_element['data-entity-embed-display'], + '#required' => TRUE, + ); + $form['attributes']['data-align'] = array( + '#type' => 'hidden', + '#default_value' => isset($entity_element['data-align']) ? $entity_element['data-align'] : '', + ); + $form['attributes']['data-caption'] = array( + '#type' => 'hidden', + '#default_value' => isset($entity_element['data-caption']) ? Html::decodeEntities($entity_element['data-caption']) : '', + '#element_validate' => array('::escapeValue'), + ); + + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['back'] = array( + '#type' => 'submit', + '#value' => $this->t('Back'), + // No regular submit-handler. This form only works via JavaScript. + '#submit' => array(), + '#ajax' => array( + 'callback' => '::submitAndShowEmbed', + 'event' => 'click', + ), + ); + $form['actions']['save_modal'] = array( + '#type' => 'submit', + '#value' => $this->t('Save'), + '#button_type' => 'primary', + // Pretend to be IEFs submit button. + '#submit' => array( + [ + 'Drupal\inline_entity_form\ElementSubmit', + 'trigger' + ] + ), + '#ief_submit_trigger' => TRUE, + '#ief_submit_trigger_all' => TRUE, + // No regular submit-handler. This form only works via JavaScript. + '#ajax' => array( + 'callback' => '::submitEntityEditStep', + 'event' => 'click', + ), + ); + } + else { + // Without the inline entity form module, the only option is to go back. + $form['message'] = array( + '#markup' => $this->t('The Inline Entity Form module is required.'), + ); + $form['actions'] = array( + '#type' => 'actions', + ); + $form['actions']['back'] = array( + '#type' => 'submit', + '#value' => $this->t('Back'), + // No regular submit-handler. This form only works via JavaScript. + '#submit' => array(), + '#ajax' => array( + 'callback' => '::submitAndShowEmbed', + 'event' => 'click', + ), + ); + } + + return $form; + } + /** * {@inheritdoc} */ @@ -778,6 +936,21 @@ class EntityEmbedDialog extends FormBase { return $this->submitStep($form, $form_state, 'embed'); } + /** + * Submit and show entity edit step after submit. + * + * @param array $form + * The form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The ajax response. + */ + public function submitAndShowEntityEdit(array $form, FormStateInterface $form_state) { + return $this->submitStep($form, $form_state, 'entity_edit'); + } + /** * Form submission handler for the entity embedding step. * @@ -839,6 +1012,46 @@ class EntityEmbedDialog extends FormBase { return $response; } + /** + * Form submission handler for the entity editing step. + * + * On success this will submit the command to save the embedded entity with + * the configured display settings to the WYSIWYG element, and then close the + * modal dialog. On form errors, this will rebuild the form and display the + * error messages. + * + * @param array $form + * An associative array containing the structure of the form. + * @param FormStateInterface $form_state + * An associative array containing the current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * The ajax response. + */ + public function submitEntityEditStep(array &$form, FormStateInterface $form_state) { + $response = new AjaxResponse(); + + // Display errors in form, if any. + if ($form_state->hasAnyErrors()) { + unset($form['#prefix'], $form['#suffix']); + $form['status_messages'] = array( + '#type' => 'status_messages', + '#weight' => -10, + ); + $response->addCommand(new HtmlCommand('#entity-embed-dialog-form', $form)); + } + else { + $form_state->set('step', 'embed'); + $form_state->setRebuild(TRUE); + $rebuild_form = $this->formBuilder->rebuildForm('entity_embed_dialog', $form_state, $form); + unset($rebuild_form['#prefix'], $rebuild_form['#suffix']); + $response->addCommand(new HtmlCommand('#entity-embed-dialog-form', $rebuild_form)); + $response->addCommand(new SetDialogTitleCommand('', $rebuild_form['#title'])); + } + + return $response; + } + /** * Form element validation handler; Escapes the value an element. * @@ -903,4 +1116,93 @@ class EntityEmbedDialog extends FormBase { } } + /** + * Determines the translate status. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * @param \Drupal\Core\Entity\EntityInterface $entity + * The embedded entity. + */ + protected function initIsTranslating(FormStateInterface $form_state, EntityInterface $entity) { + if ($this->isTranslating !== NULL) { + return; + } + + $this->isTranslating = FALSE; + if (!$entity->isTranslatable() || !$entity->getEntityType()->hasKey('default_langcode')) { + return; + } + + $default_langcode_key = $entity->getEntityType()->getKey('default_langcode'); + if (!$entity->hasField($default_langcode_key)) { + return; + } + + $langcode = $form_state->get('langcode'); + $has_translation = $entity->hasTranslation($langcode); + $adding = !$has_translation; + $editing = $has_translation && $entity->getTranslation($langcode)->get($default_langcode_key)->value === 0; + + $this->isTranslating = $adding || $editing; + } + + /** + * Prepares the entity for translation. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return \Drupal\Core\Entity\EntityInterface + * The prepared entity. + * + * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes(). + */ + protected function prepareEntity(EntityInterface $entity, FormStateInterface $form_state) { + $langcode = $form_state->get('langcode'); + + if (!$this->isTranslating) { + // Set the langcode if we are not translating. + $langcode_key = $entity->getEntityType()->getKey('langcode'); + if ($entity->get($langcode_key)->value !== $langcode) { + // If a translation in the given language already exists, switch to + // that. If there is none yet, update the language. + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + else { + $entity->set($langcode_key, $langcode); + } + } + } + else { + // Add translation if missing for the target language. + if (!$entity->hasTranslation($langcode)) { + // Get the selected translation of the entity. + $entity_langcode = $entity->language()->getId(); + $source = $form_state->get(['content_translation', 'source']); + $source_langcode = $source ? $source->getId() : $entity_langcode; + if ($entity->hasTranslation($source_langcode)) { + $entity = $entity->getTranslation($source_langcode); + } + // The entity has no content translation source field if + // no entity field is translatable, even if the host is. + if ($entity->hasField('content_translation_source')) { + // Initialise the translation with source language values. + $entity->addTranslation($langcode, $entity->toArray()); + $translation = $entity->getTranslation($langcode); + $manager = \Drupal::service('content_translation.manager'); + $manager->getTranslationMetadata($translation)->setSource($entity->language()->getId()); + } + } + if ($entity->hasField('content_translation_source')) { + $entity = $entity->getTranslation($langcode); + } + } + + return $entity; + } + } diff --git a/src/Plugin/EmbedType/Entity.php b/src/Plugin/EmbedType/Entity.php index 554ff3d..654001b 100644 --- a/src/Plugin/EmbedType/Entity.php +++ b/src/Plugin/EmbedType/Entity.php @@ -6,6 +6,7 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeRepositoryInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\PluginDependencyTrait; @@ -53,6 +54,13 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { */ protected $displayPluginManager; + /** + * The module handler. + * + * @var \Drupal\Core\Extension\ModuleHandlerInterface + */ + protected $moduleHandler; + /** * {@inheritdoc} * @@ -70,13 +78,16 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { * The entity type bundle info service. * @param \Drupal\entity_embed\EntityEmbedDisplay\EntityEmbedDisplayManager $display_plugin_manager * The plugin manager. + * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler + * The module handler. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeRepositoryInterface $entity_type_repository, EntityTypeBundleInfoInterface $bundle_info, EntityEmbedDisplayManager $display_plugin_manager) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeRepositoryInterface $entity_type_repository, EntityTypeBundleInfoInterface $bundle_info, EntityEmbedDisplayManager $display_plugin_manager, ModuleHandlerInterface $module_handler) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entity_type_manager; $this->entityTypeRepository = $entity_type_repository; $this->entityTypeBundleInfo = $bundle_info; $this->displayPluginManager = $display_plugin_manager; + $this->moduleHandler = $module_handler; } /** @@ -90,7 +101,8 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { $container->get('entity_type.manager'), $container->get('entity_type.repository'), $container->get('entity_type.bundle.info'), - $container->get('plugin.manager.entity_embed.display') + $container->get('plugin.manager.entity_embed.display'), + $container->get('module_handler') ); } @@ -106,6 +118,7 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { 'entity_browser_settings' => [ 'display_review' => 0, ], + 'inline_entity_edit_enabled' => FALSE, ]; } @@ -196,6 +209,15 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { } } + if ($this->moduleHandler->moduleExists('inline_entity_form')) { + $form['inline_entity_edit_enabled'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable inline editing'), + '#description' => $this->t('Allow embedded entities to be editable within the embed dialog.'), + '#default_value' => $this->getConfigurationValue('inline_entity_edit_enabled'), + ]; + } + return $form; } @@ -211,6 +233,9 @@ class Entity extends EmbedTypeBase implements ContainerFactoryPluginInterface { $entity_browser = $form_state->getValue('entity_browser') == '_none' ? '' : $form_state->getValue('entity_browser'); $form_state->setValue('entity_browser', $entity_browser); $form_state->setValue('entity_browser_settings', $form_state->getValue('entity_browser_settings', [])); + if ($form_state->hasValue('inline_entity_edit_enabled')) { + $form_state->setValue('inline_entity_edit_enabled', $form_state->getValue('inline_entity_edit_enabled')); + } parent::submitConfigurationForm($form, $form_state); }