translations_pack-1.0.0-beta3/src/Controller/TranslationsPackController.php

src/Controller/TranslationsPackController.php
<?php

namespace Drupal\translations_pack\Controller;

use Drupal\content_translation\Controller\ContentTranslationController;
use Drupal\translations_pack\TranslationsFormBuilder;
use Drupal\translations_pack\MockRouteMatch;
use Drupal\translations_pack\Form\LanguageSelectorForm;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangesDetectionTrait;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Form\FormState;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Form\EnforcedResponseException;
use Drupal\Core\Render\Element;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Routing\RouteMatchInterface;

class TranslationsPackController extends ContentTranslationController {
  
  use EntityChangesDetectionTrait;

  protected $original_lang;
  protected $source_lang;
  protected $language_switch = NULL;
  // succesful submit will not rebuild language form and so it won't be "active"
  protected array $active_languages = [];
  protected array $has_translation = [];
  protected array $translation_states= [];
  protected array $language_selection = [];
  protected array $tab_error = [];
  protected $entity;

  public function build_add($form_operation, Request $request, RouteMatchInterface $route_match) {
    [$entity_type_id, $operation] = explode('.', $form_operation);
    $form_object = $this->entityTypeManager()->getFormObject($entity_type_id, $operation);
    $entity = $form_object->getEntityFromRouteMatch($route_match, $entity_type_id);
    $language = $this->languageManager()
        ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
    $langcode_key = $entity->getEntityType()->getKey('langcode');
    $entity->set($langcode_key, $language->getId());
    $route_match = new MockRouteMatch($entity);
    return $this->build_pack($entity_type_id, $request, $route_match);
  }

  protected function getRequestEntity(RouteMatchInterface $route_match, $entity_type_id) {
    if ($this->entity) {
      return $this->entity;
    }
    $entity = $route_match->getParameter($entity_type_id);

    if ($entity->isDefaultTranslation()) {
      $this->entity = $entity;
    }
    else {
      $this->language_switch = $entity->language()->getId();
      $this->entity = $entity->getUntranslated();
    }
    return $this->entity;
  }

  protected function getOriginalForm($entity) {
    $entity->translations_pack_original = TRUE;
    $additions = ['langcode' => $entity->language()->getId()];
    $form = $this->entityFormBuilder()->getForm($entity, 'edit', $additions);
    unset($entity->translations_pack_original);
    return $form;
  }

  public function build($entity_type_id, Request $request, RouteMatchInterface $route_match) {
    $build = [];

    $entity = $this->getRequestEntity($route_match, $entity_type_id);

    $this->original_lang = $entity->language();
    $this->source_lang = $entity->getUntranslated()->language();
    if ($entity->hasField('moderation_state')) {
      $this->translation_states[$entity->language()->getId()] = 
        $entity->moderation_state->value;
    }

    $response_exception = NULL;
    $is_ajax = $request->request->has('_drupal_ajax');
    $active_langcode = '';
    if ($is_ajax && $request->request->has('translations_pack_active_id')) {
      $active_langcode = $request->request->get('translations_pack_active_id');
    }

    $original_completed = FALSE;
    if ($request->isMethod('POST')) {
      if ($request->request->has('language_selection')) {
        $this->language_selection = $request->request->all('language_selection');
      }
      if (!$request->request->has('form_id')) {
        $original_completed = TRUE;
      }
    }

    /*
     * forms may rebuild while original has finished submitting .
     * if not ajax ignore active langcode,
     * if ajax then active lang code is set for other forms only .
     */
    if (!$original_completed && (!$is_ajax || !$active_langcode)) {
      try {
        $this->active_languages[$this->original_lang->getId()] = $this->original_lang; 
        $build['original'] = $this->getOriginalForm($entity);
        if (isset($build['original']['langcode'])) {
          $build['original']['langcode']['#access'] = FALSE;
        }
        $build['original']['#attributes']['data-lang-code'] = $this->original_lang->getId();
      }
      catch (EnforcedResponseException $e) {
        $response_exception = $e;
        $form_states = $this->entityFormBuilder()->getFormStates();
        $langcode = $this->original_lang->getId();
        $entity = $form_states[$langcode][1]->getValue('translations_pack_entity');
        $entity->packed_newid = $form_states[$langcode][1]->getValue('translations_pack_newid');

        // get a fresh copy
        $request->request->set('form_id', '');
        $build['original'] =
          $this->entityFormBuilder()->getForm($entity, 'edit');
        $build['original']['#attributes']['data-lang-code'] = $this->original_lang->getId();
      }
    }

    $this->entityFormBuilder()->setTranslationMode();
    foreach ($this->languageManager()->getLanguages() as $lang_code => $language) {
      if ($lang_code == $this->original_lang->getId()) {
        continue;
      }
      if ($is_ajax && $active_langcode != $lang_code) {
        continue;
      }
      if ($request->isMethod('POST')) {
        if (isset($this->language_selection[$lang_code])) {
          $this->preparePostData($request, $lang_code, $entity);
        }
        else if (!$is_ajax) {
          // language selection not included in ajax
          continue;
        }
      }
      try {
        $route_match = new MockRouteMatch($entity, $language);
        if ($route_match->entity->hasTranslation($lang_code)) {
          $this->has_translation[$lang_code] = TRUE;
          if ($entity->hasField('moderation_state')) {
            $this->translation_states[$lang_code] = 
              $route_match->entity->getTranslation($lang_code)->moderation_state->value;
          }
          $translation_form =
            $this->edit($language, $route_match, $entity->getEntityTypeId());
        }
        else {
          $route_match->entity->pack_language_code = $language->getId();
          $translation_form =
            $this->add($this->source_lang, $language, $route_match, $entity->getEntityTypeId());
          unset($route_match->entity->pack_language_code);
        }
      }
      catch (EnforcedResponseException $e) {
        if (!$response_exception) {
          $response_exception = $e;
        }
        if (!$this->original_lang) {
          $this->original_lang = $language;
        }
        continue;
      }
      $build[$lang_code] = $translation_form;
      $this->active_languages[$lang_code] = $language; 
    }
    
    if ($request->isMethod('POST') && !$is_ajax) {
      $success = $this->saveTranslations($entity);
      if ($success and $response_exception) {
        throw $response_exception; 
      }
    }

    $build['original']['translations_pack_active_id'] = [
      '#type' => 'hidden',
      '#attributes' => ['name' => 'translations_pack_active_id'],
    ];
    // browser blocks submission if no change is detected.
    $build['original']['translations_pack_change'] = [
      '#type' => 'hidden',
      '#attributes' => ['name' => 'translations_pack_change'],
    ];

    return $build;
  }

