tmgmt_xtm-8.x-5.x-dev/src/Plugin/tmgmt/Translator/Cart.php

src/Plugin/tmgmt/Translator/Cart.php
<?php

namespace Drupal\tmgmt_xtm\Plugin\tmgmt\Translator;

use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\tmgmt\Entity\Job;
use Drupal\tmgmt\Entity\JobItem;
use Drupal\tmgmt\Entity\Translator;
use Masterminds\HTML5\Exception;

/**
 * Provides operations for managing translation jobs.
 *
 * @package Drupal\tmgmt_xtm\Plugin\tmgmt\Translator
 */
class Cart {

  /**
   * Requests an operation for multiple translation jobs.
   *
   * @param array $job
   *   The job to be processed.
   * @param array $jobItems
   *   The items associated with the job.
   * @param array $context
   *   The context for the operation.
   */
  public function requestOperationMultiple(array $job, array $jobItems, array &$context) {
    $translator = $this->getTranslator($job['translator']);
    if (is_null($translator)) {
      \Drupal::messenger()->addStatus(t('No translator available.'));
      return;
    }
    $helper = new Helper();
    $jobItemsBySourceLanguage = $this->groupSelectedItemsBySourceLanguage($jobItems, $job);
    $targetLanguages = $job['target_languages'];
    $jobs = $jobResults = $removeJobItemIds = [];
    foreach ($targetLanguages as $targetLanguage) {
      foreach ($jobItemsBySourceLanguage as $sourceLanguage => $job_items) {
        if ($sourceLanguage == $targetLanguage) {
          continue;
        }
        $jobModel = tmgmt_job_create(
          $sourceLanguage,
          $targetLanguage,
          \Drupal::currentUser()->id()
        );
        $jobEmptyFlag = TRUE;
        foreach ($job_items as $id => $jobItem) {
          /** @var \Drupal\tmgmt\Entity\JobItem $jobItem */
          try {
            $jobModel->addItem($jobItem->getPlugin(), $jobItem->getItemType(), $jobItem->getItemId());
            $jobItemId = $jobItem->id();
            $removeJobItemIds[$jobItemId] = $jobItemId;
            $jobResults['job_items'][$jobItemId] = $jobItemId;
            $jobEmptyFlag = FALSE;
          }
          catch (\Exception $e) {
            unset($jobItemsBySourceLanguage[$sourceLanguage][$id]);
            \Drupal::messenger()->addError($e->getMessage());
          }
        }
        if ($job['enforced_source_language']) {
          $jobModel->set('source_language', $job['forced_source_language']);
        }
        $this->createJobs($job, $jobEmptyFlag, $jobModel, $jobs);
      }
      $this->removeJobItemsFromCart($removeJobItemIds);
    }

    if (!$jobs) {
      \Drupal::messenger()->addStatus(t('From the selection you made it was not possible to create any translation job..'));
      return;
    }

    if ($job['enforced_source_language']) {
      $this->createDrupalMessageOnEnforceSourceLanguage();
      $this->createSkippedCountMessage(0, $job['enforced_source_language']);
    }

    $connector = new MultipleTargetLanguageConnector();
    $connector->xtmRequestTranslation($jobs);

    $context['results'][] = $jobResults;
    $context['message'] = t('Creating a project for the langauge "@name".',
      ['@name' => $helper->mapLanguageToXTMFormat($job['source_language'], $translator)]);

    \Drupal::messenger()->addStatus(t('The Translations request has been submitted successfully.'));
  }

  /**
   * Retrieves a translator entity by its name.
   *
   * This method searches through the available translators and returns the
   * translator that matches the provided name.
   *
   * @param string $name
   *   The name of the translator to retrieve.
   *
   * @return \Drupal\tmgmt\Entity\Translator|null
   *   The translator entity if found, NULL if no matching translator found.
   */
  private function getTranslator($name = "") {
    $translator = NULL;
    foreach (tmgmt_translator_load_available(NULL) as $translator) {
      /** @var \Drupal\tmgmt\Entity\Translator $translator */
      if ($translator->id() == $name) {
        break;
      }
    }
    return $translator;
  }

