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,40 @@
<?php
class WPML_TM_Log implements WPML_TP_API_Log_Interface {
const LOG_WP_OPTION = '_wpml_tp_api_Logger';
const LOG_MAX_SIZE = 500;
public function log( $action, $data = array() ) {
$log_base_data = array(
'timestamp' => false,
'action' => false,
);
$log_item = array_merge( $log_base_data, $data );
$log_item['timestamp'] = date( 'Y-m-d H:i:s' );
$log_item['action'] = $action;
$log = $this->get_log_data();
$log = array_slice( $log, - ( self::LOG_MAX_SIZE - 1 ) );
$log[] = $log_item;
$this->update_log( $log );
}
private function update_log( $log ) {
return update_option( self::LOG_WP_OPTION, $log, false );
}
public function flush_log() {
return update_option( self::LOG_WP_OPTION, [], false );
}
public function get_log_data() {
$log = get_option( self::LOG_WP_OPTION, [] );
return is_array( $log ) ? $log : [];
}
}

View File

@@ -0,0 +1,134 @@
<?php
class WPML_TP_API_Client {
/** @var string */
private $proxy_url;
/** @var WP_Http $http */
private $http;
/** @var WPML_TP_Lock $tp_lock */
private $tp_lock;
/** @var WPML_TP_HTTP_Request_Filter */
private $request_filter;
public function __construct(
$proxy_url,
WP_Http $http,
WPML_TP_Lock $tp_lock,
WPML_TP_HTTP_Request_Filter $request_filter
) {
$this->proxy_url = $proxy_url;
$this->http = $http;
$this->tp_lock = $tp_lock;
$this->request_filter = $request_filter;
}
/**
* @param WPML_TP_API_Request $request
* @param bool $raw_json_response
*
* @return array|mixed|stdClass|string
* @throws WPML_TP_API_Exception
*/
public function send_request( WPML_TP_API_Request $request, $raw_json_response = false ) {
if ( $this->tp_lock->is_locked( $request->get_url() ) ) {
throw new WPML_TP_API_Exception( 'Communication with translation proxy is not allowed.', $request );
}
WPML_TranslationProxy_Com_Log::log_call( $request->get_url(), $request->get_params() );
$response = $this->call_remote_api( $request );
if ( ! $response || is_wp_error( $response ) || ( isset( $response['response']['code'] ) && $response['response']['code'] >= 400 ) ) {
throw new WPML_TP_API_Exception( 'Communication error', $request, $response );
}
if ( isset( $response['headers']['content-type'] ) ) {
$content_type = $response['headers']['content-type'];
$response = $response['body'];
if ( strpos( $content_type, 'zip' ) !== false ) {
$response = gzdecode( $response );
} else {
WPML_TranslationProxy_Com_Log::log_response( $response );
}
$json_response = json_decode( $response );
if ( $json_response ) {
if ( $raw_json_response ) {
$response = $json_response;
} else {
$response = $this->handle_json_response( $request, $json_response );
}
}
}
return $response;
}
/**
* @param WPML_TP_API_Request $request
*
* @return null|string
*/
private function call_remote_api( WPML_TP_API_Request $request ) {
$context = $this->filter_request_params( $request->get_params(), $request->get_method() );
return $this->http->request( $this->proxy_url . $request->get_url(), $context );
}
/**
* @param array $params request parameters
* @param string $method HTTP request method
*
* @return array
*/
private function filter_request_params( $params, $method ) {
return $this->request_filter->build_request_context(
array(
'method' => $method,
'body' => $params,
'sslverify' => true,
'timeout' => 60,
)
);
}
/**
* @param WPML_TP_API_Request $request
* @param stdClass $response
*
* @return mixed
* @throws WPML_TP_API_Exception
*/
private function handle_json_response( WPML_TP_API_Request $request, $response ) {
if ( $request->has_api_response() ) {
if ( ! isset( $response->status->code ) || $response->status->code !== 0 ) {
throw new WPML_TP_API_Exception(
$this->generate_error_message_from_status_field( $response ),
$request,
$response
);
}
$response = $response->response;
}
return $response;
}
private function generate_error_message_from_status_field( $response ) {
$message = '';
if ( isset( $response->status->message ) ) {
if ( isset( $response->status->code ) ) {
$message = '(' . $response->status->code . ') ';
}
$message .= $response->status->message;
} else {
$message = 'Unknown error';
}
return $message;
}
}

View File

@@ -0,0 +1,46 @@
<?php
class WPML_TP_API_Exception extends Exception {
public function __construct( $message, WPML_TP_API_Request $request = null, $response = null ) {
if ( $request ) {
$message .= ' ' . $this->get_exception_message(
$request->get_url(),
$request->get_method(),
$request->get_params(),
$response
);
}
parent::__construct( $message );
}
private function get_exception_message( $url, $method, $params, $response ) {
return 'Details: |'
. ' url: '
. '`'
. $url
. '`'
. ' method: '
. '`'
. $method
. '`'
. ' param: '
. '`'
. json_encode( $this->filter_params( $params ) )
. '`'
. ' response: '
. '`'
. json_encode( $response )
. '`';
}
/**
* @param array $params
*
* @return array mixed
*/
private function filter_params( $params ) {
return wpml_collect( $params )->forget( 'accesskey' )->toArray();
}
}

View File

@@ -0,0 +1,7 @@
<?php
interface WPML_TP_API_Log_Interface {
public function log( $action, $data = array() );
}

View File

@@ -0,0 +1,131 @@
<?php
class WPML_TP_API_Request {
const API_VERSION = 1.1;
/** @var string */
private $url;
/** @var array */
private $params = array( 'api_version' => self::API_VERSION );
/** @var string */
private $method = 'GET';
/** @var bool */
private $has_api_response = true;
/**
* @param string $url
*/
public function __construct( $url ) {
if ( empty( $url ) ) {
throw new InvalidArgumentException( 'Url cannot be empty' );
}
$this->url = $url;
}
/**
* @param array $params
*/
public function set_params( array $params ) {
$this->params = array_merge( $this->params, $params );
}
/**
* @param string $method
*/
public function set_method( $method ) {
if ( ! in_array( $method, array( 'GET', 'POST', 'PUT', 'DELETE', 'HEAD' ), true ) ) {
throw new InvalidArgumentException( 'HTTP request method has invalid value' );
}
$this->method = $method;
}
/**
* @param bool $has_api_response
*/
public function set_has_api_response( $has_api_response ) {
$this->has_api_response = (bool) $has_api_response;
}
/**
* @return string
*/
public function get_url() {
$url = $this->url;
if ( $this->get_params() ) {
list( $url, $params_used_in_path ) = $this->add_parameters_to_path( $url, $this->get_params() );
if ( 'GET' === $this->get_method() ) {
$url = $this->add_query_parameters( $params_used_in_path, $url );
}
}
return $url;
}
/**
* @return array
*/
public function get_params() {
return $this->params;
}
/**
* @return string
*/
public function get_method() {
return $this->method;
}
/**
* @return bool
*/
public function has_api_response() {
return $this->has_api_response;
}
private function add_parameters_to_path( $url, array $params ) {
$used_params = array();
if ( preg_match_all( '/\{.+?\}/', $url, $symbs ) ) {
foreach ( $symbs[0] as $symb ) {
$without_braces = preg_replace( '/\{|\}/', '', $symb );
if ( preg_match_all( '/\w+/', $without_braces, $indexes ) ) {
foreach ( $indexes[0] as $index ) {
if ( isset( $params[ $index ] ) ) {
$used_params[] = $index;
$value = $params[ $index ];
$url = preg_replace( preg_quote( "/$symb/" ), $value, $url );
}
}
}
}
}
return array( $url, $used_params );
}
/**
* @param $params_used_in_path
* @param $url
*
* @return string
*/
private function add_query_parameters( $params_used_in_path, $url ) {
$url .= '?' . preg_replace(
'/\%5B\d+\%5D/',
'%5B%5D',
wpml_http_build_query(
array_diff_key(
$this->get_params(),
array_fill_keys( $params_used_in_path, 1 )
)
)
);
return $url;
}
}

View File

