diff --git a/.htaccess b/.htaccess index 10cbec9323..57aa86c4b2 100644 --- a/.htaccess +++ b/.htaccess @@ -48,8 +48,6 @@ RewriteRule ^activityLog/([^/]*)/([^/]*)[/]?(download)?$ index.php?action=activi RewriteRule ^utils/xliff-to-target$ index.php?action=xliffToTargetView [L] RewriteRule ^api/docs$ lib/View/APIDoc.php [L] -RewriteRule ^api/v1/new$ index.php?api=true&action=new [QSA,L] - RewriteRule ^(api)[/]?([^/]*)?[/]?$ index.php?api=true&action=$2 [QSA,L] RewriteRule ^api/(.*)$ router.php [QSA,L] RewriteRule ^webhooks/(.*)$ router.php [QSA,L] diff --git a/lib/Controller/API/App/AjaxUtilsController.php b/lib/Controller/API/App/AjaxUtilsController.php new file mode 100644 index 0000000000..ce6eacd07e --- /dev/null +++ b/lib/Controller/API/App/AjaxUtilsController.php @@ -0,0 +1,71 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function ping(): Response + { + $db = Database::obtain(); + $stmt = $db->getConnection()->prepare( "SELECT 1" ); + $stmt->execute(); + + return $this->response->json([ + 'data' => [ + "OK", time() + ] + ]); + } + + public function checkTMKey(): Response + { + try { + $tm_key = filter_var( $this->request->param( 'tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + + if ( empty($tm_key) ) { + throw new InvalidArgumentException("TM key not provided.", -9); + } + + $tmxHandler = new TMSService(); + $keyExists = $tmxHandler->checkCorrectKey( $tm_key ); + + if ( !isset( $keyExists ) || $keyExists === false ) { + throw new InvalidArgumentException("TM key is not valid.", -9); + } + + return $this->response->json([ + 'success' => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function clearNotCompletedUploads(): Response + { + try { + ( new Session() )->cleanupSessionFiles(); + + return $this->response->json([ + 'success' => true + ]); + + } catch ( Exception $exception ) { + return $this->returnException($exception); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/ChangeJobsStatusController.php b/lib/Controller/API/App/ChangeJobsStatusController.php new file mode 100644 index 0000000000..9e07a64f4f --- /dev/null +++ b/lib/Controller/API/App/ChangeJobsStatusController.php @@ -0,0 +1,124 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function changeStatus(): Response + { + try { + $request = $this->validateTheRequest(); + + if ( $request['res_type'] == "prj" ) { + + try { + $project = Projects_ProjectDao::findByIdAndPassword( $request['res_id'], $request['password'] ); + } catch( Exception $e ){ + $msg = "Error : wrong password provided for Change Project Status \n\n " . var_export( $_POST, true ) . "\n"; + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + throw new NotFoundException("Job not found"); + } + + $chunks = $project->getJobs(); + + Jobs_JobDao::updateAllJobsStatusesByProjectId( $project->id, $request['new_status'] ); + + foreach( $chunks as $chunk ){ + $lastSegmentsList = Translations_SegmentTranslationDao::getMaxSegmentIdsFromJob( $chunk ); + Translations_SegmentTranslationDao::updateLastTranslationDateByIdList( $lastSegmentsList, Utils::mysqlTimestamp( time() ) ); + } + + } else { + + try { + $firstChunk = Chunks_ChunkDao::getByIdAndPassword( $request['res_id'], $request['password'] ); + } catch( Exception $e ){ + $msg = "Error : wrong password provided for Change Job Status \n\n " . var_export( $_POST, true ) . "\n"; + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + throw new NotFoundException("Job not found"); + } + + Jobs_JobDao::updateJobStatus( $firstChunk, $request['new_status'] ); + $lastSegmentsList = Translations_SegmentTranslationDao::getMaxSegmentIdsFromJob( $firstChunk ); + Translations_SegmentTranslationDao::updateLastTranslationDateByIdList( $lastSegmentsList, Utils::mysqlTimestamp( time() ) ); + } + + return $this->response->json([ + 'errors' => [], + 'code' => 1, + 'data' => 'OK', + 'status' => $request['new_status'] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $name = filter_var( $this->request->param( 'name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $tm_key = filter_var( $this->request->param( 'tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $uuid = filter_var( $this->request->param( 'uuid' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $res_id = filter_var( $this->request->param( 'res_id' ), FILTER_VALIDATE_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $new_status = filter_var( $this->request->param( 'new_status' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + + if ( empty( $tm_key ) ) { + + if ( empty( INIT::$DEFAULT_TM_KEY ) ) { + throw new InvalidArgumentException("Please specify a TM key.", -2); + } + + /* + * Added the default Key. + * This means if no private key are provided the TMX will be loaded in the default MyMemory key + */ + $tm_key = INIT::$DEFAULT_TM_KEY; + } + + if ( empty( $res_id) ) { + throw new InvalidArgumentException("No id job provided", -1); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("No job password provided", -2); + } + + if ( empty( $new_status ) ) { + throw new InvalidArgumentException("No new status provided", -3); + } + + return [ + 'name' => $name, + 'tm_key' => $tm_key, + 'uuid' => $uuid, + 'res_id' => $res_id, + 'password' => $password, + 'new_status' => $new_status, + ]; + } +} diff --git a/lib/Controller/API/App/CommentController.php b/lib/Controller/API/App/CommentController.php new file mode 100644 index 0000000000..0344083292 --- /dev/null +++ b/lib/Controller/API/App/CommentController.php @@ -0,0 +1,558 @@ +appendValidator( new LoginValidator( $this ) ); + } + + /** + * @return Response + * @throws \ReflectionException + */ + public function getRange(): Response + { + $data = []; + $request = $this->validateTheRequest(); + + $struct = new Comments_CommentStruct(); + $struct->id_job = $request[ 'id_job' ]; + $struct->first_segment = $request[ 'first_seg' ]; + $struct->last_segment = $request[ 'last_seg' ]; + + $commentDao = new Comments_CommentDao( Database::obtain() ); + + $data[ 'entries' ] = [ + 'comments' => $commentDao->getCommentsForChunk( $request['job'] ) + ]; + + $data[ 'user' ] = [ + 'full_name' => $this->user->fullName() + ]; + + return $this->response->json([ + "data" => $data + ]); + } + + /** + * @return Response + * @throws \ReflectionException + * @throws \Stomp\Exception\ConnectionException + */ + public function resolve(): Response + { + try { + $request = $this->validateTheRequest(); + $prepareCommandData = $this->prepareCommentData($request); + $comment_struct = $prepareCommandData['struct']; + $users_mentioned_id = $prepareCommandData['users_mentioned_id']; + $users_mentioned = $prepareCommandData['users_mentioned']; + + $commentDao = new Comments_CommentDao( Database::obtain() ); + $new_record = $commentDao->resolveThread( $comment_struct ); + + $payload = $this->enqueueComment($new_record, $request['job']->id_project, $request['id_job'], $request['id_client']); + $users = $this->resolveUsers($comment_struct, $request['job'], $users_mentioned_id); + $this->sendEmail($comment_struct, $request['job'], $users, $users_mentioned); + + return $this->response->json([ + "data" => [ + 'entries' => $payload, + 'user' => [ + 'full_name' => $this->user->fullName() + ] + ] + ]); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return Response + * @throws \ReflectionException + * @throws \Stomp\Exception\ConnectionException + */ + public function create(): Response + { + try { + $request = $this->validateTheRequest(); + $prepareCommandData = $this->prepareCommentData($request); + $comment_struct = $prepareCommandData['struct']; + $users_mentioned_id = $prepareCommandData['users_mentioned_id']; + $users_mentioned = $prepareCommandData['users_mentioned']; + + $commentDao = new Comments_CommentDao( Database::obtain() ); + $new_record = $commentDao->saveComment( $comment_struct ); + + foreach ( $users_mentioned as $user_mentioned ) { + $mentioned_comment = $this->prepareMentionCommentData($request, $user_mentioned); + $commentDao->saveComment( $mentioned_comment ); + } + + $commentDao->destroySegmentIdCache($request[ 'id_segment' ]); + + $payload = $this->enqueueComment($new_record, $request['job']->id_project, $request['id_job'], $request['id_client']); + $users = $this->resolveUsers($comment_struct, $request['job'], $users_mentioned_id); + $this->sendEmail($comment_struct, $request['job'], $users, $users_mentioned); + + return $this->response->json([ + "data" => [ + 'entries' => $payload, + 'user' => [ + 'full_name' => $this->user->fullName() + ] + ] + ]); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return \Klein\Response + * @throws \ReflectionException + */ + public function delete() + { + try { + $request = $this->validateTheRequest(); + + if(!isset($request['id_comment'])){ + throw new InvalidArgumentException( "Id comment not provided.", -200); + } + + $user = $this->user; + $idComment = $request['id_comment']; + $commentDao = new Comments_CommentDao( Database::obtain() ); + $comment = $commentDao->getById($idComment); + + if(null === $comment){ + throw new InvalidArgumentException( "Comment not found.", -202); + } + + if($comment->uid === null){ + throw new InvalidArgumentException( "You are not the author of the comment.", -203); + } + + if((int)$comment->uid !== (int)$user->uid){ + throw new InvalidArgumentException( "You are not the author of the comment.", -203); + } + + if((int)$comment->id_segment !== (int)$request['id_segment']){ + throw new InvalidArgumentException( "Not corresponding id segment.", -204); + } + + $segments = $commentDao->getBySegmentId($comment->id_segment); + $lastSegment = end($segments); + + if((int)$lastSegment->id !== (int)$request['id_comment']){ + throw new InvalidArgumentException("Only the last element comment can be deleted.", -205); + } + + if((int)$comment->id_job !== (int)$request['id_job']){ + throw new InvalidArgumentException("Not corresponding id job.", -206); + } + + // Fix for R2 + // The comments from R2 phase are wrongly saved with source_page = 2 + $sourcePage = Utils::getSourcePageFromReferer(); + + $allowedSourcePages = []; + $allowedSourcePages[] = (int)$request['source_page']; + + if($sourcePage == 3){ + $allowedSourcePages[] = 2; + } + + if(!in_array((int)$comment->source_page, $allowedSourcePages)){ + throw new InvalidArgumentException("Not corresponding source_page.", -207); + } + + if($commentDao->deleteComment($comment->id)){ + + $commentDao->destroySegmentIdCache($comment->id_segment); + + $this->enqueueDeleteCommentMessage( + $request['id_job'], + $request['id_client'], + $request['job']->id_project, + $comment->id, + $comment->id_segment, + $this->user->email, + $request['source_page'] + ); + + return $this->response->json([ + "data" => [ + [ + "id" => (int)$comment->id + ], + 'user' => [ + 'full_name' => $this->user->fullName() + ] + ] + ]); + } + + throw new RuntimeException( "Error when deleting a comment.", -220); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array|\Klein\Response + * @throws \ReflectionException + */ + private function validateTheRequest(): array + { + $id_client = filter_var( $this->request->param( 'id_client' ), FILTER_SANITIZE_STRING ); + $username = filter_var( $this->request->param( 'username' ), FILTER_SANITIZE_STRING ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $source_page = filter_var( $this->request->param( 'source_page' ), FILTER_SANITIZE_NUMBER_INT ); + $revision_number = filter_var( $this->request->param( 'revision_number' ), FILTER_SANITIZE_NUMBER_INT ); + $first_seg = filter_var( $this->request->param( 'first_seg' ), FILTER_SANITIZE_NUMBER_INT ); + $last_seg = filter_var( $this->request->param( 'last_seg' ), FILTER_SANITIZE_NUMBER_INT ); + $id_comment = filter_var( $this->request->param( 'id_comment' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $message = filter_var( $this->request->param( 'message' ), FILTER_UNSAFE_RAW ); + $message = htmlspecialchars( $message ); + + $job = Jobs_JobDao::getByIdAndPassword( $id_job, $password, 60 * 60 * 24 ); + + if ( empty( $job ) ) { + throw new InvalidArgumentException(-10, "wrong password"); + } + + return [ + 'id_client' => $id_client, + 'username' => $username, + 'id_job' => $id_job, + 'id_segment' => $id_segment, + 'source_page' => $source_page, + 'revision_number' => $revision_number, + 'first_seg' => $first_seg, + 'last_seg' => $last_seg, + 'id_comment' => $id_comment, + 'password' => $password, + 'message' => $message, + 'job' => $job, + ]; + } + + /** + * @param $request + * @return array + * @throws \ReflectionException + */ + private function prepareCommentData($request): array + { + $struct = new Comments_CommentStruct(); + + $struct->id_segment = $request[ 'id_segment' ]; + $struct->id_job = $request[ 'id_job' ]; + $struct->full_name = $request[ 'username' ]; + $struct->source_page = $request[ 'source_page' ]; + $struct->message = $request[ 'message' ]; + $struct->revision_number = $request[ 'revision_number' ]; + $struct->email = $this->user->getEmail(); + $struct->uid = $this->user->getUid(); + + $user_mentions = $this->resolveUserMentions($struct->message); + $user_team_mentions = $this->resolveTeamMentions($request['job'], $struct->message); + $userDao = new Users_UserDao( Database::obtain() ); + $users_mentioned_id = array_unique( array_merge( $user_mentions, $user_team_mentions ) ); + $users_mentioned = $this->filterUsers( $userDao->getByUids( $users_mentioned_id ) ); + + return [ + 'struct' => $struct, + 'users_mentioned_id' => $users_mentioned_id, + 'users_mentioned' => $users_mentioned, + ]; + } + + /** + * @param $request + * @param Users_UserStruct $user + * @return Comments_CommentStruct + */ + private function prepareMentionCommentData( $request, Users_UserStruct $user ): Comments_CommentStruct + { + $struct = new Comments_CommentStruct(); + + $struct->id_segment = $request[ 'id_segment' ]; + $struct->id_job = $request[ 'id_job' ]; + $struct->full_name = $user->fullName(); + $struct->source_page = $request[ 'source_page' ]; + $struct->message = ""; + $struct->message_type = Comments_CommentDao::TYPE_MENTION; + $struct->email = $user->getEmail(); + $struct->uid = $user->getUid(); + + return $struct; + } + + /** + * @param $message + * @return array|mixed + */ + private function resolveUserMentions($message) + { + return Comments_CommentDao::getUsersIdFromContent( $message ); + } + + /** + * @param Jobs_JobStruct $job + * @param $message + * @return array + * @throws \ReflectionException + */ + private function resolveTeamMentions(Jobs_JobStruct $job, $message): array + { + $users = []; + + if ( strstr( $message, "{@team@}" ) ) { + $project = $job->getProject(); + $memberships = ( new MembershipDao() )->setCacheTTL( 60 * 60 * 24 )->getMemberListByTeamId( $project->id_team, false ); + foreach ( $memberships as $membership ) { + $users[] = $membership->uid; + } + } + + return $users; + } + + /** + * @param $users + * @param array $uidSentList + * @return array + */ + private function filterUsers( $users, $uidSentList = [] ): array + { + $userIsLogged = $this->userIsLogged; + $current_uid = $this->user->uid; + + // find deep duplicates + $users = array_filter( $users, function ( $item ) use ( $userIsLogged, $current_uid, &$uidSentList ) { + if ( $userIsLogged && $current_uid == $item->uid ) { + return false; + } + + // find deep duplicates + if ( array_search( $item->uid, $uidSentList ) !== false ) { + return false; + } + $uidSentList[] = $item->uid; + + return true; + + } ); + + return $users; + } + + /** + * @param Comments_CommentStruct $comment + * @param Jobs_JobStruct $job + * @param $users_mentioned_id + * @return array + */ + private function resolveUsers(Comments_CommentStruct $comment, Jobs_JobStruct $job, $users_mentioned_id): array + { + $commentDao = new Comments_CommentDao( Database::obtain() ); + $result = $commentDao->getThreadContributorUids( $comment ); + + $userDao = new Users_UserDao( Database::obtain() ); + $users = $userDao->getByUids( $result ); + $userDao->setCacheTTL( 60 * 60 * 24 ); + $owner = $userDao->getProjectOwner( $job->id ); + + if ( !empty( $owner->uid ) && !empty( $owner->email ) ) { + array_push( $users, $owner ); + } + + $userDao->setCacheTTL( 60 * 10 ); + $assignee = $userDao->getProjectAssignee( $job->id_project ); + if ( !empty( $assignee->uid ) && !empty( $assignee->email ) ) { + array_push( $users, $assignee ); + } + + return $this->filterUsers( $users, $users_mentioned_id ); + + } + + /** + * @param Comments_CommentStruct $comment + * @param $id_project + * @param $id_job + * @param $id_client + * @return false|string + * @throws \Stomp\Exception\ConnectionException + */ + private function enqueueComment(Comments_CommentStruct $comment, $id_project, $id_job, $id_client) { + + $payload = [ + 'message_type' => $comment->message_type, + 'message' => $comment->message, + 'id' => $comment->id, + 'id_segment' => $comment->id_segment, + 'full_name' => $comment->full_name, + 'source_page' => $comment->source_page, + 'formatted_date' => $comment->getFormattedDate(), + 'thread_id' => $comment->thread_id, + 'timestamp' => (int)$comment->timestamp, + ]; + + $message = json_encode( [ + '_type' => 'comment', + 'data' => [ + 'id_job' => $id_job, + 'passwords' => $this->getProjectPasswords($id_project), + 'id_client' => $id_client, + 'payload' => $payload + ] + ] ); + + $queueHandler = new AMQHandler(); + $queueHandler->publishToTopic( INIT::$SSE_NOTIFICATIONS_QUEUE_NAME, new Message( $message ) ); + + return $message; + } + + /** + * @param $id_project + * @return \DataAccess\ShapelessConcreteStruct[] + */ + private function projectData($id_project) { + return ( new \Projects_ProjectDao() )->setCacheTTL( 60 * 60 )->getProjectData( $id_project ); + } + + /** + * @param $id_project + * @return array + */ + private function getProjectPasswords($id_project) { + $pws = []; + + foreach ( $this->projectData($id_project) as $chunk ) { + $pws[] = $chunk[ 'jpassword' ]; + } + + return $pws; + } + + /** + * @param $id_job + * @param $id_client + * @param $id_project + * @param $id + * @param $idSegment + * @param $email + * @param $sourcePage + * @throws \Stomp\Exception\ConnectionException + */ + private function enqueueDeleteCommentMessage( + $id_job, + $id_client, + $id_project, + $id, + $idSegment, + $email, + $sourcePage + ) + { + $message = json_encode( [ + '_type' => 'comment', + 'data' => [ + 'id_job' => $id_job, + 'passwords' => $this->getProjectPasswords($id_project), + 'id_client' => $id_client, + 'payload' => [ + 'message_type' => "2", + 'id' => (int)$id, + 'id_segment' => $idSegment, + 'email' => $email, + 'source_page' => $sourcePage, + ] + ] + ] ); + + $queueHandler = new AMQHandler(); + $queueHandler->publishToTopic( INIT::$SSE_NOTIFICATIONS_QUEUE_NAME, new Message( $message ) ); + + } + + /** + * @param Comments_CommentStruct $comment + * @param Jobs_JobStruct $job + * @param array $users + * @param array $users_mentioned + * @return \Klein\Response + */ + private function sendEmail(Comments_CommentStruct $comment, Jobs_JobStruct $job, array $users, array $users_mentioned) { + + $jobUrlStruct = JobUrlBuilder::createFromJobStruct($job, [ + 'id_segment' => $comment->id_segment, + 'skip_check_segment' => true + ]); + + $url = $jobUrlStruct->getUrlByRevisionNumber($comment->revision_number); + + if(!$url){ + $this->response->code(404); + + return $this->response->json([ + "code" => -10, + "message" => "No valid url was found for this project." + ]); + } + + Log::doJsonLog( $url ); + $project_data = $this->projectData($job->id_project); + + foreach ( $users_mentioned as $user_mentioned ) { + $email = new CommentMentionEmail( $user_mentioned, $comment, $url, $project_data[ 0 ], $job ); + $email->send(); + } + + foreach ( $users as $user ) { + if ( $comment->message_type == Comments_CommentDao::TYPE_RESOLVE ) { + $email = new CommentResolveEmail( $user, $comment, $url, $project_data[ 0 ], $job ); + } else { + $email = new CommentEmail( $user, $comment, $url, $project_data[ 0 ], $job ); + } + + $email->send(); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/ConvertFileController.php b/lib/Controller/API/App/ConvertFileController.php new file mode 100644 index 0000000000..65d44f8698 --- /dev/null +++ b/lib/Controller/API/App/ConvertFileController.php @@ -0,0 +1,97 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function handle(): Response + { + try { + $data = $this->validateTheRequest(); + $cookieDir = $_COOKIE[ 'upload_session' ]; + $intDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $cookieDir; + $errDir = INIT::$STORAGE_DIR . DIRECTORY_SEPARATOR . 'conversion_errors' . DIRECTORY_SEPARATOR . $cookieDir; + + $this->featureSet->loadFromUserEmail($this->user->email); + + $convertFile = new ConvertFile( + [$data['file_name']], + $data['source_lang'], + $data['target_lang'], + $intDir, + $errDir, + $cookieDir, + $data['segmentation_rule'], + $this->featureSet, + $data['filters_extraction_parameters'], + $convertZipFile = true + ); + + $convertFile->convertFiles(); + $convertFileErrors = $convertFile->getErrors(); + + if(empty($convertFileErrors)){ + return $this->response->json([ + 'code' => 1, + 'data' => [], + 'errors' => [], + 'warnings' => [], + ]); + } + + return $this->response->json($convertFileErrors); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array|\Klein\Response + * @throws Exception + */ + private function validateTheRequest(): array + { + $file_name = filter_var( $this->request->param( 'file_name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source_lang = filter_var( $this->request->param( 'source_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $target_lang = filter_var( $this->request->param( 'target_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $segmentation_rule = filter_var( $this->request->param( 'segmentation_rule' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $filters_extraction_parameters = filter_var( $this->request->param( 'filters_extraction_parameters' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if(empty($file_name)){ + throw new InvalidArgumentException("Missing file name."); + } + + if(empty($source_lang)){ + throw new InvalidArgumentException("Missing source language."); + } + + if(empty($target_lang)){ + throw new InvalidArgumentException("Missing target language."); + } + + if(empty($segmentation_rule)){ + throw new InvalidArgumentException("Missing segmentation rule."); + } + + return [ + 'file_name' => $file_name, + 'source_lang' => $source_lang, + 'target_lang' => $target_lang, + 'segmentation_rule' => $segmentation_rule, + 'filters_extraction_parameters' => $filters_extraction_parameters, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/CopyAllSourceToTargetController.php b/lib/Controller/API/App/CopyAllSourceToTargetController.php new file mode 100644 index 0000000000..bdb8638b7f --- /dev/null +++ b/lib/Controller/API/App/CopyAllSourceToTargetController.php @@ -0,0 +1,163 @@ +appendValidator( new LoginValidator( $this ) ); + } + + /** + * @return Response + */ + public function copy(): Response + { + try { + $request = $this->validateTheRequest(); + $revision_number = $request['revision_number']; + $job_data = $request['job_data']; + + return $this->saveEventsAndUpdateTranslations( $job_data->id, $job_data->password, $revision_number); + } catch (Exception $exception){ + $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $pass = filter_var( $this->request->param( 'pass' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $revision_number = filter_var( $this->request->param( 'revision_number' ), FILTER_SANITIZE_NUMBER_INT ); + + $this->log( "Requested massive copy-source-to-target for job $id_job." ); + + if ( empty( $id_job ) ) { + throw new \InvalidArgumentException("Empty id job", -1); + + } + if ( empty( $pass ) ) { + throw new \InvalidArgumentException("Empty job password", -2); + } + + $job_data = Jobs_JobDao::getByIdAndPassword( $id_job, $pass ); + + if ( empty( $job_data ) ) { + throw new \InvalidArgumentException("Wrong id_job-password couple. Job not found", -3); + } + + return [ + 'id_job' => $id_job, + 'pass' => $pass, + 'revision_number' => $revision_number, + 'job_data' => $job_data, + ]; + } + + /** + * @param $job_id + * @param $password + * @param $revision_number + * @return Response + * @throws Exception + */ + private function saveEventsAndUpdateTranslations($job_id, $password, $revision_number): Response + { + try { + // BEGIN TRANSACTION + $database = Database::obtain(); + $database->begin(); + + $chunk = Chunks_ChunkDao::getByIdAndPassword( $job_id, $password ); + $features = $chunk->getProject()->getFeaturesSet(); + + $batchEventCreator = new TranslationEventsHandler( $chunk ); + $batchEventCreator->setFeatureSet( $features ); + $batchEventCreator->setProject( $chunk->getProject() ); + + $source_page = ReviewUtils::revisionNumberToSourcePage( $revision_number ); + $segments = $chunk->getSegments(); + + $affected_rows = 0; + + foreach ( $segments as $segment ) { + + $segment_id = (int)$segment->id; + $chunk_id = (int)$chunk->id; + + $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( $segment_id, $chunk_id ); + + if ( empty( $old_translation ) || ( $old_translation->status !== Constants_TranslationStatus::STATUS_NEW ) ) { + //no segment found + continue; + } + + $new_translation = clone $old_translation; + $new_translation->translation = $segment->segment; + $new_translation->status = Constants_TranslationStatus::STATUS_DRAFT; + $new_translation->translation_date = date( "Y-m-d H:i:s" ); + + + try { + $affected_rows += Translations_SegmentTranslationDao::updateTranslationAndStatusAndDate( $new_translation ); + } catch ( Exception $e ) { + $database->rollback(); + + throw new RuntimeException($e->getMessage(), -4); + } + + if ( $chunk->getProject()->hasFeature( Features::TRANSLATION_VERSIONS ) ) { + $segmentTranslationEventModel = new TranslationEvent( $old_translation, $new_translation, $this->user, $source_page ); + $batchEventCreator->addEvent( $segmentTranslationEventModel ); + } + } + + // save all events + $batchEventCreator->save( new BatchReviewProcessor() ); + + if ( !empty( $params[ 'segment_ids' ] ) ) { + $counter = new CounterModel(); + $counter->initializeJobWordCount( $chunk->id, $chunk->password ); + } + + $data = [ + 'code' => 1, + 'segments_modified' => $affected_rows + ];; + + $this->log( 'Segment Translation events saved completed' ); + $this->log( $data ); + + $database->commit(); + + return $this->response->json([ + 'data' => $data + ]); + } catch (Exception $exception){ + $this->returnException($exception); + } + } +} + diff --git a/lib/Controller/API/App/CreateProjectController.php b/lib/Controller/API/App/CreateProjectController.php new file mode 100644 index 0000000000..5748a5faaf --- /dev/null +++ b/lib/Controller/API/App/CreateProjectController.php @@ -0,0 +1,762 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function create(): Response + { + try { + $this->featureSet->loadFromUserEmail( $this->user->email ); + $this->data = $this->validateTheRequest(); + + $arFiles = explode( '@@SEP@@', html_entity_decode( $this->data['file_name'], ENT_QUOTES, 'UTF-8' ) ); + $default_project_name = $arFiles[ 0 ]; + + if ( count( $arFiles ) > 1 ) { + $default_project_name = "MATECAT_PROJ-" . date( "Ymdhi" ); + } + + if ( empty( $this->data['project_name'] ) ) { + $this->data['project_name'] = $default_project_name; + } + + // SET SOURCE COOKIE + CookieManager::setCookie( Constants::COOKIE_SOURCE_LANG, $this->data['source_lang'], + [ + 'expires' => time() + ( 86400 * 365 ), + 'path' => '/', + 'domain' => INIT::$COOKIE_DOMAIN, + 'secure' => true, + 'httponly' => true, + 'samesite' => 'None', + ] + ); + + // SET TARGET COOKIE + CookieManager::setCookie( Constants::COOKIE_TARGET_LANG, $this->data['target_lang'], + [ + 'expires' => time() + ( 86400 * 365 ), + 'path' => '/', + 'domain' => INIT::$COOKIE_DOMAIN, + 'secure' => true, + 'httponly' => true, + 'samesite' => 'None', + ] + ); + + //search in fileNames if there's a zip file. If it's present, get filenames and add the instead of the zip file. + + $uploadDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $_COOKIE[ 'upload_session' ]; + $newArFiles = []; + $fs = FilesStorageFactory::create(); + + foreach ( $arFiles as $__fName ) { + if ( 'zip' == AbstractFilesStorage::pathinfo_fix( $__fName, PATHINFO_EXTENSION ) ) { + + $fs->cacheZipArchive( sha1_file( $uploadDir . DIRECTORY_SEPARATOR . $__fName ), $uploadDir . DIRECTORY_SEPARATOR . $__fName ); + + $linkFiles = scandir( $uploadDir ); + + //fetch cache links, created by converter, from upload directory + foreach ( $linkFiles as $storedFileName ) { + //check if file begins with the name of the zip file. + // If so, then it was stored in the zip file. + if ( strpos( $storedFileName, $__fName ) !== false && + substr( $storedFileName, 0, strlen( $__fName ) ) == $__fName ) { + //add file name to the files array + $newArFiles[] = $storedFileName; + } + } + + } else { //this file was not in a zip. Add it normally + + if ( file_exists( $uploadDir . DIRECTORY_SEPARATOR . $__fName ) ) { + $newArFiles[] = $__fName; + } + } + } + + $arFiles = $newArFiles; + $arMeta = []; + + // create array_files_meta + foreach ( $arFiles as $arFile ) { + $arMeta[] = $this->getFileMetadata( $uploadDir . DIRECTORY_SEPARATOR . $arFile ); + } + + $projectManager = new ProjectManager(); + $projectStructure = $projectManager->getProjectStructure(); + + $projectStructure[ 'project_name' ] = $this->data['project_name']; + $projectStructure[ 'private_tm_key' ] = $this->data['private_tm_key']; + $projectStructure[ 'uploadToken' ] = $_COOKIE[ 'upload_session' ]; + $projectStructure[ 'array_files' ] = $arFiles; //list of file name + $projectStructure[ 'array_files_meta' ] = $arMeta; //list of file metadata + $projectStructure[ 'source_language' ] = $this->data['source_lang']; + $projectStructure[ 'target_language' ] = explode( ',', $this->data['target_lang'] ); + $projectStructure[ 'job_subject' ] = $this->data['job_subject']; + $projectStructure[ 'mt_engine' ] = $this->data['mt_engine']; + $projectStructure[ 'tms_engine' ] = $this->data['tms_engine']; + $projectStructure[ 'status' ] = Constants_ProjectStatus::STATUS_NOT_READY_FOR_ANALYSIS; + $projectStructure[ 'pretranslate_100' ] = $this->data['pretranslate_100']; + $projectStructure[ 'pretranslate_101' ] = $this->data['pretranslate_101']; + $projectStructure[ 'dialect_strict' ] = $this->data['dialect_strict']; + $projectStructure[ 'only_private' ] = $this->data['only_private']; + $projectStructure[ 'due_date' ] = $this->data['due_date']; + $projectStructure[ 'target_language_mt_engine_id' ] = $this->data[ 'target_language_mt_engine_id' ]; + $projectStructure[ 'user_ip' ] = Utils::getRealIpAddr(); + $projectStructure[ 'HTTP_HOST' ] = INIT::$HTTPHOST; + + // MMT Glossaries + // (if $engine is not an MMT instance, ignore 'mmt_glossaries') + $engine = Engine::getInstance( $this->data['mt_engine'] ); + if ( $engine instanceof Engines_MMT and $this->data['mmt_glossaries'] !== null ) { + $projectStructure[ 'mmt_glossaries' ] = $this->data['mmt_glossaries']; + } + + // DeepL + if ( $engine instanceof Engines_DeepL and $this->data['deepl_formality'] !== null ) { + $projectStructure[ 'deepl_formality' ] = $this->data['deepl_formality']; + } + + if ( $engine instanceof Engines_DeepL and $this->data['deepl_id_glossary'] !== null ) { + $projectStructure[ 'deepl_id_glossary' ] = $this->data['deepl_id_glossary']; + } + + if ( !empty($this->data['filters_extraction_parameters']) ) { + $projectStructure[ 'filters_extraction_parameters' ] = $this->data['filters_extraction_parameters']; + } + + if ( !empty($this->data['xliff_parameters']) ) { + $projectStructure[ 'xliff_parameters' ] = $this->data['xliff_parameters']; + } + + // with the qa template id + if ( !empty($this->data['qaModelTemplate']) ) { + $projectStructure[ 'qa_model_template' ] = $this->data['qaModelTemplate']->getDecodedModel(); + } + + if ( !empty($this->data['payableRateModelTemplate']) ) { + $projectStructure[ 'payable_rate_model_id' ] = $this->data['payableRateModelTemplate']->id; + } + + //TODO enable from CONFIG + $projectStructure[ 'metadata' ] = $this->metadata; + + $projectStructure[ 'userIsLogged' ] = true; + $projectStructure[ 'uid' ] = $this->user->uid; + $projectStructure[ 'id_customer' ] = $this->user->email; + $projectStructure[ 'owner' ] = $this->user->email; + $projectManager->setTeam( $this->data['team'] ); // set the team object to avoid useless query + + //set features override + $projectStructure[ 'project_features' ] = $this->data['project_features']; + + //reserve a project id from the sequence + $projectStructure[ 'id_project' ] = Database::obtain()->nextSequence( Database::SEQ_ID_PROJECT )[ 0 ]; + $projectStructure[ 'ppassword' ] = $projectManager->generatePassword(); + + $projectManager->sanitizeProjectStructure(); + $fs::moveFileFromUploadSessionToQueuePath( $_COOKIE[ 'upload_session' ] ); + + Queue::sendProject( $projectStructure ); + + $this->clearSessionFiles(); + $this->assignLastCreatedPid( $projectStructure[ 'id_project' ] ); + + return $this->response->json([ + 'data' => [ + 'id_project' => $projectStructure[ 'id_project' ], + 'password' => $projectStructure[ 'ppassword' ] + ], + 'errors' => [], + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $file_name = filter_var( $this->request->param( 'file_name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $project_name = filter_var( $this->request->param( 'project_name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source_lang = filter_var( $this->request->param( 'source_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $target_lang = filter_var( $this->request->param( 'target_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $job_subject = filter_var( $this->request->param( 'job_subject' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $due_date = filter_var( $this->request->param( 'due_date' ), FILTER_SANITIZE_NUMBER_INT ); + $mt_engine = filter_var( $this->request->param( 'mt_engine' ), FILTER_SANITIZE_NUMBER_INT ); + $disable_tms_engine = filter_var( $this->request->param( 'disable_tms_engine' ), FILTER_VALIDATE_BOOLEAN ); + $private_tm_key = filter_var( $this->request->param( 'private_tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $pretranslate_100 = filter_var( $this->request->param( 'pretranslate_100' ), FILTER_SANITIZE_NUMBER_INT ); + $pretranslate_101 = filter_var( $this->request->param( 'pretranslate_101' ), FILTER_SANITIZE_NUMBER_INT ); + $id_team = filter_var( $this->request->param( 'id_team' ), FILTER_SANITIZE_NUMBER_INT, [ 'flags' => FILTER_REQUIRE_SCALAR ] ); + $mmt_glossaries = filter_var( $this->request->param( 'mmt_glossaries' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $deepl_id_glossary = filter_var( $this->request->param( 'deepl_id_glossary' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $deepl_formality = filter_var( $this->request->param( 'deepl_formality' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $project_completion = filter_var( $this->request->param( 'project_completion' ), FILTER_VALIDATE_BOOLEAN ); + $get_public_matches = filter_var( $this->request->param( 'get_public_matches' ), FILTER_VALIDATE_BOOLEAN ); + $dialect_strict = filter_var( $this->request->param( 'dialect_strict' ), FILTER_SANITIZE_STRING ); + $filters_extraction_parameters = filter_var( $this->request->param( 'filters_extraction_parameters' ), FILTER_SANITIZE_STRING ); + $xliff_parameters = filter_var( $this->request->param( 'xliff_parameters' ), FILTER_SANITIZE_STRING ); + $qa_model_template_id = filter_var( $this->request->param( 'qa_model_template_id' ), FILTER_SANITIZE_NUMBER_INT ); + $payable_rate_template_id = filter_var( $this->request->param( 'pretranslate_100' ), FILTER_SANITIZE_NUMBER_INT ); + $target_language_mt_engine_id = filter_var( $this->request->param( 'target_language_mt_engine_id' ), FILTER_SANITIZE_NUMBER_INT ); + + $array_keys = json_decode( $_POST[ 'private_keys_list' ], true ); + $array_keys = array_merge( $array_keys[ 'ownergroup' ], $array_keys[ 'mine' ], $array_keys[ 'anonymous' ] ); + + // if a string is sent by the client, transform it into a valid array + if ( !empty( $private_tm_key ) ) { + $private_tm_key = [ + [ + 'key' => trim( $private_tm_key ), + 'name' => null, + 'r' => true, + 'w' => true + ] + ]; + } else { + $private_tm_key = []; + } + + if ( $array_keys ) { // some keys are selected from panel + + //remove duplicates + foreach ( $array_keys as $pos => $value ) { + if ( isset( $this->postInput[ 'private_tm_key' ][ 0 ][ 'key' ] ) + && $private_tm_key[ 0 ][ 'key' ] == $value[ 'key' ] + ) { + //same key was get from keyring, remove + $private_tm_key = []; + } + } + + //merge the arrays + $private_keyList = array_merge( $private_tm_key, $array_keys ); + } else { + $private_keyList =$private_tm_key; + } + + $postPrivateTmKey = array_filter( $private_keyList, [ "self", "sanitizeTmKeyArr" ] ); + + // NOTE: This is for debug purpose only, + // NOTE: Global $_POST Overriding from CLI + // $this->__postInput = filter_var_array( $_POST, $filterArgs ); + + + $mt_engine = ( $mt_engine != null ? $mt_engine : 0 ); // null NON รจ ammesso + $disable_tms_engine_flag = $disable_tms_engine; // se false allora MyMemory + $private_tm_key = $postPrivateTmKey; + $only_private = ( is_null( $get_public_matches ) ? false : !$get_public_matches ); + $due_date = ( empty( $due_date ) ? null : Utils::mysqlTimestamp( $due_date ) ); + + $data = [ + 'file_name' => $file_name, + 'project_name' => $project_name, + 'source_lang' => $source_lang, + 'target_lang' => $target_lang, + 'job_subject' => $job_subject, + 'disable_tms_engine' => $disable_tms_engine, + 'pretranslate_100' => $pretranslate_100, + 'pretranslate_101' => $pretranslate_101, + 'id_team' => $id_team, + 'mmt_glossaries' => $mmt_glossaries, + 'deepl_id_glossary' => $deepl_id_glossary, + 'deepl_formality' => $deepl_formality, + 'project_completion' => $project_completion, + 'get_public_matches' => $get_public_matches, + 'dialect_strict' => $dialect_strict, + 'filters_extraction_parameters' => $filters_extraction_parameters, + 'xliff_parameters' => $xliff_parameters, + 'qa_model_template_id' => $qa_model_template_id, + 'payable_rate_template_id' => $payable_rate_template_id, + 'array_keys' => $array_keys, + 'postPrivateTmKey' => $postPrivateTmKey, + 'target_language_mt_engine_id' => $target_language_mt_engine_id, + 'mt_engine' => $mt_engine, + 'disable_tms_engine_flag' => $disable_tms_engine_flag, + 'private_tm_key' => $private_tm_key, + 'only_private' => $only_private, + 'due_date' => $due_date, + ]; + + $this->setMetadataFromPostInput($data); + + if ( $disable_tms_engine_flag ) { + $data['tms_engine'] = 0; //remove default MyMemory + } + + if ( empty( $file_name ) ) { + throw new InvalidArgumentException("Missing file name.", -1); + } + + if ( empty( $job_subject ) ) { + throw new InvalidArgumentException("Missing job subject.", -5); + } + + if ( $pretranslate_100 != 1 and $pretranslate_100 != 0 ) { + throw new InvalidArgumentException("Invalid pretranslate_100 value", -6); + } + + if ( $pretranslate_101 !== null and $pretranslate_101 != 1 && $pretranslate_101 != 0 ) { + throw new InvalidArgumentException("Invalid pretranslate_101 value", -6); + } + + $data['source_lang'] = $this->validateSourceLang( Langs_Languages::getInstance(), $data['source_lang'] ); + $data['target_lang'] = $this->validateTargetLangs( Langs_Languages::getInstance(), $data['target_lang'] ); + $data['mt_engine'] = $this->validateUserMTEngine($data['mt_engine']); + $data['mmt_glossaries'] = $this->validateMMTGlossaries($data['mmt_glossaries']); + $data['deepl_formality'] = $this->validateDeepLFormalityParams($data['deepl_formality']); + $data['qa_model_template'] = $this->validateQaModelTemplate($data['qa_model_template_id']); + $data['payable_rate_model_template'] = $this->validatePayableRateTemplate($data['payable_rate_template_id']); + $data['dialect_strict'] = $this->validateDialectStrictParam($data['target_lang'], $data['dialect_strict'] ); + $data['filters_extraction_parameters'] = $this->validateFiltersExtractionParameters($data['filters_extraction_parameters']); + $data['xliff_parameters'] = $this->validateXliffParameters($data['xliff_parameters']); + $data['project_features'] = $this->appendFeaturesToProject($data['project_completion']); + $data['target_language_mt_engine_id'] = $this->generateTargetEngineAssociation($data['target_lang'], $data['mt_engine'], $data['target_language_mt_engine_id']); + $data['team'] = $this->setTeam( $id_team ); + + return $data; + } + + /** + * This function sets metadata property from input params. + * @param array $data + * + * @throws Exception + */ + private function setMetadataFromPostInput(array $data = []) + { + // new raw counter model + $options = [ Projects_MetadataDao::WORD_COUNT_TYPE_KEY => Projects_MetadataDao::WORD_COUNT_RAW ]; + + if ( isset( $data[ 'lexiqa' ] ) ) { + $options[ 'lexiqa' ] = $data[ 'lexiqa' ]; + } + + if ( isset( $data[ 'speech2text' ] ) ) { + $options[ 'speech2text' ] = $data[ 'speech2text' ]; + } + + if ( isset( $data[ 'tag_projection' ] ) ) { + $options[ 'tag_projection' ] = $data[ 'tag_projection' ]; + } + + if ( isset( $data[ 'segmentation_rule' ] ) ) { + $options[ 'segmentation_rule' ] = $data[ 'segmentation_rule' ]; + } + + $this->metadata = $options; + $this->metadata = $this->featureSet->filter( 'createProjectAssignInputMetadata', $this->metadata, [ + 'input' => $data + ] ); + } + + /** + * @param Langs_Languages $lang_handler + * @param $source_lang + * @return string + */ + private function validateSourceLang( Langs_Languages $lang_handler, $source_lang ): string + { + try { + $lang_handler->validateLanguage( $source_lang ); + } catch ( Exception $e ) { + throw new InvalidArgumentException( $e->getMessage(), -3 ); + } + + return $source_lang; + } + + /** + * @param Langs_Languages $lang_handler + * @param $target_lang + * @return string + */ + private function validateTargetLangs( Langs_Languages $lang_handler, $target_lang ): string + { + $targets = explode( ',', $target_lang ); + $targets = array_map( 'trim', $targets ); + $targets = array_unique( $targets ); + + if ( empty( $targets ) ) { + throw new InvalidArgumentException( "Missing target language.", -4 ); + } + + try { + foreach ( $targets as $target ) { + $lang_handler->validateLanguage( $target ); + } + } catch ( Exception $e ) { + throw new InvalidArgumentException( $e->getMessage() , -4 ); + } + + return implode( ',', $targets ); + } + + /** + * Check if MT engine (except MyMemory) belongs to user + * @param $mt_engine + * @return int + */ + private function validateUserMTEngine($mt_engine): int + { + if ( $mt_engine > 1 ) { + try { + EngineValidator::engineBelongsToUser( $mt_engine, $this->user->uid ); + } catch ( Exception $exception ) { + throw new InvalidArgumentException( $e->getMessage() , -2 ); + } + } + + return (int)$mt_engine; + } + + /** + * Validate `mmt_glossaries` string + * @param null $mmt_glossaries + * @return string|null + */ + private function validateMMTGlossaries($mmt_glossaries = null): ?string + { + if ( !empty( $mmt_glossaries ) ) { + try { + $mmtGlossaries = html_entity_decode( $mmt_glossaries ); + MMTValidator::validateGlossary( $mmtGlossaries ); + + return $mmtGlossaries; + + } catch ( Exception $exception ) { + throw new InvalidArgumentException( $exception->getMessage() , -6 ); + } + } + + return null; + } + + /** + * Validate DeepL params + * @param null $deepl_formality + * @return string|null + */ + private function validateDeepLFormalityParams($deepl_formality = null): ?string + { + if ( !empty( $deepl_formality ) ) { + $allowedFormalities = [ + 'default', + 'prefer_less', + 'prefer_more' + ]; + + if ( !in_array( $deepl_formality, $allowedFormalities ) ) { + throw new InvalidArgumentException( "Not allowed value of DeepL formality", -6 ); + } + + return $deepl_formality; + } + + return null; + } + + /** + * @param null $qa_model_template_id + * @return QAModelTemplateStruct|null + * @throws Exception + */ + private function validateQaModelTemplate($qa_model_template_id = null): ?QAModelTemplateStruct + { + if ( !empty( $qa_model_template_id ) and $qa_model_template_id > 0 ) { + $qaModelTemplate = QAModelTemplateDao::get( [ + 'id' => $qa_model_template_id, + 'uid' => $this->getUser()->uid + ] ); + + // check if qa_model template exists + if ( null === $qaModelTemplate ) { + throw new InvalidArgumentException( 'This QA Model template does not exists or does not belongs to the logged in user' ); + } + + return $qaModelTemplate; + } + + return null; + } + + /** + * @param null $payable_rate_template_id + * @return CustomPayableRateStruct|null + * @throws \Exception + */ + private function validatePayableRateTemplate($payable_rate_template_id = null): ?CustomPayableRateStruct + { + $payableRateModelTemplate = null; + + if ( !empty( $payable_rate_template_id ) and $payable_rate_template_id > 0 ) { + + $payableRateTemplateId = $payable_rate_template_id; + $userId = $this->getUser()->uid; + + $payableRateModelTemplate = CustomPayableRateDao::getByIdAndUser( $payableRateTemplateId, $userId ); + + if ( null === $payableRateModelTemplate ) { + throw new InvalidArgumentException( 'Payable rate model id not valid' ); + } + } + + return $payableRateModelTemplate; + } + + /** + * Validate `dialect_strict` param vs target languages + * + * Example: {"it-IT": true, "en-US": false, "fr-FR": false} + * + * @param $target_lang + * @param null $dialect_strict + * @return string|null + */ + private function validateDialectStrictParam($target_lang, $dialect_strict = null): ?string + { + if ( !empty( $dialect_strict ) ) { + $dialect_strict = trim( html_entity_decode( $dialect_strict ) ); + $target_languages = preg_replace( '/\s+/', '', $target_lang ); + $targets = explode( ',', trim( $target_languages ) ); + $dialectStrictObj = json_decode( $dialect_strict, true ); + + foreach ( $dialectStrictObj as $lang => $value ) { + if ( !in_array( $lang, $targets ) ) { + throw new InvalidArgumentException( 'Wrong `dialect_strict` object, language, ' . $lang . ' is not one of the project target languages' ); + } + + if ( !is_bool( $value ) ) { + throw new InvalidArgumentException( 'Wrong `dialect_strict` object, not boolean declared value for ' . $lang ); + } + } + + $dialect_strict = html_entity_decode( $dialect_strict ); + } + + return $dialect_strict; + } + + /** + * @param null $filters_extraction_parameters + * @return mixed|null + * @throws Exception + */ + private function validateFiltersExtractionParameters($filters_extraction_parameters = null) + { + if ( !empty( $filters_extraction_parameters) ) { + + $json = html_entity_decode( $filters_extraction_parameters ); + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/filters_extraction_parameters.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $json; + + $validator = new JSONValidator( $schema ); + $validator->validate( $validatorObject ); + + $filters_extraction_parameters = json_decode( $json ); + } + + return $filters_extraction_parameters; + } + + /** + * @param null $xliff_parameters + * @return object|null + * @throws Exception + */ + private function validateXliffParameters($xliff_parameters = null): ?string + { + if ( !empty( $xliff_parameters ) ) { + $json = html_entity_decode( $xliff_parameters ); + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/xliff_parameters_rules_content.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $json; + + $validator = new JSONValidator( $schema, true ); + $validator->validate( $validatorObject ); + $xliff_parameters = $validatorObject->decoded; + } + + return $xliff_parameters; + } + + /** + * setProjectFeatures + * + * @param null $project_completion + * @return array|mixed + * @throws Exception + */ + private function appendFeaturesToProject($project_completion = null) + { + // change project features + $projectFeatures = []; + + if ( !empty( $project_completion ) ) { + $feature = new BasicFeatureStruct(); + $feature->feature_code = 'project_completion'; + $projectFeatures[] = $feature; + } + + $projectFeatures = $this->featureSet->filter( + 'filterCreateProjectFeatures', $projectFeatures, $this + ); + + return $projectFeatures; + } + + /** + * This could be already set by MMT engine if enabled ( so check key existence and do not override ) + * + * @param $target_langs + * @param $mt_engine + * @param null $target_language_mt_engine_id + * @return array|null + * @see filterCreateProjectFeatures callback + * @see createProjectController::appendFeaturesToProject() + */ + private function generateTargetEngineAssociation($target_langs, $mt_engine, $target_language_mt_engine_id = null): ?array + { + if ( !is_null($target_language_mt_engine_id) ) { // this could be already set by MMT engine if enabled ( so check and do not override ) + + $assoc = []; + + foreach ( explode( ",", $target_langs ) as $_matecatTarget ) { + $assoc[ $_matecatTarget ] = $mt_engine; + } + + return $assoc; + } + + return null; + } + + /** + * @param null $id_team + * @return \Teams\TeamStruct|null + * @throws Exception + */ + private function setTeam( $id_team = null ) + { + if ( is_null( $id_team ) ) { + return $this->user->getPersonalTeam(); + } + + // check for the team to be allowed + $dao = new MembershipDao(); + $team = $dao->findTeamByIdAndUser( $id_team, $this->user ); + + if ( !$team ) { + throw new Exception( 'Team and user memberships do not match' ); + } + + return $team; + } + + /** + * @param $filename + * + * @return array + * + * @throws Exception + */ + private function getFileMetadata( $filename ) + { + $info = XliffProprietaryDetect::getInfo( $filename ); + $isXliff = XliffFiles::isXliff( $filename ); + $isGlossary = XliffFiles::isGlossaryFile( $filename ); + $isTMX = XliffFiles::isTMXFile( $filename ); + $getMemoryType = XliffFiles::getMemoryFileType( $filename ); + + $forceXliff = $this->getFeatureSet()->filter( + 'forceXLIFFConversion', + INIT::$FORCE_XLIFF_CONVERSION, + $this->userIsLogged, + $info[ 'info' ][ 'dirname' ] . DIRECTORY_SEPARATOR . "$filename" + ); + $mustBeConverted = XliffProprietaryDetect::fileMustBeConverted( $filename, $forceXliff, INIT::$FILTERS_ADDRESS ); + + $metadata = []; + $metadata[ 'basename' ] = $info[ 'info' ][ 'basename' ]; + $metadata[ 'dirname' ] = $info[ 'info' ][ 'dirname' ]; + $metadata[ 'extension' ] = $info[ 'info' ][ 'extension' ]; + $metadata[ 'filename' ] = $info[ 'info' ][ 'filename' ]; + $metadata[ 'mustBeConverted' ] = $mustBeConverted; + $metadata[ 'getMemoryType' ] = $getMemoryType; + $metadata[ 'isXliff' ] = $isXliff; + $metadata[ 'isGlossary' ] = $isGlossary; + $metadata[ 'isTMX' ] = $isTMX; + $metadata[ 'proprietary' ] = [ + 'proprietary' => $info[ 'proprietary' ], + 'proprietary_name' => $info[ 'proprietary_name' ], + 'proprietary_short_name' => $info[ 'proprietary_short_name' ], + ]; + + return $metadata; + } + + private function clearSessionFiles(): void + { + $gdriveSession = new Session(); + $gdriveSession->clearFileListFromSession(); + } + + private function assignLastCreatedPid( $pid ): void + { + $_SESSION[ 'redeem_project' ] = false; + $_SESSION[ 'last_created_pid' ] = $pid; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/CreateRandUserController.php b/lib/Controller/API/App/CreateRandUserController.php new file mode 100644 index 0000000000..185c8689b5 --- /dev/null +++ b/lib/Controller/API/App/CreateRandUserController.php @@ -0,0 +1,33 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function create(): Response + { + try { + /** + * @var $tms Engines_MyMemory + */ + $tms = Engine::getInstance( 1 ); + + return $this->response->json([ + 'data' => $tms->createMyMemoryKey() + ]); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/DeleteContributionController.php b/lib/Controller/API/App/DeleteContributionController.php new file mode 100644 index 0000000000..de29f7e77c --- /dev/null +++ b/lib/Controller/API/App/DeleteContributionController.php @@ -0,0 +1,209 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function delete(): Response + { + try { + $request = $this->validateTheRequest(); + $id_segment = $request['id_segment']; + $source_lang = $request['source_lang']; + $target_lang = $request['target_lang']; + $source = $request['source']; + $target = $request['target']; + $id_job = $request['id_job']; + $id_translator = $request['id_translator']; + $id_match = $request['id_match']; + $password = $request['password']; + $received_password = $request['received_password']; + + //check Job password + $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $id_job, $password ); + $this->featureSet->loadForProject( $jobStruct->getProject() ); + + $tm_keys = $jobStruct[ 'tm_keys' ]; + + $tms = Engine::getInstance( $jobStruct[ 'id_tms' ] ); + $config = $tms->getConfigStruct(); + + $Filter = MateCatFilter::getInstance( $this->getFeatureSet(), $source_lang, $target_lang, [] ); + $config[ 'segment' ] = $Filter->fromLayer2ToLayer0( $source ); + $config[ 'translation' ] = $Filter->fromLayer2ToLayer0( $target ); + $config[ 'source' ] = $source_lang; + $config[ 'target' ] = $target_lang; + $config[ 'email' ] = INIT::$MYMEMORY_API_KEY; + $config[ 'id_user' ] = []; + $config[ 'id_match' ] = $id_match; + + //get job's TM keys + try { + $userRole = ( $this->isRevision($id_job, $password) ) ? TmKeyManagement_Filter::ROLE_REVISOR : TmKeyManagement_Filter::ROLE_TRANSLATOR; + + //get TM keys with read grants + $tm_keys = TmKeyManagement_TmKeyManagement::getJobTmKeys( $tm_keys, 'w', 'tm', $this->user->uid, $userRole ); + $tm_keys = TmKeyManagement_TmKeyManagement::filterOutByOwnership( $tm_keys, $this->user->email, $jobStruct[ 'owner' ] ); + + } catch ( Exception $e ) { + $errors[] = [ + "code" => -11, + "message" => "Cannot retrieve TM keys info." + ]; + + $this->response->code($e->getCode() >= 400 ? $e->getCode() : 500); + + return $this->response->json($errors); + } + + //prepare the errors report + $set_code = []; + + /** + * @var $tm_key TmKeyManagement_TmKeyStruct + */ + + //if there's no key + if ( empty( $tm_keys ) ) { + //try deleting anyway, it may be a public segment and it may work + $TMS_RESULT = $tms->delete( $config ); + + if($TMS_RESULT){ + $this->updateSuggestionsArray($id_segment, $id_job, $id_match); + } + + $set_code[] = $TMS_RESULT; + } else { + //loop over the list of keys + foreach ( $tm_keys as $tm_key ) { + //issue a separate call for each key + $config[ 'id_user' ] = $tm_key->key; + $TMS_RESULT = $tms->delete( $config ); + + if($TMS_RESULT){ + $this->updateSuggestionsArray($id_segment, $id_job, $id_match); + } + + $set_code[] = $TMS_RESULT; + } + } + + $set_successful = true; + if ( array_search( false, $set_code, true ) ) { + //There's an errors + $set_successful = false; + } + + return $this->response->json([ + 'data' => ( $set_successful ? "OK" : null ), + 'code' => $set_successful, + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source_lang = filter_var( $this->request->param( 'source_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $target_lang = filter_var( $this->request->param( 'target_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source = filter_var( $this->request->param( 'seg' ), FILTER_UNSAFE_RAW ); + $target = filter_var( $this->request->param( 'tra' ), FILTER_UNSAFE_RAW ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $id_translator = filter_var( $this->request->param( 'id_translator' ), FILTER_SANITIZE_NUMBER_INT ); + $id_match = filter_var( $this->request->param( 'id_match' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $received_password = filter_var( $this->request->param( 'current_password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + + $source = trim($source); + $target = trim($target); + $password = trim($password); + $received_password = trim($received_password); + + if ( empty( $source_lang ) ) { + throw new InvalidArgumentException( "missing source_lang", -1); + } + + if ( empty( $target_lang ) ) { + throw new InvalidArgumentException( "missing target_lang", -2); + } + + if ( empty( $source ) ) { + throw new InvalidArgumentException( "missing source", -3); + } + + if ( empty( $target ) ) { + throw new InvalidArgumentException( "missing target", -4); + } + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException( "missing id job", -5); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException( "missing job password", -6); + } + + return [ + 'id_segment' => $id_segment , + 'source_lang' => $source_lang , + 'target_lang' => $target_lang , + 'source' => $source , + 'target' => $target , + 'id_job' => $id_job , + 'id_translator' => $id_translator , + 'id_match' => $id_match , + 'password' => $password , + 'received_password' => $received_password , + ]; + } + + /** + * Update suggestions array + * + * @param $id_segment + * @param $id_job + * @param $id_match + * @throws Exception + */ + private function updateSuggestionsArray($id_segment, $id_job, $id_match): void + { + $segmentTranslation = Translations_SegmentTranslationDao::findBySegmentAndJob($id_segment, $id_job); + $oldSuggestionsArray = json_decode($segmentTranslation->suggestions_array); + + if(!empty($oldSuggestionsArray)){ + + $newSuggestionsArray = []; + foreach ($oldSuggestionsArray as $suggestion){ + if($suggestion->id != $id_match){ + $newSuggestionsArray[] = $suggestion; + } + } + + Translations_SegmentTranslationDao::updateSuggestionsArray($id_segment, $newSuggestionsArray); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/DownloadAnalysisReportController.php b/lib/Controller/API/App/DownloadAnalysisReportController.php new file mode 100644 index 0000000000..05fb18c3c4 --- /dev/null +++ b/lib/Controller/API/App/DownloadAnalysisReportController.php @@ -0,0 +1,92 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function download() + { + try { + $request = $this->validateTheRequest(); + $_project_data = Projects_ProjectDao::getProjectAndJobData( $request['id_project'] ); + + $pCheck = new AjaxPasswordCheck(); + $access = $pCheck->grantProjectAccess( $_project_data, $this->password ); + + //check for Password correctness + if ( !$access ) { + $msg = "Error : wrong password provided for download \n\n " . var_export( $_POST, true ) . "\n"; + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + + throw new RuntimeException($msg); + } + + $this->featureSet->loadForProject( Projects_ProjectDao::findById( $request['id_project'], 60 * 60 * 24 ) ); + + $analysisStatus = new XTRFStatus( $_project_data, $this->featureSet ); + $outputContent = $analysisStatus->fetchData()->getResult(); + + $this->outputContent = $this->composeZip( $outputContent, $_project_data[0][ 'pname' ] ); + $this->_filename = $_project_data[0][ 'pname' ] . ".zip"; + + $activity = new ActivityLogStruct(); + $activity->id_job = $_project_data[ 0 ][ 'jid' ]; + $activity->id_project = $request['id_project']; //assume that all rows have the same project id + $activity->action = ActivityLogStruct::DOWNLOAD_ANALYSIS_REPORT; + $activity->ip = Utils::getRealIpAddr(); + $activity->uid = $this->user->uid; + $activity->event_date = date( 'Y-m-d H:i:s' ); + Activity::save( $activity ); + + return $this->finalize(); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array|\Klein\Response + * @throws \ReflectionException + */ + private function validateTheRequest(): array + { + $id_project = filter_var( $this->request->param( 'id_project' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $download_type = filter_var( $this->request->param( 'download_type' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if(empty($id_project)){ + throw new InvalidArgumentException("Id project not provided"); + } + + $project = Projects_ProjectDao::findById($id_project); + + if ( empty( $project ) ) { + throw new InvalidArgumentException(-10, "Wrong Id project provided"); + } + + return [ + 'project' => $project, + 'id_project' => $id_project, + 'password' => $password, + 'download_type' => $download_type, + ]; + } +} diff --git a/lib/Controller/API/App/DownloadTMXController.php b/lib/Controller/API/App/DownloadTMXController.php new file mode 100644 index 0000000000..f7462be761 --- /dev/null +++ b/lib/Controller/API/App/DownloadTMXController.php @@ -0,0 +1,118 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function download(): Response + { + try { + $request = $this->validateTheRequest(); + $res = $request['tmxHandler']->requestTMXEmailDownload( + ( $request['download_to_email'] != false ? $request['download_to_email'] : $this->user->email ), + $this->user->first_name, + $this->user->last_name, + $request['tm_key'], + $request['strip_tags'] + ); + + return $this->response->json([ + 'errors' => [], + 'data' => $res, + ]); + + } catch (Exception $exception){ + + // Exception logging... + $r = "
";
+
+            if(isset($data['download_to_email']) and !empty($data['download_to_email'])){
+                $r .= print_r( "User Email: " . $data['download_to_email'], true ) . "\n";
+            }
+
+            $r .= print_r( "User ID: " . $this->user->uid, true ) . "\n";
+            $r .= print_r( $exception->getMessage(), true ) . "\n";
+            $r .= print_r( $exception->getTraceAsString(), true ) . "\n";
+
+            $r .= "\n\n";
+            $r .= " - REQUEST URI: " . print_r( @$_SERVER[ 'REQUEST_URI' ], true ) . "\n";
+            $r .= " - REQUEST Message: " . print_r( $_REQUEST, true ) . "\n";
+            $r .= "\n\n\n";
+            $r .= "
"; + + Log::$fileName = 'php_errors.txt'; + $this->log( $r ); + + try { + Utils::sendErrMailReport( $r, "Download TMX Error: " . $e->getMessage() ); + } catch (Exception $exception){ + throw new RuntimeException("Error during sending the email"); + } + + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest() + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $tm_key = filter_var( $this->request->param( 'tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $tm_name = filter_var( $this->request->param( 'tm_name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $downloadToken = filter_var( $this->request->param( 'downloadToken' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $download_to_email = filter_var( $this->request->param( 'email' ), FILTER_SANITIZE_EMAIL ); + $strip_tags = filter_var( $this->request->param( 'strip_tags' ), FILTER_VALIDATE_BOOLEAN ); + $source = filter_var( $this->request->param( 'source' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $target = filter_var( $this->request->param( 'target' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( $download_to_email === false ) { + throw new InvalidArgumentException("Invalid email provided for download.", -1); + } + + if ( $tm_name === false ) { + throw new InvalidArgumentException("Invalid TM name provided.", -2); + } + + $tmxHandler = new TMSService(); + $tmxHandler->setName( $tm_name ); + + return [ + 'id_job' => $id_job, + 'password' => $password, + 'tm_key' => $tm_key, + 'tm_name' => $tm_name, + 'downloadToken' => $downloadToken, + 'download_to_email' => $download_to_email, + 'strip_tags' => $strip_tags, + 'source' => $source, + 'target' => $target, + 'tmxHandler' => $tmxHandler, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/EngineController.php b/lib/Controller/API/App/EngineController.php new file mode 100644 index 0000000000..446da4580a --- /dev/null +++ b/lib/Controller/API/App/EngineController.php @@ -0,0 +1,339 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function add(): Response + { + $request = $this->validateTheRequest(); + + $name = $request['name']; + $engineData = $request['data']; + $provider = $request['provider']; + + if ( empty( $name ) ) { + throw new InvalidArgumentException("Engine name required", -6); + } + + if ( empty( $engineData ) ) { + throw new InvalidArgumentException("Engine data required", -7); + } + + if ( empty( $provider ) ) { + throw new InvalidArgumentException("Engine provider required", -8); + } + + $newEngineStruct = null; + $validEngine = true; + + try { + switch ( strtolower( $provider ) ) { + + case strtolower( Constants_Engines::DEEPL ): + + $newEngineStruct = DeepLStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'DeepL-Auth-Key' ] = $engineData[ 'client_id' ]; + + DeepLValidator::validate($newEngineStruct); + + break; + + + case strtolower( Constants_Engines::MICROSOFT_HUB ): + + /** + * Create a record of type MicrosoftHub + */ + $newEngineStruct = EnginesModel_MicrosoftHubStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_id' ] = $engineData[ 'client_id' ]; + $newEngineStruct->extra_parameters[ 'category' ] = $engineData[ 'category' ]; + break; + + case strtolower( Constants_Engines::APERTIUM ): + + /** + * Create a record of type APERTIUM + */ + $newEngineStruct = EnginesModel_ApertiumStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_secret' ] = $engineData[ 'secret' ]; + + break; + + case strtolower( Constants_Engines::ALTLANG ): + + /** + * Create a record of type ALTLANG + */ + $newEngineStruct = EnginesModel_AltlangStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_secret' ] = $engineData[ 'secret' ]; + + break; + + case strtolower( Constants_Engines::SMART_MATE ): + + /** + * Create a record of type SmartMate + */ + $newEngineStruct = EnginesModel_SmartMATEStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_id' ] = $engineData[ 'client_id' ]; + $newEngineStruct->extra_parameters[ 'client_secret' ] = $engineData[ 'secret' ]; + + break; + + case strtolower( Constants_Engines::YANDEX_TRANSLATE ): + + /** + * Create a record of type YandexTranslate + */ + $newEngineStruct = EnginesModel_YandexTranslateStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_secret' ] = $engineData[ 'secret' ]; + + break; + + case strtolower( Constants_Engines::GOOGLE_TRANSLATE ): + + /** + * Create a record of type GoogleTranslate + */ + $newEngineStruct = EnginesModel_GoogleTranslateStruct::getStruct(); + + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters[ 'client_secret' ] = $engineData[ 'secret' ]; + + break; + + case strtolower(Constants_Engines::INTENTO): + /** + * Create a record of type Intento + */ + $newEngineStruct = EnginesModel_IntentoStruct::getStruct(); + $newEngineStruct->name = $name; + $newEngineStruct->uid = $this->user->uid; + $newEngineStruct->type = Constants_Engines::MT; + $newEngineStruct->extra_parameters['apikey'] = $engineData['secret']; + $newEngineStruct->extra_parameters['provider'] = $engineData['provider']; + $newEngineStruct->extra_parameters['providerkey'] = $engineData['providerkey']; + $newEngineStruct->extra_parameters['providercategory'] = $engineData['providercategory']; + break; + + default: + + // MMT + $validEngine = $newEngineStruct = $this->featureSet->filter( 'buildNewEngineStruct', false, (object)[ + 'featureSet' => $this->featureSet, + 'providerName' => $provider, + 'logged_user' => $this->user, + 'engineData' => $engineData + ] ); + break; + } + + if ( !$validEngine ) { + throw new DomainException("Engine not allowed", -4); + } + + $engineList = $this->featureSet->filter( 'getAvailableEnginesListForUser', Constants_Engines::getAvailableEnginesList(), $this->user ); + + $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); + $newCreatedDbRowStruct = null; + + if ( array_search( $newEngineStruct->class_load, $engineList ) ) { + $newEngineStruct->active = true; + $newCreatedDbRowStruct = $engineDAO->create( $newEngineStruct ); + $this->destroyUserEnginesCache(); + } + + if ( !$newCreatedDbRowStruct instanceof EnginesModel_EngineStruct ) { + + $error = $this->featureSet->filter( + 'engineCreationFailed', + [ 'code' => -9, 'message' => "Creation failed. Generic error" ], + $newEngineStruct->class_load + ); + + throw new DomainException($error['message'], $error['code']); + } + + if ( $newEngineStruct instanceof EnginesModel_MicrosoftHubStruct ) { + + $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); + $config = $newTestCreatedMT->getConfigStruct(); + $config[ 'segment' ] = "Hello World"; + $config[ 'source' ] = "en-US"; + $config[ 'target' ] = "it-IT"; + + $mt_result = $newTestCreatedMT->get( $config ); + + if ( isset( $mt_result[ 'error' ][ 'code' ] ) ) { + $engineDAO->delete( $newCreatedDbRowStruct ); + $this->destroyUserEnginesCache(); + + throw new DomainException($mt_result[ 'error' ]); + } + + } elseif ( $newEngineStruct instanceof EnginesModel_GoogleTranslateStruct ) { + + $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); + $config = $newTestCreatedMT->getConfigStruct(); + $config[ 'segment' ] = "Hello World"; + $config[ 'source' ] = "en-US"; + $config[ 'target' ] = "fr-FR"; + + $mt_result = $newTestCreatedMT->get( $config ); + + if ( isset( $mt_result[ 'error' ][ 'code' ] ) ) { + $engineDAO->delete( $newCreatedDbRowStruct ); + $this->destroyUserEnginesCache(); + + throw new DomainException($mt_result[ 'error' ]); + } + } else { + try { + $this->featureSet->run( 'postEngineCreation', $newCreatedDbRowStruct, $this->user ); + } catch ( Exception $e ) { + $engineDAO->delete( $newCreatedDbRowStruct ); + $this->destroyUserEnginesCache(); + + throw new DomainException($e->getMessage(), $e->getCode()); + } + } + + return $this->response->json([ + 'data' => [ + 'id' => $newCreatedDbRowStruct->id, + 'name' => $newCreatedDbRowStruct->name, + 'description' => $newCreatedDbRowStruct->description, + 'type' => $newCreatedDbRowStruct->type, + 'engine_type' => $newCreatedDbRowStruct->class_load, + ], + 'errors' => [], + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function disable(): Response + { + try { + $request = $this->validateTheRequest(); + $id = $request['id']; + + if ( empty( $id ) ) { + throw new InvalidArgumentException("Engine id required", -5); + } + + $engineToBeDeleted = EnginesModel_EngineStruct::getStruct(); + $engineToBeDeleted->id = $id; + $engineToBeDeleted->uid = $this->user->uid; + + $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); + $result = $engineDAO->disable( $engineToBeDeleted ); + $this->destroyUserEnginesCache(); + + if ( !$result instanceof EnginesModel_EngineStruct ) { + throw new RuntimeException("Deletion failed. Generic error", -9); + } + + $this->featureSet->run( 'postEngineDeletion', $result ); + + return $this->response->json([ + 'data' => [ + 'id' => $result->id + ], + 'errors' => [] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $id = filter_var( $this->request->param( 'id' ), FILTER_SANITIZE_STRING ); + $name = filter_var( $this->request->param( 'name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $data = filter_var( $this->request->param( 'data' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW | FILTER_FLAG_NO_ENCODE_QUOTES ] ); + $provider = filter_var( $this->request->param( 'provider' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + + return [ + 'id' => $id, + 'name' => $name, + 'data' => json_decode( $data, true ), + 'provider' => $provider, + ]; + } + + /** + * Destroy cache for engine users query + * + * @throws Exception + */ + private function destroyUserEnginesCache() + { + $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); + $engineStruct = EnginesModel_EngineStruct::getStruct(); + $engineStruct->uid = $this->user->uid; + $engineStruct->active = true; + + $engineDAO->destroyCache( $engineStruct ); + } +} + diff --git a/lib/Controller/API/App/FetchChangeRatesController.php b/lib/Controller/API/App/FetchChangeRatesController.php new file mode 100644 index 0000000000..0e73381cca --- /dev/null +++ b/lib/Controller/API/App/FetchChangeRatesController.php @@ -0,0 +1,30 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function fetch(): Response + { + try { + $changeRatesFetcher = new currency_translatedChangeRatesFetcher(); + $changeRatesFetcher->fetchChangeRates(); + + return $this->response->json( [ + 'data' => $changeRatesFetcher->getChangeRates() + ] ); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetContributionController.php b/lib/Controller/API/App/GetContributionController.php new file mode 100644 index 0000000000..19ae619c55 --- /dev/null +++ b/lib/Controller/API/App/GetContributionController.php @@ -0,0 +1,238 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function get(): Response + { + try { + $request = $this->validateTheRequest(); + + $id_client = $request['id_client']; + $id_job = $request['id_job']; + $id_segment = $request['id_segment']; + $num_results = $request['num_results']; + $text = $request['text']; + $id_translator = $request['id_translator']; + $password = $request['password']; + $received_password = $request['received_password']; + $concordance_search = $request['concordance_search']; + $switch_languages = $request['switch_languages']; + $context_before = $request['context_before']; + $context_after = $request['context_after']; + $id_before = $request['id_before']; + $id_after = $request['id_after']; + $cross_language = $request['cross_language']; + + if ( $id_translator == 'unknown_translator' ) { + $id_translator = ""; + } + + if ( empty( $num_results ) ) { + $num_results = INIT::$DEFAULT_NUM_RESULTS_FROM_TM; + } + + $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $id_job, $password ); + $dataRefMap = Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $id_segment ); + + $projectStruct = $jobStruct->getProject(); + $this->featureSet->loadForProject( $projectStruct ); + + if ( !$concordance_search ) { + $this->rewriteContributionContexts( $jobStruct->source, $jobStruct->target, $request ); + } + + $file = (new FilesPartsDao())->getBySegmentId($id_segment); + $owner = (new Users_UserDao())->getProjectOwner( $id_job ); + + $contributionRequest = new ContributionRequestStruct(); + $contributionRequest->id_file = $file->id_file; + $contributionRequest->id_job = $this->id_job; + $contributionRequest->password = $received_password; + $contributionRequest->user = $owner; + $contributionRequest->dataRefMap = $dataRefMap; + $contributionRequest->contexts = [ + 'context_before' => $request['context_before'], + 'segment' => $request['text'], + 'context_after' => $request['context_after'] + ]; + $contributionRequest->jobStruct = $jobStruct; + $contributionRequest->projectStruct = $projectStruct; + $contributionRequest->segmentId = $id_segment; + $contributionRequest->id_client = $id_client; + $contributionRequest->concordanceSearch = $concordance_search; + $contributionRequest->fromTarget = $switch_languages; + $contributionRequest->resultNum = $num_results; + $contributionRequest->crossLangTargets = $this->getCrossLanguages($cross_language); + + if ( $this->isRevision($id_job, $password) ) { + $contributionRequest->userRole = TmKeyManagement_Filter::ROLE_REVISOR; + } else { + $contributionRequest->userRole = TmKeyManagement_Filter::ROLE_TRANSLATOR; + } + + Request::contribution( $contributionRequest ); + + return $this->response->json([ + 'errors' => [], + 'data' => [ + "message" => "OK", + "id_client" => $id_client + ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest() + { + $id_client = filter_var( $this->request->param( 'id_client' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $num_results = filter_var( $this->request->param( 'num_results' ), FILTER_SANITIZE_NUMBER_INT ); + $text = filter_var( $this->request->param( 'text' ), FILTER_UNSAFE_RAW ); + $id_translator = filter_var( $this->request->param( 'id_translator' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $received_password = filter_var( $this->request->param( 'current_password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $concordance_search = filter_var( $this->request->param( 'is_concordance' ), FILTER_VALIDATE_BOOLEAN ); + $switch_languages = filter_var( $this->request->param( 'from_target' ), FILTER_VALIDATE_BOOLEAN ); + $context_before = filter_var( $this->request->param( 'context_before' ), FILTER_UNSAFE_RAW ); + $context_after = filter_var( $this->request->param( 'context_after' ), FILTER_UNSAFE_RAW ); + $id_before = filter_var( $this->request->param( 'id_before' ), FILTER_SANITIZE_NUMBER_INT ); + $id_after = filter_var( $this->request->param( 'id_after' ), FILTER_SANITIZE_NUMBER_INT ); + $cross_language = filter_var( $this->request->param( 'cross_language' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FORCE_ARRAY ] ); + $text = trim( $text ); + + if ( !$concordance_search ) { + //execute these lines only in segment contribution search, + //in case of user concordance search skip these lines + //because segment can be optional + if ( empty( $id_segment ) ) { + throw new InvalidArgumentException("missing id_segment", -1); + } + } + + if ( is_null( $text ) or $text === '' ) { + throw new InvalidArgumentException("missing text", -2); + } + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException("missing id job", -3); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("missing job password", -4); + } + + if ( empty( $id_client ) ) { + throw new InvalidArgumentException("missing id_client", -5); + } + + return [ + 'id_client' => $id_client, + 'id_job' => $id_job, + 'id_segment' => $id_segment, + 'num_results' => $num_results, + 'text' => $text, + 'id_translator' => $id_translator, + 'password' => $password, + 'received_password' => $received_password, + 'concordance_search' => $concordance_search, + 'switch_languages' => $switch_languages, + 'context_before' => $context_before, + 'context_after' => $context_after, + 'id_before' => $id_before, + 'id_after' => $id_after, + 'cross_language' => $cross_language, + ]; + } + + /** + * @param $source + * @param $target + * @param $request + * @throws Exception + */ + private function rewriteContributionContexts( $source, $target, &$request ): void + { + $featureSet = ( $this->featureSet !== null ) ? $this->featureSet : new \FeatureSet(); + + //Get contexts + $segmentsList = ( new Segments_SegmentDao )->setCacheTTL( 60 * 60 * 24 )->getContextAndSegmentByIDs( + [ + 'id_before' => $request['id_before'], + 'id_segment' => $request['id_segment'], + 'id_after' => $request['id_after'] + ] + ); + + $featureSet->filter( 'rewriteContributionContexts', $segmentsList, $request ); + + $Filter = MateCatFilter::getInstance( $featureSet, $source, $target, [] ); + + if ( $segmentsList->id_before ) { + $request['context_before'] = $Filter->fromLayer0ToLayer1( $segmentsList->id_before->segment ); + } + + if ( $segmentsList->id_segment ) { + $request['text'] = $Filter->fromLayer0ToLayer1( $segmentsList->id_segment->segment ); + } + + if ( $segmentsList->id_after ) { + $request['context_after'] = $Filter->fromLayer0ToLayer1( $segmentsList->id_after->segment ); + } + } + + /** + * Remove voids + * ("en-GB," => [0 => 'en-GB']) + * + * @param $cross_language + * @return array + */ + private function getCrossLanguages($cross_language): array + { + return !empty( $cross_language ) ? explode( ",", rtrim( $cross_language[ 0 ], ',' ) ) : []; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetProjectsController.php b/lib/Controller/API/App/GetProjectsController.php new file mode 100644 index 0000000000..005232fc38 --- /dev/null +++ b/lib/Controller/API/App/GetProjectsController.php @@ -0,0 +1,184 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function fetch(): Response + { + try { + $this->featureSet->loadFromUserEmail( $this->user->email ) ; + $request = $this->validateTheRequest(); + + $page = $request['page']; + $start = $request['start']; + $step = $request['step']; + $search_status = $request['search_status']; + $project_id = $request['project_id']; + $search_in_pname = $request['search_in_pname']; + $source = $request['source']; + $target = $request['target']; + $status = $request['status']; + $only_completed = $request['only_completed']; + $id_team = $request['id_team']; + $id_assignee = $request['id_assignee']; + $no_assignee = $request['no_assignee']; + + $team = $this->filterTeam($id_team); + + if( $team->type == Constants_Teams::PERSONAL ){ + $assignee = $this->user; + $team = null; + } else { + $assignee = $this->filterAssignee( $team, $id_assignee ); + } + + $projects = ManageUtils::getProjects( + $this->user, + $start, + $step, + $search_in_pname, + $source, + $target, + $search_status, + $only_completed, + $project_id, + $team, + $assignee, + $no_assignee + ); + + $projnum = ManageUtils::getProjectsNumber( + $this->user, + $search_in_pname, + $source, + $target, + $search_status, + $only_completed, + $team, + $assignee, + $no_assignee + ); + + return $this->response->json([ + 'data' => $projects, + 'page' => $page, + 'pnumber' => $projnum[ 0 ][ 'c' ], + 'pageStep' => $step, + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $page = filter_var( $this->request->param( 'page' ), FILTER_SANITIZE_NUMBER_INT ); + $step = filter_var( $this->request->param( 'step' ), FILTER_SANITIZE_NUMBER_INT ); + $project_id = filter_var( $this->request->param( 'project' ), FILTER_SANITIZE_NUMBER_INT ); + $search_in_pname = filter_var( $this->request->param( 'pn' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source = filter_var( $this->request->param( 'source' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $target = filter_var( $this->request->param( 'target' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $status = filter_var( $this->request->param( 'status' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $only_completed = filter_var( $this->request->param( 'onlycompleted' ), FILTER_VALIDATE_BOOLEAN, [ 'flags' => FILTER_NULL_ON_FAILURE ] ); + $id_team = filter_var( $this->request->param( 'id_team' ), FILTER_SANITIZE_NUMBER_INT ); + $id_assignee = filter_var( $this->request->param( 'id_assignee' ), FILTER_SANITIZE_NUMBER_INT ); + $no_assignee = filter_var( $this->request->param( 'no_assignee' ), FILTER_VALIDATE_BOOLEAN ); + + $search_status = (!empty( $status ) and Constants_JobStatus::isAllowedStatus( $status)) ? $status : Constants_JobStatus::STATUS_ACTIVE; + $page = (!empty( $page )) ? (int)$page : 1; + $step = (!empty( $step )) ? (int)$step : 10; + $start = ( $page - 1 ) * $step; + + if ( empty( $id_team ) ) { + throw new InvalidArgumentException("No id team provided", -1); + } + + return [ + 'page' => $page, + 'start' => $start, + 'step' => $step, + 'search_status' => $search_status, + 'project_id' => $project_id, + 'search_in_pname' => $search_in_pname, + 'source' => $source, + 'target' => $target, + 'status' => $status, + 'only_completed' => $only_completed, + 'id_team' => $id_team, + 'id_assignee' => $id_assignee, + 'no_assignee' => $no_assignee, + ]; + } + + /** + * @param TeamStruct $team + * @param $id_assignee + * + * @return Users_UserStruct|null + * @throws Exception + */ + private function filterAssignee( TeamStruct $team, $id_assignee ): ?Users_UserStruct + { + if ( is_null( $id_assignee ) ) { + return null; + } + + $dao = new MembershipDao(); + $memberships = $dao->setCacheTTL( 60 * 60 * 24 )->getMemberListByTeamId( $team->id ); + + /** + * @var $users MembershipStruct[] + */ + $users = array_values( array_filter( $memberships, function ( MembershipStruct $membership ) use ( $id_assignee ) { + return $membership->getUser()->uid == $id_assignee; + } ) ); + + if ( empty( $users ) ) { + throw new NotFoundException( 'Assignee not found in team' ); + } + + return $users[ 0 ]->getUser(); + } + + /** + * @param $id_team + * + * @return TeamStruct|null + * @throws Exception + */ + private function filterTeam($id_team): ?TeamStruct + { + $dao = new MembershipDao() ; + $team = $dao->findTeamByIdAndUser($id_team, $this->user); + + if ( !$team ) { + throw new NotFoundException( 'Team not found in user memberships', 404 ) ; + } + + return $team ; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetSearchController.php b/lib/Controller/API/App/GetSearchController.php new file mode 100644 index 0000000000..da37a210c9 --- /dev/null +++ b/lib/Controller/API/App/GetSearchController.php @@ -0,0 +1,523 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function search(): Response + { + try { + $request = $this->validateTheRequest(); + $res = $this->doSearch($request); + + return $this->response->json([ + 'data' => [], + 'errors' => [], + 'token' => $request['token'], + 'total' => $res[ 'count' ], + 'segments' => $res[ 'sid_list' ], + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function replaceAll(): Response + { + try { + $request = $this->validateTheRequest(); + $res = $this->doSearch($request); + $search_results = []; + + // and then hydrate the $search_results array + foreach ( $res[ 'sid_list' ] as $segmentId ) { + $search_results[] = Translations_SegmentTranslationDao::findBySegmentAndJob( $segmentId, $request['queryParams'][ 'job' ] )->toArray(); + } + + // set the replacement in queryParams + $request['queryParams'][ 'replacement' ] = $request['replace']; + + // update segment translations + $this->updateSegments( $search_results, $request['job'], $request['password'], $request['id_segment'], $request['queryParams'], $request['revisionNumber'] ); + + // and save replace events + $srh = $this->getReplaceHistory($request['job']); + $replace_version = ( $srh->getCursor() + 1 ); + + foreach ( $search_results as $tRow ) { + $this->saveReplacementEvent( $replace_version, $tRow, $srh, $request['queryParams'] ); + } + + return $this->response->json([ + 'success' => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + // not is use + public function redoReplaceAll(): Response + { + try { + $request = $this->validateTheRequest(); + $shr = $this->getReplaceHistory($request['job']); + $search_results = $this->getSegmentForRedoReplaceAll($shr); + $this->updateSegments( $search_results, $request['job'], $request['password'], $request['id_segment'], $request['queryParams'], $request['revisionNumber'] ); + $shr->redo(); + + return $this->response->json([ + 'success' => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + // not is use + public function undoReplaceAll(): Response + { + try { + $request = $this->validateTheRequest(); + $shr = $this->getReplaceHistory($request['job']); + $search_results = $this->getSegmentForUndoReplaceAll($shr); + $this->updateSegments( $search_results, $request['job'], $request['password'], $request['id_segment'], $request['queryParams'], $request['revisionNumber'] ); + $shr->undo(); + + return $this->response->json([ + 'success' => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest() + { + $job = filter_var( $this->request->param( 'job' ), FILTER_SANITIZE_NUMBER_INT ); + $token = filter_var( $this->request->param( 'token' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source = filter_var( $this->request->param( 'source' ), FILTER_UNSAFE_RAW ); + $target = filter_var( $this->request->param( 'target' ), FILTER_UNSAFE_RAW ); + $status = filter_var( $this->request->param( 'status' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $replace = filter_var( $this->request->param( 'replace' ), FILTER_UNSAFE_RAW ); + $password = filter_var( $this->request->param( 'password' ), FILTER_UNSAFE_RAW ); + $isMatchCaseRequested = filter_var( $this->request->param( 'matchcase' ), FILTER_VALIDATE_BOOLEAN ); + $isExactMatchRequested = filter_var( $this->request->param( 'exactmatch' ), FILTER_VALIDATE_BOOLEAN ); + $strictMode = filter_var( $this->request->param( 'strict_mode' ), FILTER_VALIDATE_BOOLEAN ); + $revision_number = filter_var( $this->request->param( 'revision_number' ), FILTER_VALIDATE_INT ); + + if ( empty( $job ) ) { + throw new InvalidArgumentException("missing id job", -2); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("missing job password", -3); + } + + switch ( $status ) { + case 'translated': + case 'approved': + case 'approved2': + case 'rejected': + case 'draft': + case 'new': + break; + default: + $status = "all"; + break; + } + + $queryParams = new SearchQueryParamsStruct( [ + 'job' => $job, + 'password' => $password, + 'key' => null, + 'src' => null, + 'trg' => null, + 'status' => $status, + 'replacement' => $replace, + 'isMatchCaseRequested' => $isMatchCaseRequested, + 'isExactMatchRequested' => $isExactMatchRequested, + 'strictMode' => $strictMode, + ] ); + + return [ + 'job' => $job, + 'token' => $token, + 'source' => $source, + 'target' => $target, + 'status' => $status, + 'replace' => $replace, + 'password' => $password, + 'isMatchCaseRequested' => $isMatchCaseRequested, + 'isExactMatchRequested' => $isExactMatchRequested, + 'strictMode' => $strictMode, + 'revisionNumber' => $revision_number, + 'queryParams' => $queryParams, + ]; + } + + /** + * @param $job_id + * @param $password + * @return Chunks_ChunkStruct|null + * @throws Exception + */ + private function getJobData($job_id, $password): ?Chunks_ChunkStruct + { + return Chunks_ChunkDao::getByIdAndPassword( (int)$job_id, $password ); + } + + /** + * @param $job_id + * @return Search_ReplaceHistory + */ + private function getReplaceHistory($job_id): Search_ReplaceHistory + { + // Search_ReplaceHistory init + $srh_driver = ( isset( INIT::$REPLACE_HISTORY_DRIVER ) and '' !== INIT::$REPLACE_HISTORY_DRIVER ) ? INIT::$REPLACE_HISTORY_DRIVER : 'redis'; + $srh_ttl = ( isset( INIT::$REPLACE_HISTORY_TTL ) and '' !== INIT::$REPLACE_HISTORY_TTL ) ? INIT::$REPLACE_HISTORY_TTL : 300; + + return Search_ReplaceHistoryFactory::create( $job_id, $srh_driver, $srh_ttl ); + } + + /** + * @param SearchQueryParamsStruct $queryParams + * @param Jobs_JobStruct $jobStruct + * @return SearchModel + * @throws Exception + */ + private function getSearchModel(SearchQueryParamsStruct $queryParams, Jobs_JobStruct $jobStruct): SearchModel + { + /** @var MateCatFilter $filter */ + $filter = MateCatFilter::getInstance( $this->getFeatureSet(), $jobStruct->source, $jobStruct->target ); + + return new SearchModel( $queryParams, $filter ); + } + + /** + * @param Search_ReplaceHistory $srh + * @return array + */ + private function getSegmentForRedoReplaceAll(Search_ReplaceHistory $srh): array + { + $results = []; + + $versionToMove = $srh->getCursor() + 1; + $events = $srh->get( $versionToMove ); + + foreach ( $events as $event ) { + $results[] = [ + 'id_segment' => $event->id_segment, + 'id_job' => $event->id_job, + 'translation' => $event->translation_before_replacement, + 'status' => $event->status, + ]; + } + + return $results; + } + + /** + * @param Search_ReplaceHistory $srh + * @return array + */ + private function getSegmentForUndoReplaceAll(Search_ReplaceHistory $srh): array + { + $results = []; + $cursor = $srh->getCursor(); + + if ( $cursor === 0 ) { + $versionToMove = 0; + } elseif ( $cursor === 1 ) { + $versionToMove = 1; + } else { + $versionToMove = $cursor - 1; + } + + $events = $srh->get( $versionToMove ); + + foreach ( $events as $event ) { + $results[] = [ + 'id_segment' => $event->id_segment, + 'id_job' => $event->id_job, + 'translation' => $event->translation_after_replacement, + 'status' => $event->status, + ]; + } + + return $results; + } + + /** + * @param $request + * @return array + */ + private function doSearch($request): array + { + $queryParams = $request['queryParams']; + + if ( !empty( $request['source'] ) and !empty( $request['target'] ) ) { + $queryParams[ 'key' ] = 'coupled'; + $queryParams[ 'src' ] = html_entity_decode( $request['source'] ); // source strings are not escaped as html entites in DB. Example: < must be decoded to < + $queryParams[ 'trg' ] = $request['target']; + } elseif ( !empty( $request['source'] ) ) { + $queryParams[ 'key' ] = 'source'; + $queryParams[ 'src' ] = html_entity_decode( $request['source'] ); // source strings are not escaped as html entites in DB. Example: < must be decoded to < + } elseif ( !empty( $request['target'] ) ) { + $queryParams[ 'key' ] = 'target'; + $queryParams[ 'trg' ] = $request['target']; + } else { + $queryParams[ 'key' ] = 'status_only'; + } + + try { + $strictMode = ( null !== $queryParams[ 'strictMode' ] ) ? $queryParams[ 'strictMode' ] : true; + $jodData = $this->getJobData($request['job'], $request['password']); + $searchModel = $this->getSearchModel($queryParams, $jodData); + + return $searchModel->search( $strictMode ); + } catch ( Exception $e ) { + throw new RuntimeException("internal error: see the log", -1000); + } + } + + /** + * @param $search_results + * @param $id_job + * @param $password + * @param $id_segment + * @param SearchQueryParamsStruct $queryParams + * @param bool $revisionNumber + * @throws Exception + */ + private function updateSegments( $search_results, $id_job, $password, $id_segment, SearchQueryParamsStruct $queryParams, $revisionNumber = false ): void + { + $db = Database::obtain(); + + $chunk = Chunks_ChunkDao::getByIdAndPassword( (int)$id_job, $password ); + $project = Projects_ProjectDao::findByJobId( (int)$id_job ); + $versionsHandler = TranslationVersions::getVersionHandlerNewInstance( $chunk, $id_segment, $this->user, $project ); + + // loop all segments to replace + foreach ( $search_results as $key => $tRow ) { + + // start the transaction + $db->begin(); + + $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( (int)$tRow[ 'id_segment' ], (int)$tRow[ 'id_job' ] ); + $segment = ( new Segments_SegmentDao() )->getById( $tRow[ 'id_segment' ] ); + + // Propagation + $propagationTotal = [ + 'propagated_ids' => [] + ]; + + if ( $old_translation->translation !== $tRow[ 'translation' ] && in_array( $old_translation->status, [ + Constants_TranslationStatus::STATUS_TRANSLATED, + Constants_TranslationStatus::STATUS_APPROVED, + Constants_TranslationStatus::STATUS_APPROVED2, + Constants_TranslationStatus::STATUS_REJECTED + ] ) + ) { + $TPropagation = new Translations_SegmentTranslationStruct(); + $TPropagation[ 'status' ] = $tRow[ 'status' ]; + $TPropagation[ 'id_job' ] = $id_job; + $TPropagation[ 'translation' ] = $tRow[ 'translation' ]; + $TPropagation[ 'autopropagated_from' ] = $id_segment; + $TPropagation[ 'serialized_errors_list' ] = $old_translation->serialized_errors_list; + $TPropagation[ 'warning' ] = $old_translation->warning; + $TPropagation[ 'segment_hash' ] = $old_translation[ 'segment_hash' ]; + + try { + $propagationTotal = Translations_SegmentTranslationDao::propagateTranslation( + $TPropagation, + $chunk, + $id_segment, + $project, + $versionsHandler + ); + + } catch ( Exception $e ) { + $msg = $e->getMessage() . "\n\n" . $e->getTraceAsString(); + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + $db->rollback(); + + throw new RuntimeException("A fatal error occurred during saving of segments"); + } + } + + $filter = MateCatFilter::getInstance( $this->getFeatureSet(), $chunk->source, $chunk->target, [] ); + $replacedTranslation = $filter->fromLayer1ToLayer0( $this->getReplacedSegmentTranslation( $tRow[ 'translation' ], $queryParams ) ); + $replacedTranslation = Utils::stripBOM( $replacedTranslation ); + + // Setup $new_translation + $new_translation = new Translations_SegmentTranslationStruct(); + $new_translation->id_segment = $tRow[ 'id_segment' ]; + $new_translation->id_job = $chunk->id; + $new_translation->status = $this->getNewStatus( $old_translation, $revisionNumber ); + $new_translation->time_to_edit = $old_translation->time_to_edit; + $new_translation->segment_hash = $segment->segment_hash; + $new_translation->translation = $replacedTranslation; + $new_translation->serialized_errors_list = $old_translation->serialized_errors_list; + $new_translation->suggestion_position = $old_translation->suggestion_position; + $new_translation->warning = $old_translation->warning; + $new_translation->translation_date = date( "Y-m-d H:i:s" ); + + $version_number = $old_translation->version_number; + if ( false === Utils::stringsAreEqual( $new_translation->translation, $old_translation->translation ) ) { + $version_number++; + } + + $new_translation->version_number = $version_number; + + // Save version + $versionsHandler->saveVersionAndIncrement( $new_translation, $old_translation ); + + // preSetTranslationCommitted + $versionsHandler->storeTranslationEvent( [ + 'translation' => $new_translation, + 'old_translation' => $old_translation, + 'propagation' => $propagationTotal, + 'chunk' => $chunk, + 'segment' => $segment, + 'user' => $this->user, + 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $revisionNumber ), + 'features' => $this->featureSet, + 'project' => $project + ] ); + + // commit the transaction + try { + Translations_SegmentTranslationDao::updateTranslationAndStatusAndDate( $new_translation ); + $db->commit(); + } catch ( Exception $e ) { + $this->log( "Lock: Transaction Aborted. " . $e->getMessage() ); + $db->rollback(); + + throw new RuntimeException("A fatal error occurred during saving of segments"); + } + + // setTranslationCommitted + try { + $this->featureSet->run( 'setTranslationCommitted', [ + 'translation' => $new_translation, + 'old_translation' => $old_translation, + 'propagated_ids' => $propagationTotal[ 'propagated_ids' ], + 'chunk' => $chunk, + 'segment' => $segment, + 'user' => $this->user, + 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $revisionNumber ) + ] ); + } catch ( Exception $e ) { + $this->log( "Exception in setTranslationCommitted callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); + + throw new RuntimeException("Exception in setTranslationCommitted callback"); + } + } + } + + /** + * @param Translations_SegmentTranslationStruct $translationStruct + * @param bool $revisionNumber + * @return string + */ + private function getNewStatus( Translations_SegmentTranslationStruct $translationStruct, $revisionNumber = false ): string + { + if ( false === $revisionNumber ) { + return Constants_TranslationStatus::STATUS_TRANSLATED; + } + + if ( $translationStruct->status === Constants_TranslationStatus::STATUS_TRANSLATED ) { + return Constants_TranslationStatus::STATUS_TRANSLATED; + } + + return Constants_TranslationStatus::STATUS_APPROVED; + } + + /** + * @param $translation + * @param SearchQueryParamsStruct $queryParams + * + * @return string|string[]|null + */ + private function getReplacedSegmentTranslation( $translation, SearchQueryParamsStruct $queryParams ) + { + $replacedSegmentTranslation = WholeTextFinder::findAndReplace( + $translation, + $queryParams->target, + $queryParams->replacement, + true, + $queryParams->isExactMatchRequested, + $queryParams->isMatchCaseRequested, + true + ); + + return ( !empty( $replacedSegmentTranslation ) ) ? $replacedSegmentTranslation[ 'replacement' ] : $translation; + } + + /** + * @param $replace_version + * @param $tRow + * @param Search_ReplaceHistory $srh + * @param SearchQueryParamsStruct $queryParams + */ + private function saveReplacementEvent( $replace_version, $tRow, Search_ReplaceHistory $srh, SearchQueryParamsStruct $queryParams ): void + { + $event = new ReplaceEventStruct(); + $event->replace_version = $replace_version; + $event->id_segment = $tRow[ 'id_segment' ]; + $event->id_job = $queryParams[ 'job' ]; + $event->job_password = $queryParams[ 'password' ]; + $event->source = $queryParams[ 'source' ]; + $event->target = $queryParams[ 'target' ]; + $event->replacement = $queryParams[ 'replacement' ]; + $event->translation_before_replacement = $tRow[ 'translation' ]; + $event->translation_after_replacement = $this->getReplacedSegmentTranslation( $tRow[ 'translation' ], $queryParams ); + $event->status = $tRow[ 'status' ]; + + $srh->save( $event ); + $srh->updateIndex( $replace_version ); + + $this->log('Replacement event for segment #' . $tRow[ 'id_segment' ] . ' correctly saved.'); + } +} diff --git a/lib/Controller/API/App/GetSegmentsController.php b/lib/Controller/API/App/GetSegmentsController.php new file mode 100644 index 0000000000..4592fd495f --- /dev/null +++ b/lib/Controller/API/App/GetSegmentsController.php @@ -0,0 +1,236 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function segments() + { + try { + $request = $this->validateTheRequest(); + $jid = $request['jid']; + $step = $request['step']; + $id_segment = $request['id_segment']; + $password = $request['password']; + $where = $request['where']; + + $job = Chunks_ChunkDao::getByIdAndPassword( $jid, $password ); + + if($job === null){ + throw new Exception("Job not found"); + } + + $project = $job->getProject(); + $featureSet = $this->getFeatureSet(); + $featureSet->loadForProject( $project ); + $lang_handler = Langs_Languages::getInstance(); + + $parsedIdSegment = $this->parseIDSegment($id_segment); + + if ( $parsedIdSegment['id_segment'] == '' ) { + $parsedIdSegment['id_segment'] = 0; + } + + $sDao = new Segments_SegmentDao(); + $data = $sDao->getPaginationSegments( + $job, + $step, + $parsedIdSegment['id_segment'], + $where, + [ + 'optional_fields' => [ + 'st.edit_distance', + 'st.version_number' + ] + ] + ); + + $segment_notes = $this->prepareNotes( $data ); + $contexts = $this->getContextGroups( $data ); + $res = []; + + foreach ( $data as $i => $seg ) { + + $id_file = $seg[ 'id_file' ]; + + if ( !isset( $res[ $id_file ] ) ) { + $res[ $id_file ][ 'jid' ] = $seg[ 'jid' ]; + $res[ $id_file ][ "filename" ] = ZipArchiveExtended::getFileName( $seg[ 'filename' ] ); + $res[ $id_file ][ 'source' ] = $lang_handler->getLocalizedName( $job->source ); + $res[ $id_file ][ 'target' ] = $lang_handler->getLocalizedName( $job->target ); + $res[ $id_file ][ 'source_code' ] = $job->source; + $res[ $id_file ][ 'target_code' ] = $job->target; + $res[ $id_file ][ 'segments' ] = []; + } + + if ( isset( $seg[ 'edit_distance' ] ) ) { + $seg[ 'edit_distance' ] = round( $seg[ 'edit_distance' ] / 1000, 2 ); + } else { + $seg[ 'edit_distance' ] = 0; + } + + $seg[ 'parsed_time_to_edit' ] = CatUtils::parse_time_to_edit( $seg[ 'time_to_edit' ] ); + + ( $seg[ 'source_chunk_lengths' ] === null ? $seg[ 'source_chunk_lengths' ] = '[]' : null ); + ( $seg[ 'target_chunk_lengths' ] === null ? $seg[ 'target_chunk_lengths' ] = '{"len":[0],"statuses":["DRAFT"]}' : null ); + $seg[ 'source_chunk_lengths' ] = json_decode( $seg[ 'source_chunk_lengths' ], true ); + $seg[ 'target_chunk_lengths' ] = json_decode( $seg[ 'target_chunk_lengths' ], true ); + + // inject original data ref map (FOR XLIFF 2.0) + $data_ref_map = json_decode( $seg[ 'data_ref_map' ], true ); + $seg[ 'data_ref_map' ] = $data_ref_map; + + $Filter = MateCatFilter::getInstance( $featureSet, $job->source, $job->target, null !== $data_ref_map ? $data_ref_map : [] ); + + $seg[ 'segment' ] = $Filter->fromLayer0ToLayer1( + CatUtils::reApplySegmentSplit( $seg[ 'segment' ], $seg[ 'source_chunk_lengths' ] ) + ); + + $seg[ 'translation' ] = $Filter->fromLayer0ToLayer1( + CatUtils::reApplySegmentSplit( $seg[ 'translation' ], $seg[ 'target_chunk_lengths' ][ 'len' ] ) + ); + + $seg[ 'translation' ] = $Filter->fromLayer1ToLayer2( $Filter->realignIDInLayer1( $seg[ 'segment' ], $seg[ 'translation' ] ) ); + $seg[ 'segment' ] = $Filter->fromLayer1ToLayer2( $seg[ 'segment' ] ); + + $seg[ 'metadata' ] = Segments_SegmentMetadataDao::getAll( $seg[ 'sid' ] ); + + $this->attachNotes( $seg, $segment_notes ); + $this->attachContexts( $seg, $contexts ); + + $res[ $id_file ][ 'segments' ][] = $seg; + } + + $result = [ + 'errors' => [], + ]; + + $result[ 'data' ][ 'files' ] = $res; + $result[ 'data' ][ 'where' ] = $where; + $result[ 'data' ] = $featureSet->filter( 'filterGetSegmentsResult', $result[ 'data' ], $job ); + + return $this->response->json($result); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $jid = filter_var( $this->request->param( 'jid' ), FILTER_SANITIZE_NUMBER_INT ); + $step = filter_var( $this->request->param( 'step' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'segment' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $where = filter_var( $this->request->param( 'where' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + + if ( empty( $jid) ) { + throw new InvalidArgumentException("No id job provided", -1); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("No job password provided", -2); + } + + if( empty($id_segment) ){ + throw new InvalidArgumentException("No is segment provided", -3); + } + + if( $step > self::MAX_PER_PAGE ) { + $step = self::MAX_PER_PAGE; + } + + return [ + 'jid' => $jid, + 'id_segment' => $id_segment, + 'password' => $password, + 'where' => $where, + 'step' => $step, + ]; + } + + /** + * @param $segment + * @param $segment_notes + */ + private function attachNotes( &$segment, $segment_notes ) { + $segment[ 'notes' ] = isset( $segment_notes[ (int)$segment[ 'sid' ] ] ) ? $segment_notes[ (int)$segment[ 'sid' ] ] : null; + } + + /** + * @param $segment + * @param $contexts + */ + private function attachContexts( &$segment, $contexts ) { + $segment[ 'context_groups' ] = isset( $contexts[ (int)$segment[ 'sid' ] ] ) ? $contexts[ (int)$segment[ 'sid' ] ] : null; + } + + /** + * @param $segments + * @return array|mixed + * @throws \API\Commons\Exceptions\AuthenticationError + * @throws \Exceptions\NotFoundException + * @throws \Exceptions\ValidationError + * @throws \TaskRunner\Exceptions\EndQueueException + * @throws \TaskRunner\Exceptions\ReQueueException + */ + private function prepareNotes( $segments ) + { + if ( !empty( $segments[ 0 ] ) ) { + $start = $segments[ 0 ][ 'sid' ]; + $last = end( $segments ); + $stop = $last[ 'sid' ]; + + if ( $this->featureSet->filter( 'prepareAllNotes', false ) ) { + $segment_notes = Segments_SegmentNoteDao::getAllAggregatedBySegmentIdInInterval( $start, $stop ); + foreach ( $segment_notes as $k => $noteObj ) { + $segment_notes[ $k ][ 0 ][ 'json' ] = json_decode( $noteObj[ 0 ][ 'json' ], true ); + } + $segment_notes = $this->featureSet->filter( 'processExtractedJsonNotes', $segment_notes ); + } else { + $segment_notes = Segments_SegmentNoteDao::getAggregatedBySegmentIdInInterval( $start, $stop ); + } + + return $segment_notes; + } + } + + /** + * @param $segments + * @return array + */ + private function getContextGroups( $segments ) + { + if ( !empty( $segments[ 0 ] ) ) { + $start = $segments[ 0 ][ 'sid' ]; + $last = end( $segments ); + $stop = $last[ 'sid' ]; + + return ( new ContextGroupDao() )->getBySIDRange( $start, $stop ); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetTagProjectionController.php b/lib/Controller/API/App/GetTagProjectionController.php new file mode 100644 index 0000000000..831792f02b --- /dev/null +++ b/lib/Controller/API/App/GetTagProjectionController.php @@ -0,0 +1,139 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function call() + { + try { + Log::$fileName = 'tagProjection.log'; + + $request = $this->validateTheRequest(); + $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $request['id_job'], $request['password'] ); + $this->featureSet->loadForProject( $jobStruct->getProject() ); + + /** + * @var $engine Engines_MyMemory + */ + $engine = Engine::getInstance( 1 ); + $engine->setFeatureSet( $this->featureSet ); + + $dataRefMap = Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $request['id_segment'] ); + $Filter = MateCatFilter::getInstance( $this->getFeatureSet(), $request['source_lang'], $request['target_lang'], $dataRefMap ); + + $config = []; + $config[ 'dataRefMap' ] = $dataRefMap; + $config[ 'source' ] = $Filter->fromLayer2ToLayer1( $request['source'] ); + $config[ 'target' ] = $Filter->fromLayer2ToLayer1( $request['target'] ); + $config[ 'source_lang' ] = $request['source_lang']; + $config[ 'target_lang' ] = $request['target_lang']; + $config[ 'suggestion' ] = $Filter->fromLayer2ToLayer1( $request['suggestion'] ); + + $result = $engine->getTagProjection( $config ); + + return $this->response->json([ + 'code' => 0, + 'data' => [ + 'translation' => $Filter->fromLayer1ToLayer2( $result->responseData ) + ], + ]); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest() + { + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $source = filter_var( $this->request->param( 'source' ), FILTER_UNSAFE_RAW ); + $target = filter_var( $this->request->param( 'target' ), FILTER_UNSAFE_RAW ); + $suggestion = filter_var( $this->request->param( 'suggestion' ), FILTER_UNSAFE_RAW ); + $source_lang = filter_var( $this->request->param( 'source_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $target_lang = filter_var( $this->request->param( 'target_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( is_null( $source ) || $source === '' ) { + throw new InvalidArgumentException("missing source segment" , -1); + } + + if ( is_null( $target ) || $target === '' ) { + throw new InvalidArgumentException("missing target segment" , -2); + } + + if ( empty( $source_lang ) ) { + throw new InvalidArgumentException("missing source lang" , -3); + } + + if ( empty( $target_lang ) ) { + throw new InvalidArgumentException("missing target lang" , -4); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("missing job password" , -5); + } + + if ( empty( $id_segment ) ) { + throw new InvalidArgumentException("missing id segment" , -6); + } + + if ( empty( $id_job ) ) { + $msg = "\n\n Critical. Quit. \n\n " . var_export( $_POST, true ); + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + + throw new InvalidArgumentException("id_job not valid" , -4); + } + + return [ + 'id_segment' => $id_segment, + 'id_job' => $id_job, + 'password' => $password, + 'source' => $source, + 'target' => $target, + 'suggestion' => $suggestion, + 'source_lang' => $source_lang, + 'target_lang' => $target_lang, + ]; + } + + + /** + * @param $data + * @param null $msg + */ + private function logTagProjection( $data, $msg = null ) + { + if ( !$msg ) { + Log::doJsonLog( $data ); + } else { + Log::doJsonLog( $msg ); + } + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetTranslationMismatchesController.php b/lib/Controller/API/App/GetTranslationMismatchesController.php new file mode 100644 index 0000000000..34cc7cb79f --- /dev/null +++ b/lib/Controller/API/App/GetTranslationMismatchesController.php @@ -0,0 +1,78 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function get(): Response + { + try { + $request = $this->validateTheRequest(); + + $id_job = $request['id_job']; + $id_segment = $request['id_segment']; + $password = $request['password']; + + $this->featureSet->loadForProject( Projects_ProjectDao::findByJobId( $id_job, 60 * 60 ) ); + $parsedIdSegment = $this->parseIdSegment($id_segment); + + if ( $parsedIdSegment['id_segment'] == '' ) { + $parsedIdSegment['id_segment'] = 0; + } + + $sDao = new Segments_SegmentDao(); + $Translation_mismatches = $sDao->setCacheTTL( 1 * 60 /* 1 minutes cache */ )->getTranslationsMismatches( $id_job, $password, $parsedIdSegment['id_segment'] ); + + return $this->response->json([ + 'errors' => [], + 'code' => 1, + 'data' => ( new SegmentTranslationMismatches( $Translation_mismatches, count( $Translation_mismatches ), $this->featureSet ) )->render() + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException("No id job provided", -1); + } + + if ( empty( $id_segment ) ) { + throw new InvalidArgumentException("No id segment provided", -1); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("No job password provided", -1); + } + + return [ + 'id_job' => $id_job, + 'id_segment' => $id_segment, + 'password' => $password, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetVolumeAnalysisController.php b/lib/Controller/API/App/GetVolumeAnalysisController.php new file mode 100644 index 0000000000..72eeabe457 --- /dev/null +++ b/lib/Controller/API/App/GetVolumeAnalysisController.php @@ -0,0 +1,69 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function analysis(): Response + { + try { + $request = $this->validateTheRequest(); + $_project_data = Projects_ProjectDao::getProjectAndJobData( $request['pid'] ); + $passCheck = new AjaxPasswordCheck(); + $access = $passCheck->grantProjectAccess( $_project_data, $request['ppassword'] ) or $passCheck->grantProjectJobAccessOnJobPass( $_project_data, null, $request['jpassword'] ); + + if ( !$access ) { + throw new AuthenticationError("Wrong Password. Access denied", -10); + } + + $analysisStatus = new Status( $_project_data, $this->featureSet, $this->user ); + + return $this->response->json($analysisStatus->fetchData()->getResult()); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $pid = filter_var( $this->request->param( 'pid' ), FILTER_SANITIZE_NUMBER_INT ); + $ppassword = filter_var( $this->request->param( 'ppassword' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $jpassword = filter_var( $this->request->param( 'jpassword' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( empty( $pid ) ) { + throw new InvalidArgumentException("No id project provided", -1); + } + + if ( empty( $ppassword ) ) { + throw new InvalidArgumentException("No project password provided", -2); + } + + if ( empty( $jpassword ) ) { + throw new InvalidArgumentException("No job password provided", -3); + } + + return [ + 'pid' => $pid, + 'ppassword' => $ppassword, + 'jpassword' => $jpassword, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/GetWarningController.php b/lib/Controller/API/App/GetWarningController.php new file mode 100644 index 0000000000..8a2d02c22c --- /dev/null +++ b/lib/Controller/API/App/GetWarningController.php @@ -0,0 +1,234 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function global(): Response + { + try { + $request = $this->validateTheGlobalRequest(); + $id_job = $request['id_job']; + $password = $request['password']; + $token = $request['token']; + + try { + $chunk = $this->getChunk($id_job, $password); + $warnings = WarningDao::getWarningsByJobIdAndPassword( $id_job, $password ); + $tMismatch = ( new Segments_SegmentDao() )->setCacheTTL( 10 * 60 /* 10 minutes cache */ )->getTranslationsMismatches( $id_job, $password ); + } catch ( Exception $e ) { + return $this->response->json([ + 'details' => [] + ]); + } + + $qa = new QAGlobalWarning( $warnings, $tMismatch ); + + $result = array_merge( + [ + 'data' => [], + 'errors' => [], + 'token' => (!empty($token) ? $token : null) + ], + $qa->render(), + Utils::getGlobalMessage() + ); + + $result = $this->featureSet->filter( 'filterGlobalWarnings', $result, [ + 'chunk' => $chunk, + ] ); + + return $this->response->json($result); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheGlobalRequest(): array + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $token = filter_var( $this->request->param( 'token' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + + if ( empty( $id_job ) ) { + throw new \InvalidArgumentException("Empty id job", -1); + } + + if ( empty( $password ) ) { + throw new \InvalidArgumentException("Empty job password", -2); + } + + return [ + 'id_job' => $id_job, + 'password' => $password, + 'token' => $token, + ]; + } + + /** + * @return Response + */ + public function local(): Response + { + try { + $request = $this->validateTheLocalRequest(); + $id = $request['id']; + $id_job = $request['id_job']; + $src_content = $request['src_content']; + $trg_content = $request['trg_content']; + $password = $request['password']; + $token = $request['token']; + $logs = $request['logs']; + $segment_status = $request['segment_status']; + $characters_counter = $request['characters_counter']; + + if(empty($segment_status)){ + $segment_status = 'draft'; + } + + /** + * Update 2015/08/11, roberto@translated.net + * getWarning needs the segment status too because of a bug: + * sometimes the client calls getWarning and sends an empty trg_content + * because the suggestion has not been loaded yet. + * This happens only if segment is in status NEW + */ + if ( $segment_status == 'new' and empty( $trg_content )) { + return $this->response->json([ + 'data' => [], + 'errors' => [] + ]); + } + + $chunk = $this->getChunk($id_job, $password); + $featureSet = $this->getFeatureSet(); + $Filter = MateCatFilter::getInstance( $featureSet, $chunk->source, $chunk->target, [] ); + + $src_content = $Filter->fromLayer0ToLayer2( $src_content ); + $trg_content = $Filter->fromLayer0ToLayer2( $trg_content ); + + $QA = new QA( $src_content, $trg_content ); + $QA->setFeatureSet( $featureSet ); + $QA->setChunk( $chunk ); + $QA->setIdSegment( $id ); + $QA->setSourceSegLang( $chunk->source ); + $QA->setTargetSegLang( $chunk->target ); + + if(isset($characters_counter )){ + $QA->setCharactersCount($characters_counter); + } + + $QA->performConsistencyCheck(); + + $result = array_merge( + [ + 'data' => [], + 'errors' => [] + ], + $this->invokeLocalWarningsOnFeatures($chunk, $src_content, $trg_content), + ( new QALocalWarning( $QA, $id ) )->render() + ); + + return $this->response->json($result); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheLocalRequest(): array + { + $id = filter_var( $this->request->param( 'id' ), FILTER_SANITIZE_NUMBER_INT ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $src_content = filter_var( $this->request->param( 'src_content' ), FILTER_UNSAFE_RAW ); + $trg_content = filter_var( $this->request->param( 'trg_content' ), FILTER_UNSAFE_RAW ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $token = filter_var( $this->request->param( 'token' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $logs = filter_var( $this->request->param( 'logs' ), FILTER_UNSAFE_RAW ); + $segment_status = filter_var( $this->request->param( 'segment_status' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $characters_counter = filter_var( $this->request->param( 'characters_counter' ), FILTER_SANITIZE_NUMBER_INT ); + + if ( empty( $id_job ) ) { + throw new \InvalidArgumentException("Empty id job", -1); + } + + if ( empty( $password ) ) { + throw new \InvalidArgumentException("Empty job password", -2); + } + + return [ + 'id' => $id, + 'id_job' => $id_job, + 'src_content' => $src_content, + 'trg_content' => $trg_content, + 'password' => $password, + 'token' => $token, + 'logs' => $logs, + 'segment_status' => $segment_status, + 'characters_counter' => $characters_counter, + ]; + } + + /** + * @param $id_job + * @param $password + * @return \Chunks_ChunkStruct|null + * @throws Exception + */ + private function getChunk($id_job, $password): ?Chunks_ChunkStruct + { + $chunk = Chunks_ChunkDao::getByIdAndPassword($id_job, $password); + $project = $chunk->getProject(); + $this->featureSet->loadForProject( $project ); + + return $chunk; + } + + /** + * @param Chunks_ChunkStruct $chunk + * @param $src_content + * @param $trg_content + * @return array + * @throws Exception + */ + private function invokeLocalWarningsOnFeatures(Chunks_ChunkStruct $chunk, $src_content, $trg_content): array + { + $data = []; + $data = $this->featureSet->filter( 'filterSegmentWarnings', $data, [ + 'src_content' => $src_content, + 'trg_content' => $trg_content, + 'project' => $chunk->getProject(), + 'chunk' => $chunk + ] ); + + return [ + 'data' => $data + ]; + } +} diff --git a/lib/Controller/API/App/LoadTMXController.php b/lib/Controller/API/App/LoadTMXController.php new file mode 100644 index 0000000000..aa3cfd719f --- /dev/null +++ b/lib/Controller/API/App/LoadTMXController.php @@ -0,0 +1,137 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function newTM(): Response + { + try { + $request = $this->validateTheRequest(); + $TMService = new TMSService(); + $file = $TMService->uploadFile(); + + $uuids = []; + + foreach ( $file as $fileInfo ) { + + if ( AbstractFilesStorage::pathinfo_fix( strtolower( $fileInfo->name ), PATHINFO_EXTENSION ) !== 'tmx' ) { + throw new Exception( "Please upload a TMX.", -8 ); + } + + $file = new TMSFile( + $fileInfo->file_path, + $request['tm_key'], + $fileInfo->name + ); + + $TMService->addTmxInMyMemory( $file ); + $uuids[] = [ "uuid" => $file->getUuid(), "name" => $file->getName() ]; + + $this->featureSet->run( 'postPushTMX', $file, $this->user ); + + /* + * We update the KeyRing only if this is NOT the Default MyMemory Key + * + * If it is NOT the default the key belongs to the user, so it's correct to update the user keyring. + */ + if ( $request['tm_key'] != INIT::$DEFAULT_TM_KEY ) { + + /* + * Update a memory key with the name of th TMX if the key name is empty + */ + $mkDao = new TmKeyManagement_MemoryKeyDao( Database::obtain() ); + $searchMemoryKey = new TmKeyManagement_MemoryKeyStruct(); + $key = new TmKeyManagement_TmKeyStruct(); + $key->key = $data['tm_key']; + + $searchMemoryKey->uid = $this->user->uid; + $searchMemoryKey->tm_key = $key; + $userMemoryKey = $mkDao->read( $searchMemoryKey ); + + if ( empty( $userMemoryKey[ 0 ]->tm_key->name ) && !empty( $userMemoryKey ) ) { + $userMemoryKey[ 0 ]->tm_key->name = $fileInfo->name; + $mkDao->atomicUpdate( $userMemoryKey[ 0 ] ); + } + } + } + + return $this->response->json([ + 'errors' => [], + 'data' => [ + 'uuids' => $uuids + ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function uploadStatus(): Response + { + try { + $request = $this->validateTheRequest(); + $TMService = new TMSService(); + $status = $TMService->tmxUploadStatus( $request['uuid'] ); + + return $this->response->json([ + 'errors' => [], + 'data' => $status[ 'data' ], + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $name = filter_var( $this->request->param( 'name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $tm_key = filter_var( $this->request->param( 'tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + $uuid = filter_var( $this->request->param( 'uuid' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW ] ); + + if ( empty( $tm_key ) ) { + + if ( empty( INIT::$DEFAULT_TM_KEY ) ) { + throw new InvalidArgumentException("Please specify a TM key.", -2); + } + + /* + * Added the default Key. + * This means if no private key are provided the TMX will be loaded in the default MyMemory key + */ + $tm_key = INIT::$DEFAULT_TM_KEY; + + } + + return [ + 'name' => $name, + 'tm_key' => $tm_key, + 'uuid' => $uuid, + ]; + } +} diff --git a/lib/Controller/API/App/OutsourceConfirmationController.php b/lib/Controller/API/App/OutsourceConfirmationController.php index 9addd3051d..13fb30cc8d 100644 --- a/lib/Controller/API/App/OutsourceConfirmationController.php +++ b/lib/Controller/API/App/OutsourceConfirmationController.php @@ -20,11 +20,11 @@ class OutsourceConfirmationController extends AbstractStatefulKleinController { public function confirm() { - $params = filter_var_array( $this->request->params(), array( - 'id_job' => FILTER_SANITIZE_STRING, - 'password' => FILTER_SANITIZE_STRING, - 'payload' => FILTER_SANITIZE_STRING, - ) ); + $params = filter_var_array( $this->request->params(), [ + 'id_job' => FILTER_SANITIZE_STRING, + 'password' => FILTER_SANITIZE_STRING, + 'payload' => FILTER_SANITIZE_STRING, + ] ); $payload = \SimpleJWT::getValidPayload( $params[ 'payload' ] ); @@ -50,8 +50,11 @@ public function confirm() { $confirmationArray = $confirmationStruct->toArray(); unset( $confirmationArray['id'] ); - $this->response->json( [ 'confirm' => $confirmationArray ] ); + $this->response->json( [ + 'errors' => [], + 'confirm' => $confirmationArray + ] ); } } \ No newline at end of file diff --git a/lib/Controller/API/App/OutsourceToController.php b/lib/Controller/API/App/OutsourceToController.php new file mode 100644 index 0000000000..74281c8cf6 --- /dev/null +++ b/lib/Controller/API/App/OutsourceToController.php @@ -0,0 +1,136 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function outsource(): Response + { + try { + $request = $this->validateTheRequest(); + $pid = $request['pid']; + $ppassword = $request['ppassword']; + $currency = $request['currency']; + $timezone = $request['timezone']; + $fixedDelivery = $request['fixedDelivery']; + $typeOfService = $request['typeOfService']; + $jobList = $request['jobList']; + + $outsourceTo = new OutsourceTo_Translated(); + $outsourceTo->setPid( $pid ) + ->setPpassword( $ppassword ) + ->setCurrency( $currency ) + ->setTimezone( $timezone ) + ->setJobList( $jobList ) + ->setFixedDelivery( $fixedDelivery ) + ->setTypeOfService( $typeOfService ) + ->performQuote(); + + /* + * Example: + * + * $client_output = array ( + * '5901-6decb661a182' => + * array ( + * 'id' => '5901-6decb661a182', + * 'quantity' => '1', + * 'name' => 'MATECAT_5901-6decb661a182', + * 'quote_pid' => '11180933', + * 'source' => 'it-IT', + * 'target' => 'en-GB', + * 'price' => '12.00', + * 'words' => '120', + * 'show_info' => '0', + * 'delivery_date' => '2014-04-29T15:00:00Z', + * ), + * ); + */ + $client_output = $outsourceTo->getQuotesResult(); + + $this->response->json( [ + 'errors' => [], + 'data' => array_values( $client_output ), + 'return_url' => [ + 'url_ok' => $outsourceTo->getOutsourceLoginUrlOk(), + 'url_ko' => $outsourceTo->getOutsourceLoginUrlKo(), + 'confirm_urls' => $outsourceTo->getOutsourceConfirm(), + ] + ] ); + + } catch (Exception $exception) { + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $pid = filter_var( $this->request->param( 'pid' ), FILTER_SANITIZE_STRING ); + $ppassword = filter_var( $this->request->param( 'ppassword' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $currency = filter_var( $this->request->param( 'currency' ), FILTER_SANITIZE_STRING ); + $timezone = filter_var( $this->request->param( 'timezone' ), FILTER_SANITIZE_STRING ); + $fixedDelivery = filter_var( $this->request->param( 'fixedDelivery' ), FILTER_SANITIZE_NUMBER_INT ); + $typeOfService = filter_var( $this->request->param( 'typeOfService' ), FILTER_SANITIZE_STRING ); + $jobList = filter_var( $this->request->param( 'jobs' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY | FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if( empty( $pid ) ){ + throw new InvalidArgumentException("No id project provided", -1); + } + + if( empty( $ppassword ) ){ + throw new InvalidArgumentException("No project Password Provided", -2); + } + + /** + * The Job List form + * + *
+         * Ex:
+         *   array(
+         *      0 => array(
+         *          'id' => 5901,
+         *          'jpassword' => '6decb661a182',
+         *      ),
+         *   );
+         * 
+ */ + if( empty( $jobList ) ){ + throw new InvalidArgumentException("No job list Provided", -3); + } + + if ( empty( $currency ) ) { + $currency = @$_COOKIE[ "matecat_currency" ]; + } + + if ( empty( $timezone ) and $timezone !== "0" ) { + $timezone = @$_COOKIE[ "matecat_timezone" ]; + } + + if ( !in_array( $typeOfService, ["premium" , "professional"] ) ) { + $typeOfService = "professional"; + } + + return [ + 'pid' => $pid, + 'ppassword' => $ppassword, + 'currency' => $currency, + 'timezone' => $timezone, + 'fixedDelivery' => $fixedDelivery, + 'typeOfService' => $typeOfService, + 'jobList' => $jobList, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/SetChunkCompletedController.php b/lib/Controller/API/App/SetChunkCompletedController.php new file mode 100644 index 0000000000..28b83333bc --- /dev/null +++ b/lib/Controller/API/App/SetChunkCompletedController.php @@ -0,0 +1,97 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function complete(): Response + { + try { + $request = $this->validateTheRequest(); + + $struct = new CompletionEventStruct( [ + 'uid' => $this->user->getUid(), + 'remote_ip_address' => Utils::getRealIpAddr(), + 'source' => Chunks_ChunkCompletionEventStruct::SOURCE_USER, + 'is_review' => $this->isRevision($request['id_job'], $request['password']) + ] ); + + $model = new EventModel( $request['job'], $struct ); + $model->save(); + + return $this->response->json([ + 'data' => [ + 'event' => [ + 'id' => (int)$model->getChunkCompletionEventId() + ] + ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array|\Klein\Response + * @throws \ReflectionException + */ + private function validateTheRequest(): array + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $received_password = filter_var( $this->request->param( 'current_password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException("Missing id job", -1); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException( "Missing id password", -2); + } + + $job = Jobs_JobDao::getByIdAndPassword( $id_job, $password, 60 * 60 * 24 ); + + if ( empty( $job ) ) { + throw new InvalidArgumentException( "wrong password", -10); + } + + return [ + 'id_job' => $id_job, + 'password' => $password, + 'received_password' => $received_password, + 'job' => $job, + ]; + } +} \ No newline at end of file diff --git a/lib/Controller/API/App/SetCurrentSegmentController.php b/lib/Controller/API/App/SetCurrentSegmentController.php new file mode 100644 index 0000000000..80445683ed --- /dev/null +++ b/lib/Controller/API/App/SetCurrentSegmentController.php @@ -0,0 +1,140 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function set() + { + try { + $request = $this->validateTheRequest(); + $revision_number = $request['revision_number']; + $id_segment = $request['id_segment']; + $id_job = $request['id_job']; + $password = $request['password']; + $split_num = $request['split_num']; + + //get Job Info, we need only a row of jobs ( split ) + $job_data = Jobs_JobDao::getByIdAndPassword( $id_job, $password ); + + if ( empty( $job_data ) ) { + throw new \InvalidArgumentException("wrong password", -10); + } + + if ( empty( $id_segment ) ) { + throw new \InvalidArgumentException("missing segment id", -1); + } + + $segmentStruct = new TranslationsSplit_SplitStruct(); + $segmentStruct->id_segment = (int)$id_segment; + $segmentStruct->id_job = $id_job; + + $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); + $currSegmentInfo = $translationDao->read( $segmentStruct ); + + /** + * Split check control + */ + $isASplittedSegment = false; + $isLastSegmentChunk = true; + + if ( count( $currSegmentInfo ) > 0 ) { + + $isASplittedSegment = true; + $currSegmentInfo = array_shift( $currSegmentInfo ); + + //get the chunk number and check whether it is the last one or not + $isLastSegmentChunk = ( $split_num == count( $currSegmentInfo->source_chunk_lengths ) - 1 ); + + if ( !$isLastSegmentChunk ) { + $nextSegmentId = $id_segment . "-" . ( $split_num + 1 ); + } + } + + /** + * End Split check control + */ + if ( !$isASplittedSegment or $isLastSegmentChunk ) { + + $segmentList = Segments_SegmentDao::getNextSegment( $id_segment, $id_job, $password, $revision_number ); + + if ( !$revision_number ) { + $nextSegmentId = CatUtils::fetchStatus( $id_segment, $segmentList ); + } else { + $nextSegmentId = CatUtils::fetchStatus( $id_segment, $segmentList, Constants_TranslationStatus::STATUS_TRANSLATED ); + if ( !$nextSegmentId ) { + $nextSegmentId = CatUtils::fetchStatus( $id_segment, $segmentList, Constants_TranslationStatus::STATUS_APPROVED ); + } + } + } + + return $this->response->json([ + 'code' => 1, + 'data' => [], + 'nextSegmentId' => $nextSegmentId ?? null, + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $revision_number = filter_var( $this->request->param( 'revision_number' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( empty( $id_job) ) { + throw new InvalidArgumentException("No id job provided", -1); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("No job password provided", -2); + } + + if ( empty( $id_segment) ) { + throw new InvalidArgumentException("No id segment provided", -3); + } + + $segment = explode( "-", $id_segment ); + $id_segment = $segment[0]; + $split_num = $segment[1]; + + return [ + 'revision_number' => $revision_number, + 'id_segment' => $id_segment, + 'id_job' => $id_job, + 'password' => $password, + 'split_num' => $split_num, + ]; + } +} diff --git a/lib/Controller/API/App/SetTranslationController.php b/lib/Controller/API/App/SetTranslationController.php new file mode 100644 index 0000000000..be2fe53a04 --- /dev/null +++ b/lib/Controller/API/App/SetTranslationController.php @@ -0,0 +1,888 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function translate(): Response + { + try { + $this->data = $this->validateTheRequest(); + $this->checkData(); + $this->initVersionHandler(); + $this->getContexts(); + + //check tag mismatch + //get original source segment, first + $dao = new \Segments_SegmentDao( \Database::obtain() ); + $this->data['segment'] = $dao->getById( $this->data['id_segment'] ); + + $segment = $this->filter->fromLayer0ToLayer2( $this->data[ 'segment' ][ 'segment' ] ); + $translation = $this->filter->fromLayer0ToLayer2( $this->data[ 'translation' ] ); + + $check = new QA( $segment, $translation ); + $check->setChunk( $this->data['chunk'] ); + $check->setFeatureSet( $this->featureSet ); + $check->setSourceSegLang( $this->data['chunk']->source ); + $check->setTargetSegLang( $this->data['chunk']->target ); + $check->setIdSegment( $this->data['id_segment'] ); + + if ( isset( $this->data[ 'characters_counter' ] ) ) { + $check->setCharactersCount( $this->data[ 'characters_counter' ] ); + } + + $check->performConsistencyCheck(); + + if ( $check->thereAreWarnings() ) { + $err_json = $check->getWarningsJSON(); + $translation = $this->filter->fromLayer2ToLayer0( $this->data[ 'translation' ] ); + } else { + $err_json = ''; + $targetNormalized = $check->getTrgNormalized(); + $translation = $this->filter->fromLayer2ToLayer0( $targetNormalized ); + } + + //PATCH TO FIX BOM INSERTIONS + $translation = Utils::stripBOM( $translation ); + + /* + * begin stats counter + * + * It works good with default InnoDB Isolation level + * + * REPEATABLE-READ offering a row level lock for this id_segment + * + */ + $db = Database::obtain(); + $db->begin(); + + $old_translation = $this->getOldTranslation(); + + $old_suggestion_array = json_decode( $this->data['suggestion_array'] ); + $old_suggestion = ( $this->data['chosen_suggestion_index'] !== null ? @$old_suggestion_array[ $this->data['chosen_suggestion_index'] - 1 ] : null ); + + $new_translation = new Translations_SegmentTranslationStruct(); + $new_translation->id_segment = $this->data['id_segment']; + $new_translation->id_job = $this->data['id_job']; + $new_translation->status = $this->data['status']; + $new_translation->segment_hash = $this->data['segment']->segment_hash; + $new_translation->translation = $translation; + $new_translation->serialized_errors_list = $err_json; + $new_translation->suggestions_array = ( $this->data['chosen_suggestion_index'] !== null ? $this->data['suggestion_array'] : $old_translation->suggestions_array ); + $new_translation->suggestion_position = ( $this->data['chosen_suggestion_index'] !== null ? $this->data['chosen_suggestion_index'] : $old_translation->suggestion_position ); + $new_translation->warning = $check->thereAreWarnings(); + $new_translation->translation_date = date( "Y-m-d H:i:s" ); + $new_translation->suggestion = ( ( !empty( $old_suggestion ) ) ? $old_suggestion->translation : $old_translation->suggestion ); + $new_translation->suggestion_source = $old_translation->suggestion_source; + $new_translation->suggestion_match = $old_translation->suggestion_match; + + // update suggestion + if ( $this->canUpdateSuggestion( $new_translation, $old_translation, $old_suggestion ) ) { + $new_translation->suggestion = $old_suggestion->translation; + + // update suggestion match + if ( $old_suggestion->match == "MT" ) { + // case 1. is MT + $new_translation->suggestion_match = 85; + $new_translation->suggestion_source = 'MT'; + } elseif ( $old_suggestion->match == 'NO_MATCH' ) { + // case 2. no match + $new_translation->suggestion_source = 'NO_MATCH'; + } else { + // case 3. otherwise is TM + $new_translation->suggestion_match = $old_suggestion->match; + $new_translation->suggestion_source = 'TM'; + } + } + + // time_to_edit should be increased only if the translation was changed + $new_translation->time_to_edit = 0; + if ( false === Utils::stringsAreEqual( $new_translation->translation, $old_translation->translation ) ) { + $new_translation->time_to_edit = $this->data['time_to_edit']; + } + + /** + * Update Time to Edit and + * + * Evaluate new Avg post-editing effort for the job: + * - get old translation + * - get suggestion + * - evaluate $_seg_oldPEE and normalize it on the number of words for this segment + * + * - get new translation + * - evaluate $_seg_newPEE and normalize it on the number of words for this segment + * + * - get $_jobTotalPEE + * - evaluate $_jobTotalPEE - $_seg_oldPEE + $_seg_newPEE and save it into the job's row + */ + $this->updateJobPEE( $old_translation->toArray(), $new_translation->toArray() ); + + // if saveVersionAndIncrement() return true it means that it was persisted a new version of the parent segment + if ( $this->VersionsHandler !== null ) { + $this->VersionsHandler->saveVersionAndIncrement( $new_translation, $old_translation ); + } + + /** + * when the status of the translation changes, the auto propagation flag + * must be removed + */ + if ( $new_translation->translation != $old_translation->translation || + $this->data['status'] == Constants_TranslationStatus::STATUS_TRANSLATED || + $this->data['status'] == Constants_TranslationStatus::STATUS_APPROVED || + $this->data['status'] == Constants_TranslationStatus::STATUS_APPROVED2 + ) { + $new_translation->autopropagated_from = 'NULL'; + } + + /** + * Translation is inserted here. + */ + try { + CatUtils::addSegmentTranslation( $new_translation, $this->isRevision($this->data['id_job'], $this->data['password']) ); + } catch ( ControllerReturnException $e ) { + $db->rollback(); + throw new RuntimeException($e->getMessage()); + } + + /** + * @see ProjectCompletion + */ + $this->featureSet->run( 'postAddSegmentTranslation', [ + 'chunk' => $this->data['chunk'], + 'is_review' => $this->isRevision($this->data['id_job'], $this->data['password']), + 'logged_user' => $this->user + ] ); + + $propagationTotal = [ + 'totals' => [], + 'propagated_ids' => [], + 'segments_for_propagation' => [] + ]; + + if ( $this->data['propagate'] && in_array( $this->data['status'], [ + Constants_TranslationStatus::STATUS_TRANSLATED, + Constants_TranslationStatus::STATUS_APPROVED, + Constants_TranslationStatus::STATUS_APPROVED2, + Constants_TranslationStatus::STATUS_REJECTED + ] ) + ) { + //propagate translations + $TPropagation = new Translations_SegmentTranslationStruct(); + $TPropagation[ 'status' ] = $this->data['status']; + $TPropagation[ 'id_job' ] = $this->data['id_job']; + $TPropagation[ 'translation' ] = $translation; + $TPropagation[ 'autopropagated_from' ] = $this->data['id_segment']; + $TPropagation[ 'serialized_errors_list' ] = $err_json; + $TPropagation[ 'warning' ] = $check->thereAreWarnings(); + $TPropagation[ 'segment_hash' ] = $old_translation[ 'segment_hash' ]; + $TPropagation[ 'translation_date' ] = Utils::mysqlTimestamp( time() ); + $TPropagation[ 'match_type' ] = $old_translation[ 'match_type' ]; + $TPropagation[ 'locked' ] = $old_translation[ 'locked' ]; + + try { + + if ( $this->VersionsHandler !== null ) { + $propagationTotal = Translations_SegmentTranslationDao::propagateTranslation( + $TPropagation, + $this->data['chunk'], + $this->data['id_segment'], + $this->data['project'], + $this->VersionsHandler + ); + } + + } catch ( Exception $e ) { + $msg = $e->getMessage() . "\n\n" . $e->getTraceAsString(); + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + $db->rollback(); + throw new RuntimeException( $e->getMessage(), $e->getCode() ); + } + } + + if ( $this->isSplittedSegment() ) { + /* put the split inside the transaction if they are present */ + $translationStruct = TranslationsSplit_SplitStruct::getStruct(); + $translationStruct->id_segment = $this->data['id_segment']; + $translationStruct->id_job = $this->data['id_job']; + + $translationStruct->target_chunk_lengths = [ + 'len' => $this->data['split_chunk_lengths'], + 'statuses' => $this->data['split_statuses'] + ]; + + $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); + $translationDao->atomicUpdate( $translationStruct ); + } + + //COMMIT THE TRANSACTION + try { + + /* + * Hooked by TranslationVersions which manage translation versions + * + * This is also the init handler of all R1/R2 handling and Qr score calculation by + * by TranslationEventsHandler and BatchReviewProcessor + */ + if ( $this->VersionsHandler !== null ) { + $this->VersionsHandler->storeTranslationEvent( [ + 'translation' => $new_translation, + 'old_translation' => $old_translation, + 'propagation' => $propagationTotal, + 'chunk' => $this->data['chunk'], + 'segment' => $this->data['segment'], + 'user' => $this->user, + 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->data['revisionNumber'] ), + 'features' => $this->featureSet, + 'project' => $this->data['project'] + ] ); + } + + $db->commit(); + + } catch ( Exception $e ) { + $this->log( "Lock: Transaction Aborted. " . $e->getMessage() ); + $db->rollback(); + + throw new RuntimeException($e->getMessage()); + } + + $newTotals = WordCountStruct::loadFromJob( $this->data['chunk'] ); + + $job_stats = CatUtils::getFastStatsForJob( $newTotals ); + $job_stats[ 'analysis_complete' ] = ( + $this->data['project'][ 'status_analysis' ] == Constants_ProjectStatus::STATUS_DONE or + $this->data['project'][ 'status_analysis' ] == Constants_ProjectStatus::STATUS_NOT_TO_ANALYZE + ); + + $file_stats = []; + $result = []; + + $result[ 'stats' ] = $job_stats; + $result[ 'file_stats' ] = $file_stats; + $result[ 'code' ] = 1; + $result[ 'data' ] = "OK"; + $result[ 'version' ] = date_create( $new_translation[ 'translation_date' ] )->getTimestamp(); + $result[ 'translation' ] = $this->getTranslationObject( $new_translation ); + + /* FIXME: added for code compatibility with front-end. Remove. */ + $_warn = $check->getWarnings(); + $warning = $_warn[ 0 ]; + /* */ + + $result[ 'warning' ][ 'cod' ] = $warning->outcome; + if ( $warning->outcome > 0 ) { + $result[ 'warning' ][ 'id' ] = $this->data['id_segment']; + } else { + $result[ 'warning' ][ 'id' ] = 0; + } + + try { + $this->featureSet->run( 'setTranslationCommitted', [ + 'translation' => $new_translation, + 'old_translation' => $old_translation, + 'propagated_ids' => isset( $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] ) ? $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] : null, + 'chunk' => $this->data['chunk'], + 'segment' => $this->data['segment'], + 'user' => $this->user, + 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->data['revisionNumber'] ) + ] ); + + } catch ( Exception $e ) { + $this->log( "Exception in setTranslationCommitted callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); + throw new RuntimeException($e->getMessage()); + } + + try { + $result = $this->featureSet->filter( 'filterSetTranslationResult', $result, [ + 'translation' => $new_translation, + 'old_translation' => $old_translation, + 'propagated_ids' => isset( $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] ) ? $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] : null, + 'chunk' => $this->data['chunk'], + 'segment' => $this->data['segment'] + ] ); + } catch ( Exception $e ) { + $this->log( "Exception in filterSetTranslationResult callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); + throw new RuntimeException($e->getMessage()); + } + + //EVERY time an user changes a row in his job when the job is completed, + // a query to do the update is executed... + // Avoid this by setting a key on redis with a reasonable TTL + $redisHandler = new RedisHandler(); + $job_status = $redisHandler->getConnection()->get( 'job_completeness:' . $this->id_job ); + if ( + ( + ( + $job_stats[ Projects_MetadataDao::WORD_COUNT_RAW ][ 'draft' ] + + $job_stats[ Projects_MetadataDao::WORD_COUNT_RAW ][ 'new' ] == 0 + ) + and empty( $job_status ) + ) + ) { + $redisHandler->getConnection()->setex( 'job_completeness:' . $this->id_job, 60 * 60 * 24 * 15, true ); //15 days + + try { + $update_completed = Jobs_JobDao::setJobComplete( $this->data['chunk'] ); + } catch ( Exception $ignore ) { + } + + if ( empty( $update_completed ) ) { + $msg = "\n\n Error setJobCompleteness \n\n " . var_export( $_POST, true ); + $redisHandler->getConnection()->del( 'job_completeness:' . $this->id_job ); + $this->log( $msg ); + Utils::sendErrMailReport( $msg ); + } + } + + $result[ 'propagation' ] = $propagationTotal; + $this->evalSetContribution( $new_translation, $old_translation ); + + return $this->response->json($result); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest() + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $received_password = filter_var( $this->request->param( 'current_password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $propagate = filter_var( $this->request->param( 'propagate' ), FILTER_NULL_ON_FAILURE ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $time_to_edit = filter_var( $this->request->param( 'time_to_edit' ), FILTER_SANITIZE_NUMBER_INT ); + $id_translator = filter_var( $this->request->param( 'id_translator' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $translation = filter_var( $this->request->param( 'translation' ), FILTER_UNSAFE_RAW ); + $segment = filter_var( $this->request->param( 'segment' ), FILTER_UNSAFE_RAW ); + $version = filter_var( $this->request->param( 'version' ), FILTER_SANITIZE_NUMBER_INT ); + $chosen_suggestion_index = filter_var( $this->request->param( 'chosen_suggestion_index' ), FILTER_SANITIZE_NUMBER_INT ); + $suggestion_array = filter_var( $this->request->param( 'suggestion_array' ), FILTER_UNSAFE_RAW ); + $status = filter_var( $this->request->param( 'status' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $splitStatuses = filter_var( $this->request->param( 'splitStatuses' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $context_before = filter_var( $this->request->param( 'context_before' ), FILTER_UNSAFE_RAW ); + $context_after = filter_var( $this->request->param( 'context_after' ), FILTER_UNSAFE_RAW ); + $id_before = filter_var( $this->request->param( 'id_before' ), FILTER_SANITIZE_NUMBER_INT ); + $id_after = filter_var( $this->request->param( 'id_after' ), FILTER_SANITIZE_NUMBER_INT ); + $revisionNumber = filter_var( $this->request->param( 'revision_number' ), FILTER_SANITIZE_NUMBER_INT ); + $guess_tag_used = filter_var( $this->request->param( 'guess_tag_used' ), FILTER_VALIDATE_BOOLEAN ); + $characters_counter = filter_var( $this->request->param( 'characters_counter' ), FILTER_SANITIZE_NUMBER_INT ); + + /* + * set by the client, mandatory + * check propagation flag, if it is null the client not sent it, leave default true, otherwise set the value + */ + $propagate = (!is_null($propagate)) ? $propagate : null; /* do nothing */ + $client_target_version = ( empty( $version ) ? '0' : $version ); + $status = strtoupper( $status ); + $split_statuses = explode( ",", strtoupper( $splitStatuses ) ); //strtoupper transforms null to "" + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException("Missing id job", -2); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("Missing password", -3); + } + + //get Job Info, we need only a row of jobs ( split ) + $chunk = Chunks_ChunkDao::getByIdAndPassword( (int)$id_job, $password ); + + if ( empty( $chunk ) ) { + throw new NotFoundException("Wrong password", -3); + } + + //add check for job status archived. + if ( strtolower( $chunk[ 'status' ] ) == Constants_JobStatus::STATUS_ARCHIVED ) { + throw new NotFoundException("Job archived", -3); + } + + $data = [ + 'id_job' => $id_job , + 'password' => $password , + 'received_password' => $received_password , + 'id_segment' => $id_segment , + 'time_to_edit' => $time_to_edit , + 'id_translator' => $id_translator , + 'translation' => $translation , + 'segment' => $segment , + 'version' => $version , + 'chosen_suggestion_index' => $chosen_suggestion_index , + 'suggestion_array' => $suggestion_array , + 'splitStatuses' => $splitStatuses , + 'context_before' => $context_before , + 'context_after' => $context_after , + 'id_before' => $id_before , + 'id_after' => $id_after , + 'revisionNumber' => (int)$revisionNumber , + 'guess_tag_used' => $guess_tag_used , + 'characters_counter' => $characters_counter , + 'propagate' => $propagate , + 'client_target_version' => $client_target_version , + 'status' => $status , + 'split_statuses' => $split_statuses , + 'chunk' => $chunk , + ]; + + $this->log( $data ); + + return $data; + } + + /** + * @return bool + */ + private function isSplittedSegment(): bool + { + return !empty( $this->data['split_statuses'][ 0 ] ) && !empty( $this->data['split_num'] ); + } + + /** + * setStatusForSplittedSegment + * + * If splitted segments have different statuses, we reset status + * to draft. + */ + private function setStatusForSplittedSegment(): void + { + if ( count( array_unique( $this->data['split_statuses'] ) ) == 1 ) { + // IF ALL translation chunks are in the same status, + // we take the status for the entire segment + $this->data['status'] = $this->data['split_statuses'][ 0 ]; + } else { + $this->data['status'] = Constants_TranslationStatus::STATUS_DRAFT; + } + } + + /** + * @throws Exception + */ + protected function checkData(): void + { + $this->data['project'] = $this->data['chunk']->getProject(); + + $featureSet = $this->getFeatureSet(); + $featureSet->loadForProject( $this->data['project'] ); + + /** @var MateCatFilter $filter */ + $this->filter = MateCatFilter::getInstance( $featureSet, $this->data['chunk']->source, $this->data['chunk']->target, Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $this->data['id_segment'] ) ); + + [ $__translation, $this->data['split_chunk_lengths'] ] = CatUtils::parseSegmentSplit( $this->data[ 'translation' ], '', $this->filter ); + + if ( is_null( $__translation ) || $__translation === '' ) { + $this->log( "Empty Translation \n\n" . var_export( $_POST, true ) ); + throw new RuntimeException( "Empty Translation \n\n" . var_export( $_POST, true ), 0 ); + } + + $explodeIdSegment = explode( "-", $this->data['id_segment']); + $this->data['id_segment'] = $explodeIdSegment[0]; + $this->data['split_num'] = $explodeIdSegment[1]; + + if ( empty( $this->data['id_segment'] ) ) { + throw new Exception("missing id_segment", -1); + } + + if ( $this->isSplittedSegment() ) { + $this->setStatusForSplittedSegment(); + } + + $this->checkStatus( $this->data['status'] ); + } + + /** + * Throws exception if status is not valid. + * + * @param $status + * + * @throws Exception + */ + private function checkStatus( $status ): void + { + switch ( $status ) { + case Constants_TranslationStatus::STATUS_TRANSLATED: + case Constants_TranslationStatus::STATUS_APPROVED: + case Constants_TranslationStatus::STATUS_APPROVED2: + case Constants_TranslationStatus::STATUS_REJECTED: + case Constants_TranslationStatus::STATUS_DRAFT: + case Constants_TranslationStatus::STATUS_NEW: + case Constants_TranslationStatus::STATUS_FIXED: + case Constants_TranslationStatus::STATUS_REBUTTED: + break; + + default: + $msg = "Error Hack Status \n\n " . var_export( $_POST, true ); + throw new Exception( $msg, -1 ); + break; + } + } + + /** + * @throws Exception + */ + private function getContexts(): void + { + //Get contexts + $segmentsList = ( new Segments_SegmentDao )->setCacheTTL( 60 * 60 * 24 )->getContextAndSegmentByIDs( + [ + 'id_before' => $this->data['id_before'], + 'id_segment' => $this->data['id_segment'], + 'id_after' => $this->data['id_after'] + ] + ); + + $this->featureSet->filter( 'rewriteContributionContexts', $segmentsList, $this->data ); + + if ( isset( $segmentsList->id_before->segment ) ) { + $this->data['context_before'] = $this->filter->fromLayer0ToLayer1( $segmentsList->id_before->segment ); + } + + if ( isset( $segmentsList->id_after->segment ) ) { + $this->data['context_after'] = $this->filter->fromLayer0ToLayer1( $segmentsList->id_after->segment ); + } + } + + /** + * init VersionHandler + */ + private function initVersionHandler(): void + { + // fix null pointer error + if ( + $this->data['chunk'] !== null and + $this->data['id_segment'] !== null and + $this->user !== null and + $this->data['project'] !== null + ) { + $this->VersionsHandler = TranslationVersions::getVersionHandlerNewInstance( $this->data['chunk'], $this->data['id_segment'], $this->user, $this->data['project'] ); + } + } + + /** + * @return Translations_SegmentTranslationStruct + * @throws Exception + */ + private function getOldTranslation(): ?Translations_SegmentTranslationStruct + { + $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( $this->data['id_segment'], $this->data['id_job'] ); + + if ( empty( $old_translation ) ) { + $old_translation = new Translations_SegmentTranslationStruct(); + } // $old_translation if `false` sometimes + + + // If volume analysis is not enabled and no translation rows exists, create the row + if ( !INIT::$VOLUME_ANALYSIS_ENABLED && empty( $old_translation[ 'status' ] ) ) { + $translation = new Translations_SegmentTranslationStruct(); + $translation->id_segment = (int)$this->data['id_segment']; + $translation->id_job = (int)$this->data['id_job']; + $translation->status = Constants_TranslationStatus::STATUS_NEW; + + $translation->segment_hash = $this->data['segment'][ 'segment_hash' ]; + $translation->translation = $this->data['segment'][ 'segment' ]; + $translation->standard_word_count = $this->data['segment'][ 'raw_word_count' ]; + + $translation->serialized_errors_list = ''; + $translation->suggestion_position = 0; + $translation->warning = false; + $translation->translation_date = date( "Y-m-d H:i:s" ); + + try { + CatUtils::addSegmentTranslation( $translation, $this->isRevision($this->data['id_job'], $this->data['password']) ); + } catch ( ControllerReturnException $e ) { + Database::obtain()->rollback(); + throw new RuntimeException($e->getMessage()); + } + + $old_translation = $translation; + } + + return $old_translation; + } + + /** + * Update suggestion only if: + * + * 1) the new state is one of these: + * - NEW + * - DRAFT + * - TRANSLATED + * + * 2) the old state is one of these: + * - NEW + * - DRAFT + * + * @param Translations_SegmentTranslationStruct $new_translation + * @param Translations_SegmentTranslationStruct $old_translation + * @param null $old_suggestion + * + * @return bool + */ + private function canUpdateSuggestion( + Translations_SegmentTranslationStruct $new_translation, + Translations_SegmentTranslationStruct $old_translation, + $old_suggestion = null ): bool + { + if ( $old_suggestion === null ) { + return false; + } + + $allowedStatuses = [ + Constants_TranslationStatus::STATUS_NEW, + Constants_TranslationStatus::STATUS_DRAFT, + Constants_TranslationStatus::STATUS_TRANSLATED, + ]; + + if ( !in_array( $new_translation->status, $allowedStatuses ) ) { + return false; + } + + if ( !in_array( $old_translation->status, $allowedStatuses ) ) { + return false; + } + + if ( + !empty( $old_suggestion ) and + isset( $old_suggestion->translation ) and + isset( $old_suggestion->match ) and + isset( $old_suggestion->created_by ) + ) { + return true; + } + + return false; + } + + /** + * @param array $old_translation + * @param array $new_translation + */ + private function updateJobPEE( array $old_translation, array $new_translation ): void + { + //update total time to edit + $tte = $old_translation[ 'time_to_edit' ]; + if ( !$this->isRevision($this->data['id_job'], $this->data['password']) ) { + if ( !Utils::stringsAreEqual( $new_translation[ 'translation' ], $old_translation[ 'translation' ] ) ) { + $tte += $new_translation[ 'time_to_edit' ]; + } + } + + $segmentRawWordCount = $this->data['segment']->raw_word_count; + $editLogSegmentStruct = new EditLogSegmentStruct( + [ + 'suggestion' => $old_translation[ 'suggestion' ], + 'translation' => $old_translation[ 'translation' ], + 'raw_word_count' => $segmentRawWordCount, + 'time_to_edit' => $old_translation[ 'time_to_edit' ] + $new_translation[ 'time_to_edit' ] + ] + ); + + $oldSegmentStatus = clone $editLogSegmentStruct; + $oldSegmentStatus->time_to_edit = $old_translation[ 'time_to_edit' ]; + + $oldPEE = $editLogSegmentStruct->getPEE(); + $oldPee_weighted = $oldPEE * $segmentRawWordCount; + + $editLogSegmentStruct->translation = $new_translation[ 'translation' ]; + $editLogSegmentStruct->pe_effort_perc = null; + + $newPEE = $editLogSegmentStruct->getPEE(); + $newPee_weighted = $newPEE * $segmentRawWordCount; + + if ( $editLogSegmentStruct->isValidForEditLog() ) { + //if the segment was not valid for editlog, and now it is, then just add the weighted pee + if ( !$oldSegmentStatus->isValidForEditLog() ) { + $newTotalJobPee = ( $this->data['chunk'][ 'avg_post_editing_effort' ] + $newPee_weighted ); + } //otherwise, evaluate it normally + else { + $newTotalJobPee = ( $this->data['chunk'][ 'avg_post_editing_effort' ] - $oldPee_weighted + $newPee_weighted ); + } + + Jobs_JobDao::updateFields( + + [ 'avg_post_editing_effort' => $newTotalJobPee, 'total_time_to_edit' => $tte ], + [ + 'id' => $this->data['id_job'], + 'password' => $this->data['password'] + ] ); + + } //segment was valid but now it is no more valid + elseif ( $oldSegmentStatus->isValidForEditLog() ) { + $newTotalJobPee = ( $this->data['chunk'][ 'avg_post_editing_effort' ] - $oldPee_weighted ); + + Jobs_JobDao::updateFields( + [ 'avg_post_editing_effort' => $newTotalJobPee, 'total_time_to_edit' => $tte ], + [ + 'id' => $this->data['id_job'], + 'password' => $this->data['password'] + ] ); + } else { + Jobs_JobDao::updateFields( + [ 'total_time_to_edit' => $tte ], + [ + 'id' => $this->data['id_job'], + 'password' => $this->data['password'] + ] ); + } + } + + /** + * This method returns a representation of the saved translation which + * should be as much as possible compliant with the future API v2. + * + * @param $saved_translation + * + * @return array + * @throws Exception + */ + private function getTranslationObject( $saved_translation ): array + { + return [ + 'version_number' => @$saved_translation[ 'version_number' ], + 'sid' => $saved_translation[ 'id_segment' ], + 'translation' => $this->filter->fromLayer0ToLayer2( $saved_translation[ 'translation' ] ), + 'status' => $saved_translation[ 'status' ] + + ]; + } + + /** + * @param $_Translation + * @param $old_translation + * + * @throws Exception + */ + private function evalSetContribution( $_Translation, $old_translation ): void + { + if ( in_array( $this->data['status'], [ + Constants_TranslationStatus::STATUS_DRAFT, + Constants_TranslationStatus::STATUS_NEW + ] ) ) { + return; + } + + $skip_set_contribution = false; + $skip_set_contribution = $this->featureSet->filter( 'filter_skip_set_contribution', + $skip_set_contribution, $_Translation, $old_translation + ); + + if ( $skip_set_contribution ) { + return; + } + + $ownerUid = Jobs_JobDao::getOwnerUid( $this->id_job, $this->password ); + $filesParts = ( new FilesPartsDao() )->getBySegmentId( $this->data['id_segment'] ); + + /** + * Set the new contribution in queue + */ + $contributionStruct = new ContributionSetStruct(); + $contributionStruct->fromRevision = $this->isRevision($this->data['id_segment'], $this->data['password']); + $contributionStruct->id_file = ( $filesParts !== null ) ? $filesParts->id_file : null; + $contributionStruct->id_job = $this->data['id_job']; + $contributionStruct->job_password = $this->data['password']; + $contributionStruct->id_segment = $this->data['id_segment']; + $contributionStruct->segment = $this->filter->fromLayer0ToLayer1( $this->data['segment'][ 'segment' ] ); + $contributionStruct->translation = $this->filter->fromLayer0ToLayer1( $_Translation[ 'translation' ] ); + $contributionStruct->api_key = INIT::$MYMEMORY_API_KEY; + $contributionStruct->uid = ( $ownerUid !== null ) ? $ownerUid : 0;; + $contributionStruct->oldTranslationStatus = $old_translation[ 'status' ]; + $contributionStruct->oldSegment = $this->filter->fromLayer0ToLayer1( $this->data['segment'][ 'segment' ] ); // + $contributionStruct->oldTranslation = $this->filter->fromLayer0ToLayer1( $old_translation[ 'translation' ] ); + + /* + * This parameter is not used by the application, but we use it to for information integrity + * + * User choice for propagation. + * + * Propagate is false IF: + * - the segment has not repetitions + * - the segment has some one or more repetitions and the user choose to not propagate it + * - the segment is already autopropagated ( marked as autopropagated_from ) and it hasn't been changed + * + * Propagate is true ( vice versa ) IF: + * - the segment has one or more repetitions and it's status is NEW/DRAFT + * - the segment has one or more repetitions and the user choose to propagate it + * - the segment has one or more repetitions, it is not modified, it doesn't have translation conflicts and a change status is requested + */ + $contributionStruct->propagationRequest = $this->data['propagate']; + $contributionStruct->id_mt = $this->data['chunk']->id_mt_engine; + + $contributionStruct->context_after = $this->data['context_after']; + $contributionStruct->context_before = $this->data['context_before']; + + $this->featureSet->filter( + 'filterContributionStructOnSetTranslation', + $contributionStruct, + $this->data['project'], + $this->data['segment'] + ); + + //assert there is not an exception by following the flow + Set::contribution( $contributionStruct ); + + if ( $contributionStruct->id_mt > 1 ) { + $contributionStruct = $this->featureSet->filter( 'filterSetContributionMT', null, $contributionStruct, $this->data['project'] ); + Set::contributionMT( $contributionStruct ); + } + } +} diff --git a/lib/Controller/API/App/SplitJobController.php b/lib/Controller/API/App/SplitJobController.php new file mode 100644 index 0000000000..8e6c878adf --- /dev/null +++ b/lib/Controller/API/App/SplitJobController.php @@ -0,0 +1,236 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function merge(): Response + { + try { + $request = $this->validateTheRequest(); + $projectStructure = $this->getProjectStructure( + $request['project_id'], + $request['project_pass'], + $request['split_raw_words'] + ); + + $pStruct = $projectStructure['pStruct']; + $pManager = $projectStructure['pManager']; + $project = $projectStructure['project']; + + $jobStructs = $this->checkMergeAccess( $request['job_id'], $project->getJobs() ); + $pStruct[ 'job_to_merge' ] = $request['job_id']; + $pManager->mergeALL( $pStruct, $jobStructs ); + + return $this->response->json([ + "data" => $pStruct[ 'split_result' ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function check(): Response + { + try { + $request = $this->validateTheRequest(); + $projectStructure = $this->getProjectStructure( + $request['project_id'], + $request['project_pass'], + $request['split_raw_words'] + ); + + $pStruct = $projectStructure['pStruct']; + $pManager = $projectStructure['pManager']; + $project = $projectStructure['project']; + $count_type = $projectStructure['count_type']; + + $this->checkSplitAccess( $project, $request['job_id'], $request['job_pass'], $project->getJobs() ); + + $pStruct[ 'job_to_split' ] = $request['job_id']; + $pStruct[ 'job_to_split_pass' ] = $request['job_pass']; + + $pManager->getSplitData( $pStruct, $request['num_split'], $request['split_values'], $count_type ); + + return $this->response->json([ + "data" => $pStruct[ 'split_result' ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function apply(): Response + { + try { + $request = $this->validateTheRequest(); + $projectStructure = $this->getProjectStructure( + $request['project_id'], + $request['project_pass'], + $request['split_raw_words'] + ); + + $pStruct = $projectStructure['pStruct']; + $pManager = $projectStructure['pManager']; + $project = $projectStructure['project']; + $count_type = $projectStructure['count_type']; + + $this->checkSplitAccess( $project, $request['job_id'], $request['job_pass'], $project->getJobs() ); + + $pStruct[ 'job_to_split' ] = $request['job_id']; + $pStruct[ 'job_to_split_pass' ] = $request['job_pass']; + + $pManager->getSplitData( $pStruct, $request['num_split'], $request['split_values'], $count_type ); + $pManager->applySplit($pStruct); + + return $this->response->json([ + "data" => $pStruct[ 'split_result' ] + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + */ + private function validateTheRequest(): array + { + $project_id = filter_var( $this->request->param( 'project_id' ), FILTER_SANITIZE_NUMBER_INT ); + $project_pass = filter_var( $this->request->param( 'project_pass' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $job_id = filter_var( $this->request->param( 'job_id' ), FILTER_SANITIZE_NUMBER_INT ); + $job_pass = filter_var( $this->request->param( 'job_pass' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $split_raw_words = filter_var( $this->request->param( 'split_raw_words' ), FILTER_VALIDATE_BOOLEAN ); + $num_split = filter_var( $this->request->param( 'num_split' ), FILTER_SANITIZE_NUMBER_INT ); + $split_values = filter_var( $this->request->param( 'split_values' ), FILTER_CALLBACK, [ 'options' => ['self', 'valuesToInt'] ] ); + + if ( empty( $project_id) ) { + throw new InvalidArgumentException("No id project provided", -1); + } + + if ( empty( $project_pass ) ) { + throw new InvalidArgumentException("No project password provided", -2); + } + + if ( empty( $job_id) ) { + throw new InvalidArgumentException("No id job provided", -3); + } + + if ( empty( $job_pass ) ) { + throw new InvalidArgumentException("No job password provided", -4); + } + + return [ + 'project_id' => (int)$project_id, + 'project_pass' => $project_pass, + 'job_id' => (int)$job_id, + 'job_pass' => $job_pass, + 'split_raw_words' => $split_raw_words, + 'num_split' => (int)$num_split, + 'split_values' => $split_values, + ]; + } + + /** + * @param $project_id + * @param $project_pass + * @param bool $split_raw_words + * @return array + * @throws Exception + */ + private function getProjectStructure($project_id, $project_pass, $split_raw_words = false): array + { + $count_type = ($split_raw_words == true) ? Projects_MetadataDao::SPLIT_RAW_WORD_TYPE : Projects_MetadataDao::SPLIT_EQUIVALENT_WORD_TYPE; + $project_struct = Projects_ProjectDao::findByIdAndPassword( $project_id, $project_pass, 60 * 60 ); + + $pManager = new ProjectManager(); + $pManager->setProjectAndReLoadFeatures( $project_struct ); + + $pStruct = $pManager->getProjectStructure(); + + return [ + 'pStruct' => $pStruct, + 'pManager' => $pManager, + 'count_type' => $count_type, + 'project' => $project_struct, + ]; + } + + /** + * @param $jid + * @param Jobs_JobStruct[] $jobList + * + * @return Jobs_JobStruct[] + * @throws Exception + */ + private function checkMergeAccess( $jid, array $jobList ): array + { + return $this->filterJobsById( $jid, $jobList ); + } + + /** + * @param Projects_ProjectStruct $project_struct + * @param $jid + * @param $job_pass + * @param array $jobList + * @throws Exception + */ + private function checkSplitAccess( Projects_ProjectStruct $project_struct, $jid, $job_pass, array $jobList ) { + + $jobToSplit = $this->filterJobsById( $jid, $jobList ); + + if ( array_shift( $jobToSplit )->password != $job_pass ) { + throw new InvalidArgumentException( "Wrong Password. Access denied", -10 ); + } + + $project_struct->getFeaturesSet()->run('checkSplitAccess', $jobList ) ; + } + + /** + * @param $jid + * @param array $jobList + * @return array + * @throws Exception + */ + private function filterJobsById( $jid, array $jobList ): array + { + $found = false; + $filteredJobs = array_values( array_filter( $jobList, function ( Jobs_JobStruct $jobStruct ) use ( &$found, $jid ) { + return $jobStruct->id == $jid and !$jobStruct->wasDeleted(); + } ) ); + + if ( empty( $filteredJobs ) ) { + throw new AuthenticationError( "Access denied", -10 ); + } + + return $filteredJobs; + } + + /** + * @param $float_val + * @return int + */ + protected function valuesToInt( $float_val ): int + { + return (int)$float_val; + } +} diff --git a/lib/Controller/API/App/SplitSegmentController.php b/lib/Controller/API/App/SplitSegmentController.php new file mode 100644 index 0000000000..86414e01d7 --- /dev/null +++ b/lib/Controller/API/App/SplitSegmentController.php @@ -0,0 +1,122 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function split(): Response + { + try { + $request = $this->validateTheRequest(); + + $translationStruct = TranslationsSplit_SplitStruct::getStruct(); + $translationStruct->id_segment = $request['id_segment']; + $translationStruct->id_job = $request['id_job']; + + $featureSet = $this->getFeatureSet(); + + /** @var MateCatFilter $Filter */ + $Filter = MateCatFilter::getInstance( $featureSet, $request['jobStruct']->source, $request['jobStruct']->target, [] ); + list( $request['segment'], $translationStruct->source_chunk_lengths ) = CatUtils::parseSegmentSplit( $request['segment'], '', $Filter ); + + /* Fill the statuses with DEFAULT DRAFT VALUES */ + $pieces = ( count( $translationStruct->source_chunk_lengths ) > 1 ? count( $translationStruct->source_chunk_lengths ) - 1 : 1 ); + $translationStruct->target_chunk_lengths = [ + 'len' => [ 0 ], + 'statuses' => array_fill( 0, $pieces, Constants_TranslationStatus::STATUS_DRAFT ) + ]; + + $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); + $result = $translationDao->atomicUpdate( $translationStruct ); + + if ( $result instanceof TranslationsSplit_SplitStruct ) { + return $this->response->json([ + 'data' => 'OK', + 'errors' => [], + ]); + } + + $this->log( "Failed while splitting/merging segment." ); + $this->log( $translationStruct ); + throw new RuntimeException("Failed while splitting/merging segment."); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $id_job = filter_var( $this->request->param( 'id_job' ), FILTER_SANITIZE_NUMBER_INT ); + $id_segment = filter_var( $this->request->param( 'id_segment' ), FILTER_SANITIZE_NUMBER_INT ); + $password = filter_var( $this->request->param( 'password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $segment = filter_var( $this->request->param( 'segment' ), FILTER_UNSAFE_RAW ); + $target = filter_var( $this->request->param( 'target' ), FILTER_UNSAFE_RAW ); + + if ( empty( $id_job ) ) { + throw new InvalidArgumentException("Missing id job", -3); + } + + if ( empty( $id_segment ) ) { + throw new InvalidArgumentException("Missing id segment", -4); + } + + if ( empty( $password ) ) { + throw new InvalidArgumentException("Missing job password", -5); + } + + // this checks that the json is valid, but not its content + if ( is_null( $segment ) ) { + throw new InvalidArgumentException("Invalid source_chunk_lengths json", -6); + } + + // check Job password + $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $id_job, $password ); + + if ( is_null( $jobStruct ) ) { + throw new NotFoundException("Job not found"); + } + + $this->featureSet->loadForProject( $jobStruct->getProject() ); + + return [ + 'id_job' => $id_job, + 'id_segment' => $id_segment, + 'job_pass' => $password, + 'segment' => $segment, + 'target' => $target, + ]; + } +} diff --git a/lib/Controller/API/App/UpdateJobKeysController.php b/lib/Controller/API/App/UpdateJobKeysController.php new file mode 100644 index 0000000000..f9762a15dd --- /dev/null +++ b/lib/Controller/API/App/UpdateJobKeysController.php @@ -0,0 +1,205 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function update(): Response + { + try { + $request = $this->validateTheRequest(); + + // moved here because self::isRevision() in constructor + // generates an infinite loop + if ( $this->user->email == $request['jobData'][ 'owner' ] ) { + $userRole = TmKeyManagement_Filter::OWNER; + } elseif ($this->isRevision($request['job_id'], $request['job_pass'])) { + $userRole = TmKeyManagement_Filter::ROLE_REVISOR; + } else { + $userRole = TmKeyManagement_Filter::ROLE_TRANSLATOR; + } + + /* + * The client send data as structured json, for now take it as a plain structure + * + * $clientDecodedJson = Array + * ( + * [owner] => Array + * ( + * [0] => Array + * ( + * [tm] => 1 + * [glos] => 1 + * [owner] => 1 + * [key] => ***************da9a9 + * [name] => + * [r] => 1 + * [w] => 1 + * ) + * + * ) + * + * [mine] => Array + * ( + * [0] => Array + * ( + * [tm] => 1 + * [glos] => 1 + * [owner] => 0 + * [key] => 952681baffb9c147b346 + * [name] => cgjhkmfgdcjkfh + * [r] => 1 + * [w] => 1 + * ) + * + * ) + * + * [anonymous] => Array + * ( + * [0] => Array + * ( + * [tm] => 1 + * [glos] => 1 + * [owner] => 0 + * [key] => ***************882eb + * [name] => Chiave di anonimo + * [r] => 0 + * [w] => 0 + * ) + * + * ) + * + * ) + * + */ + $tm_keys = json_decode( $request['tm_keys'], true ); + $clientKeys = $request['jobData']->getClientKeys($this->user, $userRole); + + /* + * sanitize owner role key type + */ + foreach ( $tm_keys[ 'mine' ] as $k => $val ) { + + // check if logged user is owner of $val['key'] + $check = array_filter($clientKeys['job_keys'], function (TmKeyManagement_ClientTmKeyStruct $element) use ($val){ + if($element->isEncryptedKey()){ + return false; + } + + return $val['key'] === $element->key; + }); + + $tm_keys[ 'mine' ][ $k ][ 'owner' ] = !empty($check); + } + + $tm_keys = array_merge( $tm_keys[ 'ownergroup' ], $tm_keys[ 'mine' ], $tm_keys[ 'anonymous' ] ); + $tm_keys = json_encode( $tm_keys ); + + try { + $totalTmKeys = TmKeyManagement_TmKeyManagement::mergeJsonKeys( $tm_keys, $request['jobData'][ 'tm_keys' ], $userRole, $this->user->uid ); + + $this->log( 'Before: ' . $request['jobData'][ 'tm_keys' ] ); + $this->log( 'After: ' . json_encode( $totalTmKeys ) ); + + if ( $this->jobOwnerIsMe($request['jobData'][ 'owner' ]) ) { + $request['jobData'][ 'only_private_tm' ] = $request['only_private']; + } + + /** @var TmKeyManagement_TmKeyStruct $totalTmKey */ + foreach ( $totalTmKeys as $totalTmKey ){ + $totalTmKey->complete_format = true; + } + + $request['jobData']->tm_keys = json_encode( $totalTmKeys ); + + $jobDao = new Jobs_JobDao( Database::obtain() ); + $jobDao->updateStruct( $request['jobData'], [ 'fields' => [ 'only_private_tm', 'tm_keys' ] ] ); + $jobDao->destroyCache( $request['jobData'] ); + + return $this->response->json([ + 'data' => 'OK' + ]); + + } catch ( Exception $e ) { + throw new RuntimeException($e->getMessage(), $e->getCode()); + } + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @return array + * @throws AuthenticationError + * @throws \ReflectionException + */ + private function validateTheRequest(): array + { + $job_id = filter_var( $this->request->param( 'job_id' ), FILTER_SANITIZE_NUMBER_INT ); + $job_pass = filter_var( $this->request->param( 'job_pass' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $current_password = filter_var( $this->request->param( 'current_password' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $get_public_matches = filter_var( $this->request->param( 'get_public_matches' ), FILTER_VALIDATE_BOOLEAN ); + $data = filter_var( $this->request->param( 'data' ), FILTER_UNSAFE_RAW, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + if ( empty( $job_id ) ) { + throw new InvalidArgumentException("Job id missing", -1); + } + + if ( empty( $job_pass ) ) { + throw new InvalidArgumentException("Job password missing", -2); + } + + // get Job Info, we need only a row of job + $jobData = Jobs_JobDao::getByIdAndPassword( (int)$job_id, $job_pass ); + + // Check if user can access the job + $pCheck = new AjaxPasswordCheck(); + + //check for Password correctness + if ( empty( $jobData ) || !$pCheck->grantJobAccessByJobData( $jobData, $job_pass ) ) { + throw new AuthenticationError("Wrong password", -10); + } + + return [ + 'job_id' => $job_id, + 'job_pass' => $job_pass, + 'jobData' => $jobData, + 'current_password' => $current_password, + 'get_public_matches' => $get_public_matches, + 'tm_keys' => CatUtils::sanitizeJSON($data), // this will be filtered inside the TmKeyManagement class + 'only_private' => !$this->request->param('get_public_matches'), + 'data' => $data, + ]; + } + + /** + * @param $owner + * @return bool + */ + private function jobOwnerIsMe($owner): bool + { + return $this->userIsLogged && $owner == $this->user->email; + } +} diff --git a/lib/Controller/API/App/UserKeysController.php b/lib/Controller/API/App/UserKeysController.php new file mode 100644 index 0000000000..107dd9c297 --- /dev/null +++ b/lib/Controller/API/App/UserKeysController.php @@ -0,0 +1,218 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function delete(): Response + { + try { + $request = $this->validateTheRequest(); + $memoryKeyToUpdate = $this->getMemoryToUpdate($request['key'], $request['description']); + $mkDao = $this->getMkDao(); + $userMemoryKeys = $mkDao->disable( $memoryKeyToUpdate ); + $this->featureSet->run('postUserKeyDelete', $userMemoryKeys->tm_key->key, $this->user->uid ); + + return $this->response->json([ + 'errors' => [], + 'data' => $userMemoryKeys, + "success" => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function update(): Response + { + try { + $request = $this->validateTheRequest(); + $memoryKeyToUpdate = $this->getMemoryToUpdate($request['key'], $request['description']); + $mkDao = $this->getMkDao(); + $userMemoryKeys = $mkDao->atomicUpdate( $memoryKeyToUpdate ); + + return $this->response->json([ + 'errors' => [], + 'data' => $userMemoryKeys, + "success" => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function newKey(): Response + { + try { + $request = $this->validateTheRequest(); + $memoryKeyToUpdate = $this->getMemoryToUpdate($request['key'], $request['description']); + $mkDao = $this->getMkDao(); + $userMemoryKeys = $mkDao->create( $memoryKeyToUpdate ); + $this->featureSet->run( 'postTMKeyCreation', [ $userMemoryKeys ], $this->user->uid ); + + return $this->response->json([ + 'errors' => [], + 'data' => $userMemoryKeys, + "success" => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function info(): Response + { + try { + $request = $this->validateTheRequest(); + $memoryKeyToUpdate = $this->getMemoryToUpdate($request['key'], $request['description']); + $mkDao = $this->getMkDao(); + $userMemoryKeys = $mkDao->read( $memoryKeyToUpdate ); + + return $this->response->json($this->getKeyUsersInfo( $userMemoryKeys )); + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + public function share(): Response + { + try { + $request = $this->validateTheRequest(); + $memoryKeyToUpdate = $this->getMemoryToUpdate($request['key'], $request['description']); + $emailList = Utils::validateEmailList($request['emails']); + $mkDao = $this->getMkDao(); + $userMemoryKeys = $mkDao->read( $memoryKeyToUpdate ); + + if(empty($userMemoryKeys)){ + throw new NotFoundException("No user memory keys found"); + } + + (new TmKeyManagement_TmKeyManagement())->shareKey($emailList, $userMemoryKeys[0], $this->user); + + return $this->response->json([ + 'errors' => [], + 'data' => $userMemoryKeys, + "success" => true + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @param array $userMemoryKeys + * @return array + */ + protected function getKeyUsersInfo( array $userMemoryKeys ): array + { + + $_userStructs = []; + foreach( $userMemoryKeys[0]->tm_key->getInUsers() as $userStruct ){ + $_userStructs[] = new Users_ClientUserFacade( $userStruct ); + } + + return [ + 'errors' => [], + "data" => $_userStructs, + "success" => true + ]; + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $key = filter_var( $this->request->param( 'key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $emails = filter_var( $this->request->param( 'emails' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $description = filter_var( $this->request->param( 'description' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + + // check for eventual errors on the input passed + if ( empty( $key ) ) { + throw new InvalidArgumentException("Key missing", -2); + } + + // Prevent XSS attack + // =========================== + // POC. Try to add this string in the input: + //
+ // in this case, an error MUST be thrown + if($_POST['description'] and $_POST['description'] !== $description){ + throw new InvalidArgumentException("Invalid key description", -3); + } + + return [ + 'key' => $key, + 'emails' => $emails, + 'description' => $description, + ]; + } + + /** + * @return TmKeyManagement_MemoryKeyDao + */ + private function getMkDao() + { + return new TmKeyManagement_MemoryKeyDao( Database::obtain() ); + } + + /** + * @param $key + * @param null $description + * @return TmKeyManagement_MemoryKeyStruct + * @throws Exception + */ + private function getMemoryToUpdate($key, $description = null) + { + $tmService = new TMSService(); + //validate the key + try { + $keyExists = $tmService->checkCorrectKey( $key ); + } catch ( Exception $e ) { + /* PROVIDED KEY IS NOT VALID OR WRONG, $keyExists IS NOT SET */ + Log::doJsonLog( $e->getMessage() ); + } + + if ( !isset( $keyExists ) || $keyExists === false ) { + Log::doJsonLog( __METHOD__ . " -> TM key is not valid." ); + throw new InvalidArgumentException( "TM key is not valid.", -4 ); + } + + $tmKeyStruct = new TmKeyManagement_TmKeyStruct(); + $tmKeyStruct->key = $key; + $tmKeyStruct->name = $description; + $tmKeyStruct->tm = true; + $tmKeyStruct->glos = true; + + $memoryKeyToUpdate = new TmKeyManagement_MemoryKeyStruct(); + $memoryKeyToUpdate->uid = $this->user->uid; + $memoryKeyToUpdate->tm_key = $tmKeyStruct; + + return $memoryKeyToUpdate; + } +} \ No newline at end of file diff --git a/lib/Controller/API/Commons/KleinController.php b/lib/Controller/API/Commons/KleinController.php index 14bd8b47d3..946b506c24 100644 --- a/lib/Controller/API/Commons/KleinController.php +++ b/lib/Controller/API/Commons/KleinController.php @@ -6,14 +6,25 @@ use AbstractControllers\TimeLogger; use API\Commons\Authentication\AuthenticationHelper; use API\Commons\Authentication\AuthenticationTrait; +use API\Commons\Exceptions\AuthenticationError; use API\Commons\Validators\Base; use ApiKeys_ApiKeyStruct; use Bootstrap; +use CatUtils; +use DomainException; use Exception; +use Exceptions\NotFoundException; use FeatureSet; +use InvalidArgumentException; use Klein\Request; use Klein\Response; +use Log; use ReflectionException; +use RuntimeException; +use SebastianBergmann\Invoker\TimeoutException; +use Swaggest\JsonSchema\InvalidValue; +use Validator\Errors\JSONValidatorException; +use Validator\Errors\JsonValidatorGenericException; /** * @property int revision_number @@ -209,4 +220,96 @@ protected function afterValidate() { protected function isJsonRequest() { return preg_match( '~^application/json~', $this->request->headers()->get( 'Content-Type' ) ); } + + /** + * @param $id_job + * @param $password + * @return bool|null + */ + protected function isRevision($id_job, $password): ?bool + { + $isRevision = CatUtils::getIsRevisionFromIdJobAndPassword( $id_job, $password ); + + if ( null === $isRevision ) { + $isRevision = CatUtils::getIsRevisionFromReferer(); + } + + return $isRevision; + } + + /** + * @param Exception $exception + * @return Response + */ + protected function returnException(Exception $exception): Response + { + // determine http code + switch (get_class($exception)){ + + case InvalidValue::class: + case JSONValidatorException::class: + case JsonValidatorGenericException::class: + case InvalidArgumentException::class: + case DomainException::class: + $httpCode = 400; + break; + + case AuthenticationError::class: + $httpCode = 401; + break; + + case NotFoundException::class: + $httpCode = 404; + break; + + case RuntimeException::class: + $httpCode = 500; + break; + + case TimeoutException::class: + $httpCode = 504; + break; + + default: + $httpCode = $exception->getCode() >= 400 ? $exception->getCode() : 500; + break; + } + + $this->response->code($httpCode); + + return $this->response->json([ + 'errors' => [ + "code" => $exception->getCode(), + "message" => $exception->getMessage(), + "debug" => [ + "trace" => $exception->getTrace(), + "file" => $exception->getFile(), + "line" => $exception->getLine(), + ] + ] + ]); + } + + /** + * @param $id_segment + * @return array + */ + protected function parseIdSegment($id_segment): array + { + $parsedSegment = explode( "-", $id_segment ); + + return [ + 'id_segment' => $parsedSegment[0], + 'split_num' => $parsedSegment[1], + ]; + } + + /** + * @param $message + * @param null $filename + */ + protected function log($message, $filename = null ): void + { + Log::doJsonLog( $message, $filename ); + } } diff --git a/lib/Controller/API/NewController.php b/lib/Controller/API/NewController.php deleted file mode 100644 index 138f20b626..0000000000 --- a/lib/Controller/API/NewController.php +++ /dev/null @@ -1,1390 +0,0 @@ - (string) The name of the project you want create - * 'source_lang' => (string) RFC 3066 language Code ( en-US ) - * 'target_lang' => (string) RFC 3066 language(s) Code. Comma separated ( it-IT,fr-FR,es-ES ) - * 'tms_engine' => (int) Identifier for Memory Server ( ZERO means disabled, ONE means MyMemory ) - * 'mt_engine' => (int) Identifier for TM Server ( ZERO means disabled, ONE means MyMemory ) - * 'private_tm_key' => (string) Private Key for MyMemory ( set to new to create a new one ) - * - */ -class NewController extends ajaxController { - - /** - * @var array - */ - private $private_tm_key; - - private $private_tm_user = null; - private $private_tm_pass = null; - - protected $new_keys = []; - - /** - * @var Engines_AbstractEngine - */ - private $mt_engine; - - /** - * @var BasicFeatureStruct[] - */ - private $projectFeatures = []; - - private $metadata = []; - - const MAX_NUM_KEYS = 6; - - private static $allowed_seg_rules = [ - 'standard', - 'patent', - 'paragraph', - '' - ]; - - protected $api_output = [ - 'status' => 'FAIL', - 'message' => 'Untraceable error (sorry, not mapped)' - ]; - - /** - * @var \Teams\TeamStruct - */ - protected $team; - - /** - * @var ModelStruct - */ - protected $qaModel; - - protected $projectStructure; - - /** - * @var QAModelTemplateStruct - */ - protected $qaModelTemplate; - - /** - * @var CustomPayableRateStruct - */ - protected $payableRateModelTemplate; - - /** - * @var ProjectManager - */ - protected $projectManager; - - public $postInput; - - /** - * @var AbstractFilesStorage - */ - protected $files_storage; - - protected $httpHeader = "HTTP/1.0 200 OK"; - - /** - * @var array - */ - private $mmtGlossaries; - - private $deepl_formality; - - private $deepl_id_glossary; - - private $dialect_strict; - - private $filters_extraction_parameters; - - private $xliff_parameters; - - private $dictation; - private $show_whitespace; - private $character_counter; - private $ai_assistant; - - private function setBadRequestHeader() { - $this->httpHeader = 'HTTP/1.0 400 Bad Request'; - } - - private function setUnauthorizedHeader() { - $this->httpHeader = 'HTTP/1.0 401 Unauthorized'; - } - - private function setInternalErrorHeader() { - $this->httpHeader = 'HTTP/1.0 500 Internal Server Error'; - } - - private function setInternalTimeoutHeader() { - $this->httpHeader = 'HTTP/1.0 504 Gateway Timeout'; - } - - public function __construct() { - - parent::__construct(); - - //force client to close connection, avoid UPLOAD_ERR_PARTIAL for keep-alive connections - header( "Connection: close" ); - - if ( !$this->__validateAuthHeader() ) { - $this->api_output[ 'message' ] = 'Project Creation Failure'; - $this->api_output[ 'debug' ] = 'Authentication failed'; - $this->setUnauthorizedHeader(); - $this->finalize(); - die(); - } - - if ( $this->userIsLogged ) { - $this->featureSet->loadFromUserEmail( $this->user->email ); - } - - $filterArgs = [ - 'project_name' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'source_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'target_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'due_date' => [ 'filter' => FILTER_VALIDATE_INT ], - 'tms_engine' => [ - 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR, - 'options' => [ 'default' => 1, 'min_range' => 0 ] - ], - 'mt_engine' => [ - 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR, - 'options' => [ 'default' => 1, 'min_range' => 0 ] - ], - 'private_tm_key' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'subject' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'segmentation_rule' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'metadata' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'pretranslate_100' => [ - 'filter' => [ 'filter' => FILTER_VALIDATE_INT ] - ], - 'pretranslate_101' => [ - 'filter' => [ 'filter' => FILTER_VALIDATE_INT ] - ], - 'id_team' => [ 'filter' => FILTER_VALIDATE_INT ], - 'id_qa_model' => [ 'filter' => FILTER_VALIDATE_INT ], - 'id_qa_model_template' => [ 'filter' => FILTER_VALIDATE_INT ], - 'payable_rate_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - 'payable_rate_template_name' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'dialect_strict' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'lexiqa' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'speech2text' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'tag_projection' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'project_completion' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'get_public_matches' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], // disable public TM matches - 'dictation' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'show_whitespace' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'character_counter' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'ai_assistant' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'instructions' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_REQUIRE_ARRAY, - ], - 'project_info' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'mmt_glossaries' => [ 'filter' => FILTER_SANITIZE_STRING ], - - 'deepl_formality' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'deepl_id_glossary' => [ 'filter' => FILTER_SANITIZE_STRING ], - - 'filters_extraction_parameters' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'xliff_parameters' => [ 'filter' => FILTER_SANITIZE_STRING ], - - 'filters_extraction_parameters_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - 'xliff_parameters_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - - 'mt_evaluation' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ] - ]; - - $this->postInput = filter_input_array( INPUT_POST, $filterArgs ); - - - /** - * ---------------------------------- - * Note 2022-10-13 - * ---------------------------------- - * - * We trim every space private_tm_key - * in order to avoid mispelling errors - * - */ - $this->postInput[ 'instructions' ] = $this->featureSet->filter( 'encodeInstructions', $_POST[ 'instructions' ] ?? null ); - - /** - * ---------------------------------- - * Note 2021-05-28 - * ---------------------------------- - * - * We trim every space private_tm_key - * in order to avoid mispelling errors - * - */ - $this->postInput[ 'private_tm_key' ] = preg_replace( "/\s+/", "", $this->postInput[ 'private_tm_key' ] ); - - if ( empty( $_FILES ) ) { - $this->setBadRequestHeader(); - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "Missing file. Not Sent." ]; - - return -1; - } - - try { - $this->__validateMetadataParam(); - $this->__validateEngines(); - $this->__validateSubjects(); - $this->__validateSegmentationRules(); - $this->__validateTmAndKeys(); - $this->__validateTeam(); - $this->__validateQaModelTemplate(); - $this->__validatePayableRateTemplate(); - $this->__validateQaModel(); - $this->__validateUserMTEngine(); - $this->__validateMMTGlossaries(); - $this->__validateDeepLGlossaryParams(); - $this->__validateDialectStrictParam(); - $this->__validateFiltersExtractionParameters(); - $this->__validateXliffParameters(); - $this->__appendFeaturesToProject(); - $this->__generateTargetEngineAssociation(); - } catch ( Exception $ex ) { - $this->api_output[ 'message' ] = "Project Creation Failure"; - $this->api_output[ 'debug' ] = $ex->getMessage(); - Log::doJsonLog( $ex->getMessage() ); - $this->setBadRequestHeader(); - - return $ex->getCode(); - } - - $this->files_storage = FilesStorageFactory::create(); - - } - - /** - * @throws Exception - */ - private function __validateSegmentationRules() { - $this->postInput[ 'segmentation_rule' ] = Constants::validateSegmentationRules( $this->postInput[ 'segmentation_rule' ] ); - } - - /** - * @throws Exception - */ - private function __validateSubjects() { - - $langDomains = LanguageDomains::getInstance(); - $subjectMap = $langDomains::getEnabledHashMap(); - - $this->postInput[ 'subject' ] = ( !empty( $this->postInput[ 'subject' ] ) ) ? $this->postInput[ 'subject' ] : 'general'; - if ( empty( $subjectMap[ $this->postInput[ 'subject' ] ] ) ) { - throw new Exception( "Subject not allowed: " . $this->postInput[ 'subject' ], -3 ); - } - - } - - private function __appendFeaturesToProject() { - if ( $this->postInput[ 'project_completion' ] ) { - $feature = new BasicFeatureStruct(); - $feature->feature_code = 'project_completion'; - $this->projectFeatures[ $feature->feature_code ] = $feature; - } - - $this->projectFeatures = $this->featureSet->filter( - 'filterCreateProjectFeatures', $this->projectFeatures, $this - ); - - } - - /** - * This could be already set by MMT engine if enabled ( so check key existence and do not override ) - * - * @see filterCreateProjectFeatures callback - * @see NewController::__appendFeaturesToProject() - */ - private function __generateTargetEngineAssociation() { - if ( !isset( $this->postInput[ 'target_language_mt_engine_id' ] ) ) { // this could be already set by MMT engine if enabled ( so check and do not override ) - foreach ( explode( ",", $this->postInput[ 'target_lang' ] ) as $_matecatTarget ) { - $this->postInput[ 'target_language_mt_engine_id' ][ $_matecatTarget ] = $this->postInput[ 'mt_engine' ]; - } - } - } - - /** - * @throws Exception - */ - private function __validateEngines() { - - if ( !isset( $this->postInput[ 'tms_engine' ] ) ) { - $this->postInput[ 'tms_engine' ] = 1; - } - - if ( !isset( $this->postInput[ 'mt_engine' ] ) ) { - $this->postInput[ 'mt_engine' ] = 1; - } - - if ( $this->postInput[ 'tms_engine' ] != 0 ) { - Engine::getInstance( $this->postInput[ 'tms_engine' ] ); - } - - if ( $this->postInput[ 'mt_engine' ] != 0 && $this->postInput[ 'mt_engine' ] != 1 ) { - if ( !$this->userIsLogged ) { - throw new Exception( "Invalid MT Engine.", -2 ); - } else { - $testEngine = Engine::getInstance( $this->postInput[ 'mt_engine' ] ); - if ( $testEngine->getEngineRow()->uid != $this->getUser()->uid ) { - throw new Exception( "Invalid MT Engine.", -21 ); - } - } - } - - } - - public function finalize() { - header( $this->httpHeader ); - $toJson = json_encode( $this->api_output ); - echo $toJson; - } - - /** - * @throws ReQueueException - * @throws AuthenticationError - * @throws ValidationError - * @throws NotFoundException - * @throws EndQueueException - * @throws ReflectionException - * @throws Exception - */ - public function doAction() { - - $fs = FilesStorageFactory::create(); - - if ( isset( $this->api_output[ 'debug' ] ) && count( $this->api_output[ 'debug' ] ) > 0 ) { - $this->setBadRequestHeader(); - - return -1; - } - - $uploadFile = new Upload(); - - try { - $stdResult = $uploadFile->uploadFiles( $_FILES ); - } catch ( Exception $e ) { - $this->setBadRequestHeader(); - $stdResult = []; - $this->result = [ - 'errors' => [ - [ "code" => -1, "message" => $e->getMessage() ] - ] - ]; - $this->api_output[ 'message' ] = $e->getMessage(); - } - - $arFiles = []; - - foreach ( $stdResult as $input_name => $input_value ) { - $arFiles[] = $input_value->name; - } - - //if fileupload was failed this index ( 0 = does not exists ) - $default_project_name = $arFiles[ 0 ] ?? null; - if ( count( $arFiles ) > 1 ) { - $default_project_name = "MATECAT_PROJ-" . date( "Ymdhi" ); - } - - if ( empty( $this->postInput[ 'project_name' ] ) ) { - $this->postInput[ 'project_name' ] = $default_project_name; //'NO_NAME'.$this->create_project_name(); - } - - $this->__validateSourceLang( Languages::getInstance() ); - $this->__validateTargetLangs( Languages::getInstance() ); - - //ONE OR MORE ERRORS OCCURRED : EXITING - //for now we sent to api output only the LAST error message, but we log all - if ( !empty( $this->result[ 'errors' ] ) ) { - $msg = "Error \n\n " . var_export( array_merge( $this->result, $_POST ), true ); - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - $this->setBadRequestHeader(); - - return -1; //exit code - } - - $cookieDir = $uploadFile->getDirUploadToken(); - $intDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $cookieDir; - $errDir = INIT::$STORAGE_DIR . DIRECTORY_SEPARATOR . 'conversion_errors' . DIRECTORY_SEPARATOR . $cookieDir; - - $status = []; - - foreach ( $arFiles as $file_name ) { - $ext = AbstractFilesStorage::pathinfo_fix( $file_name, PATHINFO_EXTENSION ); - - $conversionHandler = new ConversionHandler(); - $conversionHandler->setFileName( $file_name ); - $conversionHandler->setSourceLang( $this->postInput[ 'source_lang' ] ); - $conversionHandler->setTargetLang( $this->postInput[ 'target_lang' ] ); - $conversionHandler->setSegmentationRule( $this->postInput[ 'segmentation_rule' ] ); - $conversionHandler->setCookieDir( $cookieDir ); - $conversionHandler->setIntDir( $intDir ); - $conversionHandler->setErrDir( $errDir ); - $conversionHandler->setFeatures( $this->featureSet ); - $conversionHandler->setUserIsLogged( $this->userIsLogged ); - $conversionHandler->setFiltersExtractionParameters( $this->filters_extraction_parameters ); - - if ( $ext == "zip" ) { - // this makes the conversionhandler accumulate eventual errors on files and continue - $conversionHandler->setStopOnFileException( false ); - - $fileObjects = $conversionHandler->extractZipFile(); - - \Log::doJsonLog( 'fileObjets', $fileObjects ); - - //call convertFileWrapper and start conversions for each file - - if ( $conversionHandler->uploadError ) { - $fileErrors = $conversionHandler->getUploadedFiles(); - - foreach ( $fileErrors as $fileError ) { - if ( count( $fileError->error ) == 0 ) { - continue; - } - - $brokenFileName = ZipArchiveExtended::getFileName( $fileError->name ); - - $this->result = new ConvertedFileModel( $fileError->error[ 'code' ] ); - $this->result->addError( $fileError->error[ 'message' ], $brokenFileName ); - } - - } - - $realFileObjectInfo = $fileObjects; - $realFileObjectNames = array_map( - [ 'ZipArchiveExtended', 'getFileName' ], - $fileObjects - ); - - foreach ( $realFileObjectNames as $i => &$fileObject ) { - $__fileName = $fileObject; - $__realFileName = $realFileObjectInfo[ $i ]; - $filesize = filesize( $intDir . DIRECTORY_SEPARATOR . $__realFileName ); - - $fileObject = [ - 'name' => $__fileName, - 'size' => $filesize - ]; - $realFileObjectInfo[ $i ] = $fileObject; - } - - $this->result[ 'data' ][ $file_name ] = json_encode( $realFileObjectNames ); - - $stdFileObjects = []; - - if ( $fileObjects !== null ) { - foreach ( $fileObjects as $fName ) { - - if ( isset( $fileErrors ) && - isset( $fileErrors->{$fName} ) && - !empty( $fileErrors->{$fName}->error ) - ) { - continue; - } - - $newStdFile = new stdClass(); - $newStdFile->name = $fName; - $stdFileObjects[] = $newStdFile; - - } - } else { - $errors = $conversionHandler->getResult(); - $errors = array_map( [ 'Upload', 'formatExceptionMessage' ], $errors->getErrors() ); - - $this->result[ 'errors' ] = array_merge( $this->result[ 'errors' ], $errors ); - $this->api_output[ 'message' ] = "Zip Error"; - $this->api_output[ 'debug' ] = $this->result[ 'errors' ]; - - return false; - } - - /* Do conversions here */ - $converter = new ConvertFileWrapper( $stdFileObjects, false ); - $converter->intDir = $intDir; - $converter->errDir = $errDir; - $converter->cookieDir = $cookieDir; - $converter->source_lang = $this->postInput[ 'source_lang' ]; - $converter->target_lang = $this->postInput[ 'target_lang' ]; - $converter->featureSet = $this->featureSet; - $converter->setUser( $this->user ); - $converter->doAction(); - - $status = $error = $converter->checkResult(); - if ( $error !== null and !empty( $error->getErrors() ) ) { - - $this->result = new ConvertedFileModel( ConversionHandlerStatus::ZIP_HANDLING ); - $this->result->changeCode( $error->getCode() ); - $savedErrors = $this->result->getErrors(); - $brokenFileName = ZipArchiveExtended::getFileName( array_keys( $error->getErrors() )[ 0 ] ); - - if ( !isset( $savedErrors[ $brokenFileName ] ) ) { - $this->result->addError( $error->getErrors()[ 0 ][ 'message' ], $brokenFileName ); - } - - $this->result = $status = [ - 'code' => $error->getCode(), - 'data' => $error->getData(), - 'errors' => $error->getErrors(), - ]; - } - } else { - - $conversionHandler->processConversion(); - - $result = $conversionHandler->getResult(); - if ( $result->getCode() < 0 ) { - $status[] = $result; - } - - } - } - - $status = array_values( $status ); - - // Upload errors handling - if ( !empty( $status ) ) { - $this->api_output[ 'message' ] = 'Project Conversion Failure'; - $this->api_output[ 'debug' ] = $status[ 2 ][ array_keys( $status[ 2 ] )[ 0 ] ]; - $this->result[ 'errors' ] = $status[ 2 ][ array_keys( $status[ 2 ] )[ 0 ] ]; - Log::doJsonLog( $status ); - $this->setBadRequestHeader(); - - return -1; - } - /* Do conversions here */ - - if ( isset( $this->result[ 'data' ] ) && !empty( $this->result[ 'data' ] ) ) { - foreach ( $this->result[ 'data' ] as $zipFileName => $zipFiles ) { - $zipFiles = json_decode( $zipFiles, true ); - $fileNames = array_column( $zipFiles, 'name' ); - $arFiles = array_merge( $arFiles, $fileNames ); - } - } - - $newArFiles = []; - $linkFiles = scandir( $intDir ); - - foreach ( $arFiles as $__fName ) { - if ( 'zip' == AbstractFilesStorage::pathinfo_fix( $__fName, PATHINFO_EXTENSION ) ) { - - - $fs->cacheZipArchive( sha1_file( $intDir . DIRECTORY_SEPARATOR . $__fName ), $intDir . DIRECTORY_SEPARATOR . $__fName ); - - $linkFiles = scandir( $intDir ); - - //fetch cache links, created by converter, from upload directory - foreach ( $linkFiles as $storedFileName ) { - //check if file begins with the name of the zip file. - // If so, then it was stored in the zip file. - if ( strpos( $storedFileName, $__fName ) !== false && - substr( $storedFileName, 0, strlen( $__fName ) ) == $__fName - ) { - //add file name to the files array - $newArFiles[] = $storedFileName; - } - } - - } else { //this file was not in a zip. Add it normally - - if ( file_exists( $intDir . DIRECTORY_SEPARATOR . $__fName ) ) { - $newArFiles[] = $__fName; - } - - } - } - - $arFiles = $newArFiles; - $arMeta = []; - - // create array_files_meta - foreach ( $arFiles as $arFile ) { - $arMeta[] = $this->getFileMetadata( $intDir . DIRECTORY_SEPARATOR . $arFile ); - } - - - $this->projectManager = new ProjectManager(); - $projectStructure = $this->projectManager->getProjectStructure(); - - $projectStructure[ 'sanitize_project_options' ] = false; - - $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[ 'dictation' ] = $this->postInput[ 'dictation' ] ?? null; - $projectStructure[ 'show_whitespace' ] = $this->postInput[ 'show_whitespace' ] ?? null; - $projectStructure[ 'character_counter' ] = $this->postInput[ 'character_counter' ] ?? null; - $projectStructure[ 'ai_assistant' ] = $this->postInput[ 'ai_assistant' ] ?? null; - - //default get all public matches from TM - $projectStructure[ 'only_private' ] = ( !isset( $this->postInput[ 'get_public_matches' ] ) ? false : !$this->postInput[ 'get_public_matches' ] ); - - $projectStructure[ 'user_ip' ] = Utils::getRealIpAddr(); - $projectStructure[ 'HTTP_HOST' ] = INIT::$HTTPHOST; - $projectStructure[ 'due_date' ] = ( !isset( $this->postInput[ 'due_date' ] ) ? null : Utils::mysqlTimestamp( $this->postInput[ 'due_date' ] ) ); - $projectStructure[ 'target_language_mt_engine_id' ] = $this->postInput[ 'target_language_mt_engine_id' ]; - $projectStructure[ 'instructions' ] = $this->postInput[ 'instructions' ]; - - if ( $this->user ) { - $projectStructure[ 'userIsLogged' ] = true; - $projectStructure[ 'uid' ] = $this->user->getUid(); - $projectStructure[ 'id_customer' ] = $this->user->getEmail(); - $this->projectManager->setTeam( $this->team ); - } - - // mmtGlossaries - if ( $this->mmtGlossaries ) { - $projectStructure[ 'mmt_glossaries' ] = $this->mmtGlossaries; - } - - // DeepL - if ( $this->mt_engine instanceof Engines_DeepL and $this->deepl_formality !== null ) { - $projectStructure[ 'deepl_formality' ] = $this->deepl_formality; - } - - if ( $this->mt_engine instanceof Engines_DeepL and $this->deepl_id_glossary !== null ) { - $projectStructure[ 'deepl_id_glossary' ] = $this->deepl_id_glossary; - } - - // with the qa template id - if ( $this->qaModelTemplate ) { - $projectStructure[ 'qa_model_template' ] = $this->qaModelTemplate->getDecodedModel(); - } - - if ( $this->qaModel ) { - $projectStructure[ 'qa_model' ] = $this->qaModel->getDecodedModel(); - } - - if ( $this->payableRateModelTemplate ) { - $projectStructure[ 'payable_rate_model_id' ] = $this->payableRateModelTemplate->id; - } - - if ( $this->dialect_strict ) { - $projectStructure[ 'dialect_strict' ] = $this->dialect_strict; - } - - if ( $this->filters_extraction_parameters ) { - $projectStructure[ 'filters_extraction_parameters' ] = $this->filters_extraction_parameters; - } - - if ( $this->xliff_parameters ) { - $projectStructure[ 'xliff_parameters' ] = $this->xliff_parameters; - } - - if ( $this->postInput[ 'mt_evaluation' ] ) { - $projectStructure[ 'mt_evaluation' ] = true; - } - - //set features override - $projectStructure[ 'project_features' ] = $this->projectFeatures; - - try { - $this->projectManager->sanitizeProjectStructure(); - } catch ( Exception $e ) { - $this->api_output[ 'message' ] = $e->getMessage(); - $this->api_output[ 'debug' ] = $e->getCode(); - $this->setBadRequestHeader(); - - return -1; - } - - $fs::moveFileFromUploadSessionToQueuePath( $uploadFile->getDirUploadToken() ); - - //reserve a project id from the sequence - $projectStructure[ 'id_project' ] = Database::obtain()->nextSequence( Database::SEQ_ID_PROJECT )[ 0 ]; - $projectStructure[ 'ppassword' ] = $this->projectManager->generatePassword(); - - $projectStructure = $this->featureSet->filter( 'addNewProjectStructureAttributes', $projectStructure, $this->postInput ); - - // flag to mark the project "from API" - $projectStructure[ 'from_api' ] = true; - - $this->projectStructure = $projectStructure; - - Queue::sendProject( $projectStructure ); - - $this->_pollForCreationResult(); - - $this->_outputResult(); - } - - /** - * @param $filename - * - * @return array - * @throws AuthenticationError - * @throws NotFoundException - * @throws ValidationError - * @throws EndQueueException - * @throws ReQueueException - */ - private function getFileMetadata( $filename ) { - $info = XliffProprietaryDetect::getInfo( $filename ); - $isXliff = XliffFiles::isXliff( $filename ); - $isGlossary = XliffFiles::isGlossaryFile( $filename ); - $isTMX = XliffFiles::isTMXFile( $filename ); - $getMemoryType = XliffFiles::getMemoryFileType( $filename ); - - $forceXliff = $this->getFeatureSet()->filter( - 'forceXLIFFConversion', - INIT::$FORCE_XLIFF_CONVERSION, - $this->userIsLogged, - $info[ 'info' ][ 'dirname' ] . DIRECTORY_SEPARATOR . "$filename" - ); - $mustBeConverted = XliffProprietaryDetect::fileMustBeConverted( $filename, $forceXliff, INIT::$FILTERS_ADDRESS ); - - $metadata = []; - $metadata[ 'basename' ] = $info[ 'info' ][ 'basename' ]; - $metadata[ 'dirname' ] = $info[ 'info' ][ 'dirname' ]; - $metadata[ 'extension' ] = $info[ 'info' ][ 'extension' ]; - $metadata[ 'filename' ] = $info[ 'info' ][ 'filename' ]; - $metadata[ 'mustBeConverted' ] = $mustBeConverted; - $metadata[ 'getMemoryType' ] = $getMemoryType; - $metadata[ 'isXliff' ] = $isXliff; - $metadata[ 'isGlossary' ] = $isGlossary; - $metadata[ 'isTMX' ] = $isTMX; - $metadata[ 'proprietary' ] = [ - 'proprietary' => $info[ 'proprietary' ], - 'proprietary_name' => $info[ 'proprietary_name' ], - 'proprietary_short_name' => $info[ 'proprietary_short_name' ], - ]; - - return $metadata; - } - - protected function _outputResult() { - if ( $this->result == null ) { - $this->api_output[ 'status' ] = 504; - $this->api_output[ 'message' ] = 'Project Creation Failure'; - $this->api_output[ 'debug' ] = 'Execution timeout'; - $this->setInternalTimeoutHeader(); - } elseif ( !empty( $this->result[ 'errors' ] ) ) { - //errors already logged - $this->api_output[ 'status' ] = 500; - $this->api_output[ 'message' ] = 'Project Creation Failure'; - $this->api_output[ 'debug' ] = array_values( $this->result[ 'errors' ] ); - $this->setInternalErrorHeader(); - } else { - //everything ok - $this->_outputForSuccess(); - } - } - - protected function _outputForSuccess() { - $this->api_output[ 'status' ] = 'OK'; - $this->api_output[ 'message' ] = 'Success'; - $this->api_output[ 'id_project' ] = $this->projectStructure[ 'id_project' ]; - $this->api_output[ 'project_pass' ] = $this->projectStructure[ 'ppassword' ]; - $this->api_output[ 'new_keys' ] = $this->new_keys; - $this->api_output[ 'analyze_url' ] = $this->projectManager->getAnalyzeURL(); - } - - protected function _pollForCreationResult() { - $this->result[ 'errors' ] = $this->projectStructure[ 'result' ][ 'errors' ]->getArrayCopy(); - } - - private function __validateSourceLang( Languages $lang_handler ) { - try { - $lang_handler->validateLanguage( $this->postInput[ 'source_lang' ] ); - } catch ( Exception $e ) { - $this->api_output[ 'message' ] = $e->getMessage(); - $this->result[ 'errors' ][] = [ "code" => -3, "message" => $e->getMessage() ]; - } - } - - private function __validateTargetLangs( Languages $lang_handler ) { - try { - $this->postInput[ 'target_lang' ] = $lang_handler->validateLanguageListAsString( $this->postInput[ 'target_lang' ] ); - } catch ( Exception $e ) { - $this->api_output[ 'message' ] = $e->getMessage(); - $this->result[ 'errors' ][] = [ "code" => -4, "message" => $e->getMessage() ]; - } - } - - /** - * Tries to find authentication credentials in header. Returns false if credentials are provided and invalid. True otherwise. - * - * @return bool - * @throws ReflectionException - */ - private function __validateAuthHeader(): bool { - - $api_key = $_SERVER[ 'HTTP_X_MATECAT_KEY' ] ?? null; - $api_secret = ( !empty( $_SERVER[ 'HTTP_X_MATECAT_SECRET' ] ) ? $_SERVER[ 'HTTP_X_MATECAT_SECRET' ] : "wrong" ); - - if ( empty( $api_key ) ) { - return false; - } - - if ( false !== strpos( $_SERVER[ 'HTTP_X_MATECAT_KEY' ] ?? null, '-' ) ) { - [ $api_key, $api_secret ] = explode( '-', $_SERVER[ 'HTTP_X_MATECAT_KEY' ] ); - } - - if ( $api_key && $api_secret ) { - $key = ApiKeys_ApiKeyDao::findByKey( $api_key ); - - if ( !$key || !$key->validSecret( $api_secret ) ) { - return false; - } - - Log::doJsonLog( $key ); - $this->user = $key->getUser(); - - $this->userIsLogged = ( - !empty( $this->user->uid ) && - !empty( $this->user->email ) && - !empty( $this->user->first_name ) && - !empty( $this->user->last_name ) - ); - - } - - return true; - } - - /** - * @param $elem - * - * @return array - */ - private static function __sanitizeTmKeyArr( $elem ) { - - $element = new TmKeyManagement_TmKeyStruct( $elem ); - $element->complete_format = true; - $elem = TmKeyManagement_TmKeyManagement::sanitize( $element ); - - return $elem->toArray(); - - } - - /** - * Expects the metadata param to be a json formatted string and tries to convert it - * in array. - * Json string is expected to be flat key value, this is enforced padding 1 to json - * conversion depth param. - * - * - * @throws Exception - */ - private function __validateMetadataParam() { - - if ( !empty( $this->postInput[ 'metadata' ] ) ) { - - if ( strlen( $this->postInput[ 'metadata' ] ) > 2048 ) { - throw new Exception( 'metadata string is too long' ); - } - - $depth = 2; // only converts key value structures - $assoc = true; - $this->postInput[ 'metadata' ] = html_entity_decode( $this->postInput[ 'metadata' ] ); - $parsedMetadata = json_decode( $this->postInput[ 'metadata' ], $assoc, $depth ); - - if ( is_array( $parsedMetadata ) ) { - $this->metadata = $parsedMetadata; - } - - Log::doJsonLog( "Passed parameter metadata as json string." ); - } - - // new raw counter model - $this->metadata[ Projects_MetadataDao::WORD_COUNT_TYPE_KEY ] = Projects_MetadataDao::WORD_COUNT_RAW; - - // project_info - if ( !empty( $this->postInput[ 'project_info' ] ) ) { - $this->metadata[ 'project_info' ] = $this->postInput[ 'project_info' ]; - } - - if ( !empty( $this->postInput[ 'dialect_strict' ] ) ) { - $this->metadata[ 'dialect_strict' ] = $this->postInput[ 'dialect_strict' ]; - } - - //override metadata with explicitly declared keys ( we maintain metadata for backward compatibility ) - if ( !empty( $this->postInput[ 'lexiqa' ] ) ) { - $this->metadata[ 'lexiqa' ] = $this->postInput[ 'lexiqa' ]; - } - - if ( !empty( $this->postInput[ 'speech2text' ] ) ) { - $this->metadata[ 'speech2text' ] = $this->postInput[ 'speech2text' ]; - } - - if ( !empty( $this->postInput[ 'tag_projection' ] ) ) { - $this->metadata[ 'tag_projection' ] = $this->postInput[ 'tag_projection' ]; - } - - if ( !empty( $this->postInput[ 'project_completion' ] ) ) { - $this->metadata[ 'project_completion' ] = $this->postInput[ 'project_completion' ]; - } - - if ( !empty( $this->postInput[ 'segmentation_rule' ] ) ) { - $this->metadata[ 'segmentation_rule' ] = $this->postInput[ 'segmentation_rule' ]; - } - - $this->metadata = $this->featureSet->filter( 'filterProjectMetadata', $this->metadata, $this->postInput ); - $this->metadata = $this->featureSet->filter( 'createProjectAssignInputMetadata', $this->metadata, [ - 'input' => $this->postInput - ] ); - - } - - private static function __parseTmKeyInput( $tmKeyString ) { - $tmKeyString = trim( $tmKeyString ); - $tmKeyInfo = explode( ":", $tmKeyString ); - $read = true; - $write = true; - - $permissionString = $tmKeyInfo[ 1 ] ?? null; - - //if the key is not set, return null. It will be filtered in the next lines. - if ( empty( $tmKeyInfo[ 0 ] ) ) { - return null; - } //if permissions are set, check if they are allowed or not and eventually set permissions - - //permission string check - switch ( $permissionString ) { - case 'r': - $write = false; - break; - case 'w': - $read = false; - break; - case 'rw': - case '' : - case null: - break; - //permission string not allowed - default: - $allowed_permissions = implode( ", ", Constants_TmKeyPermissions::$_accepted_grants ); - throw new Exception( "Permission modifier string not allowed. Allowed: , $allowed_permissions" ); - break; - } - - return [ - 'key' => $tmKeyInfo[ 0 ], - 'r' => $read, - 'w' => $write, - ]; - } - - protected function __validateTmAndKeys() { - - try { - $this->private_tm_key = array_map( - [ 'NewController', '__parseTmKeyInput' ], - explode( ",", $this->postInput[ 'private_tm_key' ] ) - ); - } catch ( Exception $e ) { - throw new Exception( $e->getMessage(), -6 ); - } - - if ( count( $this->private_tm_key ) > self::MAX_NUM_KEYS ) { - throw new Exception( "Too much keys provided. Max number of keys is " . self::MAX_NUM_KEYS, -2 ); - } - - $this->private_tm_key = array_values( array_filter( $this->private_tm_key ) ); - - //If a TMX file has been uploaded and no key was provided, create a new key. - if ( empty( $this->private_tm_key ) ) { - foreach ( $_FILES as $_fileinfo ) { - $pathinfo = AbstractFilesStorage::pathinfo_fix( $_fileinfo[ 'name' ] ); - if ( $pathinfo[ 'extension' ] == 'tmx' ) { - $this->private_tm_key[] = [ 'key' => 'new' ]; - break; - } - } - } - - //remove all empty entries - foreach ( $this->private_tm_key as $__key_idx => $tm_key ) { - //from api a key is sent and the value is 'new' - if ( $tm_key[ 'key' ] == 'new' ) { - - try { - - $APIKeySrv = new TMSService(); - - $newUser = $APIKeySrv->createMyMemoryKey(); - - $this->private_tm_user = $newUser->id; - $this->private_tm_pass = $newUser->pass; - - $this->private_tm_key[ $__key_idx ] = - [ - 'key' => $newUser->key, - 'name' => null, - 'r' => $tm_key[ 'r' ], - 'w' => $tm_key[ 'w' ] - - ]; - $this->new_keys[] = $newUser->key; - - } catch ( Exception $e ) { - throw new Exception( $e->getMessage(), -1 ); - } - - } //if a string is sent, transform it into a valid array - elseif ( !empty( $tm_key ) ) { - - $uid = $this->user->uid; - - $this_tm_key = [ - 'key' => $tm_key[ 'key' ], - 'name' => null, - 'r' => $tm_key[ 'r' ], - 'w' => $tm_key[ 'w' ] - ]; - - /** - * Get the key description/name from the user keyring - */ - if ( $uid ) { - $mkDao = new TmKeyManagement_MemoryKeyDao(); - - /** - * @var $keyRing TmKeyManagement_MemoryKeyStruct[] - */ - $keyRing = $mkDao->read( - ( new TmKeyManagement_MemoryKeyStruct( [ - 'uid' => $uid, - 'tm_key' => new TmKeyManagement_TmKeyStruct( $this_tm_key ) - ] ) - ) - ); - - if ( count( $keyRing ) > 0 ) { - $this_tm_key[ 'name' ] = $keyRing[ 0 ]->tm_key->name; - } - } - - $this->private_tm_key[ $__key_idx ] = $this_tm_key; - } - - $this->private_tm_key[ $__key_idx ] = self::__sanitizeTmKeyArr( $this->private_tm_key[ $__key_idx ] ); - - } - - } - - /** - * @throws Exception - */ - private function __validateTeam() { - if ( $this->user && !empty( $this->postInput[ 'id_team' ] ) ) { - $dao = new MembershipDao(); - $org = $dao->findTeamByIdAndUser( $this->postInput[ 'id_team' ], $this->user ); - - if ( !$org ) { - throw new Exception( 'Team and user membership does not match', -1 ); - } else { - $this->team = $org; - } - } else { - if ( $this->user ) { - $this->team = $this->user->getPersonalTeam(); - } - } - } - - /** - * @throws Exception - */ - private function __validateQaModelTemplate() { - if ( !empty( $this->postInput[ 'id_qa_model_template' ] ) ) { - $qaModelTemplate = QAModelTemplateDao::get( [ - 'id' => $this->postInput[ 'id_qa_model_template' ], - 'uid' => $this->getUser()->uid - ] ); - - // check if qa_model template exists - if ( null === $qaModelTemplate ) { - throw new Exception( 'This QA Model template does not exists or does not belongs to the logged in user' ); - } - - $this->qaModelTemplate = $qaModelTemplate; - } - } - - /** - * @throws Exception - */ - private function __validatePayableRateTemplate() { - $payableRateModelTemplate = null; - - if ( !empty( $this->postInput[ 'payable_rate_template_name' ] ) ) { - if ( empty( $this->postInput[ 'payable_rate_template_id' ] ) ) { - throw new Exception( '`payable_rate_template_id` param is missing' ); - } - } - - if ( !empty( $this->postInput[ 'payable_rate_template_id' ] ) ) { - if ( empty( $this->postInput[ 'payable_rate_template_name' ] ) ) { - throw new Exception( '`payable_rate_template_name` param is missing' ); - } - } - - if ( !empty( $this->postInput[ 'payable_rate_template_name' ] ) and !empty( $this->postInput[ 'payable_rate_template_id' ] ) ) { - - $payableRateTemplateId = $this->postInput[ 'payable_rate_template_id' ]; - $payableRateTemplateName = $this->postInput[ 'payable_rate_template_name' ]; - $userId = $this->getUser()->uid; - - $payableRateModelTemplate = CustomPayableRateDao::getByIdAndUser( $payableRateTemplateId, $userId ); - - if ( null === $payableRateModelTemplate ) { - throw new Exception( 'Payable rate model id not valid' ); - } - - if ( $payableRateModelTemplate->name !== $payableRateTemplateName ) { - throw new Exception( 'Payable rate model name not matching' ); - } - } - - $this->payableRateModelTemplate = $payableRateModelTemplate; - } - - /** - * Checks if id_qa_model is valid - * - * @throws Exception - */ - private function __validateQaModel() { - if ( !empty( $this->postInput[ 'id_qa_model' ] ) ) { - - $qaModel = ModelDao::findByIdAndUser( $this->postInput[ 'id_qa_model' ], $this->getUser()->uid ); - - //XXX FALLBACK for models created before "required-login" feature (on these models there is no ownership check) - if ( empty( $qaModel ) ) { - $qaModel = ModelDao::findById( $this->postInput[ 'id_qa_model' ] ); - $qaModel->uid = $this->getUser()->uid; - } - - // check if qa_model exists - if ( empty( $qaModel ) ) { - throw new Exception( 'This QA Model does not exists' ); - } - - // check featureSet - $qaModelLabel = strtolower( $qaModel->label ); - if ( $qaModelLabel !== 'default' and $qaModel->uid != $this->getUser()->uid ) { - throw new Exception( 'This QA Model does not belong to the authenticated user' ); - } - - $this->qaModel = $qaModel; - } - } - - /** - * @throws Exception - */ - private function __validateUserMTEngine() { - - // any other engine than MyMemory - if ( $this->postInput[ 'mt_engine' ] and $this->postInput[ 'mt_engine' ] > 1 ) { - EngineValidator::engineBelongsToUser( $this->postInput[ 'mt_engine' ], $this->user->uid ); - } - } - - /** - * @throws Exception - */ - private function __validateMMTGlossaries() { - - if ( !empty( $this->postInput[ 'mmt_glossaries' ] ) ) { - - $mmtGlossaries = html_entity_decode( $this->postInput[ 'mmt_glossaries' ] ); - MMTValidator::validateGlossary( $mmtGlossaries ); - - $this->mmtGlossaries = $mmtGlossaries; - } - } - - /** - * Validate DeepL params - */ - private function __validateDeepLGlossaryParams() { - - if ( !empty( $this->postInput[ 'deepl_formality' ] ) ) { - - $allowedFormalities = [ - 'default', - 'prefer_less', - 'prefer_more' - ]; - - if ( in_array( $this->postInput[ 'deepl_formality' ], $allowedFormalities ) ) { - $this->deepl_formality = $this->postInput[ 'deepl_formality' ]; - } - } - - if ( !empty( $this->postInput[ 'deepl_id_glossary' ] ) ) { - $this->deepl_id_glossary = $this->postInput[ 'deepl_id_glossary' ]; - } - } - - /** - * Validate `dialect_strict` param vs target languages - * - * Example: {"it-IT": true, "en-US": false, "fr-FR": false} - * - * @throws Exception - */ - private function __validateDialectStrictParam() { - if ( !empty( $this->postInput[ 'dialect_strict' ] ) ) { - - $dialect_strict = trim( html_entity_decode( $this->postInput[ 'dialect_strict' ] ) ); - $target_languages = preg_replace( '/\s+/', '', $this->postInput[ 'target_lang' ] ); - $targets = explode( ',', trim( $target_languages ) ); - - // first check if `dialect_strict` is a valid JSON - if ( !Utils::isJson( $dialect_strict ) ) { - throw new Exception( "dialect_strict is not a valid JSON" ); - } - - $dialectStrictObj = json_decode( $dialect_strict, true ); - - foreach ( $dialectStrictObj as $lang => $value ) { - if ( !in_array( $lang, $targets ) ) { - throw new Exception( 'Wrong `dialect_strict` object, language, ' . $lang . ' is not one of the project target languages' ); - } - - if ( !is_bool( $value ) ) { - throw new Exception( 'Wrong `dialect_strict` object, not boolean declared value for ' . $lang ); - } - } - - $this->dialect_strict = html_entity_decode( $dialect_strict ); - } - } - - /** - * @throws Exception - */ - private function __validateFiltersExtractionParameters() { - - if ( !empty( $this->postInput[ 'filters_extraction_parameters' ] ) ) { - - $json = html_entity_decode( $this->postInput[ 'filters_extraction_parameters' ] ); - - // 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/filters_extraction_parameters.json' ); - - $validatorObject = new JSONValidatorObject(); - $validatorObject->json = $json; - - $validator = new JSONValidator( $schema ); - $validator->validate( $validatorObject ); - - $this->filters_extraction_parameters = json_decode( $json ); - - } elseif ( !empty( $this->postInput[ 'filters_extraction_parameters_template_id' ] ) ) { - - $filtersTemplate = FiltersConfigTemplateDao::getByIdAndUser( $this->postInput[ 'filters_extraction_parameters_template_id' ], $this->getUser()->uid ); - - if ( $filtersTemplate === null ) { - throw new Exception( "filters_extraction_parameters_template_id not valid" ); - } - - $this->filters_extraction_parameters = $filtersTemplate; - } - } - - /** - * @throws Exception - */ - private function __validateXliffParameters() { - - if ( !empty( $this->postInput[ 'xliff_parameters' ] ) ) { - - $json = html_entity_decode( $this->postInput[ 'xliff_parameters' ] ); - - // first check if `xliff_parameters` is a valid JSON - if ( !Utils::isJson( $json ) ) { - throw new Exception( "xliff_parameters is not a valid JSON" ); - } - - $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/xliff_parameters_rules_content.json' ); - - $validatorObject = new JSONValidatorObject(); - $validatorObject->json = $json; - - $validator = new JSONValidator( $schema, true ); - $validator->validate( $validatorObject ); - $this->xliff_parameters = json_decode( $json, true ); // decode again because we need an associative array and not stdClass - - } elseif ( !empty( $this->postInput[ 'xliff_parameters_template_id' ] ) ) { - - $xliffConfigTemplate = XliffConfigTemplateDao::getByIdAndUser( $this->postInput[ 'xliff_parameters_template_id' ], $this->getUser()->uid ); - - if ( $xliffConfigTemplate === null ) { - throw new Exception( "xliff_parameters_template_id not valid" ); - } - - $this->xliff_parameters = $xliffConfigTemplate->rules->getArrayCopy(); - } - } -} diff --git a/lib/Controller/API/StatusController.php b/lib/Controller/API/StatusController.php deleted file mode 100644 index 5939648b7c..0000000000 --- a/lib/Controller/API/StatusController.php +++ /dev/null @@ -1,103 +0,0 @@ - 'FAIL' - ); - - protected $id_project; - protected $ppassword; - - /** - * @param mixed $id_project - * - * @return $this - */ - public function setIdProject( $id_project ) { - $this->id_project = $id_project; - - return $this; - } - - /** - * @param mixed $ppassword - * - * @return $this - */ - public function setPpassword( $ppassword ) { - $this->ppassword = $ppassword; - - return $this; - } - - public function getApiOutput(){ - return json_encode( $this->api_output ); - } - - /** - * Check Status of a created Project With HTTP POST ( application/x-www-form-urlencoded ) protocol - * - * POST Params: - * - * 'id_project' => (int) ID of Project to check - * 'ppassword' => (string) Project Password - * - */ - public function __construct() { - - parent::__construct(); - - $filterArgs = array( - 'id_project' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'project_pass' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - ); - - $__postInput = filter_input_array( INPUT_GET, $filterArgs ); - - $this->id_project = $__postInput[ 'id_project' ]; - $this->ppassword = $__postInput[ 'project_pass' ]; - - } - - public function finalize() { - $toJson = json_encode( $this->api_output ); - echo $toJson; - } - - public function doAction() { - - if ( empty( $this->id_project ) ) { - $this->api_output[ 'message' ] = array( -1, "No id project provided" ); - - return -1; - } - - $_project_data = Projects_ProjectDao::getProjectAndJobData( $this->id_project ); - - $passCheck = new AjaxPasswordCheck(); - $access = $passCheck->grantProjectAccess( $_project_data, $this->ppassword ) || $passCheck->grantProjectJobAccessOnJobPass( $_project_data, null, $this->jpassword ); - - if ( !$access ) { - $this->api_output[ 'message' ] = array( -10, "Wrong Password. Access denied" ); - - return -1; - } - - $analysisStatus = new Status( $_project_data, $this->featureSet, $this->user ); - $this->api_output = $analysisStatus->fetchData()->getResult(); - } -} \ No newline at end of file diff --git a/lib/Controller/API/V1/NewController.php b/lib/Controller/API/V1/NewController.php new file mode 100644 index 0000000000..8a18079a80 --- /dev/null +++ b/lib/Controller/API/V1/NewController.php @@ -0,0 +1,1280 @@ +appendValidator( new LoginValidator( $this ) ); + } + + public function create() + { + try { + $this->featureSet->loadFromUserEmail( $this->user->email ); + $request = $this->validateTheRequest(); + $fs = FilesStorageFactory::create(); + $uploadFile = new Upload(); + + try { + $stdResult = $uploadFile->uploadFiles( $_FILES ); + } catch ( Exception $e ) { + throw new RuntimeException($e->getMessage(), -1); + } + + $arFiles = []; + + foreach ( $stdResult as $input_name => $input_value ) { + $arFiles[] = $input_value->name; + } + + // if fileupload was failed this index ( 0 = does not exists ) + $default_project_name = @$arFiles[ 0 ]; + if ( count( $arFiles ) > 1 ) { + $default_project_name = "MATECAT_PROJ-" . date( "Ymdhi" ); + } + + if ( empty( $request[ 'project_name' ] ) ) { + $request[ 'project_name' ] = $default_project_name; //'NO_NAME'.$this->create_project_name(); + } + + $cookieDir = $uploadFile->getDirUploadToken(); + $intDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $cookieDir; + $errDir = INIT::$STORAGE_DIR . DIRECTORY_SEPARATOR . 'conversion_errors' . DIRECTORY_SEPARATOR . $cookieDir; + + $status = []; + + foreach ( $arFiles as $file_name ) { + $ext = AbstractFilesStorage::pathinfo_fix( $file_name, PATHINFO_EXTENSION ); + + $conversionHandler = new ConversionHandler(); + $conversionHandler->setFileName( $file_name ); + $conversionHandler->setSourceLang( $request[ 'source_lang' ] ); + $conversionHandler->setTargetLang( $request[ 'target_lang' ] ); + $conversionHandler->setSegmentationRule( $request[ 'segmentation_rule' ] ); + $conversionHandler->setCookieDir( $cookieDir ); + $conversionHandler->setIntDir( $intDir ); + $conversionHandler->setErrDir( $errDir ); + $conversionHandler->setFeatures( $this->featureSet ); + $conversionHandler->setUserIsLogged( $this->userIsLogged ); + $conversionHandler->setFiltersExtractionParameters( $request['filters_extraction_parameters'] ); + + if ( $ext == "zip" ) { + // this makes the conversionhandler accumulate eventual errors on files and continue + $conversionHandler->setStopOnFileException( false ); + $fileObjects = $conversionHandler->extractZipFile(); + Log::doJsonLog( 'fileObjets', $fileObjects ); + + // call convertFileWrapper and start conversions for each file + if ( $conversionHandler->uploadError ) { + $fileErrors = $conversionHandler->getUploadedFiles(); + + foreach ( $fileErrors as $fileError ) { + if ( count( $fileError->error ) == 0 ) { + continue; + } + + $brokenFileName = ZipArchiveExtended::getFileName( $fileError->name ); + $result = new ConvertedFileModel( $fileError->error[ 'code' ] ); + $result->addError( $fileError->error[ 'message' ], $brokenFileName ); + } + + $realFileObjectInfo = $fileObjects; + $realFileObjectNames = array_map( + [ 'ZipArchiveExtended', 'getFileName' ], + $fileObjects + ); + + foreach ( $realFileObjectNames as $i => &$fileObject ) { + $__fileName = $fileObject; + $__realFileName = $realFileObjectInfo[ $i ]; + $filesize = filesize( $intDir . DIRECTORY_SEPARATOR . $__realFileName ); + + $fileObject = [ + 'name' => $__fileName, + 'size' => $filesize + ]; + $realFileObjectInfo[ $i ] = $fileObject; + } + + $result[ 'data' ][ $file_name ] = json_encode( $realFileObjectNames ); + $stdFileObjects = []; + + if ( $fileObjects !== null ) { + foreach ( $fileObjects as $fName ) { + + if ( isset( $fileErrors ) && + isset( $fileErrors->{$fName} ) && + !empty( $fileErrors->{$fName}->error ) + ) { + continue; + } + + $newStdFile = new stdClass(); + $newStdFile->name = $fName; + $stdFileObjects[] = $newStdFile; + + } + } else { + $errors = $conversionHandler->getResult(); + $errors = array_map( [ 'Upload', 'formatExceptionMessage' ], $errors->getErrors() ); + $result[ 'errors' ] = array_merge( $result[ 'errors' ], $errors ); + Log::doJsonLog("Zip error:" . $result['errors']); + + throw new RuntimeException("Zip Error"); + } + + /* Do conversions here */ + $converter = new ConvertFile( + $stdFileObjects, + $request['source_lang'], + $request['target_lang'], + $intDir, + $errDir, + $cookieDir, + $request['segmentation_rule'], + $this->featureSet, + $request['filters_extraction_parameters'], + false ); + + $converter->setUser( $this->user ); + $converter->convertFiles(); + + $status = $errors = $converter->getErrors(); + + if ( $errors !== null and !empty( $errors ) ) { + + $result = new ConvertedFileModel( ConversionHandlerStatus::ZIP_HANDLING ); + $result->changeCode( 500 ); + $savedErrors = $result->getErrors(); + $brokenFileName = ZipArchiveExtended::getFileName( array_keys( $errors )[ 0 ] ); + + if ( !isset( $savedErrors[ $brokenFileName ] ) ) { + $result->addError( $errors[ 0 ][ 'message' ], $brokenFileName ); + } + + $result = $status = [ + 'code' => 500, + 'data' => [], // Is it correct???? + 'errors' => $errors, + ]; + } + } + + } else { + $conversionHandler->doAction(); + $res = $conversionHandler->getResult(); + if ( $res->getCode() < 0 ) { + $status[] = [ + 'code' => $res->getCode(), + 'data' => $res->getData(), // Is it correct???? + 'errors' => $res->getErrors(), + 'warnings' => $res->getWarnings(), + ]; + } + } + } + + $status = array_values( $status ); + + // Upload errors handling + if ( !empty( $status ) ) { + throw new RuntimeException('Project Conversion Failure'); + } + + /* Do conversions here */ + if ( isset( $result[ 'data' ] ) && !empty( $result[ 'data' ] ) ) { + foreach ( $result[ 'data' ] as $zipFileName => $zipFiles ) { + $zipFiles = json_decode( $zipFiles, true ); + $fileNames = Utils::array_column( $zipFiles, 'name' ); + $arFiles = array_merge( $arFiles, $fileNames ); + } + } + + $newArFiles = []; + $linkFiles = scandir( $intDir ); + + foreach ( $arFiles as $__fName ) { + if ( 'zip' == AbstractFilesStorage::pathinfo_fix( $__fName, PATHINFO_EXTENSION ) ) { + + + $fs->cacheZipArchive( sha1_file( $intDir . DIRECTORY_SEPARATOR . $__fName ), $intDir . DIRECTORY_SEPARATOR . $__fName ); + + $linkFiles = scandir( $intDir ); + + //fetch cache links, created by converter, from upload directory + foreach ( $linkFiles as $storedFileName ) { + //check if file begins with the name of the zip file. + // If so, then it was stored in the zip file. + if ( strpos( $storedFileName, $__fName ) !== false && + substr( $storedFileName, 0, strlen( $__fName ) ) == $__fName + ) { + //add file name to the files array + $newArFiles[] = $storedFileName; + } + } + + } else { //this file was not in a zip. Add it normally + if ( file_exists( $intDir . DIRECTORY_SEPARATOR . $__fName ) ) { + $newArFiles[] = $__fName; + } + } + } + + $arFiles = $newArFiles; + $arMeta = []; + + // create array_files_meta + foreach ( $arFiles as $arFile ) { + $arMeta[] = $this->getFileMetadata( $intDir . DIRECTORY_SEPARATOR . $arFile ); + } + + $projectManager = new ProjectManager(); + $projectStructure = $projectManager->getProjectStructure(); + $projectStructure[ 'sanitize_project_options' ] = false; + $projectStructure[ 'project_name' ] = $request[ 'project_name' ]; + $projectStructure[ 'job_subject' ] = $request[ 'subject' ]; + $projectStructure[ 'private_tm_key' ] = $request['private_tm_key']; + $projectStructure[ 'private_tm_user' ] = $request['private_tm_user']; + $projectStructure[ 'private_tm_pass' ] = $request['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' ] = $request[ 'source_lang' ]; + $projectStructure[ 'target_language' ] = explode( ',', $request[ 'target_lang' ] ); + $projectStructure[ 'mt_engine' ] = $request[ 'mt_engine' ]; + $projectStructure[ 'tms_engine' ] = $request[ 'tms_engine' ]; + $projectStructure[ 'status' ] = Constants_ProjectStatus::STATUS_NOT_READY_FOR_ANALYSIS; + $projectStructure[ 'owner' ] = $this->user->email; + $projectStructure[ 'metadata' ] = $request['metadata']; + $projectStructure[ 'pretranslate_100' ] = (int)!!$request[ 'pretranslate_100' ]; // Force pretranslate_100 to be 0 or 1 + $projectStructure[ 'pretranslate_101' ] = isset( $request[ 'pretranslate_101' ] ) ? (int)$request[ 'pretranslate_101' ] : 1; + + //default get all public matches from TM + $projectStructure[ 'only_private' ] = ( !isset( $request[ 'get_public_matches' ] ) ? false : !$request[ 'get_public_matches' ] ); + + $projectStructure[ 'user_ip' ] = Utils::getRealIpAddr(); + $projectStructure[ 'HTTP_HOST' ] = INIT::$HTTPHOST; + $projectStructure[ 'due_date' ] = ( !isset( $this->postInput[ 'due_date' ] ) ? null : Utils::mysqlTimestamp( $request[ 'due_date' ] ) ); + $projectStructure[ 'target_language_mt_engine_id' ] = $request[ 'target_language_mt_engine_id' ]; + $projectStructure[ 'instructions' ] = $request[ 'instructions' ]; + + $projectStructure[ 'userIsLogged' ] = true; + $projectStructure[ 'uid' ] = $this->user->getUid(); + $projectStructure[ 'id_customer' ] = $this->user->getEmail(); + $projectManager->setTeam( $request['team'] ); + + // mmtGlossaries + if ( $request['mmtGlossaries'] ) { + $projectStructure[ 'mmt_glossaries' ] = $request['mmtGlossaries']; + } + + // DeepL + if ( $request['mt_engine'] instanceof Engines_DeepL and $request['deepl_formality'] !== null ) { + $projectStructure[ 'deepl_formality' ] = $request['deepl_formality']; + } + + if ( $request['mt_engine'] instanceof Engines_DeepL and $request['deepl_id_glossary'] !== null ) { + $projectStructure[ 'deepl_id_glossary' ] = $request['deepl_id_glossary']; + } + + // with the qa template id + if ( $request['qaModelTemplate'] ) { + $projectStructure[ 'qa_model_template' ] = $request['qaModelTemplate']->getDecodedModel(); + } + + if ( $request['qaModel'] ) { + $projectStructure[ 'qa_model' ] = $request['qaModel']->getDecodedModel(); + } + + if ( $request['payableRateModelTemplate'] ) { + $projectStructure[ 'payable_rate_model_id' ] = $request['payableRateModelTemplate']->id; + } + + if ( $request['dialect_strict'] ) { + $projectStructure[ 'dialect_strict' ] = $request['dialect_strict']; + } + + if ( $request['filters_extraction_parameters'] ) { + $projectStructure[ 'filters_extraction_parameters' ] = $request['filters_extraction_parameters']; + } + + if ( $request['xliff_parameters'] ) { + $projectStructure[ 'xliff_parameters' ] = $request['xliff_parameters']; + } + + //set features override + $projectStructure[ 'project_features' ] = $request['project_features']; + + try { + $projectManager->sanitizeProjectStructure(); + } catch ( Exception $e ) { + throw new RuntimeException($e->getMessage(), -1); + } + + $fs::moveFileFromUploadSessionToQueuePath( $uploadFile->getDirUploadToken() ); + + //reserve a project id from the sequence + $projectStructure[ 'id_project' ] = Database::obtain()->nextSequence( Database::SEQ_ID_PROJECT )[ 0 ]; + $projectStructure[ 'ppassword' ] = $projectManager->generatePassword(); + + $projectStructure = $this->featureSet->filter( 'addNewProjectStructureAttributes', $projectStructure, $_POST ); + + // flag to mark the project "from API" + $projectStructure[ 'from_api' ] = true; + + Queue::sendProject( $projectStructure ); + + $result['errors'] = $this->pollForCreationResult($projectStructure); + + if ( $result == null ) { + throw new TimeoutException('Project Creation Failure'); + } + + if ( !empty( $result[ 'errors' ] ) ) { + throw new RuntimeException('Project Creation Failure'); + } + + return $this->response->json([ + 'status' => 'OK', + 'message' => 'Success', + 'id_project' => $projectStructure[ 'id_project' ], + 'project_pass' => $projectStructure[ 'ppassword' ], + 'new_keys' => $request['new_keys'], + 'analyze_url' => $projectManager->getAnalyzeURL() + ]); + + } catch (Exception $exception){ + return $this->returnException($exception); + } + } + + /** + * @param $projectStructure + * @return mixed + */ + private function pollForCreationResult($projectStructure) + { + return $projectStructure[ 'result' ][ 'errors' ]->getArrayCopy(); + } + + /** + * @return array + * @throws Exception + */ + private function validateTheRequest(): array + { + $metadata = filter_var( $this->request->param( 'metadata' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $segmentation_rule = filter_var( $this->request->param( 'segmentation_rule' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ] ); + $project_name = filter_var( $this->request->param( 'project_name' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $source_lang = filter_var( $this->request->param( 'source_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $target_lang = filter_var( $this->request->param( 'target_lang' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $subject = filter_var( $this->request->param( 'subject' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $due_date = filter_var( $this->request->param( 'due_date' ), FILTER_SANITIZE_NUMBER_INT ); + $mt_engine = filter_var( $this->request->param( 'mt_engine' ), FILTER_SANITIZE_NUMBER_INT, [ 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR, 'options' => [ 'default' => 1, 'min_range' => 0 ] ] ); + $tms_engine = filter_var( $this->request->param( 'tms_engine' ), FILTER_VALIDATE_INT, [ 'filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_REQUIRE_SCALAR, 'options' => [ 'default' => 1, 'min_range' => 0 ] ] ); + $private_tm_key = filter_var( $this->request->param( 'private_tm_key' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); + $pretranslate_100 = filter_var( $this->request->param( 'pretranslate_100' ), FILTER_SANITIZE_NUMBER_INT ); + $pretranslate_101 = filter_var( $this->request->param( 'pretranslate_101' ), FILTER_SANITIZE_NUMBER_INT ); + $id_team = filter_var( $this->request->param( 'id_team' ), FILTER_SANITIZE_NUMBER_INT, [ 'flags' => FILTER_REQUIRE_SCALAR ] ); + $project_completion = filter_var( $this->request->param( 'project_completion' ), FILTER_VALIDATE_BOOLEAN ); + $get_public_matches = filter_var( $this->request->param( 'get_public_matches' ), FILTER_VALIDATE_BOOLEAN ); + $dialect_strict = filter_var( $this->request->param( 'dialect_strict' ), FILTER_SANITIZE_STRING ); + $qa_model_template_id = filter_var( $this->request->param( 'qa_model_template_id' ), FILTER_SANITIZE_NUMBER_INT ); + $payable_rate_template_id = filter_var( $this->request->param( 'pretranslate_100' ), FILTER_SANITIZE_NUMBER_INT ); + $target_language_mt_engine_id = filter_var( $this->request->param( 'target_language_mt_engine_id' ), FILTER_SANITIZE_NUMBER_INT ); + $id_qa_model = filter_var( $this->request->param( 'id_qa_model' ), FILTER_SANITIZE_NUMBER_INT ); + $id_qa_model_template = filter_var( $this->request->param( 'id_qa_model_template' ), FILTER_SANITIZE_NUMBER_INT ); + $payable_rate_template_name = filter_var( $this->request->param( 'payable_rate_template_name' ), FILTER_SANITIZE_STRING ); + $lexiqa = filter_var( $this->request->param( 'lexiqa' ), FILTER_VALIDATE_BOOLEAN ); + $speech2text = filter_var( $this->request->param( 'speech2text' ), FILTER_VALIDATE_BOOLEAN ); + $tag_projection = filter_var( $this->request->param( 'tag_projection' ), FILTER_VALIDATE_BOOLEAN ); + $instructions = filter_var( $this->request->param( 'instructions' ), FILTER_SANITIZE_STRING, [ 'flags' => FILTER_REQUIRE_ARRAY ] ); + $project_info = filter_var( $this->request->param( 'project_info' ), FILTER_SANITIZE_STRING ); + $mmt_glossaries = filter_var( $this->request->param( 'mmt_glossaries' ), FILTER_SANITIZE_STRING ); + $deepl_formality = filter_var( $this->request->param( 'deepl_formality' ), FILTER_SANITIZE_STRING ); + $deepl_id_glossary = filter_var( $this->request->param( 'deepl_id_glossary' ), FILTER_SANITIZE_STRING ); + $filters_extraction_parameters = filter_var( $this->request->param( 'filters_extraction_parameters' ), FILTER_SANITIZE_STRING ); + $xliff_parameters = filter_var( $this->request->param( 'xliff_parameters' ), FILTER_SANITIZE_STRING ); + $filters_extraction_parameters_template_id = filter_var( $this->request->param( 'filters_extraction_parameters_template_id' ), FILTER_SANITIZE_NUMBER_INT ); + $xliff_parameters_template_id = filter_var( $this->request->param( 'xliff_parameters_template_id' ), FILTER_SANITIZE_NUMBER_INT ); + + /** + * ---------------------------------- + * Note 2022-10-13 + * ---------------------------------- + * + * We trim every space private_tm_key + * in order to avoid mispelling errors + * + */ + $instructions = $this->featureSet->filter( 'encodeInstructions', $instructions ?? null ); + + /** + * ---------------------------------- + * Note 2021-05-28 + * ---------------------------------- + * + * We trim every space private_tm_key + * in order to avoid mispelling errors + * + */ + $private_tm_key = preg_replace( "/\s+/", "", $private_tm_key ); + + if ( empty( $_FILES ) ) { + throw new InvalidArgumentException("Missing file. Not Sent."); + } + + $lang_handler = Langs_Languages::getInstance(); + + $source_lang = $this->validateSourceLang($lang_handler, $source_lang); + $target_lang = $this->validateTargetLangs($lang_handler, $target_lang); + [$tms_engine, $mt_engine] = $this->validateEngines($tms_engine, $mt_engine); + $subject = $this->validateSubject($subject); + $segmentation_rule = $this->validateSegmentationRules($segmentation_rule); + [$private_tm_user, $private_tm_pass, $private_tm_key, $new_keys] = $this->validateTmAndKeys($private_tm_key); + $team = $this->validateTeam($id_team); + $qaModelTemplate = $this->validateQaModelTemplate($id_qa_model_template); + $payableRateModelTemplate = $this->validatePayableRateTemplate($payable_rate_template_name, $payable_rate_template_id); + $qaModel = $this->validateQaModel($id_qa_model); + $mt_engine = $this->validateUserMTEngine($mt_engine); + $mmt_glossaries = $this->validateMMTGlossaries($mmt_glossaries); + $deepl_formality = $this->validateDeepLFormality($deepl_formality); + $dialect_strict = $this->validateDialectStrictParam($target_lang, $dialect_strict); + $filters_extraction_parameters = $this->validateFiltersExtractionParameters($filters_extraction_parameters, $filters_extraction_parameters_template_id); + $xliff_parameters = $this->validateXliffParameters($xliff_parameters, $xliff_parameters_template_id); + $project_features = $this->appendFeaturesToProject($project_completion); + $target_language_mt_engine_id = $this->generateTargetEngineAssociation($target_lang, $mt_engine, $target_language_mt_engine_id); + $metadata = $this->validateMetadataParam($metadata); + + if ( !empty( $project_info ) ) { + $metadata[ 'project_info' ] = $project_info; + } + + if ( !empty( $dialect_strict ) ) { + $metadata[ 'dialect_strict' ] = $dialect_strict; + } + + if ( !empty( $lexiqa ) ) { + $metadata[ 'lexiqa' ] = $lexiqa; + } + + if ( !empty( $speech2text ) ) { + $metadata[ 'speech2text' ] = $speech2text; + } + + if ( !empty( $tag_projection ) ) { + $metadata[ 'tag_projection' ] = $tag_projection; + } + + if ( !empty( $project_completion ) ) { + $metadata[ 'project_completion' ] = $project_completion; + } + + if ( !empty( $segmentation_rule ) ) { + $metadata[ 'segmentation_rule' ] = $segmentation_rule; + } + + return [ + 'project_info' => $project_info, + 'project_name' => $project_name, + 'source_lang' => $source_lang, + 'target_lang' => $target_lang, + 'subject' => $subject, + 'pretranslate_100' => $pretranslate_100, + 'pretranslate_101' => $pretranslate_101, + 'id_team' => $id_team, + 'team' => $team, + 'mmt_glossaries' => $mmt_glossaries, + 'deepl_id_glossary' => $deepl_id_glossary, + 'deepl_formality' => $deepl_formality, + 'project_completion' => $project_completion, + 'get_public_matches' => $get_public_matches, + 'dialect_strict' => $dialect_strict, + 'filters_extraction_parameters' => $filters_extraction_parameters, + 'xliff_parameters' => $xliff_parameters, + 'filters_extraction_parameters_template_id' => $filters_extraction_parameters_template_id, + 'qa_model_template_id' => $qa_model_template_id, + 'payable_rate_template_id' => $payable_rate_template_id, + 'target_language_mt_engine_id' => $target_language_mt_engine_id, + 'tms_engine' => $tms_engine, + 'mt_engine' => $mt_engine, + 'private_tm_key' => $private_tm_key, + 'private_tm_user' => $private_tm_user, + 'private_tm_pass' => $private_tm_pass, + 'new_keys' => $new_keys, + 'due_date' => $due_date, + 'project_features' => $project_features, + 'id_qa_model' => $id_qa_model, + 'qaModel' => $qaModel, + 'metadata' => $metadata, + 'segmentation_rule' => $segmentation_rule, + 'id_qa_model_template' => $id_qa_model_template, + 'qaModelTemplate' => $qaModelTemplate, + 'payableRateModelTemplate' => $payableRateModelTemplate, + 'instructions' => $instructions, + 'lexiqa' => $lexiqa, + 'speech2text' => $speech2text, + 'tag_projection' => $tag_projection, + ]; + } + + /** + * Expects the metadata param to be a json formatted string and tries to convert it + * in array. + * Json string is expected to be flat key value, this is enforced padding 1 to json + * conversion depth param. + * + * + * @throws Exception + */ + private function validateMetadataParam($metadata = null) + { + if ( !empty( $metadata ) ) { + + if ( strlen( $metadata ) > 2048 ) { + throw new InvalidArgumentException( 'metadata string is too long' ); + } + + $depth = 2; // only converts key value structures + $assoc = true; + $metadata = html_entity_decode( $metadata ); + $parsedMetadata = json_decode( $metadata, $assoc, $depth ); + + if ( is_array( $parsedMetadata ) ) { + $this->metadata = $parsedMetadata; + } + + Log::doJsonLog( "Passed parameter metadata as json string." ); + } else { + $metadata = []; + } + + // new raw counter model + $metadata[ Projects_MetadataDao::WORD_COUNT_TYPE_KEY ] = Projects_MetadataDao::WORD_COUNT_RAW; + + // @TODO still is use??? + $metadata = $this->featureSet->filter( 'filterProjectMetadata', $metadata, $_POST ); + $metadata = $this->featureSet->filter( 'createProjectAssignInputMetadata', $metadata, [ + 'input' => $_POST + ] ); + + return $metadata; + } + + /** + * @param $tms_engine + * @param $mt_engine + * @return array + * @throws Exception + */ + private function validateEngines($tms_engine, $mt_engine): array + { + if ( !isset( $tms_engine ) ) { + $tms_engine = 1; + } + + if ( !isset( $mt_engine ) ) { + $mt_engine = 1; + } + + if ( $tms_engine != 0 ) { + Engine::getInstance( $tms_engine ); + } + + if ( $mt_engine != 0 and $mt_engine != 1 ) { + if ( !$this->userIsLogged ) { + throw new InvalidArgumentException( "Invalid MT Engine.", -2 ); + } else { + $testEngine = Engine::getInstance( $mt_engine ); + if ( $testEngine->getEngineRow()->uid != $this->getUser()->uid ) { + throw new InvalidArgumentException( "Invalid MT Engine.", -21 ); + } + } + } + + return [$tms_engine, $mt_engine]; + } + + /** + * @param $subject + * @return string + */ + private function validateSubject($subject): string + { + $langDomains = Langs_LanguageDomains::getInstance(); + $subjectMap = $langDomains::getEnabledHashMap(); + + $subject = ( !empty( $subject ) ) ? $subject : 'general'; + + if ( empty( $subjectMap[ $subject ] ) ) { + throw new InvalidArgumentException( "Subject not allowed: " . $subject, -3 ); + } + + return $subject; + } + + /** + * @param Langs_Languages $lang_handler + * @param $source_lang + * @return string + */ + private function validateSourceLang( Langs_Languages $lang_handler, $source_lang ): string + { + try { + $lang_handler->validateLanguage( $source_lang ); + + return $source_lang; + } catch ( Exception $e ) { + throw new InvalidArgumentException("Missing source language."); + } + } + + /** + * @param Langs_Languages $lang_handler + * @param $target_lang + * @return string + */ + private function validateTargetLangs( Langs_Languages $lang_handler, $target_lang ): string + { + $targets = explode( ',', $target_lang ); + $targets = array_map( 'trim', $targets ); + $targets = array_unique( $targets ); + + if ( empty( $targets ) ) { + throw new InvalidArgumentException("Missing target language."); + } + + try { + foreach ( $targets as $target ) { + $lang_handler->validateLanguage( $target ); + } + } catch ( Exception $e ) { + throw new InvalidArgumentException($e->getMessage()); + } + + return implode( ',', $targets ); + } + + /** + * @param null $project_completion + * @return array + * @throws Exception + */ + private function appendFeaturesToProject($project_completion = null): array + { + $projectFeatures = []; + + if ( $project_completion ) { + $feature = new BasicFeatureStruct(); + $feature->feature_code = 'project_completion'; + $projectFeatures[ $feature->feature_code ] = $feature; + } + + $projectFeatures = $this->featureSet->filter( + 'filterCreateProjectFeatures', $projectFeatures, $this + ); + + return $projectFeatures; + } + + /** + * This could be already set by MMT engine if enabled ( so check key existence and do not override ) + * + * @param $target_lang + * @param $mt_engine + * @param null $target_language_mt_engine_id + * @return array + * @see filterCreateProjectFeatures callback + * @see NewController::appendFeaturesToProject() + */ + private function generateTargetEngineAssociation($target_lang, $mt_engine, $target_language_mt_engine_id = null): array + { + if ( !empty($target_language_mt_engine_id) ) { // this could be already set by MMT engine if enabled ( so check and do not override ) + foreach ( explode( ",", $target_lang ) as $_matecatTarget ) { + $target_language_mt_engine_id[ $_matecatTarget ] = $mt_engine; + } + } + + return []; + } + + /** + * @param $segmentation_rule + * @return string|null + * @throws Exception + */ + private function validateSegmentationRules($segmentation_rule): ?string + { + return Constants::validateSegmentationRules( $segmentation_rule ); + } + + /** + * @param $private_tm_key + * @return array + * @throws Exception + */ + protected function validateTmAndKeys($private_tm_key): array + { + $new_keys = []; + $private_tm_user = null; + $private_tm_pass = null; + + try { + $private_tm_key = $this->parseTmKeyInput($private_tm_key); + } catch ( Exception $e ) { + throw new InvalidArgumentException( $e->getMessage(), -6 ); + } + + if ( count($private_tm_key) > self::MAX_NUM_KEYS ) { + throw new InvalidArgumentException( "Too much keys provided. Max number of keys is " . self::MAX_NUM_KEYS, -2 ); + } + + $private_tm_key = array_values( array_filter( $private_tm_key ) ); + + //If a TMX file has been uploaded and no key was provided, create a new key. + if ( empty( $private_tm_key ) ) { + foreach ( $_FILES as $_fileinfo ) { + $pathinfo = AbstractFilesStorage::pathinfo_fix( $_fileinfo[ 'name' ] ); + if ( $pathinfo[ 'extension' ] == 'tmx' ) { + $private_tm_key[] = [ 'key' => 'new' ]; + break; + } + } + } + + //remove all empty entries + foreach ( $private_tm_key as $__key_idx => $tm_key ) { + //from api a key is sent and the value is 'new' + if ( $tm_key[ 'key' ] == 'new' ) { + + try { + $APIKeySrv = new TMSService(); + $newUser = $APIKeySrv->createMyMemoryKey(); + + $private_tm_user = $newUser->id; + $private_tm_pass = $newUser->pass; + + $private_tm_key[ $__key_idx ] = + [ + 'key' => $newUser->key, + 'name' => null, + 'r' => $tm_key[ 'r' ], + 'w' => $tm_key[ 'w' ] + + ]; + $new_keys[] = $newUser->key; + + } catch ( Exception $e ) { + throw new Exception( $e->getMessage(), -1 ); + } + + } //if a string is sent, transform it into a valid array + elseif ( !empty( $tm_key ) ) { + + $uid = $this->user->uid; + + $this_tm_key = [ + 'key' => $tm_key[ 'key' ], + 'name' => null, + 'r' => $tm_key[ 'r' ], + 'w' => $tm_key[ 'w' ] + ]; + + /** + * Get the key description/name from the user keyring + */ + if ( $uid ) { + $mkDao = new TmKeyManagement_MemoryKeyDao(); + + /** + * @var $keyRing TmKeyManagement_MemoryKeyStruct[] + */ + $keyRing = $mkDao->read( + ( new TmKeyManagement_MemoryKeyStruct( [ + 'uid' => $uid, + 'tm_key' => new TmKeyManagement_TmKeyStruct( $this_tm_key ) + ] ) + ) + ); + + if ( count( $keyRing ) > 0 ) { + $this_tm_key[ 'name' ] = $keyRing[ 0 ]->tm_key->name; + } + } + + $private_tm_key[ $__key_idx ] = $this_tm_key; + } + + $private_tm_key[ $__key_idx ] = $this->sanitizeTmKeyArr( $private_tm_key[ $__key_idx ] ); + } + + return [ + 'private_tm_user' => $private_tm_user, + 'private_tm_pass' => $private_tm_pass, + 'private_tm_key' => $private_tm_key, + 'new_keys' => $new_keys, + ]; + } + + /** + * @param $elem + * + * @return array + */ + private static function sanitizeTmKeyArr( $elem ): array + { + + $element = new TmKeyManagement_TmKeyStruct( $elem ); + $element->complete_format = true; + $elem = TmKeyManagement_TmKeyManagement::sanitize( $element ); + + return $elem->toArray(); + } + + /** + * @param null $id_team + * @return TeamStruct|null + * + * @throws Exception + */ + private function validateTeam($id_team = null): ?TeamStruct + { + if ( !empty( $id_team ) ) { + $dao = new MembershipDao(); + $org = $dao->findTeamByIdAndUser( $id_team, $this->user ); + + if ( !$org ) { + throw new Exception( 'Team and user membership does not match', -1 ); + } + + return $org; + } + + return $this->user->getPersonalTeam(); + } + + /** + * @param null $id_qa_model_template + * @return QAModelTemplateStruct|null + * @throws Exception + */ + private function validateQaModelTemplate($id_qa_model_template = null): ?QAModelTemplateStruct + { + if ( !empty( $id_qa_model_template ) ) { + $qaModelTemplate = QAModelTemplateDao::get( [ + 'id' => $id_qa_model_template, + 'uid' => $this->getUser()->uid + ] ); + + // check if qa_model template exists + if ( null === $qaModelTemplate ) { + throw new InvalidArgumentException( 'This QA Model template does not exists or does not belongs to the logged in user' ); + } + + return $qaModelTemplate; + } + + return null; + } + + /** + * @param $payable_rate_template_name + * @param $payable_rate_template_id + * @return CustomPayableRateStruct|null + * @throws Exception + */ + private function validatePayableRateTemplate($payable_rate_template_name = null, $payable_rate_template_id = null): ?CustomPayableRateStruct + { + $payableRateModelTemplate = null; + + if ( !empty( $payable_rate_template_name ) ) { + if ( empty( $payable_rate_template_id ) ) { + throw new InvalidArgumentException( '`payable_rate_template_id` param is missing' ); + } + } + + if ( !empty( $payable_rate_template_id ) ) { + if ( empty( $payable_rate_template_name ) ) { + throw new InvalidArgumentException( '`payable_rate_template_name` param is missing' ); + } + } + + if ( !empty( $payable_rate_template_name ) and !empty( $payable_rate_template_id ) ) { + + $userId = $this->getUser()->uid; + $payableRateModelTemplate = CustomPayableRateDao::getByIdAndUser( $payable_rate_template_id, $userId ); + + if ( null === $payableRateModelTemplate ) { + throw new InvalidArgumentException( 'Payable rate model id not valid' ); + } + + if ( $payableRateModelTemplate->name !== $payable_rate_template_name ) { + throw new InvalidArgumentException( 'Payable rate model name not matching' ); + } + } + + return $payableRateModelTemplate; + } + + /** + * Checks if id_qa_model is valid + * + * @param null $id_qa_model + * @return \LQA\ModelStruct|null + * @throws Exception + */ + private function validateQaModel($id_qa_model = null): ?\LQA\ModelStruct + { + if ( !empty( $id_qa_model ) ) { + + $qaModel = ModelDao::findById( $id_qa_model ); + + // check if qa_model exists + if ( null === $qaModel ) { + throw new InvalidArgumentException( 'This QA Model does not exists' ); + } + + // check featureSet + $qaModelLabel = strtolower( $qaModel->label ); + $featureSetCodes = $this->getFeatureSet()->getCodes(); + + if ( $qaModelLabel !== 'default' and !in_array( $qaModelLabel, $featureSetCodes ) ) { + throw new InvalidArgumentException( 'This QA Model does not belong to the authenticated user' ); + } + + return $qaModel; + } + + return null; + } + + /** + * @param null $mt_engine + * @return string|null + * @throws Exception + */ + private function validateUserMTEngine($mt_engine = null): ?string + { + // any other engine than MyMemory + if ( $mt_engine !== null and $mt_engine > 1 ) { + try { + EngineValidator::engineBelongsToUser( $mt_engine, $this->user->uid ); + } catch (Exception $exception){ + throw new InvalidArgumentException( $exception->getMessage() ); + } + } + + return $mt_engine; + } + + /** + * @param null $mmt_glossaries + * @return string|null + */ + private function validateMMTGlossaries($mmt_glossaries = null): ?string + { + if ( !empty( $mmt_glossaries ) ) { + try { + $mmtGlossaries = html_entity_decode( $mmt_glossaries); + MMTValidator::validateGlossary( $mmtGlossaries ); + + return $mmtGlossaries; + } catch (Exception $exception){ + throw new InvalidArgumentException( $exception->getMessage() ); + } + } + + return null; + } + + /** + * Validate DeepL params + * @param null $deepl_formality + * @return null + */ + private function validateDeepLFormality($deepl_formality = null): ?string + { + + if ( !empty( $deepl_formality ) ) { + + $allowedFormalities = [ + 'default', + 'prefer_less', + 'prefer_more' + ]; + + if ( in_array( $deepl_formality, $allowedFormalities ) ) { + throw new InvalidArgumentException( "Incorrect DeepL formality value (default, prefer_less and prefer_more are the allowed values)" ); + } + + return $deepl_formality; + } + + return null; + } + + /** + * Validate `dialect_strict` param vs target languages + * + * Example: {"it-IT": true, "en-US": false, "fr-FR": false} + * + * @param $target_lang + * @param null $dialect_strict + * @return string|null + */ + private function validateDialectStrictParam($target_lang, $dialect_strict = null): ?string + { + if ( !empty( $dialect_strict ) ) { + + $dialect_strict = trim( html_entity_decode( $dialect_strict ) ); + $target_languages = preg_replace( '/\s+/', '', $target_lang ); + $targets = explode( ',', trim( $target_languages ) ); + + // first check if `dialect_strict` is a valid JSON + if ( !Utils::isJson( $dialect_strict ) ) { + throw new InvalidArgumentException( "dialect_strict is not a valid JSON" ); + } + + $dialectStrictObj = json_decode( $dialect_strict, true ); + + foreach ( $dialectStrictObj as $lang => $value ) { + if ( !in_array( $lang, $targets ) ) { + throw new InvalidArgumentException( 'Wrong `dialect_strict` object, language, ' . $lang . ' is not one of the project target languages' ); + } + + if ( !is_bool( $value ) ) { + throw new InvalidArgumentException( 'Wrong `dialect_strict` object, not boolean declared value for ' . $lang ); + } + } + + return html_entity_decode( $dialect_strict ); + } + + return null; + } + + /** + * @param null $filters_extraction_parameters + * @param null $filters_extraction_parameters_template_id + * @return \Filters\FiltersConfigTemplateStruct|mixed|null + * @throws Exception + */ + private function validateFiltersExtractionParameters($filters_extraction_parameters = null, $filters_extraction_parameters_template_id = null) + { + if ( !empty( $filters_extraction_parameters ) ) { + + $json = html_entity_decode( $filters_extraction_parameters ); + + // first check if `filters_extraction_parameters` is a valid JSON + if ( !Utils::isJson( $json ) ) { + throw new InvalidArgumentException( "filters_extraction_parameters is not a valid JSON" ); + } + + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/filters_extraction_parameters.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $json; + + $validator = new JSONValidator( $schema ); + $validator->validate( $validatorObject ); + + return json_decode( $json ); + + } + + if ( !empty( $filters_extraction_parameters_template_id ) ) { + + $filtersTemplate = FiltersConfigTemplateDao::getByIdAndUser($filters_extraction_parameters_template_id, $this->getUser()->uid ); + + if ( $filtersTemplate === null ) { + throw new InvalidArgumentException( "filters_extraction_parameters_template_id not valid" ); + } + + return $filtersTemplate; + } + + return null; + } + + /** + * @param null $xliff_parameters + * @param null $xliff_parameters_template_id + * @return array|mixed|null + * @throws Exception + */ + private function validateXliffParameters($xliff_parameters = null, $xliff_parameters_template_id = null) + { + if ( !empty( $xliff_parameters ) ) { + + $json = html_entity_decode( $xliff_parameters ); + + // first check if `xliff_parameters` is a valid JSON + if ( !Utils::isJson( $json ) ) { + throw new InvalidArgumentException( "xliff_parameters is not a valid JSON" ); + } + + $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/xliff_parameters_rules_content.json' ); + + $validatorObject = new JSONValidatorObject(); + $validatorObject->json = $json; + + $validator = new JSONValidator( $schema, true ); + $validator->validate( $validatorObject ); + + return json_decode( $json, true ); // decode again because we need an associative array and not stdClass + } + + if ( !empty( $xliff_parameters_template_id ) ) { + + $xliffConfigTemplate = XliffConfigTemplateDao::getByIdAndUser( $xliff_parameters_template_id, $this->getUser()->uid ); + + if ( $xliffConfigTemplate === null ) { + throw new InvalidArgumentException( "xliff_parameters_template_id not valid" ); + } + + return $xliffConfigTemplate->rules->getArrayCopy(); + } + + return null; + } + + /** + * @param $tmKeyString + * @return array|null + * @throws Exception + */ + private function parseTmKeyInput( $tmKeyString ): ?array + { + $tmKeyString = trim( $tmKeyString ); + $tmKeyInfo = explode( ":", $tmKeyString ); + $read = true; + $write = true; + + $permissionString = $tmKeyInfo[ 1 ] ?? null; + + //if the key is not set, return null. It will be filtered in the next lines. + if ( empty( $tmKeyInfo[ 0 ] ) ) { + return null; + } //if permissions are set, check if they are allowed or not and eventually set permissions + + //permission string check + switch ( $permissionString ) { + case 'r': + $write = false; + break; + case 'w': + $read = false; + break; + case 'rw': + case '' : + case null: + break; + //permission string not allowed + default: + $allowed_permissions = implode( ", ", Constants_TmKeyPermissions::$_accepted_grants ); + throw new Exception( "Permission modifier string not allowed. Allowed: , $allowed_permissions" ); + break; + } + + return [ + 'key' => $tmKeyInfo[ 0 ], + 'r' => $read, + 'w' => $write, + ]; + } + + /** + * @param $filename + * + * @return array + * @throws Exception + */ + private function getFileMetadata( $filename ): array + { + $info = XliffProprietaryDetect::getInfo( $filename ); + $isXliff = XliffFiles::isXliff( $filename ); + $isGlossary = XliffFiles::isGlossaryFile( $filename ); + $isTMX = XliffFiles::isTMXFile( $filename ); + $getMemoryType = XliffFiles::getMemoryFileType( $filename ); + + $forceXliff = $this->getFeatureSet()->filter( + 'forceXLIFFConversion', + INIT::$FORCE_XLIFF_CONVERSION, + $this->userIsLogged, + $info[ 'info' ][ 'dirname' ] . DIRECTORY_SEPARATOR . "$filename" + ); + $mustBeConverted = XliffProprietaryDetect::fileMustBeConverted( $filename, $forceXliff, INIT::$FILTERS_ADDRESS ); + + $metadata = []; + $metadata[ 'basename' ] = $info[ 'info' ][ 'basename' ]; + $metadata[ 'dirname' ] = $info[ 'info' ][ 'dirname' ]; + $metadata[ 'extension' ] = $info[ 'info' ][ 'extension' ]; + $metadata[ 'filename' ] = $info[ 'info' ][ 'filename' ]; + $metadata[ 'mustBeConverted' ] = $mustBeConverted; + $metadata[ 'getMemoryType' ] = $getMemoryType; + $metadata[ 'isXliff' ] = $isXliff; + $metadata[ 'isGlossary' ] = $isGlossary; + $metadata[ 'isTMX' ] = $isTMX; + $metadata[ 'proprietary' ] = [ + 'proprietary' => $info[ 'proprietary' ], + 'proprietary_name' => $info[ 'proprietary_name' ], + 'proprietary_short_name' => $info[ 'proprietary_short_name' ], + ]; + + return $metadata; + } +} \ No newline at end of file diff --git a/lib/Controller/AbstractControllers/ajaxController.php b/lib/Controller/AbstractControllers/ajaxController.php deleted file mode 100644 index 893a80910e..0000000000 --- a/lib/Controller/AbstractControllers/ajaxController.php +++ /dev/null @@ -1,123 +0,0 @@ - [], "data" => [] ]; - protected $id_segment; - - protected $split_num = null; - - /** - * Class constructor, initialize the header content type. - * @throws ReflectionException - */ - protected function __construct() { - - $this->identifyUser(); - $this->startTimer(); - - $buffer = ob_get_contents(); - ob_get_clean(); - // ob_start("ob_gzhandler"); // compress page before sending //Not supported for json response on ajax calls - header( 'Content-Type: application/json; charset=utf-8' ); - - - if ( !Bootstrap::areMandatoryKeysPresent() ) { - $output = INIT::$CONFIG_VERSION_ERR_MESSAGE; - $this->result = [ "errors" => [ [ "code" => -1000, "message" => $output ] ], "data" => [] ]; - $this->api_output = [ "errors" => [ [ "code" => -1000, "message" => $output ] ], "data" => [] ]; - Log::doJsonLog( "Error: " . INIT::$CONFIG_VERSION_ERR_MESSAGE ); - $this->finalize(); - exit; - } - - $this->featureSet = new FeatureSet(); - - $filterArgs = array( - 'current_password' => array( - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - ); - - $__postInput = (object)filter_input_array( INPUT_POST, $filterArgs ); - - if( isset( $__postInput->current_password) ){ - $this->received_password = $__postInput->current_password; - } - } - - /** - * Call the output in JSON format - * - */ - public function finalize() { - $toJson = json_encode( $this->result ); - - if (!ob_get_status()) { - ob_start(); - } - - echo $toJson; - - $this->logPageCall(); - - } - - /** - * @return bool - */ - public static function isRevision(): bool { - - $controller = static::getInstance(); - - if (isset($controller->id_job) and isset($controller->received_password)){ - $jid = $controller->id_job; - $password = $controller->received_password; - $isRevision = CatUtils::getIsRevisionFromIdJobAndPassword( $jid, $password ); - - if ( null === $isRevision ) { - $isRevision = CatUtils::getIsRevisionFromReferer(); - } - - return $isRevision; - } - - return CatUtils::getIsRevisionFromReferer(); - } - - public function parseIDSegment() { - @[ $this->id_segment, $this->split_num ] = explode( "-", $this->id_segment ); - } -} diff --git a/lib/Controller/Features/ProjectCompletion/SetChunkCompletedController.php b/lib/Controller/Features/ProjectCompletion/SetChunkCompletedController.php deleted file mode 100644 index 98166955ca..0000000000 --- a/lib/Controller/Features/ProjectCompletion/SetChunkCompletedController.php +++ /dev/null @@ -1,86 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'current_password' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_job = $this->__postInput[ 'id_job' ]; - $this->password = $this->__postInput[ 'password' ]; - $this->received_password = $this->__postInput[ 'current_password' ]; - } - - public function doAction() { - $this->chunk = Chunks_ChunkDao::getByIdAndPassword( - $this->id_job, - $this->password - ); - - if ( $this->chunk ) { - $this->processInsert(); - } else { - $this->result[ 'error' ] = 'record not found'; - } - - } - - private function processInsert() { - $struct = new CompletionEventStruct( [ - 'uid' => $this->getUid(), - 'remote_ip_address' => Utils::getRealIpAddr(), - 'source' => Chunks_ChunkCompletionEventStruct::SOURCE_USER, - 'is_review' => $this->isRevision() - ] ); - - $model = new EventModel( $this->chunk, $struct ); - $model->save(); - - $this->result[ 'data' ] = [ - 'event' => [ - 'id' => (int)$model->getChunkCompletionEventId() - ] - ]; - } - - private function getUid() { - $this->identifyUser(); - if ( $this->userIsLogged ) { - return $this->user->uid; - } else { - return null; - } - } - -} diff --git a/lib/Controller/ajaxUtilsController.php b/lib/Controller/ajaxUtilsController.php deleted file mode 100644 index 34d0293377..0000000000 --- a/lib/Controller/ajaxUtilsController.php +++ /dev/null @@ -1,75 +0,0 @@ - &$value ) { - $value = filter_var( $value, FILTER_SANITIZE_STRING, [ 'flags' => FILTER_FLAG_STRIP_LOW ] ); - } - - $this->__postInput = $posts; - - } - - public function doAction() { - - switch ( $this->__postInput[ 'exec' ] ) { - - case 'ping': - $db = Database::obtain(); - $stmt = $db->getConnection()->prepare( "SELECT 1" ); - $stmt->execute(); - $this->result[ 'data' ] = [ "OK", time() ]; - break; - case 'checkTMKey': - //get MyMemory apiKey service - - $tmxHandler = new TMSService(); - - //validate the key - try { - $keyExists = $tmxHandler->checkCorrectKey( $this->__postInput[ 'tm_key' ] ); - } catch ( Exception $e ) { - /* PROVIDED KEY IS NOT VALID OR WRONG, $keyExists IS NOT SET */ - Log::doJsonLog( $e->getMessage() ); - } - - if ( !isset( $keyExists ) || $keyExists === false ) { - $this->result[ 'errors' ][] = [ "code" => -9, "message" => "TM key is not valid." ]; - Log::doJsonLog( __METHOD__ . " -> TM key is not valid." ); - $this->result[ 'success' ] = false; - } else { - $this->result[ 'errors' ] = []; - $this->result[ 'success' ] = true; - } - - break; - case 'clearNotCompletedUploads': - try { - ( new Session() )->cleanupSessionFiles(); - } catch ( Exception $e ) { - Log::doJsonLog( "ajaxUtils::clearNotCompletedUploads : " . $e->getMessage() ); - } - break; - - } - - } -} diff --git a/lib/Controller/changeJobsStatusController.php b/lib/Controller/changeJobsStatusController.php deleted file mode 100644 index fb5905fdef..0000000000 --- a/lib/Controller/changeJobsStatusController.php +++ /dev/null @@ -1,95 +0,0 @@ - [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'id' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'new_status' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'pn' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - - ); - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - ( !empty( $postInput[ 'password' ] ) ? $this->password = $postInput[ 'password' ] : null ); - - $this->res_type = $postInput[ 'res' ]; - $this->res_id = $postInput[ 'id' ]; - - if ( Constants_JobStatus::isAllowedStatus( $postInput[ 'new_status' ] ) ) { - $this->new_status = $postInput[ 'new_status' ]; - } else { - throw new Exception( "Invalid Status" ); - } - - } - - public function doAction() { - - if ( ! $this->userIsLogged ) { - throw new Exception( "User Not Logged." ); - } - - if ( $this->res_type == "prj" ) { - - try { - $project = Projects_ProjectDao::findByIdAndPassword( $this->res_id, $this->password ); - } catch( Exception $e ){ - $msg = "Error : wrong password provided for Change Project Status \n\n " . var_export( $_POST, true ) . "\n"; - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - return null; - } - - $chunks = $project->getJobs(); - - Jobs_JobDao::updateAllJobsStatusesByProjectId( $project->id, $this->new_status ); - - foreach( $chunks as $chunk ){ - $lastSegmentsList = Translations_SegmentTranslationDao::getMaxSegmentIdsFromJob( $chunk ); - Translations_SegmentTranslationDao::updateLastTranslationDateByIdList( $lastSegmentsList, Utils::mysqlTimestamp( time() ) ); - } - - } else { - - try { - $firstChunk = Chunks_ChunkDao::getByIdAndPassword( $this->res_id, $this->password ); - } catch( Exception $e ){ - $msg = "Error : wrong password provided for Change Job Status \n\n " . var_export( $_POST, true ) . "\n"; - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - return null; - } - - Jobs_JobDao::updateJobStatus( $firstChunk, $this->new_status ); - $lastSegmentsList = Translations_SegmentTranslationDao::getMaxSegmentIdsFromJob( $firstChunk ); - Translations_SegmentTranslationDao::updateLastTranslationDateByIdList( $lastSegmentsList, Utils::mysqlTimestamp( time() ) ); - - } - - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = "OK"; - $this->result[ 'status' ] = $this->new_status; - - } - -} diff --git a/lib/Controller/commentController.php b/lib/Controller/commentController.php deleted file mode 100644 index c06d8c20dd..0000000000 --- a/lib/Controller/commentController.php +++ /dev/null @@ -1,537 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_STRING ], - 'id_client' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'username' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'source_page' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'is_anonymous' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'revision_number' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'message' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_comment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); - $this->__postInput[ 'message' ] = htmlspecialchars( $this->__postInput[ 'message' ] ); - - $this->__postInput[ 'id_comment' ] = (int)$this->__postInput[ 'id_comment' ]; - $this->__postInput[ 'id_segment' ] = (int)$this->__postInput[ 'id_segment' ]; - $this->__postInput[ 'id_job' ] = (int)$this->__postInput[ 'id_job' ]; - $this->__postInput[ 'source_page' ] = (int)$this->__postInput[ 'source_page' ]; - $this->__postInput[ 'revision_number' ] = (int)$this->__postInput[ 'revision_number' ]; - $this->__postInput[ 'source_page' ] = (int)$this->__postInput[ 'source_page' ]; - - } - - /** - * @throws Exception - */ - public function doAction() { - - $this->job = Jobs_JobDao::getByIdAndPassword( $this->__postInput[ 'id_job' ], $this->__postInput[ 'password' ], 60 * 60 * 24 ); - - if ( empty( $this->job ) ) { - $this->result[ 'errors' ][] = [ "code" => -10, "message" => "wrong password" ]; - - return; - } - - $this->route(); - - } - - /** - * @throws ReflectionException - */ - private function route(): void { - switch ( $this->__postInput[ '_sub' ] ) { - case 'getRange': - $this->getRange(); - break; - case 'resolve': - $this->resolve(); - break; - case 'create': - $this->create(); - break; - case 'delete': - $this->delete(); - break; - default: - $this->result[ 'errors' ][] = [ - "code" => -127, "message" => "Unable to route action." - ]; - } - } - - private function getRange(): void { - $this->comment_struct = new Comments_CommentStruct(); - $this->comment_struct->id_job = $this->__postInput[ 'id_job' ]; - - $commentDao = new Comments_CommentDao( Database::obtain() ); - - $this->result[ 'data' ][ 'entries' ] = [ - 'comments' => $commentDao->getCommentsForChunk( $this->job ) - ]; - $this->appendUser(); - } - - /** - * @throws ReflectionException - * @throws Exception - */ - private function resolve(): void { - $this->prepareCommentData(); - - $commentDao = new Comments_CommentDao( Database::obtain() ); - $this->new_record = $commentDao->resolveThread( $this->comment_struct ); - - $this->enqueueComment(); - $this->users = $this->resolveUsers(); - $this->sendEmail(); - $this->result[ 'data' ][ 'entries' ][ 'comments' ][] = $this->payload; - } - - private function appendUser() { - if ( $this->userIsLogged ) { - $this->result[ 'data' ][ 'user' ] = [ - 'full_name' => $this->user->fullName() - ]; - } - } - - /** - * @throws ReflectionException - * @throws Exception - */ - private function create(): void { - $this->prepareCommentData(); - - $commentDao = new Comments_CommentDao( Database::obtain() ); - $this->new_record = $commentDao->saveComment( $this->comment_struct ); - - foreach ( $this->users_mentioned as $user_mentioned ) { - $mentioned_comment = $this->prepareMentionCommentData( $user_mentioned ); - $commentDao->saveComment( $mentioned_comment ); - } - - $this->enqueueComment(); - $this->users = $this->resolveUsers(); - $this->sendEmail(); - $this->result[ 'data' ][ 'entries' ][ 'comments' ][] = $this->new_record; - $this->appendUser(); - } - - /** - * @throws ReflectionException - */ - private function prepareCommentData(): void { - $this->comment_struct = new Comments_CommentStruct(); - - $this->comment_struct->id_segment = $this->__postInput[ 'id_segment' ]; - $this->comment_struct->id_job = $this->__postInput[ 'id_job' ]; - $this->comment_struct->full_name = $this->__postInput[ 'username' ]; - $this->comment_struct->source_page = $this->__postInput[ 'source_page' ]; - $this->comment_struct->message = $this->__postInput[ 'message' ]; - $this->comment_struct->revision_number = $this->__postInput[ 'revision_number' ]; - $this->comment_struct->is_anonymous = $this->__postInput[ 'is_anonymous' ]; - $this->comment_struct->email = $this->getEmail(); - $this->comment_struct->uid = $this->getUid(); - - $user_mentions = $this->resolveUserMentions(); - $user_team_mentions = $this->resolveTeamMentions(); - $userDao = new Users_UserDao( Database::obtain() ); - $this->users_mentioned_id = array_unique( array_merge( $user_mentions, $user_team_mentions ) ); - - $this->users_mentioned = $this->filterUsers( $userDao->getByUids( $this->users_mentioned_id ) ); - } - - private function prepareMentionCommentData( Users_UserStruct $user ): Comments_CommentStruct { - $struct = new Comments_CommentStruct(); - - $struct->id_segment = $this->__postInput[ 'id_segment' ]; - $struct->id_job = $this->__postInput[ 'id_job' ]; - $struct->full_name = $user->fullName(); - $struct->source_page = $this->__postInput[ 'source_page' ]; - $struct->message = ""; - $struct->message_type = Comments_CommentDao::TYPE_MENTION; - $struct->email = $user->getEmail(); - $struct->uid = $user->getUid(); - - return $struct; - } - - /** - * Permanently delete a comment - * @throws ReflectionException - */ - private function delete(): void { - - if ( !$this->isLoggedIn() ) { - $this->result[ 'errors' ][] = [ - "code" => -201, - "message" => "You MUST log in to delete a comment." - ]; - - return; - } - - if ( !isset( $this->__postInput[ 'id_comment' ] ) ) { - $this->result[ 'errors' ][] = [ - "code" => -200, - "message" => "Id comment not provided." - ]; - - return; - } - - $idComment = $this->__postInput[ 'id_comment' ]; - $commentDao = new Comments_CommentDao( Database::obtain() ); - $comment = $commentDao->getById( $idComment ); - - if ( null === $comment ) { - $this->result[ 'errors' ][] = [ - "code" => -202, - "message" => "Comment not found." - ]; - - return; - } - - if ( $comment->uid === null ) { - $this->result[ 'errors' ][] = [ - "code" => -203, - "message" => "Anonymous comments cannot be deleted." - ]; - - return; - } - - if ( $comment->uid !== $this->user->uid ) { - $this->result[ 'errors' ][] = [ - "code" => -203, - "message" => "You are not the author of the comment." - ]; - - return; - } - - if ( $comment->id_segment !== $this->__postInput[ 'id_segment' ] ) { - $this->result[ 'errors' ][] = [ - "code" => -204, - "message" => "Not corresponding id segment." - ]; - - return; - } - - $segments = $commentDao->getBySegmentId( $comment->id_segment ); - $lastSegment = end( $segments ); - - if ( $lastSegment->id !== $this->__postInput[ 'id_comment' ] ) { - $this->result[ 'errors' ][] = [ - "code" => -205, - "message" => "Only the last element comment can be deleted." - ]; - - return; - } - - if ( $comment->id_job !== $this->__postInput[ 'id_job' ] ) { - $this->result[ 'errors' ][] = [ - "code" => -206, - "message" => "Not corresponding id job." - ]; - - return; - } - - // Fix for R2 - // The comments from R2 phase are wrongly saved with source_page = 2 - $sourcePage = Utils::getSourcePageFromReferer(); - - $allowedSourcePages = []; - $allowedSourcePages[] = $this->__postInput[ 'source_page' ]; - - if ( $sourcePage == 3 ) { - $allowedSourcePages[] = 2; - } - - if ( !in_array( $comment->source_page, $allowedSourcePages ) ) { - $this->result[ 'errors' ][] = [ - "code" => -207, - "message" => "Not corresponding source_page." - ]; - - return; - } - - if ( $commentDao->deleteComment( $comment->toCommentStruct() ) ) { - - $this->enqueueDeleteCommentMessage( $comment->id, $comment->id_segment, $this->__postInput[ 'source_page' ] ); - - $this->result[ 'data' ][] = [ - "id" => $comment->id - ]; - $this->appendUser(); - - return; - } - - $this->result[ 'errors' ][] = [ - "code" => -220, - "message" => "Error when deleting a comment." - ]; - } - - /** - * @throws Exception - */ - private function sendEmail() { - - $jobUrlStruct = JobUrlBuilder::createFromJobStruct( $this->job, [ - 'id_segment' => $this->comment_struct->id_segment, - 'skip_check_segment' => true - ] ); - - $url = $jobUrlStruct->getUrlByRevisionNumber( $this->comment_struct->revision_number ); - - if ( !$url ) { - $this->result[ 'errors' ][] = [ "code" => -10, "message" => "No valid url was found for this project." ]; - - return; - } - - Log::doJsonLog( $url ); - $project_data = $this->projectData(); - - foreach ( $this->users_mentioned as $user_mentioned ) { - $email = new CommentMentionEmail( $user_mentioned, $this->comment_struct, $url, $project_data[ 0 ], $this->job ); - $email->send(); - } - - foreach ( $this->users as $user ) { - if ( $this->comment_struct->message_type == Comments_CommentDao::TYPE_RESOLVE ) { - $email = new CommentResolveEmail( $user, $this->comment_struct, $url, $project_data[ 0 ], $this->job ); - } else { - $email = new CommentEmail( $user, $this->comment_struct, $url, $project_data[ 0 ], $this->job ); - } - - $email->send(); - } - } - - /** - * @throws ReflectionException - */ - private function projectData(): array { - if ( $this->project_data == null ) { - - // FIXME: this is not optimal, should return just one record, not an array of records. - /** - * @var $projectData ShapelessConcreteStruct[] - */ - $this->project_data = ( new Projects_ProjectDao() )->setCacheTTL( 60 * 60 )->getProjectData( $this->job[ 'id_project' ] ); - - } - - return $this->project_data; - } - - /** - * @throws ReflectionException - */ - private function resolveUsers(): array { - $commentDao = new Comments_CommentDao( Database::obtain() ); - $result = $commentDao->getThreadContributorUids( $this->comment_struct ); - - $userDao = new Users_UserDao( Database::obtain() ); - $users = $userDao->getByUids( $result ); - $userDao->setCacheTTL( 60 * 60 * 24 ); - $owner = $userDao->getProjectOwner( $this->job[ 'id' ] ); - - if ( !empty( $owner->uid ) && !empty( $owner->email ) ) { - $users[] = $owner; - } - - $userDao->setCacheTTL( 60 * 10 ); - $assignee = $userDao->getProjectAssignee( $this->job[ 'id_project' ] ); - if ( !empty( $assignee->uid ) && !empty( $assignee->email ) ) { - $users[] = $assignee; - } - - return $this->filterUsers( $users, $this->users_mentioned_id ); - - } - - /** - * @return int[] - */ - private function resolveUserMentions(): array { - return Comments_CommentDao::getUsersIdFromContent( $this->comment_struct->message ); - } - - /** - * @return int[] - * @throws ReflectionException - */ - private function resolveTeamMentions(): array { - $users = []; - - if ( strstr( $this->comment_struct->message, "{@team@}" ) ) { - $project = $this->job->getProject(); - $memberships = ( new MembershipDao() )->setCacheTTL( 60 * 60 * 24 )->getMemberListByTeamId( $project->id_team, false ); - foreach ( $memberships as $membership ) { - $users[] = $membership->uid; - } - } - - return $users; - } - - /** - * @param Users_UserStruct[] $users - * @param array $uidSentList - * - * @return array - */ - private function filterUsers( array $users, array $uidSentList = [] ): array { - $userIsLogged = $this->userIsLogged; - $current_uid = $this->user ? $this->user->uid : 0; - - // find deep duplicates - return array_filter( $users, function ( $item ) use ( $userIsLogged, $current_uid, &$uidSentList ) { - if ( $userIsLogged && $current_uid == $item->uid ) { - return false; - } - - // find deep duplicates - if ( in_array( $item->uid, $uidSentList ) ) { - return false; - } - $uidSentList[] = $item->uid; - - return true; - - } ); - } - - private function getEmail(): ?string { - if ( $this->userIsLogged ) { - return $this->user->email; - } else { - return null; - } - } - - private function getUid(): ?int { - if ( $this->userIsLogged ) { - return $this->user->uid; - } else { - return null; - } - } - - /** - * @param int $id - * @param int $idSegment - * @param int $sourcePage - * - * @throws ReflectionException - */ - private function enqueueDeleteCommentMessage( int $id, int $idSegment, int $sourcePage ) { - $message = json_encode( [ - '_type' => 'comment', - 'data' => [ - 'id_job' => (int)$this->__postInput[ 'id_job' ], - 'passwords' => $this->getProjectPasswords(), - 'id_client' => $this->__postInput[ 'id_client' ], - 'payload' => [ - 'message_type' => 2, - 'id' => $id, - 'id_segment' => $idSegment, - 'source_page' => $sourcePage, - ] - ] - ] ); - - $queueHandler = new AMQHandler(); - $queueHandler->publishToTopic( INIT::$SSE_NOTIFICATIONS_QUEUE_NAME, new Message( $message ) ); - - } - - /** - * @throws ReflectionException - */ - private function enqueueComment() { - - $message = json_encode( [ - '_type' => 'comment', - 'data' => [ - 'id_job' => $this->__postInput[ 'id_job' ], - 'passwords' => $this->getProjectPasswords(), - 'id_client' => $this->__postInput[ 'id_client' ], - 'payload' => $this->new_record - ] - ] ); - - $queueHandler = new AMQHandler(); - $queueHandler->publishToTopic( INIT::$SSE_NOTIFICATIONS_QUEUE_NAME, new Message( $message ) ); - - } - - /** - * @return string[] - * @throws ReflectionException - */ - private function getProjectPasswords(): array { - $pws = []; - foreach ( $this->projectData() as $chunk ) { - $pws[] = $chunk[ 'jpassword' ]; - } - - return $pws; - } - -} diff --git a/lib/Controller/convertFileController.php b/lib/Controller/convertFileController.php deleted file mode 100644 index b1f96a473c..0000000000 --- a/lib/Controller/convertFileController.php +++ /dev/null @@ -1,292 +0,0 @@ - [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW // | FILTER_FLAG_STRIP_HIGH - ], - 'source_lang' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'target_lang' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'segmentation_rule' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'filters_extraction_parameters_template_id' => [ - 'filter' => FILTER_SANITIZE_NUMBER_INT - ] - ]; - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->file_name = $postInput[ 'file_name' ]; - $this->source_lang = $postInput[ "source_lang" ]; - $this->target_lang = $postInput[ "target_lang" ]; - $this->segmentation_rule = $postInput[ "segmentation_rule" ]; - $this->filters_extraction_parameters_template_id = (int)$postInput[ "filters_extraction_parameters_template_id" ]; - - $this->cookieDir = $_COOKIE[ 'upload_token' ]; - $this->intDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $this->cookieDir; - $this->errDir = INIT::$STORAGE_DIR . DIRECTORY_SEPARATOR . 'conversion_errors' . DIRECTORY_SEPARATOR . $this->cookieDir; - - $this->identifyUser(); - - $this->files_storage = FilesStorageFactory::create(); - } - - /** - * @throws ReflectionException - * @throws Exception - */ - public function doAction() { - - $this->result = new ConvertedFileModel(); - - $this->lang_handler = Languages::getInstance(); - $this->validateSourceLang(); - $this->validateTargetLangs(); - $this->validateFiltersExtractionParametersTemplateId(); - - try { - $this->segmentation_rule = Constants::validateSegmentationRules( $this->segmentation_rule ); - } catch ( Exception $e ) { - $this->result->changeCode( ConversionHandlerStatus::INVALID_SEGMENTATION_RULE ); - $this->result->addError( $e->getMessage() ); - - return false; - } - - if ( !Utils::isTokenValid( $this->cookieDir ) ) { - $this->result->changeCode( ConversionHandlerStatus::INVALID_TOKEN ); - $this->result->addError( "Invalid Upload Token." ); - - return false; - } - - if ( !Utils::isValidFileName( $this->file_name ) || empty( $this->file_name ) ) { - $this->result->changeCode( ConversionHandlerStatus::INVALID_FILE ); - $this->result->addError( "Invalid File." ); - - return false; - } - - if ( $this->result->hasErrors() ) { - return false; - } - - if ( $this->isLoggedIn() ) { - $this->featureSet->loadFromUserEmail( $this->user->email ); - } - - $ext = AbstractFilesStorage::pathinfo_fix( $this->file_name, PATHINFO_EXTENSION ); - - $conversionHandler = new ConversionHandler(); - $conversionHandler->setFileName( $this->file_name ); - $conversionHandler->setSourceLang( $this->source_lang ); - $conversionHandler->setTargetLang( $this->target_lang ); - $conversionHandler->setSegmentationRule( $this->segmentation_rule ); - $conversionHandler->setCookieDir( $this->cookieDir ); - $conversionHandler->setIntDir( $this->intDir ); - $conversionHandler->setErrDir( $this->errDir ); - $conversionHandler->setFeatures( $this->featureSet ); - $conversionHandler->setUserIsLogged( $this->userIsLogged ); - $conversionHandler->setFiltersExtractionParameters( $this->filters_extraction_parameters ); - - if ( $ext == "zip" ) { - if ( $this->convertZipFile ) { - $this->handleZip( $conversionHandler ); - } else { - $this->result->changeCode( ConversionHandlerStatus::NESTED_ZIP_FILES_NOT_ALLOWED ); - $this->result->addError( "Nested zip files are not allowed." ); - - return false; - } - } else { - $conversionHandler->processConversion(); - $this->result = $conversionHandler->getResult(); - } - } - - private function validateSourceLang() { - try { - $this->lang_handler->validateLanguage( $this->source_lang ); - } catch ( Exception $e ) { - $this->result->changeCode( ConversionHandlerStatus::SOURCE_ERROR ); - $this->result->addError( $e->getMessage() ); - } - } - - /** - * @throws ReflectionException - * @throws Exception - */ - private function validateFiltersExtractionParametersTemplateId() { - if ( !empty( $this->filters_extraction_parameters_template_id ) ) { - - $filtersTemplate = FiltersConfigTemplateDao::getByIdAndUser( $this->filters_extraction_parameters_template_id, $this->getUser()->uid ); - - if ( $filtersTemplate === null ) { - throw new Exception( "filters_extraction_parameters_template_id not valid" ); - } - - $this->filters_extraction_parameters = $filtersTemplate; - } - } - - /** - * @throws Exception - */ - private function validateTargetLangs() { - try { - $this->target_lang = $this->lang_handler->validateLanguageListAsString( $this->target_lang ); - } catch ( Exception $e ) { - $this->result->changeCode( ConversionHandlerStatus::TARGET_ERROR ); - $this->result->addError( $e->getMessage() ); - } - } - - /** - * @param ConversionHandler $conversionHandler - * - * @return bool - * @throws ReflectionException - * @throws Exception - */ - private function handleZip( ConversionHandler $conversionHandler ): bool { - - // this makes the conversionhandler accumulate eventual errors on files and continue - $conversionHandler->setStopOnFileException( false ); - - $internalZipFileNames = $conversionHandler->extractZipFile(); - //call convertFileWrapper and start conversions for each file - - if ( $conversionHandler->uploadError ) { - $fileErrors = $conversionHandler->getUploadedFiles(); - - foreach ( $fileErrors as $fileError ) { - if ( count( $fileError->error ) == 0 ) { - continue; - } - - $brokenFileName = ZipArchiveExtended::getFileName( $fileError->name ); - - $this->result->changeCode( $fileError->error[ 'code' ] ); - $this->result->addError( $fileError->error[ 'message' ], $brokenFileName ); - } - - } - - $realFileNames = array_map( - [ 'ZipArchiveExtended', 'getFileName' ], - $internalZipFileNames - ); - - foreach ( $realFileNames as $i => &$fileObject ) { - $fileObject = [ - 'name' => $fileObject, - 'size' => filesize( $this->intDir . DIRECTORY_SEPARATOR . $internalZipFileNames[ $i ] ) - ]; - } - - $this->result->addData( 'zipFiles', json_encode( $realFileNames ) ); - - $stdFileObjects = []; - - if ( $internalZipFileNames !== null ) { - foreach ( $internalZipFileNames as $fName ) { - - $newStdFile = new stdClass(); - $newStdFile->name = $fName; - $stdFileObjects[] = $newStdFile; - - } - } else { - $errors = $conversionHandler->getResult(); - $errors = array_map( [ 'Upload', 'formatExceptionMessage' ], $errors->getErrors() ); - - foreach ( $errors as $error ) { - $this->result->addError( $error ); - } - - return false; - } - - /* Do conversions here */ - $converter = new ConvertFileWrapper( $stdFileObjects, false ); - $converter->intDir = $this->intDir; - $converter->errDir = $this->errDir; - $converter->cookieDir = $this->cookieDir; - $converter->source_lang = $this->source_lang; - $converter->target_lang = $this->target_lang; - $converter->featureSet = $this->featureSet; - $converter->doAction(); - - $error = $converter->checkResult(); - - $this->result->changeCode( ConversionHandlerStatus::ZIP_HANDLING ); - - // Upload errors handling - if ( $error !== null and !empty( $error->getErrors() ) ) { - $this->result->changeCode( $error->getCode() ); - $savedErrors = $this->result->getErrors(); - $brokenFileName = ZipArchiveExtended::getFileName( array_keys( $error->getErrors() )[ 0 ] ); - - if ( !isset( $savedErrors[ $brokenFileName ] ) ) { - $this->result->addError( $error->getErrors()[ 0 ][ 'message' ], $brokenFileName ); - } - } - - return true; - - } -} diff --git a/lib/Controller/copyAllSource2TargetController.php b/lib/Controller/copyAllSource2TargetController.php deleted file mode 100644 index 9765bef9a0..0000000000 --- a/lib/Controller/copyAllSource2TargetController.php +++ /dev/null @@ -1,234 +0,0 @@ -setErrorMap(); - - $filterArgs = [ - 'id_job' => [ - 'filter' => FILTER_SANITIZE_NUMBER_INT - ], - 'pass' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'revision_number' => [ - 'filter' => FILTER_SANITIZE_NUMBER_INT - ], - ]; - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_job = $postInput[ 'id_job' ]; - $this->pass = $postInput[ 'pass' ]; - $this->revisionNumber = $postInput[ 'revision_number' ]; - - Log::doJsonLog( "Requested massive copy-source-to-target for job $this->id_job." ); - - if ( empty( $this->id_job ) ) { - $errorCode = -1; - $this->addError( $errorCode ); - - } - if ( empty( $this->pass ) ) { - $errorCode = -2; - $this->addError( $errorCode ); - } - } - - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return mixed|void - * @throws ReflectionException - */ - function doAction() { - if ( !empty( $this->result[ 'errors' ] ) ) { - return; - } - - $job_data = Jobs_JobDao::getByIdAndPassword( $this->id_job, $this->pass ); - - if ( empty( $job_data ) ) { - $errorCode = -3; - $this->addError( $errorCode ); - - return; - } - - $this->_saveEventsAndUpdateTranslations( $job_data->id, $job_data->password ); - } - - /** - * @param $job_id - * @param $password - * - * @throws ReflectionException - * @throws \Exceptions\NotFoundException - */ - private function _saveEventsAndUpdateTranslations( $job_id, $password ) { - if ( !empty( $this->result[ 'errors' ] ) ) { - return; - } - - $job_data = Jobs_JobDao::getByIdAndPassword( $job_id, $password ); - - if ( empty( $job_data ) ) { - $errorCode = -3; - $this->addError( $errorCode ); - - return; - } - - // BEGIN TRANSACTION - $database = Database::obtain(); - $database->begin(); - - $chunk = Chunks_ChunkDao::getByIdAndPassword( $job_id, $password ); - $features = $chunk->getProject()->getFeaturesSet(); - - $batchEventCreator = new TranslationEventsHandler( $chunk ); - $batchEventCreator->setFeatureSet( $features ); - $batchEventCreator->setProject( $chunk->getProject() ); - - $source_page = ReviewUtils::revisionNumberToSourcePage( $this->revisionNumber ); - $segments = $chunk->getSegments(); - - $affected_rows = 0; - foreach ( $segments as $segment ) { - - $segment_id = (int)$segment->id; - $chunk_id = (int)$chunk->id; - - $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( $segment_id, $chunk_id ); - - if ( empty( $old_translation ) || ( $old_translation->status !== Constants_TranslationStatus::STATUS_NEW ) ) { - //no segment found - continue; - } - - $new_translation = clone $old_translation; - $new_translation->translation = $segment->segment; - $new_translation->status = Constants_TranslationStatus::STATUS_DRAFT; - $new_translation->translation_date = date( "Y-m-d H:i:s" ); - - - try { - $affected_rows += Translations_SegmentTranslationDao::updateTranslationAndStatusAndDate( $new_translation ); - } catch ( Exception $e ) { - $errorCode = -4; - self::$errorMap[ $errorCode ][ 'internalMessage' ] .= $e->getMessage(); - $this->addError( $errorCode ); - $database->rollback(); - - return; - } - - if ( $chunk->getProject()->hasFeature( Features::TRANSLATION_VERSIONS ) ) { - $segmentTranslationEventModel = new TranslationEvent( $old_translation, $new_translation, $this->user, $source_page ); - $batchEventCreator->addEvent( $segmentTranslationEventModel ); - } - } - - // save all events - $batchEventCreator->save( new BatchReviewProcessor() ); - - if ( !empty( $params[ 'segment_ids' ] ) ) { - $counter = new CounterModel(); - $counter->initializeJobWordCount( $chunk->id, $chunk->password ); - } - - Log::doJsonLog( 'Segment Translation events saved completed' ); - - $this->result[ 'data' ] = [ - 'code' => 1, - 'segments_modified' => $affected_rows - ]; - - Log::doJsonLog( $this->result[ 'data' ] ); - - $database->commit(); // COMMIT TRANSACTION - } - - private function setErrorMap() { - $generalOutputError = "Error while copying sources to targets. Please contact support@matecat.com"; - - self::$errorMap = [ - "-1" => [ - 'internalMessage' => "Empty id job", - 'outputMessage' => $generalOutputError - ], - "-2" => [ - 'internalMessage' => "Empty job password", - 'outputMessage' => $generalOutputError - ], - "-3" => [ - 'internalMessage' => "Wrong id_job-password couple. Job not found", - 'outputMessage' => $generalOutputError - ], - "-4" => [ - 'internalMessage' => "Error in copySegmentInTranslation: ", - 'outputMessage' => $generalOutputError - ] - ]; - } - - /** - * @param $errorCode int - */ - private function addError( $errorCode ) { - Log::doJsonLog( $this->getErrorMessage( $errorCode ) ); - $this->result[ 'errors' ][] = [ - 'code' => $errorCode, - 'message' => $this->getOutputErrorMessage( $errorCode ) - ]; - } - - /** - * @param $errorCode int - * - * @return string - */ - private function getErrorMessage( $errorCode ) { - if ( array_key_exists( $errorCode, self::$errorMap ) ) { - return self::$errorMap[ $errorCode ][ 'internalMessage' ]; - } - - return ""; - } - - /** - * @param $errorCode int - * - * @return string - */ - private function getOutputErrorMessage( $errorCode ) { - if ( array_key_exists( $errorCode, self::$errorMap ) ) { - return self::$errorMap[ $errorCode ][ 'outputMessage' ]; - } - - return ""; - } -} \ No newline at end of file diff --git a/lib/Controller/createProjectController.php b/lib/Controller/createProjectController.php deleted file mode 100644 index 6443d37527..0000000000 --- a/lib/Controller/createProjectController.php +++ /dev/null @@ -1,814 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'project_name' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'source_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'target_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'job_subject' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'due_date' => [ 'filter' => FILTER_VALIDATE_INT ], - 'mt_engine' => [ 'filter' => FILTER_VALIDATE_INT ], - 'disable_tms_engine' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'private_tm_key' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'pretranslate_100' => [ 'filter' => FILTER_VALIDATE_INT ], - 'pretranslate_101' => [ '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 ], - - 'deepl_id_glossary' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'deepl_formality' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'project_completion' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], // features customization - 'get_public_matches' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], // disable public TM matches - 'dictation' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'show_whitespace' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'character_counter' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'ai_assistant' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'dialect_strict' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'filters_extraction_parameters' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'xliff_parameters' => [ 'filter' => FILTER_SANITIZE_STRING ], - 'xliff_parameters_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - - 'qa_model_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - 'payable_rate_template_id' => [ 'filter' => FILTER_VALIDATE_INT ], - ]; - - $this->identifyUser(); - $this->setupUserFeatures(); - - $filterArgs = $this->__addFilterForMetadataInput( $filterArgs ); - - $this->postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //first we check the presence of a list from tm management panel - $array_keys = json_decode( $_POST[ 'private_keys_list' ], true ); - $array_keys = array_merge( $array_keys[ 'ownergroup' ], $array_keys[ 'mine' ], $array_keys[ 'anonymous' ] ); - - //if a string is sent by the client, transform it into a valid array - if ( !empty( $this->postInput[ 'private_tm_key' ] ) ) { - $this->postInput[ 'private_tm_key' ] = [ - [ - 'key' => trim( $this->postInput[ 'private_tm_key' ] ), - 'name' => null, - 'r' => true, - 'w' => true - ] - ]; - } else { - $this->postInput[ 'private_tm_key' ] = []; - } - - if ( $array_keys ) { // some keys are selected from panel - - //remove duplicates - foreach ( $array_keys as $pos => $value ) { - if ( isset( $this->postInput[ 'private_tm_key' ][ 0 ][ 'key' ] ) - && $this->postInput[ 'private_tm_key' ][ 0 ][ 'key' ] == $value[ 'key' ] - ) { - //same key was get from keyring, remove - $this->postInput[ 'private_tm_key' ] = []; - } - } - - //merge the arrays - $private_keyList = array_merge( $this->postInput[ 'private_tm_key' ], $array_keys ); - - - } else { - $private_keyList = $this->postInput[ 'private_tm_key' ]; - } - - $__postPrivateTmKey = array_filter( $private_keyList, [ "self", "sanitizeTmKeyArr" ] ); - - // NOTE: This is for debug purpose only, - // NOTE: Global $_POST Overriding from CLI - // $this->__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->file_name = $this->postInput[ 'file_name' ]; // da cambiare, FA SCHIFO la serializzazione - $this->project_name = $this->postInput[ 'project_name' ]; - $this->source_lang = $this->postInput[ 'source_lang' ]; - $this->target_lang = $this->postInput[ 'target_lang' ]; - $this->job_subject = $this->postInput[ 'job_subject' ]; - $this->mt_engine = ( $this->postInput[ 'mt_engine' ] != null ? $this->postInput[ 'mt_engine' ] : 0 ); // null NON รจ ammesso - $this->disable_tms_engine_flag = $this->postInput[ 'disable_tms_engine' ]; // se false allora MyMemory - $this->private_tm_key = $__postPrivateTmKey; - $this->pretranslate_100 = $this->postInput[ 'pretranslate_100' ]; - $this->pretranslate_101 = $this->postInput[ 'pretranslate_101' ]; - $this->only_private = ( is_null( $this->postInput[ 'get_public_matches' ] ) ? false : !$this->postInput[ 'get_public_matches' ] ); - $this->due_date = ( empty( $this->postInput[ 'due_date' ] ) ? null : Utils::mysqlTimestamp( $this->postInput[ 'due_date' ] ) ); - - $this->dictation = $this->postInput[ 'dictation' ] ?? null; - $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->__setMetadataFromPostInput(); - - if ( $this->disable_tms_engine_flag ) { - $this->tms_engine = 0; //remove default MyMemory - } - - if ( empty( $this->file_name ) ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "Missing file name." ]; - } - - if ( empty( $this->job_subject ) ) { - $this->result[ 'errors' ][] = [ "code" => -5, "message" => "Missing job subject." ]; - } - - if ( $this->pretranslate_100 !== 1 && $this->pretranslate_100 !== 0 ) { - $this->result[ 'errors' ][] = [ "code" => -6, "message" => "invalid pretranslate_100 value" ]; - } - - if ( $this->pretranslate_101 !== null && $this->pretranslate_101 !== 1 && $this->pretranslate_101 !== 0 ) { - $this->result[ 'errors' ][] = [ "code" => -6, "message" => "invalid pretranslate_101 value" ]; - } - - - $this->__validateSourceLang( Languages::getInstance() ); - $this->__validateTargetLangs( Languages::getInstance() ); - $this->__validateUserMTEngine(); - $this->__validateMMTGlossaries(); - $this->__validateDeepLGlossaryParams(); - $this->__validateQaModelTemplate(); - $this->__validatePayableRateTemplate(); - $this->__validateDialectStrictParam(); - $this->__validateFiltersExtractionParameters(); - $this->__validateXliffParameters(); - $this->__appendFeaturesToProject(); - $this->__generateTargetEngineAssociation(); - if ( $this->userIsLogged ) { - $this->__setTeam( $this->postInput[ 'id_team' ] ); - } - } - - /** - * setProjectFeatures - * - * @throws \Exceptions\NotFoundException - * @throws \API\Commons\Exceptions\AuthenticationError - * @throws \Exceptions\ValidationError - * @throws \TaskRunner\Exceptions\EndQueueException - * @throws \TaskRunner\Exceptions\ReQueueException - */ - - private function __appendFeaturesToProject() { - // change project features - - if ( !empty( $this->postInput[ 'project_completion' ] ) ) { - $feature = new BasicFeatureStruct(); - $feature->feature_code = 'project_completion'; - $this->projectFeatures[] = $feature; - } - - $this->projectFeatures = $this->featureSet->filter( - 'filterCreateProjectFeatures', $this->projectFeatures, $this - ); - - } - - /** - * @throws Exception - */ - public function doAction() { - //check for errors. If there are, stop execution and return errors. - if ( count( @$this->result[ 'errors' ] ) ) { - return false; - } - - $arFiles = explode( '@@SEP@@', html_entity_decode( $this->file_name, ENT_QUOTES, 'UTF-8' ) ); - - $default_project_name = $arFiles[ 0 ]; - if ( count( $arFiles ) > 1 ) { - $default_project_name = "MATECAT_PROJ-" . date( "Ymdhi" ); - } - - if ( empty( $this->project_name ) ) { - $this->project_name = $default_project_name; - } - - // SET SOURCE COOKIE - CookieManager::setCookie( Constants::COOKIE_SOURCE_LANG, $this->source_lang, - [ - 'expires' => time() + ( 86400 * 365 ), - 'path' => '/', - 'domain' => INIT::$COOKIE_DOMAIN, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'None', - ] - ); - - // SET TARGET COOKIE - CookieManager::setCookie( Constants::COOKIE_TARGET_LANG, $this->target_lang, - [ - 'expires' => time() + ( 86400 * 365 ), - 'path' => '/', - 'domain' => INIT::$COOKIE_DOMAIN, - 'secure' => true, - 'httponly' => true, - 'samesite' => 'None', - ] - ); - - //search in fileNames if there's a zip file. If it's present, get filenames and add the instead of the zip file. - - $uploadDir = INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $_COOKIE[ 'upload_token' ]; - $newArFiles = []; - $fs = FilesStorageFactory::create(); - - foreach ( $arFiles as $__fName ) { - if ( 'zip' == AbstractFilesStorage::pathinfo_fix( $__fName, PATHINFO_EXTENSION ) ) { - - $fs->cacheZipArchive( sha1_file( $uploadDir . DIRECTORY_SEPARATOR . $__fName ), $uploadDir . DIRECTORY_SEPARATOR . $__fName ); - - $linkFiles = scandir( $uploadDir ); - - //fetch cache links, created by converter, from upload directory - foreach ( $linkFiles as $storedFileName ) { - //check if file begins with the name of the zip file. - // If so, then it was stored in the zip file. - if ( strpos( $storedFileName, $__fName ) !== false && - substr( $storedFileName, 0, strlen( $__fName ) ) == $__fName ) { - //add file name to the files array - $newArFiles[] = $storedFileName; - } - } - - } else { //this file was not in a zip. Add it normally - - if ( file_exists( $uploadDir . DIRECTORY_SEPARATOR . $__fName ) ) { - $newArFiles[] = $__fName; - } - - } - } - - $arFiles = $newArFiles; - $arMeta = []; - - // create array_files_meta - foreach ( $arFiles as $arFile ) { - $arMeta[] = $this->getFileMetadata( $uploadDir . DIRECTORY_SEPARATOR . $arFile ); - } - - $projectManager = new ProjectManager(); - - $projectStructure = $projectManager->getProjectStructure(); - - $projectStructure[ 'project_name' ] = $this->project_name; - $projectStructure[ 'private_tm_key' ] = $this->private_tm_key; - $projectStructure[ 'uploadToken' ] = $_COOKIE[ 'upload_token' ]; - $projectStructure[ 'array_files' ] = $arFiles; //list of file name - $projectStructure[ 'array_files_meta' ] = $arMeta; //list of file metadata - $projectStructure[ 'source_language' ] = $this->source_lang; - $projectStructure[ 'target_language' ] = explode( ',', $this->target_lang ); - $projectStructure[ 'job_subject' ] = $this->job_subject; - $projectStructure[ 'mt_engine' ] = $this->mt_engine; - $projectStructure[ 'tms_engine' ] = $this->tms_engine; - $projectStructure[ 'status' ] = Constants_ProjectStatus::STATUS_NOT_READY_FOR_ANALYSIS; - $projectStructure[ 'pretranslate_100' ] = $this->pretranslate_100; - $projectStructure[ 'pretranslate_101' ] = $this->pretranslate_101; - $projectStructure[ 'dialect_strict' ] = $this->dialect_strict; - $projectStructure[ 'only_private' ] = $this->only_private; - $projectStructure[ 'due_date' ] = $this->due_date; - $projectStructure[ 'target_language_mt_engine_id' ] = $this->postInput[ 'target_language_mt_engine_id' ]; - $projectStructure[ 'user_ip' ] = Utils::getRealIpAddr(); - $projectStructure[ 'HTTP_HOST' ] = INIT::$HTTPHOST; - - $projectStructure[ 'dictation' ] = $this->dictation; - $projectStructure[ 'show_whitespace' ] = $this->show_whitespace; - $projectStructure[ 'character_counter' ] = $this->character_counter; - $projectStructure[ 'ai_assistant' ] = $this->ai_assistant; - - // MMT Glossaries - // (if $engine is not an MMT instance, ignore 'mmt_glossaries') - $engine = Engine::getInstance( $this->mt_engine ); - if ( $engine instanceof Engines_MMT and $this->mmt_glossaries !== null ) { - $projectStructure[ 'mmt_glossaries' ] = $this->mmt_glossaries; - } - - // DeepL - if ( $engine instanceof Engines_DeepL and $this->deepl_formality !== null ) { - $projectStructure[ 'deepl_formality' ] = $this->deepl_formality; - } - - if ( $engine instanceof Engines_DeepL and $this->deepl_id_glossary !== null ) { - $projectStructure[ 'deepl_id_glossary' ] = $this->deepl_id_glossary; - } - - if ( $this->filters_extraction_parameters ) { - $projectStructure[ 'filters_extraction_parameters' ] = $this->filters_extraction_parameters; - } - - if ( $this->xliff_parameters ) { - $projectStructure[ 'xliff_parameters' ] = $this->xliff_parameters; - } - - // with the qa template id - if ( $this->qaModelTemplate ) { - $projectStructure[ 'qa_model_template' ] = $this->qaModelTemplate->getDecodedModel(); - } - - if ( $this->payableRateModelTemplate ) { - $projectStructure[ 'payable_rate_model_id' ] = $this->payableRateModelTemplate->id; - } - - //TODO enable from CONFIG - $projectStructure[ 'metadata' ] = $this->metadata; - - if ( $this->userIsLogged ) { - $projectStructure[ 'userIsLogged' ] = true; - $projectStructure[ 'uid' ] = $this->user->uid; - $projectStructure[ 'id_customer' ] = $this->user->email; - $projectStructure[ 'owner' ] = $this->user->email; - $projectManager->setTeam( $this->team ); // set the team object to avoid useless query - } - - //set features override - $projectStructure[ 'project_features' ] = $this->projectFeatures; - - //reserve a project id from the sequence - $projectStructure[ 'id_project' ] = Database::obtain()->nextSequence( Database::SEQ_ID_PROJECT )[ 0 ]; - $projectStructure[ 'ppassword' ] = $projectManager->generatePassword(); - - try { - $projectManager->sanitizeProjectStructure(); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ - "code" => $e->getCode(), - "message" => $e->getMessage() - ]; - - return -1; - } - - try { - $fs::moveFileFromUploadSessionToQueuePath( $_COOKIE[ 'upload_token' ] ); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ - "code" => -235, // Error during moving file from upload session folder to queue path - "message" => $e->getMessage() - ]; - - return -1; - } - - Queue::sendProject( $projectStructure ); - - $this->__clearSessionFiles(); - $this->__assignLastCreatedPid( $projectStructure[ 'id_project' ] ); - - $this->result[ 'data' ] = [ - 'id_project' => $projectStructure[ 'id_project' ], - 'password' => $projectStructure[ 'ppassword' ] - ]; - - } - - /** - * @param $filename - * - * @return array - * @throws \API\Commons\Exceptions\AuthenticationError - * @throws \Exceptions\NotFoundException - * @throws \Exceptions\ValidationError - * @throws \TaskRunner\Exceptions\EndQueueException - * @throws \TaskRunner\Exceptions\ReQueueException - */ - private function getFileMetadata( $filename ) { - $info = XliffProprietaryDetect::getInfo( $filename ); - $isXliff = XliffFiles::isXliff( $filename ); - $isGlossary = XliffFiles::isGlossaryFile( $filename ); - $isTMX = XliffFiles::isTMXFile( $filename ); - $getMemoryType = XliffFiles::getMemoryFileType( $filename ); - - $forceXliff = $this->getFeatureSet()->filter( - 'forceXLIFFConversion', - INIT::$FORCE_XLIFF_CONVERSION, - $this->userIsLogged, - $info[ 'info' ][ 'dirname' ] . DIRECTORY_SEPARATOR . "$filename" - ); - $mustBeConverted = XliffProprietaryDetect::fileMustBeConverted( $filename, $forceXliff, INIT::$FILTERS_ADDRESS ); - - $metadata = []; - $metadata[ 'basename' ] = $info[ 'info' ][ 'basename' ]; - $metadata[ 'dirname' ] = $info[ 'info' ][ 'dirname' ]; - $metadata[ 'extension' ] = $info[ 'info' ][ 'extension' ]; - $metadata[ 'filename' ] = $info[ 'info' ][ 'filename' ]; - $metadata[ 'mustBeConverted' ] = $mustBeConverted; - $metadata[ 'getMemoryType' ] = $getMemoryType; - $metadata[ 'isXliff' ] = $isXliff; - $metadata[ 'isGlossary' ] = $isGlossary; - $metadata[ 'isTMX' ] = $isTMX; - $metadata[ 'proprietary' ] = [ - 'proprietary' => $info[ 'proprietary' ], - 'proprietary_name' => $info[ 'proprietary_name' ], - 'proprietary_short_name' => $info[ 'proprietary_short_name' ], - ]; - - return $metadata; - } - - /** - * Loads current features from current logged user. - */ - private function setupUserFeatures() { - if ( $this->userIsLogged ) { - $this->featureSet->loadFromUserEmail( $this->user->email ); - } - } - - private function __addFilterForMetadataInput( $filterArgs ) { - $filterArgs = array_merge( $filterArgs, [ - 'lexiqa' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'speech2text' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'tag_projection' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'segmentation_rule' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ] ); - - $filterArgs = $this->featureSet->filter( 'filterCreateProjectInputFilters', $filterArgs ); - - return $filterArgs; - } - - - private function __assignLastCreatedPid( $pid ) { - $_SESSION[ 'redeem_project' ] = false; - $_SESSION[ 'last_created_pid' ] = $pid; - } - - private function __validateTargetLangs( Languages $lang_handler ) { - $targets = explode( ',', $this->target_lang ); - $targets = array_map( 'trim', $targets ); - $targets = array_unique( $targets ); - - if ( empty( $targets ) ) { - $this->result[ 'errors' ][] = [ "code" => -4, "message" => "Missing target language." ]; - } - - try { - foreach ( $targets as $target ) { - $lang_handler->validateLanguage( $target ); - } - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -4, "message" => $e->getMessage() ]; - } - - $this->target_lang = implode( ',', $targets ); - } - - private function __validateSourceLang( Languages $lang_handler ) { - try { - $lang_handler->validateLanguage( $this->source_lang ); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -3, "message" => $e->getMessage() ]; - } - } - - private function __clearSessionFiles() { - - if ( $this->userIsLogged ) { - $gdriveSession = new Session(); - $gdriveSession->clearFileListFromSession(); - } - } - - private static function sanitizeTmKeyArr( $elem ) { - - $element = new TmKeyManagement_TmKeyStruct( $elem ); - $element->complete_format = true; - $elem = TmKeyManagement_TmKeyManagement::sanitize( $element ); - - return $elem->toArray(); - - } - - /** - * This function sets metadata property from input params. - * - */ - private function __setMetadataFromPostInput() { - - // new raw counter model - $options = [ Projects_MetadataDao::WORD_COUNT_TYPE_KEY => Projects_MetadataDao::WORD_COUNT_RAW ]; - - if ( isset( $this->postInput[ 'lexiqa' ] ) ) { - $options[ 'lexiqa' ] = $this->postInput[ 'lexiqa' ]; - } - if ( isset( $this->postInput[ 'speech2text' ] ) ) { - $options[ 'speech2text' ] = $this->postInput[ 'speech2text' ]; - } - if ( isset( $this->postInput[ 'tag_projection' ] ) ) { - $options[ 'tag_projection' ] = $this->postInput[ 'tag_projection' ]; - } - if ( isset( $this->postInput[ 'segmentation_rule' ] ) ) { - $options[ 'segmentation_rule' ] = $this->postInput[ 'segmentation_rule' ]; - } - - $this->metadata = $options; - - $this->metadata = $this->featureSet->filter( 'createProjectAssignInputMetadata', $this->metadata, [ - 'input' => $this->postInput - ] ); - } - - /** - * TODO: this should be moved to a model that. - * - * @param null $id_team - * - * @throws Exception - */ - private function __setTeam( $id_team = null ) { - if ( is_null( $id_team ) ) { - $this->team = $this->user->getPersonalTeam(); - } else { - // check for the team to be allowed - $dao = new \Teams\MembershipDao(); - $team = $dao->findTeamByIdAndUser( $id_team, $this->user ); - - if ( !$team ) { - throw new Exception( 'Team and user memberships do not match' ); - } else { - $this->team = $team; - } - } - } - - /** - * Check if MT engine (except MyMemory) belongs to user - */ - private function __validateUserMTEngine() { - - if ( $this->mt_engine > 1 and $this->isLoggedIn() ) { - try { - EngineValidator::engineBelongsToUser( $this->mt_engine, $this->user->uid ); - } catch ( Exception $exception ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => $exception->getMessage() ]; - } - } - } - - /** - * Validate `mmt_glossaries` string - */ - private function __validateMMTGlossaries() { - - if ( !empty( $this->postInput[ 'mmt_glossaries' ] ) and $this->isLoggedIn() ) { - try { - $mmtGlossaries = html_entity_decode( $this->postInput[ 'mmt_glossaries' ] ); - MMTValidator::validateGlossary( $mmtGlossaries ); - - $this->mmt_glossaries = $mmtGlossaries; - - } catch ( Exception $exception ) { - $this->result[ 'errors' ][] = [ "code" => -6, "message" => $exception->getMessage() ]; - } - } - } - - /** - * Validate DeepL params - */ - private function __validateDeepLGlossaryParams() { - - if ( $this->isLoggedIn() ) { - - if ( !empty( $this->postInput[ 'deepl_formality' ] ) ) { - - $allowedFormalities = [ - 'default', - 'prefer_less', - 'prefer_more' - ]; - - if ( in_array( $this->postInput[ 'deepl_formality' ], $allowedFormalities ) ) { - $this->deepl_formality = $this->postInput[ 'deepl_formality' ]; - } - } - - if ( !empty( $this->postInput[ 'deepl_id_glossary' ] ) ) { - $this->deepl_id_glossary = $this->postInput[ 'deepl_id_glossary' ]; - } - } - } - - /** - * @throws Exception - */ - private function __validateQaModelTemplate() { - if ( !empty( $this->postInput[ 'qa_model_template_id' ] ) and $this->postInput[ 'qa_model_template_id' ] > 0 ) { - $qaModelTemplate = QAModelTemplateDao::get( [ - 'id' => $this->postInput[ 'qa_model_template_id' ], - 'uid' => $this->getUser()->uid - ] ); - - // check if qa_model template exists - if ( null === $qaModelTemplate ) { - throw new Exception( 'This QA Model template does not exists or does not belongs to the logged in user' ); - } - - $this->qaModelTemplate = $qaModelTemplate; - } - } - - /** - * @throws Exception - */ - private function __validatePayableRateTemplate() { - $payableRateModelTemplate = null; - - if ( !empty( $this->postInput[ 'payable_rate_template_id' ] ) and $this->postInput[ 'payable_rate_template_id' ] > 0 ) { - - $payableRateTemplateId = $this->postInput[ 'payable_rate_template_id' ]; - $userId = $this->getUser()->uid; - - $payableRateModelTemplate = CustomPayableRateDao::getByIdAndUser( $payableRateTemplateId, $userId ); - - if ( null === $payableRateModelTemplate ) { - throw new Exception( 'Payable rate model id not valid' ); - } - - } - - $this->payableRateModelTemplate = $payableRateModelTemplate; - } - - /** - * Validate `dialect_strict` param vs target languages - * - * Example: {"it-IT": true, "en-US": false, "fr-FR": false} - * - * @throws Exception - */ - private function __validateDialectStrictParam() { - if ( !empty( $this->postInput[ 'dialect_strict' ] ) ) { - $dialect_strict = trim( html_entity_decode( $this->postInput[ 'dialect_strict' ] ) ); - $target_languages = preg_replace( '/\s+/', '', $this->postInput[ 'target_lang' ] ); - $targets = explode( ',', trim( $target_languages ) ); - $dialectStrictObj = json_decode( $dialect_strict, true ); - - foreach ( $dialectStrictObj as $lang => $value ) { - if ( !in_array( $lang, $targets ) ) { - throw new Exception( 'Wrong `dialect_strict` object, language, ' . $lang . ' is not one of the project target languages' ); - } - - if ( !is_bool( $value ) ) { - throw new Exception( 'Wrong `dialect_strict` object, not boolean declared value for ' . $lang ); - } - } - - $this->dialect_strict = html_entity_decode( $dialect_strict ); - } - } - - /** - * @throws Exception - */ - private function __validateFiltersExtractionParameters() { - if ( !empty( $this->postInput[ 'filters_extraction_parameters' ] ) ) { - - $json = html_entity_decode( $this->postInput[ 'filters_extraction_parameters' ] ); - $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/filters_extraction_parameters.json' ); - - $validatorObject = new JSONValidatorObject(); - $validatorObject->json = $json; - - $validator = new JSONValidator( $schema ); - $validator->validate( $validatorObject ); - - $this->filters_extraction_parameters = json_decode( $json ); - } - } - - /** - * @throws Exception - */ - private function __validateXliffParameters() { - - if ( !empty( $this->postInput[ 'xliff_parameters' ] ) ) { - - $json = html_entity_decode( $this->postInput[ 'xliff_parameters' ] ); - - // first check if `xliff_parameters` is a valid JSON - if ( !Utils::isJson( $json ) ) { - throw new Exception( "xliff_parameters is not a valid JSON" ); - } - - $schema = file_get_contents( INIT::$ROOT . '/inc/validation/schema/xliff_parameters_rules_content.json' ); - - $validatorObject = new JSONValidatorObject(); - $validatorObject->json = $json; - - $validator = new JSONValidator( $schema, true ); - $validator->validate( $validatorObject ); - $this->xliff_parameters = json_decode( $json, true ); // decode again because we need an associative array and not stdClass - - } elseif ( !empty( $this->postInput[ 'xliff_parameters_template_id' ] ) ) { - - $xliffConfigTemplate = XliffConfigTemplateDao::getByIdAndUser( $this->postInput[ 'xliff_parameters_template_id' ], $this->getUser()->uid ); - - if ( $xliffConfigTemplate === null ) { - throw new Exception( "xliff_parameters_template_id not valid" ); - } - - $this->xliff_parameters = $xliffConfigTemplate->rules->getArrayCopy(); - - } - - } - - /** - * This could be already set by MMT engine if enabled ( so check key existence and do not override ) - * - * @see filterCreateProjectFeatures callback - * @see createProjectController::__appendFeaturesToProject() - */ - private function __generateTargetEngineAssociation() { - if ( !isset( $this->postInput[ 'target_language_mt_engine_id' ] ) ) { // this could be already set by MMT engine if enabled ( so check and do not override ) - foreach ( explode( ",", $this->target_lang ) as $_matecatTarget ) { - $this->postInput[ 'target_language_mt_engine_id' ][ $_matecatTarget ] = $this->mt_engine; - } - } - } - -} - diff --git a/lib/Controller/createRandUserController.php b/lib/Controller/createRandUserController.php deleted file mode 100644 index fff2b7fa28..0000000000 --- a/lib/Controller/createRandUserController.php +++ /dev/null @@ -1,22 +0,0 @@ -result[ 'data' ] = $tms->createMyMemoryKey(); - - } - -} \ No newline at end of file diff --git a/lib/Controller/deleteContributionController.php b/lib/Controller/deleteContributionController.php deleted file mode 100644 index 4bbc147e94..0000000000 --- a/lib/Controller/deleteContributionController.php +++ /dev/null @@ -1,169 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'source_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'target_lang' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'seg' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'tra' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_match' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'current_password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - ]; - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_segment = $__postInput[ 'id_segment' ]; - $this->source_lang = $__postInput[ 'source_lang' ]; - $this->target_lang = $__postInput[ 'target_lang' ]; - $this->source = trim( $__postInput[ 'seg' ] ); - $this->target = trim( $__postInput[ 'tra' ] ); - $this->id_translator = (isset($__postInput[ 'id_translator' ]) ? trim( $__postInput[ 'id_translator' ] ) : null ); - $this->password = trim( $__postInput[ 'password' ] ); - $this->received_password = trim( $__postInput[ 'current_password' ] ); - $this->id_match = $__postInput[ 'id_match' ]; - $this->id_job = $__postInput[ 'id_job' ]; - - } - - public function doAction() { - - if ( empty( $this->source_lang ) ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "missing source_lang" ]; - } - - if ( empty( $this->target_lang ) ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing target_lang" ]; - } - - if ( empty( $this->source ) ) { - $this->result[ 'errors' ][] = [ "code" => -3, "message" => "missing source" ]; - } - - if ( empty( $this->target ) ) { - $this->result[ 'errors' ][] = [ "code" => -4, "message" => "missing target" ]; - } - - //check Job password - $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $this->id_job, $this->password ); - $this->featureSet->loadForProject( $jobStruct->getProject() ); - - $this->tm_keys = $jobStruct[ 'tm_keys' ]; - $this->identifyUser(); - - $tms = Engine::getInstance( $jobStruct[ 'id_tms' ] ); - $config = $tms->getConfigStruct(); - - $Filter = MateCatFilter::getInstance( $this->getFeatureSet(), $this->source_lang, $this->target_lang, [] ); - $config[ 'segment' ] = $Filter->fromLayer2ToLayer0( $this->source ); - $config[ 'translation' ] = $Filter->fromLayer2ToLayer0( $this->target ); - $config[ 'source' ] = $this->source_lang; - $config[ 'target' ] = $this->target_lang; - $config[ 'email' ] = INIT::$MYMEMORY_API_KEY; - $config[ 'id_user' ] = []; - $config[ 'id_match' ] = $this->id_match; - - //get job's TM keys - try { - - $tm_keys = $this->tm_keys; - - if ( self::isRevision() ) { - $this->userRole = TmKeyManagement_Filter::ROLE_REVISOR; - } - - //get TM keys with read grants - $tm_keys = TmKeyManagement_TmKeyManagement::getJobTmKeys( $tm_keys, 'w', 'tm', $this->user->uid, $this->userRole ); - $tm_keys = TmKeyManagement_TmKeyManagement::filterOutByOwnership( $tm_keys, $this->user->email, $jobStruct[ 'owner' ] ); - - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -11, "message" => "Cannot retrieve TM keys info." ]; - - return; - } - - //prepare the errors report - $set_code = []; - - /** - * @var $tm_key TmKeyManagement_TmKeyStruct - */ - - //if there's no key - if ( empty( $tm_keys ) ) { - //try deleting anyway, it may be a public segment and it may work - $TMS_RESULT = $tms->delete( $config ); - - if($TMS_RESULT){ - $this->updateSuggestionsArray(); - } - - $set_code[] = $TMS_RESULT; - } else { - //loop over the list of keys - foreach ( $tm_keys as $tm_key ) { - //issue a separate call for each key - $config[ 'id_user' ] = $tm_key->key; - $TMS_RESULT = $tms->delete( $config ); - - if($TMS_RESULT){ - $this->updateSuggestionsArray(); - } - - $set_code[] = $TMS_RESULT; - } - } - - $set_successful = true; - if ( array_search( false, $set_code, true ) ) { - //There's an errors - $set_successful = false; - } - - $this->result[ 'data' ] = ( $set_successful ? "OK" : null ); - $this->result[ 'code' ] = $set_successful; - - } - - /** - * update suggestions array - */ - private function updateSuggestionsArray() { - - $segmentTranslation = Translations_SegmentTranslationDao::findBySegmentAndJob($this->id_segment, $this->id_job); - $oldSuggestionsArray = json_decode($segmentTranslation->suggestions_array); - - if(!empty($oldSuggestionsArray)){ - - $newSuggestionsArray = []; - foreach ($oldSuggestionsArray as $suggestion){ - if($suggestion->id != $this->id_match){ - $newSuggestionsArray[] = $suggestion; - } - } - - Translations_SegmentTranslationDao::updateSuggestionsArray($this->id_segment, $newSuggestionsArray); - } - } - -} diff --git a/lib/Controller/downloadTMXController.php b/lib/Controller/downloadTMXController.php deleted file mode 100644 index afd8daeb56..0000000000 --- a/lib/Controller/downloadTMXController.php +++ /dev/null @@ -1,206 +0,0 @@ -identifyUser(); - - $filterArgs = array( - 'id_job' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'password' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'tm_key' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'tm_name' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'downloadToken' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'email' => array( - 'filter' => FILTER_VALIDATE_EMAIL - ), - 'strip_tags' => array( - 'filter' => FILTER_VALIDATE_BOOLEAN - ), - 'source' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'target' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ) - - ); - - $__postInput = filter_var_array( $_REQUEST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI Test scripts - //$__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->tm_key = $__postInput[ 'tm_key' ]; - $this->tm_name = $__postInput[ 'tm_name' ]; - $this->source = $__postInput[ 'source' ]; - $this->target = $__postInput[ 'target' ]; - $this->download_to_email = $__postInput[ 'email' ]; - $this->id_job = $__postInput[ 'id_job' ]; - $this->password = $__postInput[ 'password' ]; - $this->strip_tags = $__postInput[ 'strip_tags' ]; - - if ( !$this->userIsLogged ) { - - $output = "
\n";
-            $output .= " - REQUEST URI: " . print_r( @$_SERVER[ 'REQUEST_URI' ], true ) . "\n";
-            $output .= " - REQUEST Message: " . print_r( $_REQUEST, true ) . "\n";
-            $output .= "\n\t";
-            $output .= "Aborting...\n";
-            $output .= "
"; - - Log::$fileName = 'php_errors.txt'; - Log::doJsonLog( $output ); - - Utils::sendErrMailReport( $output, "Download TMX Error: user Not Logged" ); - exit; - } - - $this->tmxHandler = new TMSService(); - $this->tmxHandler->setName( $this->tm_name ); - - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - */ - function doAction() { - - try { - - if ( $this->download_to_email === false ) { - $this->result[ 'errors' ][] = array( - "code" => -1, - "message" => "Invalid email provided for download." - ); - - return; - } - - $this->result[ 'data' ] = $this->tmxHandler->requestTMXEmailDownload( - ( $this->download_to_email != false ? $this->download_to_email : $this->user->email ), - $this->user->first_name, - $this->user->last_name, - $this->tm_key, - $this->strip_tags - ); - - // TODO: Not used at moment, will be enabled when will be built the Log Activity Keys - /* - $activity = new ActivityLogStruct(); - $activity->id_job = $this->id_job; - $activity->action = ActivityLogStruct::DOWNLOAD_KEY_TMX; - $activity->ip = Utils::getRealIpAddr(); - $activity->uid = $this->user->uid; - $activity->event_date = date( 'Y-m-d H:i:s' ); - Activity::save( $activity ); - */ - - } catch ( Exception $e ) { - - $r = "
";
-
-            $r .= print_r( "User Email: " . $this->download_to_email, true ) . "\n";
-            $r .= print_r( "User ID: " . $this->user->uid, true ) . "\n";
-            $r .= print_r( $e->getMessage(), true ) . "\n";
-            $r .= print_r( $e->getTraceAsString(), true ) . "\n";
-
-            $r .= "\n\n";
-            $r .= " - REQUEST URI: " . print_r( @$_SERVER[ 'REQUEST_URI' ], true ) . "\n";
-            $r .= " - REQUEST Message: " . print_r( $_REQUEST, true ) . "\n";
-            $r .= "\n\n\n";
-            $r .= "
"; - - Log::$fileName = 'php_errors.txt'; - Log::doJsonLog( $r ); - - Utils::sendErrMailReport( $r, "Download TMX Error: " . $e->getMessage() ); - - $this->result[ 'errors' ][] = array( "code" => -2, "message" => "Download TMX Error: " . $e->getMessage() ); - - return; - - } - } - -} \ No newline at end of file diff --git a/lib/Controller/engineController.php b/lib/Controller/engineController.php deleted file mode 100755 index dea4c703a6..0000000000 --- a/lib/Controller/engineController.php +++ /dev/null @@ -1,383 +0,0 @@ - [ 'getTermList' ] // letsmt no longer requires this function. it's left as an example - ]; - - public function __construct() { - - parent::__construct(); - - //Session Enabled - $this->identifyUser(); - //Session Disabled - - $filterArgs = [ - 'exec' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW - ], - 'id' => [ - 'filter' => FILTER_SANITIZE_NUMBER_INT - ], - 'name' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW - ], - 'data' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW | FILTER_FLAG_NO_ENCODE_QUOTES - ], - 'provider' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW - ] - ]; - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->exec = $postInput[ 'exec' ]; - $this->id = $postInput[ 'id' ]; - $this->name = $postInput[ 'name' ]; - $this->provider = $postInput[ 'provider' ]; - $this->engineData = json_decode( $postInput[ 'data' ], true ); - - if ( is_null( $this->exec ) ) { - $this->result[ 'errors' ][] = [ 'code' => -1, 'message' => "Exec field required" ]; - - } else { - if ( !in_array( $this->exec, self::$allowed_actions ) ) { - $this->result[ 'errors' ][] = [ 'code' => -2, 'message' => "Exec value not allowed" ]; - } - } - - //ONLY LOGGED USERS CAN PERFORM ACTIONS ON KEYS - if ( !$this->userIsLogged ) { - $this->result[ 'errors' ][] = [ - 'code' => -3, - 'message' => "Login is required to perform this action" - ]; - } - - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return mixed - * @throws Exception - */ - public function doAction() { - if ( count( $this->result[ 'errors' ] ) > 0 ) { - return; - } - - switch ( $this->exec ) { - case 'add': - $this->add(); - break; - case 'delete': - $this->disable(); - break; - default: - break; - } - - } - - /** - * This method adds an engine in a user's keyring - * @throws Exception - */ - private function add() { - - $newEngineStruct = null; - $validEngine = true; - - switch ( strtolower( $this->provider ) ) { - - case strtolower( Constants_Engines::DEEPL ): - - $newEngineStruct = DeepLStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'DeepL-Auth-Key' ] = $this->engineData[ 'client_id' ]; - - try { - DeepLValidator::validate($newEngineStruct); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ 'code' => $e->getCode(), 'message' => $e->getMessage() ]; - - return; - } - - break; - - - case strtolower( Constants_Engines::MICROSOFT_HUB ): - - /** - * Create a record of type MicrosoftHub - */ - $newEngineStruct = EnginesModel_MicrosoftHubStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_id' ] = $this->engineData[ 'client_id' ]; - $newEngineStruct->extra_parameters[ 'category' ] = $this->engineData[ 'category' ]; - break; - - case strtolower( Constants_Engines::APERTIUM ): - - /** - * Create a record of type APERTIUM - */ - $newEngineStruct = EnginesModel_ApertiumStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_secret' ] = $this->engineData[ 'secret' ]; - - break; - - case strtolower( Constants_Engines::ALTLANG ): - - /** - * Create a record of type ALTLANG - */ - $newEngineStruct = EnginesModel_AltlangStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_secret' ] = $this->engineData[ 'secret' ]; - - break; - - case strtolower( Constants_Engines::SMART_MATE ): - - /** - * Create a record of type SmartMate - */ - $newEngineStruct = EnginesModel_SmartMATEStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_id' ] = $this->engineData[ 'client_id' ]; - $newEngineStruct->extra_parameters[ 'client_secret' ] = $this->engineData[ 'secret' ]; - - break; - - case strtolower( Constants_Engines::YANDEX_TRANSLATE ): - - /** - * Create a record of type YandexTranslate - */ - $newEngineStruct = EnginesModel_YandexTranslateStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_secret' ] = $this->engineData[ 'secret' ]; - - break; - - case strtolower( Constants_Engines::GOOGLE_TRANSLATE ): - - /** - * Create a record of type GoogleTranslate - */ - $newEngineStruct = EnginesModel_GoogleTranslateStruct::getStruct(); - - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters[ 'client_secret' ] = $this->engineData[ 'secret' ]; - - break; - - case strtolower(Constants_Engines::INTENTO): - /** - * Create a record of type Intento - */ - $newEngineStruct = EnginesModel_IntentoStruct::getStruct(); - $newEngineStruct->name = $this->name; - $newEngineStruct->uid = $this->user->uid; - $newEngineStruct->type = Constants_Engines::MT; - $newEngineStruct->extra_parameters['apikey'] = $this->engineData['secret']; - $newEngineStruct->extra_parameters['provider'] = $this->engineData['provider']; - $newEngineStruct->extra_parameters['providerkey'] = $this->engineData['providerkey']; - $newEngineStruct->extra_parameters['providercategory'] = $this->engineData['providercategory']; - break; - - default: - - // MMT - $validEngine = $newEngineStruct = $this->featureSet->filter( 'buildNewEngineStruct', false, (object)[ - 'featureSet' => $this->featureSet, - 'providerName' => $this->provider, - 'logged_user' => $this->user, - 'engineData' => $this->engineData - ] ); - break; - - } - - if ( !$validEngine ) { - $this->result[ 'errors' ][] = [ 'code' => -4, 'message' => "Engine not allowed" ]; - - return; - } - - $engineList = $this->featureSet->filter( 'getAvailableEnginesListForUser', Constants_Engines::getAvailableEnginesList(), $this->user ); - - $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); - $newCreatedDbRowStruct = null; - - if ( array_search( $newEngineStruct->class_load, $engineList ) ) { - $newEngineStruct->active = true; - $newCreatedDbRowStruct = $engineDAO->create( $newEngineStruct ); - $this->destroyUserEnginesCache(); - } - - if ( !$newCreatedDbRowStruct instanceof EnginesModel_EngineStruct ) { - - $this->result[ 'errors' ][] = $this->featureSet->filter( - 'engineCreationFailed', - [ 'code' => -9, 'message' => "Creation failed. Generic error" ], - $newEngineStruct->class_load - ); - - return; - } - - if ( $newEngineStruct instanceof EnginesModel_MicrosoftHubStruct ) { - - $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); - $config = $newTestCreatedMT->getConfigStruct(); - $config[ 'segment' ] = "Hello World"; - $config[ 'source' ] = "en-US"; - $config[ 'target' ] = "it-IT"; - - $mt_result = $newTestCreatedMT->get( $config ); - - if ( isset( $mt_result[ 'error' ][ 'code' ] ) ) { - $this->result[ 'errors' ][] = $mt_result[ 'error' ]; - $engineDAO->delete( $newCreatedDbRowStruct ); - $this->destroyUserEnginesCache(); - - return; - } - - } elseif ( $newEngineStruct instanceof EnginesModel_GoogleTranslateStruct ) { - - $newTestCreatedMT = Engine::createTempInstance( $newCreatedDbRowStruct ); - $config = $newTestCreatedMT->getConfigStruct(); - $config[ 'segment' ] = "Hello World"; - $config[ 'source' ] = "en-US"; - $config[ 'target' ] = "fr-FR"; - - $mt_result = $newTestCreatedMT->get( $config ); - - if ( isset( $mt_result[ 'error' ][ 'code' ] ) ) { - $this->result[ 'errors' ][] = $mt_result[ 'error' ]; - $engineDAO->delete( $newCreatedDbRowStruct ); - $this->destroyUserEnginesCache(); - - return; - } - } else { - - try { - $this->featureSet->run( 'postEngineCreation', $newCreatedDbRowStruct, $this->user ); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ 'code' => $e->getCode(), 'message' => $e->getMessage() ]; - $engineDAO->delete( $newCreatedDbRowStruct ); - $this->destroyUserEnginesCache(); - - return; - } - - } - - $this->result[ 'data' ][ 'id' ] = $newCreatedDbRowStruct->id; - $this->result[ 'data' ][ 'name' ] = $newCreatedDbRowStruct->name; - $this->result[ 'data' ][ 'description' ] = $newCreatedDbRowStruct->description; - $this->result[ 'data' ][ 'type' ] = $newCreatedDbRowStruct->type; - $this->result[ 'data' ][ 'engine_type' ] = $newCreatedDbRowStruct->class_load; - } - - /** - * This method deletes an engine from a user's keyring - * - * @throws Exception - */ - private function disable() { - - if ( empty( $this->id ) ) { - $this->result[ 'errors' ][] = [ 'code' => -5, 'message' => "Engine id required" ]; - - return; - } - - $engineToBeDeleted = EnginesModel_EngineStruct::getStruct(); - $engineToBeDeleted->id = $this->id; - $engineToBeDeleted->uid = $this->user->uid; - - $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); - $result = $engineDAO->disable( $engineToBeDeleted ); - $this->destroyUserEnginesCache(); - - if ( !$result instanceof EnginesModel_EngineStruct ) { - $this->result[ 'errors' ][] = [ 'code' => -9, 'message' => "Deletion failed. Generic error" ]; - - return; - } - - $this->featureSet->run( 'postEngineDeletion', $result ); - - $this->result[ 'data' ][ 'id' ] = $result->id; - - } - - - /** - * Destroy cache for engine users query - * - * @throws Exception - */ - private function destroyUserEnginesCache() { - $engineDAO = new EnginesModel_EngineDAO( Database::obtain() ); - $engineStruct = EnginesModel_EngineStruct::getStruct(); - $engineStruct->uid = $this->user->uid; - $engineStruct->active = true; - - $engineDAO->destroyCache( $engineStruct ); - } - -} diff --git a/lib/Controller/fetchChangeRatesController.php b/lib/Controller/fetchChangeRatesController.php deleted file mode 100644 index adad1933cc..0000000000 --- a/lib/Controller/fetchChangeRatesController.php +++ /dev/null @@ -1,26 +0,0 @@ -fetchChangeRates(); - - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = $changeRatesFetcher->getChangeRates(); - } - -} \ No newline at end of file diff --git a/lib/Controller/getContributionController.php b/lib/Controller/getContributionController.php deleted file mode 100644 index 89b5d287ed..0000000000 --- a/lib/Controller/getContributionController.php +++ /dev/null @@ -1,205 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'num_results' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'text' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_translator' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'current_password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'is_concordance' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'from_target' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'context_before' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'context_after' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_before' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_after' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_client' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'cross_language' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FORCE_ARRAY ] - ]; - - $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI -// $this->__postInput = filter_var_array( $_REQUEST, $filterArgs ); - - $this->id_segment = $this->__postInput[ 'id_segment' ]; - $this->id_before = $this->__postInput[ 'id_before' ]; - $this->id_after = $this->__postInput[ 'id_after' ]; - - $this->id_job = $this->__postInput[ 'id_job' ]; - $this->num_results = $this->__postInput[ 'num_results' ]; - $this->text = trim( $this->__postInput[ 'text' ] ); - $this->id_translator = $this->__postInput[ 'id_translator' ]; - $this->concordance_search = $this->__postInput[ 'is_concordance' ]; - $this->switch_languages = $this->__postInput[ 'from_target' ]; - $this->password = $this->__postInput[ 'password' ]; - $this->received_password = $this->__postInput[ 'current_password' ]; - $this->id_client = $this->__postInput[ 'id_client' ]; - $this->cross_language = $this->__postInput[ 'cross_language' ]; - - if ( $this->id_translator == 'unknown_translator' ) { - $this->id_translator = ""; - } - - } - - public function doAction() { - - if ( !$this->concordance_search ) { - //execute these lines only in segment contribution search, - //in case of user concordance search skip these lines - //because segment can be optional - if ( empty( $this->id_segment ) ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "missing id_segment" ]; - } - } - - if ( is_null( $this->text ) || $this->text === '' ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing text" ]; - } - - if ( empty( $this->id_job ) ) { - $this->result[ 'errors' ][] = [ "code" => -3, "message" => "missing id_job" ]; - } - - if ( empty( $this->id_client ) ) { - $this->result[ 'errors' ][] = [ "code" => -4, "message" => "missing id_client" ]; - } - - if ( empty( $this->num_results ) ) { - $this->num_results = INIT::$DEFAULT_NUM_RESULTS_FROM_TM; - } - - if ( !empty( $this->result[ 'errors' ] ) ) { - return -1; - } - -// throw new \Exceptions\NotFoundException( "Record Not Found" ); - //get Job Info, we need only a row of jobs ( split ) - $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $this->id_job, $this->password ); - - $dataRefMap = Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $this->id_segment ); - - $projectStruct = $jobStruct->getProject(); - $this->featureSet->loadForProject( $projectStruct ); - - $this->identifyUser(); - if ( !$this->concordance_search ) { - $this->_getContexts( $jobStruct->source, $jobStruct->target ); - } - - $file = (new FilesPartsDao())->getBySegmentId($this->id_segment); - $owner = (new Users_UserDao())->getProjectOwner( $this->id_job ); - - $contributionRequest = new ContributionRequestStruct(); - $contributionRequest->id_file = $file->id_file; - $contributionRequest->id_job = $this->id_job; - $contributionRequest->password = $this->received_password; - $contributionRequest->user = $owner; - $contributionRequest->dataRefMap = $dataRefMap; - $contributionRequest->contexts = [ - 'context_before' => $this->context_before, - 'segment' => $this->text, - 'context_after' => $this->context_after - ]; - $contributionRequest->jobStruct = $jobStruct; - $contributionRequest->projectStruct = $projectStruct; - $contributionRequest->segmentId = $this->id_segment; - $contributionRequest->id_client = $this->id_client; - $contributionRequest->concordanceSearch = $this->concordance_search; - $contributionRequest->fromTarget = $this->switch_languages; - $contributionRequest->resultNum = $this->num_results; - $contributionRequest->crossLangTargets = $this->getCrossLanguages(); - - if ( self::isRevision() ) { - $contributionRequest->userRole = TmKeyManagement_Filter::ROLE_REVISOR; - } else { - $contributionRequest->userRole = TmKeyManagement_Filter::ROLE_TRANSLATOR; - } - - Request::contribution( $contributionRequest ); - - $this->result = [ "errors" => [], "data" => [ "message" => "OK", "id_client" => $this->id_client ] ]; - - } - - /** - * Remove voids - * ("en-GB," => [0 => 'en-GB']) - * - * @return array - */ - private function getCrossLanguages() { - return !empty( $this->cross_language ) ? explode( ",", rtrim( $this->cross_language[ 0 ], ',' ) ) : []; - } - - /** - * @param string $source - * @param string $target - * - * @throws \Exception - */ - protected function _getContexts( $source, $target ) { - - $featureSet = ( $this->featureSet !== null ) ? $this->featureSet : new \FeatureSet(); - - //Get contexts - $segmentsList = ( new Segments_SegmentDao )->setCacheTTL( 60 * 60 * 24 )->getContextAndSegmentByIDs( - [ - 'id_before' => $this->id_before, - 'id_segment' => $this->id_segment, - 'id_after' => $this->id_after - ] - ); - - $featureSet->filter( 'rewriteContributionContexts', $segmentsList, $this->__postInput ); - - $Filter = MateCatFilter::getInstance( $featureSet, $source, $target, [] ); - - if ( $segmentsList->id_before ) { - $this->context_before = $Filter->fromLayer0ToLayer1( $segmentsList->id_before->segment ); - } - - if ( $segmentsList->id_segment ) { - $this->text = $Filter->fromLayer0ToLayer1( $segmentsList->id_segment->segment ); - } - - if ( $segmentsList->id_after ) { - $this->context_after = $Filter->fromLayer0ToLayer1( $segmentsList->id_after->segment ); - } - - } - -} - diff --git a/lib/Controller/getProjectsController.php b/lib/Controller/getProjectsController.php deleted file mode 100644 index f1d47cf6ff..0000000000 --- a/lib/Controller/getProjectsController.php +++ /dev/null @@ -1,210 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'step' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'project' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'pn' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW - ], - 'source' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW - ], - 'target' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW - ], - 'status' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_HIGH | FILTER_FLAG_STRIP_LOW - ], - 'onlycompleted' => [ - 'filter' => FILTER_VALIDATE_BOOLEAN, - 'options' => [ FILTER_NULL_ON_FAILURE ] - ], - 'id_team' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_assignee' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - - 'no_assignee' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - - ]; - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - ( !empty( $postInput[ 'status' ] ) && Constants_JobStatus::isAllowedStatus( $postInput[ 'status' ] ) ? $this->search_status = $postInput[ 'status' ] : null ); - ( !empty( $postInput[ 'page' ] ) ? $this->page = (int)$postInput[ 'page' ] : null ); - ( !empty( $postInput[ 'step' ] ) ? $this->step = (int)$postInput[ 'step' ] : null ); - - $this->start = ( $this->page - 1 ) * $this->step; - $this->project_id = $postInput[ 'project' ]; - $this->search_in_pname = $postInput[ 'pn' ]; - $this->search_source = $postInput[ 'source' ]; - $this->search_target = $postInput[ 'target' ]; - $this->id_team = $postInput[ 'id_team' ]; - $this->id_assignee = $postInput[ 'id_assignee' ]; - - $this->no_assignee = $postInput[ 'no_assignee' ]; - - $this->search_only_completed = $postInput[ 'onlycompleted' ]; - - } - - public function doAction() { - - if ( !$this->userIsLogged ) { - throw new Exception( 'User not Logged', 401 ); - } - - $this->featureSet->loadFromUserEmail( $this->user->email ) ; - - try { - $team = $this->filterTeam(); - } catch( NotFoundException $e ){ - throw new $e; - } - - if( $team->type == Constants_Teams::PERSONAL ){ - $assignee = $this->user; - $team = null; - } else { - $assignee = $this->filterAssignee( $team ); - } - - $projects = ManageUtils::getProjects( - $this->user, - $this->start, - $this->step, - $this->search_in_pname, - $this->search_source, - $this->search_target, - $this->search_status, - $this->search_only_completed, $this->project_id, - $team, $assignee, - $this->no_assignee - ); - - $projnum = ManageUtils::getProjectsNumber( $this->user, - $this->search_in_pname, $this->search_source, - $this->search_target, $this->search_status, - $this->search_only_completed, - $team, $assignee, - $this->no_assignee - ); - - $this->result[ 'data' ] = $projects; - $this->result[ 'page' ] = $this->page; - $this->result[ 'pnumber' ] = $projnum[ 0 ][ 'c' ]; - $this->result[ 'pageStep' ] = $this->step; - } - - /** - * @param $team - * - * @return Users_UserStruct - * @throws Exception - */ - - private function filterAssignee( $team ) { - - if ( is_null( $this->id_assignee ) ) { - return null; - } - - $dao = new MembershipDao(); - $memberships = $dao->setCacheTTL( 60 * 60 * 24 )->getMemberListByTeamId( $team->id ); - $id_assignee = $this->id_assignee; - /** - * @var $users \Teams\MembershipStruct[] - */ - $users = array_values( array_filter( $memberships, function ( MembershipStruct $membership ) use ( $id_assignee ) { - return $membership->getUser()->uid == $id_assignee; - } ) ); - - if ( empty( $users ) ) { - throw new Exception( 'Assignee not found in team' ); - } - - return $users[ 0 ]->getUser(); - } - - private function filterTeam() { - $dao = new MembershipDao() ; - $team = $dao->findTeamByIdAndUser($this->id_team, $this->user ) ; - if ( !$team ) { - throw new NotFoundException( 'Team not found in user memberships', 404 ) ; - } - else { - return $team ; - } - } -} diff --git a/lib/Controller/getSearchController.php b/lib/Controller/getSearchController.php deleted file mode 100644 index cef80ea1b9..0000000000 --- a/lib/Controller/getSearchController.php +++ /dev/null @@ -1,470 +0,0 @@ -identifyUser(); - - $filterArgs = [ - 'function' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'token' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW ], - 'source' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'target' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'status' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ], - 'replace' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'password' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'matchcase' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'exactmatch' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'strict_mode' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'revision_number' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ] - ]; - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->function = $__postInput[ 'function' ]; //can be: search / replace - $this->job = (int)$__postInput[ 'job' ]; - $this->token = $__postInput[ 'token' ]; - $this->source = $__postInput[ 'source' ]; - $this->target = $__postInput[ 'target' ]; - $this->status = strtolower( $__postInput[ 'status' ] ); - $this->replace = $__postInput[ 'replace' ]; - $this->password = $__postInput[ 'password' ]; - $this->isMatchCaseRequested = (bool)$__postInput[ 'matchcase' ]; - $this->isExactMatchRequested = (bool)$__postInput[ 'exactmatch' ]; - $this->strictMode = (bool)$__postInput[ 'strict_mode' ]; - $this->revisionNumber = (int)$__postInput[ 'revision_number' ]; - - switch ( $this->status ) { - case 'translated': - case 'approved': - case 'approved2': - case 'rejected': - case 'draft': - case 'new': - break; - default: - $this->status = "all"; - break; - } - - $this->queryParams = new SearchQueryParamsStruct( [ - 'job' => $this->job, - 'password' => $this->password, - 'key' => null, - 'src' => null, - 'trg' => null, - 'status' => $this->status, - 'replacement' => $this->replace, - 'isMatchCaseRequested' => $this->isMatchCaseRequested, - 'isExactMatchRequested' => $this->isExactMatchRequested, - 'strictMode' => $this->strictMode, - ] ); - - $this->db = Database::obtain(); - - // Search_ReplaceHistory init - $srh_driver = ( isset( INIT::$REPLACE_HISTORY_DRIVER ) and '' !== INIT::$REPLACE_HISTORY_DRIVER ) ? INIT::$REPLACE_HISTORY_DRIVER : 'redis'; - $srh_ttl = ( isset( INIT::$REPLACE_HISTORY_TTL ) and '' !== INIT::$REPLACE_HISTORY_TTL ) ? INIT::$REPLACE_HISTORY_TTL : 300; - $this->srh = Search_ReplaceHistoryFactory::create( $this->queryParams[ 'job' ], $srh_driver, $srh_ttl ); - - //get Job Info - $this->job_data = Chunks_ChunkDao::getByIdAndPassword( (int)$this->job, $this->password ); - - /** @var MateCatFilter $filter */ - $filter = MateCatFilter::getInstance( $this->getFeatureSet(), $this->job_data->source, $this->job_data->target ); - $this->searchModel = new SearchModel( $this->queryParams, $filter ); - } - - /** - * @return void - * @throws Exception - */ - public function doAction() { - - $this->result[ 'token' ] = $this->token; - - if ( empty( $this->job ) ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing id job" ]; - - return; - } - - $this->featureSet->loadForProject( $this->job_data->getProject() ); - - switch ( $this->function ) { - case 'find': - $this->doSearch(); - break; - case 'replaceAll': - $this->doReplaceAll(); - break; - case 'redoReplaceAll': - $this->redoReplaceAll(); - break; - case 'undoReplaceAll': - $this->undoReplaceAll(); - break; - default : - $this->result[ 'errors' ][] = [ "code" => -11, "message" => "unknown function. Use find or replace" ]; - } - } - - /** - * Perform a regular search - */ - private function doSearch() { - - if ( !empty( $this->source ) and !empty( $this->target ) ) { - $this->queryParams[ 'key' ] = 'coupled'; - $this->queryParams[ 'src' ] = html_entity_decode( $this->source ); // source strings are not escaped as html entites in DB. Example: < must be decoded to < - $this->queryParams[ 'trg' ] = $this->target; - } elseif ( !empty( $this->source ) ) { - $this->queryParams[ 'key' ] = 'source'; - $this->queryParams[ 'src' ] = html_entity_decode( $this->source ); // source strings are not escaped as html entites in DB. Example: < must be decoded to < - } elseif ( !empty( $this->target ) ) { - $this->queryParams[ 'key' ] = 'target'; - $this->queryParams[ 'trg' ] = $this->target; - } else { - $this->queryParams[ 'key' ] = 'status_only'; - } - - try { - $strictMode = ( null !== $this->queryParams[ 'strictMode' ] ) ? $this->queryParams[ 'strictMode' ] : true; - $res = $this->searchModel->search( $strictMode ); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -1000, "message" => "internal error: see the log" ]; - - return; - } - - $this->result[ 'total' ] = $res[ 'count' ]; - $this->result[ 'segments' ] = $res[ 'sid_list' ]; - } - - /** - * Perform a search and replace ฮฑฯ†ฮตฯ„ฮญฯฮฟฯ… - * - * @throws Exception - */ - private function doReplaceAll() { - - $search_results = []; - - // perform a regular search - $this->doSearch(); - - // and then hydrate the $search_results array - foreach ( $this->result[ 'segments' ] as $segmentId ) { - $search_results[] = Translations_SegmentTranslationDao::findBySegmentAndJob( $segmentId, $this->queryParams[ 'job' ], 10 )->toArray(); - } - - // set the replacement in queryParams - $this->queryParams[ 'replacement' ] = $this->replace; - - // update segment translations - $this->_updateSegments( $search_results ); - - // and save replace events - $replace_version = ( $this->srh->getCursor() + 1 ); - foreach ( $search_results as $tRow ) { - $this->_saveReplacementEvent( $replace_version, $tRow ); - } - } - - /** - * @param $replace_version - * @param $tRow - */ - private function _saveReplacementEvent( $replace_version, $tRow ) { - $event = new ReplaceEventStruct(); - $event->replace_version = $replace_version; - $event->id_segment = $tRow[ 'id_segment' ]; - $event->id_job = $this->queryParams[ 'job' ]; - $event->job_password = $this->queryParams[ 'password' ]; - $event->source = $this->queryParams[ 'source' ]; - $event->target = $this->queryParams[ 'target' ]; - $event->replacement = $this->queryParams[ 'replacement' ]; - $event->translation_before_replacement = $tRow[ 'translation' ]; - $event->translation_after_replacement = $this->_getReplacedSegmentTranslation( $tRow[ 'translation' ] ); - $event->status = $tRow[ 'status' ]; - - $this->srh->save( $event ); - $this->srh->updateIndex( $replace_version ); - - Log::doJsonLog( 'Replacement event for segment #' . $tRow[ 'id_segment' ] . ' correctly saved.' ); - } - - /** - * @param $translation - * - * @return string|string[]|null - */ - private function _getReplacedSegmentTranslation( $translation ) { - $replacedSegmentTranslation = WholeTextFinder::findAndReplace( - $translation, - $this->queryParams->target, - $this->queryParams->replacement, - true, - $this->queryParams->isExactMatchRequested, - $this->queryParams->isMatchCaseRequested, - true - ); - - return ( !empty( $replacedSegmentTranslation ) ) ? $replacedSegmentTranslation[ 'replacement' ] : $translation; - } - - /** - * @throws Exception - */ - private function undoReplaceAll() { - $search_results = $this->_getSegmentForUndoReplaceAll(); - $this->_updateSegments( $search_results ); - - $this->srh->undo(); - } - - /** - * @return array - */ - private function _getSegmentForUndoReplaceAll(): array { - $results = []; - $cursor = $this->srh->getCursor(); - - if ( $cursor === 0 ) { - $versionToMove = 0; - } elseif ( $cursor === 1 ) { - $versionToMove = 1; - } else { - $versionToMove = $cursor - 1; - } - - $events = $this->srh->get( $versionToMove ); - - foreach ( $events as $event ) { - $results[] = [ - 'id_segment' => $event->id_segment, - 'id_job' => $event->id_job, - 'translation' => $event->translation_after_replacement, - 'status' => $event->status, - ]; - } - - return $results; - } - - /** - * @throws Exception - */ - private function redoReplaceAll() { - $search_results = $this->_getSegmentForRedoReplaceAll(); - $this->_updateSegments( $search_results ); - - $this->srh->redo(); - } - - /** - * @return array - */ - private function _getSegmentForRedoReplaceAll(): array { - $results = []; - - $versionToMove = $this->srh->getCursor() + 1; - $events = $this->srh->get( $versionToMove ); - - foreach ( $events as $event ) { - $results[] = [ - 'id_segment' => $event->id_segment, - 'id_job' => $event->id_job, - 'translation' => $event->translation_before_replacement, - 'status' => $event->status, - ]; - } - - return $results; - } - - /** - * @param $search_results - * - * @return void - * @throws Exception - */ - private function _updateSegments( $search_results ): void { - $chunk = Chunks_ChunkDao::getByIdAndPassword( (int)$this->job, $this->password ); - $project = Projects_ProjectDao::findByJobId( (int)$this->job ); - $versionsHandler = TranslationVersions::getVersionHandlerNewInstance( $chunk, $this->id_segment, $this->user, $project ); - - // loop all segments to replace - foreach ( $search_results as $tRow ) { - - // start the transaction - $this->db->begin(); - - $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( (int)$tRow[ 'id_segment' ], (int)$tRow[ 'id_job' ] ); - $segment = ( new Segments_SegmentDao() )->getById( $tRow[ 'id_segment' ] ); - - // Propagation - $propagationTotal = [ - 'propagated_ids' => [] - ]; - - if ( $old_translation->translation !== $tRow[ 'translation' ] && in_array( $old_translation->status, [ - Constants_TranslationStatus::STATUS_TRANSLATED, - Constants_TranslationStatus::STATUS_APPROVED, - Constants_TranslationStatus::STATUS_APPROVED2, - Constants_TranslationStatus::STATUS_REJECTED - ] ) - ) { - - $TPropagation = clone $old_translation; - $TPropagation[ 'status' ] = $tRow[ 'status' ]; - $TPropagation[ 'translation' ] = $tRow[ 'translation' ]; - $TPropagation[ 'autopropagated_from' ] = $this->id_segment; - - try { - - $propagationTotal = Translations_SegmentTranslationDao::propagateTranslation( - $TPropagation, - $this->job_data, - $this->id_segment, - $project, - $versionsHandler - ); - - } catch ( Exception $e ) { - $msg = $e->getMessage() . "\n\n" . $e->getTraceAsString(); - $this->result[ 'errors' ][] = [ "code" => -102, "message" => $e->getMessage() ]; - Log::doJsonLog( $msg ); - $this->db->rollback(); - - return; - } - } - - $filter = MateCatFilter::getInstance( $this->getFeatureSet(), $this->job_data->source, $this->job_data->target ); - $replacedTranslation = $filter->fromLayer1ToLayer0( $this->_getReplacedSegmentTranslation( $tRow[ 'translation' ] ) ); - $replacedTranslation = Utils::stripBOM( $replacedTranslation ); - - // Setup $new_translation - $new_translation = clone $old_translation; - $new_translation->status = $this->_getNewStatus( $old_translation ); - $new_translation->translation = $replacedTranslation; - $new_translation->translation_date = date( "Y-m-d H:i:s" ); - - // commit the transaction - try { - - // Save version - $versionsHandler->saveVersionAndIncrement( $new_translation, $old_translation ); - - // preSetTranslationCommitted - $versionsHandler->storeTranslationEvent( [ - 'translation' => $new_translation, - 'old_translation' => $old_translation, - 'propagation' => $propagationTotal, - 'chunk' => $chunk, - 'segment' => $segment, - 'user' => $this->user, - 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->revisionNumber ), - 'controller_result' => & $this->result, - 'features' => $this->featureSet, - 'project' => $project - ] ); - - Translations_SegmentTranslationDao::updateTranslationAndStatusAndDate( $new_translation ); - $this->db->commit(); - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -101, "message" => $e->getMessage() ]; - Log::doJsonLog( "Lock: Transaction Aborted. " . $e->getMessage() ); - $this->db->rollback(); - - return; - } - - // setTranslationCommitted - try { - $this->featureSet->run( 'setTranslationCommitted', [ - 'translation' => $new_translation, - 'old_translation' => $old_translation, - 'propagated_ids' => $propagationTotal[ 'propagated_ids' ], - 'chunk' => $chunk, - 'segment' => $segment, - 'user' => $this->user, - 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->revisionNumber ) - ] ); - } catch ( Exception $e ) { - Log::doJsonLog( "Exception in setTranslationCommitted callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); - } - } - } - - /** - * @param Translations_SegmentTranslationStruct $translationStruct - * - * @return string - */ - private function _getNewStatus( Translations_SegmentTranslationStruct $translationStruct ): string { - - if ( false === $this->revisionNumber ) { - return Constants_TranslationStatus::STATUS_TRANSLATED; - } - - return $translationStruct->status; - } - -} diff --git a/lib/Controller/getSegmentsController.php b/lib/Controller/getSegmentsController.php deleted file mode 100644 index 5d0a6fb614..0000000000 --- a/lib/Controller/getSegmentsController.php +++ /dev/null @@ -1,204 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'step' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ], - 'where' => [ 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ], - ]; - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI Test scripts - //$__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->jid = $__postInput[ 'jid' ]; - $this->step = $__postInput[ 'step' ]; - $this->id_segment = $__postInput[ 'segment' ]; - $this->password = $__postInput[ 'password' ]; - $this->where = $__postInput[ 'where' ]; - - if( $this->step > self::MAX_PER_PAGE ) { - $this->step = self::MAX_PER_PAGE; - } - - } - - public function doAction() { - - $this->job = Chunks_ChunkDao::getByIdAndPassword( $this->jid, $this->password ); - $this->project = $this->job->getProject(); - - $featureSet = $this->getFeatureSet(); - - $featureSet->loadForProject( $this->project ); - - $lang_handler = Languages::getInstance(); - - $this->parseIDSegment(); - - if ( $this->id_segment == '' ) { - $this->id_segment = 0; - } - - $sDao = new Segments_SegmentDao(); - $data = $sDao->getPaginationSegments( - $this->job, - $this->step, - $this->id_segment, - $this->where, - [ - 'optional_fields' => [ - 'st.edit_distance', - 'st.version_number' - ] - ] - ); - - $this->prepareNotes( $data ); - $contexts = $this->getContextGroups( $data ); - - $this->pname = $this->project->name; - $this->cid = $this->project->id_customer; - $this->tid = $this->job->id_translator; - $this->create_date = $this->project->create_date; - - foreach ( $data as $i => $seg ) { - - $id_file = $seg[ 'id_file' ]; - - if ( !isset( $this->data[ $id_file ] ) ) { - $this->data[ $id_file ][ 'jid' ] = $seg[ 'jid' ]; - $this->data[ $id_file ][ "filename" ] = ZipArchiveExtended::getFileName( $seg[ 'filename' ] ); - $this->data[ $id_file ][ 'source' ] = $lang_handler->getLocalizedName( $this->job->source ); - $this->data[ $id_file ][ 'target' ] = $lang_handler->getLocalizedName( $this->job->target ); - $this->data[ $id_file ][ 'source_code' ] = $this->job->source; - $this->data[ $id_file ][ 'target_code' ] = $this->job->target; - $this->data[ $id_file ][ 'segments' ] = []; - } - - if ( isset( $seg[ 'edit_distance' ] ) ) { - $seg[ 'edit_distance' ] = round( $seg[ 'edit_distance' ] / 1000, 2 ); - } else { - $seg[ 'edit_distance' ] = 0; - } - - $seg[ 'parsed_time_to_edit' ] = CatUtils::parse_time_to_edit( min( $seg[ 'time_to_edit' ], PHP_INT_MAX ) ); - - ( $seg[ 'source_chunk_lengths' ] === null ? $seg[ 'source_chunk_lengths' ] = '[]' : null ); - ( $seg[ 'target_chunk_lengths' ] === null ? $seg[ 'target_chunk_lengths' ] = '{"len":[0],"statuses":["DRAFT"]}' : null ); - $seg[ 'source_chunk_lengths' ] = json_decode( $seg[ 'source_chunk_lengths' ], true ); - $seg[ 'target_chunk_lengths' ] = json_decode( $seg[ 'target_chunk_lengths' ], true ); - - // inject original data ref map (FOR XLIFF 2.0) - $data_ref_map = json_decode( $seg[ 'data_ref_map' ], true ); - $seg[ 'data_ref_map' ] = $data_ref_map; - - $Filter = MateCatFilter::getInstance( $featureSet, $this->job->source, $this->job->target, null !== $data_ref_map ? $data_ref_map : [] ); - - $seg[ 'segment' ] = $Filter->fromLayer0ToLayer1( - CatUtils::reApplySegmentSplit( $seg[ 'segment' ], $seg[ 'source_chunk_lengths' ] ) - ); - - $seg[ 'translation' ] = $Filter->fromLayer0ToLayer1( - CatUtils::reApplySegmentSplit( $seg[ 'translation' ], $seg[ 'target_chunk_lengths' ][ 'len' ] ) - ); - - $seg[ 'translation' ] = $Filter->fromLayer1ToLayer2( $Filter->realignIDInLayer1( $seg[ 'segment' ], $seg[ 'translation' ] ) ); - $seg[ 'segment' ] = $Filter->fromLayer1ToLayer2( $seg[ 'segment' ] ); - - $seg[ 'metadata' ] = Segments_SegmentMetadataDao::getAll( $seg[ 'sid' ] ); - - $this->attachNotes( $seg ); - $this->attachContexts( $seg, $contexts ); - - $this->data[ $id_file ][ 'segments' ][] = $seg; - } - - $this->result[ 'data' ][ 'files' ] = $this->data; - $this->result[ 'data' ][ 'where' ] = $this->where; - - $this->result[ 'data' ] = $featureSet->filter( 'filterGetSegmentsResult', $this->result[ 'data' ], $this->job ); - } - - private function attachNotes( &$segment ) { - $segment[ 'notes' ] = isset( $this->segment_notes[ (int)$segment[ 'sid' ] ] ) ? $this->segment_notes[ (int)$segment[ 'sid' ] ] : null; - } - - private function prepareNotes( $segments ) { - if ( !empty( $segments[ 0 ] ) ) { - $start = $segments[ 0 ][ 'sid' ]; - $last = end( $segments ); - $stop = $last[ 'sid' ]; - if ( $this->featureSet->filter( 'prepareAllNotes', false ) ) { - $this->segment_notes = Segments_SegmentNoteDao::getAllAggregatedBySegmentIdInInterval( $start, $stop ); - foreach ( $this->segment_notes as $k => $noteObj ) { - $this->segment_notes[ $k ][ 0 ][ 'json' ] = json_decode( $noteObj[ 0 ][ 'json' ], true ); - } - $this->segment_notes = $this->featureSet->filter( 'processExtractedJsonNotes', $this->segment_notes ); - } else { - $this->segment_notes = Segments_SegmentNoteDao::getAggregatedBySegmentIdInInterval( $start, $stop ); - } - - } - - } - - private function getContextGroups( $segments ) { - if ( !empty( $segments[ 0 ] ) ) { - $start = $segments[ 0 ][ 'sid' ]; - $last = end( $segments ); - $stop = $last[ 'sid' ]; - - return ( new ContextGroupDao() )->getBySIDRange( $start, $stop ); - } - } - - private function attachContexts( &$segment, $contexts ) { - $segment[ 'context_groups' ] = isset( $contexts[ (int)$segment[ 'sid' ] ] ) ? $contexts[ (int)$segment[ 'sid' ] ] : null; - } - - -} diff --git a/lib/Controller/getTagProjectionController.php b/lib/Controller/getTagProjectionController.php deleted file mode 100644 index 9ba8005078..0000000000 --- a/lib/Controller/getTagProjectionController.php +++ /dev/null @@ -1,147 +0,0 @@ -old_logFile = \Log::$fileName; - - parent::__construct(); - - $filterArgs = [ - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'source' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'target' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'suggestion' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'source_lang' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'target_lang' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI Test scripts - //$this->__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->id_job = $this->__postInput[ 'id_job' ]; - $this->password = $this->__postInput[ 'password' ]; - $this->source = $this->__postInput[ 'source' ]; - $this->target = $this->__postInput[ 'target' ]; - $this->source_lang = $this->__postInput[ 'source_lang' ]; - $this->target_lang = $this->__postInput[ 'target_lang' ]; - $this->suggestion = $this->__postInput[ 'suggestion' ]; - $this->id_segment = $this->__postInput[ 'id_segment' ]; - - \Log::$fileName = 'tagProjection.log'; - - } - - public function doAction() { - - if ( is_null( $this->source ) || $this->source === '' ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "missing source segment" ]; - } - - if ( is_null( $this->target ) || $this->target === '' ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing target segment" ]; - } - - if ( empty( $this->source_lang ) ) { - $this->result[ 'errors' ][] = [ "code" => -3, "message" => "missing source lang" ]; - } - - if ( empty( $this->target_lang ) ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing target lang" ]; - } - - if ( empty( $this->id_job ) ) { - $this->result[ 'errors' ][] = [ "code" => -4, "message" => "id_job not valid" ]; - - $msg = "\n\n Critical. Quit. \n\n " . var_export( array_merge( $this->result, $_POST ), true ); - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - - // critical. Quit. - return -1; - } - - //check Job password - $jobStruct = Chunks_ChunkDao::getByIdAndPassword( $this->id_job, $this->password ); - $this->featureSet->loadForProject( $jobStruct->getProject() ); - - $this->getTagProjection(); - - } - - public function getTagProjection() { - - /** - * @var $engine Engines_MyMemory - */ - $engine = Engine::getInstance( 1 ); - $engine->setFeatureSet( $this->featureSet ); - - $dataRefMap = Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $this->id_segment ); - $Filter = MateCatFilter::getInstance( $this->getFeatureSet(), $this->source_lang, $this->target_lang, $dataRefMap ); - - $config = []; - $config[ 'dataRefMap' ] = $dataRefMap; - $config[ 'source' ] = $Filter->fromLayer2ToLayer1( $this->source ); - $config[ 'target' ] = $Filter->fromLayer2ToLayer1( $this->target ); - $config[ 'source_lang' ] = $this->source_lang; - $config[ 'target_lang' ] = $this->target_lang; - $config[ 'suggestion' ] = $Filter->fromLayer2ToLayer1( $this->suggestion ); - - $result = $engine->getTagProjection( $config ); - if ( empty( $result->error ) ) { - $this->result[ 'data' ][ 'translation' ] = $Filter->fromLayer1ToLayer2( $result->responseData ); - $this->result[ 'code' ] = 0; - } else { - $this->result[ 'code' ] = $result->error->code; - $this->result[ 'errors' ] = $result->error; - $this->logTagProjection( - [ - 'request' => $config, - 'error' => $result->error - ] - ); - } - - \Log::$fileName = $this->old_logFile; - } - - public function logTagProjection( $msg = null ) { - - if ( !$msg ) { - \Log::doJsonLog( $this->result[ 'data' ] ); - } else { - \Log::doJsonLog( $msg ); - } - - } -} - - diff --git a/lib/Controller/getTranslationMismatchesController.php b/lib/Controller/getTranslationMismatchesController.php deleted file mode 100644 index 2febd61903..0000000000 --- a/lib/Controller/getTranslationMismatchesController.php +++ /dev/null @@ -1,62 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI Test scripts - //$__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->id_segment = $__postInput[ 'id_segment' ]; - $this->id_job = (int)$__postInput[ 'id_job' ]; - $this->password = $__postInput[ 'password' ]; - - $this->featureSet->loadForProject( Projects_ProjectDao::findByJobId( $this->id_job, 60 * 60 ) ); - - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return mixed - * @throws Exception - */ - function doAction() { - - $this->parseIDSegment(); - - $sDao = new Segments_SegmentDao(); - $Translation_mismatches = $sDao->setCacheTTL( 1 * 60 /* 1 minutes cache */ )->getTranslationsMismatches( $this->id_job, $this->password, $this->id_segment ); - - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = ( new SegmentTranslationMismatches( $Translation_mismatches, count( $Translation_mismatches ), $this->featureSet ) )->render(); - - } - - -} \ No newline at end of file diff --git a/lib/Controller/getVolumeAnalysisController.php b/lib/Controller/getVolumeAnalysisController.php deleted file mode 100644 index 8b49e1c9dc..0000000000 --- a/lib/Controller/getVolumeAnalysisController.php +++ /dev/null @@ -1,58 +0,0 @@ - array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'ppassword' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'jpassword' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - ); - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_project = $__postInput[ 'pid' ]; - $this->ppassword = $__postInput[ 'ppassword' ]; - $this->jpassword = $__postInput[ 'jpassword' ]; - - /** - * Retrieve user information - */ - $this->identifyUser(); - - } - - public function doAction() { - - if ( empty( $this->id_project ) ) { - $this->result[ 'errors' ] = array( -1, "No id project provided" ); - return -1; - } - - $_project_data = Projects_ProjectDao::getProjectAndJobData( $this->id_project ); - - $passCheck = new AjaxPasswordCheck(); - $access = $passCheck->grantProjectAccess( $_project_data, $this->ppassword ) || $passCheck->grantProjectJobAccessOnJobPass( $_project_data, null, $this->jpassword ); - - if ( !$access ) { - $this->result[ 'errors' ] = array( -10, "Wrong Password. Access denied" ); - return -1; - } - - $analysisStatus = new Status( $_project_data, $this->featureSet, $this->user ); - $this->result = $analysisStatus->fetchData()->getResult(); - - } - -} - diff --git a/lib/Controller/getWarningController.php b/lib/Controller/getWarningController.php deleted file mode 100644 index 20c13b1bab..0000000000 --- a/lib/Controller/getWarningController.php +++ /dev/null @@ -1,219 +0,0 @@ -identifyUser(); - - $filterArgs = [ - - 'id' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'src_content' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'trg_content' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'token' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW - ], - 'logs' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'segment_status' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'characters_counter' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - ]; - - $this->__postInput = (object)filter_input_array( INPUT_POST, $filterArgs ); - - /** - * Update 2015/08/11, roberto@translated.net - * getWarning needs the segment status too because of a bug: - * sometimes the client calls getWarning and sends an empty trg_content - * because the suggestion has not been loaded yet. - * This happens only if segment is in status NEW - */ - if ( empty( $this->__postInput->segment_status ) ) { - $this->__postInput->segment_status = 'draft'; - } - } - - /** - * Return to Javascript client the JSON error list in the form: - * - *
-     * array(
-     *       [total] => 1
-     *       [details] => Array
-     *       (
-     *           ['2224860'] => Array
-     *               (
-     *                   [id_segment] => 2224860
-     *                   [warnings] => '[{"outcome":1000,"debug":"Tag mismatch"}]'
-     *               )
-     *       )
-     *      [token] => 'token'
-     * )
-     * 
- * - */ - public function doAction() { - - $this->chunk = Chunks_ChunkDao::getByIdAndPassword( - $this->__postInput->id_job, - $this->__postInput->password - ); - - $this->project = $this->chunk->getProject(); - - $this->loadFeatures(); - - if ( empty( $this->__postInput->src_content ) ) { - $this->__globalWarningsCall(); - } else { - /** - * Update 2015/08/11, roberto@translated.net - * getWarning needs the segment status too because of a bug: - * sometimes the client calls getWarning and sends an empty trg_content - * because the suggestion has not been loaded yet. - * This happens only if segment is in status NEW - */ - if ( $this->__postInput->segment_status == 'new' && - empty( $this->__postInput->trg_content ) - ) { - return; - } - - $this->__segmentWarningsCall(); - } - - } - - private function loadFeatures() { - $this->featureSet->loadForProject( $this->project ); - } - - /** - * - * getWarning $query are in the form: - *
-     * Array
-     * (
-     * [0] => Array
-     *     (
-     *         [id_segment] => 2224900
-     *     ),
-     * [1] => Array
-     *     (
-     *         [id_segment] => 2224903
-     *     ),
-     * )
-     * 
- */ - private function __globalWarningsCall() { - - $this->result[ 'token' ] = $this->__postInput->token; - - try { - $result = WarningDao::getWarningsByJobIdAndPassword( $this->__postInput->id_job, $this->__postInput->password ); - $tMismatch = ( new Segments_SegmentDao() )->setCacheTTL( 10 * 60 /* 10 minutes cache */ )->getTranslationsMismatches( $this->__postInput->id_job, $this->__postInput->password ); - } catch ( Exception $e ) { - $this->result[ 'details' ] = []; - - return; - } - - $qa = new QAGlobalWarning( $result, $tMismatch ); - - $this->result = array_merge( - $this->result, - $qa->render(), - Utils::getGlobalMessage() - ); - - $this->invokeGlobalWarningsOnFeatures(); - - } - - /** - * @throws \API\Commons\Exceptions\AuthenticationError - * @throws \Exceptions\NotFoundException - * @throws \Exceptions\ValidationError - * @throws \TaskRunner\Exceptions\EndQueueException - * @throws \TaskRunner\Exceptions\ReQueueException - */ - private function invokeGlobalWarningsOnFeatures() { - - $this->result = $this->featureSet->filter( 'filterGlobalWarnings', $this->result, [ - 'chunk' => $this->chunk, - ] ); - - } - - /** - * Performs a check on single segment - * - * @throws Exception - */ - private function __segmentWarningsCall() { - - $this->result[ 'total' ] = 0; - - $featureSet = $this->getFeatureSet(); - $Filter = MateCatFilter::getInstance( $featureSet, $this->chunk->source, $this->chunk->target, [] ); - - $this->__postInput->src_content = $Filter->fromLayer0ToLayer2( $this->__postInput->src_content ); - $this->__postInput->trg_content = $Filter->fromLayer0ToLayer2( $this->__postInput->trg_content ); - - $QA = new QA( $this->__postInput->src_content, $this->__postInput->trg_content ); - $QA->setFeatureSet( $featureSet ); - $QA->setChunk( $this->chunk ); - $QA->setIdSegment( $this->__postInput->id ); - $QA->setSourceSegLang( $this->chunk->source ); - $QA->setTargetSegLang( $this->chunk->target ); - - if(isset($this->__postInput->characters_counter )){ - $QA->setCharactersCount($this->__postInput->characters_counter); - } - - $QA->performConsistencyCheck(); - - $this->invokeLocalWarningsOnFeatures(); - - $this->result = array_merge( $this->result, ( new QALocalWarning( $QA, $this->__postInput->id ) )->render() ); - } - - private function invokeLocalWarningsOnFeatures() { - $data = []; - $data = $this->featureSet->filter( 'filterSegmentWarnings', $data, [ - 'src_content' => $this->__postInput->src_content, - 'trg_content' => $this->__postInput->trg_content, - 'project' => $this->project, - 'chunk' => $this->chunk - ] ); - - $this->result[ 'data' ] = $data; - } - -} diff --git a/lib/Controller/loadTMXController.php b/lib/Controller/loadTMXController.php deleted file mode 100644 index 4b038729a6..0000000000 --- a/lib/Controller/loadTMXController.php +++ /dev/null @@ -1,180 +0,0 @@ - [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW - ], - 'tm_key' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'uuid' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'exec' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ] - ]; - - $postInput = (object)filter_input_array( INPUT_POST, $filterArgs ); - - $this->name = $postInput->name; - $this->tm_key = $postInput->tm_key; - $this->exec = $postInput->exec; - $this->uuid = $postInput->uuid; - - if ( empty( $this->tm_key ) ) { - - if ( empty( INIT::$DEFAULT_TM_KEY ) ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "Please specify a TM key." ]; - - return; - } - - /* - * Added the default Key. - * This means if no private key are provided the TMX will be loaded in the default MyMemory key - */ - $this->tm_key = INIT::$DEFAULT_TM_KEY; - - } - - if ( empty( $this->exec ) || !in_array( $this->exec, self::$acceptedActions ) ) { - $this->result[ 'errors' ][] = [ "code" => -7, "message" => "Action not valid." ]; - } - - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return mixed - */ - public function doAction() { - - //check if there was an error in constructor. If so, stop execution. - if ( !empty( $this->result[ 'errors' ] ) ) { - $this->result[ 'success' ] = false; - - return false; - } - - $this->result[ 'errors' ] = []; - - $this->TMService = new TMSService(); - - try { - - if ( $this->exec == "newTM" ) { - - $this->file = $this->TMService->uploadFile(); - - $uuids = []; - - foreach ( $this->file as $fileInfo ) { - - if ( AbstractFilesStorage::pathinfo_fix( strtolower( $fileInfo->name ), PATHINFO_EXTENSION ) !== 'tmx' ) { - throw new Exception( "Please upload a TMX.", -8 ); - } - - $file = new TMSFile( - $fileInfo->file_path, - $this->tm_key, - $fileInfo->name - ); - - $this->TMService->addTmxInMyMemory( $file ); - $uuids[] = [ "uuid" => $file->getUuid(), "name" => $file->getName() ]; - - $this->featureSet->run( 'postPushTMX', $file, $this->user ); - - /* - * We update the KeyRing only if this is NOT the Default MyMemory Key - * - * If it is NOT the default the key belongs to the user, so it's correct to update the user keyring. - */ - if ( $this->tm_key != INIT::$DEFAULT_TM_KEY ) { - - /* - * Update a memory key with the name of th TMX if the key name is empty - */ - $mkDao = new TmKeyManagement_MemoryKeyDao( Database::obtain() ); - $searchMemoryKey = new TmKeyManagement_MemoryKeyStruct(); - $key = new TmKeyManagement_TmKeyStruct(); - $key->key = $this->tm_key; - - $searchMemoryKey->uid = $this->user->uid; - $searchMemoryKey->tm_key = $key; - $userMemoryKey = $mkDao->read( $searchMemoryKey ); - - if ( empty( $userMemoryKey[ 0 ]->tm_key->name ) && !empty( $userMemoryKey ) ) { - $userMemoryKey[ 0 ]->tm_key->name = $fileInfo->name; - $mkDao->atomicUpdate( $userMemoryKey[ 0 ] ); - } - - } - - } - - $this->result[ 'data' ][ 'uuids' ] = $uuids; - - } else { - - $status = $this->TMService->tmxUploadStatus( $this->uuid ); - $this->result[ 'data' ] = $status[ 'data' ]; - - } - - $this->result[ 'success' ] = true; - - } catch ( Exception $e ) { - $this->result[ 'success' ] = false; - $this->result[ 'errors' ][] = [ "code" => $e->getCode(), "message" => $e->getMessage() ]; - } - - } - -} \ No newline at end of file diff --git a/lib/Controller/outsourceToController.php b/lib/Controller/outsourceToController.php deleted file mode 100644 index 815e68a07a..0000000000 --- a/lib/Controller/outsourceToController.php +++ /dev/null @@ -1,190 +0,0 @@ - - * Ex: - * array( - * 0 => array( - * 'id' => 5901, - * 'jpassword' => '6decb661a182', - * ), - * ); - * - * - * @var array - */ - private $jobList; - - /** - * Class constructor, validate/sanitize incoming params - * - */ - public function __construct() { - - //SESSION ENABLED - parent::__construct(); - - $filterArgs = array( - 'pid' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'ppassword' => array( 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ), - 'currency' => array( 'filter' => FILTER_SANITIZE_STRING ), - 'timezone' => array( 'filter' => FILTER_SANITIZE_STRING ), - 'fixedDelivery' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'typeOfService' => array( 'filter' => FILTER_SANITIZE_STRING ), - 'jobs' => array( 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_REQUIRE_ARRAY | FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ), - ); - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI - //$__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->pid = $__postInput[ 'pid' ]; - $this->ppassword = $__postInput[ 'ppassword' ]; - $this->currency = $__postInput[ 'currency' ]; - $this->timezone = $__postInput[ 'timezone' ]; - $this->fixedDelivery = $__postInput[ 'fixedDelivery' ]; - $this->typeOfService = $__postInput[ 'typeOfService' ]; - $this->jobList = $__postInput[ 'jobs' ]; - - if( empty( $this->pid ) ){ - $this->result[ 'errors' ][] = array( "code" => -1, "message" => "No id project provided" ); - } - - if( empty( $this->ppassword ) ){ - $this->result[ 'errors' ][] = array( "code" => -2, "message" => "No project Password Provided" ); - } - - if ( empty( $this->currency ) ) { - $this->currency = @$_COOKIE[ "matecat_currency" ]; - } - - if ( empty( $this->timezone ) && $this->timezone !== "0" ) { - $this->timezone = @$_COOKIE[ "matecat_timezone" ]; - } - - if ( !in_array( $this->typeOfService, array( "premium" , "professional") ) ) { - $this->typeOfService = "professional"; - } - - // Log::doJsonLog( $this->jobList ); - /** - * The Job List form - * - *
-         * Ex:
-         *   array(
-         *      0 => array(
-         *          'id' => 5901,
-         *          'jpassword' => '6decb661a182',
-         *      ),
-         *   );
-         * 
- */ - if( empty( $this->jobList ) ){ - $this->result[ 'errors' ][] = array( "code" => -3, "message" => "No job list Provided" ); - } - - } - - /** - * Perform Controller Action - * - * @return int|null - */ - public function doAction() { - - if( !empty( $this->result[ 'errors' ] ) ){ - return -1; // ERROR - } - - $outsourceTo = new OutsourceTo_Translated(); - $outsourceTo->setPid( $this->pid ) - ->setPpassword( $this->ppassword ) - ->setCurrency( $this->currency ) - ->setTimezone( $this->timezone ) - ->setJobList( $this->jobList ) - ->setFixedDelivery( $this->fixedDelivery ) - ->setTypeOfService( $this->typeOfService ) - ->performQuote(); - - /* - * Example: - * - * $client_output = array ( - * '5901-6decb661a182' => - * array ( - * 'id' => '5901-6decb661a182', - * 'quantity' => '1', - * 'name' => 'MATECAT_5901-6decb661a182', - * 'quote_pid' => '11180933', - * 'source' => 'it-IT', - * 'target' => 'en-GB', - * 'price' => '12.00', - * 'words' => '120', - * 'show_info' => '0', - * 'delivery_date' => '2014-04-29T15:00:00Z', - * ), - * ); - */ - $client_output = $outsourceTo->getQuotesResult(); -// Log::doJsonLog( $client_output ); - - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = array_values( $client_output ); - $this->result[ 'return_url' ] = array( - 'url_ok' => $outsourceTo->getOutsourceLoginUrlOk(), - 'url_ko' => $outsourceTo->getOutsourceLoginUrlKo(), - 'confirm_urls' => $outsourceTo->getOutsourceConfirm(), - ); - - } - -} diff --git a/lib/Controller/setCurrentSegmentController.php b/lib/Controller/setCurrentSegmentController.php deleted file mode 100644 index b5cd197677..0000000000 --- a/lib/Controller/setCurrentSegmentController.php +++ /dev/null @@ -1,127 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->revision_number = $__postInput[ 'revision_number' ]; - $this->id_segment = $__postInput[ 'id_segment' ]; - $this->id_job = (int)$__postInput[ 'id_job' ]; - $this->password = $__postInput[ 'password' ]; - - } - - public function doAction() { - - $this->parseIDSegment(); - - //get Job Info, we need only a row of jobs ( split ) - $job_data = Jobs_JobDao::getByIdAndPassword( $this->id_job, $this->password ); - - if ( empty( $job_data ) ) { - $this->result[ 'errors' ][] = [ "code" => -10, "message" => "wrong password" ]; - } - - if ( empty( $this->id_segment ) ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "missing segment id" ]; - } - - if ( !empty( $this->result[ 'errors' ] ) ) { - //no action on errors - return; - } - - $segmentStruct = new TranslationsSplit_SplitStruct(); - $segmentStruct->id_segment = (int)$this->id_segment; - $segmentStruct->id_job = $this->id_job; - - $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); - $currSegmentInfo = $translationDao->read( $segmentStruct ); - - /** - * Split check control - */ - $isASplittedSegment = false; - $isLastSegmentChunk = true; - - if ( count( $currSegmentInfo ) > 0 ) { - - $isASplittedSegment = true; - $currSegmentInfo = array_shift( $currSegmentInfo ); - - //get the chunk number and check whether it is the last one or not - $isLastSegmentChunk = ( $this->split_num == count( $currSegmentInfo->source_chunk_lengths ) - 1 ); - - if ( !$isLastSegmentChunk ) { - $nextSegmentId = $this->id_segment . "-" . ( $this->split_num + 1 ); - } - } - - /** - * End Split check control - */ - if ( !$isASplittedSegment || $isLastSegmentChunk ) { - - $segmentList = Segments_SegmentDao::getNextSegment( $this->id_segment, $this->id_job, $this->password, $this->revision_number ); - - if ( !$this->revision_number ) { - $nextSegmentId = CatUtils::fetchStatus( $this->id_segment, $segmentList ); - } else { - $nextSegmentId = CatUtils::fetchStatus( $this->id_segment, $segmentList, Constants_TranslationStatus::STATUS_TRANSLATED ); - if ( !$nextSegmentId ) { - $nextSegmentId = CatUtils::fetchStatus( $this->id_segment, $segmentList, Constants_TranslationStatus::STATUS_APPROVED ); - } - } - } - - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = []; - - $this->result[ 'nextSegmentId' ] = $nextSegmentId; - - } - - private static function prepareReviseStructReturnValues( $struct ) { - $return = []; - - $reflect = new ReflectionClass( 'Constants_Revise' ); - $constCache = $reflect->getConstants(); - foreach ( $constCache as $key => $val ) { - if ( strpos( $key, "ERR_" ) === false ) { - unset( $constCache[ $key ] ); - } - } - - $constCache_keys = array_map( "strtolower", array_keys( $constCache ) ); - - foreach ( $struct as $key => $val ) { - if ( in_array( $key, $constCache_keys ) ) { - - $return[] = [ - 'type' => $constCache[ strtoupper( $key ) ], - 'value' => Constants_Revise::$const2clientValues[ $val ] - ]; - } - } - - return $return; - } -} \ No newline at end of file diff --git a/lib/Controller/setSegmentSplitController.php b/lib/Controller/setSegmentSplitController.php deleted file mode 100644 index 39512e6c54..0000000000 --- a/lib/Controller/setSegmentSplitController.php +++ /dev/null @@ -1,134 +0,0 @@ -identifyUser(); - //Session Disabled - - $filterArgs = [ - 'id_job' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'segment' => [ - 'filter' => FILTER_UNSAFE_RAW - ], - 'target' => [ - 'filter' => FILTER_UNSAFE_RAW - ], - 'exec' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ] - ]; - - $postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_job = $postInput[ 'id_job' ]; - $this->id_segment = $postInput[ 'id_segment' ]; - $this->job_pass = $postInput[ 'password' ]; - $this->segment = $postInput[ 'segment' ]; - $this->target = $postInput[ 'target' ]; - $this->exec = $postInput[ 'exec' ]; - -// if ( !$this->userIsLogged ) { -// $this->result[ 'errors' ][ ] = array( -// 'code' => -2, -// 'message' => "Login is required to perform this action" -// ); -// } - - if ( empty( $this->id_job ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -3, - 'message' => 'Invalid job id' - ]; - } - - if ( empty( $this->id_segment ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -4, - 'message' => 'Invalid segment id' - ]; - } - - if ( empty( $this->job_pass ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -5, - 'message' => 'Invalid job password' - ]; - } - - //this checks that the json is valid, but not its content - if ( is_null( $this->segment ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -6, - 'message' => 'Invalid source_chunk_lengths json' - ]; - } - - //check Job password - $this->jobStruct = Chunks_ChunkDao::getByIdAndPassword( $this->id_job, $this->job_pass ); - $this->featureSet->loadForProject( $this->jobStruct->getProject() ); - - } - - public function doAction() { - - if ( !empty( $this->result[ 'errors' ] ) ) { - return; - } - - //save the 2 arrays in the DB - - $translationStruct = TranslationsSplit_SplitStruct::getStruct(); - - $translationStruct->id_segment = $this->id_segment; - $translationStruct->id_job = $this->id_job; - - $featureSet = $this->getFeatureSet(); - - /** @var MateCatFilter $Filter */ - $Filter = MateCatFilter::getInstance( $featureSet, $this->jobStruct->source, $this->jobStruct->target, [] ); - list( $this->segment, $translationStruct->source_chunk_lengths ) = CatUtils::parseSegmentSplit( $this->segment, '', $Filter ); - - /* Fill the statuses with DEFAULT DRAFT VALUES */ - $pieces = ( count( $translationStruct->source_chunk_lengths ) > 1 ? count( $translationStruct->source_chunk_lengths ) - 1 : 1 ); - $translationStruct->target_chunk_lengths = [ - 'len' => [ 0 ], - 'statuses' => array_fill( 0, $pieces, Constants_TranslationStatus::STATUS_DRAFT ) - ]; - - $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); - $result = $translationDao->atomicUpdate( $translationStruct ); - - if ( $result instanceof TranslationsSplit_SplitStruct ) { - //return success - $this->result[ 'data' ] = 'OK'; - } else { - Log::doJsonLog( "Failed while splitting/merging segment." ); - Log::doJsonLog( $translationStruct ); - } - } - -} - - diff --git a/lib/Controller/setTranslationController.php b/lib/Controller/setTranslationController.php deleted file mode 100644 index d484d34bd5..0000000000 --- a/lib/Controller/setTranslationController.php +++ /dev/null @@ -1,950 +0,0 @@ - [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'current_password' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'propagate' => [ - 'filter' => FILTER_VALIDATE_BOOLEAN, 'flags' => FILTER_NULL_ON_FAILURE - ], - 'id_segment' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'time_to_edit' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_translator' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'translation' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'segment' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'version' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'chosen_suggestion_index' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'suggestion_array' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'status' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'splitStatuses' => [ - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'context_before' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'context_after' => [ 'filter' => FILTER_UNSAFE_RAW ], - 'id_before' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'id_after' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'revision_number' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ], - 'guess_tag_used' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'characters_counter' => [ 'filter' => FILTER_SANITIZE_NUMBER_INT ] - ]; - - $this->__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - $this->id_job = $this->__postInput[ 'id_job' ]; - $this->password = $this->__postInput[ 'password' ]; - $this->received_password = $this->__postInput[ 'current_password' ]; - $this->revisionNumber = $this->__postInput[ 'revision_number' ]; - - /* - * set by the client, mandatory - * check propagation flag, if it is null the client not sent it, leave default true, otherwise set the value - */ - !is_null( $this->__postInput[ 'propagate' ] ) ? $this->propagate = $this->__postInput[ 'propagate' ] : null /* do nothing */ - ; - - $this->id_segment = $this->__postInput[ 'id_segment' ]; - $this->id_before = $this->__postInput[ 'id_before' ]; - $this->id_after = $this->__postInput[ 'id_after' ]; - - $this->time_to_edit = (int)$this->__postInput[ 'time_to_edit' ]; //cast to int, so the default is 0 - $this->id_translator = $this->__postInput[ 'id_translator' ]; - $this->client_target_version = ( empty( $this->__postInput[ 'version' ] ) ? '0' : $this->__postInput[ 'version' ] ); - - $this->chosen_suggestion_index = $this->__postInput[ 'chosen_suggestion_index' ]; - $this->suggestion_array = $this->__postInput[ 'suggestion_array' ]; - - $this->status = strtoupper( $this->__postInput[ 'status' ] ); - $this->split_statuses = explode( ",", strtoupper( $this->__postInput[ 'splitStatuses' ] ) ); //strtoupper transforms null to "" - - Log::doJsonLog( $this->__postInput ); - - } - - /** - * @return bool - */ - private function isSplittedSegment() { - return !empty( $this->split_statuses[ 0 ] ) && !empty( $this->split_num ); - } - - /** - * setStatusForSplittedSegment - * - * If splitted segments have different statuses, we reset status - * to draft. - */ - private function setStatusForSplittedSegment() { - if ( count( array_unique( $this->split_statuses ) ) == 1 ) { - // IF ALL translation chunks are in the same status, - // we take the status for the entire segment - $this->status = $this->split_statuses[ 0 ]; - } else { - $this->status = Constants_TranslationStatus::STATUS_DRAFT; - } - } - - protected function _checkData() { - - if ( empty( $this->id_job ) ) { - $this->result[ 'errors' ][] = [ "code" => -2, "message" => "missing id_job" ]; - } else { - - //get Job Info, we need only a row of jobs ( split ) - $this->chunk = Chunks_ChunkDao::getByIdAndPassword( (int)$this->id_job, $this->password ); - - if ( empty( $this->chunk ) ) { - $this->result[ 'errors' ][] = [ "code" => -10, "message" => "wrong password" ]; - } - - //add check for job status archived. - if ( $this->chunk->isArchived() ) { - $this->result[ 'errors' ][] = [ "code" => -3, "message" => "job archived" ]; - } - - $this->project = $this->chunk->getProject(); - - $featureSet = $this->getFeatureSet(); - $featureSet->loadForProject( $this->project ); - - /** @var MateCatFilter $filter */ - $this->filter = MateCatFilter::getInstance( $featureSet, $this->chunk->source, $this->chunk->target, Segments_SegmentOriginalDataDao::getSegmentDataRefMap( $this->id_segment ) ); - } - - //ONE OR MORE ERRORS OCCURRED : EXITING - if ( !empty( $this->result[ 'errors' ] ) ) { - $msg = "Error \n\n " . var_export( array_merge( $this->result, $_POST ), true ); - throw new Exception( $msg, -1 ); - } - - - [ $__translation, $this->split_chunk_lengths ] = CatUtils::parseSegmentSplit( $this->__postInput[ 'translation' ], '', $this->filter ); - - if ( is_null( $__translation ) || $__translation === '' ) { - Log::doJsonLog( "Empty Translation \n\n" . var_export( $_POST, true ) ); - - // won't save empty translation but there is no need to return an errors - throw new Exception( "Empty Translation \n\n" . var_export( $_POST, true ), 0 ); - } - - $this->parseIDSegment(); - - if ( empty( $this->id_segment ) ) { - $this->result[ 'errors' ][] = [ "code" => -1, "message" => "missing id_segment" ]; - } - - if ( $this->isSplittedSegment() ) { - $this->setStatusForSplittedSegment(); - } - - $this->checkStatus( $this->status ); - - } - - /** - * Throws exception if status is not valid. - * - * @param $status - * - * @throws Exception - */ - protected function checkStatus( $status ) { - - switch ( $status ) { - case Constants_TranslationStatus::STATUS_TRANSLATED: - case Constants_TranslationStatus::STATUS_APPROVED: - case Constants_TranslationStatus::STATUS_APPROVED2: - case Constants_TranslationStatus::STATUS_REJECTED: - case Constants_TranslationStatus::STATUS_DRAFT: - case Constants_TranslationStatus::STATUS_NEW: - case Constants_TranslationStatus::STATUS_FIXED: - case Constants_TranslationStatus::STATUS_REBUTTED: - break; - - default: - //NO debug and NO-actions for un-mapped status - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = "OK"; - - $msg = "Error Hack Status \n\n " . var_export( $_POST, true ); - throw new Exception( $msg, -1 ); - break; - } - - } - - /** - * @throws \API\Commons\Exceptions\AuthenticationError - * @throws \Exceptions\ValidationError - * @throws \TaskRunner\Exceptions\EndQueueException - * @throws \TaskRunner\Exceptions\ReQueueException - * @throws \Exceptions\NotFoundException - */ - protected function _getContexts() { - - //Get contexts - $segmentsList = ( new Segments_SegmentDao )->setCacheTTL( 60 * 60 * 24 )->getContextAndSegmentByIDs( - [ - 'id_before' => $this->id_before, - 'id_segment' => $this->id_segment, - 'id_after' => $this->id_after - ] - ); - - $this->featureSet->filter( 'rewriteContributionContexts', $segmentsList, $this->__postInput ); - - if ( isset( $segmentsList->id_before->segment ) ) { - $this->context_before = $this->filter->fromLayer0ToLayer1( $segmentsList->id_before->segment ); - } - - if ( isset( $segmentsList->id_after->segment ) ) { - $this->context_after = $this->filter->fromLayer0ToLayer1( $segmentsList->id_after->segment ); - } - } - - /** - * @return int|mixed - * @throws Exception - */ - public function doAction() { - $this->checkData(); - $this->identifyUser(); - $this->initVersionHandler(); - $this->_getContexts(); - - //check tag mismatch - //get original source segment, first - $dao = new \Segments_SegmentDao( \Database::obtain() ); - $this->segment = $dao->getById( $this->id_segment ); - - $segment = $this->filter->fromLayer0ToLayer2( $this->__postInput[ 'segment' ] ); - $translation = $this->filter->fromLayer0ToLayer2( $this->__postInput[ 'translation' ] ); - - $check = new QA( $segment, $translation ); - $check->setChunk( $this->chunk ); - $check->setFeatureSet( $this->featureSet ); - $check->setSourceSegLang( $this->chunk->source ); - $check->setTargetSegLang( $this->chunk->target ); - $check->setIdSegment( $this->id_segment ); - - if ( isset( $this->__postInput[ 'characters_counter' ] ) ) { - $check->setCharactersCount( $this->__postInput[ 'characters_counter' ] ); - } - - $check->performConsistencyCheck(); - - if ( $check->thereAreWarnings() ) { - $err_json = $check->getWarningsJSON(); - $translation = $this->filter->fromLayer2ToLayer0( $this->__postInput[ 'translation' ] ); - } else { - $err_json = ''; - $targetNormalized = $check->getTrgNormalized(); - $translation = $this->filter->fromLayer2ToLayer0( $targetNormalized ); - } - - //PATCH TO FIX BOM INSERTIONS - $translation = Utils::stripBOM( $translation ); - - /* - * begin stats counter - * - * It works good with default InnoDB Isolation level - * - * REPEATABLE-READ offering a row level lock for this id_segment - * - */ - $db = Database::obtain(); - $db->begin(); - - $old_translation = $this->_getOldTranslation(); - - $old_suggestion_array = json_decode( $this->suggestion_array ); - $old_suggestion = ( $this->chosen_suggestion_index !== null ? @$old_suggestion_array[ $this->chosen_suggestion_index - 1 ] : null ); - - $new_translation = new Translations_SegmentTranslationStruct(); - $new_translation->id_segment = $this->id_segment; - $new_translation->id_job = $this->id_job; - $new_translation->status = $this->status; - $new_translation->segment_hash = $this->segment->segment_hash; - $new_translation->translation = $translation; - $new_translation->serialized_errors_list = $err_json; - $new_translation->suggestions_array = ( $this->chosen_suggestion_index !== null ? $this->suggestion_array : $old_translation->suggestions_array ); - $new_translation->suggestion_position = ( $this->chosen_suggestion_index !== null ? $this->chosen_suggestion_index : $old_translation->suggestion_position ); - $new_translation->warning = $check->thereAreWarnings(); - $new_translation->translation_date = date( "Y-m-d H:i:s" ); - $new_translation->suggestion = ( ( !empty( $old_suggestion ) ) ? $old_suggestion->translation : $old_translation->suggestion ); - $new_translation->suggestion_source = $old_translation->suggestion_source; - $new_translation->suggestion_match = $old_translation->suggestion_match; - - // update suggestion - if ( $this->canUpdateSuggestion( $new_translation, $old_translation, $old_suggestion ) ) { - $new_translation->suggestion = $old_suggestion->translation; - - // update suggestion match - if ( $old_suggestion->match == "MT" ) { - // case 1. is MT - $new_translation->suggestion_match = 85; - $new_translation->suggestion_source = 'MT'; - } elseif ( $old_suggestion->match == 'NO_MATCH' ) { - // case 2. no match - $new_translation->suggestion_source = 'NO_MATCH'; - } else { - // case 3. otherwise is TM - $new_translation->suggestion_match = $old_suggestion->match; - $new_translation->suggestion_source = 'TM'; - } - } - - // time_to_edit should be increased only if the translation was changed - $new_translation->time_to_edit = 0; - if ( false === Utils::stringsAreEqual( $new_translation->translation, $old_translation->translation ?? '' ) ) { - $new_translation->time_to_edit = $this->time_to_edit; - } - - /** - * Update Time to Edit and - * - * Evaluate new Avg post-editing effort for the job: - * - get old translation - * - get suggestion - * - evaluate $_seg_oldPEE and normalize it on the number of words for this segment - * - * - get new translation - * - evaluate $_seg_newPEE and normalize it on the number of words for this segment - * - * - get $_jobTotalPEE - * - evaluate $_jobTotalPEE - $_seg_oldPEE + $_seg_newPEE and save it into the job's row - */ - $this->updateJobPEE( $old_translation->toArray(), $new_translation->toArray() ); - - // if saveVersionAndIncrement() return true it means that it was persisted a new version of the parent segment - if ( $this->VersionsHandler !== null ) { - $this->VersionsHandler->saveVersionAndIncrement( $new_translation, $old_translation ); - } - - /** - * when the status of the translation changes, the auto propagation flag - * must be removed - */ - if ( $new_translation->translation != $old_translation->translation || - $this->status == Constants_TranslationStatus::STATUS_TRANSLATED || - $this->status == Constants_TranslationStatus::STATUS_APPROVED || - $this->status == Constants_TranslationStatus::STATUS_APPROVED2 - ) { - $new_translation->autopropagated_from = 'NULL'; - } - - /** - * Translation is inserted here. - */ - try { - CatUtils::addSegmentTranslation( $new_translation, self::isRevision() ); - } catch ( ControllerReturnException $e ) { - $db->rollback(); - throw $e; - } - - /** - * @see ProjectCompletion - */ - $this->featureSet->run( 'postAddSegmentTranslation', [ - 'chunk' => $this->chunk, - 'is_review' => $this->isRevision(), - 'logged_user' => $this->user - ] ); - - $propagationTotal = [ - 'totals' => [], - 'propagated_ids' => [], - 'segments_for_propagation' => [] - ]; - - if ( $this->propagate && in_array( $this->status, [ - Constants_TranslationStatus::STATUS_TRANSLATED, - Constants_TranslationStatus::STATUS_APPROVED, - Constants_TranslationStatus::STATUS_APPROVED2, - Constants_TranslationStatus::STATUS_REJECTED - ] ) - ) { - //propagate translations - $TPropagation = new Translations_SegmentTranslationStruct(); - $TPropagation[ 'status' ] = $this->status; - $TPropagation[ 'id_job' ] = $this->id_job; - $TPropagation[ 'translation' ] = $translation; - $TPropagation[ 'autopropagated_from' ] = $this->id_segment; - $TPropagation[ 'serialized_errors_list' ] = $err_json; - $TPropagation[ 'warning' ] = $check->thereAreWarnings(); - $TPropagation[ 'segment_hash' ] = $old_translation[ 'segment_hash' ]; - $TPropagation[ 'translation_date' ] = Utils::mysqlTimestamp( time() ); - $TPropagation[ 'match_type' ] = $old_translation[ 'match_type' ]; - $TPropagation[ 'locked' ] = $old_translation[ 'locked' ]; - - try { - - if ( $this->VersionsHandler !== null ) { - $propagationTotal = Translations_SegmentTranslationDao::propagateTranslation( - $TPropagation, - $this->chunk, - $this->id_segment, - $this->project, - $this->VersionsHandler - ); - } - - } catch ( Exception $e ) { - $msg = $e->getMessage() . "\n\n" . $e->getTraceAsString(); - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - $db->rollback(); - throw new ControllerReturnException( $e->getMessage(), $e->getCode(), $e ); - } - } - - if ( $this->isSplittedSegment() ) { - - /* put the split inside the transaction if they are present */ - $translationStruct = TranslationsSplit_SplitStruct::getStruct(); - $translationStruct->id_segment = $this->id_segment; - $translationStruct->id_job = $this->id_job; - - $translationStruct->target_chunk_lengths = [ - 'len' => $this->split_chunk_lengths, - 'statuses' => $this->split_statuses - ]; - - $translationDao = new TranslationsSplit_SplitDAO( Database::obtain() ); - $translationDao->atomicUpdate( $translationStruct ); - - } - - //COMMIT THE TRANSACTION - try { - - /* - * Hooked by TranslationVersions which manage translation versions - * - * This is also the init handler of all R1/R2 handling and Qr score calculation by - * by TranslationEventsHandler and BatchReviewProcessor - */ - if ( $this->VersionsHandler !== null ) { - $this->VersionsHandler->storeTranslationEvent( [ - 'translation' => $new_translation, - 'old_translation' => $old_translation, - 'propagation' => $propagationTotal, - 'chunk' => $this->chunk, - 'segment' => $this->segment, - 'user' => $this->user, - 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->revisionNumber ), - 'features' => $this->featureSet, - 'project' => $this->project - ] ); - } - - $db->commit(); - - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = [ "code" => -101, "message" => $e->getMessage() ]; - Log::doJsonLog( "Lock: Transaction Aborted. " . $e->getMessage() ); - $db->rollback(); - - throw $e; - } - - $newTotals = WordCountStruct::loadFromJob( $this->chunk ); - - $job_stats = CatUtils::getFastStatsForJob( $newTotals ); - $job_stats[ 'analysis_complete' ] = ( - $this->project[ 'status_analysis' ] == Constants_ProjectStatus::STATUS_DONE || - $this->project[ 'status_analysis' ] == Constants_ProjectStatus::STATUS_NOT_TO_ANALYZE - ); - - $file_stats = []; - - $this->result[ 'stats' ] = $job_stats; - $this->result[ 'file_stats' ] = $file_stats; - $this->result[ 'code' ] = 1; - $this->result[ 'data' ] = "OK"; - $this->result[ 'version' ] = date_create( $new_translation[ 'translation_date' ] )->getTimestamp(); - $this->result[ 'translation' ] = $this->getTranslationObject( $new_translation ); - - /* FIXME: added for code compatibility with front-end. Remove. */ - $_warn = $check->getWarnings(); - $warning = $_warn[ 0 ]; - /* */ - - $this->result[ 'warning' ][ 'cod' ] = $warning->outcome; - if ( $warning->outcome > 0 ) { - $this->result[ 'warning' ][ 'id' ] = $this->id_segment; - } else { - $this->result[ 'warning' ][ 'id' ] = 0; - } - - try { - - $this->featureSet->run( 'setTranslationCommitted', [ - 'translation' => $new_translation, - 'old_translation' => $old_translation, - 'propagated_ids' => isset( $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] ) ? $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] : null, - 'chunk' => $this->chunk, - 'segment' => $this->segment, - 'user' => $this->user, - 'source_page_code' => ReviewUtils::revisionNumberToSourcePage( $this->revisionNumber ) - ] ); - - } catch ( Exception $e ) { - Log::doJsonLog( "Exception in setTranslationCommitted callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); - } - - try { - $this->result = $this->featureSet->filter( 'filterSetTranslationResult', $this->result, [ - 'translation' => $new_translation, - 'old_translation' => $old_translation, - 'propagated_ids' => isset( $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] ) ? $propagationTotal[ 'segments_for_propagation' ][ 'propagated_ids' ] : null, - 'chunk' => $this->chunk, - 'segment' => $this->segment - ] ); - } catch ( Exception $e ) { - Log::doJsonLog( "Exception in filterSetTranslationResult callback . " . $e->getMessage() . "\n" . $e->getTraceAsString() ); - } - - //EVERY time an user changes a row in his job when the job is completed, - // a query to do the update is executed... - // Avoid this by setting a key on redis with a reasonable TTL - $redisHandler = new RedisHandler(); - $job_status = $redisHandler->getConnection()->get( 'job_completeness:' . $this->id_job ); - if ( - ( - ( - $job_stats[ Projects_MetadataDao::WORD_COUNT_RAW ][ 'draft' ] + - $job_stats[ Projects_MetadataDao::WORD_COUNT_RAW ][ 'new' ] == 0 - ) - && empty( $job_status ) - ) - ) { - $redisHandler->getConnection()->setex( 'job_completeness:' . $this->id_job, 60 * 60 * 24 * 15, true ); //15 days - - try { - $update_completed = Jobs_JobDao::setJobComplete( $this->chunk ); - } catch ( Exception $ignore ) { - } - - if ( empty( $update_completed ) ) { - $msg = "\n\n Error setJobCompleteness \n\n " . var_export( $_POST, true ); - $redisHandler->getConnection()->del( 'job_completeness:' . $this->id_job ); - Log::doJsonLog( $msg ); - Utils::sendErrMailReport( $msg ); - } - - } - - $this->result[ 'propagation' ] = $propagationTotal; - $this->evalSetContribution( $new_translation, $old_translation ); - } - - /** - * Update suggestion only if: - * - * 1) the new state is one of these: - * - NEW - * - DRAFT - * - TRANSLATED - * - * 2) the old state is one of these: - * - NEW - * - DRAFT - * - * @param Translations_SegmentTranslationStruct $new_translation - * @param Translations_SegmentTranslationStruct $old_translation - * @param null $old_suggestion - * - * @return bool - */ - private function canUpdateSuggestion( Translations_SegmentTranslationStruct $new_translation, Translations_SegmentTranslationStruct $old_translation, $old_suggestion = null ) { - if ( $old_suggestion === null ) { - return false; - } - - $allowedStatuses = [ - Constants_TranslationStatus::STATUS_NEW, - Constants_TranslationStatus::STATUS_DRAFT, - Constants_TranslationStatus::STATUS_TRANSLATED, - ]; - - if ( !in_array( $new_translation->status, $allowedStatuses ) ) { - return false; - } - - if ( !in_array( $old_translation->status, $allowedStatuses ) ) { - return false; - } - - if ( - !empty( $old_suggestion ) and - isset( $old_suggestion->translation ) and - isset( $old_suggestion->match ) and - isset( $old_suggestion->created_by ) - ) { - return true; - } - - return false; - } - - /** - * @return int|mixed - */ - protected function checkData() { - try { - $this->_checkData(); - } catch ( Exception $e ) { - - if ( $e->getCode() == -1 ) { - Utils::sendErrMailReport( $e->getMessage() ); - } - - Log::doJsonLog( $e->getMessage() ); - - return $e->getCode(); - - } - } - - /** - * @return Translations_SegmentTranslationStruct - * @throws ControllerReturnException - */ - protected function _getOldTranslation() { - $old_translation = Translations_SegmentTranslationDao::findBySegmentAndJob( $this->id_segment, $this->id_job ); - - if ( empty( $old_translation ) ) { - $old_translation = new Translations_SegmentTranslationStruct(); - } // $old_translation if `false` sometimes - - - // If volume analysis is not enabled and no translation rows exists, create the row - if ( !INIT::$VOLUME_ANALYSIS_ENABLED && empty( $old_translation[ 'status' ] ) ) { - $translation = new Translations_SegmentTranslationStruct(); - $translation->id_segment = (int)$this->id_segment; - $translation->id_job = (int)$this->id_job; - $translation->status = Constants_TranslationStatus::STATUS_NEW; - - $translation->segment_hash = $this->segment[ 'segment_hash' ]; - $translation->translation = $this->segment[ 'segment' ]; - $translation->standard_word_count = $this->segment[ 'raw_word_count' ]; - - $translation->serialized_errors_list = ''; - $translation->suggestion_position = 0; - $translation->warning = false; - $translation->translation_date = date( "Y-m-d H:i:s" ); - - try { - CatUtils::addSegmentTranslation( $translation, self::isRevision() ); - } catch ( ControllerReturnException $e ) { - Database::obtain()->rollback(); - throw $e; - } - - $old_translation = $translation; - } - - return $old_translation; - } - - /** - * This method returns a representation of the saved translation which - * should be as much as possible compliant with the future API v2. - * - * @param $saved_translation - * - * @return array - * @throws Exception - */ - private function getTranslationObject( $saved_translation ) { - return [ - 'version_number' => @$saved_translation[ 'version_number' ], - 'sid' => $saved_translation[ 'id_segment' ], - 'translation' => $this->filter->fromLayer0ToLayer2( $saved_translation[ 'translation' ] ), - 'status' => $saved_translation[ 'status' ] - - ]; - } - - private function recountJobTotals( $old_status ) { - $old_wStruct = new WordCountStruct(); - $old_wStruct->setIdJob( $this->id_job ); - $old_wStruct->setJobPassword( $this->password ); - $old_wStruct->setNewWords( $this->chunk[ 'new_words' ] ); - $old_wStruct->setDraftWords( $this->chunk[ 'draft_words' ] ); - $old_wStruct->setTranslatedWords( $this->chunk[ 'translated_words' ] ); - $old_wStruct->setApprovedWords( $this->chunk[ 'approved_words' ] ); - $old_wStruct->setRejectedWords( $this->chunk[ 'rejected_words' ] ); - - $old_wStruct->setIdSegment( $this->id_segment ); - - //redundant, this is made into CounterModel::updateDB - $old_wStruct->setOldStatus( $old_status ); - $old_wStruct->setNewStatus( $this->status ); - - return $old_wStruct; - } - - private function updateJobPEE( array $old_translation, array $new_translation ) { - - //update total time to edit - $tte = $old_translation[ 'time_to_edit' ]; - if ( !self::isRevision() ) { - if ( !Utils::stringsAreEqual( $new_translation[ 'translation' ], $old_translation[ 'translation' ] ?? '' ) ) { - $tte += $new_translation[ 'time_to_edit' ]; - } - } - - $segmentRawWordCount = $this->segment->raw_word_count; - $editLogSegmentStruct = new EditLogSegmentStruct( - [ - 'suggestion' => $old_translation[ 'suggestion' ], - 'translation' => $old_translation[ 'translation' ], - 'raw_word_count' => $segmentRawWordCount, - 'time_to_edit' => $old_translation[ 'time_to_edit' ] + $new_translation[ 'time_to_edit' ] - ] - ); - - $oldSegmentStatus = clone $editLogSegmentStruct; - $oldSegmentStatus->time_to_edit = $old_translation[ 'time_to_edit' ]; - - $oldPEE = $editLogSegmentStruct->getPEE(); - $oldPee_weighted = $oldPEE * $segmentRawWordCount; - - $editLogSegmentStruct->translation = $new_translation[ 'translation' ]; - - $newPEE = $editLogSegmentStruct->getPEE(); - $newPee_weighted = $newPEE * $segmentRawWordCount; - - if ( $editLogSegmentStruct->isValidForEditLog() ) { - //if the segment was not valid for editlog, and now it is, then just add the weighted pee - if ( !$oldSegmentStatus->isValidForEditLog() ) { - $newTotalJobPee = ( $this->chunk[ 'avg_post_editing_effort' ] + $newPee_weighted ); - } //otherwise, evaluate it normally - else { - $newTotalJobPee = ( $this->chunk[ 'avg_post_editing_effort' ] - $oldPee_weighted + $newPee_weighted ); - } - - Jobs_JobDao::updateFields( - - [ 'avg_post_editing_effort' => $newTotalJobPee, 'total_time_to_edit' => $tte ], - [ - 'id' => $this->id_job, - 'password' => $this->password - ] ); - - } //segment was valid but now it is no more valid - elseif ( $oldSegmentStatus->isValidForEditLog() ) { - $newTotalJobPee = ( $this->chunk[ 'avg_post_editing_effort' ] - $oldPee_weighted ); - - Jobs_JobDao::updateFields( - [ 'avg_post_editing_effort' => $newTotalJobPee, 'total_time_to_edit' => $tte ], - [ - 'id' => $this->id_job, - 'password' => $this->password - ] ); - } else { - Jobs_JobDao::updateFields( - [ 'total_time_to_edit' => $tte ], - [ - 'id' => $this->id_job, - 'password' => $this->password - ] ); - } - - } - - /** - * init VersionHandler - */ - private function initVersionHandler() { - - // fix null pointer error - if ( - $this->chunk !== null and - $this->id_segment !== null and - $this->user !== null and - $this->project !== null - ) { - $this->VersionsHandler = TranslationVersions::getVersionHandlerNewInstance( $this->chunk, $this->id_segment, $this->user, $this->project ); - } - } - - /** - * @param $_Translation - * @param $old_translation - * - * @throws Exception - * @throws NotFoundException - * @throws \API\Commons\Exceptions\AuthenticationError - * @throws \Exceptions\ValidationError - */ - private function evalSetContribution( $_Translation, $old_translation ) { - if ( in_array( $this->status, [ - Constants_TranslationStatus::STATUS_DRAFT, - Constants_TranslationStatus::STATUS_NEW - ] ) ) { - return; - } - - $skip_set_contribution = false; - $skip_set_contribution = $this->featureSet->filter( 'filter_skip_set_contribution', - $skip_set_contribution, $_Translation, $old_translation - ); - - if ( $skip_set_contribution ) { - return; - } - - $ownerUid = Jobs_JobDao::getOwnerUid( $this->id_job, $this->password ); - $filesParts = ( new FilesPartsDao() )->getBySegmentId( $this->id_segment ); - - /** - * Set the new contribution in queue - */ - $contributionStruct = new ContributionSetStruct(); - $contributionStruct->fromRevision = self::isRevision(); - $contributionStruct->id_file = ( $filesParts !== null ) ? $filesParts->id_file : null; - $contributionStruct->id_job = $this->id_job; - $contributionStruct->job_password = $this->password; - $contributionStruct->id_segment = $this->id_segment; - $contributionStruct->segment = $this->filter->fromLayer0ToLayer1( $this->segment[ 'segment' ] ); - $contributionStruct->translation = $this->filter->fromLayer0ToLayer1( $_Translation[ 'translation' ] ); - $contributionStruct->api_key = INIT::$MYMEMORY_API_KEY; - $contributionStruct->uid = ( $ownerUid !== null ) ? $ownerUid : 0; - $contributionStruct->oldTranslationStatus = $old_translation[ 'status' ]; - $contributionStruct->oldSegment = $this->filter->fromLayer0ToLayer1( $this->segment[ 'segment' ] ); // - $contributionStruct->oldTranslation = $this->filter->fromLayer0ToLayer1( $old_translation[ 'translation' ] ); - - /* - * This parameter is not used by the application, but we use it to for information integrity - * - * User choice for propagation. - * - * Propagate is false IF: - * - the segment has not repetitions - * - the segment has some one or more repetitions and the user choose to not propagate it - * - the segment is already autopropagated ( marked as autopropagated_from ) and it hasn't been changed - * - * Propagate is true ( vice versa ) IF: - * - the segment has one or more repetitions and it's status is NEW/DRAFT - * - the segment has one or more repetitions and the user choose to propagate it - * - the segment has one or more repetitions, it is not modified, it doesn't have translation conflicts and a change status is requested - */ - $contributionStruct->propagationRequest = $this->propagate; - $contributionStruct->id_mt = $this->chunk->id_mt_engine; - - $contributionStruct->context_after = $this->context_after; - $contributionStruct->context_before = $this->context_before; - - $this->featureSet->filter( - 'filterContributionStructOnSetTranslation', - $contributionStruct, - $this->project, - $this->segment - ); - - //assert there is not an exception by following the flow - Set::contribution( $contributionStruct ); - - if ( $contributionStruct->id_mt > 1 ) { - $contributionStruct = $this->featureSet->filter( 'filterSetContributionMT', null, $contributionStruct, $this->project ); - Set::contributionMT( $contributionStruct ); - } - - } -} diff --git a/lib/Controller/splitJobController.php b/lib/Controller/splitJobController.php deleted file mode 100644 index 33f1c141b7..0000000000 --- a/lib/Controller/splitJobController.php +++ /dev/null @@ -1,169 +0,0 @@ -identifyUser(); - - $filterArgs = array( - 'exec' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'project_id' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'project_pass' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'job_id' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'job_pass' => array( - 'filter' => FILTER_SANITIZE_STRING, 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'split_raw_words' => array( 'filter' => FILTER_VALIDATE_BOOLEAN ), - 'num_split' => array( 'filter' => FILTER_SANITIZE_NUMBER_INT ), - 'split_values' => array( 'filter' => FILTER_CALLBACK, 'options' => array( 'self', 'valuesToInt' ) ), - ); - - $__postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //NOTE: This is for debug purpose only, - //NOTE: Global $_POST Overriding from CLI Test scripts - //$__postInput = filter_var_array( $_POST, $filterArgs ); - - $this->exec = $__postInput[ 'exec' ]; - $this->project_id = $__postInput[ 'project_id' ]; - $this->project_pass = $__postInput[ 'project_pass' ]; - $this->job_id = $__postInput[ 'job_id' ]; - $this->job_pass = $__postInput[ 'job_pass' ]; - $this->num_split = $__postInput[ 'num_split' ]; - $this->split_values = $__postInput[ 'split_values' ]; - $this->split_raw_words = $__postInput[ 'split_raw_words' ]; - } - - protected function valuesToInt( $float_val ) { - return (int)$float_val; - } - - public function doAction() { - - try { - $count_type = ($this->split_raw_words == true) ? Projects_MetadataDao::SPLIT_RAW_WORD_TYPE : Projects_MetadataDao::SPLIT_EQUIVALENT_WORD_TYPE; - $this->project_struct = \Projects_ProjectDao::findByIdAndPassword( $this->project_id, $this->project_pass, 60 * 60 ); - - $pManager = new ProjectManager(); - - if ( $this->user ) { - $projectStructure[ 'userIsLogged' ] = true; - $projectStructure[ 'uid' ] = $this->user->getUid(); - } - - $pManager->setProjectAndReLoadFeatures( $this->project_struct ); - - $pStruct = $pManager->getProjectStructure(); - - switch ( $this->exec ) { - case 'merge': - $jobStructs = $this->checkMergeAccess( $this->project_struct->getJobs() ); - $pStruct[ 'job_to_merge' ] = $this->job_id; - $pManager->mergeALL( $pStruct, $jobStructs ); - break; - case 'check': - $this->checkSplitAccess( $this->project_struct->getJobs() ); - - $pStruct[ 'job_to_split' ] = $this->job_id; - $pStruct[ 'job_to_split_pass' ] = $this->job_pass; - - $pManager->getSplitData( $pStruct, $this->num_split, $this->split_values, $count_type ); - break; - case 'apply': - $this->checkSplitAccess( $this->project_struct->getJobs() ); - - $pStruct[ 'job_to_split' ] = $this->job_id; - $pStruct[ 'job_to_split_pass' ] = $this->job_pass; - - $pManager->getSplitData( $pStruct, $this->num_split, $this->split_values, $count_type ); - $pManager->applySplit( $pStruct ); - break; - - } - - $this->result[ "data" ] = $pStruct[ 'split_result' ]; - - } catch ( Exception $e ) { - $this->result[ 'errors' ][] = array( "code" => $e->getCode(), "message" => $e->getMessage() ); - } - - - } - - /** - * @param Jobs_JobStruct[] $jobList - * - * @return Jobs_JobStruct[] - * @throws Exception - */ - protected function checkMergeAccess( array $jobList ) { - - return $this->filterJobsById( $jobList ); - - } - - protected function checkSplitAccess( array $jobList ) { - - $jobToSplit = $this->filterJobsById( $jobList ); - - if ( array_shift( $jobToSplit )->password != $this->job_pass ) { - throw new Exception( "Wrong Password. Access denied", -10 ); - } - - $this->project_struct->getFeaturesSet()->run('checkSplitAccess', $jobList ) ; - } - - protected function filterJobsById( array $jobList ){ - - $found = false; - $jid = $this->job_id; - $filteredJobs = array_values( array_filter( $jobList, function ( Jobs_JobStruct $jobStruct ) use ( &$found, $jid ) { - return $jobStruct->id == $jid and !$jobStruct->isDeleted(); - } ) ); - - if ( empty( $filteredJobs ) ) { - throw new Exception( "Access denied", -10 ); - } - - return $filteredJobs; - } - -} diff --git a/lib/Controller/updateJobKeysController.php b/lib/Controller/updateJobKeysController.php deleted file mode 100644 index f17d071502..0000000000 --- a/lib/Controller/updateJobKeysController.php +++ /dev/null @@ -1,226 +0,0 @@ - [ - 'filter' => FILTER_SANITIZE_NUMBER_INT - ], - 'job_pass' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'current_password' => [ - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - 'get_public_matches' => [ 'filter' => FILTER_VALIDATE_BOOLEAN ], - 'data' => [ - 'filter' => FILTER_UNSAFE_RAW, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ], - ]; - - //filter input - $_postInput = filter_input_array( INPUT_POST, $filterArgs ); - - //assign variables - $this->id_job = $_postInput[ 'job_id' ]; - $this->received_password = $_postInput[ 'current_password' ]; - $this->job_id = $_postInput[ 'job_id' ]; - $this->job_pass = $_postInput[ 'job_pass' ]; - $this->tm_keys = CatUtils::sanitizeJSON($_postInput[ 'data' ]); // this will be filtered inside the TmKeyManagement class - $this->only_private = !$_postInput[ 'get_public_matches' ]; - - //check for eventual errors on the input passed - $this->result[ 'errors' ] = []; - if ( empty( $this->job_id ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -1, - 'message' => "Job id missing" - ]; - } - - if ( empty( $this->job_pass ) ) { - $this->result[ 'errors' ][] = [ - 'code' => -2, - 'message' => "Job pass missing" - ]; - } - - //get Job Info, we need only a row of job - $this->jobData = Jobs_JobDao::getByIdAndPassword( (int)$this->job_id, $this->job_pass ); - - //Check if user can access the job - $pCheck = new AjaxPasswordCheck(); - - //check for Password correctness - if ( empty( $this->jobData ) || !$pCheck->grantJobAccessByJobData( $this->jobData, $this->job_pass ) ) { - $this->result[ 'errors' ][] = [ "code" => -10, "message" => "Wrong password" ]; - } - - $this->identifyUser(); - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return mixed - */ - function doAction() { - - // moved here because self::isRevision() in constructor - // generates an infinite loop - if ( $this->user->email == $this->jobData[ 'owner' ] ) { - $this->userRole = TmKeyManagement_Filter::OWNER; - } elseif ( self::isRevision() ) { - $this->userRole = TmKeyManagement_Filter::ROLE_REVISOR; - } - - //if some error occured, stop execution. - if ( count( @$this->result[ 'errors' ] ) ) { - return false; - } - - /* - * The client send data as structured json, for now take it as a plain structure - * - * $clientDecodedJson = Array - * ( - * [owner] => Array - * ( - * [0] => Array - * ( - * [tm] => 1 - * [glos] => 1 - * [owner] => 1 - * [key] => ***************da9a9 - * [name] => - * [r] => 1 - * [w] => 1 - * ) - * - * ) - * - * [mine] => Array - * ( - * [0] => Array - * ( - * [tm] => 1 - * [glos] => 1 - * [owner] => 0 - * [key] => 952681baffb9c147b346 - * [name] => cgjhkmfgdcjkfh - * [r] => 1 - * [w] => 1 - * ) - * - * ) - * - * [anonymous] => Array - * ( - * [0] => Array - * ( - * [tm] => 1 - * [glos] => 1 - * [owner] => 0 - * [key] => ***************882eb - * [name] => Chiave di anonimo - * [r] => 0 - * [w] => 0 - * ) - * - * ) - * - * ) - * - */ - $tm_keys = json_decode( $this->tm_keys, true ); - - $clientKeys = $this->jobData->getClientKeys($this->user, $this->userRole); - - /* - * sanitize owner role key type - */ - foreach ( $tm_keys[ 'mine' ] as $k => $val ) { - - // check if logged user is owner of $val['key'] - $check = array_filter($clientKeys['job_keys'], function (TmKeyManagement_ClientTmKeyStruct $element) use ($val){ - if($element->isEncryptedKey()){ - return false; - } - - return $val['key'] === $element->key; - }); - - $tm_keys[ 'mine' ][ $k ][ 'owner' ] = !empty($check); - } - - $tm_keys = array_merge( $tm_keys[ 'ownergroup' ], $tm_keys[ 'mine' ], $tm_keys[ 'anonymous' ] ); - $this->tm_keys = json_encode( $tm_keys ); - - try { - $totalTmKeys = TmKeyManagement_TmKeyManagement::mergeJsonKeys( $this->tm_keys, $this->jobData[ 'tm_keys' ], $this->userRole, $this->user->uid ); - - Log::doJsonLog( 'Before: ' . $this->jobData[ 'tm_keys' ] ); - Log::doJsonLog( 'After: ' . json_encode( $totalTmKeys ) ); - - if ( $this->jobOwnerIsMe() ) { - $this->jobData[ 'only_private_tm' ] = $this->only_private; - } - - /** @var TmKeyManagement_TmKeyStruct $totalTmKey */ - foreach ( $totalTmKeys as $totalTmKey ){ - $totalTmKey->complete_format = true; - } - - $this->jobData->tm_keys = json_encode( $totalTmKeys ); - - $jobDao = new \Jobs_JobDao( Database::obtain() ); - $jobDao->updateStruct( $this->jobData, [ 'fields' => [ 'only_private_tm', 'tm_keys' ] ] ); - $jobDao->destroyCache( $this->jobData ); - - $this->result[ 'data' ] = 'OK'; - - } catch ( Exception $e ) { - $this->result[ 'data' ] = 'KO'; - $this->result[ 'errors' ][] = [ "code" => $e->getCode(), "message" => $e->getMessage() ]; - } - - } - - private function jobOwnerIsMe() { - return $this->userIsLogged && $this->jobData[ 'owner' ] == $this->user->email; - } - -} \ No newline at end of file diff --git a/lib/Controller/userKeysController.php b/lib/Controller/userKeysController.php deleted file mode 100644 index f658f0223e..0000000000 --- a/lib/Controller/userKeysController.php +++ /dev/null @@ -1,202 +0,0 @@ -identifyUser(); - //Session Disabled - - //define input filters - $filterArgs = array( - 'exec' => array( - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'key' => array( - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'emails' => array( - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - 'description' => array( - 'filter' => FILTER_SANITIZE_STRING, - 'flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH - ), - ); - - //filter input - $_postInput = filter_var_array( $_REQUEST, $filterArgs ); - - //assign variables - $this->exec = $_postInput[ 'exec' ]; - $this->key = trim( $_postInput[ 'key' ] ); - $this->description = $_postInput[ 'description' ]; - $this->mail_list = $_postInput[ 'emails' ]; - - //check for eventual errors on the input passed - $this->result[ 'errors' ] = array(); - if ( empty( $this->key ) ) { - $this->result[ 'errors' ][] = array( - 'code' => -2, - 'message' => "Key missing" - ); - $this->result[ 'success' ] = false; - } - - // Prevent XSS attack - // =========================== - // POC. Try to add this string in the input: - //
- // in this case, an error MUST be thrown - - if($_POST['description'] !== $this->description){ - $this->result[ 'errors' ][] = array( - 'code' => -3, - 'message' => "Invalid key description" - ); - $this->result[ 'success' ] = false; - } - - if ( array_search( $this->exec, self::$allowed_exec ) === false ) { - $this->result[ 'errors' ][] = array( - 'code' => -5, - 'message' => "No method $this->exec allowed." - ); - $this->result[ 'success' ] = false; - } - - //ONLY LOGGED USERS CAN PERFORM ACTIONS ON KEYS, BUT INFO ARE PUBLIC - if ( !$this->userIsLogged && $this->exec != 'info' ) { - $this->result[ 'errors' ][] = array( - 'code' => -1, - 'message' => "Login is required to perform this action" - ); - $this->result[ 'success' ] = false; - } - - } - - /** - * When Called it perform the controller action to retrieve/manipulate data - * - * @return void - */ - function doAction() { - //if some error occured, stop execution. - if ( count( @$this->result[ 'errors' ] ) ) { - return; - } - - try { - $tmService = new TMSService(); - //validate the key - try { - $keyExists = $tmService->checkCorrectKey( $this->key ); - } catch ( Exception $e ) { - /* PROVIDED KEY IS NOT VALID OR WRONG, $keyExists IS NOT SET */ - Log::doJsonLog( $e->getMessage() ); - } - - if ( !isset( $keyExists ) || $keyExists === false ) { - Log::doJsonLog( __METHOD__ . " -> TM key is not valid." ); - throw new Exception( "TM key is not valid.", -4 ); - } - - $tmKeyStruct = new TmKeyManagement_TmKeyStruct(); - $tmKeyStruct->key = $this->key; - $tmKeyStruct->name = $this->description; - $tmKeyStruct->tm = true; - $tmKeyStruct->glos = true; - - - $mkDao = new TmKeyManagement_MemoryKeyDao( Database::obtain() ); - - $memoryKeyToUpdate = new TmKeyManagement_MemoryKeyStruct(); - $memoryKeyToUpdate->uid = $this->user->uid; - $memoryKeyToUpdate->tm_key = $tmKeyStruct; - - switch ( $this->exec ) { - case 'delete': - $userMemoryKeys = $mkDao->disable( $memoryKeyToUpdate ); - $this->featureSet->run('postUserKeyDelete', $userMemoryKeys->tm_key->key, $this->user->uid ); - break; - case 'update': - $userMemoryKeys = $mkDao->atomicUpdate( $memoryKeyToUpdate ); - break; - case 'newKey': - $userMemoryKeys = $mkDao->create( $memoryKeyToUpdate ); - $this->featureSet->run( 'postTMKeyCreation', [ $userMemoryKeys ], $this->user->uid ); - break; - case 'info': - $userMemoryKeys = $mkDao->read( $memoryKeyToUpdate ); - $this->_getKeyUsersInfo( $userMemoryKeys ); - break; - case 'share': - $emailList = Utils::validateEmailList($this->mail_list); - $userMemoryKeys = $mkDao->read( $memoryKeyToUpdate ); - (new TmKeyManagement_TmKeyManagement())->shareKey($emailList, $userMemoryKeys[0], $this->user); - break; - default: - throw new Exception( "Unexpected Exception", -4 ); - } - - if ( !$userMemoryKeys ) { - throw new Exception( "This key wasn't found in your keyring.", -3 ); - } - - } catch ( Exception $e ) { - $this->result[ 'data' ] = 'KO'; - $this->result[ 'success' ] = false; - $this->result[ 'errors' ][] = array( "code" => $e->getCode(), "message" => $e->getMessage() ); - } - - } - - /** - * @param TmKeyManagement_MemoryKeyStruct[] $userMemoryKeys - */ - protected function _getKeyUsersInfo( Array $userMemoryKeys ){ - - $_userStructs = []; - foreach( $userMemoryKeys[0]->tm_key->getInUsers() as $userStruct ){ - $_userStructs[] = new Users_ClientUserFacade( $userStruct ); - } - $this->result = [ - 'errors' => [], - "data" => $_userStructs, - "success" => true - ]; - - } - -} \ No newline at end of file diff --git a/lib/Model/FilesStorage/FsFilesStorage.php b/lib/Model/FilesStorage/FsFilesStorage.php index c9d7fc4201..07b40fca46 100644 --- a/lib/Model/FilesStorage/FsFilesStorage.php +++ b/lib/Model/FilesStorage/FsFilesStorage.php @@ -369,8 +369,6 @@ public static function moveFileFromUploadSessionToQueuePath( $uploadSession ) { new RecursiveDirectoryIterator( INIT::$UPLOAD_REPOSITORY . DIRECTORY_SEPARATOR . $uploadSession, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::SELF_FIRST ) as $item ) { - - // XXX we have here two different variables: // \FilesStorage\S3FilesStorage::QUEUE_FOLDER and INIT::$QUEUE_PROJECT_REPOSITORY // $destination = INIT::$QUEUE_PROJECT_REPOSITORY . DIRECTORY_SEPARATOR . $subPathName; diff --git a/lib/Routes/api_v1_routes.php b/lib/Routes/api_v1_routes.php index 8d5e62096a..76e6a98d7f 100644 --- a/lib/Routes/api_v1_routes.php +++ b/lib/Routes/api_v1_routes.php @@ -12,3 +12,5 @@ $klein->with( '/api/v1/projects/[:id_project]/[:password]', function () { route( '/creation_status', 'GET', [ 'API\V2\ProjectCreationStatusController', 'get' ] ); } ); + +route( '/api/v1/new', 'POST', ['API\V1\NewController', 'create' ] ); diff --git a/lib/Routes/utils_routes.php b/lib/Routes/utils_routes.php index 1cb6cbb70f..e805275eb9 100644 --- a/lib/Routes/utils_routes.php +++ b/lib/Routes/utils_routes.php @@ -117,3 +117,51 @@ $klein->with( '/api/app/filters-config-template', function () { route( '/default', 'GET', [ '\API\V3\FiltersConfigTemplateController', 'default' ] ); } ); + +// MISC (OLD AJAX ROUTES) +route( '/api/app/fetch-change-rates', 'POST', [ 'API\App\FetchChangeRatesController', 'fetch' ] ); +route( '/api/app/outsource-to', 'POST', [ 'API\App\OutsourceToController', 'outsource' ] ); +route( '/api/app/get-volume-analysis', 'POST', [ 'API\App\GetVolumeAnalysisController', 'analysis' ] ); +route( '/api/app/get-projects', 'POST', [ 'API\App\GetProjectsController', 'fetch' ] ); +route( '/api/app/delete-contribution', 'POST', [ 'API\App\DeleteContributionController', 'delete' ] ); +route( '/api/app/comment/resolve', 'POST', [ 'API\App\CommentController', 'resolve' ] ); +route( '/api/app/comment/delete', 'POST', [ 'API\App\CommentController', 'delete' ] ); +route( '/api/app/comment/create', 'POST', [ 'API\App\CommentController', 'create' ] ); +route( '/api/app/comment/get-range', 'POST', [ 'API\App\CommentController', 'getRange' ] ); +route( '/api/app/copy-all-source-to-target', 'POST', [ 'API\App\CopyAllSourceToTargetController', 'copy' ] ); +route( '/api/app/get-global-warning', 'POST', [ 'API\App\GetWarningController', 'global' ] ); +route( '/api/app/get-local-warning', 'POST', [ 'API\App\GetWarningController', 'local' ] ); +route( '/api/app/split-job-apply', 'POST', [ 'API\App\SplitJobController', 'apply' ] ); +route( '/api/app/split-job-check', 'POST', [ 'API\App\SplitJobController', 'check' ] ); +route( '/api/app/split-job-merge', 'POST', [ 'API\App\SplitJobController', 'merge' ] ); +route( '/api/app/user-keys-delete', 'POST', [ 'API\App\UserKeysController', 'delete' ] ); +route( '/api/app/user-keys-update', 'POST', [ 'API\App\UserKeysController', 'update' ] ); +route( '/api/app/user-keys-new-key', 'POST', [ 'API\App\UserKeysController', 'newKey' ] ); +route( '/api/app/user-keys-info', 'POST', [ 'API\App\UserKeysController', 'info' ] ); +route( '/api/app/user-keys-share', 'POST', [ 'API\App\UserKeysController', 'share' ] ); +route( '/api/app/create-random-user', 'POST', [ 'API\App\CreateRandUserController', 'create' ] ); +route( '/api/app/get-tag-projection', 'POST', [ 'API\App\GetTagProjectionController', 'call' ] ); +route( '/api/app/set-current-segment', 'POST', [ 'API\App\SetCurrentSegmentController', 'set' ] ); +route( '/api/app/get-segments', 'POST', [ 'API\App\GetSegmentsController', 'segments' ] ); +route( '/api/app/ping', 'POST', [ 'API\App\AjaxUtilsController', 'ping' ] ); +route( '/api/app/check-tm-key', 'POST', [ 'API\App\AjaxUtilsController', 'checkTMKey' ] ); +route( '/api/app/clear-not-completed-uploads', 'POST', [ 'API\App\AjaxUtilsController', 'clearNotCompletedUploads' ] ); +route( '/api/app/get-translation-mismatches', 'POST', [ 'API\App\GetTranslationMismatchesController', 'get' ] ); +route( '/api/app/add-engine', 'POST', [ 'API\App\EngineController', 'add' ] ); +route( '/api/app/disable-engine', 'POST', [ 'API\App\EngineController', 'disable' ] ); +route( '/api/app/get-contribution', 'POST', [ 'API\App\GetContributionController', 'get' ] ); +route( '/api/app/search', 'POST', [ 'API\App\GetSearchController', 'search' ] ); +route( '/api/app/replace-all', 'POST', [ 'API\App\GetSearchController', 'replaceAll' ] ); +route( '/api/app/redo-replace-all', 'POST', [ 'API\App\GetSearchController', 'redoReplaceAll' ] ); +route( '/api/app/undo-replace-all', 'POST', [ 'API\App\GetSearchController', 'undoReplaceAll' ] ); +route( '/api/app/update-job-keys', 'POST', [ 'API\App\UpdateJobKeysController', 'update' ] ); +route( '/api/app/set-translation', 'POST', [ 'API\App\SetTranslationController', 'translate' ] ); +route( '/api/app/split-segment', 'POST', ['API\App\SplitSegmentController', 'split' ] ); +route( '/api/app/new-tmx', 'POST', ['API\App\LoadTMXController', 'newTM' ] ); +route( '/api/app/upload-tmx-status', 'POST', ['API\App\LoadTMXController', 'uploadStatus' ] ); +route( '/api/app/change-job-status', 'POST', ['API\App\ChangeJobsStatusController', 'changeStatus' ] ); +route( '/api/app/download-tmx', 'POST', ['API\App\DownloadTMXController', 'download' ] ); +route( '/api/app/new-project', 'POST', ['API\App\CreateProjectController', 'create' ] ); +route( '/api/app/convert-file', 'POST', ['API\App\ConvertFileController', 'handle' ] ); +route( '/api/app/set-chunk-completed', 'POST', ['API\App\SetChunkCompletedController', 'complete' ] ); +route( '/api/app/download-analysis-report', 'POST', ['API\App\DownloadAnalysisReportController', 'download' ] ); \ No newline at end of file diff --git a/lib/Utils/API/ConvertFile.php b/lib/Utils/API/ConvertFile.php new file mode 100644 index 0000000000..fa058bcf9b --- /dev/null +++ b/lib/Utils/API/ConvertFile.php @@ -0,0 +1,305 @@ +lang_handler = Langs_Languages::getInstance(); + $this->files = $files; + $this->convertZipFile = $convertZipFile; + $this->setSourceLang($source_lang); + $this->setTargetLangs($target_lang); + $this->intDir = $intDir; + $this->errDir = $errDir; + $this->cookieDir = $cookieDir; + $this->segmentation_rule = $segmentation_rule; + $this->featureSet = $featureSet; + $this->filters_extraction_parameters = $filters_extraction_parameters; + } + + /** + * @param Users_UserStruct $user + */ + public function setUser( Users_UserStruct $user ) + { + $this->user = $user; + } + + /** + * @param $source_lang + */ + private function setSourceLang($source_lang): void + { + try { + $this->lang_handler->validateLanguage( $source_lang ); + $this->source_lang = $source_lang; + } catch ( Exception $e ) { + throw new InvalidArgumentException($e->getMessage(), ConversionHandlerStatus::SOURCE_ERROR); + } + } + + /** + * @param $target_lang + */ + private function setTargetLangs($target_lang): void + { + $targets = explode( ',', $target_lang ); + $targets = array_map( 'trim', $targets ); + $targets = array_unique( $targets ); + + if ( empty( $targets ) ) { + throw new InvalidArgumentException("Missing target language."); + } + + try { + foreach ( $targets as $target ) { + $this->lang_handler->validateLanguage( $target ); + } + + } catch ( Exception $e ) { + throw new InvalidArgumentException($e->getMessage(), ConversionHandlerStatus::TARGET_ERROR); + } + + $this->target_lang = implode( ',', $targets ); + } + + /** + * @return array + * @throws Exception + */ + public function convertFiles(): array + { + foreach ($this->files as $fileName ) { + $this->file_name = $fileName; + $this->resultStack[] = $this->convertFile(); + } + + return $this->resultStack; + } + + /** + * @return ConvertedFileModel + * @throws Exception + */ + private function convertFile(): ?ConvertedFileModel + { + $result = new ConvertedFileModel(); + + try { + $this->segmentation_rule = Constants::validateSegmentationRules( $this->segmentation_rule ); + } catch ( Exception $e ){ + throw new InvalidArgumentException($e->getMessage(), ConversionHandlerStatus::INVALID_SEGMENTATION_RULE); + } + + if ( !Utils::isTokenValid( $this->cookieDir ) ) { + throw new InvalidArgumentException("Invalid Upload Token.", ConversionHandlerStatus::INVALID_TOKEN); + } + + if ( !Utils::isValidFileName( $this->file_name ) || empty( $this->file_name ) ) { + throw new InvalidArgumentException("Invalid File.", ConversionHandlerStatus::INVALID_FILE); + } + + $ext = AbstractFilesStorage::pathinfo_fix( $this->file_name, PATHINFO_EXTENSION ); + + $conversionHandler = new ConversionHandler(); + $conversionHandler->setFileName( $this->file_name ); + $conversionHandler->setSourceLang( $this->source_lang ); + $conversionHandler->setTargetLang( $this->target_lang ); + $conversionHandler->setSegmentationRule( $this->segmentation_rule ); + $conversionHandler->setCookieDir( $this->cookieDir ); + $conversionHandler->setIntDir( $this->intDir ); + $conversionHandler->setErrDir( $this->errDir ); + $conversionHandler->setFeatures( $this->featureSet ); + $conversionHandler->setUserIsLogged( true ); + $conversionHandler->setFiltersExtractionParameters( $this->filters_extraction_parameters ); + + if ( $ext == "zip" ) { + if ( $this->convertZipFile ) { + try { + $this->handleZip( $conversionHandler ); + } catch (Exception $exception){ + throw new RuntimeException("Handling of zip files failed.", ConversionHandlerStatus::ZIP_HANDLING); + } + } else { + throw new RuntimeException("Nested zip files are not allowed.", ConversionHandlerStatus::NESTED_ZIP_FILES_NOT_ALLOWED); + } + } else { + $conversionHandler->doAction(); + $result = $conversionHandler->getResult(); + } + + return $result; + } + + /** + * @param ConversionHandler $conversionHandler + * + * @return bool + * @throws Exception + */ + private function handleZip( ConversionHandler $conversionHandler ): bool + { + // this makes the conversionhandler accumulate eventual errors on files and continue + $conversionHandler->setStopOnFileException( false ); + + $internalZipFileNames = $conversionHandler->extractZipFile(); + //call convertFileWrapper and start conversions for each file + + if ( $conversionHandler->uploadError ) { + $fileErrors = $conversionHandler->getUploadedFiles(); + + foreach ( $fileErrors as $fileError ) { + if ( count( $fileError->error ) == 0 ) { + continue; + } + + $brokenFileName = ZipArchiveExtended::getFileName( $fileError->name ); + + throw new RuntimeException($fileError->error[ 'message' ], $fileError->error[ 'code' ]); //@TODO usare $brokenFileName + } + } + + $realFileNames = array_map( + [ 'ZipArchiveExtended', 'getFileName' ], + $internalZipFileNames + ); + + foreach ( $realFileNames as $i => &$fileObject ) { + $fileObject = [ + 'name' => $fileObject, + 'size' => filesize( $this->intDir . DIRECTORY_SEPARATOR . $internalZipFileNames[ $i ] ) + ]; + } + + $zipFiles = json_encode( $realFileNames ); + $stdFileObjects = []; + + if ( $internalZipFileNames !== null ) { + foreach ( $internalZipFileNames as $fName ) { + $stdFileObjects[] = $fName; + } + } else { + $errors = $conversionHandler->getResult(); + $errors = array_map( [ 'Upload', 'formatExceptionMessage' ], $errors[ 'errors' ] ); + + foreach ($errors as $error){ + throw new Exception($error); + } + + return false; + } + + /* Do conversions here */ + $converter = new ConvertFile( + $stdFileObjects, + $this->source_lang, + $this->target_lang, + $this->intDir, + $this->errDir, + $this->cookieDir, + $this->segmentation_rule, + $this->featureSet, + $this->filters_extraction_parameters, + false + ); + + $converter->convertFiles(); + $error = $converter->getErrors(); + + //$this->result->changeCode(ConversionHandlerStatus::ZIP_HANDLING); + + // Upload errors handling +// if($error !== null and !empty($error->getErrors())){ +// $this->result->changeCode($error->getCode()); +// $savedErrors = $this->result->getErrors(); +// $brokenFileName = ZipArchiveExtended::getFileName( array_keys($error->getErrors())[0] ); +// +// if( !isset( $savedErrors[$brokenFileName] ) ){ +// $this->result->addError($error->getErrors()[0]['message'], $brokenFileName); +// } +// } + + return $zipFiles; + } + + /** + * Check on executed conversion results + * @return array + */ + public function getErrors(): array + { + $errors = []; + + /** @var ConvertedFileModel $res */ + foreach ( $this->resultStack as $res ) { + if ( $res->hasAnErrorCode() ) { + $errors[] = $res->getErrors(); + } + } + + return $errors; + } +} \ No newline at end of file diff --git a/lib/Utils/API/ConvertFileWrapper.php b/lib/Utils/API/ConvertFileWrapper.php deleted file mode 100644 index 11421de22b..0000000000 --- a/lib/Utils/API/ConvertFileWrapper.php +++ /dev/null @@ -1,67 +0,0 @@ -fileStruct = $stdResult; - $this->convertZipFile = (bool)$convertZipFile; - $this->identifyUser(); - } - - public function setUser( Users_UserStruct $user = null ) { - $this->user = new Users_UserStruct(); - $this->userIsLogged = false; - if ( !empty( $user ) ) { - $this->user = $user; - $this->userIsLogged = true; - } - } - - public function doAction() { - - foreach ( $this->fileStruct as $_file ) { - $this->file_name = $_file->name; - parent::doAction(); - $this->resultStack[] = $this->result; - } - - } - - /** - * Check on executed conversion results - * @return ConvertedFileModel - */ - public function checkResult() { - - $failure = false; - - /** @var ConvertedFileModel $res */ - foreach ( $this->resultStack as $res ) { - if ( $res->hasAnErrorCode() ) { - $failure = true; - } - } - - if ( $failure ) { - return end( $this->resultStack ); - } - - return null; - - } - -} \ No newline at end of file diff --git a/lib/Utils/Engines/MyMemory.php b/lib/Utils/Engines/MyMemory.php index 1d27392157..0a529ec99f 100644 --- a/lib/Utils/Engines/MyMemory.php +++ b/lib/Utils/Engines/MyMemory.php @@ -751,8 +751,7 @@ public function fastAnalysis( $segs_array ) { * MyMemory private endpoint * * @param $config - * - * @return array|Engines_Results_MyMemory_TagProjectionResponse + * @return array */ public function getTagProjection( $config ) { diff --git a/package.json b/package.json index 85c4d9f18f..95d7f14f63 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "eslint-plugin-jest-dom": "^5.0.0", "eslint-plugin-react": "^7.23.2", "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-testing-library": "^6.0.0", + "eslint-plugin-testing-library": "^7.0.0", "fs-extra": "^11.1.1", "glob": "^11.0.0", "html-webpack-plugin": "5.6.3", @@ -83,7 +83,7 @@ "sass-loader": "16.0.3", "style-loader": "4.0.0", "undici": "^5.28.2", - "webpack": "5.95.0", + "webpack": "5.96.1", "webpack-cli": "5.1.4", "webpack-concat-files-plugin": "^0.5.2", "whatwg-fetch": "^3.6.20" diff --git a/public/js/cat_source/es6/actions/CommentsActions.js b/public/js/cat_source/es6/actions/CommentsActions.js index dee15cbb8f..08ccf88d31 100644 --- a/public/js/cat_source/es6/actions/CommentsActions.js +++ b/public/js/cat_source/es6/actions/CommentsActions.js @@ -20,14 +20,18 @@ const CommentsActions = { sourcePage: config.revisionNumber ? config.revisionNumber + 1 : 1, message: text, isAnonymous, - }).then((resp) => { - AppDispatcher.dispatch({ - actionType: CommentsConstants.ADD_COMMENT, - comment: resp.data.entries.comment[0], - sid: sid, - }) - return resp }) + .then((resp) => { + AppDispatcher.dispatch({ + actionType: CommentsConstants.ADD_COMMENT, + comment: resp.data.entries.comment[0], + sid: sid, + }) + return resp + }) + .catch(() => { + // showGenericWarning(); + }) }, resolveThread: function (sid) { markAsResolvedThread({ diff --git a/public/js/cat_source/es6/api/addMTEngine/addMTEngine.js b/public/js/cat_source/es6/api/addMTEngine/addMTEngine.js index 36c47f84a1..3af0e45805 100644 --- a/public/js/cat_source/es6/api/addMTEngine/addMTEngine.js +++ b/public/js/cat_source/es6/api/addMTEngine/addMTEngine.js @@ -11,8 +11,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const addMTEngine = async ({name, provider, dataMt}) => { const paramsData = { - action: 'engine', - exec: 'add', name, provider, data: JSON.stringify(dataMt), @@ -23,7 +21,7 @@ export const addMTEngine = async ({name, provider, dataMt}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/add-engine`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/checkConnectionPing/checkConnectionPing.js b/public/js/cat_source/es6/api/checkConnectionPing/checkConnectionPing.js index 26208dfdb8..b860fc0b05 100644 --- a/public/js/cat_source/es6/api/checkConnectionPing/checkConnectionPing.js +++ b/public/js/cat_source/es6/api/checkConnectionPing/checkConnectionPing.js @@ -6,16 +6,13 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' * @returns {Promise} */ export const checkConnectionPing = async () => { - const dataParams = { - action: 'ajaxUtils', - exec: 'ping', - } + const dataParams = {} const formData = new FormData() Object.keys(dataParams).forEach((key) => { formData.append(key, dataParams[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=ajaxUtils`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/ping`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/checkSplitRequest/checkSplitRequest.js b/public/js/cat_source/es6/api/checkSplitRequest/checkSplitRequest.js index 5d28da20f3..cd17160b69 100644 --- a/public/js/cat_source/es6/api/checkSplitRequest/checkSplitRequest.js +++ b/public/js/cat_source/es6/api/checkSplitRequest/checkSplitRequest.js @@ -2,14 +2,13 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' import {flattenObject} from '../../utils/queryString' export const checkSplitRequest = async ( - job, - project, - numsplit, - arrayValues, - splitRawWords, + job, + project, + numsplit, + arrayValues, + splitRawWords, ) => { const params = flattenObject({ - exec: 'check', project_id: project.id, project_pass: project.password, job_id: job.id, @@ -23,7 +22,7 @@ export const checkSplitRequest = async ( Object.keys(params).forEach((key) => { formData.append(key, params[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=splitJob`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/split-job-check`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/checkTMKey/checkTMKey.js b/public/js/cat_source/es6/api/checkTMKey/checkTMKey.js index 3679622915..0864b5ef6b 100644 --- a/public/js/cat_source/es6/api/checkTMKey/checkTMKey.js +++ b/public/js/cat_source/es6/api/checkTMKey/checkTMKey.js @@ -9,8 +9,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const checkTMKey = async ({tmKey}) => { const paramsData = { - action: 'ajaxUtils', - exec: 'checkTMKey', tm_key: tmKey, } const formData = new FormData() @@ -18,14 +16,11 @@ export const checkTMKey = async ({tmKey}) => { Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) - const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, - { - method: 'POST', - body: formData, - credentials: 'include', - }, - ) + const response = await fetch(`${getMatecatApiDomain()}api/app/check-tm-key`, { + method: 'POST', + body: formData, + credentials: 'include', + }) if (!response.ok) return Promise.reject(response) diff --git a/public/js/cat_source/es6/api/clearNotCompletedUploads/clearNotCompletedUploads.js b/public/js/cat_source/es6/api/clearNotCompletedUploads/clearNotCompletedUploads.js index 20a9013386..107df6894a 100644 --- a/public/js/cat_source/es6/api/clearNotCompletedUploads/clearNotCompletedUploads.js +++ b/public/js/cat_source/es6/api/clearNotCompletedUploads/clearNotCompletedUploads.js @@ -8,16 +8,14 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' * @returns {Promise} */ export const clearNotCompletedUploads = async (time = new Date().getTime()) => { - const paramsData = { - exec: 'clearNotCompletedUploads', - } + const paramsData = {} const formData = new FormData() Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=ajaxUtils&${time}`, + `${getMatecatApiDomain()}api/app/clear-not-completed-uploads`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/confirmSplitRequest/confirmSplitRequest.js b/public/js/cat_source/es6/api/confirmSplitRequest/confirmSplitRequest.js index 6e9f6c9ccb..48ede9bbe6 100644 --- a/public/js/cat_source/es6/api/confirmSplitRequest/confirmSplitRequest.js +++ b/public/js/cat_source/es6/api/confirmSplitRequest/confirmSplitRequest.js @@ -9,7 +9,6 @@ export const confirmSplitRequest = async ( splitRawWords, ) => { const params = flattenObject({ - exec: 'apply', project_id: project.id, project_pass: project.password, job_id: job.id, @@ -23,7 +22,7 @@ export const confirmSplitRequest = async ( Object.keys(params).forEach((key) => { formData.append(key, params[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=splitJob`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/split-job-apply`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js b/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js index 2a8486abe1..ba91cf62e6 100644 --- a/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js +++ b/public/js/cat_source/es6/api/convertFileRequest/convertFileRequest.js @@ -32,7 +32,7 @@ export const convertFileRequest = async ({ if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) - const response = await fetch(`action/convertFile/`, { + const response = await fetch(`api/app/convert-file`, { method: 'POST', credentials: 'include', signal: signal, diff --git a/public/js/cat_source/es6/api/copyAllSourceToTarget/copyAllSourceToTarget.js b/public/js/cat_source/es6/api/copyAllSourceToTarget/copyAllSourceToTarget.js index 0552914fcd..aec305c884 100644 --- a/public/js/cat_source/es6/api/copyAllSourceToTarget/copyAllSourceToTarget.js +++ b/public/js/cat_source/es6/api/copyAllSourceToTarget/copyAllSourceToTarget.js @@ -15,7 +15,6 @@ export const copyAllSourceToTarget = async ({ revisionNumber = config.revisionNumber, } = {}) => { const paramsData = { - action: 'copyAllSource2Target', id_job: idJob, pass: password, revision_number: revisionNumber ? revisionNumber : undefined, @@ -26,7 +25,7 @@ export const copyAllSourceToTarget = async ({ if (paramsData[key] !== undefined) formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=copyAllSource2Target`, + `${getMatecatApiDomain()}api/app/copy-all-source-to-target`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/createNewTmKey/createNewTmKey.js b/public/js/cat_source/es6/api/createNewTmKey/createNewTmKey.js index b1b14a5774..d4294e4d25 100644 --- a/public/js/cat_source/es6/api/createNewTmKey/createNewTmKey.js +++ b/public/js/cat_source/es6/api/createNewTmKey/createNewTmKey.js @@ -10,8 +10,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const createNewTmKey = async ({key, description}) => { const paramsData = { - action: 'userKeys', - exec: 'newKey', key, description, } @@ -21,7 +19,7 @@ export const createNewTmKey = async ({key, description}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/user-keys-new-key`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/createProject/createProject.js b/public/js/cat_source/es6/api/createProject/createProject.js index a398abf751..9715a6eabf 100644 --- a/public/js/cat_source/es6/api/createProject/createProject.js +++ b/public/js/cat_source/es6/api/createProject/createProject.js @@ -15,7 +15,7 @@ export const createProject = async (options) => { Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) - const response = await fetch(`${config.basepath}?action=createProject`, { + const response = await fetch(`${config.basepath}api/app/new-project`, { method: 'POST', body: formData, credentials: 'include', diff --git a/public/js/cat_source/es6/api/deleteComment/deleteComment.js b/public/js/cat_source/es6/api/deleteComment/deleteComment.js index ab343fd61e..a14d461649 100644 --- a/public/js/cat_source/es6/api/deleteComment/deleteComment.js +++ b/public/js/cat_source/es6/api/deleteComment/deleteComment.js @@ -20,7 +20,6 @@ export const deleteComment = async ({ sourcePage = config.revisionNumber, }) => { const dataParams = { - _sub: 'delete', id_comment: idComment, id_segment: idSegment, id_job: idJob, @@ -32,7 +31,7 @@ export const deleteComment = async ({ Object.keys(dataParams).forEach((key) => { if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=comment`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/comment/delete`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/deleteContribution/deleteContribution.js b/public/js/cat_source/es6/api/deleteContribution/deleteContribution.js index 481026888e..f1b12f9ae5 100644 --- a/public/js/cat_source/es6/api/deleteContribution/deleteContribution.js +++ b/public/js/cat_source/es6/api/deleteContribution/deleteContribution.js @@ -28,7 +28,6 @@ export const deleteContribution = async ({ sid, }) => { const dataParams = { - action: 'deleteContribution', source_lang: sourceLanguage, target_lang: targetLanguage, id_job: idJob, @@ -46,7 +45,7 @@ export const deleteContribution = async ({ if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=deleteContribution`, + `${getMatecatApiDomain()}api/app/delete-contribution`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/deleteMTEngine/deleteMTEngine.js b/public/js/cat_source/es6/api/deleteMTEngine/deleteMTEngine.js index 704dc78041..95b9248895 100644 --- a/public/js/cat_source/es6/api/deleteMTEngine/deleteMTEngine.js +++ b/public/js/cat_source/es6/api/deleteMTEngine/deleteMTEngine.js @@ -9,8 +9,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const deleteMTEngine = async ({id}) => { const paramsData = { - action: 'engine', - exec: 'delete', id, } const formData = new FormData() @@ -19,7 +17,7 @@ export const deleteMTEngine = async ({id}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/disable-engine`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/deleteTmKey/deleteTmKey.js b/public/js/cat_source/es6/api/deleteTmKey/deleteTmKey.js index 758ec7c293..cb7d520432 100644 --- a/public/js/cat_source/es6/api/deleteTmKey/deleteTmKey.js +++ b/public/js/cat_source/es6/api/deleteTmKey/deleteTmKey.js @@ -9,8 +9,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const deleteTmKey = async ({key}) => { const paramsData = { - action: 'userKeys', - exec: 'delete', key, } const formData = new FormData() @@ -19,7 +17,7 @@ export const deleteTmKey = async ({key}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/user-keys-delete`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/downloadTMX/downloadTMX.js b/public/js/cat_source/es6/api/downloadTMX/downloadTMX.js index 34445d94a8..77f48379f0 100644 --- a/public/js/cat_source/es6/api/downloadTMX/downloadTMX.js +++ b/public/js/cat_source/es6/api/downloadTMX/downloadTMX.js @@ -19,7 +19,6 @@ export const downloadTMX = async ({ stripTags, }) => { const paramsData = { - action: 'downloadTMX', tm_key: key, tm_name: name, strip_tags: stripTags, @@ -32,7 +31,7 @@ export const downloadTMX = async ({ formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/download-tmx`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/getChangeRates/getChangeRates.js b/public/js/cat_source/es6/api/getChangeRates/getChangeRates.js index 0b1d36fcfa..92688b0ba5 100644 --- a/public/js/cat_source/es6/api/getChangeRates/getChangeRates.js +++ b/public/js/cat_source/es6/api/getChangeRates/getChangeRates.js @@ -2,7 +2,7 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' export const getChangeRates = async () => { const response = await fetch( - `${getMatecatApiDomain()}?action=fetchChangeRates`, + `${getMatecatApiDomain()}api/app/fetch-change-rates`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getComments/getComments.js b/public/js/cat_source/es6/api/getComments/getComments.js index 51dd23622c..806838aa40 100644 --- a/public/js/cat_source/es6/api/getComments/getComments.js +++ b/public/js/cat_source/es6/api/getComments/getComments.js @@ -16,8 +16,6 @@ export const getComments = async ({ password = config.password, }) => { const dataParams = { - action: 'comment', - _sub: 'getRange', first_seg: firstSegment, last_seg: lastSegment, id_job: idJob, @@ -28,7 +26,7 @@ export const getComments = async ({ Object.keys(dataParams).forEach((key) => { if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=comment`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/comment/get-range`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/getConcordance/getConcordance.js b/public/js/cat_source/es6/api/getConcordance/getConcordance.js index 8e0abc0907..7ef9b14bbc 100644 --- a/public/js/cat_source/es6/api/getConcordance/getConcordance.js +++ b/public/js/cat_source/es6/api/getConcordance/getConcordance.js @@ -22,7 +22,6 @@ export const getConcordance = async ( currentPassword = config.currentPassword, ) => { const dataParams = { - action: 'getContribution', is_concordance: 1, from_target: type, id_segment: UI.currentSegmentId, @@ -39,7 +38,7 @@ export const getConcordance = async ( formData.append(key, dataParams[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=getContribution`, + `${getMatecatApiDomain()}api/app/get-contribution`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getContributions/getContributions.js b/public/js/cat_source/es6/api/getContributions/getContributions.js index 5886f30209..6949262600 100644 --- a/public/js/cat_source/es6/api/getContributions/getContributions.js +++ b/public/js/cat_source/es6/api/getContributions/getContributions.js @@ -30,7 +30,6 @@ export const getContributions = async ({ const txt = target const obj = { - action: 'getContribution', password: password, is_concordance: 0, id_segment: idSegment, @@ -55,7 +54,7 @@ export const getContributions = async ({ if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=getContribution`, + `${getMatecatApiDomain()}api/app/get-contribution`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getGlobalWarnings/getGlobalWarnings.js b/public/js/cat_source/es6/api/getGlobalWarnings/getGlobalWarnings.js index 0138882083..d00f6c0a1b 100644 --- a/public/js/cat_source/es6/api/getGlobalWarnings/getGlobalWarnings.js +++ b/public/js/cat_source/es6/api/getGlobalWarnings/getGlobalWarnings.js @@ -2,7 +2,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' export const getGlobalWarnings = async ({id_job, password}) => { const paramsData = { - action: 'getWarning', id_job, password, } @@ -13,7 +12,7 @@ export const getGlobalWarnings = async ({id_job, password}) => { }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/get-global-warning`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getInfoTmKey/getInfoTmKey.js b/public/js/cat_source/es6/api/getInfoTmKey/getInfoTmKey.js index 3288abe92a..8dc41f041f 100644 --- a/public/js/cat_source/es6/api/getInfoTmKey/getInfoTmKey.js +++ b/public/js/cat_source/es6/api/getInfoTmKey/getInfoTmKey.js @@ -9,8 +9,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const getInfoTmKey = async ({key}) => { const paramsData = { - action: 'userKeys', - exec: 'info', key, } const formData = new FormData() @@ -19,7 +17,7 @@ export const getInfoTmKey = async ({key}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/user-keys-info`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/getJobVolumeAnalysis/getJobVolumeAnalysis.js b/public/js/cat_source/es6/api/getJobVolumeAnalysis/getJobVolumeAnalysis.js index 7f67be19d3..94d123a44a 100644 --- a/public/js/cat_source/es6/api/getJobVolumeAnalysis/getJobVolumeAnalysis.js +++ b/public/js/cat_source/es6/api/getJobVolumeAnalysis/getJobVolumeAnalysis.js @@ -22,7 +22,7 @@ export const getJobVolumeAnalysis = async ( }) const response = await fetch( - `${getMatecatApiDomain()}?action=getVolumeAnalysis`, + `${getMatecatApiDomain()}api/app/get-volume-analysis`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getLocalWarnings/getLocalWarnings.js b/public/js/cat_source/es6/api/getLocalWarnings/getLocalWarnings.js index d6429c8ffa..059b1b3e2d 100644 --- a/public/js/cat_source/es6/api/getLocalWarnings/getLocalWarnings.js +++ b/public/js/cat_source/es6/api/getLocalWarnings/getLocalWarnings.js @@ -10,7 +10,6 @@ export const getLocalWarnings = async ({ characters_counter, }) => { const paramsData = { - action: 'getWarning', id, id_job, password, @@ -26,7 +25,7 @@ export const getLocalWarnings = async ({ }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}&type=local`, + `${getMatecatApiDomain()}api/app/get-local-warning`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getOutsourceQuote/getOutsourceQuote.js b/public/js/cat_source/es6/api/getOutsourceQuote/getOutsourceQuote.js index afebece732..a31c7a1452 100644 --- a/public/js/cat_source/es6/api/getOutsourceQuote/getOutsourceQuote.js +++ b/public/js/cat_source/es6/api/getOutsourceQuote/getOutsourceQuote.js @@ -43,7 +43,7 @@ export const getOutsourceQuote = async ( Object.keys(data).forEach((key) => { formData.append(key, data[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=outsourceTo`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/outsource-to`, { method: 'POST', body: formData, credentials: 'include', diff --git a/public/js/cat_source/es6/api/getProjects/getProjects.js b/public/js/cat_source/es6/api/getProjects/getProjects.js index b60775f6b4..37d1182d8a 100644 --- a/public/js/cat_source/es6/api/getProjects/getProjects.js +++ b/public/js/cat_source/es6/api/getProjects/getProjects.js @@ -32,7 +32,7 @@ export const getProjects = async ({ formData.append(key, data[key]) }) - const res = await fetch(`${getMatecatApiDomain()}?action=getProjects`, { + const res = await fetch(`${getMatecatApiDomain()}api/app/get-projects`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/getProjects/getProjects.test.js b/public/js/cat_source/es6/api/getProjects/getProjects.test.js index f6c8df7682..4dc5e17de4 100644 --- a/public/js/cat_source/es6/api/getProjects/getProjects.test.js +++ b/public/js/cat_source/es6/api/getProjects/getProjects.test.js @@ -11,7 +11,7 @@ test('works properly with empty filter', async () => { const payload = {errors: [], data: {fake: 'data'}} mswServer.use( - http.post(config.basepath, () => { + http.post(config.basepath + 'api/app/get-projects', () => { return HttpResponse.json(payload) }), ) @@ -25,7 +25,7 @@ test('works properly with full filter', async () => { const payload = {errors: [], data: {fake: 'data'}} mswServer.use( - http.post(config.basepath, () => { + http.post(config.basepath + 'api/app/get-projects', () => { return HttpResponse.json(payload) }), ) @@ -43,7 +43,7 @@ test('throws on non empty errors', async () => { const payload = {errors: [500, 'VERY_BAD_ERROR'], data: {fake: 'data'}} mswServer.use( - http.post(config.basepath, () => { + http.post(config.basepath + 'api/app/get-projects', () => { return HttpResponse.json(payload) }), ) diff --git a/public/js/cat_source/es6/api/getSegments/getSegments.js b/public/js/cat_source/es6/api/getSegments/getSegments.js index dbbcce3a7a..899cd8ae73 100644 --- a/public/js/cat_source/es6/api/getSegments/getSegments.js +++ b/public/js/cat_source/es6/api/getSegments/getSegments.js @@ -2,7 +2,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' export const getSegments = async ({jid, password, step, segment, where}) => { const paramsData = { - action: 'getSegments', jid, password, step, @@ -16,7 +15,7 @@ export const getSegments = async ({jid, password, step, segment, where}) => { }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/get-segments`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getTagProjection/getTagProjection.js b/public/js/cat_source/es6/api/getTagProjection/getTagProjection.js index fc19c4786d..7119395784 100644 --- a/public/js/cat_source/es6/api/getTagProjection/getTagProjection.js +++ b/public/js/cat_source/es6/api/getTagProjection/getTagProjection.js @@ -11,7 +11,6 @@ export const getTagProjection = async ({ id_segment, }) => { const paramsData = { - action: 'getTagProjection', id_job, password, source, @@ -28,7 +27,7 @@ export const getTagProjection = async ({ }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/get-tag-projection`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getTranslationMismatches/getTranslationMismatches.js b/public/js/cat_source/es6/api/getTranslationMismatches/getTranslationMismatches.js index f9ce168213..a5e97b1cbe 100644 --- a/public/js/cat_source/es6/api/getTranslationMismatches/getTranslationMismatches.js +++ b/public/js/cat_source/es6/api/getTranslationMismatches/getTranslationMismatches.js @@ -6,7 +6,6 @@ export const getTranslationMismatches = async ({ id_job, }) => { const paramsData = { - action: 'getTranslationMismatches', password, id_segment, id_job, @@ -18,7 +17,7 @@ export const getTranslationMismatches = async ({ }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/get-translation-mismatches`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/getVolumeAnalysis/getVolumeAnalysis.js b/public/js/cat_source/es6/api/getVolumeAnalysis/getVolumeAnalysis.js index fe03c2fcf3..2e5d6f8a4b 100644 --- a/public/js/cat_source/es6/api/getVolumeAnalysis/getVolumeAnalysis.js +++ b/public/js/cat_source/es6/api/getVolumeAnalysis/getVolumeAnalysis.js @@ -22,7 +22,7 @@ export const getVolumeAnalysis = async ( }) const response = await fetch( - `${getMatecatApiDomain()}?action=getVolumeAnalysis`, + `${getMatecatApiDomain()}api/app/get-volume-analysis`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/loadTMX/loadTMX.js b/public/js/cat_source/es6/api/loadTMX/loadTMX.js index d9a252bbab..0065ea12a6 100644 --- a/public/js/cat_source/es6/api/loadTMX/loadTMX.js +++ b/public/js/cat_source/es6/api/loadTMX/loadTMX.js @@ -10,8 +10,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const loadTMX = async ({key, name, uuid}) => { const paramsData = { - action: 'loadTMX', - exec: 'uploadStatus', tm_key: key, name, uuid, @@ -22,7 +20,7 @@ export const loadTMX = async ({key, name, uuid}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/upload-tmx-status`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/markAsResolvedThread/markAsResolvedThread.js b/public/js/cat_source/es6/api/markAsResolvedThread/markAsResolvedThread.js index 885a753052..d4a03f65f1 100644 --- a/public/js/cat_source/es6/api/markAsResolvedThread/markAsResolvedThread.js +++ b/public/js/cat_source/es6/api/markAsResolvedThread/markAsResolvedThread.js @@ -20,8 +20,6 @@ export const markAsResolvedThread = async ({ password = config.password, }) => { const dataParams = { - action: 'comment', - _sub: 'resolve', id_job: idJob, id_segment: idSegment, username, @@ -33,7 +31,7 @@ export const markAsResolvedThread = async ({ Object.keys(dataParams).forEach((key) => { if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=comment`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/comment/resolve`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/mergeJobChunks/mergeJobChunks.js b/public/js/cat_source/es6/api/mergeJobChunks/mergeJobChunks.js index 79708da247..09e1c64487 100644 --- a/public/js/cat_source/es6/api/mergeJobChunks/mergeJobChunks.js +++ b/public/js/cat_source/es6/api/mergeJobChunks/mergeJobChunks.js @@ -3,7 +3,6 @@ import {flattenObject} from '../../utils/queryString' export const mergeJobChunks = async (project, job) => { const params = flattenObject({ - exec: 'merge', project_id: project.id, project_pass: project.password, job_id: job.id, @@ -13,7 +12,7 @@ export const mergeJobChunks = async (project, job) => { Object.keys(params).forEach((key) => { formData.append(key, params[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=splitJob`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/split-job-merge`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/replaceAllIntoSegments/replaceAllIntoSegments.js b/public/js/cat_source/es6/api/replaceAllIntoSegments/replaceAllIntoSegments.js index 393807add8..ca879819e6 100644 --- a/public/js/cat_source/es6/api/replaceAllIntoSegments/replaceAllIntoSegments.js +++ b/public/js/cat_source/es6/api/replaceAllIntoSegments/replaceAllIntoSegments.js @@ -29,8 +29,6 @@ export const replaceAllIntoSegments = async ({ revisionNumber = config.revisionNumber, }) => { const paramsData = { - action: 'getSearch', - function: 'replaceAll', job: idJob, password, token, @@ -47,7 +45,7 @@ export const replaceAllIntoSegments = async ({ Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=getSearch`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/replace-all`, { method: 'POST', body: formData, credentials: 'include', diff --git a/public/js/cat_source/es6/api/searchTermIntoSegments/searchTermIntoSegments.js b/public/js/cat_source/es6/api/searchTermIntoSegments/searchTermIntoSegments.js index a3308a6b33..8c3df83b19 100644 --- a/public/js/cat_source/es6/api/searchTermIntoSegments/searchTermIntoSegments.js +++ b/public/js/cat_source/es6/api/searchTermIntoSegments/searchTermIntoSegments.js @@ -30,8 +30,6 @@ export const searchTermIntoSegments = async ({ revisionNumber, }) => { const paramsData = { - action: 'getSearch', - function: 'find', job: idJob, password, token, @@ -49,7 +47,7 @@ export const searchTermIntoSegments = async ({ Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=getSearch`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/search`, { method: 'POST', body: formData, credentials: 'include', diff --git a/public/js/cat_source/es6/api/setChunkComplete/setChunkComplete.js b/public/js/cat_source/es6/api/setChunkComplete/setChunkComplete.js index efc996b21f..3690aa522c 100644 --- a/public/js/cat_source/es6/api/setChunkComplete/setChunkComplete.js +++ b/public/js/cat_source/es6/api/setChunkComplete/setChunkComplete.js @@ -6,7 +6,6 @@ export const setChunkComplete = async ({ current_password, }) => { const paramsData = { - action: 'Features_ProjectCompletion_SetChunkCompleted', id_job, password, current_password, @@ -18,7 +17,7 @@ export const setChunkComplete = async ({ }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/set-chunk-completed`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/setCurrentSegment/setCurrentSegment.js b/public/js/cat_source/es6/api/setCurrentSegment/setCurrentSegment.js index f4398563fe..04716dae6e 100644 --- a/public/js/cat_source/es6/api/setCurrentSegment/setCurrentSegment.js +++ b/public/js/cat_source/es6/api/setCurrentSegment/setCurrentSegment.js @@ -7,7 +7,6 @@ export const setCurrentSegment = async ({ id_job, }) => { const paramsData = { - action: 'setCurrentSegment', password, revision_number, id_segment, @@ -20,7 +19,7 @@ export const setCurrentSegment = async ({ }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/set-current-segment`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/setTranslation/setTranslation.js b/public/js/cat_source/es6/api/setTranslation/setTranslation.js index c0c3379f6c..134078c5ba 100644 --- a/public/js/cat_source/es6/api/setTranslation/setTranslation.js +++ b/public/js/cat_source/es6/api/setTranslation/setTranslation.js @@ -16,7 +16,7 @@ export const setTranslation = async (objRequest) => { formData.append(key, dataParams[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=setTranslation`, + `${getMatecatApiDomain()}api/app/set-translation`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/shareTmKey/shareTmKey.js b/public/js/cat_source/es6/api/shareTmKey/shareTmKey.js index 3c408f68f8..2f152cc072 100644 --- a/public/js/cat_source/es6/api/shareTmKey/shareTmKey.js +++ b/public/js/cat_source/es6/api/shareTmKey/shareTmKey.js @@ -10,8 +10,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const shareTmKey = async ({key, emails}) => { const paramsData = { - action: 'userKeys', - exec: 'share', key, emails, } @@ -21,7 +19,7 @@ export const shareTmKey = async ({key, emails}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/user-keys-share`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/splitSegment/splitSegment.js b/public/js/cat_source/es6/api/splitSegment/splitSegment.js index 8d19316bab..2dd8b76ec2 100644 --- a/public/js/cat_source/es6/api/splitSegment/splitSegment.js +++ b/public/js/cat_source/es6/api/splitSegment/splitSegment.js @@ -27,7 +27,7 @@ export const splitSegment = async ( formData.append(key, dataParams[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=setSegmentSplit`, + `${getMatecatApiDomain()}api/app/split-segment`, { method: 'POST', credentials: 'include', diff --git a/public/js/cat_source/es6/api/submitComment/submitComment.js b/public/js/cat_source/es6/api/submitComment/submitComment.js index db920a4c59..b9eafa3a7d 100644 --- a/public/js/cat_source/es6/api/submitComment/submitComment.js +++ b/public/js/cat_source/es6/api/submitComment/submitComment.js @@ -25,8 +25,6 @@ export const submitComment = async ({ isAnonymous = false, }) => { const dataParams = { - action: 'comment', - _sub: 'create', id_job: idJob, id_segment: idSegment, revision_number: revisionNumber, @@ -41,7 +39,7 @@ export const submitComment = async ({ Object.keys(dataParams).forEach((key) => { if (dataParams[key] !== undefined) formData.append(key, dataParams[key]) }) - const response = await fetch(`${getMatecatApiDomain()}?action=comment`, { + const response = await fetch(`${getMatecatApiDomain()}api/app/comment/create`, { method: 'POST', credentials: 'include', body: formData, diff --git a/public/js/cat_source/es6/api/tmCreateRandUser/tmCreateRandUser.js b/public/js/cat_source/es6/api/tmCreateRandUser/tmCreateRandUser.js index 9a09d3cea9..fba9e9ae38 100644 --- a/public/js/cat_source/es6/api/tmCreateRandUser/tmCreateRandUser.js +++ b/public/js/cat_source/es6/api/tmCreateRandUser/tmCreateRandUser.js @@ -6,16 +6,14 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' * @returns {Promise} */ export const tmCreateRandUser = async () => { - const paramsData = { - action: 'createRandUser', - } + const paramsData = {} const formData = new FormData() Object.keys(paramsData).forEach((key) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/create-random-user`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js b/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js index 45a1b7b927..42cf2ad737 100644 --- a/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js +++ b/public/js/cat_source/es6/api/updateJobKeys/updateJobKeys.js @@ -19,7 +19,6 @@ export const updateJobKeys = async ({ dataTm, }) => { const paramsData = { - action: 'updateJobKeys', job_id: idJob, job_pass: password, get_public_matches: getPublicMatches, @@ -32,7 +31,7 @@ export const updateJobKeys = async ({ formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/update-job-keys`, { method: 'POST', body: 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..c618270511 100644 --- a/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js +++ b/public/js/cat_source/es6/api/updateTmKey/updateTmKey.js @@ -10,8 +10,6 @@ import {getMatecatApiDomain} from '../../utils/getMatecatApiDomain' */ export const updateTmKey = async ({key, description}) => { const paramsData = { - action: 'userKeys', - exec: 'update', key, description, } @@ -21,7 +19,7 @@ export const updateTmKey = async ({key, description}) => { formData.append(key, paramsData[key]) }) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/user-keys-update`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/api/uploadTm/uploadTm.js b/public/js/cat_source/es6/api/uploadTm/uploadTm.js index cbd0d8f690..540ea0b588 100644 --- a/public/js/cat_source/es6/api/uploadTm/uploadTm.js +++ b/public/js/cat_source/es6/api/uploadTm/uploadTm.js @@ -19,8 +19,6 @@ export const uploadTm = async ({ password = config.password, }) => { const paramsData = { - action: 'loadTMX', - exec: 'newTM', tm_key: tmKey, name: keyName, r: '1', @@ -37,7 +35,7 @@ export const uploadTm = async ({ ) const response = await fetch( - `${getMatecatApiDomain()}?action=${paramsData.action}`, + `${getMatecatApiDomain()}api/app/new-tmx`, { method: 'POST', body: formData, diff --git a/public/js/cat_source/es6/components/analyze/AnalyzeHeader.js b/public/js/cat_source/es6/components/analyze/AnalyzeHeader.js index 2e47a11e35..226d0346f3 100644 --- a/public/js/cat_source/es6/components/analyze/AnalyzeHeader.js +++ b/public/js/cat_source/es6/components/analyze/AnalyzeHeader.js @@ -343,8 +343,7 @@ class AnalyzeHeader extends React.Component { var ppassword = config.password var form = - '
' + - ' ' + + ' ' + ' ' + diff --git a/public/js/cat_source/es6/components/header/manage/FilterProjects.test.js b/public/js/cat_source/es6/components/header/manage/FilterProjects.test.js index 7341749747..83290b6585 100644 --- a/public/js/cat_source/es6/components/header/manage/FilterProjects.test.js +++ b/public/js/cat_source/es6/components/header/manage/FilterProjects.test.js @@ -1159,7 +1159,7 @@ const apiGetProjects = { const executeMswServer = (response) => { mswServer.use( ...[ - http.post(config.basepath, () => { + http.post(config.basepath + 'api/app/get-projects', () => { return HttpResponse.json(response) }), ], diff --git a/public/js/cat_source/es6/components/outsource/OutsourceVendor.js b/public/js/cat_source/es6/components/outsource/OutsourceVendor.js index 2e26daafbc..2529bb2724 100644 --- a/public/js/cat_source/es6/components/outsource/OutsourceVendor.js +++ b/public/js/cat_source/es6/components/outsource/OutsourceVendor.js @@ -86,54 +86,62 @@ class OutsourceVendor extends React.Component { typeOfService, timezoneToShow, currency, - ).then(function (quoteData) { - if (quoteData.data && quoteData.data.length > 0) { - if ( - quoteData.data[0][0].quote_available !== '1' && - quoteData.data[0][0].outsourced !== '1' - ) { + ) + .then(function (quoteData) { + if (quoteData.data && quoteData.data.length > 0) { + if ( + quoteData.data[0][0].quote_available !== '1' && + quoteData.data[0][0].outsourced !== '1' + ) { + self.setState({ + outsource: true, + quoteNotAvailable: true, + }) + return + } else if ( + quoteData.data[0][0].quote_result !== '1' && + quoteData.data[0][0].outsourced !== '1' + ) { + self.setState({ + outsource: true, + errorQuote: true, + }) + return + } + + self.quoteResponse = quoteData.data[0] + let chunk = Immutable.fromJS(quoteData.data[0][0]) + + self.url_ok = quoteData.return_url.url_ok + self.url_ko = quoteData.return_url.url_ko + self.confirm_urls = quoteData.return_url.confirm_urls + self.data_key = chunk.get('id') + self.setState({ outsource: true, - quoteNotAvailable: true, + quoteNotAvailable: false, + errorQuote: false, + chunkQuote: chunk, + revision: chunk.get('typeOfService') === 'premium' ? true : false, + jobOutsourced: chunk.get('outsourced') === '1', + outsourceConfirmed: chunk.get('outsourced') === '1', + deliveryDate: new Date(chunk.get('delivery')), }) - return - } else if ( - quoteData.data[0][0].quote_result !== '1' && - quoteData.data[0][0].outsourced !== '1' - ) { + } else { self.setState({ - outsource: true, + outsource: false, errorQuote: true, + errorOutsource: true, }) - return } - - self.quoteResponse = quoteData.data[0] - let chunk = Immutable.fromJS(quoteData.data[0][0]) - - self.url_ok = quoteData.return_url.url_ok - self.url_ko = quoteData.return_url.url_ko - self.confirm_urls = quoteData.return_url.confirm_urls - self.data_key = chunk.get('id') - - self.setState({ - outsource: true, - quoteNotAvailable: false, - errorQuote: false, - chunkQuote: chunk, - revision: chunk.get('typeOfService') === 'premium' ? true : false, - jobOutsourced: chunk.get('outsourced') === '1', - outsourceConfirmed: chunk.get('outsourced') === '1', - deliveryDate: new Date(chunk.get('delivery')), - }) - } else { - self.setState({ + }) + .catch(() => { + this.setState({ outsource: false, errorQuote: true, errorOutsource: true, }) - } - }) + }) } getCurrentCurrency() { 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..75c0779336 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 @@ -152,7 +152,9 @@ export const TMCreateResourceRow = ({row}) => { title: 'Invalid key', type: 'error', text: - !errors || errors[0].code === '23000' + !errors || + errors.length === 0 || + (errors.length && errors[0].code === '23000') ? 'The key you entered is invalid.' : errors[0].message, position: 'br', diff --git a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.test.js b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.test.js index 29eda60807..5e92715892 100644 --- a/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.test.js +++ b/public/js/cat_source/es6/components/settingsPanel/Contents/TranslationMemoryGlossaryTab/TranslationMemoryGlossaryTab.test.js @@ -87,7 +87,93 @@ const ROW_COLUMNS_TESTID = { } const getRowElementById = ({id, column}) => screen.queryByTestId(`${column}-${id}`) +const executeMswServer = () => { + mswServer.use( + http.post(config.basepath + 'api/app/create-random-user', ({request}) => { + const response = { + errors: [], + data: { + key: '6f03df0307c7a161afa9', + id: 'MyMemory_5007d86025f58b50f29c', + pass: 'f22815f87d', + mtLangSupported: true, + error: { + code: 0, + message: '', + }, + }, + } + + return HttpResponse.json(response) + }), + ) + + mswServer.use( + http.post(config.basepath + 'api/app/user-keys-new-key', ({request}) => { + const response = { + errors: [], + data: { + key: '6f03df0307c7a161afa9', + id: 'MyMemory_5007d86025f58b50f29c', + pass: 'f22815f87d', + mtLangSupported: true, + error: { + code: 0, + message: '', + }, + }, + } + + return HttpResponse.json(response) + }), + ) + + mswServer.use( + http.post(config.basepath + 'api/app/user-keys-delete', ({request}) => { + const response = { + errors: [], + data: { + key: '6f03df0307c7a161afa9', + id: 'MyMemory_5007d86025f58b50f29c', + pass: 'f22815f87d', + mtLangSupported: true, + error: { + code: 0, + message: '', + }, + }, + } + + return HttpResponse.json(response) + }), + ) + mswServer.use( + http.post(config.basepath + 'api/app/check-tm-key', ({request}) => { + const response = {errors: [], data: [], success: true} + + return HttpResponse.json(response) + }), + ) + mswServer.use( + http.post(config.basepath + 'api/app/user-keys-info', ({request}) => { + const response = { + errors: [], + data: { + key: '6f03df0307c7a161afa9', + id: 'MyMemory_5007d86025f58b50f29c', + pass: 'f22815f87d', + mtLangSupported: true, + error: { + code: 0, + message: '', + }, + }, + } + return HttpResponse.json(response) + }), + ) +} test('Operation about MyMemory row', async () => { const user = userEvent.setup() const contextValues = contextMockValues() @@ -188,31 +274,7 @@ test('Enabled/disable key', async () => { }) test('Create and delete new resource', async () => { - mswServer.use( - http.post(config.basepath, ({request}) => { - const url = new URL(request.url) - const action = url.searchParams.get('action') - const response = - action === 'createRandUser' - ? { - errors: [], - data: { - key: '6f03df0307c7a161afa9', - id: 'MyMemory_5007d86025f58b50f29c', - pass: 'f22815f87d', - mtLangSupported: true, - error: { - code: 0, - message: '', - }, - }, - } - : {errors: [], data: []} - - return HttpResponse.json(response) - }), - ) - + executeMswServer() const user = userEvent.setup() const contextValues = contextMockValues({noTmKeys: true}) @@ -275,19 +337,7 @@ test('Create and delete new resource', async () => { }) test('Create shared resource', async () => { - mswServer.use( - http.post(config.basepath, ({request}) => { - const url = new URL(request.url) - const action = url.searchParams.get('action') - const response = - action === 'ajaxUtils' - ? {errors: [], data: [], success: true} - : {errors: [], data: []} - - return HttpResponse.json(response) - }), - ) - + executeMswServer() const user = userEvent.setup() const contextValues = contextMockValues({noTmKeys: true}) @@ -338,30 +388,7 @@ test('Create shared resource', async () => { }) test('Row Menu items', async () => { - mswServer.use( - http.post(config.basepath, ({request}) => { - const url = new URL(request.url) - const action = url.searchParams.get('action') - const response = - action === 'createRandUser' - ? { - errors: [], - data: { - key: '6f03df0307c7a161afa9', - id: 'MyMemory_5007d86025f58b50f29c', - pass: 'f22815f87d', - mtLangSupported: true, - error: { - code: 0, - message: '', - }, - }, - } - : {errors: [], data: []} - - return HttpResponse.json(response) - }), - ) + executeMswServer() const user = userEvent.setup() const contextValues = contextMockValues({noTmKeys: true}) diff --git a/public/js/cat_source/es6/pages/NewProject.js b/public/js/cat_source/es6/pages/NewProject.js index d1d5a66ef0..69903bbda1 100644 --- a/public/js/cat_source/es6/pages/NewProject.js +++ b/public/js/cat_source/es6/pages/NewProject.js @@ -412,7 +412,6 @@ const NewProject = () => { // update store recently used target languages setRecentlyUsedLanguages(targetLangs) const getParams = () => ({ - action: 'createProject', file_name: getFilenameFromUploadedFiles(), project_name: projectNameRef.current.value, source_lang: sourceLang.id, diff --git a/yarn.lock b/yarn.lock index 65a057f703..332a415c77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2532,10 +2532,26 @@ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== -"@types/estree@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/eslint-scope@^3.7.7": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== "@types/glob@^7.1.1": version "7.2.0" @@ -2585,7 +2601,7 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2614,11 +2630,6 @@ dependencies: undici-types "~6.19.2" -"@types/semver@^7.3.12": - version "7.5.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== - "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -2651,15 +2662,7 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/scope-manager@8.16.0": +"@typescript-eslint/scope-manager@8.16.0", "@typescript-eslint/scope-manager@^8.15.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz#ebc9a3b399a69a6052f3d88174456dd399ef5905" integrity sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg== @@ -2667,29 +2670,11 @@ "@typescript-eslint/types" "8.16.0" "@typescript-eslint/visitor-keys" "8.16.0" -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - "@typescript-eslint/types@8.16.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.16.0.tgz#49c92ae1b57942458ab83d9ec7ccab3005e64737" integrity sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ== -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@8.16.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz#9d741e56e5b13469b5190e763432ce5551a9300c" @@ -2704,21 +2689,7 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@^5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/utils@^8.15.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.16.0.tgz#c71264c437157feaa97842809836254a6fc833c3" integrity sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA== @@ -2728,14 +2699,6 @@ "@typescript-eslint/types" "8.16.0" "@typescript-eslint/typescript-estree" "8.16.0" -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@8.16.0": version "8.16.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz#d5086afc060b01ff7a4ecab8d49d13d5a7b07705" @@ -2908,11 +2871,6 @@ acorn-globals@^7.0.0: acorn "^8.1.0" acorn-walk "^8.0.2" -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -2923,11 +2881,16 @@ acorn-walk@^8.0.2: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.1.0, acorn@^8.8.1, acorn@^8.8.2: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" @@ -3306,16 +3269,6 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.21.10: - version "4.22.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" - integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== - dependencies: - caniuse-lite "^1.0.30001580" - electron-to-chromium "^1.4.648" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - browserslist@^4.22.2: version "4.23.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" @@ -3382,11 +3335,6 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001580: - version "1.0.30001607" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz#b91e8e033f6bca4e13d3d45388d87fa88931d9a5" - integrity sha512-WcvhVRjXLKFB/kmOFVwELtMxyhq3iM/MvmXcyCe2PNf166c39mptscOc/45TTS96n2gpNV2z7+NakArTWZCQ3w== - caniuse-lite@^1.0.30001640: version "1.0.30001655" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz#0ce881f5a19a2dcfda2ecd927df4d5c1684b982f" @@ -3978,11 +3926,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.648: - version "1.4.730" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.730.tgz#5e382c83085b50b9c63cb08692e8fcd875c1b9eb" - integrity sha512-oJRPo82XEqtQAobHpJIR3zW5YO3sSRRkPz2an4yxi1UvqhsGm54vR/wzTFV74a3soDOJ8CKW7ajOOX5ESzddwg== - electron-to-chromium@^1.4.820: version "1.5.13" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" @@ -4222,7 +4165,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -escalade@^3.1.1, escalade@^3.1.2, escalade@^3.2.0: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -4338,14 +4281,15 @@ eslint-plugin-react@^7.23.2: string.prototype.matchall "^4.0.11" string.prototype.repeat "^1.0.0" -eslint-plugin-testing-library@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.5.0.tgz#02532698270f525be8ceeb4740f66eef0f6f4729" - integrity sha512-Ls5TUfLm5/snocMAOlofSOJxNN0aKqwTlco7CrNtMjkTdQlkpSMaeTCDHCuXfzrI97xcx2rSCNeKeJjtpkNC1w== +eslint-plugin-testing-library@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.0.0.tgz#9a2ff094d33abab2214f41bcd1a54004b22091e9" + integrity sha512-Bwrn5Qi08Lf5Huv4ZGDNYxwkFLAyGQIPB9lC0ALlojymP32aKsSxWnccP1NvIcI5vMhkENg4Y5Td/Q9/tLYmGQ== dependencies: - "@typescript-eslint/utils" "^5.62.0" + "@typescript-eslint/scope-manager" "^8.15.0" + "@typescript-eslint/utils" "^8.15.0" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -4366,7 +4310,7 @@ eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -4524,7 +4468,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.0.3, fast-glob@^3.2.9, fast-glob@^3.3.2: +fast-glob@^3.0.3, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -4903,18 +4847,6 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -6250,7 +6182,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.2.3, merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -6720,7 +6652,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -6837,9 +6769,9 @@ postcss@^8.4.33: source-map-js "^1.0.2" posthog-js@^1.57.2: - version "1.190.2" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.190.2.tgz#3c906800b46308abc07adfac422a86029fd4e736" - integrity sha512-H9PirkFZDirh/aPmxcmFv1CBozCjGPRqUR6lulgaq2sP9Jtt8Z/yT42MZuGUcZNrUVdjQ7aNgBJq7nplB8cASg== + version "1.194.1" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.194.1.tgz#cc867858c9c4258e2cce9732f76d19a8833b372c" + integrity sha512-d68hmU9DY4iPe3WneBlnglERhimRhXuF7Lx0Au6OTmOL+IFdFUxB3Qf5LaLqJc1QLt3NUolMq1HiXOaIULe3kQ== dependencies: core-js "^3.38.1" fflate "^0.4.8" @@ -6847,9 +6779,9 @@ posthog-js@^1.57.2: web-vitals "^4.2.0" preact@^10.19.3: - version "10.25.0" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.0.tgz#22a1c93ce97336c5d01d74f363433ab0cd5cde64" - integrity sha512-6bYnzlLxXV3OSpUxLdaxBmE7PMOu0aR3pG6lryK/0jmvcDFPlcXGQAt5DpK3RITWiDrfYZRI0druyaK/S9kYLg== + version "10.25.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.1.tgz#1c4b84253c42dee874bfbf6a92bdce45e3662665" + integrity sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og== prelude-ls@^1.2.1: version "1.2.1" @@ -6977,9 +6909,9 @@ react-hook-form@^7.45.4: integrity sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw== react-hotkeys-hook@^4.5.0: - version "4.5.1" - resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz#990260ecc7e5a431414148a93b02a2f1a9707897" - integrity sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg== + version "4.6.1" + resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-4.6.1.tgz#db9066c07377a1c8be067a238ab16e328266345a" + integrity sha512-XlZpbKUj9tkfgPgT9gA+1p7Ey6vFIZHttUjPqpTdyT5nqQ8mHL7elxvSbaC+dpSiHUSmr21Ya1mDxBZG3aje4Q== react-is@^16.13.1: version "16.13.1" @@ -7359,11 +7291,6 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.6.0: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -7371,6 +7298,11 @@ semver@^7.5.3, semver@^7.5.4: dependencies: lru-cache "^6.0.0" +semver@^7.6.0: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + serialize-javascript@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -7859,11 +7791,6 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.0.0, tslib@^2.1.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" @@ -7874,13 +7801,6 @@ tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -8027,14 +7947,6 @@ unplugin@1.0.1: webpack-sources "^3.2.3" webpack-virtual-modules "^0.5.0" -update-browserslist-db@^1.0.13: - version "1.0.16" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" - integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== - dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" - update-browserslist-db@^1.1.0, update-browserslist-db@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" @@ -8189,18 +8101,18 @@ webpack-virtual-modules@^0.5.0: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== -webpack@5.95.0: - version "5.95.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.95.0.tgz#8fd8c454fa60dad186fbe36c400a55848307b4c0" - integrity sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q== +webpack@5.96.1: + version "5.96.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.96.1.tgz#3676d1626d8312b6b10d0c18cc049fba7ac01f0c" + integrity sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA== dependencies: - "@types/estree" "^1.0.5" + "@types/eslint-scope" "^3.7.7" + "@types/estree" "^1.0.6" "@webassemblyjs/ast" "^1.12.1" "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" - acorn "^8.7.1" - acorn-import-attributes "^1.9.5" - browserslist "^4.21.10" + acorn "^8.14.0" + browserslist "^4.24.0" chrome-trace-event "^1.0.2" enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1"