tmgmt_smartling-8.x-4.11/src/Plugin/tmgmt/Translator/SmartlingTranslator.php

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

/**
 * @file
 * Contains \Drupal\tmgmt_smartling\Plugin\tmgmt\Translator\SmartlingTranslator.
 */

namespace Drupal\tmgmt_smartling\Plugin\tmgmt\Translator;

use Drupal;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Drupal\file\FileUsage\DatabaseFileUsageBackend;
use Drupal\tmgmt\ContinuousTranslatorInterface;
use Drupal\tmgmt\Data;
use Drupal\tmgmt\Entity\JobItem;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\tmgmt\Translator\AvailableResult;
use Drupal\tmgmt\Translator\TranslatableResult;
use Drupal\tmgmt\TranslatorInterface;
use Drupal\tmgmt\TranslatorPluginBase;
use Drupal\tmgmt_extension_suit\ExtendedTranslatorPluginInterface;
use Drupal\tmgmt_extension_suit\Utils\FlowScheduler;
use Drupal\tmgmt_file\Format\FormatManager;
use Drupal\tmgmt_smartling\Event\RequestTranslationEvent;
use Drupal\tmgmt_smartling\Smartling\ConnectorInfo;
use Drupal\tmgmt_smartling\Smartling\SmartlingApiWrapper;
use Drupal\tmgmt_smartling\Smartling\Submission\TranslationRequestManager;
use Exception;
use GuzzleHttp\ClientInterface;
use Psr\Log\LoggerInterface;
use Smartling\AuditLog\Params\CreateRecordParameters;
use Smartling\BaseApiAbstract;
use Smartling\File\Params\DownloadFileParameters;
use Smartling\File\Params\UploadFileParameters;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Smartling translator plugin.
 *
 * @TranslatorPlugin(
 *   id = "smartling",
 *   label = @Translation("Smartling translator"),
 *   description = @Translation("Smartling Translator service."),
 *   ui = "Drupal\tmgmt_smartling\SmartlingTranslatorUi",
 *   files = TRUE,
 * )
 */