  protected function tabsModerationStates(&$item_list) {
    foreach ($this->translation_states as $langcode => $state) {
      $label = $item_list['#items'][$langcode]['#plain_text'];
      unset($item_list['#items'][$langcode]['#plain_text']);
      $item_list['#items'][$langcode] += [
        '#type' => 'inline_template',
        '#template' => '{{label}} | {{state}}',
        '#context' => ['label' => $label, 'state' => $state]
      ];
    }
  }

  public function build_pack($entity_type_id, Request $request, RouteMatchInterface $route_match) {
    $entity = $this->getRequestEntity($route_match, $entity_type_id);
    $build = $this->build($entity_type_id, $request, $route_match);
    $selector_form = new LanguageSelectorForm();
    $language_selection = $this->language_selection ? $this->language_selection : $this->has_translation;
    $build['language_selector'] = $this->formBuilder()
      ->getForm($selector_form, $language_selection, $this->active_languages);

    $build['original']['tabs'] = $build['language_selector']['tabs'];
    $build['original']['tabs']['#weight'] = -1;
    if ($entity->hasField('moderation_state')) {
      $this->tabsModerationStates($build['original']['tabs']);
    }
    unset($build['language_selector']['tabs']);

    foreach ($this->languageManager()->getLanguages() as $lang_code => $language) {
      if (!isset($build[$lang_code])) {
        continue;
      }
      if ($lang_code == $this->original_lang->getId()) {
        continue;
      }

      $this->integrateTranslationForm($build['original'], $build[$lang_code], $lang_code);
    }
    $this->markTabErrors($build['original']['tabs']);
    $build['#attached'] = [
      'library' => ['translations_pack/tabs']
    ];
    if ($this->language_switch) {
      $build['#attached']['drupalSettings']
        ['translations_pack_switch'] = $this->language_switch;
    }
    $this->moduleHandler()->alter('translations_pack', $build, $entity);
    $themeManager = \Drupal::theme();
    $themeManager->alter('translations_pack', $build, $entity);

    return $build;
  }

  function preparePostData(Request $request, $lang_code, ContentEntityInterface $entity) {
    $postdata = $request->request;
    foreach (['form_id', 'form_build_id', 'form_token'] as $name) {
      $postdata->set($name, $postdata->get("{$name}_{$lang_code}"));
    }
    // this is controlled in translations_pack_form_alter
    $postdata->set('op', 'save');
  }

