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: + //
\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: - //