  /**
   * Groups job items by their source language.
   *
   * @param array $jobItems
   *   An array of job items to be grouped.
   * @param array $job
   *   An array containing job-related information, including the enforced source language.
   *
   * @return array
   *   An associative array where the keys are source languages and the values are arrays of job items.
   */
  private function groupSelectedItemsBySourceLanguage(array $jobItems, array $job) {
    $enforcedSourceLanguage = NULL;
    if ($job['enforced_source_language']) {
      $enforcedSourceLanguage = $job['forced_source_language'];
    }
    $skippedCount = 0;
    $jobItemsBySourceLanguage = [];

    foreach ($jobItems as $jobItem) {
      $sourceLanguage = $enforcedSourceLanguage ?: $jobItem->getSourceLangCode();

      /** @var \Drupal\tmgmt\Entity\JobItem $jobItem */
      if (in_array($sourceLanguage, $jobItem->getExistingLangCodes())) {
        $jobItemsBySourceLanguage[$sourceLanguage][$jobItem->id()] = $jobItem;
      }
      else {
        $skippedCount++;
      }
    }

    if ($skippedCount > 0) {
      $languages = \Drupal::languageManager()->getLanguages();
      \Drupal::messenger()->addWarning(\Drupal::translation()->formatPlural(
        $skippedCount,
        'One item skipped as for the language @language it was not possible to retrieve a translation.',
        '@count items skipped as for the language @language it was not possible to retrieve a translations.',
        ['@language' => $languages[$enforcedSourceLanguage]->getName()]
      ));
    }

    return $jobItemsBySourceLanguage;
  }

  /**
   * Creates job instances based on the provided job data and adds them to the list of jobs.
   *
   * @param array $job
   *   An array containing job details including translator, template, and project mode.
   * @param bool $jobEmpty
   *   Indicates whether the job is empty or not.
   * @param Job $jobModel
   *   The job model entity that will be populated and saved.
   * @param array $jobs
   *   A reference to an array where created job instances will be added.
   *
   * @return MessengerInterface|void
   * @throws EntityStorageException
   */
  private function createJobs(array &$job, bool $jobEmpty, Job $jobModel, array &$jobs) {
    if ($jobEmpty) {
      return;
    }
    $translator = $this->getTranslator($job['translator']);
    $jobModel->set('translator', $translator);
    $settings = [];
    if (isset($job['template'])) {
      $settings[Connector::API_TEMPLATE_ID] = $job['template'];
    }

    if (isset($job['project_mode'])) {
      $settings[Connector::API_PROJECT_MODE] = $job['project_mode'];
    }
    $jobModel->set('settings', $settings);
    $translatable = $jobModel->canRequestTranslation();

    if (!$translatable->getSuccess()) {
      return \Drupal::messenger()->addWarning($translatable->getReason());
    }

    $jobModel->save();
    $jobs[] = $jobModel;
  }

  /**
   * Removes job items from the cart and deletes them from storage.
   *
   * @param array $removeJobItemIds
   *   An array of job item IDs to be removed and deleted.
   *
   * @return void
   */
  private function removeJobItemsFromCart(array $removeJobItemIds) {
    if ($removeJobItemIds) {
      tmgmt_cart_get()->removeJobItems($removeJobItemIds);
      $storage_handler = \Drupal::entityTypeManager()
        ->getStorage('tmgmt_job_item');
      $entities = $storage_handler->loadMultiple($removeJobItemIds);
      $storage_handler->delete($entities);
    }
  }