  function saveTranslations(ContentEntityInterface $entity) {
    $type = $entity->getEntityTypeId();
    $entity_storage = $this->entityTypeManager()->getStorage($type);
    $entity_pack = NULL;
    $draft_pack = NULL;
    if ($entity->isNew()) {
      $langcode = $this->original_lang->getId();
      $form_states = $this->customFormBuilder->getFormStates();
      list($form_object, $form_state) = $form_states[$langcode];
      $entity_pack = $form_object->getEntity();
      if (!$entity_pack->id()) {
        $newid = $form_state->getValue('translations_pack_newid');
        if ($newid) {
          $entity_pack = $entity_storage->load($newid);
          $entity_pack->setNewRevision(FALSE);
          $entity_pack->setSyncing(TRUE);
        }
        else {
          $this->getLogger('translations_pack')->error('saved id got lost');
          return;
        }
      }
      foreach ($entity_pack->getTranslationLanguages(FALSE) as $langcode => $language) {
        // check the validation fail case
        if ($langcode != $this->original_lang->getId()) {
          $entity_pack->removeTranslation($langcode);
        }
      }
    }
    else {
      if ($entity->isDefaultRevision()) {
        $entity_pack = clone $entity;
        $entity_pack->setNewRevision(FALSE);
        $entity_pack->setSyncing(TRUE);
      }
      else {
        $draft_pack = clone $entity;
        $draft_pack->setNewRevision(FALSE);
        $draft_pack->setSyncing(TRUE);
      }
    }
    $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
    $success = true;
    $translation_changes = FALSE;
    $translation_changes_draft = FALSE;
    foreach ($this->customFormBuilder->getFormStates() as $langcode => $form_pair) {
      if ($langcode == $this->original_lang->getId()) {
        continue;
      }
      if (!isset($this->language_selection[$langcode])) {
        continue;
      }
      list($form_object, $form_state) = $form_pair;
      if ($form_state->hasAnyErrors()) {
        $success = false;
        continue;
      }

      $saved_entity = $form_state->saved_entity;
      if (!$saved_entity) {
        //$saved_entity = $form_object->getEntity();
        $args = ['@language' => $this->languageManager->getLanguageName($langcode)];
        $this->messenger()
          ->addWarning($this->t('@language translation not saved', $args));
          continue;
      }
      if ($saved_entity->hasField('moderstation_state')) {
        $state_changed = 
          $saved_entity->_original_moderation_state != $saved_entity->moderation_state->value;
      }
      else {
        $state_changed = FALSE;
      }
      if (!$state_changed && !$saved_entity->hasTranslationChanges()) {
        continue;
      }
      if ($saved_entity->isDefaultRevision()) {
        $translation_changes = TRUE;
        if (!$entity_pack) {
          $entity_pack = $saved_entity;
        }
        else {
          if ($entity_pack->hasTranslation($langcode)) {
            $new_pack = $entity_pack->getTranslation($langcode);
          }
          else {
            $new_pack = $entity_pack->addTranslation($langcode);
          }
          foreach ($saved_entity as $fieldname => $field_items) {
            if ($field_items->getFieldDefinition()->isTranslatable()) {
              $new_pack->set($fieldname, $field_items->getValue());
            }
          }
          $entity_pack = $new_pack;
        }
      }
      else {
        $translation_changes_draft = TRUE;
        if (!$draft_pack) {
          $draft_pack = $saved_entity;
        }
        else {
          if ($draft_pack->hasTranslation($langcode)) {
            $new_pack = $draft_pack->getTranslation($langcode);
          }
          else {
            $new_pack = $draft_pack->addTranslation($langcode);
          }
          foreach ($saved_entity as $fieldname => $field_items) {
            if ($field_items->getFieldDefinition()->isTranslatable()) {
              $new_pack->set($fieldname, $field_items->getValue());
            }
          }
          $draft_pack = $new_pack;
        }
      }
    }
    if ($success) {
      if ($translation_changes && $entity_pack) {
        $entity_pack->save();
      }
      if ($translation_changes_draft && $draft_pack) {
        $draft_pack->save();
      }
      $this->messenger()->addStatus($this->t('translations saved'));
    }
    $entity = $entity_pack;
    return $success;
  }

  protected $translatable_names = [];

  protected $control_names = ['form_id', 'form_token', 'form_build_id'];

