first commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\API\CacheStorage;
|
||||
|
||||
use WPML\FP\Obj;
|
||||
|
||||
class StaticVariable implements Storage {
|
||||
/** @var array */
|
||||
private static $cache = [];
|
||||
|
||||
/** @var self */
|
||||
private static $instance;
|
||||
|
||||
public static function getInstance() {
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $key, $default = null ) {
|
||||
return Obj::propOr( $default, $key, self::$cache );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function save( $key, $value ) {
|
||||
self::$cache[ $key ] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function delete( $key ) {
|
||||
self::$cache = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\API\CacheStorage;
|
||||
|
||||
interface Storage {
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $key, $default = null );
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function save( $key, $value );
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function delete( $key );
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\API\CacheStorage;
|
||||
|
||||
use WPML\LIB\WP\Transient as WPTransient;
|
||||
|
||||
class Transient implements Storage {
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $key, $default = null ) {
|
||||
return WPTransient::getOr( $key, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function save( $key, $value ) {
|
||||
WPTransient::set( $key, $value, 3600 * 24 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*/
|
||||
public function delete( $key ) {
|
||||
WPTransient::delete( $key );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\API;
|
||||
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\LIB\WP\Transient;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\ATE\API\CacheStorage\Storage;
|
||||
use function WPML\FP\curryN;
|
||||
|
||||
class CachedATEAPI {
|
||||
|
||||
const CACHE_OPTION = 'wpml-tm-ate-api-cache';
|
||||
|
||||
/** @var \WPML_TM_ATE_API */
|
||||
private $ateAPI;
|
||||
|
||||
/** @var Storage */
|
||||
private $storage;
|
||||
|
||||
private $cachedFns = [ 'get_languages_supported_by_automatic_translations', 'get_language_details', 'get_language_mapping' ];
|
||||
|
||||
/**
|
||||
* @param \WPML_TM_ATE_API $ateAPI
|
||||
*/
|
||||
public function __construct( \WPML_TM_ATE_API $ateAPI, Storage $storage ) {
|
||||
$this->ateAPI = $ateAPI;
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
public function __call( $name, $args ) {
|
||||
return Lst::includes( $name, $this->cachedFns ) ? $this->callWithCache( $name, $args ) : call_user_func_array( [ $this->ateAPI, $name ], $args );
|
||||
}
|
||||
|
||||
private function callWithCache( $fnName, $args ) {
|
||||
$result = Obj::pathOr( null, [ $fnName, \serialize( $args ) ], $this->storage->get( self::CACHE_OPTION, [] ) );
|
||||
if ( ! $result ) {
|
||||
return call_user_func_array( [ $this->ateAPI, $fnName ], $args )->map( $this->cacheValue( $fnName, $args ) );
|
||||
}
|
||||
|
||||
return Maybe::of( $result );
|
||||
|
||||
}
|
||||
|
||||
public function cacheValue( $fnName = null, $args = null, $result = null ) {
|
||||
$fn = curryN( 3, function ( $fnName, $args, $result ) {
|
||||
$data = $this->storage->get( self::CACHE_OPTION, [] );
|
||||
$data[ $fnName ][ serialize( $args ) ] = $result;
|
||||
$this->storage->save( self::CACHE_OPTION, $data );
|
||||
|
||||
return $result;
|
||||
} );
|
||||
|
||||
return call_user_func_array( $fn, func_get_args() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\ClonedSites;
|
||||
|
||||
use WPML\UIPage;
|
||||
|
||||
class ApiCommunication {
|
||||
|
||||
const SITE_CLONED_ERROR = 426;
|
||||
|
||||
const SITE_MOVED_OR_COPIED_MESSAGE = "WPML has detected a change in your site's URL. To continue translating your site, go to your <a href='%s'>WordPress Dashboard</a> and tell WPML if your site has been <a href='%s'>moved or copied</a>.";
|
||||
const SITE_MOVED_OR_COPIED_DOCS_URL = 'https://wpml.org/documentation/translating-your-contents/advanced-translation-editor/using-advanced-translation-editor-when-you-move-or-use-a-copy-of-your-site/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm';
|
||||
|
||||
/**
|
||||
* @var Lock
|
||||
*/
|
||||
private $lock;
|
||||
|
||||
/**
|
||||
* @param Lock $lock
|
||||
*/
|
||||
public function __construct( Lock $lock ) {
|
||||
$this->lock = $lock;
|
||||
}
|
||||
|
||||
public function handleClonedSiteError( $response ) {
|
||||
if ( self::SITE_CLONED_ERROR === $response['response']['code'] ) {
|
||||
$parsedResponse = json_decode( $response['body'], true );
|
||||
if ( isset( $parsedResponse['errors'] ) ) {
|
||||
$this->handleClonedDetection( $parsedResponse['errors'] );
|
||||
}
|
||||
return new \WP_Error( self::SITE_CLONED_ERROR, 'Site Moved or Copied - Action Required' );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function checkCloneSiteLock() {
|
||||
if ( Lock::isLocked() ) {
|
||||
$errorMessage = sprintf( __( self::SITE_MOVED_OR_COPIED_MESSAGE, 'sitepress-multilingual-cms' ),
|
||||
UIPage::getTMDashboard(),
|
||||
self::SITE_MOVED_OR_COPIED_DOCS_URL
|
||||
);
|
||||
|
||||
return new \WP_Error( self::SITE_CLONED_ERROR, $errorMessage );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function unlockClonedSite() {
|
||||
return $this->lock->unlock();
|
||||
}
|
||||
|
||||
private function handleClonedDetection( $error_data ) {
|
||||
$error = array_pop( $error_data );
|
||||
$this->lock->lock( $error );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\ClonedSites;
|
||||
|
||||
class FingerprintGenerator {
|
||||
const SITE_FINGERPRINT_HEADER = 'SITE-FINGERPRINT';
|
||||
const NEW_SITE_FINGERPRINT_HEADER = 'NEW-SITE-FINGERPRINT';
|
||||
|
||||
public function getSiteFingerprint() {
|
||||
$siteFingerprint = [
|
||||
'wp_url' => $this->getSiteUrl(),
|
||||
];
|
||||
|
||||
return json_encode( $siteFingerprint );
|
||||
}
|
||||
|
||||
private function getSiteUrl() {
|
||||
|
||||
$siteUrl = defined( 'ATE_CLONED_SITE_URL' ) ? ATE_CLONED_SITE_URL : site_url();
|
||||
|
||||
return $this->getDefaultSiteUrl( $siteUrl );
|
||||
}
|
||||
|
||||
private function getDefaultSiteUrl( $siteUrl ) {
|
||||
global $sitepress;
|
||||
$filteredSiteUrl = false;
|
||||
if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN === (int) $sitepress->get_setting( 'language_negotiation_type' ) ) {
|
||||
/* @var WPML_URL_Converter $wpml_url_converter */
|
||||
global $wpml_url_converter;
|
||||
$site_url_default_lang = $wpml_url_converter->get_default_site_url();
|
||||
$filteredSiteUrl = filter_var( $site_url_default_lang, FILTER_SANITIZE_URL );
|
||||
}
|
||||
|
||||
$defaultSiteUrl = $filteredSiteUrl ? $filteredSiteUrl : $siteUrl;
|
||||
$defaultSiteUrl = defined( 'ATE_CLONED_DEFAULT_SITE_URL' ) ? ATE_CLONED_DEFAULT_SITE_URL : $defaultSiteUrl;
|
||||
|
||||
return $defaultSiteUrl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace WPML\TM\ATE\ClonedSites;
|
||||
|
||||
class Lock {
|
||||
const CLONED_SITE_OPTION = 'otgs_wpml_tm_ate_cloned_site_lock';
|
||||
|
||||
public function lock( $lockData ) {
|
||||
if ( $this->isLockDataPresent( $lockData ) ) {
|
||||
update_option(
|
||||
self::CLONED_SITE_OPTION,
|
||||
[
|
||||
'stored_fingerprint' => $lockData['stored_fingerprint'],
|
||||
'received_fingerprint' => $lockData['received_fingerprint'],
|
||||
'fingerprint_confirmed' => $lockData['fingerprint_confirmed'],
|
||||
],
|
||||
'no'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function isLockDataPresent( $lockData ) {
|
||||
return isset( $lockData['stored_fingerprint'] )
|
||||
&& isset( $lockData['received_fingerprint'] )
|
||||
&& isset( $lockData['fingerprint_confirmed'] );
|
||||
}
|
||||
|
||||
public function unlock() {
|
||||
delete_option( self::CLONED_SITE_OPTION );
|
||||
}
|
||||
|
||||
public static function isLocked() {
|
||||
return (bool) get_option( self::CLONED_SITE_OPTION, false ) && \WPML_TM_ATE_Status::is_enabled();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\ClonedSites;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
|
||||
class Report {
|
||||
const REPORT_TYPE_COPY = 'copy';
|
||||
const REPORT_TYPE_MOVE = 'move';
|
||||
|
||||
/**
|
||||
* @var \WPML_TM_AMS_API
|
||||
*/
|
||||
private $apiClient;
|
||||
|
||||
/**
|
||||
* @var ApiCommunication
|
||||
*/
|
||||
private $apiCommunicationHandler;
|
||||
|
||||
/**
|
||||
* @var \WPML_TM_ATE_Job_Repository
|
||||
*/
|
||||
private $ateJobsRepository;
|
||||
|
||||
/**
|
||||
* Update jobs synchronisation
|
||||
*
|
||||
* @var \WPML_TP_Sync_Update_Job
|
||||
*/
|
||||
private $updateJobs;
|
||||
|
||||
/**
|
||||
* @var \WPML_Translation_Job_Factory
|
||||
*/
|
||||
private $translationJobFactory;
|
||||
|
||||
/**
|
||||
* @param \WPML_TM_AMS_API $apiClient
|
||||
* @param ApiCommunication $apiCommunicationHandler
|
||||
* @param \WPML_TM_ATE_Job_Repository $ateJobsRepository
|
||||
* @param \WPML_Translation_Job_Factory $translationJobFactory
|
||||
*/
|
||||
public function __construct(
|
||||
\WPML_TM_AMS_API $apiClient,
|
||||
ApiCommunication $apiCommunicationHandler,
|
||||
\WPML_TM_ATE_Job_Repository $ateJobsRepository,
|
||||
\WPML_TP_Sync_Update_Job $updateJobs,
|
||||
\WPML_Translation_Job_Factory $translationJobFactory
|
||||
) {
|
||||
$this->apiClient = $apiClient;
|
||||
$this->apiCommunicationHandler = $apiCommunicationHandler;
|
||||
$this->ateJobsRepository = $ateJobsRepository;
|
||||
$this->updateJobs = $updateJobs;
|
||||
$this->translationJobFactory = $translationJobFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $reportType
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function report( $reportType ) {
|
||||
$reportCallback = \wpml_collect( [
|
||||
self::REPORT_TYPE_COPY => $this->reportCopiedSite(),
|
||||
self::REPORT_TYPE_MOVE => $this->reportMovedSite(),
|
||||
] )->get( $reportType, Fns::always( Fns::always( false ) ) );
|
||||
|
||||
$reportResult = $reportCallback();
|
||||
|
||||
if ($reportResult) {
|
||||
do_action( 'wpml_tm_ate_synchronize_translators' );
|
||||
}
|
||||
|
||||
return $reportResult;
|
||||
}
|
||||
|
||||
private function reportCopiedSite() {
|
||||
return function () {
|
||||
$reportResult = $this->apiClient->reportCopiedSite();
|
||||
$isConfirmed = $this->apiClient->processCopyReportConfirmation( $reportResult );
|
||||
|
||||
if ( $isConfirmed ) {
|
||||
$jobsInProgress = $this->ateJobsRepository->get_jobs_to_sync();
|
||||
/** @var \WPML_TM_Post_Job_Entity $jobInProgress */
|
||||
foreach ( $jobsInProgress as $jobInProgress ) {
|
||||
$jobInProgress->set_status( ICL_TM_NOT_TRANSLATED );
|
||||
$this->updateJobs->update_state( $jobInProgress );
|
||||
$this->translationJobFactory->delete_job_data( $jobInProgress->get_translate_job_id() );
|
||||
}
|
||||
$this->apiCommunicationHandler->unlockClonedSite();
|
||||
}
|
||||
|
||||
return $isConfirmed;
|
||||
};
|
||||
}
|
||||
|
||||
private function reportMovedSite() {
|
||||
return function () {
|
||||
$reportResult = $this->apiClient->reportMovedSite();
|
||||
$movedSuccessfully = $this->apiClient->processMoveReport( $reportResult );
|
||||
|
||||
if ( $movedSuccessfully ) {
|
||||
$this->apiCommunicationHandler->unlockClonedSite();
|
||||
}
|
||||
|
||||
return $movedSuccessfully;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\ClonedSites;
|
||||
|
||||
class ReportAjax implements \IWPML_Backend_Action, \IWPML_DIC_Action {
|
||||
|
||||
/**
|
||||
* @var Report
|
||||
*/
|
||||
private $reportHandler;
|
||||
|
||||
/**
|
||||
* @param Report $reportHandler
|
||||
*/
|
||||
public function __construct( Report $reportHandler ) {
|
||||
$this->reportHandler = $reportHandler;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wp_ajax_wpml_save_cloned_sites_report_type', [ $this, 'reportSiteCloned' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $reportType
|
||||
*/
|
||||
public function handleInstallerSiteUrlDetection($reportType) {
|
||||
$this->reportHandler->report( $reportType );
|
||||
}
|
||||
|
||||
public function reportSiteCloned() {
|
||||
if ( $this->isValidRequest() && $this->reportHandler->report( $_POST['reportType'] ) ) {
|
||||
wp_send_json_success();
|
||||
} else {
|
||||
wp_send_json_error();
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidRequest() {
|
||||
return array_key_exists( 'nonce', $_POST )
|
||||
&& array_key_exists( 'reportType', $_POST )
|
||||
&& wp_verify_nonce( $_POST['nonce'], 'icl_doc_translation_method_cloned_nonce' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\API;
|
||||
|
||||
|
||||
class ErrorMessages {
|
||||
|
||||
|
||||
public static function serverUnavailable( $uuid ) {
|
||||
return [
|
||||
'header' => self::serverUnavailableHeader(),
|
||||
'description' => self::invalidResponseDescription( $uuid ),
|
||||
];
|
||||
}
|
||||
|
||||
public static function offline( $uuid ) {
|
||||
$description = _x( 'WPML needs an Internet connection to translate your site’s content. It seems that your server is not allowing external connections, or your network is temporarily down.', 'part1', 'wpml-translation-management' );
|
||||
$description .= _x( 'If this is the first time you’re seeing this message, please wait a minute and reload the page. If the problem persists, contact %1$s for help and mention that your website ID is %2$s.', 'part2', 'wpml-translation-management' );
|
||||
|
||||
return [
|
||||
'header' => __( 'Cannot Connect to the Internet', 'wpml-translation-management' ),
|
||||
'description' => sprintf( $description, self::getSupportLink(), $uuid ),
|
||||
];
|
||||
}
|
||||
|
||||
public static function invalidResponse( $uuid ) {
|
||||
return [
|
||||
'header' => __( 'WPML’s Advanced Translation Editor is not working', 'wpml-translation-management' ),
|
||||
'description' => self::invalidResponseDescription( $uuid ),
|
||||
];
|
||||
}
|
||||
|
||||
public static function respondedWithError() {
|
||||
return __( "WPML's Advanced Translation Editor responded with an error", 'wpml-translation-management' );
|
||||
}
|
||||
|
||||
public static function serverUnavailableHeader() {
|
||||
return __( 'WPML’s Advanced Translation Editor is not responding', 'wpml-translation-management' );
|
||||
}
|
||||
|
||||
public static function invalidResponseDescription( $uuid ) {
|
||||
$description = _x( 'WPML cannot connect to the translation editor. If this is the first time you’re seeing this message, please wait a minute and reload the page.', 'part1', 'wpml-translation-management' );
|
||||
$description .= _x( 'If the problem persists, contact %1$s for help and mention that your website ID is %2$s.', 'part2', 'wpml-translation-management' );
|
||||
|
||||
return sprintf( $description, self::getSupportLink(), $uuid );
|
||||
}
|
||||
|
||||
public static function getSupportLink() {
|
||||
return '<a href="https://wpml.org/forums/forum/english-support/" target="_blank" rel="noreferrer">'
|
||||
. __( 'WPML support', 'wpml-translation-management' ) . '</a>';
|
||||
}
|
||||
|
||||
public static function bodyWithoutRequiredFields() {
|
||||
return __( 'The body does not contain the required fields', 'wpml-translation-management' );
|
||||
}
|
||||
|
||||
public static function uuidAlreadyExists() {
|
||||
return __( 'UUID already exists', 'wpml-translation-management' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,807 @@
|
||||
<?php
|
||||
|
||||
use WPML\TM\ATE\ClonedSites\FingerprintGenerator;
|
||||
use WPML\TM\ATE\Log\Entry;
|
||||
use WPML\TM\ATE\Log\Storage;
|
||||
use WPML\TM\ATE\Log\EventsTypes;
|
||||
use WPML\TM\ATE\ClonedSites\ApiCommunication as ClonedSitesHandler;
|
||||
use WPML\FP\Json;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\FP\Either;
|
||||
use WPML\TM\ATE\API\ErrorMessages;
|
||||
use WPML\FP\Fns;
|
||||
use function WPML\FP\pipe;
|
||||
use WPML\FP\Logic;
|
||||
use function WPML\FP\invoke;
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_AMS_API {
|
||||
|
||||
const HTTP_ERROR_CODE_400 = 400;
|
||||
|
||||
private $auth;
|
||||
private $endpoints;
|
||||
private $wp_http;
|
||||
|
||||
/**
|
||||
* @var ClonedSitesHandler
|
||||
*/
|
||||
private $clonedSitesHandler;
|
||||
|
||||
/**
|
||||
* @var FingerprintGenerator
|
||||
*/
|
||||
private $fingerprintGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_API constructor.
|
||||
*
|
||||
* @param WP_Http $wp_http
|
||||
* @param WPML_TM_ATE_Authentication $auth
|
||||
* @param WPML_TM_ATE_AMS_Endpoints $endpoints
|
||||
* @param ClonedSitesHandler $clonedSitesHandler
|
||||
* @param FingerprintGenerator $fingerprintGenerator
|
||||
*/
|
||||
public function __construct(
|
||||
WP_Http $wp_http,
|
||||
WPML_TM_ATE_Authentication $auth,
|
||||
WPML_TM_ATE_AMS_Endpoints $endpoints,
|
||||
ClonedSitesHandler $clonedSitesHandler,
|
||||
FingerprintGenerator $fingerprintGenerator
|
||||
) {
|
||||
$this->wp_http = $wp_http;
|
||||
$this->auth = $auth;
|
||||
$this->endpoints = $endpoints;
|
||||
$this->clonedSitesHandler = $clonedSitesHandler;
|
||||
$this->fingerprintGenerator = $fingerprintGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $translator_email
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function enable_subscription( $translator_email ) {
|
||||
$result = null;
|
||||
|
||||
$verb = 'PUT';
|
||||
$url = $this->endpoints->get_enable_subscription();
|
||||
$url = str_replace( '{translator_email}', base64_encode( $translator_email ), $url );
|
||||
|
||||
$response = $this->signed_request( $verb, $url );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = json_decode( $response['body'], true );
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $translator_email
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function is_subscription_activated( $translator_email ) {
|
||||
$result = null;
|
||||
|
||||
$url = $this->endpoints->get_subscription_status();
|
||||
|
||||
$url = str_replace( '{translator_email}', base64_encode( $translator_email ), $url );
|
||||
$url = str_replace( '{WEBSITE_UUID}', $this->auth->get_site_id(), $url );
|
||||
|
||||
$response = $this->signed_request( 'GET', $url );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = json_decode( $response['body'], true );
|
||||
$result = $result['subscription'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
*
|
||||
* @throws \InvalidArgumentException Exception.
|
||||
*/
|
||||
public function get_status() {
|
||||
$result = null;
|
||||
|
||||
$registration_data = $this->get_registration_data();
|
||||
$shared = array_key_exists( 'shared', $registration_data ) ? $registration_data['shared'] : null;
|
||||
|
||||
if ( $shared ) {
|
||||
$url = $this->endpoints->get_ams_status();
|
||||
$url = str_replace( '{SHARED_KEY}', $shared, $url );
|
||||
|
||||
$response = $this->request( 'GET', $url );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$registration_data = $this->get_registration_data();
|
||||
if ( isset( $response_body['activated'] ) && (bool) $response_body['activated'] ) {
|
||||
$registration_data['status'] = WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE;
|
||||
$this->set_registration_data( $registration_data );
|
||||
}
|
||||
$result = $response_body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to register a manager and, at the same time, create a website in AMS.
|
||||
* This is called only when registering the site with AMS.
|
||||
* To register new managers or translators `\WPML_TM_ATE_AMS_Endpoints::get_ams_synchronize_managers`
|
||||
* and `\WPML_TM_ATE_AMS_Endpoints::get_ams_synchronize_translators` will be used.
|
||||
*
|
||||
* @param WP_User $manager The WP_User instance of the manager.
|
||||
* @param WP_User[] $translators An array of WP_User instances representing the current translators.
|
||||
* @param WP_User[] $managers An array of WP_User instances representing the current managers.
|
||||
*
|
||||
* @return \WPML\FP\Either
|
||||
*/
|
||||
public function register_manager( WP_User $manager, array $translators, array $managers ) {
|
||||
$uuid = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE, false );
|
||||
|
||||
$makeRequest = $this->makeRegistrationRequest( $manager, $translators, $managers );
|
||||
|
||||
$logErrorResponse = $this->logErrorResponse();
|
||||
|
||||
$getErrors = Fns::memorize( function ( $response ) {
|
||||
return $this->get_errors( $response, false );
|
||||
} );
|
||||
|
||||
$handleErrorResponse = $this->handleErrorResponse( $logErrorResponse, $getErrors );
|
||||
$handleGeneralError = $handleErrorResponse( Fns::identity(), pipe( [ ErrorMessages::class, 'invalidResponse' ], Either::left() ) );
|
||||
$handle409Error = $this->handle409Error( $handleErrorResponse, $makeRequest );
|
||||
|
||||
return Either::of( $uuid )
|
||||
->chain( $makeRequest )
|
||||
->chain( $handle409Error )
|
||||
->chain( $handleGeneralError )
|
||||
->chain( $this->handleInvalidBodyError() )
|
||||
->map( $this->saveRegistrationData( $manager ) );
|
||||
}
|
||||
|
||||
private function makeRegistrationRequest( $manager, $translators, $managers ) {
|
||||
$buildParams = function ( $uuid ) use ( $manager, $translators, $managers ) {
|
||||
$manager_data = $this->get_user_data( $manager, true );
|
||||
$translators_data = $this->get_users_data( $translators );
|
||||
$managers_data = $this->get_users_data( $managers, true );
|
||||
$sitekey = function_exists( 'OTGS_Installer' ) ? OTGS_Installer()->get_site_key( 'wpml' ) : null;
|
||||
|
||||
$params = $manager_data;
|
||||
$params['website_url'] = get_site_url();
|
||||
$params['website_uuid'] = $uuid;
|
||||
|
||||
$params['translators'] = $translators_data;
|
||||
$params['translation_managers'] = $managers_data;
|
||||
if ( $sitekey ) {
|
||||
$params['site_key'] = $sitekey;
|
||||
}
|
||||
|
||||
return $params;
|
||||
};
|
||||
|
||||
$handleUnavailableATEError = function ( $response, $uuid ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$this->log_api_error(
|
||||
ErrorMessages::serverUnavailableHeader(),
|
||||
[ 'responseError' => $response->get_error_message(), 'website_uuid' => $uuid ]
|
||||
);
|
||||
$msg = $this->ping_healthy_wpml_endpoint() ? ErrorMessages::serverUnavailable( $uuid ) : ErrorMessages::offline( $uuid );
|
||||
|
||||
return Either::left( $msg );
|
||||
}
|
||||
|
||||
return Either::of( [ $response, $uuid ] );
|
||||
};
|
||||
|
||||
return function ( $uuid ) use ( $buildParams, $handleUnavailableATEError ) {
|
||||
$response = $this->request( 'POST', $this->endpoints->get_ams_register_client(), $buildParams( $uuid ) );
|
||||
|
||||
return $handleUnavailableATEError( $response, $uuid );
|
||||
};
|
||||
}
|
||||
|
||||
private function logErrorResponse() {
|
||||
return function ( $error, $uuid ) {
|
||||
$this->log_api_error(
|
||||
ErrorMessages::respondedWithError(),
|
||||
[
|
||||
'responseError' => $error->get_error_code() === 409 ? ErrorMessages::uuidAlreadyExists() : $error->get_error_message(),
|
||||
'website_uuid' => $uuid
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
private function handleErrorResponse($logErrorResponse, $getErrors) {
|
||||
return \WPML\FP\curryN( 3, function ( $shouldHandleError, $errorHandler, $data ) use ( $logErrorResponse, $getErrors ) {
|
||||
list( $response, $uuid ) = $data;
|
||||
|
||||
$error = $getErrors( $response );
|
||||
|
||||
if ( $shouldHandleError( $error ) ) {
|
||||
$logErrorResponse( $error, $uuid );
|
||||
|
||||
return $errorHandler( $uuid );
|
||||
}
|
||||
|
||||
return Either::of( $data );
|
||||
} );
|
||||
}
|
||||
|
||||
private function handle409Error($handleErrorResponse, $makeRequest) {
|
||||
$is409Error = Logic::both( Fns::identity(), pipe( invoke( 'get_error_code' ), Relation::equals( 409 ) ) );
|
||||
|
||||
return $handleErrorResponse($is409Error, function ( $uuid ) use ( $makeRequest ) {
|
||||
$uuid = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE, true );
|
||||
|
||||
return $makeRequest( $uuid );
|
||||
} );
|
||||
}
|
||||
|
||||
private function handleInvalidBodyError( ) {
|
||||
return function ( $data ) {
|
||||
list( $response, $uuid ) = $data;
|
||||
|
||||
if ( ! $this->response_has_keys( $response ) ) {
|
||||
$this->log_api_error(
|
||||
ErrorMessages::respondedWithError(),
|
||||
[ 'responseError' => ErrorMessages::bodyWithoutRequiredFields(), 'response' => json_encode( $response ), 'website_uuid' => $uuid ]
|
||||
);
|
||||
|
||||
return Either::left( ErrorMessages::invalidResponse( $uuid ) );
|
||||
}
|
||||
|
||||
return Either::of( $data );
|
||||
};
|
||||
}
|
||||
|
||||
private function saveRegistrationData($manager) {
|
||||
return function ( $data ) use ( $manager ) {
|
||||
list( $response) = $data;
|
||||
|
||||
$registration_data = $this->get_registration_data();
|
||||
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
$registration_data['user_id'] = $manager->ID;
|
||||
$registration_data['secret'] = $response_body['secret_key'];
|
||||
$registration_data['shared'] = $response_body['shared_key'];
|
||||
$registration_data['status'] = WPML_TM_ATE_Authentication::AMS_STATUS_ENABLED;
|
||||
|
||||
return $this->set_registration_data( $registration_data );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the data required by AMS to register a user.
|
||||
*
|
||||
* @param WP_User $wp_user The user from which data should be extracted.
|
||||
* @param bool $with_name_details True if name details should be included.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_user_data( WP_User $wp_user, $with_name_details = false ) {
|
||||
$data = array();
|
||||
|
||||
$data['email'] = $wp_user->user_email;
|
||||
|
||||
if ( $with_name_details ) {
|
||||
$data['display_name'] = $wp_user->display_name;
|
||||
$data['first_name'] = $wp_user->first_name;
|
||||
$data['last_name'] = $wp_user->last_name;
|
||||
} else {
|
||||
$data['name'] = $wp_user->display_name;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function prepareClonedSiteArguments( $method ) {
|
||||
$headers = [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
FingerprintGenerator::NEW_SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(),
|
||||
];
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'headers' => $headers,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function reportCopiedSite() {
|
||||
return $this->processReport(
|
||||
$this->endpoints->get_ams_site_copy(),
|
||||
'POST'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function reportMovedSite() {
|
||||
return $this->processReport(
|
||||
$this->endpoints->get_ams_site_move(),
|
||||
'PUT'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response Response from reportMovedSite()
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function processMoveReport( $response ) {
|
||||
if ( ! $this->response_has_body( $response ) ) {
|
||||
return new WP_Error( 'auth_error', 'Unable to report site moved.' );
|
||||
}
|
||||
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
if ( isset( $response_body['moved_successfully'] ) && (bool) $response_body['moved_successfully'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'auth_error', 'Unable to report site moved.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response_body body from reportMovedSite() response.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function storeAuthData( $response_body ) {
|
||||
$setRegistrationDataResult = $this->updateRegistrationData( $response_body );
|
||||
$setUuidResult = $this->updateSiteUuId( $response_body );
|
||||
|
||||
return $setRegistrationDataResult && $setUuidResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response_body body from reportMovedSite() response.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateRegistrationData( $response_body ) {
|
||||
$registration_data = $this->get_registration_data();
|
||||
|
||||
$registration_data['secret'] = $response_body['new_secret_key'];
|
||||
$registration_data['shared'] = $response_body['new_shared_key'];
|
||||
|
||||
return $this->set_registration_data( $registration_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response_body body from reportMovedSite() response.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function updateSiteUuId( $response_body ) {
|
||||
$this->override_site_id( $response_body['new_website_uuid'] );
|
||||
|
||||
return update_option(
|
||||
WPML_Site_ID::SITE_ID_KEY . ':ate',
|
||||
$response_body['new_website_uuid'],
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private function sendSiteReportConfirmation() {
|
||||
$url = $this->endpoints->get_ams_site_confirm();
|
||||
$method = 'POST';
|
||||
|
||||
$args = $this->prepareClonedSiteArguments( $method );
|
||||
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$registration_data = $this->get_registration_data();
|
||||
$query['new_shared_key'] = $registration_data['shared'];
|
||||
$query['token'] = uuid_v5( wp_generate_uuid4(), $url );
|
||||
$query['new_website_uuid'] = $this->auth->get_site_id();
|
||||
$url_parts['query'] = http_build_query( $query );
|
||||
|
||||
$url = http_build_url( $url_parts );
|
||||
|
||||
$signed_url = $this->auth->signUrl( $method, $url );
|
||||
|
||||
$response = $this->wp_http->request( $signed_url, $args );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
return (bool) $response_body['confirmed'];
|
||||
}
|
||||
|
||||
return new WP_Error( 'auth_error', 'Unable confirm site copied.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function processReport( $url, $method ) {
|
||||
$args = $this->prepareClonedSiteArguments( $method );
|
||||
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$registration_data = $this->get_registration_data();
|
||||
$query['shared_key'] = $registration_data['shared'];
|
||||
$query['token'] = uuid_v5( wp_generate_uuid4(), $url );
|
||||
$query['website_uuid'] = $this->auth->get_site_id();
|
||||
$url_parts['query'] = http_build_query( $query );
|
||||
|
||||
$url = http_build_url( $url_parts );
|
||||
|
||||
$signed_url = $this->auth->signUrl( $method, $url );
|
||||
|
||||
return $this->wp_http->request( $signed_url, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response Response from reportCopiedSite()
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function processCopyReportConfirmation( $response ) {
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
return $this->storeAuthData( $response_body ) && (bool) $this->sendSiteReportConfirmation();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of WP_User instances into an array of data nedded by AMS to identify users.
|
||||
*
|
||||
* @param WP_User[] $users An array of WP_User instances.
|
||||
* @param bool $with_name_details True if name details should be included.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_users_data( array $users, $with_name_details = false ) {
|
||||
$user_data = array();
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
$wp_user = get_user_by( 'id', $user->ID );
|
||||
$user_data[] = $this->get_user_data( $wp_user, $with_name_details );
|
||||
}
|
||||
|
||||
return $user_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a reponse has a body.
|
||||
*
|
||||
* @param array|\WP_Error $response The response of the remote request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function response_has_body( $response ) {
|
||||
return ! is_wp_error( $response ) && array_key_exists( 'body', $response );
|
||||
}
|
||||
|
||||
private function get_errors( $response, $logError = true ) {
|
||||
$response_errors = null;
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response_errors = $response;
|
||||
} elseif ( array_key_exists( 'body', $response ) && $response['response']['code'] >= self::HTTP_ERROR_CODE_400 ) {
|
||||
$main_error = array();
|
||||
$errors = array();
|
||||
$error_message = $response['response']['message'];
|
||||
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
if ( ! $response_body ) {
|
||||
$error_message = $response['body'];
|
||||
$main_error = array( $response['body'] );
|
||||
} elseif ( array_key_exists( 'errors', $response_body ) ) {
|
||||
$errors = $response_body['errors'];
|
||||
$main_error = array_shift( $errors );
|
||||
$error_message = $this->get_error_message( $main_error, $response['body'] );
|
||||
}
|
||||
|
||||
$response_errors = new WP_Error( $response['response']['code'], $error_message, $main_error );
|
||||
|
||||
foreach ( $errors as $error ) {
|
||||
$error_message = $this->get_error_message( $error, $response['body'] );
|
||||
$error_status = isset( $error['status'] ) ? 'ams_error: ' . $error['status'] : '';
|
||||
$response_errors->add( $error_status, $error_message, $error );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $logError && $response_errors ) {
|
||||
$this->log_api_error( $response_errors->get_error_message(), $response_errors->get_error_data() );
|
||||
}
|
||||
|
||||
return $response_errors;
|
||||
}
|
||||
|
||||
private function log_api_error( $message, $data ) {
|
||||
$entry = new Entry();
|
||||
$entry->eventType = EventsTypes::SERVER_AMS;
|
||||
$entry->description = $message;
|
||||
$entry->extraData = [ 'errorData' => $data ];
|
||||
|
||||
wpml_tm_ate_ams_log( $entry );
|
||||
}
|
||||
|
||||
private function ping_healthy_wpml_endpoint() {
|
||||
$response = $this->request( 'GET', defined( 'WPML_TM_INTERNET_CHECK_URL' ) ? WPML_TM_INTERNET_CHECK_URL : 'https://health.wpml.org/', [] );
|
||||
|
||||
return ! is_wp_error( $response ) && (int) \WPML\FP\Obj::path( [ 'response', 'code' ], $response ) === 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ams_error
|
||||
* @param string $default
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_error_message( $ams_error, $default ) {
|
||||
$title = isset( $ams_error['title'] ) ? $ams_error['title'] . ': ' : '';
|
||||
$details = isset( $ams_error['detail'] ) ? $ams_error['detail'] : $default;
|
||||
|
||||
return $title . $details;
|
||||
}
|
||||
|
||||
private function response_has_keys( $response ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
return array_key_exists( 'secret_key', $response_body ) && array_key_exists( 'shared_key', $response_body );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_registration_data() {
|
||||
return get_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $registration_data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function set_registration_data( $registration_data ) {
|
||||
return update_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, $registration_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $managers
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function synchronize_managers( array $managers ) {
|
||||
$result = null;
|
||||
|
||||
$managers_data = $this->get_users_data( $managers, true );
|
||||
|
||||
if ( $managers_data ) {
|
||||
$url = $this->endpoints->get_ams_synchronize_managers();
|
||||
$url = str_replace( '{WEBSITE_UUID}', wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ), $url );
|
||||
|
||||
$params = array( 'translation_managers' => $managers_data );
|
||||
|
||||
$response = $this->signed_request( 'PUT', $url, $params );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = $response_body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $translators
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function synchronize_translators( array $translators ) {
|
||||
$result = null;
|
||||
|
||||
$translators_data = $this->get_users_data( $translators );
|
||||
|
||||
if ( $translators_data ) {
|
||||
$url = $this->endpoints->get_ams_synchronize_translators();
|
||||
|
||||
$params = array( 'translators' => $translators_data );
|
||||
|
||||
$response = $this->signed_request( 'PUT', $url, $params );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = $response_body;
|
||||
}
|
||||
} elseif ( is_wp_error( $response ) ) {
|
||||
$result = $response;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $url
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function request( $method, $url, array $params = null ) {
|
||||
$lock = $this->clonedSitesHandler->checkCloneSiteLock();
|
||||
if ( $lock ) {
|
||||
return $lock;
|
||||
}
|
||||
|
||||
$method = strtoupper( $method );
|
||||
$headers = [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
FingerprintGenerator::SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(),
|
||||
];
|
||||
|
||||
$args = [
|
||||
'method' => $method,
|
||||
'headers' => $headers,
|
||||
'timeout' => max( ini_get( 'max_execution_time' ) / 2, 5 ),
|
||||
];
|
||||
|
||||
if ( $params ) {
|
||||
$args['body'] = wp_json_encode( $params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
$response = $this->wp_http->request( $this->add_versions_to_url( $url ), $args );
|
||||
|
||||
if ( ! is_wp_error( $response ) ) {
|
||||
$response = $this->clonedSitesHandler->handleClonedSiteError( $response );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function signed_request( $verb, $url, array $params = null ) {
|
||||
$verb = strtoupper( $verb );
|
||||
$signed_url = $this->auth->get_signed_url_with_parameters( $verb, $url, $params );
|
||||
|
||||
if ( is_wp_error( $signed_url ) ) {
|
||||
return $signed_url;
|
||||
}
|
||||
|
||||
return $this->request( $verb, $signed_url, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function add_versions_to_url( $url ) {
|
||||
$url_parts = wp_parse_url( $url );
|
||||
$query = array();
|
||||
if ( array_key_exists( 'query', $url_parts ) ) {
|
||||
parse_str( $url_parts['query'], $query );
|
||||
}
|
||||
$query['wpml_core_version'] = ICL_SITEPRESS_VERSION;
|
||||
$query['wpml_tm_version'] = WPML_TM_VERSION;
|
||||
|
||||
$url_parts['query'] = http_build_query( $query );
|
||||
$url = http_build_url( $url_parts );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function override_site_id( $site_id ) {
|
||||
$this->auth->override_site_id( $site_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function getCredits() {
|
||||
return $this->getSignedResult(
|
||||
'GET',
|
||||
$this->endpoints->get_credits()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function resumeAll() {
|
||||
return $this->getSignedResult(
|
||||
'GET',
|
||||
$this->endpoints->get_resume_all()
|
||||
);
|
||||
}
|
||||
|
||||
public function send_sitekey( $sitekey ) {
|
||||
$siteId = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE );
|
||||
$response = $this->getSignedResult(
|
||||
'POST',
|
||||
$this->endpoints->get_send_sitekey(),
|
||||
[
|
||||
'site_key' => $sitekey,
|
||||
'website_uuid' => $siteId,
|
||||
]
|
||||
);
|
||||
|
||||
return Relation::propEq( 'updated_website', $siteId, $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private function getSignedResult( $verb, $url, array $params = null ) {
|
||||
$result = null;
|
||||
|
||||
$response = $this->signed_request( $verb, $url, $params );
|
||||
|
||||
if ( $this->response_has_body( $response ) ) {
|
||||
|
||||
$result = $this->get_errors( $response );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = Json::toArray( $response['body'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
<?php
|
||||
|
||||
use WPML\TM\ATE\ClonedSites\FingerprintGenerator;
|
||||
use WPML\TM\ATE\Log\Entry;
|
||||
use WPML\TM\ATE\Log\EventsTypes;
|
||||
use WPML\TM\ATE\ClonedSites\ApiCommunication as ClonedSitesHandler;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Either;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Str;
|
||||
use WPML\Element\API\Entity\LanguageMapping;
|
||||
use WPML\LIB\WP\WordPress;
|
||||
use WPML\TM\Editor\ATEDetailedErrorMessage;
|
||||
use function WPML\FP\invoke;
|
||||
use function WPML\FP\pipe;
|
||||
use WPML\Element\API\Languages;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\FP\Maybe;
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_API {
|
||||
|
||||
const TRANSLATED = 6;
|
||||
const DELIVERING = 7;
|
||||
const NOT_ENOUGH_CREDIT_STATUS = 31;
|
||||
const CANCELLED_STATUS = 20;
|
||||
const SHOULD_HIDE_STATUS = 42;
|
||||
|
||||
private $wp_http;
|
||||
private $auth;
|
||||
private $endpoints;
|
||||
|
||||
/**
|
||||
* @var ClonedSitesHandler
|
||||
*/
|
||||
private $clonedSitesHandler;
|
||||
|
||||
/**
|
||||
* @var FingerprintGenerator
|
||||
*/
|
||||
private $fingerprintGenerator;
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_API constructor.
|
||||
*
|
||||
* @param WP_Http $wp_http
|
||||
* @param WPML_TM_ATE_Authentication $auth
|
||||
* @param WPML_TM_ATE_AMS_Endpoints $endpoints
|
||||
* @param ClonedSitesHandler $clonedSitesHandler
|
||||
* @param FingerprintGenerator $fingerprintGenerator
|
||||
*/
|
||||
public function __construct(
|
||||
WP_Http $wp_http,
|
||||
WPML_TM_ATE_Authentication $auth,
|
||||
WPML_TM_ATE_AMS_Endpoints $endpoints,
|
||||
ClonedSitesHandler $clonedSitesHandler,
|
||||
FingerprintGenerator $fingerprintGenerator
|
||||
) {
|
||||
$this->wp_http = $wp_http;
|
||||
$this->auth = $auth;
|
||||
$this->endpoints = $endpoints;
|
||||
$this->clonedSitesHandler = $clonedSitesHandler;
|
||||
$this->fingerprintGenerator = $fingerprintGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
*
|
||||
* @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/jobs/create
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function create_jobs( array $params ) {
|
||||
return $this->requestWithLog(
|
||||
$this->endpoints->get_ate_jobs(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => $params,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|array $ate_job_id
|
||||
*
|
||||
* @return array|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function confirm_received_job( $ate_job_id ) {
|
||||
return $this->requestWithLog( $this->endpoints->get_ate_confirm_job( $ate_job_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|int $jobIds
|
||||
* @param bool $onlyFailed
|
||||
*
|
||||
* @return array|mixed|object|string|\WP_Error|null
|
||||
*/
|
||||
public function cancelJobs( $jobIds, $onlyFailed = false ) {
|
||||
return $this->requestWithLog(
|
||||
$this->endpoints->getAteCancelJobs(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [
|
||||
'id' => (array) $jobIds,
|
||||
'only_failed' => $onlyFailed
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|int $jobIds
|
||||
* @param bool $force
|
||||
*
|
||||
* @return array|mixed|object|string|\WP_Error|null
|
||||
*/
|
||||
public function hideJobs( $jobIds, $force = false ) {
|
||||
return $this->requestWithLog(
|
||||
$this->endpoints->getAteHideJobs(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [
|
||||
'id' => (array) $jobIds,
|
||||
'force' => $force
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $job_id
|
||||
* @param string $return_url
|
||||
*
|
||||
* @return string|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get_editor_url( $job_id, $return_url ) {
|
||||
$lock = $this->clonedSitesHandler->checkCloneSiteLock();
|
||||
if ( $lock ) {
|
||||
return new WP_Error( 'communication_error', 'ATE communication is locked, please update configuration' );
|
||||
}
|
||||
|
||||
$url = $this->endpoints->get_ate_editor();
|
||||
$url = str_replace(
|
||||
[
|
||||
'{job_id}',
|
||||
'{translator_email}',
|
||||
'{return_url}',
|
||||
],
|
||||
[
|
||||
$job_id,
|
||||
urlencode( filter_var( wp_get_current_user()->user_email, FILTER_SANITIZE_URL ) ),
|
||||
urlencode( filter_var( $return_url, FILTER_SANITIZE_URL ) ),
|
||||
],
|
||||
$url
|
||||
);
|
||||
|
||||
return $this->auth->get_signed_url_with_parameters( 'GET', $url, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ate_job_id
|
||||
* @param WPML_Element_Translation_Job $job_object
|
||||
* @param int|null $sentFrom
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function clone_job( $ate_job_id, WPML_Element_Translation_Job $job_object, $sentFrom = null ) {
|
||||
$url = $this->endpoints->get_clone_job( $ate_job_id );
|
||||
$params = [
|
||||
'id' => $ate_job_id,
|
||||
'notify_url' =>
|
||||
\WPML\TM\ATE\REST\PublicReceive::get_receive_ate_job_url( $job_object->get_id() ),
|
||||
'site_identifier' => wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ),
|
||||
'source_id' => wpml_tm_get_records()
|
||||
->icl_translate_job_by_job_id( $job_object->get_id() )
|
||||
->rid(),
|
||||
'permalink' => $job_object->get_url( true ),
|
||||
'ate_ams_console_url' => wpml_tm_get_ams_ate_console_url(),
|
||||
];
|
||||
|
||||
if ( $sentFrom ) {
|
||||
$params['job_type'] = $sentFrom;
|
||||
}
|
||||
|
||||
$result = $this->requestWithLog( $url, [ 'method' => 'POST', 'body' => $params ] );
|
||||
|
||||
return $result && ! is_wp_error( $result ) ?
|
||||
[
|
||||
'id' => $result->job_id,
|
||||
'ate_status' => Obj::propOr( WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_CREATED, 'status', $result )
|
||||
] :
|
||||
false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ate_job_id
|
||||
*
|
||||
* @return array|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get_job( $ate_job_id ) {
|
||||
if ( ! $ate_job_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->requestWithLog( $this->endpoints->get_ate_jobs( $ate_job_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* If `$job_ids` is not an empty array,
|
||||
* the `$statuses` parameter will be ignored in ATE's endpoint.
|
||||
*
|
||||
* @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/jobs/status
|
||||
*
|
||||
* @param null|array $job_ids
|
||||
* @param null|array $statuses
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get_jobs( $job_ids, $statuses = null ) {
|
||||
return $this->requestWithLog( $this->endpoints->get_ate_jobs( $job_ids, $statuses ) );
|
||||
}
|
||||
|
||||
public function get_job_status_with_priority( $job_id ) {
|
||||
return $this->requestWithLog(
|
||||
$this->endpoints->get_ate_job_status(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [ 'id' => $job_id,
|
||||
'preview' => true],
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $wpml_job_ids
|
||||
*
|
||||
* @return array|mixed|object|WP_Error|null
|
||||
*/
|
||||
public function get_jobs_by_wpml_ids( $wpml_job_ids ) {
|
||||
return $this->requestWithLog( $this->endpoints->get_ate_jobs_by_wpml_job_ids( $wpml_job_ids ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $pairs
|
||||
* @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/migration/migrate
|
||||
* @return bool
|
||||
*/
|
||||
public function migrate_source_id( array $pairs ) {
|
||||
$lock = $this->clonedSitesHandler->checkCloneSiteLock();
|
||||
if ( $lock ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$verb = 'POST';
|
||||
|
||||
$url = $this->auth->get_signed_url_with_parameters( $verb, $this->endpoints->get_source_id_migration(), $pairs );
|
||||
if ( is_wp_error( $url ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$result = $this->wp_http->request(
|
||||
$url,
|
||||
array(
|
||||
'timeout' => 60,
|
||||
'method' => $verb,
|
||||
'headers' => $this->json_headers(),
|
||||
'body' => wp_json_encode( $pairs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = $this->clonedSitesHandler->handleClonedSiteError( $result );
|
||||
}
|
||||
|
||||
return $this->get_response_errors( $result ) === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LanguageMapping[] $languagesToMap
|
||||
*
|
||||
* @return Either
|
||||
*/
|
||||
public function create_language_mapping( array $languagesToMap ) {
|
||||
$result = $this->requestWithLog(
|
||||
$this->endpoints->getLanguages(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [
|
||||
'mappings' => Fns::map( invoke( 'toATEFormat' ), Obj::values( $languagesToMap ) )
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
// it has an error when there is at least one record which has falsy "result" => "created" field
|
||||
$hasError = Lst::find( Logic::complement( Obj::path( [ 'result', 'created' ] ) ) );
|
||||
|
||||
$logError = Fns::tap( function ( $data ) {
|
||||
$entry = new Entry();
|
||||
$entry->eventType = EventsTypes::SERVER_ATE;
|
||||
$entry->description = __( 'Saving of Language mapping to ATE failed', 'wpml-translation-management' );
|
||||
$entry->extraData = $data;
|
||||
|
||||
wpml_tm_ate_ams_log( $entry );
|
||||
} );
|
||||
|
||||
return WordPress::handleError( $result )
|
||||
->map( Obj::prop( 'mappings' ) )
|
||||
->chain( Logic::ifElse( $hasError, pipe( $logError, Either::left() ), Either::right() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mappingIds
|
||||
*
|
||||
* @return false|array
|
||||
*/
|
||||
public function remove_language_mapping( $mappingIds ) {
|
||||
$result = $this->requestWithLog(
|
||||
$this->endpoints->getDeleteLanguagesMapping(),
|
||||
[ 'method' => 'POST', 'body' => [ 'mappings' => $mappingIds ] ]
|
||||
);
|
||||
|
||||
return is_wp_error( $result ) ? false : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $languageCodes
|
||||
* @param null|string $sourceLanguage
|
||||
*
|
||||
* @return Maybe
|
||||
*/
|
||||
public function get_languages_supported_by_automatic_translations( $languageCodes, $sourceLanguage = null ) {
|
||||
$sourceLanguage = $sourceLanguage ?: Languages::getDefaultCode();
|
||||
|
||||
$result = $this->requestWithLog(
|
||||
$this->endpoints->getLanguagesCheckPairs(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [
|
||||
[
|
||||
'source_language' => $sourceLanguage,
|
||||
'target_languages' => $languageCodes,
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
return Maybe::of( $result )
|
||||
->reject( 'is_wp_error' )
|
||||
->map( Obj::prop( 'results' ) )
|
||||
->map( Lst::find( Relation::propEq( 'source_language', $sourceLanguage ) ) )
|
||||
->map( Obj::prop( 'target_languages' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* It returns language details from ATE including the info about translation engine supporting this language.
|
||||
*
|
||||
* If $inTheWebsiteContext is true, then we are taking into consideration user's translation engine settings.
|
||||
* It means that generally language may be supported e.g. by google, but when he turns off this engine, it will be reflected in the response.
|
||||
*
|
||||
* @param string $languageCode
|
||||
* @param bool $inTheWebsiteContext
|
||||
*
|
||||
* @return Maybe
|
||||
*/
|
||||
public function get_language_details( $languageCode, $inTheWebsiteContext = true ) {
|
||||
$result = $this->requestWithLog( sprintf( $this->endpoints->getShowLanguage(), $languageCode ), [ 'method' => 'GET' ] );
|
||||
|
||||
return Maybe::of( $result )
|
||||
->reject( 'is_wp_error' )
|
||||
->map( Obj::prop( $inTheWebsiteContext ? 'website_language' : 'language' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_available_languages() {
|
||||
$result = $this->requestWithLog( $this->endpoints->getLanguages(), [ 'method' => 'GET' ] );
|
||||
|
||||
return is_wp_error( $result ) ? [] : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Maybe
|
||||
*/
|
||||
public function get_language_mapping() {
|
||||
$result = $this->requestWithLog( $this->endpoints->getLanguagesMapping(), [ 'method' => 'GET' ] );
|
||||
|
||||
return Maybe::of( $result )->reject( 'is_wp_error' );
|
||||
}
|
||||
|
||||
public function start_translation_memory_migration() {
|
||||
$result = $this->requestWithLog(
|
||||
$this->endpoints->startTranlsationMemoryIclMigration(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [
|
||||
'site_identifier' => $this->get_website_id( site_url() ),
|
||||
'ts_id' => 10,
|
||||
// random numbers for now, we should check what needs to be done for the final version.
|
||||
'ts_access_key' => 20,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return WordPress::handleError( $result );
|
||||
}
|
||||
|
||||
public function check_translation_memory_migration() {
|
||||
$result = $this->requestWithLog(
|
||||
$this->endpoints->checkStatusTranlsationMemoryIclMigration(),
|
||||
[
|
||||
'method' => 'GET',
|
||||
'body' => [
|
||||
'site_identifier' => $this->get_website_id( site_url() ),
|
||||
'ts_id' => 10,
|
||||
// random numbers for now, we should check what needs to be done for the final version.
|
||||
'ts_access_key' => 20,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return WordPress::handleError( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://ate.pages.onthegosystems.com/ate-docs/ATE/API/V1/icl/translators/import
|
||||
*
|
||||
* @param $iclToken
|
||||
* @param $iclServiceId
|
||||
*
|
||||
* @return callable|Either
|
||||
*/
|
||||
public function import_icl_translators( $tsId, $tsAccessKey ) {
|
||||
$params = [
|
||||
'site_identifier' => $this->auth->get_site_id(),
|
||||
'ts_id' => $tsId,
|
||||
'ts_access_key' => $tsAccessKey
|
||||
];
|
||||
|
||||
$result = $this->requestWithLog( $this->endpoints->importIclTranslators(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => $params
|
||||
] );
|
||||
|
||||
return WordPress::handleError( $result );
|
||||
}
|
||||
|
||||
private function get_response( $result ) {
|
||||
$errors = $this->get_response_errors( $result );
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
return $this->get_response_body( $result );
|
||||
}
|
||||
|
||||
private function get_response_body( $result ) {
|
||||
if ( is_array( $result ) && array_key_exists( 'body', $result ) && ! is_wp_error( $result ) ) {
|
||||
$body = json_decode( $result['body'] );
|
||||
|
||||
if ( isset( $body->authenticated ) && ! (bool) $body->authenticated ) {
|
||||
return new WP_Error( 'ate_auth_failed', $body->message );
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function get_response_errors( $response ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
$response_errors = null;
|
||||
|
||||
$response = (array) $response;
|
||||
if ( array_key_exists( 'body', $response ) && $response['response']['code'] >= 400 ) {
|
||||
$errors = array();
|
||||
|
||||
$response_body = json_decode( $response['body'], true );
|
||||
|
||||
if ( is_array( $response_body ) && array_key_exists( 'errors', $response_body ) ) {
|
||||
$errors = $response_body['errors'];
|
||||
}
|
||||
|
||||
$response_errors = new WP_Error( $response['response']['code'], $response['response']['message'], $errors );
|
||||
}
|
||||
|
||||
return $response_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function json_headers() {
|
||||
return [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
FingerprintGenerator::SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encode_body_args( array $args ) {
|
||||
return wp_json_encode( $args, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $xliff_url
|
||||
* @param array|\stdClass|false|null $job
|
||||
*
|
||||
* @return string
|
||||
* @throws Requests_Exception
|
||||
*/
|
||||
public function get_remote_xliff_content( $xliff_url, $job = null ) {
|
||||
|
||||
$entry = $this->prepare_xliff_log_entry( $xliff_url, $job );
|
||||
|
||||
wpml_tm_ate_ams_log( $entry, true );
|
||||
|
||||
/** @var \WP_Error|array $response */
|
||||
$response = $this->wp_http->get( $xliff_url, array(
|
||||
'timeout' => min( 30, ini_get( 'max_execution_time' ) ?: 10 )
|
||||
) );
|
||||
|
||||
wpml_tm_ate_ams_log_remove( $entry );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
throw new Requests_Exception( $response->get_error_message(), $response->get_error_code() );
|
||||
}
|
||||
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
public function override_site_id( $site_id ) {
|
||||
$this->auth->override_site_id( $site_id );
|
||||
}
|
||||
|
||||
public function get_website_id( $site_url ) {
|
||||
$lock = $this->clonedSitesHandler->checkCloneSiteLock();
|
||||
if ( $lock ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$signed_url = $this->auth->get_signed_url_with_parameters( 'GET', $this->endpoints->get_websites() );
|
||||
if ( is_wp_error( $signed_url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$requestArguments = [ 'headers' => $this->json_headers() ];
|
||||
|
||||
$response = $this->wp_http->request( $signed_url, $requestArguments );
|
||||
|
||||
if ( ! is_wp_error( $response ) ) {
|
||||
$response = $this->clonedSitesHandler->handleClonedSiteError( $response );
|
||||
}
|
||||
|
||||
$sites = $this->get_response( $response );
|
||||
|
||||
foreach ( $sites as $site ) {
|
||||
if ( $site->url === $site_url ) {
|
||||
return $site->uuid;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/sync/all
|
||||
*
|
||||
* @param array $ateJobIds
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function sync_all( array $ateJobIds ) {
|
||||
return $this->requestWithLog(
|
||||
$this->endpoints->get_sync_all(),
|
||||
[
|
||||
'method' => 'POST',
|
||||
'body' => [ 'ids' => $ateJobIds ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/sync/page
|
||||
*
|
||||
* @param string $token
|
||||
* @param int $page
|
||||
*
|
||||
* @return array|mixed|null|object|WP_Error
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function sync_page( $token, $page ) {
|
||||
return $this->requestWithLog( $this->endpoints->get_sync_page( $token, $page ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $requestArgs
|
||||
*
|
||||
* @return array|mixed|object|string|WP_Error|null
|
||||
*/
|
||||
private function request( $url, array $requestArgs = [] ) {
|
||||
$lock = $this->clonedSitesHandler->checkCloneSiteLock();
|
||||
if ( $lock ) {
|
||||
return $lock;
|
||||
}
|
||||
|
||||
$requestArgs = array_merge(
|
||||
[
|
||||
'timeout' => 60,
|
||||
'method' => 'GET',
|
||||
'headers' => $this->json_headers(),
|
||||
],
|
||||
$requestArgs
|
||||
);
|
||||
|
||||
$bodyArgs = isset( $requestArgs['body'] ) && is_array( $requestArgs['body'] )
|
||||
? $requestArgs['body'] : null;
|
||||
|
||||
$signedUrl = $this->auth->get_signed_url_with_parameters( $requestArgs['method'], $url, $bodyArgs );
|
||||
|
||||
if ( is_wp_error( $signedUrl ) ) {
|
||||
return $signedUrl;
|
||||
}
|
||||
|
||||
if ( $bodyArgs ) {
|
||||
$requestArgs['body'] = $this->encode_body_args( $bodyArgs );
|
||||
}
|
||||
|
||||
$result = $this->wp_http->request( $signedUrl, $requestArgs );
|
||||
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$result = $this->clonedSitesHandler->handleClonedSiteError( $result );
|
||||
}
|
||||
|
||||
return $this->get_response( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $requestArgs
|
||||
*
|
||||
* @return array|mixed|object|string|WP_Error|null
|
||||
*/
|
||||
private function requestWithLog( $url, array $requestArgs = [] ) {
|
||||
$response = $this->request( $url, $requestArgs );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$entry = new Entry();
|
||||
$entry->eventType = EventsTypes::SERVER_ATE;
|
||||
$entry->description = $response->get_error_message();
|
||||
$errorCode = $response->get_error_code();
|
||||
$entry->extraData = [
|
||||
'url' => $url,
|
||||
'requestArgs' => $requestArgs,
|
||||
];
|
||||
|
||||
if ( $errorCode ) {
|
||||
$entry->extraData['status'] = $errorCode;
|
||||
}
|
||||
if ( $response->get_error_data( $errorCode ) ) {
|
||||
$entry->extraData['details'] = $response->get_error_data( $errorCode );
|
||||
}
|
||||
|
||||
wpml_tm_ate_ams_log( $entry );
|
||||
|
||||
ATEDetailedErrorMessage::saveDetailedError( $response );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $xliff_url
|
||||
* @param array|\stdClass|false|null $job
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
private function prepare_xliff_log_entry( $xliff_url, $job ) {
|
||||
$entry = new WPML\TM\ATE\Log\Entry();
|
||||
|
||||
if ( $job ) {
|
||||
$entry->ateJobId = Obj::prop('ateJobId', $job);
|
||||
$entry->wpmlJobId = Obj::prop('jobId', $job);
|
||||
}
|
||||
|
||||
$entry->eventType = WPML\TM\ATE\Log\EventsTypes::SERVER_ATE;
|
||||
$entry->description = 'Started attempt to download xliff file. The process did not finish.';
|
||||
$entry->extraData = [ 'xliff_url' => $xliff_url ];
|
||||
|
||||
return $entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Authentication {
|
||||
const AMS_DATA_KEY = 'WPML_TM_AMS';
|
||||
const AMS_STATUS_NON_ACTIVE = 'non-active';
|
||||
const AMS_STATUS_ENABLED = 'enabled';
|
||||
const AMS_STATUS_ACTIVE = 'active';
|
||||
|
||||
/** @var string|null $site_id */
|
||||
private $site_id = null;
|
||||
|
||||
public function get_signed_url_with_parameters( $verb, $url, $params = null ) {
|
||||
if ( $this->has_keys() ) {
|
||||
$url = $this->add_required_arguments_to_url( $verb, $url, $params );
|
||||
return $this->signUrl( $verb, $url, $params );
|
||||
}
|
||||
|
||||
return new WP_Error( 'auth_error', 'Unable to authenticate' );
|
||||
}
|
||||
|
||||
public function signUrl( $verb, $url, $params = null ) {
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$query = $this->get_url_query( $url );
|
||||
$query['signature'] = $this->get_signature( $verb, $url, $params );
|
||||
|
||||
$url_parts['query'] = $this->build_query( $query );
|
||||
|
||||
return http_build_url( $url_parts );
|
||||
}
|
||||
|
||||
private function get_signature( $verb, $url, array $params = null ) {
|
||||
if ( $this->has_keys() ) {
|
||||
$verb = strtolower( $verb );
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$query_to_sign = $this->get_url_query( $url );
|
||||
|
||||
if ( $params && 'get' !== $verb ) {
|
||||
$query_to_sign['body'] = md5( wp_json_encode( $params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) );
|
||||
}
|
||||
|
||||
$url_parts_to_sign = $url_parts;
|
||||
$url_parts_to_sign['query'] = $this->build_query( $query_to_sign );
|
||||
|
||||
$url_to_sign = http_build_url( $url_parts_to_sign );
|
||||
|
||||
$string_to_sign = strtolower( $verb ) . $url_to_sign;
|
||||
|
||||
$sha1 = hash_hmac( 'sha1', $string_to_sign, $this->get_secret(), true );
|
||||
|
||||
return base64_encode( $sha1 );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function has_keys() {
|
||||
return $this->get_secret() && $this->get_shared();
|
||||
}
|
||||
|
||||
private function get_secret() {
|
||||
return $this->get_ams_data_property( 'secret' );
|
||||
}
|
||||
|
||||
private function get_shared() {
|
||||
return $this->get_ams_data_property( 'shared' );
|
||||
}
|
||||
|
||||
private function get_ams_data_property( $field ) {
|
||||
$data = $this->get_ams_data();
|
||||
if ( array_key_exists( $field, $data ) ) {
|
||||
return $data[ $field ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_ams_data() {
|
||||
return get_option( self::AMS_DATA_KEY, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function add_required_arguments_to_url( $verb, $url, array $params = null ) {
|
||||
$verb = strtolower( $verb );
|
||||
|
||||
$url_parts = wp_parse_url( $url );
|
||||
|
||||
$query = $this->get_url_query( $url );
|
||||
if ( $params && 'get' === $verb ) {
|
||||
foreach ( $params as $key => $value ) {
|
||||
$query[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$query['wpml_core_version'] = ICL_SITEPRESS_VERSION;
|
||||
$query['wpml_tm_version'] = WPML_TM_VERSION;
|
||||
$query['shared_key'] = $this->get_shared();
|
||||
$query['token'] = uuid_v5( wp_generate_uuid4(), $url );
|
||||
$query['website_uuid'] = $this->get_site_id();
|
||||
$query['ui_language_code'] = apply_filters(
|
||||
'wpml_get_user_admin_language',
|
||||
wpml_get_default_language(),
|
||||
get_current_user_id()
|
||||
);
|
||||
if( function_exists( 'OTGS_Installer' ) ) {
|
||||
$query['site_key'] = OTGS_Installer()->get_site_key( 'wpml' );
|
||||
}
|
||||
|
||||
$url_parts['query'] = http_build_query( $query );
|
||||
|
||||
return http_build_url( $url_parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_url_query( $url ) {
|
||||
$url_parts = wp_parse_url( $url );
|
||||
$query = array();
|
||||
if ( array_key_exists( 'query', $url_parts ) ) {
|
||||
parse_str( $url_parts['query'], $query );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $query
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
protected function build_query( $query ) {
|
||||
if ( PHP_VERSION_ID >= 50400 ) {
|
||||
$final_query = http_build_query( $query, '', '&', PHP_QUERY_RFC3986 );
|
||||
} else {
|
||||
$final_query = str_replace(
|
||||
array( '+', '%7E' ),
|
||||
array( '%20', '~' ),
|
||||
http_build_query( $query )
|
||||
);
|
||||
}
|
||||
|
||||
return $final_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $site_id
|
||||
*/
|
||||
public function override_site_id( $site_id ) {
|
||||
$this->site_id = $site_id;
|
||||
}
|
||||
|
||||
public function get_site_id() {
|
||||
return $this->site_id ? $this->site_id : wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user