  /**
   * Creates a warning message about the enforced source language.
   *
   * @return void
   */
  private function createDrupalMessageOnEnforceSourceLanguage() {
    \Drupal::messenger()->addWarning(
      t('You have enforced the job source language which most likely resulted in having a
      translation of your original content as the job source text. You should review the job translation
      received from the translator carefully to prevent content quality loss.')
    );
  }

  /**
   * Creates an error message for skipped items based on the count and source language.
   *
   * @param int $skippedCount
   *   The number of items skipped due to the language not being available for translation.
   * @param string $enforcedSourceLanguage
   *   The source language code that was enforced for translation.
   *
   * @return void
   *   This method does not return a value.
   */
  private function createSkippedCountMessage(int $skippedCount, string $enforcedSourceLanguage) {
    if ($skippedCount) {
      $languages = \Drupal::languageManager()->getLanguages();
      \Drupal::messenger()->addError(\Drupal::translation()->formatPlural(
        $skippedCount,
        'One item skipped as for the language @language it was not possible to retrieve a translation.',
        '@count items skipped as for the language @language it was not possible to retrieve a translations.',
        ['@language' => $languages[$enforcedSourceLanguage]->getName()]
      ));
    }
  }

  /**
   * Handles the operation request by creating translation jobs and updating the cart.
   *
   * @param array $job
   *   An associative array containing job-related data. Keys include:
   *   - 'translator': The translator ID.
   *   - 'target_language': The target language code for the translation.
   *   - 'source_language': The source language code for the translation.
   *   - 'enforced_source_language': The source language code enforced for the translation (if any).
   * @param array $jobItems
   *   An array of job items to be processed. Each item is an instance of `\Drupal\tmgmt\Entity\JobItem`.
   * @param array &$context
   *   An array used to pass data between different parts of the system. This method adds job results and messages to this array.
   *
   * @return void
   *   It updates the context and messages directly.
   */
  public function requestOperation(array $job, array $jobItems, array &$context) {
    $translator = $this->getTranslator($job['translator']);
    if (is_null($translator)) {
      \Drupal::messenger()->addStatus(t('No translator available.'));
      return;
    }

    $helper = new Helper();
    $jobItemsBySourceLanguage = $this->groupSelectedItemsBySourceLanguage($jobItems, $job);
    $targetLanguage = $job['target_language'];
    $jobs = $jobResults = $removeJobItemIds = [];

    foreach ($jobItemsBySourceLanguage as $sourceLanguage => $job_items) {
      if ($sourceLanguage == $targetLanguage) {
        continue;
      }

      $jobModel = tmgmt_job_create($sourceLanguage, $targetLanguage, \Drupal::currentUser()->id());
      $jobEmptyFlag = TRUE;

      foreach ($job_items as $id => $jobItem) {
        /** @var \Drupal\tmgmt\Entity\JobItem $jobItem */
        try {
          $jobModel->addItem($jobItem->getPlugin(), $jobItem->getItemType(), $jobItem->getItemId());
          $jobItemId = $jobItem->id();
          $removeJobItemIds[$jobItemId] = $jobItemId;
          $jobResults['job_items'][$jobItemId] = $jobItemId;
          $jobEmptyFlag = FALSE;
        }
        catch (Exception $e) {
          unset($jobItemsBySourceLanguage[$sourceLanguage][$id]);
          \Drupal::messenger()->addError($e->getMessage());
        }
      }
      $this->createJobs($job, $jobEmptyFlag, $jobModel, $jobs);
    }

    $this->removeJobItemsFromCart($removeJobItemIds);

    if (!$job) {
      \Drupal::messenger()->addStatus(t('From the selection you made it was not possible to create any translation job.'));
      return;
    }

    if ($job['enforced_source_language']) {
      $this->createDrupalMessageOnEnforceSourceLanguage();
      $this->createSkippedCountMessage(0, $job['enforced_source_language']);
    }

    $this->doXtmTranslation($jobs);

    $context['results'][] = $jobResults;
    $context['message'] = t(
      'Creating a project for the language "@name".',
      ['@name' => $helper->mapLanguageToXTMFormat($job['source_language'], $translator)]
    );

    \Drupal::messenger()->addStatus(t('The Translations request has been submitted successfully.'));
  }

  /**
   * Processes translation requests by sending them to the XTM connector.
   *
   * @param array $jobs
   *   An array of jobs to be processed. Each job should be structured according to the requirements of the XTM connector.
   *
   * @return void
   *   It performs actions to send translation requests.
   */
  private function doXtmTranslation(array $jobs) {
    $connector = new Connector();
    foreach ($jobs as $job) {
      $connector->xtmRequestTranslation($job);
    }
  }

  /**
   * Processes form submission for creating translation projects.
   *
   * This method handles the form submission, organizes job items by their source language,
   * and prepares batch operations for creating translation projects based on the provided settings.
   *
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The form state object containing the submitted values and other form-related data.
   *
   * @return void
   *   It sets up a batch process for creating projects.
   */
  public function formSubmit(FormStateInterface $formState) {
    $jobItemsBySourceLanguage = [];
    $jobItemsLoad = JobItem::loadMultiple(array_filter($formState->getValue('items')));

    foreach ($jobItemsLoad as $jobItem) {
      /** @var \Drupal\tmgmt\Entity\JobItem $jobItem */
      $jobItemsBySourceLanguage[$jobItem->getSourceLangCode()][$jobItem->id()] = $jobItem;
    }

    $multiLang = $formState->getValue('multi_lang');
    $translator = $this->getTranslator($multiLang['xtm_translator']);
    if (is_null($translator)) {
      return;
    }
    $multipleLanguageXtmProject = $translator->getSetting('multiple_language_xtm_project');
    $operations = [];
    if (1 === $multipleLanguageXtmProject) {
      foreach ($jobItemsBySourceLanguage as $sourceLanguage => $jobItems) {
        $operations = $this->prepareMultipleOperations(
          $formState,
          $sourceLanguage,
          array_filter($formState->getValue('target_language')),
          $jobItems,
          $operations
        );
      }
    }
    else {
      foreach (array_filter($formState->getValue('target_language')) as $targetLanguage) {
        foreach ($jobItemsBySourceLanguage as $sourceLanguage => $jobItems) {
          if ($sourceLanguage == $targetLanguage) {
            continue;
          }
          $operations = $this->prepareOperations(
            $formState,
            $sourceLanguage,
            $targetLanguage,
            $jobItems,
            $operations
          );
        }
      }
    }

    $batch = [
      'operations' => $operations,
      'finished' => 'tmgmt_xtm_cart_request_operations_finished',
      'title' => t('Creating multiple projects'),
      'progress_message' => t('Processed @current out of @total projects.'),
      'error_message' => t('XTM translator has encountered an error.'),
    ];

    batch_set($batch);
  }

  /**
   * Prepares batch operations for creating jobs in multiple target language XTM projects.
   *
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The form state object containing the submitted values and other form-related data.
   * @param string $sourceLanguage
   *   The source language code.
   * @param array $targetLanguage
   *   An array of target language codes.
   * @param array $jobItems
   *   An array of job items to be included in the operations.
   * @param array $operations
   *   The existing array of operations to be updated with new operations.
   *
   * @return array
   *   The updated array of operations including the new operations for multiple target languages.
   */
  private function prepareMultipleOperations(
    FormStateInterface $formState,
    $sourceLanguage,
    $targetLanguage,
    $jobItems,
    $operations,
  ) {
    $multiLang = $formState->getValue('multi_lang');

    if (!isset($multiLang['settings_fieldset']['template'])) {
      $multiLang['settings_fieldset']['template'] = "";
    }

    $enforcedSourceLanguage = $formState->getValue('enforced_source_language');
    $forcedSourceLanguage = $formState->getValue('enforced_source_language')
      ? $formState->getValue('source_language') : FALSE;
    $operations[] = [
      'tmgmt_xtm_cart_request_operation_multiple',
      [
        [
          'source_language' => $sourceLanguage,
          'target_languages' => $targetLanguage,
          'user_id' => \Drupal::currentUser()->id(),
          'translator' => $multiLang['xtm_translator'],
          'template' => $multiLang['settings_fieldset']['template'],
          'project_mode' => $multiLang['settings_fieldset']['project_mode'],
          'enforced_source_language' => $enforcedSourceLanguage,
          'forced_source_language' => $forcedSourceLanguage,
        ],
        $jobItems,
      ],
    ];
    return $operations;
  }

  /**
   * Prepares batch operations for creating a job in a single target language XTM project.
   *
   * @param \Drupal\Core\Form\FormStateInterface $formState
   *   The form state object containing the submitted values and other form-related data.
   * @param string $sourceLanguage
   *   The source language code.
   * @param string $targetLanguage
   *   The target language code.
   * @param \Drupal\tmgmt\Entity\JobItem $jobItems
   *   An array of job items to be included in the operation.
   * @param array $operations
   *   The existing array of operations to be updated with a new operation.
   *
   * @return array
   *   The updated array of operations including the new operation for a single target language.
   */
  private function prepareOperations(
    FormStateInterface $formState,
    $sourceLanguage,
    $targetLanguage,
    $jobItems,
    $operations,
  ) {
    $multiLang = $formState->getValue('multi_lang');

    if (!isset($multiLang['settings_fieldset']['template'])) {
      $multiLang['settings_fieldset']['template'] = "";
    }
    $forcedSourceLanguage = $formState->getValue('enforced_source_language')
      ? $formState->getValue('source_language') : FALSE;

    $operations[] = [
      'tmgmt_xtm_cart_request_operation',
      [
        [
          'source_language' => $sourceLanguage,
          'target_language' => $targetLanguage,
          'user_id' => \Drupal::currentUser()->id(),
          'translator' => $multiLang['xtm_translator'],
          'template' => $multiLang['settings_fieldset']['template'],
          'project_mode' => $multiLang['settings_fieldset']['project_mode'],
          'enforced_source_language' => $formState->getValue('enforced_source_language'),
          'forced_source_language' => $forcedSourceLanguage,

        ],
        $jobItems,
      ],
    ];
    return $operations;
  }

  /**
   * Creates the cart content templates form for XTM projects.
   *
   * @param array $form
   *   The form array to be updated.
   * @param \Drupal\Core\Form\FormState $formState
   *   The form state object.
   *
   * @return array
   *   The updated form array with cart content templates.
   */
  public function getCartContentTemplates(array &$form, FormState $formState) {
    $multiLang = $formState->getValue('multi_lang');
    $translator = $this->getTranslator($multiLang['xtm_translator']);

    if ($translator instanceof Translator) {
      $form['multi_lang']['settings_fieldset']['template'] = $this->getTemplateForm($translator);
    }

    return $form['multi_lang']['settings_fieldset'];
  }

  /**
   * Generates the template form for the XTM translator.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The XTM translator entity.
   *
   * @return array
   *   The render array for the template form.
   */
  private function getTemplateForm(Translator $translator) {
    $connector = new Connector();
    $templates = $connector->getTemplates($translator);

    if (empty($templates)) {
      return [
        '#markup' => t('<p>There are no templates for the selected translator.</p>'),
      ];
    }
    else {
      return [
        '#type' => 'select',
        '#title' => t('XTM project template'),
        '#default_value' => NULL,
        '#description' => t('Select an XTM template for each project.'),
        '#options' => ['' => t('none')] + $templates,
        '#name' => 'multi_lang[settings_fieldset][template]',
        '#id' => 'edit-multi-lang-settings-fieldset-template',
        '#validated' => TRUE,
      ];
    }
  }

  /**
   * Builds the form for XTM one-click multilingual projects.
   *
   * @param array $form
   *   The form array to be updated.
   *
   * @return void
   *   Returns Cart Form for XTM one-click multilingual projects.
   */
  public function getForm(array &$form) {
    $availableTranslators = [];
    $translator = NULL;
    $translators = $this->getXtmTranslators();
    foreach ($translators as $translator) {
      /** @var \Drupal\tmgmt\Entity\Translator $translator */
      $translatorId = $translator->id();
      $availableTranslators[$translatorId] = $translator->label();
    }

    if (empty($availableTranslators)) {
      return;
    }

    $index = array_search('target_language', array_keys($form)) + 1;
    $form = array_slice($form, 0, $index, TRUE) + ['multi_lang' => []] + array_slice(
        $form,
        $index,
        count($form) - $index,
        TRUE
      );
    $this->createMultiLangForm($form, $availableTranslators);
    if ($translator instanceof Translator) {
      $form['multi_lang']['settings_fieldset']['template'] = $this->getTemplateForm($translator);
      $form['multi_lang']['settings_fieldset']['project_mode'] = $this->getProjectModeForm($translator);
    }

    $form['multi_lang']['request_multiple_translations'] = [
      '#type' => 'submit',
      '#value' => t('Request multiple translations'),
      '#submit' => ['tmgmt_xtm_cart_request_translation_form_submit'],
      '#validate' => ['tmgmt_xtm_cart_source_overview_validate'],
    ];
  }

  /**
   * Retrieves available XTM translators.
   *
   * @return array
   *   An array of XTM translator entities.
   */
  private function getXtmTranslators() {
    $translators = [];
    foreach (tmgmt_translator_load_available(NULL) as $translator) {
      /** @var \Drupal\tmgmt\Entity\Translator $translator */
      if ($translator->getPluginId() == 'xtm') {
        $translators[] = $translator;
      }
    }
    return $translators;
  }

  /**
   * Creates the form for selecting multiple language settings.
   *
   * @param array $form
   *   The form array to be updated.
   * @param array $availableTranslators
   *   An array of available XTM translators.
   *
   * @return void
   */
  private function createMultiLangForm(array &$form, array $availableTranslators) {
    $form['multi_lang'] = [
      '#tree' => TRUE,
      '#type' => 'fieldset',
      '#title' => t('XTM one-click multilingual projects'),
      '#description' => t('Request multiple translations with one click, skipping the Job overview settings.
            Ensure to activate the option <b>Enable multilingual projects in XTM</b> in the <b>XTM Plugin settings</b> to include all target languages in the same XTM Cloud project.'),
    ];

    $form['multi_lang']['xtm_translator'] = [
      '#type' => 'select',
      '#title' => t('XTM translator'),
      '#default_value' => NULL,
      '#description' => t('Select a translator.'),
      '#options' => ['' => t('- Please choose -')] + $availableTranslators,
      '#ajax' => [
        'wrapper' => 'templates-fieldset-wrapper',
        'callback' => 'ajax_tmgmt_xtm_cart_content_templates',
        'progress' => [
          'type' => 'throbber',
          'message' => t('Please wait, searching for available templates...'),
        ],
      ],
    ];

    $form['multi_lang']['settings_fieldset'] = [
      '#type' => 'container',
      '#prefix' => '<div id="templates-fieldset-wrapper">',
      '#suffix' => '</div>',
    ];
  }

  /**
   * Retrieves the project mode options form for the XTM translator.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The XTM translator entity.
   *
   * @return array
   *   The render array for the project mode form.
   */
  private function getProjectModeForm(Translator $translator) {
    $helper = new Helper();
    return [
      '#type' => 'radios',
      '#title' => t('Project mode'),
      '#default_value' => !is_null($projectMode = $translator->getSetting('default_project_mode'))
        ? $projectMode : 0,
      '#options' => $helper->getProjectModes(),
    ];
  }

}

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

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