  function integrateTranslationForm(array &$original, array &$translation_form, $lang_code) {
    if (isset($translation_form['langcode'])) {
      $translation_form['langcode']['#access'] = FALSE;
    }
    
    if (!$this->translatable_names) {
      $this->setupTranslationPack($original, $translation_form);
    }
    foreach ($this->translatable_names as $key => $array_parents) {
      $element = &NestedArray::getValue($translation_form, $array_parents);
      if (!$element) {
        $this->getLogger('translations_pack')
          ->error('missing field in translation: @key', ['@key' => $key]);
        continue;
      }
      $this->childrenTitleLanguage($element, $lang_code);
      $original_pack = &NestedArray::getValue($original, $array_parents); 
      $element['#attributes']['class'][] = 'field-language-' . $lang_code;
      $element['#attributes']['data-lang-pack'] = $lang_code;
      $original_pack["{$key}_{$lang_code}"] = $element;
      if (!empty($element['#children_errors'])) {
        $this->tab_error[$lang_code] = TRUE;
      }
      unset($original_pack["{$key}_{$lang_code}"]['#groups']);
    }

    if (isset($translation_form['actions']['delete_translation'])) {
      $delete_element = &$translation_form['actions']['delete_translation'];
      $language = $this->active_languages[$lang_code];
      $args = ['@language' => $language->getName()];
      $delete_element['#title'] = new FormattableMarkup('Delete @language translation', $args);
      $original_delete = $original['actions']['delete'] ?? $original['actions']['delete_translation'];
      $delete_element['#weight'] = $original_delete['#weight'];
    }
    elseif (isset($translation_form['actions']['delete'])) {
      $delete_element = &$translation_form['actions']['delete'];
      $delete_element['#title'] =t('Delete all translations');
      $delete_element['#weight'] = $original['actions']['delete_translation']['#weight'];
    }
    if (isset($delete_element)) {
      $delete_element['#attributes']['class'][] = 'field-language-' . $lang_code;
      $delete_element['#attributes']['data-lang-pack'] = $lang_code;
      $original['actions']['delete_translation_' . $lang_code] = $delete_element; 
    }

    foreach ($this->control_names as $key) {
      $translation_form[$key]['#parents'][0] .= '_' . $lang_code;
      $translation_form[$key]['#name'] .= '_' . $lang_code;
      $original["{$key}_{$lang_code}"] = $translation_form[$key];
      unset($translation_form[$key]);
    }

    foreach (Element::children($translation_form) as $key) {
      unset($translation_form[$key]);
    }
  }


  function setupTranslationPack(array &$original, array &$translation_form) {
    foreach (Element::children($translation_form) as $key) {
      if (isset($translation_form[$key]['#groups'])
        && isset($translation_form[$key]['#groups'][$key])) {
        // this is a group
        continue;
      }
      if (isset($translation_form[$key]['#access'])
        && !($translation_form[$key]['#access'])) {
        // "hidden" field
        continue;
      }
      if (empty($translation_form[$key]['#multilingual'])
        || in_array($key, $this->control_names)
        || !isset($original[$key])
        ) {
        continue;
      }
      $element = &$this->findInChildren($original[$key], '#group');
      if (!$element) {
        $element = &$original[$key];
      }

      $this->translatable_names[$key] = $element['#array_parents'];
      $this->setupElementPack($element, $key, $original);
    }
    $this->setupDeletionPack($original);
  }

  function setupElementPack(&$element, $key, &$original) {
    $pack = [
      '#type' => 'container',
      '#attributes' => ['class' => ['translation-pack', "field-$key"]],
      '#weight' => $element['#weight'],
    ];
    $original_code = $this->original_lang->getId();
    $this->childrenTitleLanguage($element, $original_code);
    $parents = $element['#array_parents'];
    $element_index = array_pop($parents);
    $parent_element = &NestedArray::getValue($original, $parents);

    if (isset($element['#group'])) {
      $pack['#group'] = $element['#group'];
      $pack['#parents'] = $element['#parents'];
      $pack['#groups'] = &$element['#groups'];
    }

    $element['#attributes']['data-lang-pack'] = 'original';
    $element['#attributes']['class'][] = 'field-language-' . $original_code;
    $pack[$key . '_original'] = $element;
    unset($pack[$key . '_original']['#groups']);
    $pack[$key . '_original']['#attributes']['class'][] 
      = 'active';
    if (isset($pack['#groups'])) {
      $parent = $pack['#group'];
      foreach ($pack['#groups'][$parent] as $index => &$child) {
        if (!is_array($child)) {
          continue;
        }
        $child['#test_reference'] = true;
        if (isset($element['#test_reference'])) {
          $parent_element[$element_index] = $pack;
          $pack['#groups'][$parent][$index] = &$parent_element[$element_index];
        }
        unset($child['#test_reference']);
      }
    }
    else {
      $parent_element[$element_index] = $pack;
    }
  }

