diff --git a/inc/validation/schema/job_keys.json b/inc/validation/schema/job_keys.json new file mode 100644 index 0000000000..e666ad787a --- /dev/null +++ b/inc/validation/schema/job_keys.json @@ -0,0 +1,78 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "mine": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "$ref": "#/definitions/key" + } + }, + "ownergroup": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "$ref": "#/definitions/key" + } + }, + "anonymous": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "$ref": "#/definitions/key" + } + } + }, + "required": [ + "mine", + "ownergroup", + "anonymous" + ], + "definitions": { + "key": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "key": { + "type": "string" + }, + "glos": { + "type": ["boolean", "integer", "string"] + }, + "owner": { + "type": ["boolean", "integer", "string"] + }, + "tm": { + "type": ["boolean", "integer", "string"] + }, + "r": { + "type": ["boolean", "integer", "string"] + }, + "w": { + "type": ["boolean", "integer", "string"] + }, + "penalty": { + "type": "integer", + "nullable": true, + "maximum": 100, + "minimum": 0 + } + }, + "required": [ + "name", + "key", + "glos", + "tm", + "r", + "w" + ] + } + } +} \ No newline at end of file diff --git a/inc/validation/schema/private_tm_key_json.json b/inc/validation/schema/private_tm_key_json.json new file mode 100644 index 0000000000..d4e4afbe36 --- /dev/null +++ b/inc/validation/schema/private_tm_key_json.json @@ -0,0 +1,49 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "tm_prioritization": { + "type": "boolean" + }, + "keys": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "$ref": "#/definitions/key" + } + } + }, + "required": [ + "tm_prioritization", + "keys" + ], + "definitions": { + "key": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "read": { + "type": "boolean" + }, + "write": { + "type": "boolean" + }, + "penalty": { + "type": "integer", + "nullable": true, + "maximum": 100, + "minimum": 0 + } + }, + "additionalProperties": false, + "required": [ + "key", + "read", + "write" + ] + } + } +} \ No newline at end of file diff --git a/inc/validation/schema/project_template.json b/inc/validation/schema/project_template.json index 53ba787637..7e5f5a9925 100644 --- a/inc/validation/schema/project_template.json +++ b/inc/validation/schema/project_template.json @@ -11,6 +11,9 @@ "id_team": { "type": ["integer", "string"] }, + "tm_prioritization": { + "type": "boolean" + }, "pretranslate_100": { "type": "boolean" }, @@ -92,6 +95,12 @@ }, "w": { "type": "boolean" + }, + "penalty": { + "type": "integer", + "nullable": true, + "maximum": 100, + "minimum": 0 } } }, diff --git a/lib/Controller/API/App/TmKeyManagementController.php b/lib/Controller/API/App/TmKeyManagementController.php index bc493ab65d..1e16d4ec18 100644 --- a/lib/Controller/API/App/TmKeyManagementController.php +++ b/lib/Controller/API/App/TmKeyManagementController.php @@ -31,10 +31,11 @@ public function getByJob(){ exit(); } + $job_keyList = json_decode( $chunk->tm_keys, true ); + if(!$this->isLoggedIn()){ $tmKeys = []; - $job_keyList = json_decode( $chunk->tm_keys, true ); foreach ( $job_keyList as $jobKey ) { $jobKey = new TmKeyManagement_ClientTmKeyStruct( $jobKey ); @@ -63,7 +64,7 @@ public function getByJob(){ $keys = $userKeys->getKeys( $chunk->tm_keys ); $this->response->json( [ - 'tm_keys' => $keys['job_keys'] + 'tm_keys' => $this->sortKeysInTheRightOrder($keys['job_keys'], $job_keyList) ] ); } @@ -76,4 +77,35 @@ public function getByJob(){ private function isJobRevision($idJob, $password) { return CatUtils::getIsRevisionFromIdJobAndPassword( $idJob, $password ); } + + /** + * This function sorts the $keys array based on $job_keyList. + * $keys can contain shared and/or hidden keys + * + * @param $keys + * @param $jobKeyList + * @return mixed + */ + private function sortKeysInTheRightOrder($keys, $jobKeyList) + { + $sortedKeys = []; + + foreach ($jobKeyList as $jobKey){ + $filter = array_filter($keys, function ($key) use($jobKey){ + + if($jobKey['key'] === $key->key){ + return true; + } + + // compare only last 5 chars (hidden keys) + return substr($jobKey['key'], -5) === substr($key->key, -5); + }); + + if(!empty($filter)){ + $sortedKeys[] = array_values($filter)[0]; + } + } + + return $sortedKeys; + } } \ No newline at end of file diff --git a/lib/Controller/API/NewController.php b/lib/Controller/API/NewController.php index 138f20b626..408905b0a4 100644 --- a/lib/Controller/API/NewController.php +++ b/lib/Controller/API/NewController.php @@ -53,6 +53,11 @@ class NewController extends ajaxController { */ private $private_tm_key; + /** + * @var array + */ + private $tm_prioritization = false; + private $private_tm_user = null; private $private_tm_pass = null; @@ -189,6 +194,7 @@ public function __construct() { 'options' => [ 'default' => 1, 'min_range' => 0 ] ], 'private_tm_key' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], + 'private_tm_key_json' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], 'subject' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ], @@ -246,7 +252,7 @@ public function __construct() { * Note 2022-10-13 * ---------------------------------- * - * We trim every space private_tm_key + * We trim every space in instructions * in order to avoid mispelling errors * */ @@ -657,21 +663,22 @@ public function doAction() { $projectStructure[ 'project_name' ] = $this->postInput[ 'project_name' ]; $projectStructure[ 'job_subject' ] = $this->postInput[ 'subject' ]; - $projectStructure[ 'private_tm_key' ] = $this->private_tm_key; - $projectStructure[ 'private_tm_user' ] = $this->private_tm_user; - $projectStructure[ 'private_tm_pass' ] = $this->private_tm_pass; - $projectStructure[ 'uploadToken' ] = $uploadFile->getDirUploadToken(); - $projectStructure[ 'array_files' ] = $arFiles; //list of file name - $projectStructure[ 'array_files_meta' ] = $arMeta; //list of file metadata - $projectStructure[ 'source_language' ] = $this->postInput[ 'source_lang' ]; - $projectStructure[ 'target_language' ] = explode( ',', $this->postInput[ 'target_lang' ] ); - $projectStructure[ 'mt_engine' ] = $this->postInput[ 'mt_engine' ]; - $projectStructure[ 'tms_engine' ] = $this->postInput[ 'tms_engine' ]; - $projectStructure[ 'status' ] = Constants_ProjectStatus::STATUS_NOT_READY_FOR_ANALYSIS; - $projectStructure[ 'owner' ] = $this->user->email; - $projectStructure[ 'metadata' ] = $this->metadata; - $projectStructure[ 'pretranslate_100' ] = (int)!!$this->postInput[ 'pretranslate_100' ]; // Force pretranslate_100 to be 0 or 1 - $projectStructure[ 'pretranslate_101' ] = isset( $this->postInput[ 'pretranslate_101' ] ) ? (int)$this->postInput[ 'pretranslate_101' ] : 1; + $projectStructure[ 'private_tm_key' ] = $this->private_tm_key; + $projectStructure[ 'private_tm_user' ] = $this->private_tm_user; + $projectStructure[ 'private_tm_pass' ] = $this->private_tm_pass; + $projectStructure[ 'uploadToken' ] = $uploadFile->getDirUploadToken(); + $projectStructure[ 'array_files' ] = $arFiles; //list of file name + $projectStructure[ 'array_files_meta' ] = $arMeta; //list of file metadata + $projectStructure[ 'source_language' ] = $this->postInput[ 'source_lang' ]; + $projectStructure[ 'target_language' ] = explode( ',', $this->postInput[ 'target_lang' ] ); + $projectStructure[ 'mt_engine' ] = $this->postInput[ 'mt_engine' ]; + $projectStructure[ 'tms_engine' ] = $this->postInput[ 'tms_engine' ]; + $projectStructure[ 'tm_prioritization' ] = $this->tm_prioritization; + $projectStructure[ 'status' ] = Constants_ProjectStatus::STATUS_NOT_READY_FOR_ANALYSIS; + $projectStructure[ 'owner' ] = $this->user->email; + $projectStructure[ 'metadata' ] = $this->metadata; + $projectStructure[ 'pretranslate_100' ] = (int)!!$this->postInput[ 'pretranslate_100' ]; // Force pretranslate_100 to be 0 or 1 + $projectStructure[ 'pretranslate_101' ] = isset( $this->postInput[ 'pretranslate_101' ] ) ? (int)$this->postInput[ 'pretranslate_101' ] : 1; $projectStructure[ 'dictation' ] = $this->postInput[ 'dictation' ] ?? null; $projectStructure[ 'show_whitespace' ] = $this->postInput[ 'show_whitespace' ] ?? null; @@ -1030,10 +1037,44 @@ private static function __parseTmKeyInput( $tmKeyString ) { protected function __validateTmAndKeys() { try { - $this->private_tm_key = array_map( + if(!empty($this->postInput[ 'private_tm_key_json' ])){ + $json = html_entity_decode( $this->postInput[ 'private_tm_key_json' ] ); + + // first check if `filters_extraction_parameters` is a valid JSON + if ( !Utils::isJson( $json ) ) { + throw new Exception( "filters_extraction_parameters is not a valid JSON" ); + } + + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/private_tm_key_json.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $json; + + $validator = new JSONValidator( $schema ); + $validator->validate( $validatorObject ); + + $privateTmKeyJsonObject = json_decode($json); + + $this->tm_prioritization = $privateTmKeyJsonObject->tm_prioritization; + + $this->private_tm_key = array_map( + function ($item){ + return [ + 'key' => $item->key, + 'r' => $item->read, + 'w' => $item->write, + 'penalty' => $item->penalty, + ]; + }, + $privateTmKeyJsonObject->keys + ); + + } else { + $this->private_tm_key = array_map( [ 'NewController', '__parseTmKeyInput' ], explode( ",", $this->postInput[ 'private_tm_key' ] ) - ); + ); + } } catch ( Exception $e ) { throw new Exception( $e->getMessage(), -6 ); } @@ -1071,10 +1112,11 @@ protected function __validateTmAndKeys() { $this->private_tm_key[ $__key_idx ] = [ - 'key' => $newUser->key, - 'name' => null, - 'r' => $tm_key[ 'r' ], - 'w' => $tm_key[ 'w' ] + 'key' => $newUser->key, + 'name' => null, + 'penalty' => $tm_key[ 'penalty' ] ?? null, + 'r' => $tm_key[ 'r' ], + 'w' => $tm_key[ 'w' ], ]; $this->new_keys[] = $newUser->key; @@ -1089,10 +1131,11 @@ protected function __validateTmAndKeys() { $uid = $this->user->uid; $this_tm_key = [ - 'key' => $tm_key[ 'key' ], - 'name' => null, - 'r' => $tm_key[ 'r' ], - 'w' => $tm_key[ 'w' ] + 'key' => $tm_key[ 'key' ], + 'name' => null, + 'penalty' => $tm_key[ 'penalty' ] ?? null, + 'r' => $tm_key[ 'r' ], + 'w' => $tm_key[ 'w' ] ]; /** diff --git a/lib/Controller/API/V3/MetaDataController.php b/lib/Controller/API/V3/MetaDataController.php index 6ba27a25af..3829e55578 100644 --- a/lib/Controller/API/V3/MetaDataController.php +++ b/lib/Controller/API/V3/MetaDataController.php @@ -46,7 +46,7 @@ private function getProjectInfo( Projects_ProjectStruct $project ) { foreach ( $project->getMetadata() as $metadatum ) { $key = $metadatum->key; - $metadata->$key = $metadatum->getValue(); + $metadata->$key = is_numeric($metadatum->getValue()) ? (int)$metadatum->getValue() : $metadatum->getValue(); } return $metadata; @@ -64,7 +64,7 @@ private function getJobMetaData( Jobs_JobStruct $job ) { foreach ( $jobMetaDataDao->getByJobIdAndPassword( $job->id, $job->password, 60 * 5 ) as $metadatum ) { $key = $metadatum->key; - $metadata->$key = $metadatum->value; + $metadata->$key = is_numeric($metadatum->value) ? (int)$metadatum->value : $metadatum->value; } return $metadata; @@ -72,8 +72,8 @@ private function getJobMetaData( Jobs_JobStruct $job ) { /** * @param Jobs_JobStruct $job - * * @return array + * @throws \ReflectionException */ private function getJobFilesMetaData( Jobs_JobStruct $job ) { @@ -84,7 +84,7 @@ private function getJobFilesMetaData( Jobs_JobStruct $job ) { $metadatum = new stdClass(); foreach ( $filesMetaDataDao->getByJobIdProjectAndIdFile( $job->getProject()->id, $file->id, 60 * 5 ) as $meta ) { $key = $meta->key; - $metadatum->$key = $meta->value; + $metadatum->$key = is_numeric($meta->value) ? (int)$meta->value : $meta->value; } $metadataObject = new \stdClass(); diff --git a/lib/Controller/createProjectController.php b/lib/Controller/createProjectController.php index 6443d37527..dc82b2bce9 100644 --- a/lib/Controller/createProjectController.php +++ b/lib/Controller/createProjectController.php @@ -67,6 +67,8 @@ class createProjectController extends ajaxController { public $postInput; + private $tm_prioritization; + /** * @throws Exception */ @@ -87,6 +89,7 @@ public function __construct() { 'private_tm_key' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], 'pretranslate_100' => [ 'filter' => FILTER_VALIDATE_INT ], 'pretranslate_101' => [ 'filter' => FILTER_VALIDATE_INT ], + 'tm_prioritization' => [ 'filter' => FILTER_VALIDATE_INT ], 'id_team' => [ 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR ], 'mmt_glossaries' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], @@ -116,6 +119,9 @@ public function __construct() { $this->postInput = filter_input_array( INPUT_POST, $filterArgs ); //first we check the presence of a list from tm management panel + + $this->__validateTMKeysArray($_POST[ 'private_keys_list' ]); + $array_keys = json_decode( $_POST[ 'private_keys_list' ], true ); $array_keys = array_merge( $array_keys[ 'ownergroup' ], $array_keys[ 'mine' ], $array_keys[ 'anonymous' ] ); @@ -176,6 +182,7 @@ public function __construct() { $this->show_whitespace = $this->postInput[ 'show_whitespace' ] ?? null; $this->character_counter = $this->postInput[ 'character_counter' ] ?? null; $this->ai_assistant = $this->postInput[ 'ai_assistant' ] ?? null; + $this->tm_prioritization = $this->postInput[ 'tm_prioritization' ] ?? null; $this->__setMetadataFromPostInput(); @@ -355,6 +362,7 @@ public function doAction() { $projectStructure[ 'show_whitespace' ] = $this->show_whitespace; $projectStructure[ 'character_counter' ] = $this->character_counter; $projectStructure[ 'ai_assistant' ] = $this->ai_assistant; + $projectStructure[ 'tm_prioritization' ] = $this->tm_prioritization ?? null; // MMT Glossaries // (if $engine is not an MMT instance, ignore 'mmt_glossaries') @@ -515,6 +523,25 @@ private function __assignLastCreatedPid( $pid ) { $_SESSION[ 'last_created_pid' ] = $pid; } + /** + * @param $tm_keys + * @throws \Exception + */ + private function __validateTMKeysArray($tm_keys) + { + try { + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/job_keys.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $tm_keys; + + $validator = new JSONValidator( $schema ); + $validator->validate( $validatorObject ); + } catch ( Exception $e ) { + $this->result[ 'errors' ][] = [ "code" => -12, "message" => $e->getMessage() ]; + } + } + private function __validateTargetLangs( Languages $lang_handler ) { $targets = explode( ',', $this->target_lang ); $targets = array_map( 'trim', $targets ); diff --git a/lib/Controller/getContributionController.php b/lib/Controller/getContributionController.php index 89b5d287ed..510b59ae43 100644 --- a/lib/Controller/getContributionController.php +++ b/lib/Controller/getContributionController.php @@ -3,6 +3,7 @@ use Contribution\ContributionRequestStruct; use Contribution\Request; use Files\FilesPartsDao; +use Jobs\MetadataDao; use Matecat\SubFiltering\MateCatFilter; class getContributionController extends ajaxController { @@ -148,10 +149,61 @@ public function doAction() { $contributionRequest->userRole = TmKeyManagement_Filter::ROLE_TRANSLATOR; } - Request::contribution( $contributionRequest ); + $jobsMetadataDao = new MetadataDao(); + $dialect_strict = $jobsMetadataDao->get( $jobStruct->id, $jobStruct->password, 'dialect_strict', 10 * 60 ); + + if ( $dialect_strict !== null ) { + $contributionRequest->dialect_strict = $dialect_strict->value == 1; + } + + $tm_prioritization = $jobsMetadataDao->get( $jobStruct->id, $jobStruct->password, 'tm_prioritization', 10 * 60 ); - $this->result = [ "errors" => [], "data" => [ "message" => "OK", "id_client" => $this->id_client ] ]; + if ( $tm_prioritization !== null ) { + $contributionRequest->tm_prioritization = $tm_prioritization->value == 1; + } + + // penalty_key + $penalty_key = []; + $tmKeys = json_decode( $jobStruct->tm_keys, true ); + foreach ($tmKeys as $tmKey){ + if(isset($tmKey['penalty']) and is_numeric($tmKey['penalty'])){ + $penalty_key[] = $tmKey['penalty']; + } else { + $penalty_key[] = 0; + } + } + + if(!empty($penalty_key)){ + $contributionRequest->penalty_key = $penalty_key; + } + + Request::contribution( $contributionRequest ); + + $this->result = [ + "errors" => [], + "data" => [ + "message" => "OK", + "id_client" => $this->id_client, + "request" => [ + 'session_id' => $contributionRequest->getSessionId(), + 'id_file' => (int)$contributionRequest->id_file, + 'id_job' => (int)$contributionRequest->id_job, + 'password' => $contributionRequest->password, + 'contexts' => $contributionRequest->contexts, + 'id_client' => $contributionRequest->id_client, + 'userRole' => $contributionRequest->userRole, + 'tm_prioritization' => $contributionRequest->tm_prioritization, + 'penalty_key' => $contributionRequest->penalty_key, + 'crossLangTargets' => $contributionRequest->crossLangTargets, + 'fromTarget' => $contributionRequest->fromTarget, + 'dialect_strict' => $contributionRequest->dialect_strict, + 'segmentId' => $contributionRequest->segmentId ? (string)$contributionRequest->segmentId : null, + 'resultNum' => (int)$contributionRequest->resultNum, + 'concordanceSearch' => $contributionRequest->concordanceSearch, + ] + ] + ]; } /** diff --git a/lib/Controller/updateJobKeysController.php b/lib/Controller/updateJobKeysController.php index f17d071502..9368121318 100644 --- a/lib/Controller/updateJobKeysController.php +++ b/lib/Controller/updateJobKeysController.php @@ -1,6 +1,8 @@ [ + 'filter' => FILTER_VALIDATE_BOOLEAN + ], 'job_id' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], @@ -59,6 +66,7 @@ public function __construct() { $this->received_password = $_postInput[ 'current_password' ]; $this->job_id = $_postInput[ 'job_id' ]; $this->job_pass = $_postInput[ 'job_pass' ]; + $this->tm_prioritization = $_postInput[ 'tm_prioritization' ]; $this->tm_keys = CatUtils::sanitizeJSON($_postInput[ 'data' ]); // this will be filtered inside the TmKeyManagement class $this->only_private = !$_postInput[ 'get_public_matches' ]; @@ -89,6 +97,13 @@ public function __construct() { $this->result[ 'errors' ][] = [ "code" => -10, "message" => "Wrong password" ]; } + // validate $this->tm_keys + try { + $this->validateTMKeysArray(); + } catch (Exception $exception){ + $this->result[ 'errors' ][] = [ "code" => -12, "message" => $exception->getMessage() ]; + } + $this->identifyUser(); } @@ -166,6 +181,7 @@ function doAction() { * */ $tm_keys = json_decode( $this->tm_keys, true ); + // validate $tm_keys $clientKeys = $this->jobData->getClientKeys($this->user, $this->userRole); @@ -210,6 +226,13 @@ function doAction() { $jobDao->updateStruct( $this->jobData, [ 'fields' => [ 'only_private_tm', 'tm_keys' ] ] ); $jobDao->destroyCache( $this->jobData ); + // update tm_prioritization job metadata + if($this->tm_prioritization !== null){ + $tm_prioritization = $this->tm_prioritization == true ? "1" : "0"; + $jobsMetadataDao = new Jobs\MetadataDao(); + $jobsMetadataDao->set( $this->id_job, $this->job_pass, 'tm_prioritization', $tm_prioritization ); + } + $this->result[ 'data' ] = 'OK'; } catch ( Exception $e ) { @@ -223,4 +246,18 @@ private function jobOwnerIsMe() { return $this->userIsLogged && $this->jobData[ 'owner' ] == $this->user->email; } + /** + * @throws Exception + */ + private function validateTMKeysArray() + { + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/job_keys.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $this->tm_keys; + + $validator = new JSONValidator( $schema ); + $validator->validate( $validatorObject ); + } + } \ No newline at end of file diff --git a/lib/Model/Jobs/JobStruct.php b/lib/Model/Jobs/JobStruct.php index 1f9a4d28f4..fb65f2b271 100644 --- a/lib/Model/Jobs/JobStruct.php +++ b/lib/Model/Jobs/JobStruct.php @@ -22,8 +22,8 @@ class Jobs_JobStruct extends DataAccess_AbstractDaoSilentStruct implements DataA public int $job_first_segment; public int $job_last_segment; - public string $source; - public string $target; + public string $source = 'es-US'; + public string $target = 'fr-FR'; public string $tm_keys = '[]'; public ?string $id_translator = null; public ?string $job_type = null; @@ -97,9 +97,9 @@ public function getTMProps(): array { $projectData = $this->getProject(); return [ - 'project_id' => $projectData->id, - 'project_name' => $projectData->name, - 'job_id' => $this->id, + 'project_id' => $projectData->id, + 'project_name' => $projectData->name, + 'job_id' => $this->id, ]; } diff --git a/lib/Model/Jobs/MetadataDao.php b/lib/Model/Jobs/MetadataDao.php index 8e3358f4d5..1b194e5190 100644 --- a/lib/Model/Jobs/MetadataDao.php +++ b/lib/Model/Jobs/MetadataDao.php @@ -10,6 +10,8 @@ class MetadataDao extends \DataAccess_AbstractDao { const TABLE = 'job_metadata'; const _query_metadata_by_job_id_key = "SELECT * FROM job_metadata WHERE id_job = :id_job AND `key` = :key "; + const _query_metadata_by_job_password = "SELECT * FROM job_metadata WHERE id_job = :id_job AND password = :password "; + const _query_metadata_by_job_password_key = "SELECT * FROM job_metadata WHERE id_job = :id_job AND password = :password AND `key` = :key "; /** * @param $id_job @@ -35,19 +37,15 @@ public function destroyCacheByJobId( $id_job, $key ) { } /** - * @param $id_job - * @param $password + * @param $id_job + * @param $password * @param int $ttl - * - * @return DataAccess_IDaoStruct[] + * @return array|DataAccess_IDaoStruct[] + * @throws \ReflectionException */ public function getByJobIdAndPassword( $id_job, $password, $ttl = 0 ) { - $stmt = $this->_getStatementForQuery( - "SELECT * FROM job_metadata WHERE " . - " id_job = :id_job " . - " AND password = :password " - ); + $stmt = $this->_getStatementForQuery(self::_query_metadata_by_job_password); $result = $this->setCacheTTL( $ttl )->_fetchObject( $stmt, new MetadataStruct(), [ 'id_job' => $id_job, @@ -57,6 +55,12 @@ public function getByJobIdAndPassword( $id_job, $password, $ttl = 0 ) { return @$result; } + public function destroyCacheByJobAndPassword( $id_job, $password ) { + $stmt = $this->_getStatementForQuery( self::_query_metadata_by_job_password ); + + return $this->_destroyObjectCache( $stmt, MetadataStruct::class, [ 'id_job' => $id_job, 'password' => $password ] ); + } + /** * @param $id_job * @param $password @@ -66,12 +70,7 @@ public function getByJobIdAndPassword( $id_job, $password, $ttl = 0 ) { * @return MetadataStruct */ public function get( $id_job, $password, $key, $ttl = 0 ) { - $stmt = $this->_getStatementForQuery( - "SELECT * FROM job_metadata WHERE " . - " id_job = :id_job " . - " AND password = :password " . - " AND `key` = :key " - ); + $stmt = $this->_getStatementForQuery(self::_query_metadata_by_job_password_key); $result = $this->setCacheTTL( $ttl )->_fetchObject( $stmt, new MetadataStruct(), [ 'id_job' => $id_job, @@ -88,6 +87,16 @@ public function get( $id_job, $password, $key, $ttl = 0 ) { } + public function destroyCacheByJobAndPasswordAndKey( $id_job, $password, $key ) { + $stmt = $this->_getStatementForQuery( self::_query_metadata_by_job_password_key ); + + return $this->_destroyObjectCache( $stmt, MetadataStruct::class, [ + 'id_job' => $id_job, + 'password' => $password, + 'key' => $key + ] ); + } + /** * @param $id_job * @param $password @@ -113,6 +122,8 @@ public function set( $id_job, $password, $key, $value ) { ] ); $this->destroyCacheByJobId( $id_job, $key ); + $this->destroyCacheByJobAndPassword( $id_job, $password ); + $this->destroyCacheByJobAndPasswordAndKey( $id_job, $password, $key ); return $this->get( $id_job, $password, $key ); } diff --git a/lib/Model/Projects/ProjectTemplateDao.php b/lib/Model/Projects/ProjectTemplateDao.php index c89a800355..0b1e467e06 100644 --- a/lib/Model/Projects/ProjectTemplateDao.php +++ b/lib/Model/Projects/ProjectTemplateDao.php @@ -52,6 +52,7 @@ public static function getDefaultTemplate( $uid ): ProjectTemplateStruct { $default->uid = $uid; $default->pretranslate_100 = false; $default->pretranslate_101 = true; + $default->tm_prioritization = false; $default->get_public_matches = true; $default->payable_rate_template_id = 0; $default->qa_model_template_id = 0; @@ -379,9 +380,9 @@ static function getByIdAndUser( int $id, int $uid, int $ttl = 60 ): ?ProjectTemp public static function save( ProjectTemplateStruct $projectTemplateStruct ): ProjectTemplateStruct { $sql = "INSERT INTO " . self::TABLE . - " ( `name`, `is_default`, `uid`, `id_team`, `segmentation_rule`, `tm`, `mt`, `payable_rate_template_id`,`qa_model_template_id`, `filters_template_id`, `xliff_config_template_id`, `pretranslate_100`, `pretranslate_101`, `get_public_matches`, `subject`, `source_language`, `target_language`, `created_at` ) " . + " ( `name`, `is_default`, `uid`, `id_team`, `segmentation_rule`, `tm`, `mt`, `payable_rate_template_id`,`qa_model_template_id`, `filters_template_id`, `xliff_config_template_id`, `pretranslate_100`, `pretranslate_101`, `tm_prioritization`, `get_public_matches`, `subject`, `source_language`, `target_language`, `created_at` ) " . " VALUES " . - " ( :name, :is_default, :uid, :id_team, :segmentation_rule, :tm, :mt, :payable_rate_template_id, :qa_model_template_id, :filters_template_id, :xliff_config_template_id, :pretranslate_100, :pretranslate_101, :get_public_matches, :subject, :source_language, :target_language, :now ); "; + " ( :name, :is_default, :uid, :id_team, :segmentation_rule, :tm, :mt, :payable_rate_template_id, :qa_model_template_id, :filters_template_id, :xliff_config_template_id, :pretranslate_100, :pretranslate_101, :tm_prioritization, :get_public_matches, :subject, :source_language, :target_language, :now ); "; $now = ( new DateTime() )->format( 'Y-m-d H:i:s' ); @@ -397,6 +398,7 @@ static function save( ProjectTemplateStruct $projectTemplateStruct ): ProjectTem "tm" => $projectTemplateStruct->tm, "pretranslate_100" => $projectTemplateStruct->pretranslate_100, "pretranslate_101" => $projectTemplateStruct->pretranslate_101, + "tm_prioritization" => $projectTemplateStruct->tm_prioritization, "get_public_matches" => $projectTemplateStruct->get_public_matches, "payable_rate_template_id" => $projectTemplateStruct->payable_rate_template_id, "qa_model_template_id" => $projectTemplateStruct->qa_model_template_id, @@ -441,6 +443,7 @@ static function update( ProjectTemplateStruct $projectTemplateStruct ): ProjectT `mt` = :mt, `pretranslate_100` = :pretranslate_100, `pretranslate_101` = :pretranslate_101, + `tm_prioritization` = :tm_prioritization, `get_public_matches` = :get_public_matches, `payable_rate_template_id` = :payable_rate_template_id, `qa_model_template_id` = :qa_model_template_id, @@ -465,6 +468,7 @@ static function update( ProjectTemplateStruct $projectTemplateStruct ): ProjectT "tm" => $projectTemplateStruct->tm, "pretranslate_100" => $projectTemplateStruct->pretranslate_100, "pretranslate_101" => $projectTemplateStruct->pretranslate_101, + "tm_prioritization" => $projectTemplateStruct->tm_prioritization, "get_public_matches" => $projectTemplateStruct->get_public_matches, "payable_rate_template_id" => $projectTemplateStruct->payable_rate_template_id, "qa_model_template_id" => $projectTemplateStruct->qa_model_template_id, diff --git a/lib/Model/Projects/ProjectTemplateStruct.php b/lib/Model/Projects/ProjectTemplateStruct.php index 33ed75ce71..2cf96e76c3 100644 --- a/lib/Model/Projects/ProjectTemplateStruct.php +++ b/lib/Model/Projects/ProjectTemplateStruct.php @@ -23,6 +23,7 @@ class ProjectTemplateStruct extends DataAccess_AbstractDaoSilentStruct implement public int $xliff_config_template_id = 0; public bool $pretranslate_100 = false; public bool $pretranslate_101 = false; + public bool $tm_prioritization = false; public bool $get_public_matches = true; public string $created_at; public ?string $modified_at = null; @@ -48,6 +49,7 @@ public function hydrateFromJSON( string $json, int $uid, ?int $id = null ): Proj $this->segmentation_rule = (!empty($json->segmentation_rule)) ? json_encode( $json->segmentation_rule ) : null; $this->pretranslate_100 = $json->pretranslate_100; $this->pretranslate_101 = $json->pretranslate_101; + $this->tm_prioritization = $json->tm_prioritization; $this->get_public_matches = $json->get_public_matches; $this->mt = json_encode( $json->mt ); $this->tm = (!empty($json->tm)) ? json_encode( $json->tm ) : null; @@ -124,6 +126,7 @@ public function jsonSerialize(): array { 'get_public_matches' => $this->get_public_matches, 'pretranslate_100' => $this->pretranslate_100, 'pretranslate_101' => $this->pretranslate_101, + 'tm_prioritization' => $this->tm_prioritization, 'subject' => $this->subject, 'source_language' => $this->source_language, 'target_language' => $this->getTargetLanguage(), diff --git a/lib/Utils/Analysis/Workers/FastAnalysis.php b/lib/Utils/Analysis/Workers/FastAnalysis.php index 3cde98be10..729d18bc68 100644 --- a/lib/Utils/Analysis/Workers/FastAnalysis.php +++ b/lib/Utils/Analysis/Workers/FastAnalysis.php @@ -18,6 +18,7 @@ use FilesStorage\AbstractFilesStorage; use FilesStorage\FilesStorageFactory; use INIT; +use Jobs\MetadataDao; use Jobs_JobDao; use Log; use Model\Analysis\AnalysisDao; @@ -685,6 +686,13 @@ protected function _insertFastAnalysis( $queue_element[ 'id_job' ] = $id_job; $queue_element[ 'payable_rates' ] = $jobs_payable_rates[ $id_job ]; // assign the right payable rate for the current job + $jobsMetadataDao = new MetadataDao(); + $tm_prioritization = $jobsMetadataDao->get($id_job, $password, 'tm_prioritization', 10 * 60 ); + + if ( $tm_prioritization !== null ) { + $queue_element['tm_prioritization'] = $tm_prioritization->value == 1; + } + $element = new QueueElement(); $element->params = $queue_element; $element->classLoad = '\Analysis\Workers\TMAnalysisWorker'; diff --git a/lib/Utils/Analysis/Workers/TMAnalysisWorker.php b/lib/Utils/Analysis/Workers/TMAnalysisWorker.php index e32ef0e3f6..31ce8c4914 100644 --- a/lib/Utils/Analysis/Workers/TMAnalysisWorker.php +++ b/lib/Utils/Analysis/Workers/TMAnalysisWorker.php @@ -527,6 +527,7 @@ protected function _getMatches( QueueElement $queueElement ): array { $_config[ 'context_before' ] = $queueElement->params->context_before; $_config[ 'context_after' ] = $queueElement->params->context_after; $_config[ 'additional_params' ] = @$queueElement->params->additional_params; + $_config[ 'priority_key' ] = $queueElement->params->tm_prioritization ?? null; $jobsMetadataDao = new MetadataDao(); $dialect_strict = $jobsMetadataDao->get( $queueElement->params->id_job, $queueElement->params->password, 'dialect_strict', 10 * 60 ); @@ -535,13 +536,26 @@ protected function _getMatches( QueueElement $queueElement ): array { $_config[ 'dialect_strict' ] = $dialect_strict->value == 1; } + // penalty_key + $penalty_key = []; $tm_keys = TmKeyManagement_TmKeyManagement::getJobTmKeys( $queueElement->params->tm_keys, 'r', 'tm' ); + if ( is_array( $tm_keys ) && !empty( $tm_keys ) ) { foreach ( $tm_keys as $tm_key ) { $_config[ 'id_user' ][] = $tm_key->key; + + if(isset($tm_key->penalty) and is_numeric($tm_key->penalty)){ + $penalty_key[] = $tm_key->penalty; + } else { + $penalty_key[] = 0; + } } } + if(!empty($penalty_key)){ + $_config['penalty_key'] = $penalty_key; + } + $_config[ 'num_result' ] = 3; $id_mt_engine = $queueElement->params->id_mt_engine; diff --git a/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php b/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php index 420a2a16ab..9bbcfa71a5 100644 --- a/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php +++ b/lib/Utils/AsyncTasks/Workers/GetContributionWorker.php @@ -164,16 +164,16 @@ protected function _publishPayload( array $content, ContributionRequestStruct $c } $_object = [ - '_type' => $type, - 'data' => [ - 'id_job' => $contributionStruct->getJobStruct()->id, - 'passwords' => $contributionStruct->getJobStruct()->password, - 'payload' => [ - 'id_segment' => $contributionStruct->segmentId, - 'matches' => $content, - ], - 'id_client' => $contributionStruct->id_client, - ] + '_type' => $type, + 'data' => [ + 'id_job' => $contributionStruct->getJobStruct()->id, + 'passwords' => $contributionStruct->getJobStruct()->password, + 'payload' => [ + 'id_segment' => (string)$contributionStruct->segmentId, + 'matches' => $content, + ], + 'id_client' => $contributionStruct->id_client, + ] ]; $this->publishToSseTopic( $_object ); @@ -221,10 +221,10 @@ private static function __compareScore( $a, $b ) { public function normalizeTMMatches( array &$matches, ContributionRequestStruct $contributionStruct, FeatureSet $featureSet, $targetLang ) { $Filter = MateCatFilter::getInstance( - $featureSet, - $contributionStruct->getJobStruct()->source, - $targetLang, - json_decode( $contributionStruct->dataRefMap, true ) + $featureSet, + $contributionStruct->getJobStruct()->source, + $targetLang, + $contributionStruct->dataRefMap ); foreach ( $matches as &$match ) { @@ -266,9 +266,9 @@ public function normalizeTMMatches( array &$matches, ContributionRequestStruct $ } $match[ 'created_by' ] = Utils::changeMemorySuggestionSource( - $match, - $contributionStruct->getJobStruct()->tm_keys, - $user->uid + $match, + $contributionStruct->getJobStruct()->tm_keys, + $user->uid ); } @@ -419,11 +419,17 @@ protected function _getMatches( ContributionRequestStruct $contributionStruct, $ $_config[ 'num_result' ] = $contributionStruct->resultNum; $_config[ 'isConcordance' ] = $contributionStruct->concordanceSearch; - $jobsMetadataDao = new MetadataDao(); - $dialect_strict = $jobsMetadataDao->get( $jobStruct->id, $jobStruct->password, 'dialect_strict' ); + if ( $contributionStruct->dialect_strict !== null ) { + $_config[ 'dialect_strict' ] = $contributionStruct->dialect_strict; + } + + if ( $contributionStruct->tm_prioritization !== null ) { + + $_config[ 'priority_key' ] = $contributionStruct->tm_prioritization; + } - if ( $dialect_strict !== null ) { - $_config[ 'dialect_strict' ] = $dialect_strict->value == 1; + if ( !empty($contributionStruct->penalty_key) ) { + $_config[ 'penalty_key' ] = $contributionStruct->penalty_key; } if ( $contributionStruct->concordanceSearch && $contributionStruct->fromTarget ) { @@ -493,7 +499,7 @@ protected function _getMatches( ContributionRequestStruct $contributionStruct, $ if ( !empty( $temp_matches ) ) { - $dataRefMap = ( isset( $contributionStruct->dataRefMap ) and $contributionStruct->dataRefMap !== '' ) ? json_decode( $contributionStruct->dataRefMap, true ) : []; + $dataRefMap = $contributionStruct->dataRefMap ?: []; $tms_match = $temp_matches->get_matches_as_array( 2, $dataRefMap, $_config[ 'source' ], $_config[ 'target' ] ); } } @@ -611,7 +617,13 @@ private function _sortByLenDesc( $stringA, $stringB ) { */ private function updateAnalysisSuggestion( $matches, ContributionRequestStruct $contributionStruct, FeatureSet $featureSet ) { - if ( is_array( $matches ) and count( $matches ) > 0 ) { + if ( + is_array( $matches ) and + count( $matches ) > 0 and + $contributionStruct->segmentId !== null and + $contributionStruct->getJobStruct() !== null and + !empty($contributionStruct->getJobStruct()->id) + ) { $segmentTranslation = Translations_SegmentTranslationDao::findBySegmentAndJob( $contributionStruct->segmentId, $contributionStruct->getJobStruct()->id ); @@ -638,9 +650,9 @@ private function updateAnalysisSuggestion( $matches, ContributionRequestStruct $ } $matches[ $k ][ 'created_by' ] = Utils::changeMemorySuggestionSource( - $m, - $contributionStruct->getJobStruct()->tm_keys, - $user->uid + $m, + $contributionStruct->getJobStruct()->tm_keys, + $user->uid ); } } @@ -655,9 +667,9 @@ private function updateAnalysisSuggestion( $matches, ContributionRequestStruct $ $data[ 'suggestion_match' ] = str_replace( '%', '', $match[ 'match' ] ); $where = [ - 'id_segment' => $contributionStruct->segmentId, - 'id_job' => $contributionStruct->getJobStruct()->id, - 'status' => Constants_TranslationStatus::STATUS_NEW + 'id_segment' => $contributionStruct->segmentId, + 'id_job' => $contributionStruct->getJobStruct()->id, + 'status' => Constants_TranslationStatus::STATUS_NEW ]; Translations_SegmentTranslationDao::updateFirstTimeOpenedContribution( $data, $where ); diff --git a/lib/Utils/Contribution/ContributionRequestStruct.php b/lib/Utils/Contribution/ContributionRequestStruct.php index 4f7218dd4a..8a3501d357 100644 --- a/lib/Utils/Contribution/ContributionRequestStruct.php +++ b/lib/Utils/Contribution/ContributionRequestStruct.php @@ -24,14 +24,14 @@ class ContributionRequestStruct extends ShapelessConcreteStruct implements DataA public $jobStruct; - public $dataRefMap; + public $dataRefMap = []; public $projectStruct; public $contexts = [ - 'context_before' => null, - 'segment' => null, - 'context_after' => null + 'context_before' => null, + 'segment' => null, + 'context_after' => null ]; /** @@ -57,7 +57,7 @@ class ContributionRequestStruct extends ShapelessConcreteStruct implements DataA /** * @var int */ - public $resultNum = 3; + public $resultNum = 10; /** * @var bool @@ -69,9 +69,13 @@ class ContributionRequestStruct extends ShapelessConcreteStruct implements DataA */ public $fromTarget = false; - public $crossLangTargets = [] ; + public $dialect_strict = null; + + public $tm_prioritization = null; + + public $penalty_key = []; # Private members /** diff --git a/lib/Utils/Engines/MyMemory.php b/lib/Utils/Engines/MyMemory.php index 1d27392157..9025100884 100644 --- a/lib/Utils/Engines/MyMemory.php +++ b/lib/Utils/Engines/MyMemory.php @@ -34,7 +34,7 @@ class Engines_MyMemory extends Engines_AbstractEngine { 'prop' => null, 'get_mt' => 1, 'id_user' => null, - 'num_result' => 3, + 'num_result' => 10, 'mt_only' => false, 'isConcordance' => false, 'isGlossary' => false, @@ -195,6 +195,23 @@ public function get( $_config ) { $parameters[ 'numres' ] = $_config[ 'num_result' ]; $parameters[ 'client_id' ] = isset( $_config[ 'uid' ] ) ? $_config[ 'uid' ] : 0; + // TM prioritization + $parameters[ 'priority_key' ] = (isset( $_config[ 'priority_key' ] ) and $_config[ 'priority_key' ] == true) ? 1 : 0; + + if(isset($_config[ 'penalty_key' ] ) and !empty($_config[ 'penalty_key' ]) ){ + $penalties = []; + + foreach ($_config[ 'penalty_key' ] as $penalty){ + if(is_numeric($penalty)){ + $penalties[] = $penalty / 100; + } + } + + if(!empty($penalties)){ + $parameters[ 'penalty_key' ] = implode(",", $penalties); + } + } + if ( isset( $_config[ 'dialect_strict' ] ) ) { $parameters[ 'dialect_strict' ] = $_config[ 'dialect_strict' ]; } diff --git a/lib/Utils/Engines/Results/MyMemory/Matches.php b/lib/Utils/Engines/Results/MyMemory/Matches.php index 2e979951a6..320e6b4a22 100644 --- a/lib/Utils/Engines/Results/MyMemory/Matches.php +++ b/lib/Utils/Engines/Results/MyMemory/Matches.php @@ -35,6 +35,8 @@ class Engines_Results_MyMemory_Matches { public $source; public $target; + public $penalty; + public function __construct() { //NEEDED TO UNIFORM DATA as array( $matches ) @@ -136,6 +138,7 @@ public function getMatches( $layerNum = 2, array $dataRefMap = [], $source = nul $this->tm_properties = array_key_exists( 'tm_properties', $match ) ? json_decode( $match[ 'tm_properties' ], true ) : []; $this->target = array_key_exists( 'target', $match ) ? $match[ 'target' ] : $target; $this->source = array_key_exists( 'source', $match ) ? $match[ 'source' ] : $source; + $this->penalty = array_key_exists( 'penalty', $match ) ? $match[ 'penalty' ] : $source; $this->prop = $match[ 'prop' ]; diff --git a/lib/Utils/ProjectManager.php b/lib/Utils/ProjectManager.php index fd7ad1e8a0..9771f13293 100644 --- a/lib/Utils/ProjectManager.php +++ b/lib/Utils/ProjectManager.php @@ -113,6 +113,8 @@ class ProjectManager { */ protected $metadataDao; + protected $tm_prioritization; + /** * ProjectManager constructor. * @@ -199,7 +201,8 @@ public function __construct( ArrayObject $projectStructure = null ) { 'ai_assistant' => null, 'filters_extraction_parameters' => new RecursiveArrayObject(), 'xliff_parameters' => new RecursiveArrayObject(), - 'mt_evaluation' => false + 'mt_evaluation' => false, + 'tm_prioritization' => null ] ); } @@ -1336,6 +1339,7 @@ protected function _createJobs( ArrayObject $projectStructure ) { $newTmKey->tm = true; $newTmKey->glos = true; $newTmKey->owner = true; + $newTmKey->penalty = $tmKeyObj[ 'penalty' ] ?? null; $newTmKey->name = $tmKeyObj[ 'name' ]; $newTmKey->key = $tmKeyObj[ 'key' ]; $newTmKey->r = $tmKeyObj[ 'r' ]; @@ -1387,6 +1391,12 @@ protected function _createJobs( ArrayObject $projectStructure ) { $projectStructure[ 'array_jobs' ][ 'payable_rates' ]->offsetSet( $newJob->id, $payableRates ); $jobsMetadataDao = new Jobs\MetadataDao(); + + // tm_prioritization + if ( isset( $projectStructure[ 'tm_prioritization' ] ) ) { + $jobsMetadataDao->set( $newJob->id, $newJob->password, 'tm_prioritization', ($projectStructure[ 'tm_prioritization' ] == true ? "1" : "0") ); + } + // dialect_strict if ( isset( $projectStructure[ 'dialect_strict' ] ) ) { $dialectStrictObj = json_decode( $projectStructure[ 'dialect_strict' ], true ); diff --git a/lib/Utils/TmKeyManagement/TmKeyManagement.php b/lib/Utils/TmKeyManagement/TmKeyManagement.php index 9c1a94dff8..b7c9436196 100644 --- a/lib/Utils/TmKeyManagement/TmKeyManagement.php +++ b/lib/Utils/TmKeyManagement/TmKeyManagement.php @@ -248,6 +248,15 @@ public static function sanitize( TmKeyManagement_TmKeyStruct $obj ){ $obj->target = filter_var( $obj->target, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ); } + if( !is_null( $obj->penalty) ){ + + $obj->penalty = filter_var( $obj->penalty, FILTER_SANITIZE_NUMBER_INT ); + + if(is_numeric($obj->penalty) and ($obj->penalty < 0 or $obj->penalty > 100)){ + throw new DomainException("Penalty value must be included in the interval [0-100]"); + } + } + return $obj; } @@ -361,27 +370,28 @@ public static function mergeJsonKeys( $Json_clientKeys, $Json_jobKeys, $userRole } //override the static values - $_job_ket_element = $reverse_lookup_client_json[ 'elements' ][ $_index_position ]; - $_job_Key->tm = filter_var( $_job_ket_element->tm, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); - $_job_Key->glos = filter_var( $_job_ket_element->glos, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_key_element = $reverse_lookup_client_json[ 'elements' ][ $_index_position ]; + $_job_Key->tm = filter_var( $_job_key_element->tm, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->glos = filter_var( $_job_key_element->glos, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->penalty = filter_var( $_job_key_element->penalty, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE ); if ( $userRole == TmKeyManagement_Filter::OWNER ) { //override grants - $_job_Key->r = filter_var( $_job_ket_element->r, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); - $_job_Key->w = filter_var( $_job_ket_element->w, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->r = filter_var( $_job_key_element->r, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->w = filter_var( $_job_key_element->w, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); } elseif ( $userRole == TmKeyManagement_Filter::ROLE_REVISOR || $userRole == TmKeyManagement_Filter::ROLE_TRANSLATOR ) { //override role specific grants - $_job_Key->{TmKeyManagement_Filter::$GRANTS_MAP[ $userRole ][ 'r' ]} = filter_var( $_job_ket_element->r, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); - $_job_Key->{TmKeyManagement_Filter::$GRANTS_MAP[ $userRole ][ 'w' ]} = filter_var( $_job_ket_element->w, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->{TmKeyManagement_Filter::$GRANTS_MAP[ $userRole ][ 'r' ]} = filter_var( $_job_key_element->r, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $_job_Key->{TmKeyManagement_Filter::$GRANTS_MAP[ $userRole ][ 'w' ]} = filter_var( $_job_key_element->w, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); } //change name if modified - if ( $_job_Key->name != $_job_ket_element->name ) { - $_job_Key->name = $_job_ket_element->name; + if ( $_job_Key->name != $_job_key_element->name ) { + $_job_Key->name = $_job_key_element->name; } //set as owner if it is but should be already set diff --git a/lib/Utils/TmKeyManagement/TmKeyStruct.php b/lib/Utils/TmKeyManagement/TmKeyStruct.php index 4780766c24..c6121794aa 100644 --- a/lib/Utils/TmKeyManagement/TmKeyStruct.php +++ b/lib/Utils/TmKeyManagement/TmKeyStruct.php @@ -125,6 +125,11 @@ class TmKeyManagement_TmKeyStruct extends stdClass implements JsonSerializable { */ public $complete_format = false; + /** + * @var null + */ + public $penalty = null; + /** * When a key return back from the client we have to know if it is hashed * @@ -205,7 +210,8 @@ public function getCrypt() { } /** - * @return Users_UserStruct[] + * @return array|Users_UserStruct[] + * @throws ReflectionException */ public function getInUsers() { @@ -240,6 +246,7 @@ public function jsonSerialize() { 'w_transl' => $this->w_transl, 'r_rev' => $this->r_rev, 'w_rev' => $this->w_rev, + 'penalty' => $this->penalty ?? 0, 'is_shared' => $this->is_shared, 'is_private' => $this->isEncryptedKey() ]; @@ -251,6 +258,7 @@ public function jsonSerialize() { 'owner' => $this->owner, 'name' => $this->name, 'key' => $this->key, + 'penalty' => $this->penalty ?? 0, 'is_shared' => $this->is_shared, ]; } diff --git a/migrations/20241203143100_add_mt_prioritization_to_project_templates.php b/migrations/20241203143100_add_mt_prioritization_to_project_templates.php new file mode 100644 index 0000000000..e3848a17c9 --- /dev/null +++ b/migrations/20241203143100_add_mt_prioritization_to_project_templates.php @@ -0,0 +1,14 @@ + :last-child { + grid-column: 6; + } } .row-content-create-resource { grid-template-columns: 100%; + padding: 0 10px; > form { padding: 0 !important; + background-color: unset; + + > :last-child { + grid-column: 7; + } } } } @@ -89,6 +110,7 @@ } .settings-panel-row-active { + padding: 10px; background-color: $transparentBlue; } @@ -98,6 +120,30 @@ } } +.translation-memory-glossary-tab { + .settings-panel-table { + .settings-panel-row-content:not(.row-content-create-resource):not( + .row-content-default-memory + ) + > :last-child { + grid-column: 7; + } + } + + .settings-panel-table:not(.translation-memory-glossary-tab-active-table) { + .settings-panel-row { + padding: 0 10px; + } + .settings-panel-row-content { + padding: 0 10px; + } + } + + .settings-panel-row-content > *:not(.settings-panel-row-drag-handle) { + padding: 0; + } +} + .translation-memory-glossary-tab-inactive-resources { margin-top: 20px; } @@ -108,6 +154,12 @@ justify-content: center; } +.tm-key-add-shared-resource { + input { + width: 90%; + } +} + .tm-key-row-name { width: 100%; padding: 4px; @@ -145,10 +197,8 @@ .tm-key-row-menu-button { .menu-button-wrapper { - gap: 8px; - > :first-child { - min-width: 170px; + min-width: 140px; border-radius: 2px; } @@ -160,7 +210,7 @@ } .just-button-import-tmx { - min-width: 170px; + min-width: 140px; border-radius: 2px; font-size: 16px; color: #000; @@ -175,6 +225,10 @@ } } +.tm-key-row-menu-button-dropdown { + margin-left: -10px; +} + .tm-key-row-icons { color: $grey; } @@ -312,3 +366,83 @@ border-top: 3px solid #ffcc01; background-color: $orangeDefaultTransparent2; } + +.translation-memory-glossary-tab-active-table { + .settings-panel-row { + padding: 0 10px; + + &:first-child { + padding: 10px !important; + } + + &:last-child { + padding: 0 10px 10px; + } + + &:last-child:not(:nth-child(2)) { + .settings-panel-row-active { + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + } + } + } + + .tm-prioritization-container { + display: flex; + align-items: center; + justify-content: space-between; + height: 56px; + padding: 20px; + background-color: $grey3; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + + .tm-prioritization-text-content { + display: flex; + flex-direction: column; + align-items: flex-start; + + h4 { + margin: 0; + font-size: 16px; + } + + span { + color: $grey6; + } + } + + .switch-container-outer { + width: 155px; + } +} + +.tm-row-penalty { + .tm-row-penalty-button, + .penalty-numeric-stepper-close-button { + border-radius: 2px; + font-size: 16px; + color: #000 !important; + background: $grey4 !important; + padding: 4px 8px; + text-align: center; + border: 1px solid $grey7 !important; + font-size: 16px !important; + + &:hover { + background-color: $grey5 !important; + } + } + + .tm-row-penalty-numeric-stepper { + display: flex; + gap: 5px; + } + + .penalty-numeric-stepper-close-button { + width: 28px !important; + height: 28px !important; + color: $grey6 !important; + } +} diff --git a/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js b/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js index 45a1b7b927..02322ebccc 100644 --- a/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js +++ b/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js @@ -17,6 +17,7 @@ export const updateJobKeys = async ({ currentPassword = config.currentPassword, getPublicMatches, dataTm, + tmPrioritization, }) => { const paramsData = { action: 'updateJobKeys', @@ -25,6 +26,7 @@ export const updateJobKeys = async ({ get_public_matches: getPublicMatches, data: dataTm, current_password: currentPassword, + tm_prioritization: tmPrioritization, } const formData = new FormData() diff --git a/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js b/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js index f433c83e94..b4d0fdfba8 100644 --- a/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js +++ b/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js @@ -8,12 +8,13 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' * @param {string} options.description * @returns {Promise} */ -export const updateTmKey = async ({key, description}) => { +export const updateTmKey = async ({key, description, penalty}) => { const paramsData = { action: 'userKeys', exec: 'update', key, description, + penalty, } const formData = new FormData() diff --git a/public/js/cat_source/es6/components/common/MenuButton/MenuButton.js b/public/js/cat_source/es6/components/common/MenuButton/MenuButton.js index 96c28b0f32..4907080384 100644 --- a/public/js/cat_source/es6/components/common/MenuButton/MenuButton.js +++ b/public/js/cat_source/es6/components/common/MenuButton/MenuButton.js @@ -9,6 +9,7 @@ export const MenuButton = ({ icon = , onClick, className = '', + dropdownClassName = '', itemsTarget, children, disabled, @@ -124,7 +125,7 @@ export const MenuButton = ({ ref={portalRef} className={`menu-button-items${ isVisibleRectArrow ? ' menu-button-items-rect-arrow' : '' - } ${isReversed ? 'menu-button-items-reversed' : ''}`} + } ${isReversed ? 'menu-button-items-reversed' : ''} ${dropdownClassName}`} style={{ left: itemsCoords.left, top: itemsCoords.top, diff --git a/public/js/cat_source/es6/components/common/NumericStepper/NumericStepper.js b/public/js/cat_source/es6/components/common/NumericStepper/NumericStepper.js new file mode 100644 index 0000000000..adfd568089 --- /dev/null +++ b/public/js/cat_source/es6/components/common/NumericStepper/NumericStepper.js @@ -0,0 +1,94 @@ +import React, {useEffect, useRef, useState} from 'react' +import PropTypes from 'prop-types' +import {Button, BUTTON_SIZE} from '../Button/Button' +import {debounce} from 'lodash' +import ArrowDown from '../../../../../../img/icons/ArrowDown' + +export const NumericStepper = ({ + value, + onChange, + minimumValue, + maximumValue, + name, + valuePlaceholder, + disabled, + stepValue = 1, +}) => { + const [valueInput, setValueInput] = useState('') + const [isInFocus, setIsInFocus] = useState(false) + + const ref = useRef() + + useEffect(() => { + const label = isInFocus + ? value + : !isInFocus && valuePlaceholder + ? valuePlaceholder + : value + + setValueInput(label) + }, [isInFocus, value, valuePlaceholder]) + + const increase = () => { + const newValue = value + stepValue + onChange(newValue <= maximumValue ? newValue : maximumValue) + } + const decrease = () => { + const newValue = value - stepValue + onChange(newValue >= minimumValue ? newValue : minimumValue) + } + const onChageInput = ({currentTarget: {value}}) => { + if (/^\d+$/g.test(value)) { + setValueInput(value) + + const newValue = parseInt(value) + onChange( + newValue > maximumValue + ? maximumValue + : newValue < minimumValue + ? minimumValue + : newValue, + ) + } else if (value === '') setValueInput(value) + } + + const debounceSelectAll = debounce((event) => event.target.select(), 100) + + return ( +
+ { + debounceSelectAll(event) + setIsInFocus(true) + }} + onBlur={() => setIsInFocus(false)} + onKeyUp={({key}) => key === 'Enter' && ref.current.blur()} + /> +
+ + +
+
+ ) +} + +NumericStepper.propTypes = { + value: PropTypes.number.isRequired, + onChange: PropTypes.func.isRequired, + minimumValue: PropTypes.number.isRequired, + maximumValue: PropTypes.number.isRequired, + name: PropTypes.string, + valuePlaceholder: PropTypes.string, + disabled: PropTypes.bool, + stepValue: PropTypes.number, +} diff --git a/public/js/cat_source/es6/components/common/NumericStepper/index.js b/public/js/cat_source/es6/components/common/NumericStepper/index.js new file mode 100644 index 0000000000..88559a96b1 --- /dev/null +++ b/public/js/cat_source/es6/components/common/NumericStepper/index.js @@ -0,0 +1 @@ +export * from './NumericStepper' diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TMCreateResourceRow.js b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TMCreateResourceRow.js index 034a11fdc6..9eb5f8d182 100644 --- a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TMCreateResourceRow.js +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TMCreateResourceRow.js @@ -292,7 +292,7 @@ export const TMCreateResourceRow = ({row}) => { data-testid={row.id} /> -
+
{row.id === SPECIAL_ROWS_ID.addSharedResource && ( { const {isImportTMXInProgress} = useContext(CreateProjectContext) @@ -54,6 +56,12 @@ export const TMKeyRow = ({row, onExpandRow}) => { const valueChange = useRef(false) + const penalty = row.penalty ?? 0 + + const onChangePenalty = (value) => { + updateRow({isLookup, isUpdating, penalty: value}) + } + const isMMSharedKey = row.id === SPECIAL_ROWS_ID.defaultTranslationMemory const isOwner = isOwnerOfKey(row.key) const getPublicMatches = currentProjectTemplate.get_public_matches @@ -83,7 +91,7 @@ export const TMKeyRow = ({row, onExpandRow}) => { setIsUpdating(isUpdating) } - const updateRow = ({isLookup, isUpdating}) => { + const updateRow = ({isLookup, isUpdating, penalty}) => { if (!isMMSharedKey) { const updatedKeys = tmKeys.map((tm) => tm.id === row.id @@ -96,6 +104,7 @@ export const TMKeyRow = ({row, onExpandRow}) => { : true, r: isLookup, w: !tm.isActive ? isLookup : isUpdating, + penalty: typeof penalty === 'number' ? penalty : tm.penalty, } : tm, ) @@ -138,6 +147,7 @@ export const TMKeyRow = ({row, onExpandRow}) => { if (name) { updateTmKey({ key: row.key, + penalty: row.penalty, description: name, }).catch(() => { CatToolActions.addNotification({ @@ -260,6 +270,35 @@ export const TMKeyRow = ({row, onExpandRow}) => { } } + const renderPenalty = + penalty > 0 ? ( +
+ + +
+ ) : ( + + ) + return (
@@ -300,10 +339,13 @@ export const TMKeyRow = ({row, onExpandRow}) => { data-testid={`tmkey-row-name-${row.id}`} >
-
{row.key}
+ {!isMMSharedKey &&
{row.key}
}
{iconDetails.icon}
+ {!isMMSharedKey && isOwner && row.isActive && ( +
{renderPenalty}
+ )} {!isMMSharedKey && isOwner ? (
{ onClick={() => handleExpandeRow(ImportTMX)} icon={} className="tm-key-row-menu-button" + dropdownClassName="tm-key-row-menu-button-dropdown" disabled={isImportTMXInProgress} itemsTarget={portalTarget} > diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TmPrioritization.js b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TmPrioritization.js new file mode 100644 index 0000000000..347a14ef35 --- /dev/null +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TmPrioritization.js @@ -0,0 +1,41 @@ +import React, {useCallback, useContext} from 'react' +import Switch from '../../../common/Switch' +import {SettingsPanelContext} from '../../SettingsPanelContext' + +export const TmPrioritization = () => { + const {currentProjectTemplate, modifyingCurrentTemplate} = + useContext(SettingsPanelContext) + + const isActive = currentProjectTemplate.tmPrioritization + const setIsActive = useCallback( + (value) => + modifyingCurrentTemplate((prevTemplate) => ({ + ...prevTemplate, + tmPrioritization: value, + })), + [modifyingCurrentTemplate], + ) + + const onChange = (isActive) => { + setIsActive(isActive) + } + + return ( +
+
+

TM prioritization

+ + Activate to prioritize translation memories based on their order.{' '} + + More details + + +
+ +
+ ) +} diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.js b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.js index 91db222eb5..cd9f8c70eb 100644 --- a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.js +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.js @@ -21,6 +21,7 @@ const COLUMNS_TABLE_ACTIVE = [ {name: ''}, {name: ''}, {name: ''}, + {name: ''}, ] const COLUMNS_TABLE_INACTIVE = [ @@ -87,7 +88,15 @@ export const orderTmKeys = (tmKeys, keysOrdered) => { export const getTmDataStructureToSendServer = ({tmKeys = [], keysOrdered}) => { const mine = tmKeys .filter(({key, isActive}) => isOwnerOfKey(key) && isActive) - .map(({tm, glos, key, name, r, w}) => ({tm, glos, key, name, r, w})) + .map(({tm, glos, key, name, r, w, penalty}) => ({ + tm, + glos, + key, + name, + r, + w, + penalty, + })) return JSON.stringify({ ownergroup: [], @@ -99,13 +108,8 @@ export const getTmDataStructureToSendServer = ({tmKeys = [], keysOrdered}) => { export const TranslationMemoryGlossaryTabContext = createContext({}) export const TranslationMemoryGlossaryTab = () => { - const { - tmKeys, - setTmKeys, - openLoginModal, - modifyingCurrentTemplate, - currentProjectTemplate, - } = useContext(SettingsPanelContext) + const {tmKeys, setTmKeys, modifyingCurrentTemplate, currentProjectTemplate} = + useContext(SettingsPanelContext) const getPublicMatches = currentProjectTemplate.getPublicMatches const isPretranslate100Active = currentProjectTemplate.pretranslate100 @@ -114,6 +118,7 @@ export const TranslationMemoryGlossaryTab = () => { ...prevTemplate, pretranslate100: value, })) + const tmPrioritization = currentProjectTemplate.tmPrioritization const [specialRows, setSpecialRows] = useState([ { ...DEFAULT_TRANSLATION_MEMORY, @@ -128,6 +133,7 @@ export const TranslationMemoryGlossaryTab = () => { tmKeys: undefined, getPublicMatches: undefined, currentProjectTemplate: undefined, + tmPrioritization: undefined, }) previousStatesRef.current.currentProjectTemplate = currentProjectTemplate @@ -146,7 +152,11 @@ export const TranslationMemoryGlossaryTab = () => { r: false, w: false, isActive: false, - ...(tmFromTemplate && {...tmFromTemplate, isActive: true}), + penalty: 0, + ...(tmFromTemplate && { + ...tmFromTemplate, + isActive: true, + }), name: tmItem.name, } }), @@ -184,6 +194,7 @@ export const TranslationMemoryGlossaryTab = () => { updateJobKeys({ getPublicMatches, dataTm: getTmDataStructureToSendServer({tmKeys, keysOrdered}), + tmPrioritization, }).then(() => CatToolActions.onTMKeysChangeStatus()) } } @@ -292,7 +303,7 @@ export const TranslationMemoryGlossaryTab = () => { ? 'row-content-default-memory' : id === SPECIAL_ROWS_ID.addSharedResource || id === SPECIAL_ROWS_ID.newResource - ? 'row-content-create-resource' + ? 'settings-panel-row-active row-content-create-resource' : '', node: !isCreateResourceRow ? ( @@ -319,22 +330,33 @@ export const TranslationMemoryGlossaryTab = () => { tmKeysActive.length !== prevTmKeysActive.length || tmKeysActive .filter(({key}) => isOwnerOfKey(key)) - .some(({key, r, w}) => { + .some(({key, r, w, penalty}) => { const prevTm = prevTmKeysActive.find((prev) => prev.key === key) - return prevTm && (r !== prevTm.r || w !== prevTm.w) - }) + return ( + prevTm && + (r !== prevTm.r || w !== prevTm.w || penalty !== prevTm.penalty) + ) + }) || + tmPrioritization !== current.tmPrioritization if (shouldUpdateTmKeysJob) { + const tmCurrentProjectTemplate = + previousStatesRef.current.currentProjectTemplate.tm + + const keysOrdered = tmCurrentProjectTemplate.map(({key}) => key) + updateJobKeys({ getPublicMatches, - dataTm: getTmDataStructureToSendServer({tmKeys}), + dataTm: getTmDataStructureToSendServer({tmKeys, keysOrdered}), + tmPrioritization, }).then(() => CatToolActions.onTMKeysChangeStatus()) } } current.tmKeys = tmKeys current.getPublicMatches = getPublicMatches - }, [tmKeys, getPublicMatches]) + current.tmPrioritization = tmPrioritization + }, [tmKeys, getPublicMatches, tmPrioritization]) const onAddSharedResource = () => setSpecialRows([ @@ -396,6 +418,7 @@ export const TranslationMemoryGlossaryTab = () => {
isActive)} onChangeRowsOrder={onOrderActiveRows} @@ -413,7 +436,6 @@ export const TranslationMemoryGlossaryTab = () => { />
diff --git a/public/js/cat_source/es6/components/settingsPanel/SettingsPanel.js b/public/js/cat_source/es6/components/settingsPanel/SettingsPanel.js index 82cfdf6e31..12c37d5779 100644 --- a/public/js/cat_source/es6/components/settingsPanel/SettingsPanel.js +++ b/public/js/cat_source/es6/components/settingsPanel/SettingsPanel.js @@ -47,6 +47,7 @@ export const TEMPLATE_PROPS_BY_TAB = { SCHEMA_KEYS.tm, SCHEMA_KEYS.getPublicMatches, SCHEMA_KEYS.pretranslate100, + SCHEMA_KEYS.tmPrioritization, ], [SETTINGS_PANEL_TABS.machineTranslation]: [SCHEMA_KEYS.mt], [SETTINGS_PANEL_TABS.qualityFramework]: [SCHEMA_KEYS.qaModelTemplateId], diff --git a/public/js/cat_source/es6/components/settingsPanel/SettingsPanelTable/SettingsPanelTable.js b/public/js/cat_source/es6/components/settingsPanel/SettingsPanelTable/SettingsPanelTable.js index cf55d20a6d..c13340b449 100644 --- a/public/js/cat_source/es6/components/settingsPanel/SettingsPanelTable/SettingsPanelTable.js +++ b/public/js/cat_source/es6/components/settingsPanel/SettingsPanelTable/SettingsPanelTable.js @@ -4,9 +4,12 @@ import React, { createRef, useState, createContext, + Fragment, } from 'react' import PropTypes from 'prop-types' import {SettingsPanelRow} from './SettingsPanelRow' +import {SPECIAL_ROWS_ID} from '../Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab' +import {TmPrioritization} from '../Contents/TranslationMemoryGlossaryTab/TmPrioritization' export const SettingsPanelTableContext = createContext({}) @@ -105,16 +108,23 @@ export const SettingsPanelTable = ({ rowsRef.current[index] = ref return ( - + + + {row.id === SPECIAL_ROWS_ID.defaultTranslationMemory && + rows.length > 2 && ( +
+ +
+ )} +
) } diff --git a/public/js/cat_source/es6/hooks/useProjectTemplates.js b/public/js/cat_source/es6/hooks/useProjectTemplates.js index 69ce1681dc..02e8e78eef 100644 --- a/public/js/cat_source/es6/hooks/useProjectTemplates.js +++ b/public/js/cat_source/es6/hooks/useProjectTemplates.js @@ -63,6 +63,7 @@ export const SCHEMA_KEYS = { sourceLanguage: 'source_language', subject: 'subject', targetLanguage: 'target_language', + tmPrioritization: 'tm_prioritization', } function useProjectTemplates(tmKeys, isCattool = config.is_cattool) { diff --git a/public/js/cat_source/es6/pages/CatTool.js b/public/js/cat_source/es6/pages/CatTool.js index 7d8018d56d..3460dde799 100644 --- a/public/js/cat_source/es6/pages/CatTool.js +++ b/public/js/cat_source/es6/pages/CatTool.js @@ -385,6 +385,18 @@ function CatTool() { typeof projectTemplates[1].mt !== 'undefined')) && Array.isArray(projectTemplates[1].tm) + useEffect(() => { + if ( + isFakeCurrentTemplateReady && + typeof jobMetadata?.job?.tm_prioritization !== 'undefined' + ) { + modifyingCurrentTemplate((prevTemplate) => ({ + ...prevTemplate, + tmPrioritization: jobMetadata?.job?.tm_prioritization === 1, + })) + } + }, [jobMetadata?.job, isFakeCurrentTemplateReady, modifyingCurrentTemplate]) + return ( <>
{ const isMatchingKeyFromQuery = tm_keys.some( ({key}) => tmKeyFromQueryString === key, ) - setTmKeys([ ...tm_keys.map((key) => ({ ...key, @@ -407,6 +406,7 @@ const NewProject = () => { qaModelTemplateId, payableRateTemplateId, XliffConfigTemplateId, + tmPrioritization, } = currentProjectTemplate // update store recently used target languages @@ -445,6 +445,7 @@ const NewProject = () => { deepl_formality: mt.extra.deepl_formality, }), xliff_parameters_template_id: XliffConfigTemplateId, + tm_prioritization: tmPrioritization ? 1 : 0, }) if (!projectSent) { @@ -689,7 +690,11 @@ const NewProject = () => { r: false, w: false, isActive: false, - ...(tmFromTemplate && {...tmFromTemplate, isActive: true}), + penalty: 0, + ...(tmFromTemplate && { + ...tmFromTemplate, + isActive: true, + }), name: tmItem.name, } }) diff --git a/tests/unit/TestMyMemory/GetMyMemoryTest.php b/tests/unit/TestMyMemory/GetMyMemoryTest.php index 4a13b33b98..611d8f44bd 100644 --- a/tests/unit/TestMyMemory/GetMyMemoryTest.php +++ b/tests/unit/TestMyMemory/GetMyMemoryTest.php @@ -56,7 +56,7 @@ public function setUp(): void { 'prop' => null, 'get_mt' => true, 'id_user' => [ 0 => "a6043e606ac9b5d7ff24" ], - 'num_result' => 3, + 'num_result' => 10, 'mt_only' => false, 'isConcordance' => false, 'isGlossary' => false, @@ -166,7 +166,7 @@ public function test_get_segment_with_mock_for__call0() { 'langpair' => 'it-IT|en-US', 'de' => 'demo@matecat.com', 'mt' => true, - 'numres' => 3, + 'numres' => 10, 'key' => 'a6043e606ac9b5d7ff24', 'client_id' => 0 ], @@ -183,7 +183,7 @@ public function test_get_segment_with_mock_for__call0() { * @var Engines_MyMemory */ $this->engine_MyMemory = @$this->getMockBuilder( '\Engines_MyMemory' )->setConstructorArgs( [ $this->engine_struct_param ] )->setMethods( [ '_call' ] )->getMock(); - $this->engine_MyMemory->expects( $this->exactly( 1 ) )->method( '_call' )->with( $url_mock_param, $curl_mock_param )->willReturn( $mock_json_return ); + $this->engine_MyMemory->expects( $this->exactly( 1 ) )->method( '_call' )->with( $url_mock_param )->willReturn( $mock_json_return ); $result = $this->engine_MyMemory->get( $this->config_param_of_get ); /** @@ -230,7 +230,7 @@ public function test_get_segment_with_mock_for__call_and_at_least_one_match_foun 'langpair' => 'it-IT|en-US', 'de' => 'demo@matecat.com', 'mt' => true, - 'numres' => 3, + 'numres' => 10, 'key' => 'a6043e606ac9b5d7ff24', 'client_id' => 0 ], @@ -248,7 +248,7 @@ public function test_get_segment_with_mock_for__call_and_at_least_one_match_foun * @var Engines_MyMemory */ $this->engine_MyMemory = $this->getMockBuilder( '\Engines_MyMemory' )->setConstructorArgs( [ $this->engine_struct_param ] )->setMethods( [ '_call' ] )->getMock(); - $this->engine_MyMemory->expects( $this->once() )->method( '_call' )->with( $url_mock_param, $curl_mock_param )->willReturn( $mock_json_return ); + $this->engine_MyMemory->expects( $this->once() )->method( '_call' )->with( $url_mock_param )->willReturn( $mock_json_return ); $result = $this->engine_MyMemory->get( $this->config_param_of_get ); /** @@ -369,7 +369,7 @@ public function test_get_with_error_from_mocked__call_for_coverage_purpose() { 'langpair' => 'it-IT|en-US', 'de' => 'demo@matecat.com', 'mt' => true, - 'numres' => 3, + 'numres' => 10, 'key' => 'a6043e606ac9b5d7ff24', 'client_id' => 0 ], @@ -393,7 +393,7 @@ public function test_get_with_error_from_mocked__call_for_coverage_purpose() { * @var Engines_MyMemory */ $this->engine_MyMemory = $this->getMockBuilder( '\Engines_MyMemory' )->setConstructorArgs( [ $this->engine_struct_param ] )->setMethods( [ '_call' ] )->getMock(); - $this->engine_MyMemory->expects( $this->once() )->method( '_call' )->with( $url_mock_param, $curl_mock_param )->willReturn( $rawValue_error ); + $this->engine_MyMemory->expects( $this->once() )->method( '_call' )->with( $url_mock_param )->willReturn( $rawValue_error ); $result = $this->engine_MyMemory->get( $this->config_param_of_get ); /**