lionbridge_translation_provider-8.x-2.4/tmgmt_contentapi/src/Services/CreateConnectorJob.php

tmgmt_contentapi/src/Services/CreateConnectorJob.php
<?php

namespace Drupal\tmgmt_contentapi\Services;

use Drupal\Component\Utility\Xss;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Render\Markup;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\JobQueue;
use Drupal\tmgmt\Entity\Job;
use Drupal\tmgmt_contentapi\Services\CapiDataProcessor;
use Drupal\tmgmt_contentapi\Swagger\Client\Api\JobApi;
use Drupal\tmgmt_contentapi\Swagger\Client\Api\RequestApi;
use Drupal\tmgmt_contentapi\Swagger\Client\Api\SourceFileApi;
use Drupal\tmgmt_contentapi\Swagger\Client\Model\CreateJob;
use Drupal\tmgmt_contentapi\Swagger\Client\Model\CreateRequestFile;
use Drupal\tmgmt_contentapi\Swagger\Client\Model\ProviderId;
use Drupal\tmgmt_contentapi\Services\JobHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\tmgmt_contentapi\Swagger\Client\Api\TranslationMemoryApi;
use Drupal\tmgmt_contentapi\Swagger\Client\Model\CreateRequestUpdateTM;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\tmgmt_contentapi\Services\QueueOperations;
use Drupal\tmgmt_contentapi\Services\JobUploadManagerService;

/**
 * Service to create a job.
 */
class CreateConnectorJob {
  use StringTranslationTrait;

  /**
   * The array for content api bundle.
   *
   * @var array
   */
  protected $contentApiBundle;

  /**
   * The contains job request object.
   *
   * @var \Drupal\tmgmt_contentapi\Swagger\Client\Model\CreateJob
   */
  protected $jobRequest;

  /**
   * The contains job api object.
   *
   * @var \Drupal\tmgmt_contentapi\Swagger\Client\Api\JobApi
   */
  protected $jobApi;

  /**
   * The contains cp Jobs.
   *
   * @var \Drupal\tmgmt_contentapi\Swagger\Client\Model\Job
   */
  protected $createdCpJob;

  /**
   * The Source file api.
   *
   * @var \Drupal\tmgmt_contentapi\Swagger\Client\Api\SourceFileApi
   */
  protected $fileApi;

  /**
   * The request api.
   *
   * @var \Drupal\tmgmt_contentapi\Swagger\Client\Api\RequestApi
   */
  protected $transRequestApi;

  /**
   * The transfer file service.
   *
   * @var \Drupal\tmgmt_contentapi\Services\ExportJobFiles
   */
  protected $transferFiles;

  /**
   * Token to submit the job.
   *
   * @var string
   */
  protected $capiToken;

  /**
   * Token API object.
   *
   * @var array
   */
  protected $capi;

  /**
   * The stream wrapper service.
   *
   * @var \Drupal\Core\StreamWrapper\StreamWrapperManager
   */
  protected $streamWrapperManager;

  /**
   * The filesystem service.
   *
   * @var \Drupal\Core\Datetime\DrupalDateTime
   */
  protected $drupalDateTime;

  /**
   * The capi data processor service.
   *
   * @var \Drupal\tmgmt_contentapi\Services\CapiDataProcessor
   */
  protected $capiDataProcessor;

  /**
   * The Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Files array to delete if exception occures.
   *
   * @var array
   */
  protected $fileArrayToDelte;

  /**
   * Zip path of current job.
   *
   * @var string
   */
  protected $zipPath;

  /**
   * Zip Archive path.
   *
   * @var obj
   */
  protected $zipArchivePath;

  /**
   * The queue factory.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $queueFactory;

  /**
   * The queue object used for processing jobs.
   *
   * @var object
   */
  protected $queue;

  /**
   * The job queue service.
   *
   * @var \Drupal\tmgmt\JobQueue
   */
  protected $jobQueue;

  /**
   * The queue object used for processing job items.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $itemQueue;

  /**
   * Logger Factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * Job helper service.
   *
   * @var \Drupal\tmgmt_contentapi\Services\JobHelper
   */
  protected $jobHelper;

  /**
   * The queue operations.
   *
   * @var \Drupal\tmgmt_contentapi\Services\QueueOperations
   */
  protected $queueOperations;