  function setupDeletionPack(&$original) {
    if (isset($original['actions']['delete'])) {
      $element = &$original['actions']['delete'];
      $element['#title'] = t('Delete all translations');
    }
    elseif (isset($original['actions']['delete_translation'])) {
      $element = &$original['actions']['delete_translation'];
      $args = ['@language' => $this->original_lang->getName()];
      $element['#title'] = t('Delete @language translation', $args);
    }
    else {
      return;
    }

    $original['actions']['#attributes']['class'][] = 'translation-pack';
    $original_code = $this->original_lang->getId();

    $element['#attributes']['data-lang-pack'] = 'original';
    $element['#attributes']['class'][] = 'field-language-' . $original_code;
    $element['#attributes']['class'][] = 'active';
  }

  function &findInChildren(&$element, $control, $level = 0) {
    $null = NULL;
    if ($level > 16) {
      $this->getLogger('translations_pack')->error('findInChildren recursion exceeded limit');
      return $null;
    }
    if (isset($element[$control])) {
      return $element;
    }
    $level++;
    foreach (Element::children($element) as $key) {
      $found = &$this->findInChildren($element[$key], $control, $level);
      if ($found) {
        return $found;
      }
    }
    return $null;
  }

  function childrenTitleLanguage(&$element, $lang_code, $level = 0) {
    if ($level > 16) {
      $this->getLogger('translations_pack')
        ->error('childrenTitleLanguage recursion exceeded limit');
      return;
    }
    $level++;
    $language = $this->active_languages[$lang_code];
    if (isset($element['#title'])) {
      $args = ['@title' => $element['#title'], '@language' => $language->getName()];
      $element['#title'] = new FormattableMarkup('@title (@language)', $args);
    }
    foreach (Element::children($element) as $key) {
      $this->childrenTitleLanguage($element[$key], $lang_code, $level);
    }
  }

  function markTabErrors(&$tabs) {
    foreach ($this->tab_error as $langcode => $set) {
      $tab = &$tabs['#rows'][0][$langcode];
      if (isset($tab['class'])) {
        $tab['class'][] = 'has-error';
      }
      else {
        $tab['class'] = ['has-error'];
      }
    }
  }

  protected $customFormBuilder = NULL;

  protected function entityFormBuilder() {
    if (!$this->customFormBuilder) {
      $this->customFormBuilder = new TranslationsFormBuilder(
        $this->entityTypeManager(),
        $this->formBuilder(),
        $this->moduleHandler()
      );
    }
    return $this->customFormBuilder;
  }

  /**
   * Core module assumes entity is not new, so override to add isNew check
   */
  public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) {
    $source_langcode = $source->getId();
    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
    $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());

    // Once translations from the default revision are added, there may be
    // additional draft translations that don't exist in the default revision.
    // Add those translations too so that they aren't deleted when the new
    // translation is saved.
    /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
    if (!$entity->isNew()) {
      $default_revision = $storage->load($entity->id());
      // Check the entity isn't missing any translations.
      $languages = $this->languageManager()->getLanguages();
      foreach ($languages as $language) {
        $langcode = $language->getId();
        if ($entity->hasTranslation($langcode) || $target->getId() === $langcode) {
          continue;
        }
        $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
        if ($latest_revision_id) {
          if ($default_revision->hasTranslation($langcode)) {
            $existing_translation = $default_revision->getTranslation($langcode);
            $existing_translation->setNewRevision(FALSE);
            $existing_translation->isDefaultRevision(FALSE);
            $existing_translation->setRevisionTranslationAffected(FALSE);
            $entity->addTranslation($langcode, $existing_translation->toArray());
          }
        }
      }
    }
    /** @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */
    $source_translation = $entity->getTranslation($source_langcode);
    $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray());

    // Make sure we do not inherit the affected status from the source values.
    if ($entity->getEntityType()->isRevisionable()) {
      $target_translation->setRevisionTranslationAffected(NULL);
    }

    /** @var \Drupal\user\UserInterface $user */
    $user = $this->entityTypeManager()->getStorage('user')->load($this->currentUser()->id());
    $metadata = $this->manager->getTranslationMetadata($target_translation);

    // Update the translation author to current user, as well the translation
    // creation time.
    $metadata->setAuthor($user);
    $metadata->setCreatedTime(REQUEST_TIME);
    $metadata->setSource($source_langcode);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc