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;
}
}