  /**
   * The job upload manager service.
   *
   * @var \Drupal\tmgmt_contentapi\Services\JobUploadManagerService
   */
  protected $jobUploadManager;

  /**
   * The queue object used for processing file.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $jobSubmissionQueue;

  /**
   * The queue object used for processing item files.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $generateItemXlfFileQueue;

  /**
   * Queue name for export translation jobs to capi.
   */
  const QUEUE_NAME_EXPORT_JOBS = 'export_translation_jobs_to_capi';

  /**
   * Queue name for send files to CAPI.
   */
  const QUEUE_NAME_SEND_FILES = 'send_file_for_translation_to_capi';

  /**
   * Queue name for generate files to CAPI.
   */
  const QUEUE_NAME_GENERATE_FILES = 'generate_file_for_translation_to_capi';

  /**
   * Connector version for CAPI.
   */
  const CONNECTOR_VERSION = 'V 9.4.1';

  /**
   * Constructor.
   *
   * @param ExportJobFiles $transferFiles
   *   Object for custom service export job files.
   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
   *   Object for stream wrapper manager service.
   * @param \Drupal\tmgmt_contentapi\Services\CapiDataProcessor $capiDataProcessor
   *   Object for capi data processor service.
   * @param \Drupal\tmgmt\JobQueue $jobQueue
   *   Job queue object.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
   *   Service for logger factory.
   * @param \Drupal\tmgmt_contentapi\Services\JobHelper $jobHelper
   *   Service for job helper.
   * @param \Drupal\Core\Queue\QueueFactory $queueFactory
   *   Service for queue factory.
   * @param \Drupal\tmgmt_contentapi\Services\QueueOperations $queueOperations
   *   Service for queue operations.
   * @param \Drupal\tmgmt_contentapi\Services\JobUploadManagerService $jobUploadManager
   *   Service for job upload management.
   */
  public function __construct(
    ExportJobFiles $transferFiles,
    StreamWrapperManagerInterface $streamWrapperManager,
    CapiDataProcessor $capiDataProcessor,
    JobQueue $jobQueue,
    LoggerChannelFactoryInterface $logger,
    JobHelper $jobHelper,
    QueueFactory $queueFactory,
    QueueOperations $queueOperations,
    JobUploadManagerService $jobUploadManager,
  ) {

    $this->transferFiles = $transferFiles;
    $this->streamWrapperManager = $streamWrapperManager;
    $this->capiDataProcessor = $capiDataProcessor;
    $this->jobQueue = $jobQueue;
    $this->logger = $logger;
    $this->jobHelper = $jobHelper;
    $this->queueFactory = $queueFactory;
    $this->queueOperations = $queueOperations;
    $this->jobUploadManager = $jobUploadManager;
    // Set default values for variables.
    $this->jobSubmissionQueue = $this->queueFactory->get(self::QUEUE_NAME_EXPORT_JOBS);
    $this->itemQueue = $this->queueFactory->get(self::QUEUE_NAME_SEND_FILES);
    $this->generateItemXlfFileQueue = $this->queueFactory->get(self::QUEUE_NAME_GENERATE_FILES);
    $this->contentApiBundle = [];
    $this->jobRequest = NULL;
    $this->jobApi = new JobApi();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
          $container->get('tmgmt_contentapi.export_job'),
          $container->get('stream_wrapper_manager'),
          $container->get('tmgmt_contentapi.capi_data_processor'),
          $container->get('tmgmt.queue'),
          $container->get('logger.factory'),
          $container->get('tmgmt_contentapi.job_helper'),
          $container->get('queue'),
          $container->get('tmgmt_contentapi.queue_operations'),
          $container->get('tmgmt_contentapi.job_upload_manager'),
      );
  }

  /**
   * Summary of genrateJobRequst.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   *
   * @return \Drupal\tmgmt_contentapi\Swagger\Client\Model\CreateJob
   *   Request job object.
   */
  public function genrateJobRequst(JobInterface $job) {
    $source_lang = $job->getRemoteSourceLanguage();
    $target_lang = $job->getRemoteTargetLanguage();
    $shouldquote = (bool) ($job->getSetting('capi-settings')['quote']['is_quote']);
    $capijobsettings = $job->getSetting("capi-settings");

    $jobarray = [
      'job_name' => $this->jobHelper->getJobLabelNoSpeChars($job),
      'description' => $capijobsettings["description"] ?? NULL,
      'po_reference' => $capijobsettings["po_reference"] ?? NULL,
      'due_date' => $capijobsettings["due_date"] ?? NULL,
      'should_quote' => $shouldquote,
      'source_lang_code' => $source_lang,
      'target_lang_code' => $target_lang,
      'connector_name' => 'Lionbridge Connector for Drupal 9 and 10',
      'connector_version' => self::CONNECTOR_VERSION,
    ];

    // Get analysis code settings if not empty. Add the same extended metadata to job array.
    $analysis_code = $capijobsettings['analysis_code'] ?? [];
    if (!empty($analysis_code)) {
      $extendedMetadata = [];
      foreach ($analysis_code as $key => $value) {
        $exploded_key = explode('_', $key);
        if (!empty($value)) {
          $extendedMetadata['AnalysisCode' . $exploded_key[1] . '_name'] = $exploded_key[0];
          $extendedMetadata['AnalysisCode' . $exploded_key[1] . '_value'] = $value;
        }
      }
      $jobarray['extended_metadata'] = $this->sortAnalysisCode($extendedMetadata);
    }

    if (isset($capijobsettings["custom_data"]) && $capijobsettings["custom_data"] !== "") {
      $job['custom_data'] = $capijobsettings["custom_data"];
    }
    $jobrequest = new CreateJob($jobarray);
    return $jobrequest;
  }

  /**
   * Function to create cp jobs.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   */
  public function createCpJobs(JobInterface $job) {
    // Create cp job.
    $this->jobRequest = $this->genrateJobRequst($job);
    $translator = $job->getTranslator();
    $this->capiToken = \DRUPAL::service('tmgmt_contentapi.capi_details')->getCapiToken($translator);
    $this->createdCpJob = $this->jobApi->jobsPost($this->capiToken, body: $this->jobRequest);
  }

  /**
   * Function to transfer files to cpa.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   */
  public function uploadTransferFilesToCpa(JobInterface $job) {

    // Save job to make sure all settings are available later when job queue and item queue are processed.
    $job->save();
    $upload_info = [
      'item_id' => $job->id(),
      'job' => $job,
    ];
    // ROBUSTNESS IMPROVEMENT: Enhanced duplicate check using database tracking
    if ($this->jobUploadManager->isDuplicateJobSubmission($job, self::QUEUE_NAME_EXPORT_JOBS)) {
      return;
    }
    $this->jobSubmissionQueue->createItem($upload_info);
    $remainingJobCount = $this->jobQueue->count();
    $workers = 1;
    // If this is last jobs in process then start queue item processing.
    // Execut queue using only one worker. Multiple workers leads to item stuck in queue.
    // If job item alread exist in queue it means queue worker is already started.
    // Then no need to create new queue worker.
    if ($remainingJobCount <= $workers) {
      $this->queueOperations->processQueue(self::QUEUE_NAME_EXPORT_JOBS);
    }
  }

  /**
   * STEP 1: Function to generate files.
   *
   * @param array $job
   *   Job object.
   * @param bool $is_translation_memory
   *   Check if job is for translation memory.
   */
  public function addItemsToQueueToGenerateXlfFiles(JobInterface $job, $is_translation_memory = FALSE) {
    $transfer_files = [];
    $this->createdCpJob = NULL;
    if (!$is_translation_memory) {
      $this->createCpJobs($job);
      $capi_job_id = $this->createdCpJob->getJobId();
    }
    else {
      $capi_job_id = $this->jobHelper->getCpJobIdfromLocJob($job);
    }
    // Prepare variables, verify 'task' set to trans and if folder is created to load the files.
    $this->transferFiles->jobFilesToTransfer($job, $capi_job_id);
    $job_items = $job->getItems();
    $one_export_file = $job->getTranslator()->getSetting(name: "one_export_file");
    // Add items to queue for generating xlf files.
    $total_items = count($job_items);
    foreach ($job_items as $key => $item) {
      $item_info = [
        'id' => $item->id(),
        'jobid' => $job->id(),
        'capi_job_id' => $capi_job_id,
        'translator' => $job->getTranslator(),
        'is_translation_memory' => $is_translation_memory,
        'getRemoteSourceLanguage' => $job->getRemoteSourceLanguage(),
        'getRemoteTargetLanguage' => $job->getRemoteTargetLanguage(),
        'total_items' => $total_items,
        'one_export_file' => $one_export_file,
        'allFilesPath' => $this->transferFiles->allFilesPath,
        'zipPath' => $this->transferFiles->zipPath,
      ];
      // ROBUSTNESS IMPROVEMENT: Create initial tracking record for file generation stage
      $this->jobUploadManager->createUploadTrackingRecord($job, $capi_job_id, $item->id(), 'FILE_GENERATING_INITIAL');
      
      // For single file don't add every item in queue one item is enough.
      if ($one_export_file) {
        if ($key === array_key_last($job_items)) {
          $this->generateItemXlfFileQueue->createItem($item_info);
        }
      }
      else {
        // For other configuration add every item in queue for file generation.
        $this->generateItemXlfFileQueue->createItem($item_info);
      }

    }
    // Start send files worker only when all job files are added in queue.
    if ($this->jobSubmissionQueue->numberOfItems() > 1) {
      return;
    }
    // Process first 100 files rest will get processed in next cron run.
    // This is to avoid memory limit issues.
    $this->queueOperations->processQueue(self::QUEUE_NAME_GENERATE_FILES, 200);
  }

  /**
   * STEP 2: This function will get process on each item queue item to generate file.
   *
   * @param mixed $data
   *   Data to generate files.
   */
  public function generateFiles($data = []) {
    $job = Job::load($data['jobid']);
    if ($data["one_export_file"]) {
      $this->transferFiles->singleExportFileForAllItems($job, $data['capi_job_id'], $data['allFilesPath']);
    }
    else {
      $this->transferFiles->seperateExportForItem($job, $data['capi_job_id'], $data['id'], $data['allFilesPath']);
    }
    // ROBUSTNESS IMPROVEMENT: Update status to indicate file generation completed
    // For single file scenarios, update ALL items since one file represents all items
    if ($data["one_export_file"]) {
      // Single file scenario: Update ALL items to FILE_GENERATED status since one file contains all items
      $this->jobUploadManager->updateFileUploadStatus('all', $data['jobid'], 'FILE_GENERATED', NULL, FALSE);
    } else {
      // Separate files scenario: Update only the specific item that was processed
      $this->jobUploadManager->updateFileUploadStatus($data['id'], $data['jobid'], 'FILE_GENERATED', NULL, FALSE);
    }
    // Check upload status of current job.
    $upload_status = $this->queueOperations->getJobUploadStatus($data['jobid'], $data['capi_job_id']);
    // Check if all files are generated.
    if ($upload_status['file_generated'] == $upload_status['total']) {
      $file_array_exported_files = $this->queueOperations->getValueFromStatevariable($data['capi_job_id'] . '_' . $job->id() . '_transfer_files');
      // Create zip here.
      $this->transferFiles->zipExportedFiles($job, $file_array_exported_files, $data['zipPath']);
      // Check if send all files via zip is set or not accordingly file will sent.
      $this->transferFiles->finalZipArrayOfFilesToTransfer($job, $data['capi_job_id'], $data['zipPath']);
      // Finaly add files to queue for submission.
      $this->addFilesToQueueForSubmission($job, $data);

      $this->logger->get("TMGMT_CONTENTAPI_SENDIN_FILE")->notice('File send for translation from queue started');
      $this->queueOperations->processQueue(self::QUEUE_NAME_SEND_FILES);
    }

  }

  /**
   * STEP 3: Function to batch submit files to CAPI.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   * @param array $file_info
   *   Array containing file information.
   */
  public function addFilesToQueueForSubmission(JobInterface $job, array $file_info) {
    // Check if get translator is not null.
    try {
      $transfer_files = $this->queueOperations->getValueFromStatevariable($file_info['capi_job_id'] . '_' . $job->id() . '_transfer_files');
      // If file already uploaded then skip adding to queue.
      $already_uploaded_files = $this->capiDataProcessor->getAlreadyUploadItems($job->id());
      foreach ($already_uploaded_files as $id) {
        if (isset($transfer_files[$id])) {
          unset($transfer_files[$id]);
        }
      }
      
      // Get total items for this job.
      $total_items = count($transfer_files);
      // Add item info in queue for processing.
      foreach ($transfer_files as $id => $tmpfile) {
        $data = [];
        $data["filename"] = $tmpfile->getFilename();
        $stmrg = $this->streamWrapperManager->getViaUri($tmpfile->getFileUri());
        $extpath = $stmrg->realpath();
        $zip = new \ZipArchive();
        if ($zip->open($extpath) === TRUE) {
          $zip->extractTo($file_info['allFilesPath']);
          $zip->close();
        }
        $data["filetype"] = $tmpfile->getMimeType();
        $stmrg = $this->streamWrapperManager->getViaUri($tmpfile->getFileUri());
        $extpath = $stmrg->realpath();
        $jobpath = $job->getTranslator()->getSetting('transfer-settings') ? $extpath : $file_info['allFilesPath'] . "/" . $data["filename"];

        // SINGLE FILE SCENARIO FIX: Use 'all' identifier for single-response scenarios
        // This ensures consistency with JobUploadManagerService.checkUploadRetryLimits() expectations
        $is_single_response = $this->jobUploadManager->isSingleResponseScenario($job);
        $queue_item_id = $is_single_response ? 'all' : $id;

        $item_info = [
          'id' => $queue_item_id,
          'jobid' => $job->id(),
          'capi_job_id' => $file_info['capi_job_id'],
          'translator' => $job->getTranslator(),
          'is_translation_memory' => $file_info['is_translation_memory'],
          'getRemoteSourceLanguage' => $job->getRemoteSourceLanguage(),
          'getRemoteTargetLanguage' => $job->getRemoteTargetLanguage(),
          'data' => $data,
          'extpath' => $extpath,
          'jobpath' => $jobpath,
          'allFilesPath' => $file_info['allFilesPath'],
          'zipPath' => $file_info['zipPath'],
          'total_items' => $total_items,
          'provider_id' => $job->getSetting("capi-settings")["provider"],
        ];
        
        // ROBUSTNESS IMPROVEMENT: Update status to indicate file is ready for upload (Stage 2)
        // Use consistent ID logic: 'all' for single-response scenarios, specific ID for separate files
        $this->jobUploadManager->updateFileUploadStatus($queue_item_id, $job->id(), 'READY_FOR_UPLOAD', NULL, FALSE);

        $this->itemQueue->createItem($item_info);
      }
      $this->logger->get("TMGMT_CONTENTAPI_JOB_POST")->notice(
        'File added in queue to send it for submission job ids: @jobids ',
        [
          '@jobids' => $job->id() . '_' . $file_info['capi_job_id'],
        ]
      );
    }
    catch (\Exception $ex) {
      // Release job from queue if any exception occurs.
      $this->cleanUpJobOnFailure($job->id(), $data['capi_job_id'], $data['allFilesPath'], $data['zipPath'], $ex);
      // Log the error.
      $this->logger->get(channel: 'TMGMT_CONTENTAPI_JOB_POST')->error(
            'Issue while processing item : @joblabel, Job: @job, CAPI jobid: @capijobid Error: @errormessage.',
            context: [
              '@joblabel' => $job->label(),
              '@job' => $job->id(),
              '@capijobid' => $data['capi_job_id'],
              '@errormessage' => $ex->getMessage(),
            ]
            );
    }
    // Free memory to avoid memory leak.
    unset($job);
    unset($this->contentApiBundle);
    unset($transfer_files);
    unset($this->createdCpJob);
    // Collect garbage.
    gc_collect_cycles();
  }

  /**
   * Function to post files to CAPI.
   *
   * @param array $item_info
   *   Array containing item information.
   */
  public function postFilesToCapi($item_info) {
    // Get all the information from item info array.
    $id = $item_info['id'];
    $jobid = $item_info['jobid'];
    $capi_job_id = $item_info['capi_job_id'];
    $translator = $item_info['translator'];

    // ROBUSTNESS IMPROVEMENT: Check for retry limits using JobUploadManagerService
    $retry_status = $this->jobUploadManager->checkUploadRetryLimits($id, $jobid, $capi_job_id);
    if ($retry_status === 'RETRY_EXCEEDED') {
      // Max retries exceeded - don't process and let queue remove item
      return;
    }
    
    // Update status to indicate upload is in progress using JobUploadManagerService
    $this->jobUploadManager->updateFileUploadStatus($id, $jobid, 'UPLOADING', NULL, FALSE);
    $is_translation_memory = $item_info['is_translation_memory'];
    $getRemoteSourceLanguage = $item_info['getRemoteSourceLanguage'];
    $getRemoteTargetLanguage = $item_info['getRemoteTargetLanguage'];
    $data = $item_info['data'];
    $extpath = $item_info['extpath'];
    $jobpath = $item_info['jobpath'];
    $allFilesPath = $item_info['allFilesPath'];
    $zipPath = $item_info['zipPath'];
    $total_items = $item_info['total_items'];

    // Get capi token.
    $this->capiToken = \DRUPAL::service('tmgmt_contentapi.capi_details')->getCapiToken($translator);
    // $this->capiToken = '';
    // Create object for file api and request api.
    $fileApi = new SourceFileApi();
    $transRequestApi = new RequestApi();
    // Process the file.
    try {
      $filerequest = new \SplFileObject($extpath);
      // File upload to TUS CLINET.
      $contentapitmpfile = $fileApi->jobsJobIdUploadPost($this->capiToken, $capi_job_id, $data["filename"], $data["filetype"], $filerequest);
      $fileApi->uploadFile($contentapitmpfile, $jobpath);
      // If request is for translation memory then update translation memory.
      // Else continue with file upload for job creation.
      if ($is_translation_memory) {
        $tmupdateapi = new TranslationMemoryApi();
        $tmupdaterequest = new CreateRequestUpdateTM(
          [
            'file_id' => $contentapitmpfile->getfms_file_id(),
            'source_native_language_code' => $getRemoteSourceLanguage,
            'target_native_language_code' => $getRemoteTargetLanguage,
          ]
          );
        $tmupdateapi->jobsJobIdTmUpdatefilePut($this->capiToken, $capi_job_id, $tmupdaterequest);
      }
      else {
        $request = new CreateRequestFile([
          "request_name" => $jobid . "_" . $id . "_" . $getRemoteSourceLanguage . "-" . $getRemoteTargetLanguage,
          "source_native_id" => $jobid . "_" . $id,
          "source_native_language_code" => $getRemoteSourceLanguage,
          "target_native_language_codes" => [$getRemoteTargetLanguage],
          "file_id" => $contentapitmpfile['fms_file_id'],
        ]);
        $contentapitmprequst = $transRequestApi->jobsJobIdRequestsAddfilePost($this->capiToken, $capi_job_id, $request);
        // Store API response directly in tmgmt_capi_request_processor table via updateFileUploadStatus
      }
      // Log the file post success.
      $this->logger->get("TMGMT_CONTENTAPI_FILE_POST_SUCCESS")->notice(
        'jobids: @jobids - File posted successfully for file:@filename ',
        [
          '@jobids' => $jobid . '_' . $capi_job_id,
          '@filename' => $data["filename"],
        ]);
      
      $request_id = $contentapitmprequst[0]->getRequestId() ?? '';
      
      // ROBUSTNESS IMPROVEMENT: Update file upload status using JobUploadManagerService
      $this->jobUploadManager->updateFileUploadStatus($id, $jobid, 'UPLOADED',  $contentapitmprequst, FALSE);

      // ROBUSTNESS IMPROVEMENT: Use database-driven completion check via JobUploadManagerService
      if ($this->jobUploadManager->areAllFilesUploaded($jobid, $capi_job_id)) {
        $this->logger->get("TMGMT_CONTENTAPI_JOB_POST_COMPLETION")->notice(
          'jobids: @jobids - All files uploaded successfully, proceeding with job submission',
          [
            '@jobids' => $jobid . '_' . $capi_job_id,
          ]);
        
        $lock = \Drupal::lock();
        if ($lock->acquire("final_submit_lock_$capi_job_id")) {
          try {
            $this->submitJobPostUpload($jobid, $capi_job_id, $allFilesPath, $zipPath);
          } finally {
            $lock->release("final_submit_lock_$capi_job_id");
          }
        }
      }
    }
    catch (\Exception $ex) {
      // ROBUSTNESS IMPROVEMENT: Use centralized retry logic via JobUploadManagerService
      // Add failed attempt.
      $this->jobUploadManager->updateFileUploadStatus($item_info['id'], $item_info['jobid'], 'FAILED', NULL, TRUE, $ex->getMessage());
      $retry_status = $this->jobUploadManager->checkUploadRetryLimits($item_info['id'], $item_info['jobid'], $item_info['capi_job_id']);
      
      if ($retry_status === 'RETRY_EXCEEDED') {
        // Max retries exceeded - perform cleanup and don't retry
        $this->cleanUpJobOnFailure($item_info['jobid'], $item_info['capi_job_id'], $item_info['allFilesPath'], $item_info['zipPath'], $ex);
        return;
      }
      
      // For CONTINUE, attempt retry
      $this->logger->get("TMGMT_CONTENTAPI_FILE_POST_FAILED")->error(
        'jobids: @jobids - Failed File posting for file:@filename, Error: @errormessage',
        [
          '@jobids' => $item_info['jobid'] . '_' . $item_info['capi_job_id'],
          '@filename' => $data["filename"],
          '@errormessage' => $ex->getMessage(),
        ]
      );
      
      // Re-throw exception so Drupal queue retries the item (retry logic handled by checkUploadRetryLimits)
      throw new \Exception(
        "File upload failed: " . $ex->getMessage(),
        $ex->getCode(),
        $ex
      );
    }
    unset($contentapitmpfile);
    unset($contentapitmprequst);
    unset($request);
    unset($filerequest);
  }

  /**
   * Function to submit job to content API.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   */
  public function submitJobToContentApi(JobInterface $job) {
    try {
      $this->drupalDateTime = new DrupalDateTime('now', 'UTC');
      $this->uploadTransferFilesToCpa($job);
      // Generate message which user can see once job added to queue for further processing.
      $messageToPass = $this->t('Job(s) creation is in progress and is running in the background. You can view the progress and updated status on the Job Overview page.');
      \Drupal::messenger()->addMessage(Markup::create($messageToPass));
    }
    catch (\Exception $ex) {
      $this->logger->get('TMGMT_CONTENTAPI_JOB_POST')->error(
            'Issue while creating job : @joblabel, Error: @errormessage.',
            [
              '@joblabel' => $job->label(),
              '@errormessage' => $ex->getMessage(),
            ]
            );
    }
  }

  /**
   * Function to submit translation memory to content API.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   Job under process.
   */
  public function updateTranslationMemory(JobInterface $job) {
    // If job state is not finished then return.
    if ($job->getState() != JobInterface::STATE_FINISHED) {
      return;
    }
    // Submit translation memory to content api.
    $translator = $job->getTranslator();
    $this->capiToken = \DRUPAL::service('tmgmt_contentapi.capi_details')->getCapiToken($translator);
    // If job submission for translation memory.
    $this->addItemsToQueueToGenerateXlfFiles($job, TRUE);
    $job->submitted($this->t("Job sent to update translation memory!"));
    $messageToPass = $this->t('TM update request successfully submitted.');
    \Drupal::messenger()->addMessage(Markup::create($messageToPass));
  }

  /**
   * Function to submit job to content API.
   *
   * @param int $jobid
   *   Job under process.
   * @param string $capi_job_id
   *   Capi job id.
   * @param string $allFilesPath
   *   All files path.
   * @param string $zipPath
   *   Zip path.
   */
  public function submitJobPostUpload(int $jobid, string $capi_job_id, $allFilesPath, $zipPath) {
    try {
      // Load job object.
      $job = Job::load($jobid);
      $pr = $job->getSetting("capi-settings")["provider"];
      $jid = $capi_job_id;
      $prmodel = new ProviderId(['provider_id' => $pr]);
      $jobsubmitresult = $this->jobApi->jobsJobIdSubmitPut($this->capiToken, $jid, $prmodel);
      if ($jobsubmitresult) {
        // Get content api bundle from tmgmt_capi_request_processor table instead of separate response table
        $is_single_response = $this->jobUploadManager->isSingleResponseScenario($job);
        $contentApiBundle = $this->jobUploadManager->getApiResponsesFromProcessorTable($jobid, $capi_job_id, $is_single_response);
        $this->logger->get("TMGMT_CONTENTAPI_JOB_POST_CONTENT_BUNDEL_COUNT")->notice('Count: @count ', ['@count' => count($contentApiBundle)]);
        $this->jobHelper->addCpaSettingsToJob($job, serialize($contentApiBundle));
        // Set message if quote is set else success.
        if ($job->getSetting('capi-settings')['quote']['is_quote']) {
          $job->submitted($this->t("This job was submitted for a quote. To submit your job for processing, you must log into your translation provider's system to approve this quote."));
        }
        else {
          $job->submitted($this->t("Job sent to provider!"));
        }
        // ROBUSTNESS IMPROVEMENT: Update existing records instead of creating new ones
        // Records were already created in addFilesToQueueForSubmission for upload tracking
        $this->jobUploadManager->updateExistingRecordsWithProviderDetails($job, $pr, $capi_job_id);
        // Delete transfer files.
        $transfer_files = $this->queueOperations->getvalueFromStatevariable($capi_job_id . '_' . $jobid . '_transfer_files');
        $this->transferFiles->deleteProcessedFiles($transfer_files);
        // Delete temporary state variables (API responses now stored in tmgmt_capi_request_processor)
        $this->queueOperations->deleteStateVariable($capi_job_id . '_' . $jobid . '_transfer_files');
        // Log the job sent successfully.
        $this->logger->get("TMGMT_CONTENTAPI_JOB_POST")->notice('Job sent successfully job ids: @jobids ', ['@jobids' => $job->id() . '_' . $jid]);
      }
    }
    catch (\Throwable $ex) {
      // Log the reson for error.
      $this->logger->get('TMGMT_CONTENTAPI_JOB_POST')->error(
            'Failed to process job, please resubmit the same job: @joblabel, jobid: @jobids, language: @languages Error: @errormessage.',
            context: [
              '@joblabel' => $job->label(),
              '@jobids' => $job->id(),
              '@languages' => $job->getRemoteSourceLanguage() . ' to ' . $job->getRemoteTargetLanguage(),
              '@errormessage' => $ex->getMessage(),
            ]
            );
      $this->cleanUpJobOnFailure($jobid, $capi_job_id, $allFilesPath, $zipPath, $ex);
    }
  }

  /**
   * ENHANCED: Smart failure handling using dedicated JobUploadManagerService.
   *
   * Delegates to the specialized JobUploadManagerService for intelligent cleanup.
   * Maintains backward compatibility while providing enhanced failure recovery.
   *
   * @param int $jobid
   *   Job under process.
   * @param string $capi_job_id
   *   Capi job id.
   * @param string $allFilesPath
   *   All files path.
   * @param string $zipPath
   *   Zip path.
   * @param \Exception $ex
   *   Exception object.
   * @param array $specific_failed_items
   *   Optional: Array of specific item IDs that failed. If empty, will detect automatically.
   * @param bool $force_complete_cleanup
   *   Optional: Force complete cleanup even if some items succeeded (default: FALSE).
   */
  public function cleanUpJobOnFailure(int $jobid, string $capi_job_id, string $allFilesPath, string $zipPath, \Throwable $ex, array $specific_failed_items = [], bool $force_complete_cleanup = FALSE) {
    // Delegate to the specialized JobUploadManagerService
    $result = $this->jobUploadManager->handleJobFailure(
      $jobid, 
      $capi_job_id, 
      $allFilesPath, 
      $zipPath, 
      $ex, 
      $specific_failed_items, 
      $force_complete_cleanup
    );
    
    // Log the result for debugging
    $this->logger->get('TMGMT_CONTENTAPI_JOB_POST_CLEANUP')->info(
      'Smart cleanup completed for job @jobids using strategy: @strategy - Result: @message',
      [
        '@jobids' => $jobid . '_' . $capi_job_id,
        '@strategy' => $result['strategy'],
        '@message' => $result['message'],
      ]
    );
    
    return $result;
  }



  /**
   * Function to sort analysis array.
   *
   * @param array $analysis_code
   *   Array to sort.
   *
   * @return array
   *   Sorted array.
   */
  protected function sortAnalysisCode(array $analysis_code) {
    // Custom sorting function.
    uksort($analysis_code, function ($a, $b) {
      // Extract numbers from keys.
      preg_match('/AnalysisCode(\d+)_/', $a, $matchesA);
      preg_match('/AnalysisCode(\d+)_/', $b, $matchesB);

      $numA = (int) $matchesA[1];
      $numB = (int) $matchesB[1];

      // Compare the numbers.
      return $numA <=> $numB;
    });
    return $analysis_code;
  }

}

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

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