class SmartlingTranslator extends TranslatorPluginBase implements
  ExtendedTranslatorPluginInterface,
  ContainerFactoryPluginInterface,
  ContinuousTranslatorInterface {

  /**
   * @var \GuzzleHttp\ClientInterface
   */
  protected $client;

  /**
   * @var \Drupal\tmgmt_file\Format\FormatManager
   */
  protected $formatPluginsManager;

  /**
   * @var \Drupal\file\FileUsage\DatabaseFileUsageBackend
   */
  protected $fileUsage;

  /**
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * @var LoggerInterface
   */
  private $logger;

  /**
   * @var SmartlingApiWrapper
   */
  private $smartlingApiWrapper;

  /**
   * @var TranslationRequestManager
   */
  private $translationRequestManager;

  /**
   * @var ModuleHandlerInterface
   */
  protected $moduleHandler;

  protected $currentUser;

  protected $fileUrlGenerator;

  /**
   * @var Data
   */
  protected $dataService;

  /**
   * @var FlowScheduler
   */
  protected $flowScheduler;

  /**
   * Constructs a LocalActionBase object.
   *
   * @param \GuzzleHttp\ClientInterface $client
   *   The Guzzle HTTP client.
   * @param \Drupal\tmgmt_file\Format\FormatManager $format_plugin_manager
   * @param \Drupal\file\FileUsage\DatabaseFileUsageBackend $file_usage
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param array $plugin_definition
   *   The plugin implementation definition.
   * @param \Psr\Log\LoggerInterface $logger
   * @param \Drupal\tmgmt_smartling\Smartling\SmartlingApiWrapper $api_wrapper
   * @param \Drupal\tmgmt_smartling\Smartling\Submission\TranslationRequestManager $translation_request_manager
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   */
  public function __construct(
    ClientInterface $client,
    FormatManager $format_plugin_manager,
    DatabaseFileUsageBackend $file_usage,
    EventDispatcherInterface $event_dispatcher,
    array $configuration,
    $plugin_id,
    array $plugin_definition,
    LoggerInterface $logger,
    SmartlingApiWrapper $api_wrapper,
    TranslationRequestManager $translation_request_manager,
    ModuleHandlerInterface $module_handler,
    AccountProxyInterface $current_user,
    FileUrlGeneratorInterface $file_url_generator,
    Data $data_service,
    FlowScheduler $flow_scheduler
  ) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->client = $client;
    $this->formatPluginsManager = $format_plugin_manager;
    $this->fileUsage = $file_usage;
    $this->eventDispatcher = $event_dispatcher;
    $this->logger = $logger;
    $this->smartlingApiWrapper = $api_wrapper;
    $this->translationRequestManager = $translation_request_manager;
    $this->moduleHandler = $module_handler;
    $this->currentUser = $current_user;
    $this->fileUrlGenerator = $file_url_generator;
    $this->dataService = $data_service;
    $this->flowScheduler = $flow_scheduler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $container->get('http_client'),
      $container->get('plugin.manager.tmgmt_file.format'),
      $container->get('file.usage'),
      $container->get('event_dispatcher'),
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('logger.channel.smartling'),
      $container->get('tmgmt_smartling.smartling_api_wrapper'),
      $container->get('tmgmt_smartling.translation_request_manager'),
      $container->get('module_handler'),
      $container->get('current_user'),
      $container->get('file_url_generator'),
      $container->get('tmgmt.data'),
      $container->get('tmgmt_extension_suit.utils.flow_scheduler')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function checkAvailable(TranslatorInterface $translator) {
    if ($translator->getSetting('user_id') &&
      $translator->getSetting('token_secret') &&
      $translator->getSetting('project_id')
    ) {
      return AvailableResult::yes();
    }
    return AvailableResult::no(t('@translator is not available. Make sure it is properly <a href=:configured>configured</a>.', [
      '@translator' => $translator->label(),
      ':configured' => $translator->toUrl()->toString(),
     ]));
  }

  /**
   * {@inheritdoc}
   */
  public function checkTranslatable(TranslatorInterface $translator, JobInterface $job) {
    // Anything can be exported.
    return TranslatableResult::yes();
  }

  /**
   * Returns callback url.
   *
   * Host value can be overridden by value defined in translator settings.
   *
   * @param JobInterface $job
   *
   * @return Drupal\Core\GeneratedUrl|string
   */
  private function getCallbackUrl(JobInterface $job) {
    $callback_url = Url::fromRoute('tmgmt_smartling.push_callback', ['job' => $job->id()])->setOptions(['absolute' => TRUE])->toString();
    $relative_callback_url = Url::fromRoute('tmgmt_smartling.push_callback', ['job' => $job->id()])->toString();
    $callback_url_host = rtrim($job->getTranslator()->getSetting('callback_url_host'), '/');

    if (!empty($callback_url_host)) {
      $callback_url = Url::fromUserInput($relative_callback_url, [
        'base_url' => $callback_url_host,
      ])->toString();
    }

    return $callback_url;
  }

  /**
   * Returns callback url for attachment.
   *
   * Host value can be overridden by value defined in translator settings.
   *
   * @param JobInterface $job
   * @param FileInterface $file
   *
   * @return Drupal\Core\GeneratedUrl|string
   */
  private function getCallbackAttachmentUrl(JobInterface $job, FileInterface $file) {
    $params = [
      'job' => $job->id(),
      'file' => $file->id(),
    ];

    $callback_url = Url::fromRoute('tmgmt_smartling.push_callback_attachment', $params)->setOptions(['absolute' => TRUE])->toString();
    $relative_callback_url = Url::fromRoute('tmgmt_smartling.push_callback_attachment', $params)->toString();
    $callback_url_host = rtrim($job->getTranslator()->getSetting('callback_url_host'), '/');

    if (!empty($callback_url_host)) {
      $callback_url = Url::fromUserInput($relative_callback_url, [
        'base_url' => $callback_url_host,
      ])->toString();
    }

    return $callback_url;
  }

  /**
   * @param array $settings
   * @return \Drupal\tmgmt_smartling\Smartling\SmartlingApiWrapper
   */
  public function getApiWrapper(array $settings) {
    ConnectorInfo::setUpCurrentClientInfo();

    $this->smartlingApiWrapper->setSettings($settings);

    return $this->smartlingApiWrapper;
  }

  /**
   * {@inheritdoc}
   */
  public function requestTranslation(JobInterface $job) {
    $batch_uid = $job->getSetting('batch_uid');
    $api_wrapper = $this->getApiWrapper($job->getTranslator()->getSettings());
    $error_notification_message = t('File @name (job id = @job_id) wasn\'t uploaded. Please see logs for more info.', [
      '@name' => $this->getFileName($job),
      '@job_id' => $job->id(),
    ])->render();

    $api_wrapper->createAuditLogRecord(
      $job,
      NULL,
      $this->currentUser,
      CreateRecordParameters::ACTION_TYPE_UPLOAD
    );

    // Skip processing if job/batch hasn't been created.
    if (empty($batch_uid)) {
      $this->logger->error(t('File @name (job id = @job_id) wasn\'t uploaded due to previous error(s).', [
        '@name' => $this->getFileName($job),
        '@job_id' => $job->id(),
      ])->render());

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => $error_notification_message,
        "type" => "error",
      ]);

      return;
    }

    $translation_request = $this->translationRequestManager->upsertTranslationRequest($job);

    if (empty($translation_request)) {
      $this->logger->error('Can\'t upsert translation request for file @name (job id = @job_id).', [
        '@name' => $this->getFileName($job),
        '@job_id' => $job->id(),
      ]);

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => 'Can\'t upsert translation request. ' . $error_notification_message,
        "type" => "error",
      ]);

      return;
    }

    $name = $this->getFileName($job);
    $export_format = pathinfo($name, PATHINFO_EXTENSION);
    $export = $this->formatPluginsManager->createInstance($export_format);
    $path = $job->getSetting('scheme') . '://tmgmt_sources/' . $name;
    $dirname = dirname($path);

    if (\Drupal::service('file_system')->prepareDirectory($dirname, FileSystemInterface::CREATE_DIRECTORY)) {
      $data = $export->export($job);
      $file = \Drupal::service('file.repository')->writeData($data, $path, FileSystemInterface::EXISTS_REPLACE);
      $this->fileUsage->add($file, 'tmgmt_smartling', 'tmgmt_job', $job->id());
      $job->submitted('Exported file can be downloaded <a href="@link">here</a>.', array('@link' => $this->fileUrlGenerator->generateAbsoluteString($path)));
    }
    else {
      $e = new \Exception('It is not possible to create a directory ' . $dirname);

      if (class_exists(\Drupal\Component\Utility\DeprecationHelper::class)) {
        \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => \Drupal\Core\Utility\Error::logException(\Drupal::logger('tmgmt_smartling'), $e), fn() => watchdog_exception('tmgmt_smartling', $e));
      } else {
        watchdog_exception('tmgmt_smartling', $e);
      }

      $job->rejected('Job has been rejected with following error: @error',
        ['@error' => $e->getMessage()], 'error');

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => $error_notification_message,
        "type" => "error",
      ]);

      $this->translationRequestManager->commitError($job, $translation_request, $e);

      return;
    }

    try {
      $upload_params = new UploadFileParameters();
      $upload_params->setClientLibId(BaseApiAbstract::getCurrentClientId(), BaseApiAbstract::getCurrentClientVersion());
      $upload_params->setAuthorized(0);

      if ($job->getTranslator()->getSetting('callback_url_use')) {
        $upload_params->set('callbackUrl', $this->getCallbackUrl($job));
      }

      $file_type = $export_format === 'xlf' ? 'xliff' : $export_format;
      $upload_params->setLocalesToApprove($job->getRemoteTargetLanguage());

      $api_wrapper->getApi('batch')->uploadBatchFile(
        $file->getFileUri(),
        $file->getFilename(),
        $file_type,
        $batch_uid,
        $this->addSmartlingDirectives($upload_params, $job)
      );

      $message = t('File uploaded. Job id: @job_id, file name: @name.', [
        '@name' => $this->getFileName($job),
        '@job_id' => $job->id(),
      ]);

      $this->logger->info($message);

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => $message->render(),
        "type" => "status",
      ]);

      // Upload attachments through the same batch.
      // TODO: maybe upload through the queue?
      if (method_exists($this->dataService, 'getTranslatableFiles')) {
        foreach ($job->getItems() as $item) {
          foreach ($this->dataService->getTranslatableFiles($item->getData()) as $file) {
            $this->requestTranslationAttachment($job, $file);
          }
        }
      }

      if ($job->id() == $job->getSetting('batch_execute_on_job')) {
        $api_wrapper->executeBatch($batch_uid);

        $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
          "message" => t("Finished: content is in the job. You may need to wait a few seconds before content is authorized (if you checked 'authorize' checkbox).")
            ->render(),
          "type" => "status",
        ]);
      }

      $this->eventDispatcher->dispatch(new RequestTranslationEvent($job), RequestTranslationEvent::REQUEST_TRANSLATION_EVENT);

      if (!$this->translationRequestManager->commitSuccessfulUpload($job, $translation_request)) {
        $warning_message = 'Can\'t update submitted date for translation request = @translation_request.';
        $warning_message_context = [
          '@translation_request' => json_encode($translation_request),
        ];

        $this->logger->warning($warning_message, $warning_message_context);

        $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
          "message" => 'Can\'t update submitted date for translation request. See logs for more info.',
          "type" => "warning",
        ]);
      }
    }
    catch (Exception $e) {
      if (class_exists(\Drupal\Component\Utility\DeprecationHelper::class)) {
        \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => \Drupal\Core\Utility\Error::logException(\Drupal::logger('tmgmt_smartling'), $e), fn() => watchdog_exception('tmgmt_smartling', $e));
      } else {
        watchdog_exception('tmgmt_smartling', $e);
      }

      $job->rejected('Job has been rejected with following error: @error uploading @file', [
        '@error' => $e->getMessage(),
        '@file' => $file->getFileUri()
      ], 'error');

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => t('Error while uploading @file. Please see logs for more info.', [
          '@file' => $file->getFileUri()
        ])->render(),
        "type" => "error",
      ]);

      $this->translationRequestManager->commitError($job, $translation_request, $e);
    }
    // @todo disallow to submit translation to unsupported language.
  }

  /**
   * {@inheritdoc}
   */
  public function requestTranslationAttachment(JobInterface $job, FileInterface $file) {
    $api_wrapper = $this->getApiWrapper($job->getTranslator()->getSettings());

    try {
      $translation_request = $this->translationRequestManager->upsertTranslationRequest($job, $file);
      $attachment_file_name = $this->getAttachmentFileName($job, $file);

      try {
        $upload_params = new UploadFileParameters();
        $upload_params->setClientLibId(BaseApiAbstract::getCurrentClientId(), BaseApiAbstract::getCurrentClientVersion());
        $upload_params->setAuthorized(0);

        if ($job->getTranslator()->getSetting('callback_url_use')) {
          $upload_params->set('callbackUrl', $this->getCallbackAttachmentUrl($job, $file));
        }

        $upload_params->setLocalesToApprove($job->getRemoteTargetLanguage());

        $this->smartlingApiWrapper->getApi('batch')->uploadBatchFile(
          $file->getFileUri(),
          $attachment_file_name,
          $this->mapFileExtensionToFileType(
            pathinfo($file->getFileUri(), PATHINFO_EXTENSION)
          ),
          $job->getSetting('batch_uid'),
          $upload_params,
        );

        $message = t('Attachment file uploaded. Job id: @job_id, file name: @name.', [
          '@name' => $attachment_file_name,
          '@job_id' => $job->id(),
        ]);

        $job->addMessage($message);

        $this->logger->info($message);

        $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
          "message" => $message->render(),
          "type" => "status",
        ]);

        $this->translationRequestManager->commitSuccessfulUpload($job, $translation_request);
      } catch (Exception $e) {
        if (class_exists(\Drupal\Component\Utility\DeprecationHelper::class)) {
          \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => \Drupal\Core\Utility\Error::logException(\Drupal::logger('tmgmt_smartling'), $e), fn() => watchdog_exception('tmgmt_smartling', $e));
        } else {
          watchdog_exception('tmgmt_smartling', $e);
        }

        $job->addMessage('Error "@error" while uploading attachment @file', [
          '@error' => $e->getMessage(),
          '@file' => $attachment_file_name
        ], 'error');

        $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
          "message" => t('Error while uploading file attachment @file. Please see logs for more info.', [
            '@file' => $attachment_file_name
          ])->render(),
          "type" => "error",
        ]);

        $this->translationRequestManager->commitError($job, $translation_request, $e);
      }
    } catch (Exception $e) {
      if (class_exists(\Drupal\Component\Utility\DeprecationHelper::class)) {
        \Drupal\Component\Utility\DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '10.1.0', fn() => \Drupal\Core\Utility\Error::logException(\Drupal::logger('tmgmt_smartling'), $e), fn() => watchdog_exception('tmgmt_smartling', $e));
      } else {
        watchdog_exception('tmgmt_smartling', $e);
      }

      $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
        "message" => t('Error while upserting translation request. Please see logs for more info.')->render(),
        "type" => "error",
      ]);
    }
  }

  /**
   * @param string $fileExtension
   *
   * @return string
   */
  protected function mapFileExtensionToFileType($file_extension) {
    $mapping = [
      'po' => 'gettext',
      'pot' => 'gettext',
      'properties' => 'java_properties',
      'txt' => 'plain_text',
    ];

    return empty($mapping[$file_extension]) ? $file_extension : $mapping[$file_extension];
  }

  /**
   * Adds smartling directives to upload parameters.
   *
   * Array of directives can be altered by `tmgmt_smartling_directives_alter`
   * hook.
   *
   * @param \Smartling\File\Params\UploadFileParameters $params
   * @param \Drupal\tmgmt\JobInterface $job
   *
   * @return \Smartling\File\Params\UploadFileParameters
   */
  protected function addSmartlingDirectives(UploadFileParameters $params, JobInterface $job) {
    $directives = [
      'smartling.translate_paths' => 'html/body/div/div, html/body/div/span',
      'smartling.string_format_paths' => 'html : html/body/div/div, @default : html/body/div/span',
      'smartling.variants_enabled' => 'true',
      'smartling.source_key_paths' => 'html/body/div/{div.sl-variant}, html/body/div/{span.sl-variant}',
      'smartling.character_limit_paths' => 'html/body/div/limit',
      'smartling.placeholder_format_custom' => $job->getSetting('custom_regexp_placeholder'),
      'smartling.include_translatable_attributes' => $job->getSetting('translatable_attributes'),
      'smartling.exclude_translatable_attributes' => $job->getSetting('exclude_translatable_attributes'),
      'smartling.force_block_for_tags' => $job->getSetting('force_block_for_tags')
    ];

    $this->moduleHandler->alter('tmgmt_smartling_directives', $directives);

    if (is_array($directives)) {
      $directives = $this->filterDirectives($directives);

      foreach ($directives as $directive_name => $directive_value) {
        $params->set($directive_name, $directive_value);
      }
    }

    return $params;
  }

  /**
   * @param array $directives
   * @return array
   */
  protected function filterDirectives(array $directives) {
    $allowed_directives_for_xml_file = [
      'smartling.entity_escaping',
      'smartling.variants_enabled',
      'smartling.translate_paths',
      'smartling.string_format_paths',
      'smartling.placeholder_format_custom',
      'smartling.placeholder_format',
      'smartling.sltrans',
      'smartling.source_key_paths',
      'smartling.pseudo_inflation',
      'smartling.instruction_paths',
      'smartling.character_limit_paths',
      'smartling.force_inline_for_tags',
      'smartling.include_translatable_attributes',
      'smartling.exclude_translatable_attributes',
      'smartling.force_block_for_tags'
    ];

    $result = [];

    foreach ($directives as $directive_name => $directive_value) {
      if (in_array($directive_name, $allowed_directives_for_xml_file)) {
        $result[$directive_name] = $directive_value;
      }
    }

    return $result;
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedRemoteLanguages(TranslatorInterface $translator) {
    if (\Drupal::isConfigSyncing()) {
      return [];
    }

    $languages = [];

    // Prevent access if the translator isn't configured yet.
    if (!$translator->getSetting('project_id')) {
      // @todo should be implemented by an Exception.
      return $languages;
    }
    try {
      $smartling_project_details = $this->getApiWrapper($translator->getSettings())->getApi('project')->getProjectDetails();
      foreach ($smartling_project_details['targetLocales'] as $language) {
        $languages[$language['localeId']] = $language['localeId'];
      }
    }
    catch (\Exception $e) {
      $this->logger->error('Can not get languages from the translator: @message', [
        '@message' => $e->getMessage(),
      ]);

      return $languages;
    }

    return $languages;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefaultRemoteLanguagesMappings() {
    return array(
      'zh-hans' => 'zh-CN',
      'nl' => 'nl-NL',
      'en' => 'en-EN'
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedTargetLanguages(TranslatorInterface $translator, $source_language) {
    $remote_languages = $this->getSupportedRemoteLanguages($translator);
    unset($remote_languages[$source_language]);

    return $remote_languages;
  }

  /**
   * {@inheritdoc}
   */
  public function hasCheckoutSettings(JobInterface $job) {
    return TRUE;
  }

  /**
   * Returns file name.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   * @return string
   */
  public function getFileName(JobInterface $job) {
    // TODO: identical filename task.
    // $extension = $job->getSetting('export_format');
    //
    // try {
    //   // Try to load existing file name from tmgmt_job table.
    //   $filename = $job->get('job_file_name');
    //
    //   if (!empty($filename->getValue())) {
    //     $filename = $filename->getValue()[0]['value'];
    //   }
    //   // Job item title should be included into a filename only if there is a
    //   // single JobItem in a Job. If there are 3 JobItems in a file - file name
    //   // should be "@entity_type_@entity_id>". And finally for every Job with
    //   // more than 3 JobItems - standard "JobId@id"
    //   elseif ($extension == 'xml') {
    //     $file_names = [];
    //     $job_items = $job->getItems();
    //     $job_items_count = count($job_items);
    //
    //     if ($job_items_count == 1) {
    //       $file_name_type = 'expanded';
    //     }
    //     else if ($job_items_count > 1 && $job_items_count <= 3) {
    //       $file_name_type = 'simplified';
    //     }
    //     else {
    //       $file_name_type = 'default';
    //     }
    //
    //     foreach ($job_items as $job_item) {
    //       $job_item_id = $job_item->getItemId();
    //       $job_item_type = $job_item->getItemType();
    //
    //       switch ($file_name_type) {
    //         case 'expanded':
    //           $temp_name = $job_item->getSourceLabel() . '_' . $job_item_type . '_' . $job_item_id;
    //
    //           break;
    //
    //         case 'simplified':
    //           $temp_name = $job_item_type . '_' . $job_item_id;
    //
    //           break;
    //
    //         default:
    //           $file_names[$job_item_id] = 'JobID' . $job->id() . '_' . $job->getSourceLangcode() . '_' . $job->getTargetLangcode();
    //
    //           break 2;
    //       }
    //
    //       $file_names[$job_item_id] = $temp_name;
    //     }
    //
    //     ksort($file_names);
    //     $filename = $this->cleanFileName(implode('_', $file_names) . '.' . $extension);
    //   }
    //   else {
    //     $filename = '';
    //   }
    // } catch (\Exception $e) {
    //   $filename = '';
    // }
    //
    // // Fallback to default file name.
    // if (empty($filename) || !$job->getSetting('identical_file_name')) {
    //   $filename = "JobID" . $job->id() . '_' . $job->getSourceLangcode() . '_' . $job->getTargetLangcode() . '.' . $extension;
    // }
    //
    // return $filename;

    try {
      $filename = $job->get('job_file_name');
      $filename = !empty($filename->getValue()) ? $filename->getValue()[0]['value'] : '';
    } catch (\Exception $e) {
      $filename = '';
    }

    if (empty($filename)) {
      $extension = $job->getSetting('export_format');

      // Continuous job doesn't return id for some reason but returns uuid.
      // Let's use UUID instead.
      $id = $job->isContinuous() ? $job->uuid() : $job->id();
      $name = "JobID" . $id . '_' . $job->getSourceLangcode() . '_' . $job->getTargetLangcode();

      // Alter name before saving it into database.
      $cloned_job = clone $job;
      \Drupal::moduleHandler()->alter('tmgmt_smartling_filename', $name, $cloned_job);

      $filename = $name . '.' . $extension;
    }

    return $filename;
  }

  /**
   * {@inheritdoc}
   */
  public function getAttachmentFileName(JobInterface $job, FileInterface $file) {
    // Continuous job doesn't return id for some reason but returns uuid.
    // Let's use UUID instead.
    $id = $job->isContinuous() ? $job->uuid() : $job->id();
    $name = "JobID" . $id . '_' . $job->getSourceLangcode() . '_' . $job->getTargetLangcode();

    $cloned_file = clone $file;
    $attachmentFilename = pathinfo($file->getFilename(), PATHINFO_FILENAME);
    $attachmentFileExtension = pathinfo($file->getFilename(), PATHINFO_EXTENSION);
    $composedAttachmentFilename = $name . '_' . $this->cleanFileName($attachmentFilename);

    \Drupal::moduleHandler()->alter('tmgmt_smartling_attachment_filename', $composedAttachmentFilename, $cloned_job, $cloned_file);

    $filename = mb_substr(
      $composedAttachmentFilename,
      0,
      255 - mb_strlen($attachmentFileExtension) - 1
    ) . '.' . $attachmentFileExtension;

    return $filename;
  }

  /**
   * Return clean filename, sanitized for path traversal vulnerability.
   *
   * Url (https://code.google.com/p/teenage-mutant-ninja-turtles
   * /wiki/AdvancedObfuscationPathtraversal).
   *
   * @param string $filename
   *   File name.
   *
   * @return string
   *   Return clean filename.
   */
  private function cleanFileName($filename) {
    $trim_filename = trim($filename);

    if (empty($trim_filename)) {
      return '';
    }

    $pattern = '/[^a-zA-Z0-9_\-\:]/i';
    $info = pathinfo(trim($filename));
    $filename = preg_replace($pattern, '_', $info['filename']);

    if (isset($info['extension']) && !empty($info['extension'])) {
      $filename .= '.' . preg_replace($pattern, '_', $info['extension']);
    }

    return (string) $filename;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultSettings() {
    return array(
      'export_format' => 'xml',
      'allow_override' => TRUE,
      'scheme' => 'public',
      'retrieval_type' => 'published',
      'callback_url_use' => FALSE,
      'callback_url_host' => '',
      'auto_authorize_locales' => TRUE,
      'xliff_processing' => TRUE,
      'custom_regexp_placeholder' => '(@|%|!)[\w-]+',
      'translatable_attributes' => 'title, alt',
      'exclude_translatable_attributes' => '',
      'force_block_for_tags' => '',
      'context_skip_host_verifying' => FALSE,
      'context_url_host' => '',
      'identical_file_name' => FALSE,
      'enable_smartling_logging' => TRUE,
      'enable_notifications' => TRUE,
      'async_mode' => FALSE,
      'enable_basic_auth' => FALSE,
      'download_by_job_items' => FALSE,
      'basic_auth' => [
        'login' => '',
        'password' => '',
      ],
      'exclude_context_options' => [],
    );
  }

  /**
   * {@inheritdoc}
   */
  public function requestJobItemsTranslation(array $job_items) {
    /** @var \Drupal\tmgmt\Entity\Job $job */
    $job = reset($job_items)->getJob();

    $this->flowScheduler->scheduleUpload(
      $job->id(),
      $this->moduleHandler->invokeAll('tmgmt_extension_suit_updated_entity_jobs', [[$job->id()], $job->getTranslatorId()]),
      TRUE
    );

    foreach ($job_items as $job_item) {
      if ($job->isContinuous()) {
        $job_item->active();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function isReadyForDownload(JobInterface $job) {
    return $this->translationRequestManager->isTranslationRequestReadyForDownload($job);
  }

  /**
   * {@inheritdoc}
   */
  public function isAttachmentReadyForDownload(JobInterface $job, FileInterface $file) {
    return $this->translationRequestManager->isTranslationRequestReadyForDownload($job, $file);
  }

  public function abortTranslation(JobInterface $job) {
    $api_wrapper = $this->getApiWrapper($job->getTranslator()->getSettings());
    $api_wrapper->createAuditLogRecord(
      $job,
      NULL,
      $this->currentUser,
      CreateRecordParameters::ACTION_TYPE_CANCEL
    );

    return parent::abortTranslation($job);
  }

  /**
   * @inheritdoc
   */
  public function downloadTranslation(JobInterface $job, ?JobItemInterface $jobItem = NULL) {
    return tmgmt_smartling_download_file($job, $jobItem);
  }

  /**
   * @inheritdoc
   */
  public function downloadAttachmentTranslation(JobInterface $job, FileInterface $file) {
    // TODO: maybe download through the queue?
    if (method_exists($this->dataService, 'getTranslatableFiles') && method_exists($this->dataService, 'createFileTranslation')) {
      foreach ($job->getItems() as $item) {
        foreach ($this->dataService->getTranslatableFiles($item->getData()) as $key => $attachedFile) {
          if ($attachedFile->id() === $file->id()) {
            $api_wrapper = $this->getApiWrapper($job->getTranslator()->getSettings());
            $translation_request = $this->translationRequestManager->getTranslationRequest($job, $file);

            if (empty($translation_request)) {
              $job->addMessage('File download failed for job = @job_id: can\'t find related translation request.', ['@job' => $job->id()], 'error');

              $this->logger->error('Can\'t retrieve translation request for file @name (job id = @job_id).', [
                '@name' => $this->getAttachmentFileName($job, $file),
                '@job_id' => $job->id(),
              ]);

              $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
                "message" => t('File @name (job id = @job_id) wasn\'t downloaded: can\'t find related translation request. See logs for more info.', [
                  '@name' => $this->getAttachmentFileName($job, $file),
                  '@job_id' => $job->id(),
                ])->render(),
                "type" => "error",
              ]);

              return FALSE;
            }

            $api_wrapper->createAuditLogRecord(
              $job,
              NULL,
              Drupal::currentUser(),
              CreateRecordParameters::ACTION_TYPE_DOWNLOAD
            );

            try {
              $smartling_api = $api_wrapper->getApi('file');
              $retrieval_type = $job->getTranslator()->getSetting('retrieval_type');
              $filename = $translation_request["fileUri"];
              $download_parameters = new DownloadFileParameters();
              $download_parameters->set('retrievalType', $retrieval_type);
              $content = $smartling_api->downloadFile($filename, $job->getRemoteTargetLanguage(), $download_parameters);

              $translation_file = $this->dataService->createFileTranslation($file, $job->getTargetLangcode(), $content);
              $item->setState(JobItem::STATE_ACTIVE);
              $item->addTranslatedData($this->dataService->unflatten([
                $key => ['#file' => $translation_file->id()]
              ]));

              $this->translationRequestManager->commitSuccessfulDownload($job, $translation_request);
            }
            catch (\Exception $e) {
              $api_wrapper->createFirebaseRecord("tmgmt_smartling", "notifications", 10, [
                "message" => t('File @name (job id = @job_id) wasn\'t downloaded. Please see logs for more info.', [
                  '@name' => $filename,
                  '@job_id' => $job->id(),
                ])->render(),
                "type" => "error",
              ]);

              $this->translationRequestManager->commitError($job, $translation_request, $e);

              return FALSE;
            }

            return TRUE;
          }
        }
      }
    }

    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function cancelTranslation(JobInterface $job) {
    // TODO: Implement cancelTranslation() method.
  }

  /**
   * Requests translation.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   * @param array $data
   *
   * @return mixed
   */
  public function requestTranslationExtended(JobInterface $job, array $data) {
    // Pass queue item data into job settings.
    $settings_map_item = $job->settings->get(0);

    if ($settings_map_item) {
      $settings = $settings_map_item->getValue();

      if (isset($data['batch_uid'])) {
        $settings['batch_uid'] = $data['batch_uid'];
      }

      if ($data['batch_execute_on_job']) {
        $settings['batch_execute_on_job'] = $data['batch_execute_on_job'];
      }

      $job->settings->set(0, $settings);
    }

    $this->requestTranslation($job);
  }

}

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

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