@@ -0,0 +1,24 @@
<?php
abstract class WPML_TP_API {
/** @var WPML_TP_API_Client */
protected $client;
/** @var WPML_TP_Project */
protected $project;
/** @var WPML_TP_API_Log_Interface */
protected $logger;
public function __construct( WPML_TP_API_Client $client, WPML_TP_Project $project, WPML_TP_API_Log_Interface $logger = null ) {
$this->client = $client;
$this->project = $project;
$this->logger = $logger;
}
protected function log( $action, array $params = array() ) {
if ( null !== $this->logger ) {
$this->logger->log( $action, $params );
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
class WPML_TP_Batch_Sync_API extends WPML_TP_API {
const INIT_SYNC = '/batches/sync.json';
const CHECK_STATUS = '/batches/sync/status.json';
/**
* @param array $batch_ids
*
* @return int[]
* @throws WPML_TP_API_Exception
*/
public function init_synchronization( array $batch_ids ) {
$request = new WPML_TP_API_Request( self::INIT_SYNC );
$request->set_params(
array(
'batch_id' => $batch_ids,
'accesskey' => $this->project->get_access_key(),
)
);
return $this->handle_response( $request );
}
/**
* @return int[]
* @throws WPML_TP_API_Exception
*/
public function check_progress() {
$request = new WPML_TP_API_Request( self::CHECK_STATUS );
$request->set_params( array( 'accesskey' => $this->project->get_access_key() ) );
return $this->handle_response( $request );
}
/**
* @param WPML_TP_API_Request $request
*
* @return array
* @throws WPML_TP_API_Exception
*/
private function handle_response( WPML_TP_API_Request $request ) {
$result = $this->client->send_request( $request, true );
if ( ! $result || empty( $result ) || ! isset( $result->queued_batches ) ) {
throw new WPML_TP_API_Exception( 'Batch synchronization could not be initialized' );
}
return array_map( 'intval', $result->queued_batches );
}
}

View File

@@ -0,0 +1,55 @@
<?php
class WPML_TP_Job_States {
const RECEIVED = 'received';
const WAITING_TRANSLATIONS = 'waiting_translation';
const TRANSLATION_READY = 'translation_ready';
const DELIVERED = 'delivered';
const CANCELLED = 'cancelled';
const ANY = 'any';
/**
* @return array
*/
public static function get_possible_states() {
return array(
self::RECEIVED,
self::WAITING_TRANSLATIONS,
self::TRANSLATION_READY,
self::DELIVERED,
self::CANCELLED,
self::ANY,
);
}
/**
* @return string
*/
public static function get_default_state() {
return self::WAITING_TRANSLATIONS;
}
/**
* @return array
*/
public static function get_finished_states() {
return array(
self::TRANSLATION_READY,
self::DELIVERED,
self::CANCELLED,
);
}
public static function map_tp_state_to_local( $tp_state ) {
switch ( $tp_state ) {
case self::TRANSLATION_READY:
case self::DELIVERED:
return ICL_TM_TRANSLATION_READY_TO_DOWNLOAD;
case self::CANCELLED:
return ICL_TM_NOT_TRANSLATED;
default:
return ICL_TM_IN_PROGRESS;
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
class WPML_TP_Job_Status {
/** @var int */
private $tp_id;
/** @var int */
private $batch_id;
/** @var string */
private $status;
/** @var int */
private $revision;
/** @var WPML_TM_Job_TS_Status|null */
private $ts_status;
/**
* @param int $tp_id
* @param int $batch_id
* @param string $state
* @param WPML_TM_Job_TS_Status|null $ts_status
* @param int $revision
*/
public function __construct( $tp_id, $batch_id, $state, $revision = 1, $ts_status = null ) {
$this->tp_id = (int) $tp_id;
$this->batch_id = (int) $batch_id;
if ( ! in_array( $state, WPML_TP_Job_States::get_possible_states(), true ) ) {
$state = 'any';
}
$this->status = $state;
$this->revision = (int) $revision;
$this->ts_status = $ts_status;
}
/**
* @return int
*/
public function get_tp_id() {
return $this->tp_id;
}
/**
* @return int
*/
public function get_batch_id() {
return $this->batch_id;
}
/**
* @return string
*/
public function get_status() {
return $this->status;
}
/**
* @return int
*/
public function get_revision() {
return $this->revision;
}
/**
* @return WPML_TM_Job_TS_Status|null
*/
public function get_ts_status() {
return $this->ts_status;
}
}

View File

@@ -0,0 +1,136 @@
<?php
class WPML_TP_Jobs_API extends WPML_TP_API {
const CHUNK_SIZE = 100;
/**
* @param int[] $tp_job_ids
*
* @return WPML_TP_Job_Status[]
* @throws WPML_TP_API_Exception
*/
public function get_jobs_statuses( array $tp_job_ids ) {
$this->log( 'Get jobs status', $tp_job_ids );
$chunks = array();
while ( $tp_job_ids ) {
$chunk_ids = array_splice( $tp_job_ids, 0, self::CHUNK_SIZE );
$chunks[] = $this->get_chunk_of_job_statuses( $chunk_ids );
}
$result = call_user_func_array( 'array_merge', $chunks );
return array_map( array( $this, 'build_job_status' ), $result );
}
private function get_chunk_of_job_statuses( $tp_job_ids ) {
$request = new WPML_TP_API_Request( '/jobs.json' );
$request->set_params(
array(
'filter' => array(
'job_ids' => array_filter( $tp_job_ids, 'is_int' ),
'archived' => 1,
),
'accesskey' => $this->project->get_access_key(),
)
);
return $this->client->send_request( $request );
}
/**
* @param array $cms_ids
* @param bool $archived
*
* @return array|mixed|stdClass|string
* @throws WPML_TP_API_Exception
*/
public function get_jobs_per_cms_ids( array $cms_ids, $archived = false ) {
$request = new WPML_TP_API_Request( '/jobs.json' );
$filter = array(
'filter' => array(
'cms_ids' => $cms_ids,
),
'accesskey' => $this->project->get_access_key(),
);
if ( $archived ) {
$filter['filter']['archived'] = (int) $archived;
$request->set_params( $filter );
}
return $this->client->send_request( $request );
}
/**
* @param WPML_TM_Job_Entity $job
* @param string $state
* @param string $post_url
*
* @throws WPML_TP_API_Exception
*/
public function update_job_state(
WPML_TM_Job_Entity $job,
$state = WPML_TP_Job_States::DELIVERED,
$post_url = null
) {
$params = array(
'job_id' => $job->get_tp_id(),
'project_id' => $this->project->get_id(),
'accesskey' => $this->project->get_access_key(),
'job' => array(
'state' => $state,
),
);
if ( $post_url ) {
$params['job']['url'] = $post_url;
}
$request = new WPML_TP_API_Request( '/jobs/{job_id}.json' );
$request->set_params( $params );
$request->set_method( 'PUT' );
$this->client->send_request( $request );
}
private function build_job_status( stdClass $raw_data ) {
return new WPML_TP_Job_Status(
$raw_data->id,
isset( $raw_data->batch->id ) ? $raw_data->batch->id : 0,
$raw_data->job_state,
isset( $raw_data->translation_revision ) ? $raw_data->translation_revision : 1,
$raw_data->ts_status ? new WPML_TM_Job_TS_Status(
$raw_data->ts_status->status,
$raw_data->ts_status->links
) : null
);
}
/**
* @return WPML_TP_Job_Status[]
* @throws WPML_TP_API_Exception
*/
public function get_revised_jobs() {
$this->log( 'Get revised jobs' );
$request = new WPML_TP_API_Request( '/jobs.json' );
$request->set_params(
array(
'filter' => array(
'job_state' => WPML_TP_Job_States::TRANSLATION_READY,
'archived' => 0,
'revision_greater_than' => 1,
),
'accesskey' => $this->project->get_access_key(),
)
);
$result = $this->client->send_request( $request );
return array_map( array( $this, 'build_job_status' ), $result );
}
}

View File

@@ -0,0 +1,105 @@
<?php
use WPML\TM\TranslationProxy\Services\Project\Project;
use WPML\TM\TranslationProxy\Services\Project\SiteDetails;
class WPML_TP_Project_API extends WPML_TP_API {
const API_VERSION = 1.1;
const PROJECTS_ENDPOINT = '/projects.json';
/**
* @throws WPML_TP_API_Exception
*/
public function refresh_language_pairs() {
$this->log( 'Refresh language pairs -> Request sent' );
$request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT );
$request->set_method( 'PUT' );
$request->set_params(
[
'project' => [ 'refresh_language_pairs' => 1 ],
'refresh_language_pairs' => 1,
'project_id' => $this->project->get_id(),
'accesskey' => $this->project->get_access_key(),
]
);
$this->client->send_request( $request );
}
/**
* @param stdClass $service
* @param SiteDetails $site_details
*
* @return stdClass
* @throws WPML_TP_API_Exception
*/
public function create_project( \stdClass $service, SiteDetails $site_details ) {
$project_data = array_merge(
$site_details->getBlogInfo(),
[
'delivery_method' => $site_details->getDeliveryMethod(),
'sitekey' => WP_Installer_API::get_site_key( 'wpml' ),
'client_external_id' => WP_Installer_API::get_ts_client_id(),
]
);
$params = [
'api_version' => self::API_VERSION,
'service' => [ 'id' => $service->id ],
'project' => $project_data,
'custom_fields' => $service->custom_fields_data,
'client' => $site_details->getClientData(),
'linked_by' => \TranslationProxy::get_service_linked_by_suid( $service->suid ),
];
$request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT );
$request->set_method( 'POST' );
$request->set_params( $params );
return $this->client->send_request( $request );
}
/**
* @param Project $project
* @param \stdClass $credentials
*
* @throws WPML_TP_API_Exception
*/
public function update_project_credentials( Project $project, \stdClass $credentials ) {
$request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT );
$request->set_method( 'PUT' );
$request->set_params(
[
'api_version' => self::API_VERSION,
'accesskey' => $project->accessKey,
'project' => [
'custom_fields' => (array) $credentials,
],
]
);
$this->client->send_request( $request );
}
/**
* @param Project $project
*
* @return array|mixed|stdClass|string
* @throws WPML_TP_API_Exception
*/
public function get_extra_fields( Project $project ) {
$params = [
'accesskey' => $project->accessKey,
'api_version' => self::API_VERSION,
'project_id' => $project->id,
];
$request = new WPML_TP_API_Request( '/projects/{project_id}/extra_fields.json' );
$request->set_method( 'GET' );
$request->set_params( $params );
return $this->client->send_request( $request );
}
}

View File

@@ -0,0 +1,21 @@
<?php
class WPML_TP_Services {
public function get_current_project() {
return TranslationProxy::get_current_project();
}
public function get_current_service() {
return TranslationProxy::get_current_service();
}
/**
* @param $service_id
* @param bool $custom_fields
*
* @throws WPMLTranslationProxyApiException
*/
public function select_service( $service_id, $custom_fields = false ) {
TranslationProxy::select_service( $service_id, $custom_fields );
}
}

View File

@@ -0,0 +1,50 @@
<?php
class WPML_TP_XLIFF_API extends WPML_TP_API {
/** @var WPML_TP_Xliff_Parser */
private $xliff_parser;
public function __construct(
WPML_TP_API_Client $client,
WPML_TP_Project $project,
WPML_TP_API_Log_Interface $logger,
WPML_TP_Xliff_Parser $xliff_parser
) {
parent::__construct( $client, $project, $logger );
$this->xliff_parser = $xliff_parser;
}
/**
* @param int $tp_job_id
* @param bool $parse
*
* @return WPML_TP_Translation_Collection|string
* @throws WPML_TP_API_Exception
*/
public function get_remote_translations( $tp_job_id, $parse = true ) {
$request = new WPML_TP_API_Request( '/jobs/{job_id}/xliff.json' );
$request->set_params(
array(
'job_id' => $tp_job_id,
'accesskey' => $this->project->get_access_key(),
)
);
$result = $this->client->send_request( $request );
if ( ! $result || empty( $result ) || false === strpos( $result, 'xliff' ) ) {
throw new WPML_TP_API_Exception( 'XLIFF file could not be fetched for tp_job: ' . $tp_job_id, $request );
}
$result = apply_filters( 'wpml_tm_data_from_pro_translation', $result );
if ( ! $parse ) {
return $result;
}
$xliff = @simplexml_load_string( $result );
if ( ! $xliff ) {
throw new WPML_TP_API_Exception( 'XLIFF file could not be parsed.' );
}
return $this->xliff_parser->parse( $xliff );
}
}

View File

@@ -0,0 +1,55 @@
<?php
class WPML_TP_Xliff_Parser {
/**
* @param SimpleXMLElement $xliff
*
* @return WPML_TP_Translation_Collection
*/
public function parse( SimpleXMLElement $xliff ) {
$source_lang = (string) $xliff->file->attributes()->{'source-language'};
$target_lang = (string) $xliff->file->attributes()->{'target-language'};
$translations = array();
foreach ( $xliff->file->body->children() as $node ) {
$translations[] = new WPML_TP_Translation(
(string) $node->attributes()->id,
$this->get_cdata_value( $node, 'source' ),
$this->get_cdata_value( $node, 'target' )
);
}
return new WPML_TP_Translation_Collection(
$translations,
$source_lang,
$target_lang
);
}
/**
* @param SimpleXMLElement $xliff_node
* @param string $field
*
* @return string
*/
protected function get_cdata_value( SimpleXMLElement $xliff_node, $field ) {
$value = '';
if ( isset( $xliff_node->$field->mrk ) ) {
$value = (string) $xliff_node->$field->mrk;
} elseif ( isset( $xliff_node->$field ) ) {
$value = (string) $xliff_node->$field;
}
return self::restore_new_line( $value );
}
/**
* @param string $string
*
* @return string
*/
public static function restore_new_line( $string ) {
return preg_replace( '/<br class="xliff-newline"\s*\/>/i', "\n", $string );
}
}

View File

@@ -0,0 +1,13 @@
<?php
/**
* @author OnTheGo Systems
*/
class WPMLTranslationProxyApiException extends Exception {
public function __construct( $message, $code = 0 ) {
WPML_TranslationProxy_Com_Log::log_error( $message );
parent::__construct( $message, $code );
}
}

View File

@@ -0,0 +1,170 @@
<?php
/**
* Class WPML_TM_CMS_ID
*/
class WPML_TM_CMS_ID extends WPML_TM_Record_User {
private $cms_id_parts_glue = '_';
private $cms_id_parts_fallback_glue = '|||';
/** @var WPML_Translation_Job_Factory $tm_job_factory */
private $job_factory;
/** @var wpdb $wpdb */
private $wpdb;
/**
* WPML_TM_CMS_ID constructor.
*
* @param WPML_TM_Records $tm_records
* @param WPML_Translation_Job_Factory $job_factory
*/
public function __construct( &$tm_records, &$job_factory ) {
parent::__construct( $tm_records );
$this->job_factory = &$job_factory;
$this->wpdb = $this->tm_records->wpdb();
}
/**
* @param int $post_id
* @param string $post_type
* @param string $source_language
* @param string $target_language
*
* @return string
*/
public function build_cms_id( $post_id, $post_type, $source_language, $target_language ) {
$cms_id_parts = array( $post_type, $post_id, $source_language, $target_language );
return implode( $this->cms_id_parts_glue, $cms_id_parts );
}
/**
* Returns the cms_id for a given job
*
* @param int $job_id
*
* @return false|string
*/
function cms_id_from_job_id( $job_id ) {
$original_element_row = $this->wpdb->get_row(
$this->wpdb->prepare(
"SELECT o.element_id,
o.element_type,
o.language_code as source_lang,
i.language_code as target_lang
FROM {$this->wpdb->prefix}icl_translations o
JOIN {$this->wpdb->prefix}icl_translations i
ON i.trid = o.trid
AND i.source_language_code = o.language_code
JOIN {$this->wpdb->prefix}icl_translation_status s
ON s.translation_id = i.translation_id
JOIN {$this->wpdb->prefix}icl_translate_job j
ON j.rid = s.rid
WHERE j.job_id = %d
LIMIT 1",
$job_id
)
);
$type_parts = (bool) $original_element_row === true ? explode( '_', $original_element_row->element_type, 2 ) : false;
return is_array( $type_parts ) && count( $type_parts ) === 2
? $this->build_cms_id( $original_element_row->element_id, end( $type_parts ), $original_element_row->source_lang, $original_element_row->target_lang )
: false;
}
/**
* @param string $cms_id
*
* @return array;
*/
public function parse_cms_id( $cms_id ) {
if ( $this->is_standard_format( $cms_id ) ) {
$parts = array_filter( explode( $this->cms_id_parts_glue, $cms_id ) );
while ( count( $parts ) > 4 ) {
$parts_copy = $parts;
$parts[0] = $parts_copy[0] . $this->cms_id_parts_glue . $parts_copy[1];
unset( $parts_copy[0] );
unset( $parts_copy[1] );
$parts = array_merge( array( $parts[0] ), array_values( array_filter( $parts_copy ) ) );
}
} else {
$parts = explode( $this->cms_id_parts_fallback_glue, $cms_id );
}
return array_pad( array_slice( $parts, 0, 4 ), false, 4 );
}
/**
* @param string $cms_id
* @param bool|TranslationProxy_Service $translation_service
*
* @return int|null translation id for the given cms_id's target
*/
public function get_translation_id( $cms_id, $translation_service = false ) {
list( $post_type, $element_id, , $target_lang ) = $this->parse_cms_id( $cms_id );
$translation = $this->wpdb->get_row(
$this->wpdb->prepare(
"
SELECT t.translation_id, j.job_id, t.element_id
FROM {$this->wpdb->prefix}icl_translations t
JOIN {$this->wpdb->prefix}icl_translations o
ON o.trid = t.trid
AND o.element_type = t.element_type
LEFT JOIN {$this->wpdb->prefix}icl_translation_status st
ON st.translation_id = t.translation_id
LEFT JOIN {$this->wpdb->prefix}icl_translate_job j
ON j.rid = st.rid
WHERE o.element_id=%d
AND t.language_code=%s
AND o.element_type LIKE %s
LIMIT 1",
$element_id,
$target_lang,
'%_' . $post_type
)
);
$translation_id = $this->maybe_cleanup_broken_row( $translation, $translation_service );
if ( $translation_service && ! isset( $translation_id ) && $translation_service ) {
$job_id = $this->job_factory->create_local_post_job( $element_id, $target_lang );
$job = $this->job_factory->get_translation_job( $job_id, false, false, true );
$translation_id = $job ? $job->get_translation_id() : 0;
if ( $translation_id ) {
$this->tm_records->icl_translation_status_by_translation_id( $translation_id )->update(
array(
'status' => ICL_TM_IN_PROGRESS,
'translation_service' => $translation_service->id,
)
);
}
}
return $translation_id;
}
private function maybe_cleanup_broken_row( $translation, $translation_service ) {
if ( $translation
&& ( $translation_id = $translation->translation_id )
&& ! $translation->element_id
&& $translation_service
&& ! $translation->job_id
) {
$this->tm_records->icl_translations_by_translation_id( $translation_id )->delete();
$translation_id = null;
}
return isset( $translation_id ) ? $translation_id : null;
}
/**
* @param $cms_id
*
* @return bool
*/
private function is_standard_format( $cms_id ) {
return count( array_filter( explode( $this->cms_id_parts_fallback_glue, $cms_id ) ) ) < 3;
}
}

View File

@@ -0,0 +1,122 @@
<?php
class WPML_TP_HTTP_Request_Filter {
/**
* @return array filtered response
*/
public function build_request_context( array $request ) {
if ( ! $this->contains_resource( $request ) ) {
$request['headers'] = 'Content-type: application/json';
$request['body'] = wp_json_encode( $request['body'] );
} else {
list( $headers, $body ) = $this->_prepare_multipart_request( $request['body'] );
$request['headers'] = $headers;
$request['body'] = $body;
}
if ( $request['method'] === 'GET' ) {
unset( $request['body'] );
}
return $request;
}
/**
* Checks if a request contains a file resource handle
*
* @param array $request_snippet
*
* @return bool
*/
private function contains_resource( array $request_snippet ) {
foreach ( $request_snippet as $part ) {
if ( is_resource( $part ) === true || ( is_array( $part ) && $this->contains_resource( $part ) ) ) {
return true;
}
}
return false;
}
private function _prepare_multipart_request( $params ) {
$boundary = '----' . microtime();
$header = "Content-Type: multipart/form-data; boundary=$boundary";
$content = self::_add_multipart_contents( $boundary, $params );
$content .= "--$boundary--\r\n";
return array( $header, $content );
}
private function _add_multipart_contents(
$boundary,
$params,
$context = array()
) {
$initial_context = $context;
$content = '';
foreach ( $params as $key => $value ) {
$context = $initial_context;
$context[] = $key;
if ( is_array( $value ) ) {
$content .= self::_add_multipart_contents(
$boundary,
$value,
$context
);
} else {
$pieces = array_slice( $context, 1 );
if ( $pieces ) {
$name = "{$context[0]}[" . implode( '][', $pieces ) . ']';
} else {
$name = "{$context[0]}";
}
$content .= "--$boundary\r\n" . "Content-Disposition: form-data; name=\"$name\"";
if ( is_resource( $value ) ) {
$filename = self::get_file_name( $params, $key );
$content .= "; filename=\"$filename\"\r\n" . "Content-Type: application/octet-stream\r\n\r\n" . gzencode( stream_get_contents( $value ) ) . "\r\n";
} else {
$content .= "\r\n\r\n$value\r\n";
}
}
}
return $content;
}
private function get_file_name( $params, $default = 'file' ) {
$title = isset( $params['title'] ) ? sanitize_title_with_dashes(
strtolower(
filter_var(
$params['title'],
FILTER_SANITIZE_FULL_SPECIAL_CHARS,
FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH
)
)
)
: '';
if ( str_replace( array( '-', '_' ), '', $title ) == '' ) {
$title = $default;
}
$source_language = isset( $params['source_language'] ) ? $params['source_language'] : '';
$target_language = isset( $params['target_language'] ) ? $params['target_language'] : '';
$filename = implode(
'-',
array_filter(
array(
$title,
$source_language,
$target_language,
)
)
);
return $filename . '.xliff.gz';
}
}

View File

@@ -0,0 +1,16 @@
<?php
abstract class WPML_TP_Project_User {
/** @var TranslationProxy_Project $project */
protected $project;
/**
* WPML_TP_Project_User constructor.
*
* @param TranslationProxy_Project $project
*/
public function __construct( &$project ) {
$this->project = &$project;
}
}

View File

@@ -0,0 +1,57 @@
<?php
class WPML_TP_Refresh_Language_Pairs {
const AJAX_ACTION = 'wpml-tp-refresh-language-pairs';
/**
* @var WPML_TP_Project_API
*/
private $tp_api;
/**
* WPML_TP_AJAX constructor.
*
* @param WPML_TP_Project_API $wpml_tp_api
*/
public function __construct( WPML_TP_Project_API $wpml_tp_api ) {
$this->tp_api = $wpml_tp_api;
}
public function add_hooks() {
add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'refresh_language_pairs' ) );
}
public function refresh_language_pairs() {
if ( $this->is_valid_request() ) {
try {
$this->tp_api->refresh_language_pairs();
wp_send_json_success(
array(
'msg' => __( 'Language pairs refreshed', 'wpml-translation-management' ),
)
);
} catch ( Exception $e ) {
wp_send_json_error(
array(
'msg' => __( 'Language pairs not refreshed, please try again', 'wpml-translation-management' ),
)
);
}
} else {
wp_send_json_error(
array(
'msg' => __( 'Invalid Request', 'wpml-translation-management' ),
)
);
}
}
/**
* @return bool
*/
private function is_valid_request() {
return array_key_exists( 'nonce', $_POST ) &&
wp_verify_nonce( filter_var( $_POST['nonce'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ), self::AJAX_ACTION );
}
}

View File

@@ -0,0 +1,119 @@
<?php
class WPML_TP_String_Job extends WPML_WPDB_User {
/** @var WPML_Translation_Basket $basket */
private $basket;
/** @var WPML_Translation_Job_Factory $job_factory */
private $job_factory;
/**
* WPML_TP_String_Job constructor.
*
* @param wpdb $wpdb
* @param WPML_Translation_Basket $basket
* @param WPML_Translation_Job_Factory $job_factory
*/
public function __construct( &$wpdb, &$basket, &$job_factory ) {
parent::__construct( $wpdb );
$this->basket = &$basket;
$this->job_factory = &$job_factory;
}
function send_strings_to_translation_service( $string_ids, $target_language, $translator_id ) {
/** @var WPML_String_Translation $WPML_String_Translation */
global $WPML_String_Translation;
if ( sizeof( $string_ids ) > 0 ) {
$project = $this->basket->get_project();
$strings = array();
$word_count = 0;
$source_language = $this->basket->get_source_language();
foreach ( $string_ids as $string_id ) {
$string_data_query = "SELECT id, context, name, value FROM {$this->wpdb->prefix}icl_strings WHERE id=%d";
$string_data_prepare = $this->wpdb->prepare( $string_data_query, $string_id );
$string_data = $this->wpdb->get_row( $string_data_prepare );
$word_count += $WPML_String_Translation->estimate_word_count( $string_data->value, $source_language );
$strings[] = $string_data;
}
$xliff = new WPML_TM_Xliff_Writer( $this->job_factory );
try {
$tp_job_id = $project->send_to_translation_batch_mode(
$xliff->get_strings_xliff_file(
$strings,
$source_language,
$target_language
),
'String Translations',
'',
'',
$source_language,
$target_language,
$word_count
);
} catch ( Exception $e ) {
return [ 'errors' => [ $e->getMessage() ] ];
}
if ( $tp_job_id ) {
foreach ( $strings as $string_data ) {
$translation_service = TranslationProxy_Service::get_translator_data_from_wpml( $translator_id );
$string_translation_id = icl_add_string_translation(
$string_data->id,
$target_language,
null,
ICL_TM_IN_PROGRESS,
$translation_service['translator_id'],
$translation_service['translation_service'],
TranslationProxy_Batch::update_translation_batch(
$this->basket->get_name()
)
);
if ( $string_translation_id ) {
$data = array(
'rid' => $tp_job_id,
'string_translation_id' => $string_translation_id,
'timestamp' => date( 'Y-m-d H:i:s' ),
'md5' => md5( $string_data->value ),
);
$current_rid = $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT rid FROM {$this->wpdb->prefix}icl_string_status WHERE string_translation_id=%d",
$string_translation_id
)
);
if ( $current_rid ) {
$this->wpdb->update( $this->wpdb->prefix . 'icl_string_status', $data, array( 'rid' => $current_rid ) );
} else {
$this->wpdb->insert( $this->wpdb->prefix . 'icl_string_status', $data ); // insert rid
}
}
}
$data = array(
'rid' => $tp_job_id,
'module' => '',
'origin' => $source_language,
'target' => $target_language,
'status' => ICL_TM_IN_PROGRESS,
);
if ( ! empty( $current_rid ) ) {
$data['ts_status'] = null;
$this->wpdb->update( $this->wpdb->prefix . 'icl_core_status', $data, array( 'rid' => $current_rid ) );
} else {
$this->wpdb->insert( $this->wpdb->prefix . 'icl_core_status', $data );
}
if ( $project->errors && count( $project->errors ) ) {
$tp_job_id['errors'] = $project->errors;
}
return $tp_job_id;
}
}
return 0;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Class WPML_TP_Translator
*/
class WPML_TP_Translator {
/**
* Return translator status array.
*
* @param bool $force
*
* @return array
*/
public function get_icl_translator_status( $force = false ) {
return TranslationProxy_Translator::get_icl_translator_status( $force );
}
}

View File

@@ -0,0 +1,186 @@
<?php
class WPML_Translation_Proxy_Networking {
const API_VERSION = 1.1;
/** @var WP_Http $http */
private $http;
/** @var WPML_TP_Lock $tp_lock */
private $tp_lock;
public function __construct( WP_Http $http, WPML_TP_Lock $tp_lock ) {
$this->http = $http;
$this->tp_lock = $tp_lock;
}
/**
* @param string $url
* @param array $params
* @param string $method
* @param bool|true $has_return_value
* @param bool|true $json_response
* @param bool|true $has_api_response
*
* @return array|mixed|stdClass|string
* @throws WPMLTranslationProxyApiException
*/
public function send_request(
$url,
$params = array(),
$method = 'GET',
$has_return_value = true,
$json_response = true,
$has_api_response = true
) {
if ( $this->tp_lock->is_locked( $url ) ) {
throw new WPMLTranslationProxyApiException( 'Communication with translation proxy is not allowed.' );
}
if ( ! $url ) {
throw new WPMLTranslationProxyApiException( 'Empty target URL given!' );
}
$response = null;
$method = strtoupper( $method );
if ( $params ) {
$url = TranslationProxy_Api::add_parameters_to_url( $url, $params );
if ( 'GET' === $method ) {
$url .= '?' . wpml_http_build_query( $params );
}
}
if ( ! isset( $params['api_version'] ) || ! $params['api_version'] ) {
$params['api_version'] = self::API_VERSION;
}
WPML_TranslationProxy_Com_Log::log_call( $url, $params );
$api_response = $this->call_remote_api( $url, $params, $method, $has_return_value );
if ( $has_return_value ) {
if ( ! isset( $api_response['headers']['content-type'] ) ) {
throw new WPMLTranslationProxyApiException( 'Invalid HTTP response, no content type in header given!' );
}
$content_type = $api_response['headers']['content-type'];
$api_response = $api_response['body'];
$api_response = strpos( $content_type, 'zip' ) !== false ? gzdecode( $api_response ) : $api_response;
WPML_TranslationProxy_Com_Log::log_response( $json_response ? $api_response : 'XLIFF received' );
if ( $json_response ) {
$response = json_decode( $api_response );
if ( $has_api_response ) {
if ( ! $response || ! isset( $response->status->code ) || $response->status->code !== 0 ) {
$exception_message = $this->get_exception_message(
$url,
$method,
$params,
$response
);
if ( isset( $response->status->message ) ) {
$exception_message = '';
if ( isset( $response->status->code ) ) {
$exception_message = '(' . $response->status->code . ') ';
}
$exception_message .= $response->status->message;
}
throw new WPMLTranslationProxyApiException( $exception_message );
}
$response = $response->response;
}
} else {
$response = $api_response;
}
}
return $response;
}
public function get_extra_fields_remote( $project ) {
$params = array(
'accesskey' => $project->access_key,
'api_version' => self::API_VERSION,
'project_id' => $project->id,
);
return TranslationProxy_Api::proxy_request( '/projects/{project_id}/extra_fields.json', $params );
}
/**
* @param string $url
* @param array $params
* @param string $method
* @param bool $has_return_value
*
* @throws \WPMLTranslationProxyApiException
*
* @return null|string
*/
private function call_remote_api(
$url,
$params,
$method,
$has_return_value = true
) {
$context = $this->filter_request_params( $params, $method );
$response = $this->http->request( $url, $context );
if ( ( $has_return_value && (bool) $response === false )
|| is_wp_error( $response )
|| ( isset( $response['response']['code'] ) && $response['response']['code'] > 400 ) ) {
throw new WPMLTranslationProxyApiException(
$this->get_exception_message(
$url,
$method,
$context,
$response
)
);
}
return $response;
}
private function get_exception_message( $url, $method, $context, $response ) {
$sanitized_url = WPML_TranslationProxy_Com_Log::sanitize_url( $url );
$sanitized_context = WPML_TranslationProxy_Com_Log::sanitize_data( $context );
$sanitized_response = WPML_TranslationProxy_Com_Log::sanitize_data( $response );
return 'Cannot communicate with the remote service |'
. ' url: '
. '`'
. $sanitized_url
. '`'
. ' method: '
. '`'
. $method
. '`'
. ' param: '
. '`'
. wp_json_encode( $sanitized_context )
. '`'
. ' response: '
. '`'
. wp_json_encode( $sanitized_response )
. '`';
}
/**
* @param array $params request parameters
* @param string $method HTTP request method
*
* @return array
*/
private function filter_request_params( $params, $method ) {
$request_filter = new WPML_TP_HTTP_Request_Filter();
return $request_filter->build_request_context(
array(
'method' => $method,
'body' => $params,
'sslverify' => true,
'timeout' => 60,
)
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
class WPML_TranslationProxy_Com_Log {
private static $wrapped_class;
/**
* @return WPML_TranslationProxy_Communication_Log
*/
private static function get_wrapped_class_instance() {
if ( null === self::$wrapped_class ) {
global $sitepress;
self::$wrapped_class = new WPML_TranslationProxy_Communication_Log( $sitepress );
}
return self::$wrapped_class;
}
public static function log_call( $url, $params ) {
self::get_wrapped_class_instance()->log_call( $url, $params );
}
public static function get_keys_to_block() {
return self::get_wrapped_class_instance()->get_keys_to_block();
}
public static function log_response( $response ) {
self::get_wrapped_class_instance()->log_response( $response );
}
public static function log_error( $message ) {
self::get_wrapped_class_instance()->log_error( $message );
}
public static function log_xml_rpc( $data ) {
self::get_wrapped_class_instance()->log_xml_rpc( $data );
}
public static function get_log() {
return self::get_wrapped_class_instance()->get_log();
}
public static function clear_log() {
self::get_wrapped_class_instance()->clear_log();
}
public static function is_logging_enabled() {
return self::get_wrapped_class_instance()->is_logging_enabled();
}
/**
* @param string|array|stdClass $params
*
* @return array|stdClass
*/
public static function sanitize_data( $params ) {
return self::get_wrapped_class_instance()->sanitize_data( $params );
}
/**
* @param $url
*
* @return mixed
*/
public static function sanitize_url( $url ) {
return self::get_wrapped_class_instance()->sanitize_url( $url );
}
public static function set_logging_state( $state ) {
self::get_wrapped_class_instance()->set_logging_state( $state );
}
public static function add_com_log_link() {
self::get_wrapped_class_instance()->add_com_log_link();
}
}

View File

@@ -0,0 +1,28 @@
<?php
class WPML_Update_PickUp_Method {
private $sitepress;
public function __construct( $sitepress ) {
$this->sitepress = $sitepress;
}
public function update_pickup_method( $data, $project = false ) {
$method = isset( $data['icl_translation_pickup_method'] ) ? intval( $data['icl_translation_pickup_method'] ) : null;
$iclsettings['translation_pickup_method'] = $method;
$response = 'ok';
try {
if ( $project ) {
$project->set_delivery_method( ICL_PRO_TRANSLATION_PICKUP_XMLRPC == $method ? 'xmlrpc' : 'polling' );
$this->sitepress->save_settings( $iclsettings );
} elseif ( ICL_PRO_TRANSLATION_PICKUP_XMLRPC == $method ) {
$response = 'no-ts';
}
} catch ( RuntimeException $e ) {
$response = 'cant-update';
}
return $response;
}
}

View File

@@ -0,0 +1,8 @@
<?php
class WPML_TP_Lock_Factory {
public function create() {
return new WPML_TP_Lock( new WPML_WP_API() );
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_TP_Lock_Notice_Factory implements IWPML_Backend_Action_Loader {
public function create() {
$tp_lock_factory = new WPML_TP_Lock_Factory();
$notices = wpml_get_admin_notices();
return new WPML_TP_Lock_Notice( $tp_lock_factory->create(), $notices );
}
}

View File

@@ -0,0 +1,36 @@
<?php
class WPML_TP_Lock_Notice implements IWPML_Action {
const NOTICE_GROUP = 'tp-lock';
const NOTICE_LOCKED = 'locked';
/** @var WPML_TP_Lock $tp_lock */
private $tp_lock;
/** @var WPML_Notices $notices */
private $notices;
public function __construct( WPML_TP_Lock $tp_lock, WPML_Notices $notices ) {
$this->tp_lock = $tp_lock;
$this->notices = $notices;
}
public function add_hooks() {
add_action( 'admin_init', array( $this, 'handle_notice' ) );
}
public function handle_notice() {
$locker_reason = $this->tp_lock->get_locker_reason();
if ( (bool) $locker_reason ) {
$text = '<p>' . __( 'Some communications with the translation proxy are locked.', 'wpml-translation-management' ) . '</p>';
$text .= '<p>' . $locker_reason . '</p>';
$notice = $this->notices->create_notice( self::NOTICE_LOCKED, $text, self::NOTICE_GROUP );
$notice->set_css_class_types( 'notice-warning' );
$this->notices->add_notice( $notice );
} else {
$this->notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_LOCKED );
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
class WPML_TP_Lock {
private $lockable_endpoints = array(
'/jobs/{job_id}/xliff.json',
);
/** @var WPML_WP_API $wp_api */
private $wp_api;
public function __construct( WPML_WP_API $wp_api ) {
$this->wp_api = $wp_api;
}
/**
* @param string $url
*
* @return bool
*/
public function is_locked( $url ) {
return $this->get_locker_reason() && $this->is_lockable( $url );
}
/**
* @return string|false
*/
public function get_locker_reason() {
if ( 'test' === $this->wp_api->constant( 'WPML_ENVIRONMENT' ) ) {
return __( 'The constant WPML_ENVIRONMENT is set to "Test".', 'wpml-translation-management' );
}
return false;
}
/**
* @param string $url
*
* @return bool
*/
private function is_lockable( $url ) {
$endpoint = preg_replace( '#^' . OTG_TRANSLATION_PROXY_URL . '#', '', $url, 1 );
return in_array( $endpoint, $this->lockable_endpoints, true );
}
}

View File

@@ -0,0 +1,167 @@
<?php
class WPML_TranslationProxy_Communication_Log {
private $keys_to_block;
private $sitepress;
public function __construct( SitePress $sitepress ) {
$this->keys_to_block = array(
'api_token',
'username',
'api_key',
'sitekey',
'accesskey',
'file',
);
$this->sitepress = $sitepress;
}
public function log_call( $url, $params ) {
$sanitized_params = $this->sanitize_data( $params );
$sanitized_url = $this->sanitize_url( $url );
$this->add_to_log( 'call - ' . $sanitized_url . ' - ' . json_encode( $sanitized_params ) );
}
public function get_keys_to_block() {
return $this->keys_to_block;
}
public function log_response( $response ) {
$this->add_to_log( 'response - ' . $response );
}
public function log_error( $message ) {
$this->add_to_log( 'error - ' . $message );
}
public function log_xml_rpc( $data ) {
$this->add_to_log( 'xml-rpc - ' . json_encode( $data ) );
}
public function get_log() {
return get_option( 'wpml_tp_com_log', '' );
}
public function clear_log() {
$this->save_log( '' );
}
public function is_logging_enabled() {
return $this->sitepress->get_setting( 'tp-com-logging', true );
}
/**
* @param string|array|stdClass $params
*
* @return array|stdClass
*/
public function sanitize_data( $params ) {
$sanitized_params = $params;
if ( is_object( $sanitized_params ) ) {
$sanitized_params = get_object_vars( $sanitized_params );
}
if ( is_array( $sanitized_params ) ) {
foreach ( $sanitized_params as $key => $value ) {
$sanitized_params[ $key ] = $this->sanitize_data_item( $key, $sanitized_params[ $key ] );
}
}
return $sanitized_params;
}
/**
* @param string $key
* @param string|array|stdClass $item
*
* @return string|array|stdClass
*/
private function sanitize_data_item( $key, $item ) {
if ( is_array( $item ) || is_object( $item ) ) {
$item = $this->sanitize_data( $item );
} elseif ( in_array( $key, $this->get_keys_to_block(), true ) ) {
$item = 'UNDISCLOSED';
} elseif ( $this->is_json( $item ) ) {
$item = json_encode( $this->sanitize_data_item( $key, json_decode( $item ) ) );
}
return $item;
}
/**
* @param $url
*
* @return mixed
*/
public function sanitize_url( $url ) {
$original_url_parsed = (string) wpml_parse_url( $url, PHP_URL_QUERY );
parse_str( $original_url_parsed, $original_query_vars );
$sanitized_query_vars = $this->sanitize_data( $original_query_vars );
return add_query_arg( $sanitized_query_vars, $url );
}
public function set_logging_state( $state ) {
$this->sitepress->set_setting( 'tp-com-logging', $state );
$this->sitepress->save_settings();
}
public function add_com_log_link() {
$url = esc_attr( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=com-log' );
?>
<?php
printf(
__(
'For retrieving debug information for communication between your%1$s site and the translation system, use the <a href="%2$s">communication log</a> page.',
'wpml-translation-management'
),
'<br>',
$url
);
?>
</p>
<?php
}
private function now() {
return date( 'm/d/Y h:i:s a', time() );
}
private function add_to_log( $string ) {
if ( $this->is_logging_enabled() ) {
$max_log_length = 10000;
$string = $this->now() . ' - ' . $string;
$log = $this->get_log();
$log .= $string;
$log .= PHP_EOL;
$log_length = strlen( $log );
if ( $log_length > $max_log_length ) {
$log = substr( $log, $log_length - $max_log_length );
}
$this->save_log( $log );
}
}
private function save_log( $log ) {
update_option( 'wpml_tp_com_log', $log, false );
}
/**
* @param mixed $item
*
* @return bool
*/
private function is_json( $item ) {
return is_string( $item ) && json_decode( $item ) && json_last_error() === JSON_ERROR_NONE;
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* @author OnTheGo Systems
*/
class WPML_TP_Extra_Field {
/** @var string */
public $type = 'text';
/** @var string */
public $label;
/** @var string */
public $name;
/** @var array */
public $items;
}

View File

@@ -0,0 +1,77 @@
<?php
namespace WPML\TM\TranslationProxy\Services;
use WPML\TM\TranslationProxy\Services\Project\Manager;
class Authorization {
/** @var Storage */
private $storage;
/** @var Manager */
private $projectManager;
/**
* @param Storage $storage
* @param Manager $projectManager
*/
public function __construct( Storage $storage, Manager $projectManager ) {
$this->storage = $storage;
$this->projectManager = $projectManager;
}
/**
* @param \stdClass $credentials
*
* @throws \RuntimeException
* @throws \WPML_TP_API_Exception
*/
public function authorize( \stdClass $credentials ) {
$service = $this->getCurrentService();
$service->custom_fields_data = $credentials;
$project = $this->projectManager->create( $service );
$this->storage->setCurrentService( $service );
do_action( 'wpml_tm_translation_service_authorized', $service, $project );
}
/**
* @param \stdClass $credentials
*
* @throws \WPML_TP_API_Exception
*/
public function updateCredentials( \stdClass $credentials ) {
$service = $this->getCurrentService();
$this->projectManager->updateCredentials( $service, $credentials );
$service->custom_fields_data = $credentials;
$this->storage->setCurrentService( $service );
}
/**
* @throws \RuntimeException
*/
public function deauthorize() {
$service = $this->getCurrentService();
$service->custom_fields_data = null;
$this->storage->setCurrentService( $service );
do_action( 'wpml_tp_service_de_authorized', $service );
}
/**
* @return \stdClass
* @throws \RuntimeException
*/
private function getCurrentService() {
$service = $this->storage->getCurrentService();
if ( (bool) $service === false ) {
throw new \RuntimeException( 'Tried to authenticate a service, but no service is active!' );
}
return $service;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace WPML\TM\TranslationProxy\Services;
use WPML\TM\TranslationProxy\Services\Project\Manager;
use function WPML\Container\make;
class AuthorizationFactory {
/**
* @return Authorization
* @throws \Auryn\InjectionException
*/
public function create() {
$projectManager = make(
Manager::class,
[
':projectApi' => wpml_tm_get_tp_project_api(),
]
);
return make( Authorization::class, [ ':projectManager' => $projectManager ] );
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace WPML\TM\TranslationProxy\Services\Project;
class Manager {
/** @var \WPML_TP_Project_API */
private $projectApi;
/** @var Storage */
private $projectStorage;
/** @var SiteDetails */
private $siteDetails;
/**
* @param \WPML_TP_Project_API $projectApi
* @param Storage $projectStorage
* @param SiteDetails $siteDetails
*/
public function __construct(
\WPML_TP_Project_API $projectApi,
Storage $projectStorage,
SiteDetails $siteDetails
) {
$this->projectApi = $projectApi;
$this->projectStorage = $projectStorage;
$this->siteDetails = $siteDetails;
}
/**
* @param \stdClass $service
*
* @return Project
* @throws \WPML_TP_API_Exception
*/
public function create( \stdClass $service ) {
$project = $this->projectStorage->getByService( $service ) ?: $this->fromTranslationProxy( $service );
$project->extraFields = $this->projectApi->get_extra_fields( $project );
$this->projectStorage->save( $service, $project );
do_action( 'wpml_tp_project_created', $service, $project, $this->projectStorage->getProjects()->toArray() );
return $project;
}
/**
* @param \stdClass $service
* @param \stdClass $credentials
*
* @return Project|null
* @throws \WPML_TP_API_Exception
*/
public function updateCredentials( \stdClass $service, \stdClass $credentials ) {
$project = $this->projectStorage->getByService( $service );
if ( ! $project ) {
throw new \RuntimeException( 'Project does not exist' );
}
$this->projectApi->update_project_credentials( $project, $credentials );
$project->extraFields = $this->projectApi->get_extra_fields( $project );
$this->projectStorage->save( $this->createServiceWithNewCredentials( $service, $credentials ), $project );
return $project;
}
/**
* @param \stdClass $service
*
* @return Project
* @throws \WPML_TP_API_Exception
*/
private function fromTranslationProxy( \stdClass $service ) {
$response = $this->projectApi->create_project( $service, $this->siteDetails );
return Project::fromResponse( $response->project );
}
/**
* @param \stdClass $service
* @param \stdClass $credentials
*
* @return \stdClass
*/
private function createServiceWithNewCredentials( \stdClass $service, \stdClass $credentials ) {
$updatedService = clone $service;
$updatedService->custom_fields_data = $credentials;
return $updatedService;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace WPML\TM\TranslationProxy\Services\Project;
class Project {
/** @var int */
public $id;
/** @var string */
public $accessKey;
/** @var string */
public $tsId;
/** @var string */
public $tsAccessKey;
/** @var \stdClass */
public $extraFields;
/**
* @return array
*/
public function toArray() {
return [
'id' => $this->id,
'access_key' => $this->accessKey,
'ts_id' => $this->tsId,
'ts_access_key' => $this->tsAccessKey,
'extra_fields' => $this->extraFields,
];
}
/**
* @param array $data
*
* @return Project
*/
public static function fromArray( array $data ) {
$project = new Project();
$project->id = $data['id'];
$project->accessKey = $data['access_key'];
$project->tsId = $data['ts_id'];
$project->tsAccessKey = $data['ts_access_key'];
$project->extraFields = $data['extra_fields'];
return $project;
}
public static function fromResponse( \stdClass $response ) {
$project = new Project();
$project->id = $response->id;
$project->accessKey = $response->accesskey;
$project->tsId = $response->ts_id;
$project->tsAccessKey = $response->ts_accesskey;
return $project;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace WPML\TM\TranslationProxy\Services\Project;
class SiteDetails {
/** @var \SitePress */
private $sitepress;
/**
* @param \SitePress $sitepress
*/
public function __construct( \SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
/**
* @return string
*/
public function getDeliveryMethod() {
return (int) $this->sitepress->get_setting( 'translation_pickup_method' ) === ICL_PRO_TRANSLATION_PICKUP_XMLRPC
? 'xmlrpc'
: 'polling';
}
/**
* @return array
*/
public function getBlogInfo() {
return [
'url' => get_option( 'siteurl' ),
'name' => get_option( 'blogname' ),
'description' => get_option( 'blogdescription' ),
];
}
/**
* @return array
*/
public function getClientData() {
$current_user = wp_get_current_user();
return [
'email' => $current_user->user_email,
'name' => $current_user->display_name,
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace WPML\TM\TranslationProxy\Services\Project;
use WPML\Collect\Support\Collection;
class Storage {
/** @var \SitePress */
private $sitepress;
/**
* @param \SitePress $sitepress
*/
public function __construct( \SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
/**
* @param \stdClass $service
*
* @return Project|null
*/
public function getByService( \stdClass $service ) {
return $this->getProjects()->get( \TranslationProxy_Project::generate_service_index( $service ) );
}
/**
* @param \stdClass $service
* @param Project $project
*/
public function save( \stdClass $service, Project $project ) {
$index = \TranslationProxy_Project::generate_service_index( $service );
$this->sitepress->set_setting(
'icl_translation_projects',
$this->getProjects()->put( $index, $project )->map(
function ( Project $project ) {
return $project->toArray();
}
)->toArray(),
true
);
}
/**
* @return Collection
*/
public function getProjects() {
$projects = $this->sitepress->get_setting( 'icl_translation_projects', [] );
if ( ! is_array( $projects ) ) {
$projects = [];
}
return \wpml_collect( $projects )->map(
function ( array $rawProject ) {
return Project::fromArray( $rawProject );
}
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\TM\TranslationProxy\Services;
class Storage {
/** @var \SitePress $sitepress */
private $sitepress;
/**
* @param \SitePress $sitepress
*/
public function __construct( \SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
/**
* Gets the current translation service
*
* @return bool|\stdClass
*/
public function getCurrentService() {
return $this->sitepress->get_setting( 'translation_service' );
}
/**
* Saves the input service as the current translation service setting.
*
* @param \stdClass $service
*/
public function setCurrentService( \stdClass $service ) {
do_action( 'wpml_tm_before_set_translation_service', $service );
$this->sitepress->set_setting( 'translation_service', $service, true );
}
}

View File

@@ -0,0 +1,61 @@
<?php
class WPML_TP_Sync_Ajax_Handler {
const AJAX_ACTION = 'wpml-tp-sync-job-states';
/** @var WPML_TP_Sync_Jobs */
private $tp_sync;
/** @var WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up */
private $wpml_tm_last_picked_up;
/**
* WPML_TP_Sync_Jobs constructor.
*
* @param WPML_TP_Sync_Jobs $tp_sync
* @param WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up
*/
public function __construct( WPML_TP_Sync_Jobs $tp_sync, WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up ) {
$this->tp_sync = $tp_sync;
$this->wpml_tm_last_picked_up = $wpml_tm_last_picked_up;
}
public function add_hooks() {
add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'handle' ) );
}
public function handle() {
try {
if ( isset( $_REQUEST['update_last_picked_up'] ) ) {
$this->wpml_tm_last_picked_up->set();
}
$jobs = $this->tp_sync->sync();
do_action( 'wpml_tm_empty_mail_queue' );
wp_send_json_success( $jobs->map( array( $this, 'map_job_to_result' ) ) );
return true;
} catch ( Exception $e ) {
wp_send_json_error( $e->getMessage(), 503 );
return false;
}
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return array
*/
public function map_job_to_result( WPML_TM_Job_Entity $job ) {
return array(
'id' => $job->get_id(),
'type' => $job->get_type(),
'status' => $job->get_status(),
'hasCompletedTranslation' => $job->has_completed_translation(),
'needsUpdate' => $job->does_need_update(),
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_TM_Sync_Installer_Wrapper {
/**
* @return bool
*/
public function is_wpml_registered() {
return (bool) WP_Installer::instance()->get_site_key( 'wpml' );
}
}

View File

@@ -0,0 +1,62 @@
<?php
class WPML_TM_Sync_Jobs_Revision {
/** @var WPML_TM_Jobs_Repository */
private $jobs_repository;
/** @var WPML_TP_Jobs_API */
private $tp_api;
/**
* WPML_TM_Sync_Jobs_Revision constructor.
*
* @param WPML_TM_Jobs_Repository $jobs_repository
* @param WPML_TP_Jobs_API $tp_api
*/
public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Jobs_API $tp_api ) {
$this->jobs_repository = $jobs_repository;
$this->tp_api = $tp_api;
}
/**
* @return WPML_TM_Job_Entity[]
* @throws WPML_TP_API_Exception
*/
public function sync() {
$result = array();
$tp_statuses_of_revised_jobs = $this->tp_api->get_revised_jobs();
if ( $tp_statuses_of_revised_jobs ) {
$revised_jobs = array();
foreach ( $tp_statuses_of_revised_jobs as $tp_job_status ) {
$revised_jobs[ $tp_job_status->get_tp_id() ] = $tp_job_status->get_revision();
}
$job_search = new WPML_TM_Jobs_Search_Params(
array(
'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE,
'tp_id' => array_keys( $revised_jobs ),
)
);
/** @var WPML_TM_Job_Entity $job */
foreach ( $this->jobs_repository->get( $job_search ) as $job ) {
if ( isset( $revised_jobs[ $job->get_tp_id() ] ) && $job->get_revision() < $revised_jobs[ $job->get_tp_id() ] ) {
$job->set_status( WPML_TP_Job_States::map_tp_state_to_local( WPML_TP_Job_States::TRANSLATION_READY ) );
$job->set_revision( $revised_jobs[ $job->get_tp_id() ] );
$result[] = $job;
do_action( 'wpml_tm_revised_job_notification', $job->get_id() );
}
}
}
return $result;
}
}

View File

@@ -0,0 +1,79 @@
<?php
class WPML_TM_Sync_Jobs_Status {
/** @var WPML_TM_Jobs_Repository */
private $jobs_repository;
/** @var WPML_TP_Jobs_API */
private $tp_api;
/**
* WPML_TM_Sync_Jobs_Status constructor.
*
* @param WPML_TM_Jobs_Repository $jobs_repository
* @param WPML_TP_Jobs_API $tp_api
*/
public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Jobs_API $tp_api ) {
$this->jobs_repository = $jobs_repository;
$this->tp_api = $tp_api;
}
/**
* @return WPML_TM_Jobs_Collection
* @throws WPML_TP_API_Exception
*/
public function sync() {
return $this->update_tp_state_of_jobs(
$this->jobs_repository->get(
new WPML_TM_Jobs_Search_Params(
array(
'status' => array( ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ),
'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE,
)
)
)
);
}
/**
* @param WPML_TM_Jobs_Collection $jobs
*
* @return WPML_TM_Jobs_Collection
* @throws WPML_TP_API_Exception
*/
private function update_tp_state_of_jobs( WPML_TM_Jobs_Collection $jobs ) {
$tp_ids = $this->extract_tp_id_from_jobs( $jobs );
if ( $tp_ids ) {
$tp_statuses = $this->tp_api->get_jobs_statuses( $tp_ids );
foreach ( $tp_statuses as $job_status ) {
$job = $jobs->get_by_tp_id( $job_status->get_tp_id() );
if ( $job ) {
$job->set_status( WPML_TP_Job_States::map_tp_state_to_local( $job_status->get_status() ) );
$job->set_ts_status( $job_status->get_ts_status() );
if ( WPML_TP_Job_States::CANCELLED === $job_status->get_status() ) {
do_action( 'wpml_tm_canceled_job_notification', $job );
}
}
}
}
return $jobs;
}
/**
* @param WPML_TM_Jobs_Collection $jobs
*
* @return array
*/
private function extract_tp_id_from_jobs( WPML_TM_Jobs_Collection $jobs ) {
$tp_ids = array();
foreach ( $jobs as $job ) {
$tp_ids[] = $job->get_tp_id();
}
return $tp_ids;
}
}

View File

@@ -0,0 +1,44 @@
<?php
class WPML_TP_Sync_Jobs {
/** @var WPML_TM_Sync_Jobs_Status */
private $jobs_status_sync;
/** @var WPML_TM_Sync_Jobs_Revision */
private $jobs_revision_sync;
/** @var WPML_TP_Sync_Update_Job */
private $update_job;
/**
* WPML_TP_Sync_Jobs constructor.
*
* @param WPML_TM_Sync_Jobs_Status $jobs_status_sync
* @param WPML_TM_Sync_Jobs_Revision $jobs_revision_sync
* @param WPML_TP_Sync_Update_Job $update_job
*/
public function __construct(
WPML_TM_Sync_Jobs_Status $jobs_status_sync,
WPML_TM_Sync_Jobs_Revision $jobs_revision_sync,
WPML_TP_Sync_Update_Job $update_job
) {
$this->jobs_status_sync = $jobs_status_sync;
$this->jobs_revision_sync = $jobs_revision_sync;
$this->update_job = $update_job;
}
/**
* @return WPML_TM_Jobs_Collection
* @throws WPML_TP_API_Exception
*/
public function sync() {
return new WPML_TM_Jobs_Collection(
$this->jobs_status_sync
->sync()
->append( $this->jobs_revision_sync->sync() )
->filter_by_status( array( ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR ), true )
->map( array( $this->update_job, 'update_state' ) )
);
}
}

View File

@@ -0,0 +1,147 @@
<?php
use WPML\FP\Relation;
use \WPML\LIB\WP\Post;
class WPML_TP_Sync_Update_Job {
private $strategies = array(
WPML_TM_Job_Entity::POST_TYPE => 'update_post_job',
WPML_TM_Job_Entity::STRING_TYPE => 'update_string_job',
WPML_TM_Job_Entity::PACKAGE_TYPE => 'update_post_job',
WPML_TM_Job_Entity::STRING_BATCH => 'update_post_job',
);
/** @var wpdb */
private $wpdb;
/** @var SitePress */
private $sitepress;
/**
* @param wpdb $wpdb
*/
public function __construct( wpdb $wpdb, SitePress $sitepress ) {
$this->wpdb = $wpdb;
$this->sitepress = $sitepress;
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TM_Job_Entity
*/
public function update_state( WPML_TM_Job_Entity $job ) {
if ( ! array_key_exists( $job->get_type(), $this->strategies ) ) {
return $job;
}
$method = $this->strategies[ $job->get_type() ];
return call_user_func( array( $this, $method ), $job );
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TM_Job_Entity
*/
private function update_string_job( WPML_TM_Job_Entity $job ) {
if ( $job->get_tp_id() ) {
$this->wpdb->update(
$this->wpdb->prefix . 'icl_core_status',
array(
'status' => $job->get_status(),
'tp_revision' => $job->get_revision(),
'ts_status' => $this->get_ts_status_in_ts_format( $job ),
),
array( 'rid' => $job->get_tp_id() )
);
}
$data = array(
'status' => $job->get_status(),
);
if ( ICL_TM_NOT_TRANSLATED === $job->get_status() ) {
$data['translator_id'] = null;
}
$this->wpdb->update(
$this->wpdb->prefix . 'icl_string_translations',
$data,
array( 'id' => $job->get_id() )
);
icl_update_string_status( $job->get_original_element_id() );
return $job;
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TM_Job_Entity
*/
private function update_post_job( WPML_TM_Job_Entity $job ) {
$job_id = $job->get_id();
if ( $job->get_status() === ICL_TM_NOT_TRANSLATED ) {
$prev_status = $this->get_job_prev_status( $job_id );
if ( $prev_status && Relation::propEq( 'needs_update', '1', $prev_status ) ) {
$this->wpdb->update( $this->wpdb->prefix . 'icl_translation_status', $prev_status, [ 'rid' => $job_id ] );
$job->set_needs_update( true );
return $job;
}
}
$this->wpdb->update(
$this->wpdb->prefix . 'icl_translation_status',
array(
'status' => $job->get_status(),
'tp_revision' => $job->get_revision(),
'ts_status' => $this->get_ts_status_in_ts_format( $job ),
),
array( 'rid' => $job_id )
);
if (
ICL_TM_NOT_TRANSLATED === $job->get_status()
&& ( $post_type = Post::getType( $job->get_original_element_id() ) )
) {
$this->sitepress->delete_orphan_element(
$job->get_original_element_id(),
'post_' . $post_type,
$job->get_target_language()
);
}
return $job;
}
private function get_job_prev_status( $job_id ) {
$previous_state = $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT _prevstate
FROM {$this->wpdb->prefix}icl_translation_status
WHERE rid=%d
LIMIT 1",
$job_id
)
);
return unserialize( $previous_state );
}
/**
* In the db, we store the exact json format that we get from TS. It includes an extra ts_status key
*
* @param WPML_TM_Job_Entity $job
*
* @return string
*/
private function get_ts_status_in_ts_format( WPML_TM_Job_Entity $job ) {
$ts_status = $job->get_ts_status();
return $ts_status ? wp_json_encode( array( 'ts_status' => json_decode( (string) $ts_status ) ) ) : null;
}
}

View File

@@ -0,0 +1,15 @@
<?php
class WPML_TP_Sync_Orphan_Jobs_Factory {
/**
* @return WPML_TP_Sync_Orphan_Jobs
*/
public function create() {
global $wpdb, $sitepress;
return new WPML_TP_Sync_Orphan_Jobs(
wpml_tm_get_jobs_repository(),
new WPML_TP_Sync_Update_Job( $wpdb, $sitepress )
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
class WPML_TP_Sync_Orphan_Jobs {
/** @var WPML_TM_Jobs_Repository */
private $jobs_repository;
/** @var WPML_TP_Sync_Update_Job */
private $update_job;
/**
* @param WPML_TM_Jobs_Repository $jobs_repository
* @param WPML_TP_Sync_Update_Job $update_job
*/
public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Sync_Update_Job $update_job ) {
$this->jobs_repository = $jobs_repository;
$this->update_job = $update_job;
}
/**
* @return WPML_TM_Jobs_Collection
*/
public function cancel_orphans() {
$params = new WPML_TM_Jobs_Search_Params(
array(
'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE,
'tp_id' => 0,
'status' => array( ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ),
)
);
return $this->jobs_repository->get( $params )->map( array( $this, 'cancel_job' ), true );
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TM_Job_Entity
*/
public function cancel_job( WPML_TM_Job_Entity $job ) {
$job->set_status( ICL_TM_NOT_TRANSLATED );
return $this->update_job->update_state( $job );
}
}

View File

@@ -0,0 +1,36 @@
<?php
class WPML_TP_Apply_Single_Job {
/** @var WPML_TP_Translations_Repository */
private $translations_repository;
/** @var WPML_TP_Apply_Translation_Strategies */
private $strategy_dispatcher;
/**
* @param WPML_TP_Translations_Repository $translations_repository
* @param WPML_TP_Apply_Translation_Strategies $strategy_dispatcher
*/
public function __construct(
WPML_TP_Translations_Repository $translations_repository,
WPML_TP_Apply_Translation_Strategies $strategy_dispatcher
) {
$this->translations_repository = $translations_repository;
$this->strategy_dispatcher = $strategy_dispatcher;
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TM_Job_Entity
* @throws WPML_TP_API_Exception
*/
public function apply( WPML_TM_Job_Entity $job ) {
$translations = $this->translations_repository->get_job_translations_by_job_entity( $job );
$this->strategy_dispatcher->get( $job )->apply( $job, $translations );
$job->set_status( ICL_TM_COMPLETE );
return $job;
}
}

View File

@@ -0,0 +1,69 @@
<?php
class WPML_TP_Apply_Translation_Post_Strategy implements WPML_TP_Apply_Translation_Strategy {
/** @var WPML_TP_Jobs_API */
private $jobs_api;
/** @var wpdb */
private $wpdb;
/**
* @param WPML_TP_Jobs_API $jobs_api
*/
public function __construct( WPML_TP_Jobs_API $jobs_api ) {
$this->jobs_api = $jobs_api;
global $wpdb;
$this->wpdb = $wpdb;
}
/**
* @param WPML_TM_Job_Entity $job
* @param WPML_TP_Translation_Collection $translations
*
* @return void
* @throws WPML_TP_API_Exception
*/
public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations ) {
if ( ! $job instanceof WPML_TM_Post_Job_Entity ) {
throw new InvalidArgumentException( 'A job must have post type' );
}
kses_remove_filters();
wpml_tm_save_data( $this->build_data( $job, $translations ) );
kses_init();
$this->jobs_api->update_job_state( $job, 'delivered' );
}
/**
* @param WPML_TM_Job_Entity $job
* @param WPML_TP_Translation_Collection $translations
*
* @return array
*/
private function build_data( WPML_TM_Post_Job_Entity $job, WPML_TP_Translation_Collection $translations ) {
$data = array(
'job_id' => $job->get_translate_job_id(),
'fields' => array(),
'complete' => 1
);
/** @var WPML_TP_Translation $translation */
foreach ( $translations as $translation ) {
foreach ( $job->get_elements() as $element ) {
if ( $element->get_type() === $translation->get_field() ) {
$data['fields'][ $element->get_type() ] = array(
'data' => $translation->get_target(),
'finished' => 1,
'tid' => $element->get_id(),
'field_type' => $element->get_type(),
'format' => $element->get_format()
);
}
}
}
return $data;
}
}

View File

@@ -0,0 +1,62 @@
<?php
class WPML_TP_Apply_Translation_Strategies {
/** @var WPML_TP_Apply_Translation_Post_Strategy */
private $post_strategy;
/** @var WPML_TP_Apply_Translation_String_Strategy */
private $string_strategy;
/** @var wpdb */
private $wpdb;
/**
* @param wpdb $wpdb
*/
public function __construct( wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
/**
* @param WPML_TM_Job_Entity $job
*
* @return WPML_TP_Apply_Translation_Strategy
*/
public function get( WPML_TM_Job_Entity $job ) {
switch ( $job->get_type() ) {
case WPML_TM_Job_Entity::STRING_TYPE:
return $this->get_string_strategy();
case WPML_TM_Job_Entity::POST_TYPE:
case WPML_TM_Job_Entity::PACKAGE_TYPE:
case WPML_TM_Job_Entity::STRING_BATCH:
return $this->get_post_strategy();
default:
throw new InvalidArgumentException( 'Job type: ' . $job->get_type() . ' is not supported' );
}
}
/**
* @return WPML_TP_Apply_Translation_Post_Strategy
*/
private function get_post_strategy() {
if ( ! $this->post_strategy ) {
$this->post_strategy = new WPML_TP_Apply_Translation_Post_Strategy( wpml_tm_get_tp_jobs_api() );
}
return $this->post_strategy;
}
/**
* @return WPML_TP_Apply_Translation_String_Strategy
*/
private function get_string_strategy() {
if ( ! $this->string_strategy ) {
$this->string_strategy = new WPML_TP_Apply_Translation_String_Strategy(
wpml_tm_get_tp_jobs_api(),
$this->wpdb
);
}
return $this->string_strategy;
}
}

View File

@@ -0,0 +1,11 @@
<?php
interface WPML_TP_Apply_Translation_Strategy {
/**
* @param WPML_TM_Job_Entity $job
* @param WPML_TP_Translation_Collection $translations
*
* @return void
*/
public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations );
}

View File

@@ -0,0 +1,62 @@
<?php
class WPML_TP_Apply_Translation_String_Strategy implements WPML_TP_Apply_Translation_Strategy {
/** @var WPML_TP_Jobs_API */
private $jobs_api;
/** @var wpdb */
private $wpdb;
/**
* @param WPML_TP_Jobs_API $jobs_api
* @param wpdb $wpdb
*/
public function __construct( WPML_TP_Jobs_API $jobs_api, wpdb $wpdb ) {
$this->jobs_api = $jobs_api;
$this->wpdb = $wpdb;
}
/**
* @param WPML_TM_Job_Entity $job
* @param WPML_TP_Translation_Collection $translations
*
* @return void
* @throws WPML_TP_API_Exception
*/
public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations ) {
if ( ! icl_translation_add_string_translation(
$job->get_tp_id(),
$this->map_translations_to_legacy_array( $translations ),
$translations->get_target_language()
)
) {
throw new WPML_TP_API_Exception( 'Could not apply string translation!' );
}
$this->update_local_job_status( $job, ICL_TM_COMPLETE );
$this->jobs_api->update_job_state( $job, 'delivered' );
}
/**
* @param WPML_TM_Job_Entity $job
* @param int $status
*/
private function update_local_job_status( WPML_TM_Job_Entity $job, $status ) {
$this->wpdb->update(
$this->wpdb->prefix . 'icl_core_status',
array( 'status' => $status ),
array( 'id' => $job->get_id() )
);
}
private function map_translations_to_legacy_array( WPML_TP_Translation_Collection $translations ) {
$result = array();
/** @var WPML_TP_Translation $translation */
foreach ( $translations as $translation ) {
$result[ $translation->get_field() ] = $translation->get_target();
}
return $result;
}
}

View File

@@ -0,0 +1,141 @@
<?php
class WPML_TP_Apply_Translations {
/** @var WPML_TM_Jobs_Repository */
private $jobs_repository;
/** @var WPML_TP_Apply_Single_Job */
private $apply_single_job;
/** @var WPML_TP_Sync_Jobs */
private $tp_sync;
/**
* @param WPML_TM_Jobs_Repository $jobs_repository
* @param WPML_TP_Apply_Single_Job $apply_single_job
* @param WPML_TP_Sync_Jobs $tp_sync
*/
public function __construct(
WPML_TM_Jobs_Repository $jobs_repository,
WPML_TP_Apply_Single_Job $apply_single_job,
WPML_TP_Sync_Jobs $tp_sync
) {
$this->jobs_repository = $jobs_repository;
$this->apply_single_job = $apply_single_job;
$this->tp_sync = $tp_sync;
}
/**
* @param array $params
*
* @return WPML_TM_Jobs_Collection
* @throws WPML_TP_API_Exception
*/
public function apply( array $params ) {
$jobs = $this->get_jobs( $params );
if ( $this->has_in_progress_jobs( $jobs ) ) {
$jobs = $this->sync_jobs( $jobs );
}
$cancelled_jobs = $jobs->filter_by_status( ICL_TM_NOT_TRANSLATED );
$downloaded_jobs = new WPML_TM_Jobs_Collection(
$jobs->filter_by_status( ICL_TM_TRANSLATION_READY_TO_DOWNLOAD )
->map( array( $this->apply_single_job, 'apply' ) )
);
return $downloaded_jobs->append( $cancelled_jobs );
}
/**
* @param WPML_TM_Jobs_Collection $jobs
*
* @return bool
*/
private function has_in_progress_jobs( WPML_TM_Jobs_Collection $jobs ) {
return count( $jobs->filter_by_status( ICL_TM_IN_PROGRESS ) ) > 0;
}
/**
* @param array $params
*
* @return array|WPML_TM_Jobs_Collection
*/
private function get_jobs( array $params ) {
if ( $params ) {
if ( isset( $params['original_element_id'], $params['element_type'] ) ) {
$jobs = $this->get_jobs_by_original_element( $params['original_element_id'], $params['element_type'] );
} else {
$jobs = $this->get_jobs_by_ids( $params );
}
} else {
$jobs = $this->get_all_ready_jobs();
}
return $jobs;
}
/**
* @param int $original_element_id
* @param string $element_type
*
* @return WPML_TM_Jobs_Collection
*/
private function get_jobs_by_original_element( $original_element_id, $element_type ) {
$params = new WPML_TM_Jobs_Search_Params();
$params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE );
$params->set_original_element_id( $original_element_id );
$params->set_job_types( $element_type );
return $this->jobs_repository->get( $params );
}
/**
* @param array $params
*
* @return WPML_TM_Jobs_Collection
*/
private function get_jobs_by_ids( array $params ) {
$jobs = array();
foreach ( $params as $param ) {
$jobs[] = $this->jobs_repository->get_job( $param['id'], $param['type'] );
}
return new WPML_TM_Jobs_Collection( $jobs );
}
/**
* @return WPML_TM_Jobs_Collection
*/
private function get_all_ready_jobs() {
return $this->jobs_repository->get(
new WPML_TM_Jobs_Search_Params(
array(
'status' => array( ICL_TM_TRANSLATION_READY_TO_DOWNLOAD ),
'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE,
)
)
);
}
/**
* @param WPML_TM_Jobs_Collection $jobs
*
* @return WPML_TM_Jobs_Collection
* @throws WPML_TP_API_Exception
*/
private function sync_jobs( WPML_TM_Jobs_Collection $jobs ) {
$synced_jobs = $this->tp_sync->sync();
/** @var WPML_TM_Job_Entity $job */
foreach ( $jobs as $job ) {
foreach ( $synced_jobs as $synced_job ) {
if ( $job->is_equal( $synced_job ) ) {
$job->set_status( $synced_job->get_status() );
break;
}
}
}
return $jobs;
}
}

View File

@@ -0,0 +1,55 @@
<?php
use WPML\FP\Fns;
use function WPML\FP\invoke;
class WPML_TP_Translation_Collection implements IteratorAggregate {
/** @var WPML_TP_Translation[] */
private $translations;
/** @var string */
private $source_language;
/** @var string */
private $target_language;
/**
* @param WPML_TP_Translation[] $translations
* @param string $source_language
* @param string $target_language
*/
public function __construct( array $translations, $source_language, $target_language ) {
$this->translations = $translations;
$this->source_language = $source_language;
$this->target_language = $target_language;
}
/**
* @return string
*/
public function get_source_language() {
return $this->source_language;
}
/**
* @return string
*/
public function get_target_language() {
return $this->target_language;
}
public function getIterator() {
return new ArrayIterator( $this->translations );
}
/**
* @return array
*/
public function to_array() {
return [
'source_language' => $this->source_language,
'target_language' => $this->target_language,
'translations' => Fns::map( invoke( 'to_array' ), $this->translations ),
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
class WPML_TP_Translation {
/** @var string */
private $field;
/** @var string */
private $source;
/** @var string */
private $target;
/**
* @param string $field
* @param string $source
* @param string $target
*/
public function __construct( $field, $source, $target ) {
$this->field = $field;
$this->source = $source;
$this->target = $target;
}
/**
* @return string
*/
public function get_field() {
return $this->field;
}
/**
* @return string
*/
public function get_source() {
return $this->source;
}
/**
* @return string
*/
public function get_target() {
return $this->target;
}
public function to_array() {
return get_object_vars( $this );
}
}

View File

@@ -0,0 +1,69 @@
<?php
class WPML_TP_Translations_Repository {
/** @var WPML_TP_XLIFF_API */
private $xliff_api;
/** @var WPML_TM_Jobs_Repository */
private $jobs_repository;
/**
* @param WPML_TP_XLIFF_API $xliff_api
* @param WPML_TM_Jobs_Repository $jobs_repository
*/
public function __construct( WPML_TP_XLIFF_API $xliff_api, WPML_TM_Jobs_Repository $jobs_repository ) {
$this->xliff_api = $xliff_api;
$this->jobs_repository = $jobs_repository;
}
/**
* @param int $job_id
* @param int $job_type
* @param bool $parse When true, it returns the parsed translation, otherwise, it returns the raw XLIFF.
*
* @return WPML_TP_Translation_Collection|string
* @throws WPML_TP_API_Exception|InvalidArgumentException
*/
public function get_job_translations( $job_id, $job_type, $parse = true ) {
$job = $this->jobs_repository->get_job( $job_id, $job_type );
if ( ! $job ) {
throw new InvalidArgumentException( 'Cannot find job' );
}
return $this->get_job_translations_by_job_entity( $job, $parse );
}
/**
* @param WPML_TM_Job_Entity $job
* @param bool $parse When true, it returns the parsed translation, otherwise, it returns the raw XLIFF.
*
* @return WPML_TP_Translation_Collection|string
* @throws WPML_TP_API_Exception
*/
public function get_job_translations_by_job_entity( WPML_TM_Job_Entity $job, $parse = true ) {
$correct_states = array( ICL_TM_TRANSLATION_READY_TO_DOWNLOAD, ICL_TM_COMPLETE );
if ( ! in_array( $job->get_status(), $correct_states, true ) ) {
throw new InvalidArgumentException( 'Job\'s translation is not ready.' );
}
if ( ! $job->get_tp_id() ) {
throw new InvalidArgumentException( 'This is only a local job.' );
}
$translations = $this->xliff_api->get_remote_translations( $job->get_tp_id(), $parse );
if ( $parse ) {
/**
* It filters translations coming from the Translation Proxy.
*
* @param \WPML_TP_Translation_Collection $translations
* @param \WPML_TM_Job_Entity $job
*/
$translations = apply_filters( 'wpml_tm_proxy_translations', $translations, $job );
}
return $translations;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @author OnTheGo Systems
*/
class WPML_TP_Extra_Field_Display {
private $fields_with_items = array( 'select', 'radio', 'checkbox' );
/**
* WPML_TP_Extra_Field_Display constructor.
*/
public function __construct() {
}
public function render( $field ) {
if ( $this->must_render( $field ) ) {
$field_id = 'wpml-tp-extra-field-' . $field->name;
$row = '<tr>';
$row .= '<th scope="row">';
$row .= '<label for="' . esc_attr( $field_id ) . '">' . esc_html( $field->label ) . '</label>';
$row .= '</th>';
$row .= '<td>';
switch ( $field->type ) {
case 'select':
$row .= '<select id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field->name ) . '">';
foreach ( $field->items as $id => $name ) {
$row .= '<option value="' . esc_attr( $id ) . '">' . esc_html( $name ) . '</option>';
}
$row .= '</select>';
break;
case 'textarea':
$row .= '<textarea id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field->name ) . '"></textarea>';
break;
case 'radio':
case 'checkbox':
$row .= '<ol>';
foreach ( $field->items as $id => $name ) {
$row .= '<li>';
$row .= '<input id="' . esc_attr( $field_id . '-' . $id ) . '" type="' . esc_attr( $field->type ) . '" name="' . esc_attr( $field->name ) . '" value="' . esc_attr( $id ) . '"> ';
$row .= '<label for="' . esc_attr( $field_id . '-' . $id ) . '">' . esc_html( $name ) . '</label>';
$row .= '</li>';
}
$row .= '</ol>';
break;
case 'text':
default:
$type = null !== $field->type ? $field->type : 'text';
$row .= '<input id="' . esc_attr( $field_id ) . '" type="' . esc_attr( $type ) . '" name="' . esc_attr( $field->name ) . '">';
break;
}
$row .= '</td>';
$row .= '</tr>';
return $row;
}
return '';
}
/**
* @param $field
*
* @return bool
*/
private function must_render( $field ) {
$must_render = isset( $field->type ) && $field->type;
if ( $must_render && in_array( $field->type, $this->fields_with_items, true ) ) {
$must_render = isset( $field->items ) && $field->items;
}
return $must_render;
}
}