File: /home/hanode/public_html/wp-content/plugins/sitepress-multilingual-cms/classes/editor/Editor.php
<?php
namespace WPML\TM\Editor;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\FP\Left;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Right;
use WPML\LIB\WP\User;
use WPML\Setup\Option as SetupOption;
use WPML\TM\API\Jobs;
use WPML\TM\ATE\Log\Entry;
use WPML\TM\ATE\Log\Storage;
use WPML\TM\ATE\Review\ReviewStatus;
use WPML\TM\ATE\Sync\Trigger;
use WPML\TM\Jobs\Manual;
use WPML\TM\Menu\TranslationQueue\CloneJobs;
use function WPML\Container\make;
use function WPML\FP\curryN;
use function WPML\FP\invoke;
use function WPML\FP\pipe;
class Editor {
const ATE_JOB_COULD_NOT_BE_CREATED = 101;
const ATE_EDITOR_URL_COULD_NOT_BE_FETCHED = 102;
const ATE_IS_NOT_ACTIVE = 103;
/** @var CloneJobs */
private $clone_jobs;
/** @var Manual */
private $manualJobs;
/**
* Editor constructor.
*
* @param CloneJobs $clone_jobs
* @param Manual $manualJobs
*/
public function __construct( CloneJobs $clone_jobs, Manual $manualJobs ) {
$this->clone_jobs = $clone_jobs;
$this->manualJobs = $manualJobs;
}
/**
* @param array $params
*
* @return array
*/
public function open( $params ) {
/**
* @param \WPML_Element_Translation_Job $jobObject
*
* @return bool
*/
$shouldOpenCTE = function ( $jobObject ) use ( $params ) {
if ( ! \WPML_TM_ATE_Status::is_enabled() ) {
return true;
}
$previousJob = $this->previousJob( $params, $jobObject );
/**
* If job object isn't translated and previous job exists this means that a new job is created and post needs update
* So we check if it should stick to WPML translation editor
*/
if ( ! $jobObject->get_basic_data_property( 'translated' ) && $previousJob ) {
return wpml_tm_load_old_jobs_editor()->shouldStickToWPMLEditor( $jobObject->get_id(), $previousJob );
}
return wpml_tm_load_old_jobs_editor()->editorForTranslationsPreviouslyCreatedUsingCTE() === \WPML_TM_Editors::WPML &&
wpml_tm_load_old_jobs_editor()->get_current_editor( $jobObject->get_id() ) === \WPML_TM_Editors::WPML;
};
/**
* It maybe needed when a job was translated via the Translation Proxy before and now, we want to open it in the editor.
*
* @param \WPML_Element_Translation_Job $jobObject
*
* @return \WPML_Element_Translation_Job
*/
$maybeUpdateTranslationServiceColumn = function ( $jobObject ) {
if ( $jobObject->get_translation_service() !== 'local' ) {
$jobObject->set_basic_data_property( 'translation_service', 'local' );
Jobs::setTranslationService( $jobObject->get_id(), 'local' );
}
return $jobObject;
};
$dataOfTranslationCreatedInNativeEditorViaConnection = $this->manualJobs->maybeGetDataIfTranslationCreatedInNativeEditorViaConnection( $params );
if ( $dataOfTranslationCreatedInNativeEditorViaConnection ) {
update_post_meta( $dataOfTranslationCreatedInNativeEditorViaConnection['originalPostId'], \WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, 'yes' );
return $this->displayWPNative( $dataOfTranslationCreatedInNativeEditorViaConnection );
}
return Either::of( $params )
->map( [ $this->manualJobs, 'createOrReuse' ] )
->filter( Logic::isTruthy() )
->filter( invoke( 'user_can_translate' )->with( User::getCurrent() ) )
->map( $maybeUpdateTranslationServiceColumn )
->map( Logic::ifElse( $shouldOpenCTE, $this->displayCTE(), $this->tryToDisplayATE( $params ) ) )
->getOrElse( [ 'editor' => \WPML_TM_Editors::NONE, 'jobObject' => null ] );
}
/**
* @param array $params
* @param \WPML_Element_Translation_Job $jobObject
*
* @return array
*/
private function tryToDisplayATE( $params = null, $jobObject = null ) {
$fn = curryN( 2, function ( $params, $jobObject ) {
$handleNotActiveATE = Logic::ifElse(
[ \WPML_TM_ATE_Status::class, 'is_active' ],
Either::of(),
pipe( $this->handleATEJobCreationError( $params, self::ATE_IS_NOT_ACTIVE ), Either::left() )
);
/**
* Create a new ATE job when somebody clicks the "pencil" icon to edit existing translation.
*
* @param \WPML_Element_Translation_Job $jobObject
*
* @return Either<\WPML_Element_Translation_Job>
*/
$cloneCompletedATEJob = function ( $jobObject ) use ( $params ) {
if ( $this->isValidATEJob( $jobObject ) && (int) $jobObject->get_status_value() === ICL_TM_COMPLETE ) {
$sentFrom = isset( $params['preview'] ) ? Jobs::SENT_FROM_REVIEW : Jobs::SENT_MANUALLY;
return $this->clone_jobs->cloneCompletedATEJob( $jobObject, $sentFrom )
->bimap( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED ), Fns::identity() );
}
return Either::of( $jobObject );
};
/**
* @param \WPML_Element_Translation_Job $jobObject
*
* @return callable|Left|Right
*/
$handleMissingATEJob = function ( $jobObject ) use ( $params ) {
// ATE editor is already set. All fine, we can proceed.
if ( $this->isValidATEJob( $jobObject ) ) {
return Either::of( $jobObject );
}
/**
* The new job has been created because either there was no translation at all or translation was "needs update".
* The ATE job could not be created inside WPML_TM_ATE_Jobs_Actions::added_translation_jobs ,and we have to return the error message.
*/
if ( ! $jobObject->get_basic_data_property( 'translated' ) && $this->previousJob( $params, $jobObject ) ) {
return Either::left( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED, $jobObject ) );
}
/**
* It creates a corresponding job in ATE for already existing WPML job in such situations:
* 1. Previously job was created in CTE, but a user selected the setting to translate existing CTE jobs in ATE
* 2. The job used to be handled by the Translation Proxy or the native WP editor
* 3. ATE job could not be created before and user clicked "Retry" button
* 4. Job was sent via basket and ATE job could not be created
*/
return $this->createATECounterpartForExistingWPMLJob( $params, $jobObject );
};
return Either::of( $jobObject )
->chain( $handleNotActiveATE )
->chain( $cloneCompletedATEJob )
->chain( $handleMissingATEJob )
->map( Fns::tap( pipe( invoke( 'get_id' ), Jobs::setStatus( Fns::__, ICL_TM_IN_PROGRESS ) ) ) )
->map( $this->openATE( $params ) )
->coalesce( Fns::identity(), Fns::identity() )
->get();
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @param \WPML_Element_Translation_Job $jobObject
*
* @return array
*/
private function displayCTE( $jobObject = null ) {
$fn = curryN( 1, function ( $jobObject ) {
wpml_tm_load_old_jobs_editor()->set( $jobObject->get_id(), \WPML_TM_Editors::WPML );
return [ 'editor' => \WPML_TM_Editors::WPML, 'jobObject' => $jobObject ];
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @param array $dataOfTranslationCreatedInNativeEditorViaConnection
*
* @return array
*/
private function displayWPNative( array $dataOfTranslationCreatedInNativeEditorViaConnection ) {
$url = 'post.php?' . http_build_query(
[
'lang' => $dataOfTranslationCreatedInNativeEditorViaConnection['targetLanguageCode'],
'action' => 'edit',
'post_type' => str_replace( 'post_', '', $dataOfTranslationCreatedInNativeEditorViaConnection['postType'] ),
'post' => $dataOfTranslationCreatedInNativeEditorViaConnection['translatedPostId']
]
);
return [ 'editor' => \WPML_TM_Editors::WP, 'jobObject' => null, 'url' => $url ];
}
/**
* @param \WPML_Element_Translation_Job $jobObject
*
* @return void
*/
private function maybeSetReviewStatus( $jobObject ) {
if ( Relation::propEq( 'review_status', ReviewStatus::NEEDS_REVIEW, $jobObject->to_array() ) ) {
Jobs::setReviewStatus( $jobObject->get_id(), SetupOption::shouldBeReviewed() ? ReviewStatus::EDITING : null );
}
}
/**
* It returns an url to place where a user should be redirected. The url contains a job id and error's code.
*
* @param array $params
* @param int $code
* @param \WPML_Element_Translation_Job $jobObject
*
* @return array
*/
private function handleATEJobCreationError( $params = null, $code = null, $jobObject = null ) {
$fn = curryN( 3, function ( $params, $code, $jobObject ) {
ATERetry::incrementCount( $jobObject->get_id() );
$retryCount = ATERetry::getCount( $jobObject->get_id() );
if ( $retryCount > 0 ) {
Storage::add( Entry::retryJob( $jobObject->get_id(),
[
'retry_count' => ATERetry::getCount( $jobObject->get_id() )
]
) );
}
return [
'editor' => \WPML_TM_Editors::ATE,
'url' => add_query_arg( [ 'ateJobCreationError' => $code, 'jobId' => $jobObject->get_id() ], $this->getReturnUrl( $params ) )
];
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* It asserts a job's editor.
*
* @param string $editor
* @param \WPML_Element_Translation_Job $jobObject
*
* @return bool
*/
private function isJobEditorEqualTo( $editor, $jobObject ) {
return $jobObject->get_basic_data_property( 'editor' ) === $editor;
}
/**
* It checks if we have a previous job in _icl_translate_job DB table and returns it if exists or returns false otherwise.
* It happens when none translation for a specific language has existed so far or when a translation has been "needs update".
*
* @param array $params
* @param \WPML_Element_Translation_Job $jobObject
*
* @return object|bool
*/
private function previousJob( $params, $jobObject ) {
/**
* If we get previous job ID passed in $params (like how it happens when updating translation from posts list).,
* then we can compare it with the job_id coming from $jobObject
*
* Otherwise, if we're getting same job_id inside $params and $jobObject (like how it happens when updating translation from translations queue).,
* then we get the previous job ID and compare it with the job_id coming from $jobObject, if they are not equal we return the previous job object, or we return false otherwise.
*
* @see wpmldev-541
*/
$jobObjectJobId = (int) $jobObject->get_id();
$jobIdInParams = (int) Obj::prop( 'job_id', $params );
if ( $jobObjectJobId !== $jobIdInParams ) {
return Jobs::get( $jobIdInParams );
}
$previousJob = Jobs::getPreviousJob( $jobIdInParams );
return $previousJob && (int) $previousJob->job_id !== $jobObjectJobId ? $previousJob : false;
}
/**
* @param array $params
* @param \WPML_Element_Translation_Job $jobObject
*
* @return callable|Left<array>|Right<\WPML_Element_Translation_Job>
*/
private function createATECounterpartForExistingWPMLJob( $params, $jobObject ) {
if ( $this->clone_jobs->cloneWPMLJob( $jobObject->get_id() ) ) {
ATERetry::reset( $jobObject->get_id() );
$jobObject->set_basic_data_property( 'editor', \WPML_TM_Editors::ATE );
return Either::of( $jobObject );
}
return Either::left( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED, $jobObject ) );
}
/**
* At this stage, we know that a corresponding job in ATE is created and we should open ATE editor.
* We are trying to do that.
*
* @param array $params
* @param \WPML_Element_Translation_Job $jobObject
*
* @return false|mixed
*/
private function openATE( $params = null, $jobObject = null ) {
$fn = curryN( 2, function ( $params, $jobObject ) {
$this->maybeSetReviewStatus( $jobObject );
$editor_url = apply_filters( 'wpml_tm_ate_jobs_editor_url', '', $jobObject->get_id(), $this->getReturnUrl( $params ) );
if ( $editor_url ) {
$response['editor'] = \WPML_TM_Editors::ATE;
$response['url'] = $editor_url;
$response['jobObject'] = $jobObject;
return $response;
}
return $this->handleATEJobCreationError( $params, self::ATE_EDITOR_URL_COULD_NOT_BE_FETCHED, $jobObject );
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @return string
*/
private function getReturnUrl( $params ) {
$return_url = '';
if ( array_key_exists( 'return_url', $params ) ) {
$return_url = filter_var( $params['return_url'], FILTER_SANITIZE_URL );
$return_url_parts = wp_parse_url( (string) $return_url );
$admin_url = get_admin_url();
$admin_url_parts = wp_parse_url( $admin_url );
if ( strpos( $return_url_parts['path'], $admin_url_parts['path'] ) === 0 ) {
$admin_url_parts['path'] = $return_url_parts['path'];
} else {
$admin_url_parts = $return_url_parts;
}
$admin_url_parts['query'] = $this->prepareQueryParameters(
Obj::propOr( '', 'query', $return_url_parts ),
Obj::prop( 'lang', $params )
);
$return_url = http_build_url( $admin_url_parts );
}
return $return_url;
}
private function prepareQueryParameters( $query, $returnLanguage ) {
$parameters = [];
parse_str( $query, $parameters );
unset( $parameters['ate_original_id'] );
unset( $parameters['back'] );
unset( $parameters['complete'] );
if ( $returnLanguage ) {
// We need the lang parameter to display the post list in the language which was used before ATE.
$parameters['lang'] = $returnLanguage;
}
return http_build_query( $parameters );
}
/**
* @param \WPML_Element_Translation_Job $jobObject
*
* @return bool
*/
private function isValidATEJob( \WPML_Element_Translation_Job $jobObject ) {
return $this->isJobEditorEqualTo( \WPML_TM_Editors::ATE, $jobObject ) &&
(int) $jobObject->get_basic_data_property( 'editor_job_id' ) > 0;
}
}