first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\LIB\WP\Post;
use WPML\TM\API\Jobs;
use function WPML\FP\partial;
use function WPML\FP\pipe;
class AcceptTranslation implements IHandler {
public function run( Collection $data ) {
$postId = $data->get( 'postId' );
$jobId = $data->get( 'jobId' );
$canEdit = partial( 'current_user_can', 'edit_post' );
$completeJob = Fns::tap( pipe(
Fns::always( $jobId ),
Fns::tap( Jobs::setStatus( Fns::__, ICL_TM_COMPLETE ) ),
Fns::tap( Jobs::setReviewStatus( Fns::__, ReviewStatus::ACCEPTED ) )
) );
return Either::of( $postId )
->filter( $canEdit )
->map( Post::setStatusWithoutFilters( Fns::__, 'publish' ) )
->map( $completeJob )
->bimap( Fns::always( $jobId ), Fns::always( $jobId ) );
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use WPML\LIB\WP\Post;
use WPML\Setup\Option;
use WPML\TM\API\Jobs;
use function WPML\FP\pipe;
use function WPML\FP\spreadArgs;
class ApplyJob implements \IWPML_Backend_Action, \IWPML_REST_Action, \IWPML_AJAX_Action {
/** @var string[] */
private static $excluded_from_review = [ 'st-batch', 'package' ];
public function add_hooks() {
if ( Option::shouldBeReviewed() ) {
self::addJobStatusHook();
self::addTranslationCompleteHook();
self::addTranslationPreSaveHook();
}
}
private static function addJobStatusHook() {
$applyReviewStatus = function ( $status, $job ) {
if (
self::shouldBeReviewed( $job )
&& $status === ICL_TM_COMPLETE
) {
Jobs::setReviewStatus(
(int) $job->job_id,
ReviewStatus::NEEDS_REVIEW );
}
return $status;
};
Hooks::onFilter( 'wpml_tm_applied_job_status', 10, 2 )
->then( spreadArgs( $applyReviewStatus ) );
}
private static function addTranslationCompleteHook() {
$isHoldToReviewMode = Fns::always( Option::getReviewMode() === 'before-publish' );
$isPostNewlyCreated = Fns::converge( Relation::equals(), [
Obj::prop( 'post_date' ),
Obj::prop( 'post_modified' )
] );
$setPostStatus = pipe(
Maybe::of(),
Fns::filter( $isHoldToReviewMode ),
Fns::map( Post::get() ),
Fns::filter( Logic::isNotNull() ),
Fns::filter( $isPostNewlyCreated ),
Fns::map( Obj::prop( 'ID' ) ),
Fns::map( Post::setStatus( Fns::__, 'draft' ) )
);
Hooks::onAction( 'wpml_pro_translation_completed' )
->then( spreadArgs( $setPostStatus ) );
}
private static function addTranslationPreSaveHook() {
$keepDraftPostsDraftIfNeedsReview = function ( $postArr, $job ) {
if (
self::shouldBeReviewed( $job )
&& isset( $postArr['ID'] )
&& get_post_status( $postArr['ID'] ) === 'draft'
) {
$postArr['post_status'] = 'draft';
}
return $postArr;
};
Hooks::onFilter( 'wpml_pre_save_pro_translation', 10, 2 )
->then( spreadArgs( $keepDraftPostsDraftIfNeedsReview ) );
}
/**
* @param $job
*
* @return bool
*/
private static function shouldBeReviewed( $job ) {
return ! Lst::includes( $job->element_type_prefix, self::$excluded_from_review )
&& $job->automatic
&& (int) $job->original_doc_id !== (int) get_option( 'page_for_posts' );
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
use WPML\TM\API\Jobs;
use function WPML\Container\make;
class ApproveTranslations implements IHandler {
public function run( Collection $data ) {
$jobIds = $data->get( 'jobsIds' );
return wpml_collect( $jobIds )
->map( Jobs::get() )
->filter( ReviewStatus::doesJobNeedReview() )
->map( Obj::addProp( 'translated_id', Jobs::getTranslatedPostId() ) )
->map( Obj::props( [ 'job_id', 'translated_id' ] ) )
->map( Lst::zipObj( [ 'jobId', 'postId' ] ) )
->map( 'wpml_collect' )
->map( [ make( AcceptTranslation::class ), 'run' ] )
->map( function ( Either $result ) {
$isJobApproved = Fns::isRight( $result );
$jobId = $result->coalesce( Fns::identity(), Fns::identity() )->getOrElse( 0 );
return [ 'jobId' => $jobId, 'status' => $isJobApproved ];
} );
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Cast;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Post;
use WPML\TM\API\Job\Map;
use WPML\TM\API\Jobs;
use function WPML\FP\pipe;
class Cancel implements IHandler {
public function run( Collection $data ) {
$jobIds = $data->get( 'jobsIds' );
$deleteDrafts = $data->get( 'deleteDrafts' );
$reviewJobs = wpml_collect( $jobIds )
->map( Jobs::get() )
->filter( ReviewStatus::doesJobNeedReview() );
if ( $reviewJobs->count() ) {
$reviewJobs->map( Obj::prop( 'job_id' ) )
->map( Jobs::clearReviewStatus() );
if ( $deleteDrafts ) {
$this->deleteDrafts( $reviewJobs );
}
return $reviewJobs->pluck( 'job_id' )->map( Cast::toInt() );
}
return [];
}
private function deleteDrafts( Collection $reviewJobs ) {
$doCancelJobsAction = function ( Collection $jobIds ) {
$getJobEntity = function ( $jobId ) {
return wpml_tm_get_jobs_repository()->get_job( Map::fromJobId( $jobId ), \WPML_TM_Job_Entity::POST_TYPE );
};
$jobEntities = $jobIds->map( $getJobEntity )->toArray();
do_action( 'wpml_tm_jobs_cancelled', $jobEntities );
};
$getTranslatedId = Fns::memorize( pipe( Jobs::get(), Jobs::getTranslatedPostId() ) );
$isDraft = pipe( $getTranslatedId, Post::getStatus(), Relation::equals( 'draft' ) );
$deleteTranslatedPost = pipe( $getTranslatedId, Post::delete() );
$reviewJobs = $reviewJobs->map( Obj::prop( 'job_id' ) )
->map( Jobs::setNotTranslatedStatus() )
->map( Jobs::clearTranslated() );
$doCancelJobsAction( $reviewJobs );
$reviewJobs->filter( $isDraft )
->map( Fns::tap( $deleteTranslatedPost ) )
->map( Jobs::delete() );
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Element\API\Post;
use WPML\Element\API\PostTranslations;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\TM\API\Translators;
use WPML_Translations_Queue;
use function WPML\FP\invoke;
use function WPML\FP\partial;
use function WPML\FP\pipe;
class NextTranslationLink {
public static function get( $currentJob ) {
global $sitepress;
$doNotFilterPreviewLang = Fns::always( false );
$addPreviewLangFilter = function () use ( $doNotFilterPreviewLang ) {
add_filter( 'wpml_should_filter_preview_lang', $doNotFilterPreviewLang );
};
$removePreviewLangFilter = function () use ( $doNotFilterPreviewLang ) {
remove_filter( 'wpml_should_filter_preview_lang', $doNotFilterPreviewLang );
};
$getTranslationPostId = Fns::memorize( self::getTranslationPostId() );
$switchToPostLang = function ( $job ) use ( $sitepress, $getTranslationPostId ) {
Maybe::of( $job )
->chain( $getTranslationPostId )
->map( Post::getLang() )
->map( [ $sitepress, 'switch_lang' ] );
};
$restoreLang = function () use ( $sitepress ) {
$sitepress->switch_lang( null );
};
$getLink = Fns::converge( Fns::liftA2( PreviewLink::get() ), [
$getTranslationPostId,
Maybe::safe( invoke( 'get_translate_job_id' ) )
] );
return Maybe::of( $currentJob )
->map( self::getNextJob() )
->map( Fns::tap( $switchToPostLang ) )
->map( Fns::tap( $addPreviewLangFilter ) )
->chain( $getLink )
->map( Fns::tap( $removePreviewLangFilter ) )
->map( Fns::tap( $restoreLang ) )
->getOrElse( null );
}
private static function getTranslationPostId() {
return function ( $nextJob ) {
return Maybe::of( $nextJob )
->map( pipe( invoke( 'get_original_element_id' ), PostTranslations::get() ) )
->map( Obj::prop( $nextJob->get_target_language() ) )
->map( Obj::prop( 'element_id' ) );
};
}
/**
* @return \Closure :: \stdClass -> \WPML_TM_Post_Job_Entity
*/
private static function getNextJob() {
return function ( $currentJob ) {
$getJob = function ( $sourceLanguage, $targetLanguages ) use ( $currentJob ) {
$excludeCurrentJob = pipe( invoke( 'get_translate_job_id' ), Relation::equals( (int) $currentJob->job_id ), Logic::not() );
$samePostTypes = function ( $nextJob ) use ( $currentJob ) {
$currentJobPostType = \get_post_type( $currentJob->original_doc_id );
$nextJobPostType = \get_post_type( $nextJob->get_original_element_id() );
return $currentJobPostType === $nextJobPostType;
};
$nextJob = \wpml_collect(wpml_tm_get_jobs_repository()
->get( self::buildSearchParams( $sourceLanguage, $targetLanguages ) ) )
->filter( $samePostTypes )
->first( $excludeCurrentJob );
if ( ! $nextJob ) {
$nextJob = \wpml_collect( wpml_tm_get_jobs_repository()
->get( self::buildSearchParams( $sourceLanguage, $targetLanguages ) ) )
->first( $excludeCurrentJob );
}
return $nextJob;
};
$languagePairs = \wpml_collect( Obj::propOr( [], 'language_pairs', Translators::getCurrent() ) );
$filterTargetLanguages = function ( $targetLanguages, $sourceLanguage ) {
$icl_translation_filter = WPML_Translations_Queue::get_cookie_filters();
if ( isset( $icl_translation_filter['to'] ) && '' !== $icl_translation_filter['to'] ) {
return [
'source' => $sourceLanguage,
'targets' => [ $icl_translation_filter['to'] ],
];
}
return [
'source' => $sourceLanguage,
'targets' => $targetLanguages,
];
};
$filterJobByPairOfLanguages = function ( $job, $pairOfLanguages ) use ( $getJob ) {
return $job ?: $getJob( Obj::prop( 'source', $pairOfLanguages ), Obj::prop( 'targets', $pairOfLanguages ) );
};
return $languagePairs
->map($filterTargetLanguages)
->reduce($filterJobByPairOfLanguages);
};
}
/**
* @param string $sourceLang
* @param string[] $targetLanguages
*
* @return \WPML_TM_Jobs_Search_Params
*/
private static function buildSearchParams( $sourceLang, array $targetLanguages ) {
return ( new \WPML_TM_Jobs_Search_Params() )
->set_needs_review()
->set_source_language( $sourceLang )
->set_target_language( $targetLanguages );
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Obj;
/**
* This will allow displaying private CPT reviews on the frontend.
*/
class NonPublicCPTPreview {
const POST_TYPE = 'wpmlReviewPostType';
/**
* @param array $args
*
* @return array
*/
public static function addArgs( array $args ) {
return Obj::assoc( self::POST_TYPE, \get_post_type( $args['preview_id'] ), $args );
}
/**
* @return callable
*/
public static function allowReviewPostTypeQueryVar() {
return Lst::append( self::POST_TYPE );
}
/**
* @return callable
*/
public static function enforceReviewPostTypeIfSet() {
return Logic::ifElse(
Obj::prop( self::POST_TYPE ),
Obj::renameProp( self::POST_TYPE, 'post_type' ),
Fns::identity()
) ;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Collect\Support\Traits\Macroable;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\FP\Str;
use WPML\TM\API\Jobs;
use function WPML\FP\curryN;
/**
* Class PreviewLink
*
* @package WPML\TM\ATE\Review
*
* @method static callable|string getWithSpecifiedReturnUrl( ...$returnUrl, ...$translationPostId, ...$jobId ) : Curried:: int->int->string
* @method static callable|string get( ...$translationPostId, ...$jobId ) : Curried:: int->int->string
* @method static callable|string getByJob( ...$job ) : Curried:: \stdClass->string
* @method static Callable|string getNonceName( ...$translationPostId ) : Curried:: int->string
*/
class PreviewLink {
use Macroable;
public static function init() {
self::macro( 'getWithSpecifiedReturnUrl', curryN( 3, function ( $returnUrl, $translationPostId, $jobId ) {
return \add_query_arg(
NonPublicCPTPreview::addArgs( [
'p' => $translationPostId,
'preview_id' => $translationPostId,
'preview_nonce' => \wp_create_nonce( self::getNonceName( $translationPostId ) ),
'preview' => true,
'jobId' => $jobId,
'returnUrl' => urlencode( $returnUrl ),
] ),
\get_permalink( $translationPostId )
);
} ) );
self::macro( 'get', curryN( 2, function ( $translationPostId, $jobId ) {
$returnUrl = Obj::propOr( Obj::prop( 'REQUEST_URI', $_SERVER ), 'returnUrl', $_GET );
return self::getWithSpecifiedReturnUrl( $returnUrl, $translationPostId, $jobId );
} ) );
self::macro( 'getByJob', curryN( 1, Fns::converge(
self::get(),
[
Jobs::getTranslatedPostId(),
Obj::prop( 'job_id' ),
]
) ) );
self::macro( 'getNonceName', Str::concat( 'post_preview_' ) );
}
}
PreviewLink::init();

View File

@@ -0,0 +1,19 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\FP\Relation;
class ReviewCompletedNotice implements \IWPML_Backend_Action {
public function add_hooks() {
if ( Relation::propEq( 'reviewCompleted', 'inWPML', $_GET ) ) {
$text = esc_html__( "You've completed reviewing everything. WPML will let you know when there's new content to review.", 'wpml-translation-management' );
wpml_get_admin_notices()->add_notice(
\WPML_Notice::make( 'reviewCompleted', $text )
->set_css_class_types( 'notice-info' )
->set_flash()
);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Collect\Support\Traits\Macroable;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Obj;
use function WPML\FP\curryN;
use function WPML\FP\pipe;
/**
* Class ReviewStatus
* @package WPML\TM\ATE\Review
*
* @method static callable|bool needsReview( ...$reviewStatus ) - Curried :: string->bool
* @method static callable|bool doesJobNeedReview( ...$job ) - Curried :: \stdClass->bool
*/
class ReviewStatus {
use Macroable;
const NEEDS_REVIEW = 'NEEDS_REVIEW';
const EDITING = 'EDITING';
const ACCEPTED = 'ACCEPTED';
public static function init() {
self::macro( 'needsReview', Lst::includes( Fns::__, [ ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ] ) );
self::macro( 'doesJobNeedReview', curryN( 1, Logic::ifElse(
Fns::identity(),
pipe( Obj::prop( 'review_status' ), self::needsReview() ),
Fns::always(false)
) ));
}
}
ReviewStatus::init();

View File

@@ -0,0 +1,194 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\API\Sanitize;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use WPML\LIB\WP\Post;
use WPML\Element\API\Post as WPMLPost;
use WPML\TM\API\Jobs;
use WPML\TM\API\Translators;
use WPML\TM\WP\App\Resources;
use WPML\FP\Obj;
use function WPML\FP\pipe;
use function WPML\FP\spreadArgs;
class ReviewTranslation implements \IWPML_Frontend_Action, \IWPML_Backend_Action {
public function add_hooks() {
if ( self::hasValidNonce() ) {
Hooks::onFilter( 'query_vars' )
->then( spreadArgs( NonPublicCPTPreview::allowReviewPostTypeQueryVar() ) );
Hooks::onFilter( 'request' )
->then( spreadArgs( NonPublicCPTPreview::enforceReviewPostTypeIfSet() ) );
Hooks::onFilter( 'the_preview' )
->then( Hooks::getArgs( [ 0 => 'post' ] ) )
->then( $this->handleTranslationReview() );
}
Hooks::onFilter( 'user_has_cap', 10, 3 )
->then( spreadArgs( function ( $userCaps, $requiredCaps, $args ) {
if ( Relation::propEq( 0, 'edit_post', $args ) ) {
$translator = Translators::getCurrent();
if ( $translator->ID ) {
$postId = $args[2];
$job = Jobs::getPostJob( $postId, Post::getType( $postId ), WPMLPost::getLang( $postId ) );
if ( ReviewStatus::doesJobNeedReview( $job ) && self::canEditLanguage( $translator, $job ) ) {
return Lst::concat( $userCaps, Lst::zipObj( $requiredCaps, Lst::repeat( true, count( $requiredCaps ) ) ) );
}
}
return $userCaps;
}
return $userCaps;
} ) );
Hooks::onFilter( 'wpml_tm_allowed_translators_for_job', 10, 2 )
->then( spreadArgs( function ( $allowedTranslators, \WPML_Element_Translation_Job $job ) {
$job = $job->to_array();
$translator = Translators::getCurrent();
if ( ReviewStatus::doesJobNeedReview( $job ) && self::canEditLanguage( $translator, $job ) ) {
return array_merge( $allowedTranslators, [ $translator->ID ] );
}
return $allowedTranslators;
} ) );
}
private static function canEditLanguage( $translator, $job ) {
if ( ! $job ) {
return false;
}
return Lst::includes( Obj::prop('language_code', $job), Obj::pathOr( [], [ 'language_pairs', Obj::prop('source_language_code', $job) ], $translator ) );
}
/**
* This will ensure to block the standard preview
* for non-public CPTs.
*
* @return bool
*/
private static function hasValidNonce() {
$get = Obj::prop( Fns::__, $_GET );
return (bool) \wp_verify_nonce(
$get( 'preview_nonce' ),
PreviewLink::getNonceName( (int) $get( 'preview_id' ) )
);
}
public function handleTranslationReview() {
return function ( $data ) {
$post = Obj::prop( 'post', $data );
$jobId = filter_input( INPUT_GET, 'jobId', FILTER_SANITIZE_NUMBER_INT );
if ( $jobId ) {
/**
* This hooks is fired as soon as a translation review is about to be displayed.
*
* @since 4.5.0
*
* @param int $jobId The job Id.
* @param object|\WP_Post $post The job's related object to be reviewed.
*/
do_action( 'wpml_tm_handle_translation_review', $jobId, $post );
Hooks::onFilter( 'wp_redirect' )
->then( [ __CLASS__, 'failGracefullyOnPreviewRedirection' ] );
Hooks::onAction( 'template_redirect', PHP_INT_MAX )
->then( function () {
Hooks::onAction( 'wp_footer' )
->then( [ __CLASS__, 'printReviewToolbarAnchor' ] );
} );
show_admin_bar( false );
$enqueue = Resources::enqueueApp( 'translationReview' );
$enqueue( $this->getData( $jobId, $post ) );
}
return $post;
};
}
public static function printReviewToolbarAnchor() {
echo '
<script type="text/javascript" >
var ajaxurl = "' . \admin_url( 'admin-ajax.php', 'relative' ) . '"
</script>
<div id="wpml_translation_review"></div>
';
}
/**
* @return null This will stop the redirection.
*/
public static function failGracefullyOnPreviewRedirection() {
do_action( 'wp_head' );
self::printReviewToolbarAnchor();
echo '
<div class="wpml-review__modal-mask wpml-review__modal-mask-transparent">
<div class="wpml-review__modal-box wpml-review__modal-box-transparent wpml-review__modal-preview-not-available">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 0C10.8 0 0 10.8 0 24C0 37.2 10.8 48 24 48C37.2 48 48 37.2 48 24C48 10.8 37.2 0 24 0ZM24 43.5C13.2 43.5 4.5 34.8 4.5 24C4.5 13.2 13.2 4.5 24 4.5C34.8 4.5 43.5 13.2 43.5 24C43.5 34.8 34.8 43.5 24 43.5Z" fill="url(#paint0_linear)"/><path d="M24 10.2C22.5 10.2 21 11.4 21 13.2C21 15 22.2 16.2 24 16.2C25.8 16.2 27 15 27 13.2C27 11.4 25.5 10.2 24 10.2ZM24 20.4C22.8 20.4 21.9 21.3 21.9 22.5V35.7C21.9 36.9 22.8 37.8 24 37.8C25.2 37.8 26.1 36.9 26.1 35.7V22.5C26.1 21.3 25.2 20.4 24 20.4Z" fill="url(#paint1_linear)"/><defs><linearGradient id="paint0_linear" x1="38.6667" y1="6.66666" x2="8" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="#27AD95"/><stop offset="1" stop-color="#2782AD"/></linearGradient><linearGradient id="paint1_linear" x1="38.6667" y1="6.66666" x2="8" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="#27AD95"/><stop offset="1" stop-color="#2782AD"/></linearGradient></defs></svg>
<h2>'. esc_html__( 'Preview is not available', 'wpml-translation-management' ) .'</h2>
<p>'. sprintf(esc_html__( 'Click %sEdit Translation%s in the toolbar above to review your translation in the editor.', 'wpml-translation-management' ), '<strong>', '</strong>') .'</p>
</div>
</div>
';
return null;
}
public function getData( $jobId, $post ) {
$job = Jobs::get( $jobId );
$editUrl = \add_query_arg( [ 'preview' => 1 ], Jobs::getEditUrl( Jobs::getCurrentUrl(), $jobId ) );
return [
'name' => 'reviewTranslation',
'data' => [
'jobEditUrl' => $editUrl,
'nextJobUrl' => NextTranslationLink::get( $job ),
'jobId' => (int) $jobId,
'postId' => $post->ID,
'isPublished' => Relation::propEq( 'post_status', 'publish', $post ) ? 1 : 0,
'needsReview' => ReviewStatus::doesJobNeedReview( $job ),
'completedInATE' => $this->isCompletedInATE( $_GET ),
'needsUpdate' => Relation::propEq( 'review_status', ReviewStatus::EDITING, $job ),
'previousTranslation' => Sanitize::stringProp( 'previousTranslation', $_GET ),
'backUrl' => Obj::prop( 'returnUrl', $_GET ),
'endpoints' => [
'accept' => AcceptTranslation::class,
'update' => UpdateTranslation::class
],
]
];
}
public function isCompletedInATE( $params ) {
$completedInATE = pipe(
Obj::prop( 'complete_no_changes' ),
'strval',
Logic::cond( [
[ Relation::equals( '1' ), Fns::always( 'COMPLETED_WITHOUT_CHANGED' ) ],
[ Relation::equals( '0' ), Fns::always( 'COMPLETED' ) ],
[ Fns::always( true ), Fns::always( 'NOT_COMPLETED' ) ],
] )
);
return $completedInATE( $params );
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Element\API\Languages;
use WPML\Element\API\PostTranslations;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use WPML\Setup\Option;
use WPML\TM\API\Jobs;
use function WPML\FP\partial;
use function WPML\FP\pipe;
class StatusIcons implements \IWPML_Backend_Action {
public function add_hooks() {
$ifNeedsReview = function ( $fn ) {
$getJob = Fns::converge( Jobs::getTridJob(), [
Obj::prop( 'trid' ),
Obj::prop( 'languageCode' )
] );
$doesNeedReview = pipe(
$getJob,
ReviewStatus::doesJobNeedReview()
);
return Logic::ifElse( $doesNeedReview, $fn, Obj::prop( 'default' ) );
};
Hooks::onFilter( 'wpml_css_class_to_translation', PHP_INT_MAX , 5 )
->then( Hooks::getArgs( [ 0 => 'default', 2 => 'languageCode', 3 => 'trid', 4 => 'status' ] ) )
->then( $ifNeedsReview( Fns::always( 'otgs-ico-needs-review' ) ) );
Hooks::onFilter( 'wpml_text_to_translation', PHP_INT_MAX, 6 )
->then( Hooks::getArgs( [ 0 => 'default', 2 => 'languageCode', 3 => 'trid', 5 => 'status' ] ) )
->then( $ifNeedsReview ( self::getReviewTitle( 'languageCode' ) ) );
Hooks::onFilter( 'wpml_link_to_translation', PHP_INT_MAX, 6 )
->then( Hooks::getArgs( [ 0 => 'default', 1 => 'postId', 2 => 'langCode', 3 => 'trid', 5 => 'status' ] ) )
->then( $this->setLink() );
}
public static function getReviewTitle( $langProp ) {
return pipe(
self::getLanguageName( $langProp ),
Fns::unary( partial( 'sprintf', __( 'Review %s language', 'wpml-translation-management' ) ) )
);
}
public static function getEditTitle( $langProp ) {
return pipe(
self::getLanguageName( $langProp ),
Fns::unary( partial( 'sprintf', __( 'Edit %s translation', 'wpml-translation-management' ) ) )
);
}
private static function getLanguageName( $langProp ) {
return Fns::memorize( pipe(
Obj::prop( $langProp ),
Languages::getLanguageDetails(),
Obj::prop( 'display_name' )
) );
}
private function setLink() {
return function ( $data ) {
$isInProgress = pipe(
Obj::prop( 'status' ),
Lst::includes( Fns::__, [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS, ICL_TM_ATE_NEEDS_RETRY ] )
);
$isInProgressOrCompleted = Logic::anyPass( [ $isInProgress, Relation::propEq( 'status', ICL_TM_COMPLETE ) ] );
$getTranslations = Fns::memorize( PostTranslations::get() );
$getTranslation = Fns::converge( Obj::prop(), [
Obj::prop( 'langCode' ),
pipe( Obj::prop( 'postId' ), $getTranslations )
] );
$getJob = Fns::converge( Jobs::getPostJob(), [
Obj::prop( 'postId' ),
Fns::always( 'post' ),
Obj::prop( 'langCode' )
] );
$doesNeedsReview = pipe( Obj::prop( 'job' ), ReviewStatus::doesJobNeedReview() );
$getPreviewLink = Fns::converge( PreviewLink::get(), [
Obj::path( [ 'translation', 'element_id' ] ),
Obj::path( [ 'job', 'job_id' ] )
] );
$disableInProgressIconOfAutomaticJob = Logic::ifElse(
Logic::both( $isInProgress, Obj::path( [ 'job', 'automatic' ] ) ),
Fns::always( 0 ), // no link at all
Obj::prop( 'default' )
);
return Maybe::of( $data )
->filter( $isInProgressOrCompleted )
->map( Obj::addProp( 'translation', $getTranslation ) )
->filter( Obj::prop( 'translation' ) )
->reject( Obj::path( [ 'translation', 'original' ] ) )
->map( Obj::addProp( 'job', $getJob ) )
->filter( Obj::prop( 'job' ) )
->map( Logic::ifElse( $doesNeedsReview, $getPreviewLink, $disableInProgressIconOfAutomaticJob ) )
->getOrElse( Obj::prop( 'default', $data ) );
};
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace WPML\TM\ATE\Review;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Obj;
use WPML\TM\API\ATE;
use WPML\TM\API\Jobs;
use function WPML\Container\make;
use function WPML\FP\partial;
use function WPML\FP\pipe;
class UpdateTranslation implements IHandler {
public function run( Collection $data ) {
$jobId = $data->get( 'jobId' );
$postId = $data->get( 'postId' );
$completedInATE = $data->get( 'completedInATE' );
if ( $completedInATE === 'COMPLETED_WITHOUT_CHANGED' ) {
return $this->completeWithoutChanges( $jobId );
}
$ateAPI = make( ATE::class );
$applyTranslation = pipe(
partial( [ $ateAPI, 'applyTranslation' ], $jobId, $postId ),
Logic::ifElse(
Fns::identity(),
Fns::tap( function () use ( $jobId ) {
Jobs::setReviewStatus( $jobId, ReviewStatus::NEEDS_REVIEW );
} ),
Fns::identity()
)
);
$hasStatus = function ( $statuses ) {
return pipe( Obj::prop( 'status_id' ), Lst::includes( Fns::__, $statuses ) );
};
$shouldApplyXLIFF = $hasStatus( [
\WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERING,
\WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_TRANSLATED,
\WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_EDITED,
] );
$applyXLIFF = Logic::ifElse(
pipe( Obj::prop( 'translated_xliff' ), $applyTranslation ),
Fns::always( Either::of( 'applied' ) ),
Fns::always( Either::left( 'error' ) )
);
$isDelivered = $hasStatus( [ \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERED ] );
$isTranslating = $hasStatus( [ \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_TRANSLATING ] );
$userClickedCompleteInATE = Fns::always( $completedInATE === 'COMPLETED' );
$otherwise = Fns::always( true );
$handleATEResult = Logic::cond( [
[ $shouldApplyXLIFF, $applyXLIFF ],
[ $isDelivered, Fns::always( Either::of( 'applied-without-changes' ) ) ],
[ $userClickedCompleteInATE, Fns::always( Either::of( 'underway' ) ) ],
[ $isTranslating, Fns::always( Either::of( 'in-progress' ) ) ],
[ Logic::isEmpty(), Fns::always( Either::left( 'error' ) ) ],
[ $otherwise, Fns::always( Either::of( 'in-progress' ) ) ]
] );
return Either::of( $jobId )
->map( [ $ateAPI, 'checkJobStatus' ] )
->chain( $handleATEResult );
}
private function completeWithoutChanges( $jobId ) {
$applyWithoutChanges = pipe(
Fns::tap( Jobs::setStatus( Fns::__, ICL_TM_COMPLETE ) ),
Fns::tap( Jobs::setReviewStatus( Fns::__, ReviewStatus::NEEDS_REVIEW ) )
);
return Either::of( $jobId )
->map( $applyWithoutChanges )
->map( Fns::always( 'applied-without-changes' ) );
}
}