first commit
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_API_Hook_Copy_Post_To_Language
|
||||
*/
|
||||
class WPML_API_Hook_Copy_Post_To_Language implements IWPML_Action {
|
||||
|
||||
/** @var WPML_Post_Duplication $post_duplication */
|
||||
private $post_duplication;
|
||||
|
||||
public function __construct( WPML_Post_Duplication $post_duplication ) {
|
||||
$this->post_duplication = $post_duplication;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_copy_post_to_language', array( $this, 'copy_post_to_language' ), 10, 3 );
|
||||
}
|
||||
|
||||
public function copy_post_to_language( $post_id, $target_language, $mark_as_duplicate ) {
|
||||
$duplicate_post_id = $this->post_duplication->make_duplicate( $post_id, $target_language );
|
||||
|
||||
if( ! $mark_as_duplicate ) {
|
||||
delete_post_meta( $duplicate_post_id, '_icl_lang_duplicate_of' );
|
||||
}
|
||||
|
||||
return $duplicate_post_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_API_Hook_Links
|
||||
*
|
||||
* This class provides various links by hooks
|
||||
*/
|
||||
class WPML_API_Hook_Links implements IWPML_Action {
|
||||
|
||||
const POST_TRANSLATION_SETTINGS_PRIORITY = 10;
|
||||
const LINK_TO_TRANSLATION_PRIORITY = 9;
|
||||
|
||||
/** @var WPML_Post_Status_Display_Factory */
|
||||
private $post_status_display_factory;
|
||||
|
||||
public function __construct(
|
||||
WPML_Post_Status_Display_Factory $post_status_display_factory
|
||||
) {
|
||||
$this->post_status_display_factory = $post_status_display_factory;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter(
|
||||
'wpml_get_post_translation_settings_link',
|
||||
array(
|
||||
$this,
|
||||
'get_post_translation_settings_link',
|
||||
),
|
||||
self::POST_TRANSLATION_SETTINGS_PRIORITY,
|
||||
1
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'wpml_get_link_to_edit_translation',
|
||||
array(
|
||||
$this,
|
||||
'get_link_to_edit_translation',
|
||||
),
|
||||
self::LINK_TO_TRANSLATION_PRIORITY,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
public function get_post_translation_settings_link( $link ) {
|
||||
return admin_url( 'admin.php?page=' . WPML_PLUGIN_FOLDER . '/menu/translation-options.php#icl_custom_posts_sync_options' );
|
||||
}
|
||||
|
||||
public function get_link_to_edit_translation( $link, $post_id, $lang ) {
|
||||
$status_display = $this->post_status_display_factory->create();
|
||||
$status_data = $status_display->get_status_data( $post_id, $lang );
|
||||
|
||||
$status_link = $status_data[1];
|
||||
$trid = $status_data[2];
|
||||
$css_class = $status_data[3];
|
||||
|
||||
return apply_filters( 'wpml_link_to_translation', $status_link, $post_id, $lang, $trid, $css_class );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
class WPML_API_Hook_Permalink implements IWPML_Action {
|
||||
|
||||
/** @var WPML_URL_Converter $url_converter */
|
||||
private $url_converter;
|
||||
|
||||
/** @var IWPML_Resolve_Object_Url $absolute_resolver */
|
||||
private $absolute_resolver;
|
||||
|
||||
public function __construct( WPML_URL_Converter $url_converter, IWPML_Resolve_Object_Url $absolute_resolver ) {
|
||||
$this->url_converter = $url_converter;
|
||||
$this->absolute_resolver = $absolute_resolver;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_permalink', array( $this, 'wpml_permalink_filter' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param null|string $lang
|
||||
* @param bool $absolute_url If `true`, WPML will try to resolve the object behind the URL
|
||||
* and try to find the matching translation's URL.
|
||||
* WARNING: This is a heavy process which could lead to performance hit.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function wpml_permalink_filter( $url, $lang = null, $absolute_url = false ) {
|
||||
if ( $absolute_url ) {
|
||||
$new_url = $this->absolute_resolver->resolve_object_url( $url, $lang );
|
||||
|
||||
if ( $new_url ) {
|
||||
$url = $new_url;
|
||||
}
|
||||
} else {
|
||||
$url = $this->url_converter->convert_url( $url, $lang );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class WPML_API_Hook_Sync_Custom_Fields implements IWPML_Action {
|
||||
|
||||
/** @var WPML_Sync_Custom_Fields $sync_custom_fields */
|
||||
private $sync_custom_fields;
|
||||
|
||||
public function __construct( WPML_Sync_Custom_Fields $sync_custom_fields ) {
|
||||
$this->sync_custom_fields = $sync_custom_fields;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_sync_custom_field', array( $this, 'sync_custom_field' ), 10, 2 );
|
||||
add_action( 'wpml_sync_all_custom_fields', array( $this, 'sync_all_custom_fields' ), 10, 1 );
|
||||
}
|
||||
|
||||
public function sync_custom_field( $post_id, $custom_field_name ) {
|
||||
$this->sync_custom_fields->sync_to_translations( $post_id, $custom_field_name );
|
||||
}
|
||||
|
||||
public function sync_all_custom_fields( $post_id ) {
|
||||
$this->sync_custom_fields->sync_all_custom_fields( $post_id );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_API_Hook_Translation_Element implements IWPML_Action {
|
||||
private $flags_factory;
|
||||
private $sitepress;
|
||||
private $translation_element_factory;
|
||||
|
||||
/**
|
||||
* WPML_API_Hook_Post constructor.
|
||||
*
|
||||
* @param SitePress $sitepress
|
||||
* @param WPML_Translation_Element_Factory $translation_element_factory
|
||||
* @param WPML_Flags_Factory $flags_factory
|
||||
*/
|
||||
public function __construct(
|
||||
SitePress $sitepress,
|
||||
WPML_Translation_Element_Factory $translation_element_factory,
|
||||
WPML_Flags_Factory $flags_factory
|
||||
) {
|
||||
$this->sitepress = $sitepress;
|
||||
$this->translation_element_factory = $translation_element_factory;
|
||||
$this->flags_factory = $flags_factory;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
/**
|
||||
* Use this filter to obtain the language flag URL of a given post
|
||||
*
|
||||
* @param string $default
|
||||
* @param int $element_id
|
||||
* @param string $element_type any of `WPML_Translation_Element_Factory::ELEMENT_TYPE_POST`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_TERM`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_MENU`
|
||||
*/
|
||||
add_filter( 'wpml_post_language_flag_url', array( $this, 'get_post_language_flag_url' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $default
|
||||
* @param int $element_id
|
||||
* @param string $element_type any of `WPML_Translation_Element_Factory::ELEMENT_TYPE_POST`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_TERM`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_MENU`
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_post_language_flag_url(
|
||||
$default,
|
||||
$element_id,
|
||||
$element_type = WPML_Translation_Element_Factory::ELEMENT_TYPE_POST
|
||||
) {
|
||||
if ( ! $element_id ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$wpml_post = $this->translation_element_factory->create( $element_id, $element_type );
|
||||
$flag = $this->flags_factory->create();
|
||||
|
||||
return $flag->get_flag_url( $wpml_post->get_language_code() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class WPML_API_Hook_Translation_Mode implements IWPML_Action {
|
||||
|
||||
const OPTION_KEY = 'custom_posts_sync_option';
|
||||
|
||||
/** Allowed modes */
|
||||
const DO_NOT_TRANSLATE = 'do_not_translate';
|
||||
const TRANSLATE = 'translate';
|
||||
const DISPLAY_AS_TRANSLATED = 'display_as_translated';
|
||||
|
||||
/** @var WPML_Settings_Helper $settings */
|
||||
private $settings;
|
||||
|
||||
public function __construct( WPML_Settings_Helper $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
if ( is_admin() ) {
|
||||
add_action( 'wpml_set_translation_mode_for_post_type', array( $this, 'set_mode_for_post_type' ), 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $post_type
|
||||
* @param string $translation_mode any of
|
||||
* `WPML_API_Hook_Translation_Mode::DO_NOT_TRANSLATE`,
|
||||
* `WPML_API_Hook_Translation_Mode::TRANSLATE`,
|
||||
* `WPML_API_Hook_Translation_Mode::DISPLAY_AS_TRANSLATED`
|
||||
*/
|
||||
public function set_mode_for_post_type( $post_type, $translation_mode ) {
|
||||
switch ( $translation_mode ) {
|
||||
case self::DO_NOT_TRANSLATE:
|
||||
$this->settings->set_post_type_not_translatable( $post_type );
|
||||
break;
|
||||
|
||||
case self::TRANSLATE:
|
||||
$this->settings->set_post_type_translatable( $post_type );
|
||||
break;
|
||||
|
||||
case self::DISPLAY_AS_TRANSLATED:
|
||||
$this->settings->set_post_type_display_as_translated( $post_type );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class WPML_API_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
|
||||
|
||||
public function create() {
|
||||
global $wpdb, $sitepress, $wpml_post_translations, $wpml_url_converter;
|
||||
|
||||
$hooks = array();
|
||||
|
||||
$hooks[] = new WPML_API_Hook_Sync_Custom_Fields(
|
||||
new WPML_Sync_Custom_Fields(
|
||||
new WPML_Translation_Element_Factory( $sitepress ),
|
||||
$sitepress->get_custom_fields_translation_settings( WPML_COPY_CUSTOM_FIELD )
|
||||
)
|
||||
);
|
||||
|
||||
$hooks[] = new WPML_API_Hook_Links( new WPML_Post_Status_Display_Factory( $sitepress ) );
|
||||
|
||||
$hooks[] = new WPML_API_Hook_Translation_Element(
|
||||
$sitepress,
|
||||
new WPML_Translation_Element_Factory( $sitepress ),
|
||||
new WPML_Flags_Factory( $wpdb )
|
||||
);
|
||||
|
||||
$hooks[] = new WPML_API_Hook_Translation_Mode( new WPML_Settings_Helper( $wpml_post_translations, $sitepress ) );
|
||||
|
||||
$hooks[] = new WPML_API_Hook_Copy_Post_To_Language( new WPML_Post_Duplication( $wpdb, $sitepress ) );
|
||||
|
||||
$url_resolver_factory = new WPML_Resolve_Object_Url_Helper_Factory();
|
||||
$absolute_resolver = $url_resolver_factory->create( WPML_Resolve_Object_Url_Helper_Factory::ABSOLUTE_URL_RESOLVER );
|
||||
$hooks[] = new WPML_API_Hook_Permalink( $wpml_url_converter, $absolute_resolver );
|
||||
|
||||
return $hooks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_TM_API_Hook_Links
|
||||
*
|
||||
* This class provides various links by hooks
|
||||
*/
|
||||
class WPML_TM_API_Hook_Links implements IWPML_Action {
|
||||
|
||||
public function add_hooks() {
|
||||
// TODO: Use WPML_API_Hook_Links::POST_TRANSLATION_SETTINGS_PRIORITY + 1 instead of the hardcoded 11.
|
||||
// It's done this way right now so there's no potential for an error if TM is updated before Core for
|
||||
// the minor 3.9.1 release
|
||||
add_filter(
|
||||
'wpml_get_post_translation_settings_link',
|
||||
array(
|
||||
$this,
|
||||
'get_post_translation_settings_link',
|
||||
),
|
||||
11,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public function get_post_translation_settings_link( $link ) {
|
||||
return admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS . '&sm=mcsetup#icl_custom_posts_sync_options' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_API_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
|
||||
|
||||
public function create() {
|
||||
$hooks = array();
|
||||
|
||||
$hooks[] = new WPML_TM_API_Hook_Links();
|
||||
|
||||
return $hooks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\Rest;
|
||||
|
||||
use \WP_REST_Request;
|
||||
|
||||
class Adaptor extends \WPML_REST_Base {
|
||||
|
||||
/** @var ITarget $target */
|
||||
private $target;
|
||||
|
||||
public function set_target( ITarget $target ) {
|
||||
$this->target = $target;
|
||||
$this->namespace = $target->get_namespace();
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$routes = $this->target->get_routes();
|
||||
foreach ( $routes as $route ) {
|
||||
$this->register_route( $route['route'], $route['args'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return $this->target->get_allowed_capabilities( $request );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\Rest;
|
||||
|
||||
use IWPML_Action;
|
||||
|
||||
abstract class Base implements ITarget, IWPML_Action {
|
||||
|
||||
/** @var Adaptor */
|
||||
private $adaptor;
|
||||
|
||||
public function __construct( Adaptor $adaptor ) {
|
||||
$this->adaptor = $adaptor;
|
||||
$adaptor->set_target( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_namespace();
|
||||
|
||||
public function add_hooks() {
|
||||
$this->adaptor->add_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getStringType() {
|
||||
return [
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'WPML_REST_Arguments_Sanitation::string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getIntType() {
|
||||
return [
|
||||
'type' => 'int',
|
||||
'validate_callback' => 'WPML_REST_Arguments_Validation::integer',
|
||||
'sanitize_callback' => 'WPML_REST_Arguments_Sanitation::integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\REST;
|
||||
|
||||
abstract class Base extends \WPML\Rest\Base {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace() {
|
||||
return 'wpml/tm/v1';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\REST\XMLConfig\Custom;
|
||||
|
||||
use WP_REST_Request;
|
||||
|
||||
class Actions extends \WPML_REST_Base {
|
||||
/** @var array<string> */
|
||||
private $capabilities = [ 'manage_options' ];
|
||||
|
||||
/**
|
||||
* @var \WPML_Custom_XML
|
||||
*/
|
||||
private $custom_xml;
|
||||
/**
|
||||
* @var \WPML_XML_Config_Validate
|
||||
*/
|
||||
private $validate;
|
||||
|
||||
public function __construct( \WPML_Custom_XML $custom_xml, \WPML_XML_Config_Validate $validate ) {
|
||||
parent::__construct();
|
||||
$this->custom_xml = $custom_xml;
|
||||
$this->validate = $validate;
|
||||
}
|
||||
|
||||
|
||||
function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
function register_routes() {
|
||||
parent::register_route(
|
||||
'/custom-xml-config',
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $this, 'read_content' ],
|
||||
]
|
||||
);
|
||||
parent::register_route(
|
||||
'/custom-xml-config',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'update_content' ],
|
||||
]
|
||||
);
|
||||
parent::register_route(
|
||||
'/custom-xml-config/validate',
|
||||
[
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'validate_content' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* REST
|
||||
*
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_content( WP_REST_Request $request ) {
|
||||
$content = $request->get_param( 'content' );
|
||||
|
||||
$this->custom_xml->set( $content, false );
|
||||
\WPML_Config::load_config_run();
|
||||
|
||||
return $this->custom_xml->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* REST
|
||||
*
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return \LibXMLError[]
|
||||
*/
|
||||
public function validate_content( WP_REST_Request $request ) {
|
||||
$content = $request->get_param( 'content' );
|
||||
|
||||
if ( ! $this->validate->from_string( $content ) ) {
|
||||
return $this->validate->get_errors();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* REST
|
||||
*/
|
||||
public function read_content() {
|
||||
return $this->custom_xml->get();
|
||||
}
|
||||
|
||||
function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return $this->capabilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\REST\XMLConfig\Custom;
|
||||
|
||||
|
||||
class Factory extends \WPML_REST_Factory_Loader {
|
||||
public function create() {
|
||||
return new Actions( new \WPML_Custom_XML(), new \WPML_XML_Config_Validate( \WPML_Config::PATH_TO_XSD ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\REST;
|
||||
|
||||
use IWPML_Deferred_Action_Loader;
|
||||
use IWPML_REST_Action_Loader;
|
||||
use WPML\TM\ATE\REST\Retry;
|
||||
use WPML\TM\ATE\REST\Sync;
|
||||
use \WPML\TM\ATE\REST\FixJob;
|
||||
use WPML\TM\ATE\REST\Download;
|
||||
use WPML\TM\ATE\REST\PublicReceive;
|
||||
use function WPML\Container\make;
|
||||
|
||||
class FactoryLoader implements IWPML_REST_Action_Loader, IWPML_Deferred_Action_Loader {
|
||||
|
||||
const REST_API_INIT_ACTION = 'rest_api_init';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_load_action() {
|
||||
return self::REST_API_INIT_ACTION;
|
||||
}
|
||||
|
||||
public function create() {
|
||||
return [
|
||||
Sync::class => make( Sync::class ),
|
||||
Download::class => make( Download::class ),
|
||||
Retry::class => make( Retry::class ),
|
||||
PublicReceive::class => make( PublicReceive::class ),
|
||||
FixJob::class => make( FixJob::class ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\Rest;
|
||||
|
||||
interface ITarget {
|
||||
|
||||
function get_routes();
|
||||
function get_allowed_capabilities( \WP_REST_Request $request );
|
||||
function get_namespace();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace WPML\TM\ATE\Factories;
|
||||
|
||||
class Proxy extends \WPML_REST_Factory_Loader {
|
||||
/**
|
||||
* @return \WPML\TM\ATE\Proxy
|
||||
*/
|
||||
public function create() {
|
||||
$endpoints = new \WPML_TM_ATE_AMS_Endpoints();
|
||||
|
||||
return new \WPML\TM\ATE\Proxy( $endpoints );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE;
|
||||
|
||||
class Proxy extends \WPML_REST_Base {
|
||||
/**
|
||||
* @var \WPML_TM_ATE_AMS_Endpoints
|
||||
*/
|
||||
private $endpoints;
|
||||
|
||||
public function __construct( \WPML_TM_ATE_AMS_Endpoints $endpoints ) {
|
||||
parent::__construct( 'wpml/ate/v1' );
|
||||
|
||||
$this->endpoints = $endpoints;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
parent::register_route(
|
||||
'/ate/proxy',
|
||||
array(
|
||||
'methods' => \WP_REST_Server::ALLMETHODS,
|
||||
'callback' => [ $this, 'proxy' ],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_args( \WP_REST_Request $request ) {
|
||||
$request_params = $this->get_request_params( $request );
|
||||
|
||||
$url = $request_params['url'];
|
||||
$headers = $request_params['headers'];
|
||||
$query = $request_params['query'];
|
||||
$content_type = $request_params['content_type'];
|
||||
|
||||
if ( $content_type ) {
|
||||
if ( ! $headers ) {
|
||||
$headers = [];
|
||||
}
|
||||
$headers[] = 'Content-Type: ' . $content_type;
|
||||
}
|
||||
|
||||
$args = [
|
||||
'method' => $request_params['method'],
|
||||
'headers' => $headers,
|
||||
];
|
||||
|
||||
$body = $request_params['body'];
|
||||
if ( $body ) {
|
||||
$args['body'] = $body;
|
||||
}
|
||||
|
||||
return [ $url, $query, $args, $content_type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
private function validate_request( \WP_REST_Request $request ) {
|
||||
if ( ! $this->get_request_params( $request ) ) {
|
||||
return new \WP_Error( 'endpoint_without_parameters', 'Endpoint called with no parameters.', [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
list( $url, , $args ) = $this->get_args( $request );
|
||||
|
||||
$has_all_required_parameters = $url && $args['method'] && $args['headers'];
|
||||
if ( ! $has_all_required_parameters ) {
|
||||
return new \WP_Error( 'missing_required_parameters', 'Required parameters missing.', [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
if ( \strtolower( $request->get_method() ) !== \strtolower( $args['method'] ) ) {
|
||||
return new \WP_Error( 'invalid_method', 'Invalid method.', [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
$ateBaseUrl = $this->endpoints->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_ATE );
|
||||
$amsBaseUrl = $this->endpoints->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS );
|
||||
|
||||
if ( \strpos( \strtolower( $url ), $ateBaseUrl ) !== 0 && \strpos( \strtolower( $url ), $amsBaseUrl ) !== 0 ) {
|
||||
return new \WP_Error( 'invalid_url', 'Invalid URL.', [ 'status' => 400 ] );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*/
|
||||
public function proxy( \WP_REST_Request $request ) {
|
||||
list( $url, $params, $args, $content_type ) = $this->get_args( $request );
|
||||
|
||||
$status_code = 200;
|
||||
$status_message = 'OK';
|
||||
|
||||
$validation = $this->validate_request( $request );
|
||||
if ( \is_wp_error( $validation ) ) {
|
||||
$status_code = $validation->get_error_data()['status'];
|
||||
$status_message = $validation->get_error_message();
|
||||
$response_body = '';
|
||||
} else {
|
||||
|
||||
if ( \is_array( $params ) ) {
|
||||
$params = \http_build_query( $params );
|
||||
}
|
||||
|
||||
$endpoint = \http_build_url( $url, [ 'query' => $params ] );
|
||||
|
||||
$response = \wp_remote_request( $endpoint, $args );
|
||||
|
||||
$response_body = \wp_remote_retrieve_body( $response );
|
||||
}
|
||||
|
||||
$protocol = ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' );
|
||||
if ( 200 === $status_code ) {
|
||||
header( "{$protocol} {$status_code} {$status_message}" );
|
||||
} else {
|
||||
header( "Status: ${status_code} ${status_message}" );
|
||||
}
|
||||
header( "Content-Type: {$content_type}" );
|
||||
|
||||
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $response_body;
|
||||
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$this->break_the_default_response_flow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return string[]|string
|
||||
*/
|
||||
public function get_allowed_capabilities( \WP_REST_Request $request ) {
|
||||
return [ \WPML_Manage_Translations_Role::CAPABILITY, 'manage_options' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_request_params( \WP_REST_Request $request ) {
|
||||
$params = [
|
||||
'url' => null,
|
||||
'method' => null,
|
||||
'query' => null,
|
||||
'headers' => null,
|
||||
'body' => null,
|
||||
'content_type' => 'application/json',
|
||||
];
|
||||
|
||||
if ( $request->get_params() ) {
|
||||
$params = \array_merge( $params, $request->get_params() );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function break_the_default_response_flow() {
|
||||
$shortcut = function () {
|
||||
return function () {
|
||||
die();
|
||||
};
|
||||
|
||||
};
|
||||
$die_handlers = [
|
||||
'wp_die_ajax_handler',
|
||||
'wp_die_json_handler',
|
||||
'wp_die_jsonp_handler',
|
||||
'wp_die_xmlrpc_handler',
|
||||
'wp_die_xml_handler',
|
||||
'wp_die_handler',
|
||||
];
|
||||
foreach ( $die_handlers as $die_handler ) {
|
||||
\add_filter( $die_handler, $shortcut, 10 );
|
||||
}
|
||||
|
||||
\wp_die();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use WPML\API\Sanitize;
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*
|
||||
* The following method can be used as REST arguments sanitation callback
|
||||
*/
|
||||
class WPML_REST_Arguments_Sanitation {
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function boolean( $value ) {
|
||||
/**
|
||||
* `FILTER_VALIDATE_BOOLEAN` returns `NULL` if not valid, but in all other cases, it sanitizes the value
|
||||
*/
|
||||
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function integer( $value ) {
|
||||
return (int) self::float( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function float( $value ) {
|
||||
return (float) filter_var( $value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function string( $value ) {
|
||||
return Sanitize::string( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function url( $value ) {
|
||||
return filter_var( $value, FILTER_SANITIZE_URL );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function email( $value ) {
|
||||
return filter_var( $value, FILTER_SANITIZE_EMAIL );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static function array_of_integers( $value ) {
|
||||
return array_map( 'intval', $value );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*
|
||||
* The following method can be used as REST arguments validation callback
|
||||
*/
|
||||
class WPML_REST_Arguments_Validation {
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function boolean( $value ) {
|
||||
return null !== filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function integer( $value ) {
|
||||
return false !== filter_var( $value, FILTER_VALIDATE_INT );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function float( $value ) {
|
||||
return false !== filter_var( $value, FILTER_VALIDATE_FLOAT );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function url( $value ) {
|
||||
return false !== filter_var( $value, FILTER_VALIDATE_URL );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function email( $value ) {
|
||||
return false !== filter_var( $value, FILTER_VALIDATE_EMAIL );
|
||||
}
|
||||
|
||||
/**
|
||||
*@param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function is_array( $value ) {
|
||||
return is_array( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
static function date( $value ) {
|
||||
try {
|
||||
$d = new DateTime( $value );
|
||||
|
||||
return $d && $d->format( 'Y-m-d' ) == $value;
|
||||
} catch ( Exception $e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_REST_Base
|
||||
*
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
abstract class WPML_REST_Base {
|
||||
const CAPABILITY_EXTERNAL = 'external';
|
||||
|
||||
const REST_NAMESPACE = 'wpml/v1';
|
||||
/**
|
||||
* @var null
|
||||
*/
|
||||
protected $namespace;
|
||||
|
||||
/**
|
||||
* WPML_REST_Base constructor.
|
||||
*
|
||||
* @param null $namespace Defaults to `\WPML_REST_Base::REST_NAMESPACE`.
|
||||
*/
|
||||
public function __construct( $namespace = null ) {
|
||||
if ( ! $namespace ) {
|
||||
$namespace = self::REST_NAMESPACE;
|
||||
}
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
abstract public function add_hooks();
|
||||
|
||||
public function validate_permission( WP_REST_Request $request ) {
|
||||
$user_can = $this->user_has_matching_capabilities( $request );
|
||||
|
||||
if ( ! $user_can ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nonce = $this->get_nonce( $request );
|
||||
|
||||
return $user_can && wp_verify_nonce( $nonce, 'wp_rest' );
|
||||
}
|
||||
|
||||
abstract public function get_allowed_capabilities( WP_REST_Request $request );
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param array $args
|
||||
*/
|
||||
protected function register_route( $route, array $args ) {
|
||||
$args = $this->ensure_permission( $args );
|
||||
|
||||
register_rest_route( $this->namespace, $route, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function ensure_permission( array $args ) {
|
||||
if ( ! array_key_exists( 'permission_callback', $args ) || ! $args['permission_callback'] ) {
|
||||
$args['permission_callback'] = array( $this, 'validate_permission' );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function user_has_matching_capabilities( WP_REST_Request $request ) {
|
||||
$capabilities = $this->get_allowed_capabilities( $request );
|
||||
|
||||
$user_can = false;
|
||||
if ( self::CAPABILITY_EXTERNAL === $capabilities ) {
|
||||
$user_can = true;
|
||||
} elseif ( is_string( $capabilities ) ) {
|
||||
$user_can = current_user_can( $capabilities );
|
||||
} elseif ( is_array( $capabilities ) ) {
|
||||
foreach ( $capabilities as $capability ) {
|
||||
$user_can = $user_can || current_user_can( $capability );
|
||||
}
|
||||
}
|
||||
|
||||
return $user_can;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
private function get_nonce( WP_REST_Request $request ) {
|
||||
$nonce = $request->get_header( 'x_wp_nonce' );
|
||||
if ( ! $nonce ) {
|
||||
$nonce = $request->get_param( '_wpnonce' );
|
||||
}
|
||||
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_REST_Extend_Args_Factory implements IWPML_REST_Action_Loader {
|
||||
/**
|
||||
* @return IWPML_Action|IWPML_Action[]|null
|
||||
*/
|
||||
public function create() {
|
||||
global $sitepress;
|
||||
|
||||
return new WPML_REST_Extend_Args( $sitepress );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_REST_Extend_Args implements IWPML_Action {
|
||||
|
||||
const REST_LANGUAGE_ARGUMENT = 'wpml_language';
|
||||
|
||||
/** @var \SitePress $sitepress */
|
||||
private $sitepress;
|
||||
|
||||
/** @var string $current_language_backup */
|
||||
private $current_language_backup;
|
||||
|
||||
public function __construct( SitePress $sitepress ) {
|
||||
$this->sitepress = $sitepress;
|
||||
}
|
||||
|
||||
function add_hooks() {
|
||||
add_filter( 'rest_endpoints', array( $this, 'rest_endpoints' ) );
|
||||
add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 );
|
||||
add_filter( 'rest_request_after_callbacks', array( $this, 'rest_request_after_callbacks' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the `wpml_language` argument (optional) to all REST calls with arguments.
|
||||
*
|
||||
* @param array $endpoints
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rest_endpoints( array $endpoints ) {
|
||||
$valid_language_codes = $this->get_active_language_codes();
|
||||
|
||||
foreach ( $endpoints as $route => &$endpoint ) {
|
||||
foreach ( $endpoint as $key => &$data ) {
|
||||
if ( is_numeric( $key ) ) {
|
||||
$data['args'][ self::REST_LANGUAGE_ARGUMENT ] = array(
|
||||
'type' => 'string',
|
||||
'description' => "WPML's language code",
|
||||
'required' => false,
|
||||
'enum' => $valid_language_codes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `wpml_language` is provided, backups the current language, then switch to the provided one.
|
||||
*
|
||||
* @param \WP_REST_Response|array|mixed $response
|
||||
* @param \WP_REST_Server|array|mixed $rest_server
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function rest_request_before_callbacks( $response, $rest_server, $request ) {
|
||||
$this->current_language_backup = null;
|
||||
$current_language = $this->sitepress->get_current_language();
|
||||
$rest_language = $request->get_param( self::REST_LANGUAGE_ARGUMENT );
|
||||
|
||||
if ( $rest_language && $rest_language !== $current_language ) {
|
||||
$this->current_language_backup = $current_language;
|
||||
$this->sitepress->switch_lang( $rest_language );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restore the backup language, if set.
|
||||
*
|
||||
* @param \WP_REST_Response|array|mixed $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function rest_request_after_callbacks( $response ) {
|
||||
if ( $this->current_language_backup ) {
|
||||
$this->sitepress->switch_lang( $this->current_language_backup );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_active_language_codes() {
|
||||
return array_keys( $this->sitepress->get_active_languages() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
abstract class WPML_REST_Factory_Loader implements IWPML_REST_Action_Loader, IWPML_Deferred_Action_Loader {
|
||||
const REST_API_INIT_ACTION = 'rest_api_init';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_load_action() {
|
||||
return self::REST_API_INIT_ACTION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_Rest {
|
||||
private $http;
|
||||
|
||||
/**
|
||||
* WPML_Rest constructor.
|
||||
*
|
||||
* @param WP_Http $http
|
||||
*/
|
||||
public function __construct( WP_Http $http ) {
|
||||
$this->http = $http;
|
||||
}
|
||||
|
||||
public function is_available() {
|
||||
return function_exists( 'rest_get_server' );
|
||||
}
|
||||
|
||||
public function is_rest_request() {
|
||||
return defined( 'REST_REQUEST' ) && REST_REQUEST;
|
||||
}
|
||||
|
||||
public function has_registered_routes() {
|
||||
return (bool) rest_get_server()->get_routes();
|
||||
}
|
||||
|
||||
public function has_discovered_routes() {
|
||||
return (bool) $this->get_discovered_routes();
|
||||
}
|
||||
|
||||
private function get_discovered_routes() {
|
||||
$url = $this->get_discovery_url();
|
||||
$response = $this->http->get( $url );
|
||||
$body = json_decode( $response['body'], true );
|
||||
|
||||
return array_key_exists( 'routes', $body ) ? $body['routes'] : array();
|
||||
}
|
||||
|
||||
public function get_discovery_url() {
|
||||
$url_prefix = 'wp-json';
|
||||
if ( function_exists( 'rest_get_url_prefix' ) ) {
|
||||
$url_prefix = rest_get_url_prefix();
|
||||
}
|
||||
|
||||
return untrailingslashit( trailingslashit( get_site_url() ) . $url_prefix );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_Apply_TP_Translation_Factory extends WPML_REST_Factory_Loader {
|
||||
/**
|
||||
* @return WPML_TM_REST_Apply_TP_Translation
|
||||
*/
|
||||
public function create() {
|
||||
global $wpdb;
|
||||
|
||||
return new WPML_TM_REST_Apply_TP_Translation(
|
||||
new WPML_TP_Apply_Translations(
|
||||
wpml_tm_get_jobs_repository(),
|
||||
new WPML_TP_Apply_Single_Job(
|
||||
wpml_tm_get_tp_translations_repository(),
|
||||
new WPML_TP_Apply_Translation_Strategies( $wpdb )
|
||||
),
|
||||
wpml_tm_get_tp_sync_jobs()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_Apply_TP_Translation extends WPML_REST_Base {
|
||||
/** @var WPML_TP_Apply_Translations */
|
||||
private $apply_translations;
|
||||
|
||||
public function __construct( WPML_TP_Apply_Translations $apply_translations ) {
|
||||
parent::__construct( 'wpml/tm/v1' );
|
||||
|
||||
$this->apply_translations = $apply_translations;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
parent::register_route(
|
||||
'/tp/apply-translations',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'apply_translations' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WP_Error|int|array
|
||||
*/
|
||||
public function apply_translations( WP_REST_Request $request ) {
|
||||
try {
|
||||
$params = $request->get_json_params();
|
||||
|
||||
if ( $params ) {
|
||||
if ( ! isset( $params['original_element_id'] ) ) {
|
||||
$params = array_filter( $params, array( $this, 'validate_job' ) );
|
||||
if ( ! $params ) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->apply_translations->apply( $params )->map( array( $this, 'map_jobs_to_array' ) );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 400, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return array( WPML_Manage_Translations_Role::CAPABILITY, WPML_Translator_Role::CAPABILITY );
|
||||
}
|
||||
|
||||
public function map_jobs_to_array( WPML_TM_Job_Entity $job ) {
|
||||
return [
|
||||
'id' => $job->get_id(),
|
||||
'type' => $job->get_type(),
|
||||
'status' => $job->get_status(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validate_job( array $job ) {
|
||||
return isset( $job['id'], $job['type'] ) && \WPML_TM_Job_Entity::is_type_valid( $job['type'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_Batch_Sync_Factory extends WPML_REST_Factory_Loader {
|
||||
/**
|
||||
* @return WPML_TM_REST_Batch_Sync
|
||||
*/
|
||||
public function create() {
|
||||
return new WPML_TM_REST_Batch_Sync(
|
||||
new WPML_TP_Batch_Sync_API(
|
||||
wpml_tm_get_tp_api_client(),
|
||||
wpml_tm_get_tp_project(),
|
||||
new WPML_TM_Log()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_Batch_Sync extends WPML_REST_Base {
|
||||
/** @var WPML_TP_Batch_Sync_API */
|
||||
private $batch_sync_api;
|
||||
|
||||
public function __construct( WPML_TP_Batch_Sync_API $batch_sync_api ) {
|
||||
parent::__construct( 'wpml/tm/v1' );
|
||||
$this->batch_sync_api = $batch_sync_api;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
parent::register_route(
|
||||
'/tp/batches/sync',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'init' ),
|
||||
'args' => array(
|
||||
'batchId' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_batch_ids' ),
|
||||
'sanitize_callback' => array( $this, 'sanitize_batch_ids' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
parent::register_route(
|
||||
'/tp/batches/status',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'check_progress' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function init( WP_REST_Request $request ) {
|
||||
try {
|
||||
return $this->batch_sync_api->init_synchronization( $request->get_param( 'batchId' ) );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 500, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public function check_progress() {
|
||||
try {
|
||||
return $this->batch_sync_api->check_progress();
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 500, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return array( WPML_Manage_Translations_Role::CAPABILITY, WPML_Translator_Role::CAPABILITY );
|
||||
}
|
||||
|
||||
public function validate_batch_ids( $batches ) {
|
||||
return is_array( $batches );
|
||||
}
|
||||
|
||||
public function sanitize_batch_ids( $batches ) {
|
||||
return array_map( 'intval', $batches );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Download_File {
|
||||
|
||||
public function send( $file_name, $content, $content_type = 'application/x-xliff+xml' ) {
|
||||
add_filter( 'rest_pre_echo_response', array( $this, 'force_wp_rest_server_download' ) );
|
||||
|
||||
header( 'Content-Description: File Transfer' );
|
||||
header( 'Content-Type: ' . $content_type );
|
||||
header( 'Content-Disposition: attachment; filename="' . $file_name . '"' );
|
||||
header( 'Content-Transfer-Encoding: binary' );
|
||||
header( 'Expires: 0' );
|
||||
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
|
||||
header( 'Pragma: public' );
|
||||
header( 'Content-Length: ' . strlen( $content ) );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function force_wp_rest_server_download( $content ) {
|
||||
echo $content;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use WPML\TM\Jobs\Utils\ElementLinkFactory;
|
||||
|
||||
class WPML_TM_REST_Jobs_Factory extends WPML_REST_Factory_Loader {
|
||||
/**
|
||||
* @return WPML_TM_REST_Jobs
|
||||
*/
|
||||
public function create() {
|
||||
global $sitepress, $wpdb;
|
||||
|
||||
return new WPML_TM_REST_Jobs(
|
||||
wpml_tm_get_jobs_repository( true, false, true ),
|
||||
new WPML_TM_Rest_Jobs_Criteria_Parser(),
|
||||
new WPML_TM_Rest_Jobs_View_Model(
|
||||
WPML_TM_Rest_Jobs_Translation_Service::create(),
|
||||
new WPML_TM_Rest_Jobs_Element_Info(
|
||||
new WPML_TM_Rest_Jobs_Package_Helper_Factory()
|
||||
),
|
||||
new WPML_TM_Rest_Jobs_Language_Names( $sitepress ),
|
||||
new WPML_TM_Rest_Job_Translator_Name(),
|
||||
new WPML_TM_Rest_Job_Progress(),
|
||||
ElementLinkFactory::create()
|
||||
),
|
||||
new WPML_TP_Sync_Update_Job( $wpdb, $sitepress ),
|
||||
new WPML_TM_Last_Picked_Up( $sitepress )
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
/**
|
||||
* WPML_TM_REST_Jobs class file.
|
||||
*
|
||||
* @package wpml-translation-management
|
||||
*/
|
||||
|
||||
use WPML\FP\Obj;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\LIB\WP\User;
|
||||
use WPML\TM\ATE\Review\Cancel;
|
||||
use function WPML\FP\pipe;
|
||||
use function WPML\FP\partial;
|
||||
use function WPML\FP\invoke;
|
||||
use function WPML\FP\curryN;
|
||||
|
||||
/**
|
||||
* Class WPML_TM_REST_Jobs
|
||||
*/
|
||||
class WPML_TM_REST_Jobs extends WPML_REST_Base {
|
||||
const CAPABILITY = 'translate';
|
||||
|
||||
/**
|
||||
* Jobs repository
|
||||
*
|
||||
* @var WPML_TM_Jobs_Repository
|
||||
*/
|
||||
private $jobs_repository;
|
||||
|
||||
/**
|
||||
* Rest jobs criteria parser
|
||||
*
|
||||
* @var WPML_TM_Rest_Jobs_Criteria_Parser
|
||||
*/
|
||||
private $criteria_parser;
|
||||
|
||||
/**
|
||||
* View model
|
||||
*
|
||||
* @var WPML_TM_Rest_Jobs_View_Model
|
||||
*/
|
||||
private $view_model;
|
||||
|
||||
/**
|
||||
* Update jobs synchronisation
|
||||
*
|
||||
* @var WPML_TP_Sync_Update_Job
|
||||
*/
|
||||
private $update_jobs;
|
||||
|
||||
/**
|
||||
* Last picked up jobs
|
||||
*
|
||||
* @var WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up
|
||||
*/
|
||||
private $wpml_tm_last_picked_up;
|
||||
|
||||
/**
|
||||
* WPML_TM_REST_Jobs constructor.
|
||||
*
|
||||
* @param WPML_TM_Jobs_Repository $jobs_repository Jobs repository.
|
||||
* @param WPML_TM_Rest_Jobs_Criteria_Parser $criteria_parser Rest jobs criteria parser.
|
||||
* @param WPML_TM_Rest_Jobs_View_Model $view_model View model.
|
||||
* @param WPML_TP_Sync_Update_Job $update_jobs Update jobs synchronisation.
|
||||
* @param WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up Last picked up jobs.
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_TM_Jobs_Repository $jobs_repository,
|
||||
WPML_TM_Rest_Jobs_Criteria_Parser $criteria_parser,
|
||||
WPML_TM_Rest_Jobs_View_Model $view_model,
|
||||
WPML_TP_Sync_Update_Job $update_jobs,
|
||||
WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up
|
||||
) {
|
||||
parent::__construct( 'wpml/tm/v1' );
|
||||
|
||||
$this->jobs_repository = $jobs_repository;
|
||||
$this->criteria_parser = $criteria_parser;
|
||||
$this->view_model = $view_model;
|
||||
$this->update_jobs = $update_jobs;
|
||||
$this->wpml_tm_last_picked_up = $wpml_tm_last_picked_up;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add hooks
|
||||
*/
|
||||
public function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register routes
|
||||
*/
|
||||
public function register_routes() {
|
||||
parent::register_route(
|
||||
'/jobs',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_jobs' ),
|
||||
'args' => array(
|
||||
'local_job_ids' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'scope' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_TM_Jobs_Search_Params', 'is_valid_scope' ),
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'id' => array(
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ),
|
||||
),
|
||||
'title' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'source_language' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'target_language' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'needs_update' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_TM_Jobs_Needs_Update_Param', 'is_valid' ),
|
||||
),
|
||||
'limit' => array(
|
||||
'type' => 'integer',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ),
|
||||
),
|
||||
'offset' => array(
|
||||
'type' => 'integer',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ),
|
||||
),
|
||||
'sorting' => array(
|
||||
'validate_callback' => array( $this, 'validate_sorting' ),
|
||||
),
|
||||
'translated_by' => array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ),
|
||||
),
|
||||
'sent_from' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ),
|
||||
),
|
||||
'sent_to' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ),
|
||||
),
|
||||
'deadline_from' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ),
|
||||
),
|
||||
'deadline_to' => array(
|
||||
'type' => 'string',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
parent::register_route(
|
||||
'/jobs/assign',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'assign_job' ),
|
||||
'args' => array(
|
||||
'jobId' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ),
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ),
|
||||
),
|
||||
'type' => array(
|
||||
'required' => false,
|
||||
'validate_callback' => [ WPML_TM_Job_Entity::class, 'is_type_valid' ],
|
||||
),
|
||||
'translatorId' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ),
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
parent::register_route(
|
||||
'/jobs/cancel',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'cancel_jobs' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get jobs
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_jobs( WP_REST_Request $request ) {
|
||||
try {
|
||||
$criteria = $this->criteria_parser->build_criteria( $request );
|
||||
|
||||
$model = $this->view_model->build(
|
||||
$this->jobs_repository->get( $criteria ),
|
||||
$this->jobs_repository->get_count( $criteria )
|
||||
);
|
||||
|
||||
$model['last_picked_up_date'] = $this->wpml_tm_last_picked_up->get();
|
||||
|
||||
return $model;
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 500, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign job.
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
*
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException Exception on error.
|
||||
*/
|
||||
public function assign_job( WP_REST_Request $request ) {
|
||||
/**
|
||||
* It can be job_id from icl_translate_job or id from icl_string_translations
|
||||
*
|
||||
* @var int $job_id
|
||||
*/
|
||||
$job_id = $request->get_param( 'jobId' );
|
||||
$job_type = $request->get_param( 'type' ) ? $request->get_param( 'type' ) : WPML_TM_Job_Entity::POST_TYPE;
|
||||
|
||||
$assignJob = curryN( 4, 'wpml_tm_assign_translation_job');
|
||||
|
||||
return Maybe::of( $request->get_param( 'translatorId' ) )
|
||||
->filter( User::get() )
|
||||
->map( $assignJob( $job_id, Fns::__, 'local', $job_type ) )
|
||||
->map( Obj::objOf( 'assigned' ) )
|
||||
->getOrElse( null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel job
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function cancel_jobs( WP_REST_Request $request ) {
|
||||
try {
|
||||
// $validateParameter :: [id, type] -> bool
|
||||
$validateParameter = pipe( Obj::prop( 'type' ), [ \WPML_TM_Job_Entity::class, 'is_type_valid' ] );
|
||||
|
||||
// $getJob :: [id, type] -> \WPML_TM_Job_Entity
|
||||
$getJob = Fns::converge( [ $this->jobs_repository, 'get_job' ], [ Obj::prop( 'id' ), Obj::prop( 'type' ) ] );
|
||||
|
||||
// $jobEntityToArray :: \WPML_TM_Job_Entity -> [id, type]
|
||||
$jobEntityToArray = function ( \WPML_TM_Job_Entity $job ) {
|
||||
return [
|
||||
'id' => $job->get_id(),
|
||||
'type' => $job->get_type(),
|
||||
];
|
||||
};
|
||||
|
||||
$jobs = \wpml_collect( $request->get_json_params() )
|
||||
->filter( $validateParameter )
|
||||
->map( $getJob )
|
||||
->filter()
|
||||
->map( Fns::tap( invoke( 'set_status' )->with( ICL_TM_NOT_TRANSLATED ) ) )
|
||||
->map( Fns::tap( [ $this->update_jobs, 'update_state' ] ) );
|
||||
|
||||
do_action( 'wpml_tm_jobs_cancelled', $jobs->toArray() );
|
||||
|
||||
return $jobs->map( $jobEntityToArray )->values()->toArray();
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 500, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed capabilities
|
||||
*
|
||||
* @param WP_REST_Request $request REST request.
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return array( WPML_Manage_Translations_Role::CAPABILITY, WPML_Translator_Role::CAPABILITY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sorting
|
||||
*
|
||||
* @param mixed $sorting Sorting parameters.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_sorting( $sorting ) {
|
||||
if ( ! is_array( $sorting ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ( $sorting as $column => $asc_or_desc ) {
|
||||
new WPML_TM_Jobs_Sorting_Param( $column, $asc_or_desc );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate job
|
||||
*
|
||||
* @param mixed $job Job.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validate_job( $job ) {
|
||||
return is_array( $job ) && isset( $job['id'] ) && isset( $job['type'] ) && \WPML_TM_Job_Entity::is_type_valid( $job['type'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_TP_XLIFF_Factory extends WPML_REST_Factory_Loader {
|
||||
|
||||
public function create() {
|
||||
return new WPML_TM_REST_TP_XLIFF(
|
||||
new WPML_TP_Translations_Repository(
|
||||
wpml_tm_get_tp_xliff_api(),
|
||||
wpml_tm_get_jobs_repository()
|
||||
),
|
||||
new WPML_TM_Rest_Download_File()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_TP_XLIFF extends WPML_REST_Base {
|
||||
/** @var WPML_TP_Translations_Repository */
|
||||
private $translation_repository;
|
||||
|
||||
/** @var WPML_TM_Rest_Download_File */
|
||||
private $download_file;
|
||||
|
||||
public function __construct(
|
||||
WPML_TP_Translations_Repository $translation_repository,
|
||||
WPML_TM_Rest_Download_File $download_file
|
||||
) {
|
||||
parent::__construct( 'wpml/tm/v1' );
|
||||
|
||||
$this->translation_repository = $translation_repository;
|
||||
$this->download_file = $download_file;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
public function register_routes() {
|
||||
parent::register_route(
|
||||
'/tp/xliff/download/(?P<job_id>\d+)/(?P<job_type>\w+)',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_job_translations_from_tp' ),
|
||||
'args' => array(
|
||||
'job_id' => array(
|
||||
'required' => true,
|
||||
),
|
||||
'job_type' => array(
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_job_type' ),
|
||||
),
|
||||
'json' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array|string|WP_Error
|
||||
*/
|
||||
public function get_job_translations_from_tp( WP_REST_Request $request ) {
|
||||
try {
|
||||
if ( $request->get_param( 'json' ) ) {
|
||||
return $this->translation_repository->get_job_translations(
|
||||
$request->get_param( 'job_id' ),
|
||||
$request->get_param( 'job_type' )
|
||||
)->to_array();
|
||||
} else {
|
||||
return $this->download_job_translation( $request );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 400, $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function download_job_translation( WP_REST_Request $request ) {
|
||||
try {
|
||||
$content = $this->translation_repository->get_job_translations(
|
||||
$request->get_param( 'job_id' ),
|
||||
$request->get_param( 'job_type' ),
|
||||
false
|
||||
);
|
||||
} catch ( WPML_TP_API_Exception $e ) {
|
||||
return new WP_Error( 500, $e->getMessage() );
|
||||
}
|
||||
|
||||
$file_name = sprintf( 'job-%d.xliff', $request->get_param( 'job_id' ) );
|
||||
return $this->download_file->send( $file_name, $content );
|
||||
}
|
||||
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return array( WPML_Manage_Translations_Role::CAPABILITY, WPML_Translator_Role::CAPABILITY );
|
||||
}
|
||||
|
||||
public function validate_job_type( $value ) {
|
||||
return in_array(
|
||||
$value,
|
||||
array(
|
||||
WPML_TM_Job_Entity::POST_TYPE,
|
||||
WPML_TM_Job_Entity::STRING_TYPE,
|
||||
WPML_TM_Job_Entity::PACKAGE_TYPE,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Job_Progress {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Job_Entity $job
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get( WPML_TM_Job_Entity $job ) {
|
||||
if ( $job->get_translation_service() !== 'local' ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $job->get_status() !== ICL_TM_IN_PROGRESS ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $job->get_type() === WPML_TM_Job_Entity::STRING_TYPE ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$sql = "
|
||||
SELECT field_finished FROM {$this->wpdb->prefix}icl_translate translate
|
||||
INNER JOIN {$this->wpdb->prefix}icl_translate_job translate_job ON translate_job.job_id = translate.job_id
|
||||
INNER JOIN {$this->wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = translate_job.rid
|
||||
WHERE translation_status.rid = %d AND translate.field_translate = 1 AND LENGTH(translate.field_data) > 0
|
||||
";
|
||||
$sql = $this->wpdb->prepare( $sql, $job->get_id() );
|
||||
|
||||
$elements = $this->wpdb->get_col( $sql );
|
||||
$translated = array_filter( $elements );
|
||||
|
||||
$percentage = (int) ( count( $translated ) / count( $elements ) * 100 );
|
||||
|
||||
return sprintf( _x( '%s completed', 'Translation jobs list', 'wpml-transation-manager' ), $percentage . '%' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Job_Translator_Name {
|
||||
|
||||
public function get( $translator_id ) {
|
||||
$user = get_user_by( 'id', $translator_id );
|
||||
if ( $user instanceof WP_User ) {
|
||||
return $user->display_name;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Jobs_Columns {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function get_columns() {
|
||||
return array(
|
||||
'id' => __( 'ID', 'wpml-translation-management' ),
|
||||
'title' => __( 'Title', 'wpml-translation-management' ),
|
||||
'languages' => __( 'Languages', 'wpml-translation-management' ),
|
||||
'batch_name' => __( 'Batch name', 'wpml-translation-management' ),
|
||||
'translator' => __( 'Translated by', 'wpml-translation-management' ),
|
||||
'sent_date' => __( 'Sent on', 'wpml-translation-management' ),
|
||||
'deadline' => __( 'Deadline', 'wpml-translation-management' ),
|
||||
'status' => __( 'Status', 'wpml-translation-management' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function get_sortable() {
|
||||
return array(
|
||||
'id' => __( 'ID', 'wpml-translation-management' ),
|
||||
'title' => __( 'Title', 'wpml-translation-management' ),
|
||||
'batch_name' => __( 'Batch name', 'wpml-translation-management' ),
|
||||
'language' => __( 'Language', 'wpml-translation-management' ),
|
||||
'sent_date' => __( 'Sent on', 'wpml-translation-management' ),
|
||||
'deadline_date' => __( 'Deadline', 'wpml-translation-management' ),
|
||||
'status' => __( 'Status', 'wpml-translation-management' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Cast;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\LIB\WP\User;
|
||||
use WPML\TM\API\Translators;
|
||||
|
||||
class WPML_TM_Rest_Jobs_Criteria_Parser {
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WPML_TM_Jobs_Search_Params
|
||||
*/
|
||||
public function build_criteria( WP_REST_Request $request ) {
|
||||
$params = new WPML_TM_Jobs_Search_Params();
|
||||
|
||||
$params = $this->set_scope( $params, $request );
|
||||
$params = $this->set_pagination( $params, $request );
|
||||
$params = $this->set_filters( $params, $request );
|
||||
$params = $this->set_sorting( $params, $request );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Jobs_Search_Params $params
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WPML_TM_Jobs_Search_Params
|
||||
*/
|
||||
private function set_scope( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) {
|
||||
$scope = $request->get_param( 'scope' );
|
||||
if ( WPML_TM_Jobs_Search_Params::is_valid_scope( $scope ) ) {
|
||||
$params->set_scope( $scope );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Jobs_Search_Params $params
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WPML_TM_Jobs_Search_Params
|
||||
*/
|
||||
private function set_pagination( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) {
|
||||
$limit = (int) $request->get_param( 'limit' );
|
||||
if ( $limit > 0 ) {
|
||||
$params->set_limit( $limit );
|
||||
|
||||
$offset = (int) $request->get_param( 'offset' );
|
||||
if ( $offset > 0 ) {
|
||||
$params->set_offset( $offset );
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
private function set_filters( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) {
|
||||
foreach ( [ 'id', 'source_language', 'translated_by', 'element_type' ] as $key ) {
|
||||
$value = (string) $request->get_param( $key );
|
||||
if ( $value ) {
|
||||
$params->{'set_' . $key}( $value );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( [ 'local_job_ids', 'title', 'target_language', 'batch_name' ] as $key ) {
|
||||
$value = (string) $request->get_param( $key );
|
||||
if ( strlen( $value ) ) {
|
||||
$params->{'set_' . $key}( explode( ',', $value ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $request->get_param( 'status' ) !== null ) {
|
||||
$statuses = Fns::map( Cast::toInt(), explode( ',', $request->get_param( 'status' ) ) );
|
||||
|
||||
if ( $statuses === [ ICL_TM_NEEDS_REVIEW ] ) {
|
||||
$params->set_needs_review( true );
|
||||
} else {
|
||||
$params->set_status( Fns::reject( Relation::equals( ICL_TM_NEEDS_REVIEW ), $statuses ) );
|
||||
|
||||
if ( ! Lst::includes( ICL_TM_NEEDS_REVIEW, $statuses ) ) {
|
||||
$params->set_needs_review( false );
|
||||
}
|
||||
}
|
||||
}
|
||||
$params->set_exclude_cancelled();
|
||||
|
||||
if ( $request->get_param( 'needs_update' ) ) {
|
||||
$params->set_needs_update( new WPML_TM_Jobs_Needs_Update_Param( $request->get_param( 'needs_update' ) ) );
|
||||
}
|
||||
|
||||
$date_range_values = array( 'sent', 'deadline' );
|
||||
foreach ( $date_range_values as $date_range_value ) {
|
||||
$from = $request->get_param( $date_range_value . '_from' );
|
||||
$to = $request->get_param( $date_range_value . '_to' );
|
||||
|
||||
if ( $from || $to ) {
|
||||
$from = $from ? new DateTime( $from ) : $from;
|
||||
$to = $to ? new DateTime( $to ) : $to;
|
||||
|
||||
if ( $from && $to && $from > $to ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$params->{'set_' . $date_range_value}( new WPML_TM_Jobs_Date_Range( $from, $to ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $request->get_param( 'pageName' ) === \WPML_TM_Jobs_List_Script_Data::TRANSLATION_QUEUE_PAGE ) {
|
||||
global $wpdb;
|
||||
$where[] = $wpdb->prepare( '(translate_job.translator_id = %d OR translate_job.translator_id = 0 OR translate_job.translator_id IS NULL)', User::getCurrentId() );
|
||||
if ( ! $request->get_param( 'includeTranslationServiceJobs' ) ) {
|
||||
$where[] = 'translation_status.translation_service = "local"';
|
||||
}
|
||||
$where[] = "( automatic = 0 OR review_status = 'NEEDS_REVIEW' )";
|
||||
|
||||
$where[] = $this->buildLanguagePairsCriteria();
|
||||
|
||||
$params->set_custom_where_conditions( $where );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function buildLanguagePairsCriteria() {
|
||||
$translator = Translators::getCurrent();
|
||||
|
||||
$buildWhereForPair = function ( $targets, $source ) {
|
||||
return sprintf(
|
||||
'( translations.source_language_code = "%s" AND translations.language_code IN (%s) )',
|
||||
$source,
|
||||
wpml_prepare_in( $targets )
|
||||
);
|
||||
};
|
||||
|
||||
return '( ' . \wpml_collect( $translator->language_pairs )
|
||||
->map( $buildWhereForPair )
|
||||
->implode( ' OR ' ) . ' ) ';
|
||||
}
|
||||
|
||||
private function set_sorting( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) {
|
||||
$sorting = [];
|
||||
|
||||
$sortingParams = $request->get_param( 'sorting' );
|
||||
if ( $sortingParams ) {
|
||||
$sorting = $this->build_sorting_params( $sortingParams );
|
||||
}
|
||||
|
||||
if ( $request->get_param( 'pageName' ) === \WPML_TM_Jobs_List_Script_Data::TRANSLATION_QUEUE_PAGE ) {
|
||||
$sorting[] = new \WPML_TM_Jobs_Sorting_Param( "IF ((status = 10 AND (review_status = 'EDITING' OR review_status = 'NEEDS_REVIEW')) OR status = 1, 1,IF (status = 2, 2, IF (needs_update = 1, 3, 4)))", 'ASC' );
|
||||
$sorting[] = new \WPML_TM_Jobs_Sorting_Param( 'translator_id', 'DESC' );
|
||||
$sorting[] = new \WPML_TM_Jobs_Sorting_Param( 'translate_job_id', 'DESC' );
|
||||
}
|
||||
|
||||
$params->set_sorting( $sorting );
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $request_param
|
||||
*
|
||||
* @return WPML_TM_Jobs_Sorting_Param[]
|
||||
*/
|
||||
private function build_sorting_params( array $request_param ) {
|
||||
return \wpml_collect( $request_param )->map(
|
||||
function ( $direction, $column ) {
|
||||
return new WPML_TM_Jobs_Sorting_Param( $column, $direction );
|
||||
}
|
||||
)->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Obj;
|
||||
|
||||
class WPML_TM_Rest_Jobs_Element_Info {
|
||||
/** @var WPML_TM_Rest_Jobs_Package_Helper_Factory */
|
||||
private $package_helper_factory;
|
||||
|
||||
/** @var array|null */
|
||||
private $post_types;
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Rest_Jobs_Package_Helper_Factory $package_helper_factory
|
||||
*/
|
||||
public function __construct( WPML_TM_Rest_Jobs_Package_Helper_Factory $package_helper_factory ) {
|
||||
$this->package_helper_factory = $package_helper_factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Job_Entity $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get( WPML_TM_Job_Entity $job ) {
|
||||
$type = $job->get_type();
|
||||
$id = $job->get_original_element_id();
|
||||
$result = [];
|
||||
|
||||
switch ( $type ) {
|
||||
case WPML_TM_Job_Entity::POST_TYPE:
|
||||
/** @var WPML_TM_Post_Job_Entity $job */
|
||||
$result = $this->get_for_post( $id, $job->get_element_id() );
|
||||
break;
|
||||
case WPML_TM_Job_Entity::STRING_TYPE:
|
||||
case WPML_TM_Job_Entity::STRING_BATCH:
|
||||
$result = $this->get_for_title( $job->get_title() );
|
||||
break;
|
||||
case WPML_TM_Job_Entity::PACKAGE_TYPE:
|
||||
$result = $this->get_for_package( $id );
|
||||
break;
|
||||
}
|
||||
|
||||
if ( empty( $result ) ) {
|
||||
$result = array(
|
||||
'name' => '',
|
||||
'url' => null,
|
||||
);
|
||||
do_action( 'wpml_tm_jobs_log', 'WPML_TM_Rest_Jobs_Element_Info::get', array( $id, $type ), 'Empty result' );
|
||||
}
|
||||
|
||||
$result['url'] = apply_filters( 'wpml_tm_job_list_element_url', $result['url'], $id, $type );
|
||||
|
||||
if ( $job instanceof WPML_TM_Post_Job_Entity ) {
|
||||
$result['type'] = $this->get_type_info( $job );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $originalPostId
|
||||
* @param int $translatedPostId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_for_post( $originalPostId, $translatedPostId ) {
|
||||
$result = array();
|
||||
|
||||
$post = get_post( $originalPostId );
|
||||
if ( $post ) {
|
||||
$permalink = get_permalink( $post );
|
||||
|
||||
$result = [
|
||||
'name' => $post->post_title,
|
||||
'url' => $permalink,
|
||||
'status' => Obj::propOr( 'draft', 'post_status', get_post( $translatedPostId ) ),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_for_package( $id ) {
|
||||
$result = array();
|
||||
|
||||
$helper = $this->package_helper_factory->create();
|
||||
if ( ! $helper ) {
|
||||
return array(
|
||||
'name' => __( 'String package job', 'wpml-translation-management' ),
|
||||
'url' => null,
|
||||
);
|
||||
}
|
||||
|
||||
$package = $helper->get_translatable_item( null, $id );
|
||||
if ( $package ) {
|
||||
$result = array(
|
||||
'name' => $package->title,
|
||||
'url' => $package->edit_link,
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_for_title( $title ) {
|
||||
return [
|
||||
'name' => $title,
|
||||
'url' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Post_Job_Entity $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_type_info( WPML_TM_Post_Job_Entity $job ) {
|
||||
$generalType = substr(
|
||||
$job->get_element_type(),
|
||||
0,
|
||||
strpos( $job->get_element_type(), '_' )
|
||||
);
|
||||
|
||||
switch ( $generalType ) {
|
||||
case 'post':
|
||||
case 'package':
|
||||
$specificType = substr( $job->get_element_type(), strlen( $generalType ) + 1 );
|
||||
$label = Obj::pathOr(
|
||||
$job->get_element_type(),
|
||||
[ $specificType, 'labels', 'singular_name' ],
|
||||
$this->get_post_types()
|
||||
);
|
||||
break;
|
||||
case 'st-batch':
|
||||
$label = __( 'Strings', 'wpml-translation-management' );
|
||||
break;
|
||||
default:
|
||||
$label = $job->get_element_type();
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => $job->get_element_type(),
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
|
||||
private function get_post_types() {
|
||||
if ( $this->post_types === null ) {
|
||||
$this->post_types = \WPML\API\PostTypes::getTranslatableWithInfo();
|
||||
}
|
||||
|
||||
return $this->post_types;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Jobs_Language_Names {
|
||||
/** @var SitePress */
|
||||
private $sitepress;
|
||||
|
||||
/** @var array */
|
||||
private $active_languages;
|
||||
|
||||
/**
|
||||
* @param SitePress $sitepress
|
||||
*/
|
||||
public function __construct( SitePress $sitepress ) {
|
||||
$this->sitepress = $sitepress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $code
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get( $code ) {
|
||||
$languages = $this->get_active_languages();
|
||||
|
||||
return isset( $languages[ $code ] ) ? $languages[ $code ] : $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_active_languages() {
|
||||
if ( ! $this->active_languages ) {
|
||||
foreach ( $this->sitepress->get_active_languages() as $code => $data ) {
|
||||
$this->active_languages[ $code ] = $data['display_name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->active_languages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Jobs_Package_Helper_Factory {
|
||||
/** @var WPML_Package_Helper */
|
||||
private $package_helper = false;
|
||||
|
||||
/**
|
||||
* @return null|WPML_Package_Helper
|
||||
*/
|
||||
public function create() {
|
||||
if ( false === $this->package_helper ) {
|
||||
if ( defined( 'WPML_ST_VERSION' ) && class_exists( 'WPML_Package_Helper' ) ) {
|
||||
$this->package_helper = new WPML_Package_Helper();
|
||||
} else {
|
||||
$this->package_helper = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->package_helper;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Rest_Jobs_Translation_Service {
|
||||
/** @var WPML_WP_Cache */
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param WPML_WP_Cache $cache
|
||||
*/
|
||||
public function __construct( WPML_WP_Cache $cache ) {
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|int $service_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name( $service_id ) {
|
||||
$name = '';
|
||||
if ( is_numeric( $service_id ) ) {
|
||||
$service = $this->get_translation_service( (int) $service_id );
|
||||
if ( $service ) {
|
||||
$name = $service->name;
|
||||
}
|
||||
} else {
|
||||
$name = __( 'Local', 'wpml-translation-management' );
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
private function get_translation_service( $service_id ) {
|
||||
$key = 'service_' . $service_id;
|
||||
$found = false;
|
||||
$service = $this->cache->get( $key, $found );
|
||||
|
||||
if ( ! $found ) {
|
||||
$current_service = TranslationProxy::get_current_service();
|
||||
if ( $current_service && $current_service->id === $service_id ) {
|
||||
$service = $current_service;
|
||||
} else {
|
||||
$service = TranslationProxy_Service::get_service( $service_id );
|
||||
}
|
||||
|
||||
$this->cache->set( $key, $service );
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_TM_Rest_Jobs_Translation_Service
|
||||
*/
|
||||
public static function create() {
|
||||
$cache = new WPML_WP_Cache( 'wpml-tm-services' );
|
||||
|
||||
return new WPML_TM_Rest_Jobs_Translation_Service( $cache );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
use WPML\Element\API\PostTranslations;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\API\Jobs;
|
||||
use WPML\TM\ATE\Review\PreviewLink;
|
||||
use WPML\TM\ATE\Review\ReviewStatus;
|
||||
use WPML\TM\Jobs\Utils\ElementLink;
|
||||
|
||||
class WPML_TM_Rest_Jobs_View_Model {
|
||||
/** @var WPML_TM_Rest_Jobs_Translation_Service */
|
||||
private $translation_service;
|
||||
|
||||
/** @var WPML_TM_Rest_Jobs_Element_Info */
|
||||
private $element_info;
|
||||
|
||||
/** @var WPML_TM_Rest_Jobs_Language_Names */
|
||||
private $language_names;
|
||||
|
||||
/** @var WPML_TM_Rest_Job_Translator_Name */
|
||||
private $translator_name;
|
||||
|
||||
/** @var WPML_TM_Rest_Job_Progress */
|
||||
private $progress;
|
||||
|
||||
/** @var ElementLink $element_link */
|
||||
private $element_link;
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Rest_Jobs_Translation_Service $translation_service
|
||||
* @param WPML_TM_Rest_Jobs_Element_Info $element_info
|
||||
* @param WPML_TM_Rest_Jobs_Language_Names $language_names
|
||||
* @param WPML_TM_Rest_Job_Translator_Name $translator_name
|
||||
* @param WPML_TM_Rest_Job_Progress $progress
|
||||
* @param ElementLink $element_link
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_TM_Rest_Jobs_Translation_Service $translation_service,
|
||||
WPML_TM_Rest_Jobs_Element_Info $element_info,
|
||||
WPML_TM_Rest_Jobs_Language_Names $language_names,
|
||||
WPML_TM_Rest_Job_Translator_Name $translator_name,
|
||||
WPML_TM_Rest_Job_Progress $progress,
|
||||
ElementLink $element_link
|
||||
) {
|
||||
$this->translation_service = $translation_service;
|
||||
$this->element_info = $element_info;
|
||||
$this->language_names = $language_names;
|
||||
$this->translator_name = $translator_name;
|
||||
$this->progress = $progress;
|
||||
$this->element_link = $element_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Jobs_Collection $jobs
|
||||
* @param int $total_jobs_count
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function build( WPML_TM_Jobs_Collection $jobs, $total_jobs_count ) {
|
||||
$result = [ 'jobs' => [] ];
|
||||
|
||||
foreach ( $jobs as $job ) {
|
||||
$result['jobs'][] = $this->map_job( $job );
|
||||
}
|
||||
|
||||
$result['total'] = $total_jobs_count;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Job_Entity $job
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function map_job( WPML_TM_Job_Entity $job ) {
|
||||
$extra_data = [];
|
||||
$viewUrl = '';
|
||||
|
||||
if ( $job instanceof WPML_TM_Post_Job_Entity ) {
|
||||
$extra_data['icl_translate_job_id'] = $job->get_translate_job_id();
|
||||
$extra_data['editor_job_id'] = $job->get_editor_job_id();
|
||||
|
||||
$viewUrl = $this->getViewUrl( $job );
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $job->get_rid(),
|
||||
'type' => $job->get_type(),
|
||||
'tp_id' => $job->get_tp_id(),
|
||||
'status' => $job->get_status(),
|
||||
'needs_update' => $job->does_need_update(),
|
||||
'review_status' => $job->get_review_status(),
|
||||
'language_codes' => [
|
||||
'source' => $job->get_source_language(),
|
||||
'target' => $job->get_target_language(),
|
||||
],
|
||||
'languages' => [
|
||||
'source' => $this->language_names->get( $job->get_source_language() ),
|
||||
'target' => $this->language_names->get( $job->get_target_language() ),
|
||||
],
|
||||
'translation_service_id' => $job->get_translation_service(),
|
||||
'translation_service' => $this->translation_service->get_name( $job->get_translation_service() ),
|
||||
'sent_date' => $job->get_sent_date()->format( 'Y-m-d' ),
|
||||
'deadline' => $job->get_deadline() ? $job->get_deadline()->format( 'Y-m-d' ) : '',
|
||||
'ts_status' => (string) $job->get_ts_status(),
|
||||
'element' => $this->element_info->get( $job ),
|
||||
'translator_name' => $job->get_translator_id() ? $this->translator_name->get( $job->get_translator_id() ) : '',
|
||||
'translator_id' => $job->get_translator_id() ? (int) $job->get_translator_id() : '',
|
||||
'is_ate_job' => $job->is_ate_job(),
|
||||
'automatic' => $job->is_automatic(),
|
||||
'progress' => $this->progress->get( $job ),
|
||||
'batch' => [
|
||||
'id' => $job->get_batch()->get_id(),
|
||||
'name' => $job->get_batch()->get_name(),
|
||||
'tp_id' => $job->get_batch()->get_tp_id(),
|
||||
],
|
||||
'extra_data' => $extra_data,
|
||||
'edit_url' => $this->get_edit_url( $job ),
|
||||
'view_url' => $viewUrl,
|
||||
'view_original_url' => $this->element_link->getOriginal( $job ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Job_Entity $job
|
||||
*
|
||||
* @return mixed|string|void
|
||||
*/
|
||||
private function get_edit_url( $job ) {
|
||||
$edit_url = '';
|
||||
if ( $job->get_original_element_id() ) {
|
||||
|
||||
$jobId = $job instanceof WPML_TM_Post_Job_Entity ? $job->get_translate_job_id() : $job->get_rid();
|
||||
|
||||
$translation_queue_page = admin_url( 'admin.php?page='
|
||||
. WPML_TM_FOLDER
|
||||
. '/menu/translations-queue.php&job_id='
|
||||
. $jobId );
|
||||
$edit_url = apply_filters( 'icl_job_edit_url', $translation_queue_page, $jobId );
|
||||
}
|
||||
|
||||
return $edit_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Post_Job_Entity $job
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getViewUrl( WPML_TM_Post_Job_Entity $job ) {
|
||||
$needsReview = Lst::includes( $job->get_review_status(), [
|
||||
ReviewStatus::NEEDS_REVIEW,
|
||||
ReviewStatus::EDITING
|
||||
] );
|
||||
|
||||
return $needsReview ? $this->getReviewUrl( $job ) : $this->element_link->getTranslation( $job );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_Post_Job_Entity $job
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getReviewUrl( WPML_TM_Post_Job_Entity $job ) {
|
||||
$translation = PostTranslations::getInLanguage( $job->get_original_element_id(), $job->get_target_language() );
|
||||
$returnUrl = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php' );
|
||||
|
||||
return PreviewLink::getWithSpecifiedReturnUrl( $returnUrl, $translation->element_id, $job->get_translate_job_id() );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Download;
|
||||
|
||||
use Exception;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\ATE\ReturnedJobsQueue;
|
||||
use WPML_TM_ATE_API;
|
||||
use WPML_TM_ATE_Jobs;
|
||||
|
||||
class Consumer {
|
||||
|
||||
/** @var WPML_TM_ATE_API $ateApi */
|
||||
private $ateApi;
|
||||
|
||||
/** @var WPML_TM_ATE_Jobs $ateJobs */
|
||||
private $ateJobs;
|
||||
|
||||
public function __construct( WPML_TM_ATE_API $ateApi, WPML_TM_ATE_Jobs $ateJobs ) {
|
||||
$this->ateApi = $ateApi;
|
||||
$this->ateJobs = $ateJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $job
|
||||
*
|
||||
* @return array|\stdClass|false
|
||||
* @throws Exception
|
||||
*/
|
||||
public function process( $job ) {
|
||||
$xliffContent = $this->ateApi->get_remote_xliff_content( Obj::prop('url', $job), $job );
|
||||
$wpmlJobId = $this->ateJobs->apply( $xliffContent );
|
||||
|
||||
if ( $wpmlJobId ) {
|
||||
$processedJob = Obj::assoc( 'jobId', $wpmlJobId, $job );
|
||||
|
||||
ReturnedJobsQueue::remove( $wpmlJobId );
|
||||
|
||||
return $processedJob;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Download;
|
||||
|
||||
class Job {
|
||||
|
||||
/** @var int $ateJobId */
|
||||
public $ateJobId;
|
||||
|
||||
/** @var string $url */
|
||||
public $url;
|
||||
|
||||
/** @var int */
|
||||
public $ateStatus;
|
||||
|
||||
/**
|
||||
* This property is not part of the database data,
|
||||
* but it can be added when the job is downloaded
|
||||
* to provide more information to the UI.
|
||||
*
|
||||
* @var int $jobId
|
||||
*/
|
||||
public $jobId;
|
||||
|
||||
/** @var int */
|
||||
public $status = ICL_TM_IN_PROGRESS;
|
||||
|
||||
/**
|
||||
* @param \stdClass $item
|
||||
*
|
||||
* @return Job
|
||||
*/
|
||||
public static function fromAteResponse( \stdClass $item ) {
|
||||
$job = new self();
|
||||
$job->ateJobId = $item->ate_id;
|
||||
$job->url = $item->download_link;
|
||||
$job->ateStatus = (int) $item->status;
|
||||
$job->jobId = (int) $item->id;
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \stdClass $row
|
||||
*
|
||||
* @return Job
|
||||
*/
|
||||
public static function fromDb( \stdClass $row ) {
|
||||
$job = new self();
|
||||
$job->ateJobId = $row->editor_job_id;
|
||||
$job->url = $row->download_url;
|
||||
|
||||
return $job;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Download;
|
||||
|
||||
use Exception;
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\ATE\Log\Entry;
|
||||
use WPML\TM\ATE\Log\EventsTypes;
|
||||
use WPML_TM_ATE_API;
|
||||
|
||||
class Process {
|
||||
/** @var Consumer $consumer */
|
||||
private $consumer;
|
||||
|
||||
/** @var WPML_TM_ATE_API $ateApi */
|
||||
private $ateApi;
|
||||
|
||||
public function __construct( Consumer $consumer, WPML_TM_ATE_API $ateApi ) {
|
||||
$this->consumer = $consumer;
|
||||
$this->ateApi = $ateApi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $jobs
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function run( $jobs ) {
|
||||
$jobs = \wpml_collect( $jobs )->map( function ( $job ) {
|
||||
$processedJob = null;
|
||||
|
||||
try {
|
||||
$processedJob = $this->consumer->process( $job );
|
||||
|
||||
if ( ! $processedJob ) {
|
||||
global $iclTranslationManagement;
|
||||
$message = 'The translation job could not be applied.';
|
||||
|
||||
if ( $iclTranslationManagement->messages_by_type( 'error' ) ) {
|
||||
|
||||
$iclTranslationManagementError = implode( ' ', Lst::pluck( 'text', $iclTranslationManagement->messages_by_type( 'error' ) ) );
|
||||
$message .= ' ' . $iclTranslationManagementError;
|
||||
}
|
||||
|
||||
throw new Exception( $message );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
|
||||
$this->logException( $e, $processedJob ?: $job );
|
||||
}
|
||||
|
||||
return $processedJob;
|
||||
} )->filter()->values();
|
||||
|
||||
$this->acknowledgeAte( $jobs );
|
||||
|
||||
return $jobs;
|
||||
}
|
||||
|
||||
private function acknowledgeAte( Collection $processedJobs ) {
|
||||
if ( $processedJobs->count() ) {
|
||||
$this->ateApi->confirm_received_job( $processedJobs->pluck( 'ateJobId' )->toArray() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Exception $e
|
||||
* @param Job|null $job
|
||||
*/
|
||||
private function logException( Exception $e, $job = null ) {
|
||||
$entry = new Entry();
|
||||
$entry->description = $e->getMessage();
|
||||
|
||||
if ( $job ) {
|
||||
$entry->ateJobId = Obj::prop('ateJobId', $job);
|
||||
$entry->wpmlJobId = Obj::prop('jobId', $job);
|
||||
$entry->extraData = [ 'downloadUrl' => Obj::prop('url', $job) ];
|
||||
}
|
||||
|
||||
if ( $e instanceof \Requests_Exception ) {
|
||||
$entry->eventType = EventsTypes::SERVER_XLIFF;
|
||||
} else {
|
||||
$entry->eventType = EventsTypes::JOB_DOWNLOAD;
|
||||
}
|
||||
|
||||
wpml_tm_ate_ams_log( $entry );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Hooks;
|
||||
|
||||
use function WPML\Container\make;
|
||||
use WPML\Element\API\Languages;
|
||||
use WPML\FP\Fns;
|
||||
use function WPML\FP\invoke;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use function WPML\FP\pipe;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\Setup\Option;
|
||||
|
||||
class JobActions implements \IWPML_Action {
|
||||
|
||||
/** @var \WPML_TM_ATE_API $apiClient */
|
||||
private $apiClient;
|
||||
|
||||
public function __construct( \WPML_TM_ATE_API $apiClient ) {
|
||||
$this->apiClient = $apiClient;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_tm_job_cancelled', [ $this, 'cancelJobInATE' ] );
|
||||
add_action( 'wpml_tm_jobs_cancelled', [ $this, 'cancelJobsInATE' ] );
|
||||
add_action( 'wpml_set_translate_everything', [ $this, 'hideJobsAfterTranslationMethodChange' ] );
|
||||
add_action( 'wpml_update_active_languages', [ $this, 'hideJobsAfterRemoveLanguage' ] );
|
||||
}
|
||||
|
||||
public function cancelJobInATE( \WPML_TM_Post_Job_Entity $job ) {
|
||||
if ( $job->is_ate_editor() ) {
|
||||
$this->apiClient->cancelJobs( $job->get_editor_job_id() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_TM_Post_Job_Entity[]|\WPML_TM_Post_Job_Entity $jobs
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cancelJobsInATE( $jobs ) {
|
||||
/**
|
||||
* We need this check because if we pass only one job to the hook:
|
||||
* do_action( 'wpml_tm_jobs_cancelled', [ $job ] )
|
||||
* then WordPress converts it to $job.
|
||||
*/
|
||||
if ( is_object( $jobs ) ) {
|
||||
$jobs = [ $jobs ];
|
||||
}
|
||||
|
||||
$getIds = pipe(
|
||||
Fns::filter( invoke( 'is_ate_editor' ) ),
|
||||
Fns::map( invoke( 'get_editor_job_id' ) )
|
||||
);
|
||||
$this->apiClient->cancelJobs( $getIds( $jobs ) );
|
||||
}
|
||||
|
||||
public function hideJobsAfterRemoveLanguage( $oldLanguages ) {
|
||||
$removedLanguages = Lst::diff( array_keys( $oldLanguages ), array_keys( Languages::getActive() ) );
|
||||
|
||||
if ( $removedLanguages ) {
|
||||
$inProgressJobsSearchParams = self::getInProgressSearch()
|
||||
->set_target_language( array_values( $removedLanguages ) );
|
||||
|
||||
$this->hideJobs( $inProgressJobsSearchParams );
|
||||
|
||||
Fns::map( [ Option::class, 'removeLanguageFromCompleted' ], $removedLanguages );
|
||||
}
|
||||
}
|
||||
|
||||
public function hideJobsAfterTranslationMethodChange( $translateEverythingActive ) {
|
||||
if ( ! $translateEverythingActive ) {
|
||||
$this->hideJobs( self::getInProgressSearch() );
|
||||
}
|
||||
}
|
||||
|
||||
private static function getInProgressSearch() {
|
||||
return ( new \WPML_TM_Jobs_Search_Params() )->set_status( [
|
||||
ICL_TM_WAITING_FOR_TRANSLATOR,
|
||||
ICL_TM_IN_PROGRESS
|
||||
] );
|
||||
}
|
||||
|
||||
private function hideJobs( \WPML_TM_Jobs_Search_Params $jobsSearchParams ) {
|
||||
$translationJobs = wpml_collect( wpml_tm_get_jobs_repository()->get( $jobsSearchParams ) )
|
||||
->filter( invoke( 'is_ate_editor' ) )
|
||||
->filter( invoke( 'is_automatic' ) );
|
||||
|
||||
$canceledInATE = $this->apiClient->hideJobs(
|
||||
$translationJobs->map( invoke( 'get_editor_job_id' ) )->values()->toArray()
|
||||
);
|
||||
|
||||
$isResponseValid = $canceledInATE && ! is_wp_error( $canceledInATE );
|
||||
$jobsHiddenInATE = $isResponseValid ? Obj::propOr( [], 'jobs', $canceledInATE ) : [];
|
||||
$isHiddenInATE = function ( $job ) use ( $isResponseValid, $jobsHiddenInATE ) {
|
||||
return $isResponseValid && Lst::includes( $job->get_editor_job_id(), $jobsHiddenInATE );
|
||||
};
|
||||
|
||||
$setStatus = Fns::tap( function ( \WPML_TM_Post_Job_Entity $job ) use ( $isHiddenInATE ) {
|
||||
$status = $isHiddenInATE( $job ) ? ICL_TM_ATE_CANCELLED : ICL_TM_NOT_TRANSLATED;
|
||||
$job->set_status( $status );
|
||||
} );
|
||||
|
||||
$translationJobs->map( $setStatus )
|
||||
->map( Fns::tap( [ make( \WPML_TP_Sync_Update_Job::class ), 'update_state' ] ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Hooks;
|
||||
|
||||
use function WPML\Container\make;
|
||||
|
||||
class JobActionsFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader {
|
||||
|
||||
public function create() {
|
||||
return \WPML_TM_ATE_Status::is_enabled_and_activated()
|
||||
? new JobActions( make( \WPML_TM_ATE_API::class ) )
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Hooks;
|
||||
|
||||
use WPML\TM\ATE\ReturnedJobsQueue;
|
||||
|
||||
class ReturnedJobActions implements \IWPML_Action {
|
||||
/** @var callable :: int->string->void */
|
||||
private $addToQueue;
|
||||
|
||||
/** @var callable :: int->string->void */
|
||||
private $remove_translation_duplicate_status;
|
||||
|
||||
/**
|
||||
* @param callable $addToQueue
|
||||
* @param callable $removeTranslationDuplicateStatus
|
||||
*/
|
||||
public function __construct( callable $addToQueue, callable $removeTranslationDuplicateStatus ) {
|
||||
$this->addToQueue = $addToQueue;
|
||||
$this->remove_translation_duplicate_status = $removeTranslationDuplicateStatus;
|
||||
}
|
||||
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'init', [ $this, 'addToQueue' ] );
|
||||
}
|
||||
|
||||
public function addToQueue() {
|
||||
if ( isset( $_GET['ate_original_id'] ) ) {
|
||||
$ateJobId = (int) $_GET['ate_original_id'];
|
||||
|
||||
if ( isset( $_GET['complete'] ) ) {
|
||||
call_user_func( $this->addToQueue, $ateJobId, ReturnedJobsQueue::STATUS_COMPLETED );
|
||||
call_user_func( $this->remove_translation_duplicate_status, $ateJobId );
|
||||
} elseif ( isset( $_GET['back'] ) ) {
|
||||
call_user_func( $this->addToQueue, $ateJobId, ReturnedJobsQueue::STATUS_BACK );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Hooks;
|
||||
|
||||
use WPML\TM\ATE\ReturnedJobsQueue;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\partialRight;
|
||||
|
||||
class ReturnedJobActionsFactory implements \IWPML_Backend_Action_Loader, \IWPML_REST_Action_Loader {
|
||||
|
||||
public function create() {
|
||||
$ateJobs = make( \WPML_TM_ATE_Jobs::class );
|
||||
|
||||
$add = partialRight( [ ReturnedJobsQueue::class, 'add' ], [ $ateJobs, 'get_wpml_job_id' ] );
|
||||
$removeTranslationDuplicateStatus = partialRight( [ ReturnedJobsQueue::class, 'removeJobTranslationDuplicateStatus' ], [ $ateJobs, 'get_wpml_job_id' ] );
|
||||
|
||||
return new ReturnedJobActions( $add, $removeTranslationDuplicateStatus );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_AMS_Synchronize_Actions_Factory implements IWPML_Backend_Action_Loader {
|
||||
|
||||
/**
|
||||
* @return IWPML_Action|IWPML_Action[]|null
|
||||
*/
|
||||
public function create() {
|
||||
if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) {
|
||||
$ams_api = WPML\Container\make( WPML_TM_AMS_API::class );
|
||||
|
||||
global $wpdb;
|
||||
$user_query_factory = new WPML_WP_User_Query_Factory();
|
||||
|
||||
$wp_roles = wp_roles();
|
||||
$translator_records = new WPML_Translator_Records( $wpdb, $user_query_factory, $wp_roles );
|
||||
$manager_records = new WPML_Translation_Manager_Records( $wpdb, $user_query_factory, $wp_roles );
|
||||
$admin_translators = new WPML_Translator_Admin_Records( $wpdb, $user_query_factory, $wp_roles );
|
||||
$user_records = new WPML_TM_AMS_Users( $manager_records, $translator_records, $admin_translators );
|
||||
$user_factory = new WPML_WP_User_Factory();
|
||||
$translator_activation_records = new WPML_TM_AMS_Translator_Activation_Records( new WPML_WP_User_Factory() );
|
||||
|
||||
return new WPML_TM_AMS_Synchronize_Actions(
|
||||
$ams_api,
|
||||
$user_records,
|
||||
$user_factory,
|
||||
$translator_activation_records,
|
||||
$manager_records,
|
||||
$translator_records
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_AMS_Synchronize_Actions implements IWPML_Action {
|
||||
|
||||
const ENABLED_FOR_TRANSLATION_VIA_ATE = 'wpml_enabled_for_translation_via_ate';
|
||||
|
||||
/**
|
||||
* @var WPML_TM_AMS_API
|
||||
*/
|
||||
private $ams_api;
|
||||
/**
|
||||
* @var WPML_TM_AMS_Users
|
||||
*/
|
||||
private $ams_user_records;
|
||||
/**
|
||||
* @var WPML_WP_User_Factory $user_factory
|
||||
*/
|
||||
private $user_factory;
|
||||
|
||||
/**
|
||||
* @var WPML_TM_AMS_Translator_Activation_Records
|
||||
*/
|
||||
private $translator_activation_records;
|
||||
|
||||
/** @var WPML_Translation_Manager_Records */
|
||||
private $tm_records;
|
||||
|
||||
/** @var WPML_Translator_Records */
|
||||
private $translator_records;
|
||||
|
||||
/** @var int[] */
|
||||
private $deletedManagerIds = [];
|
||||
|
||||
/** @var int[] */
|
||||
private $deletedTranslatorIds = [];
|
||||
|
||||
public function __construct(
|
||||
WPML_TM_AMS_API $ams_api,
|
||||
WPML_TM_AMS_Users $ams_user_records,
|
||||
WPML_WP_User_Factory $user_factory,
|
||||
WPML_TM_AMS_Translator_Activation_Records $translator_activation_records,
|
||||
WPML_Translation_Manager_Records $tm_records,
|
||||
WPML_Translator_Records $translator_records
|
||||
) {
|
||||
$this->ams_api = $ams_api;
|
||||
$this->ams_user_records = $ams_user_records;
|
||||
$this->user_factory = $user_factory;
|
||||
$this->translator_activation_records = $translator_activation_records;
|
||||
$this->tm_records = $tm_records;
|
||||
$this->translator_records = $translator_records;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_tm_ate_synchronize_translators', array( $this, 'synchronize_translators' ) );
|
||||
add_action( 'wpml_update_translator', array( $this, 'synchronize_translators' ) );
|
||||
add_action( 'wpml_tm_ate_synchronize_managers', array( $this, 'synchronize_managers' ) );
|
||||
add_action( 'wpml_tm_ate_enable_subscription', array( $this, 'enable_subscription' ) );
|
||||
add_action( 'delete_user', array( $this, 'prepare_user_deleted' ), 10, 1 );
|
||||
add_action( 'deleted_user', array( $this, 'user_changed' ), 10, 1 );
|
||||
add_action( 'profile_update', array( $this, 'user_changed' ), 10, 1 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function synchronize_translators() {
|
||||
$result = $this->ams_api->synchronize_translators( $this->ams_user_records->get_translators() );
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$this->translator_activation_records->update( isset( $result['translators'] ) ? $result['translators'] : array() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function synchronize_managers() {
|
||||
$this->ams_api->synchronize_managers( $this->ams_user_records->get_managers() );
|
||||
}
|
||||
|
||||
public function enable_subscription( $user_id ) {
|
||||
$user = $this->user_factory->create( $user_id );
|
||||
if ( ! $user->get_meta( self::ENABLED_FOR_TRANSLATION_VIA_ATE ) ) {
|
||||
$this->ams_api->enable_subscription( $user->user_email );
|
||||
$user->update_meta( self::ENABLED_FOR_TRANSLATION_VIA_ATE, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*/
|
||||
public function prepare_user_deleted( $user_id ) {
|
||||
if ( $this->tm_records->does_user_have_capability( $user_id ) ) {
|
||||
$this->deletedManagerIds[] = $user_id;
|
||||
}
|
||||
if ( $this->translator_records->does_user_have_capability( $user_id ) ) {
|
||||
$this->deletedTranslatorIds[] = $user_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
*/
|
||||
public function user_changed( $user_id ) {
|
||||
if ( in_array( $user_id, $this->deletedManagerIds ) || $this->tm_records->does_user_have_capability( $user_id ) ) {
|
||||
$this->synchronize_managers();
|
||||
}
|
||||
if ( in_array( $user_id, $this->deletedTranslatorIds ) || $this->translator_records->does_user_have_capability( $user_id ) ) {
|
||||
$this->synchronize_translators();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_AMS_Synchronize_Users_On_Access_Denied_Factory implements IWPML_Backend_Action_Loader {
|
||||
public function create() {
|
||||
return new WPML_TM_AMS_Synchronize_Users_On_Access_Denied();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_AMS_Synchronize_Users_On_Access_Denied {
|
||||
|
||||
const ERROR_MESSAGE = 'Authentication error, please contact your translation manager to check your subscription';
|
||||
|
||||
/** @var WPML_TM_AMS_Synchronize_Actions */
|
||||
private $ams_synchronize_actions;
|
||||
|
||||
/** @var WPML_TM_ATE_Jobs */
|
||||
private $ate_jobs;
|
||||
|
||||
public function add_hooks() {
|
||||
if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) {
|
||||
add_action( 'init', array( $this, 'catch_access_error' ) );
|
||||
}
|
||||
}
|
||||
|
||||
public function catch_access_error() {
|
||||
if ( ! $this->ate_redirected_due_to_lack_of_access() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_ams_synchronize_actions()->synchronize_translators();
|
||||
|
||||
if ( ! isset( $_GET['ate_job_id'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wpml_job_id = $this->get_ate_jobs()->get_wpml_job_id( $_GET['ate_job_id'] );
|
||||
if ( ! $wpml_job_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = admin_url(
|
||||
'admin.php?page='
|
||||
. WPML_TM_FOLDER
|
||||
. '/menu/translations-queue.php&job_id='
|
||||
. $wpml_job_id
|
||||
);
|
||||
|
||||
wp_safe_redirect( $url, 302, 'WPML' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function ate_redirected_due_to_lack_of_access() {
|
||||
return isset( $_GET['message'] ) && false !== strpos( $_GET['message'], self::ERROR_MESSAGE );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IWPML_Action|IWPML_Action[]|WPML_TM_AMS_Synchronize_Actions
|
||||
*/
|
||||
private function get_ams_synchronize_actions() {
|
||||
if ( ! $this->ams_synchronize_actions ) {
|
||||
$factory = new WPML_TM_AMS_Synchronize_Actions_Factory();
|
||||
$this->ams_synchronize_actions = $factory->create();
|
||||
}
|
||||
|
||||
return $this->ams_synchronize_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_TM_ATE_Jobs
|
||||
*/
|
||||
private function get_ate_jobs() {
|
||||
if ( ! $this->ate_jobs ) {
|
||||
$ate_jobs_records = wpml_tm_get_ate_job_records();
|
||||
$this->ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records );
|
||||
}
|
||||
|
||||
return $this->ate_jobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_AMS_Synchronize_Actions $ams_synchronize_actions
|
||||
*/
|
||||
public function set_ams_synchronize_actions( WPML_TM_AMS_Synchronize_Actions $ams_synchronize_actions ) {
|
||||
$this->ams_synchronize_actions = $ams_synchronize_actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_TM_ATE_Jobs $ate_jobs
|
||||
*/
|
||||
public function set_ate_jobs( WPML_TM_ATE_Jobs $ate_jobs ) {
|
||||
$this->ate_jobs = $ate_jobs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_ATE_API_Error {
|
||||
|
||||
public function log( $message ) {
|
||||
$wpml_admin_notices = wpml_get_admin_notices();
|
||||
|
||||
$notice = new WPML_Notice(
|
||||
WPML_TM_ATE_Jobs_Actions::RESPONSE_ATE_ERROR_NOTICE_ID,
|
||||
sprintf(
|
||||
__( 'There was a problem communicating with ATE: %s ', 'wpml-translation-management' ),
|
||||
'(<i>' . $message . '</i>)'
|
||||
),
|
||||
WPML_TM_ATE_Jobs_Actions::RESPONSE_ATE_ERROR_NOTICE_GROUP
|
||||
);
|
||||
$notice->set_css_class_types( array( 'warning' ) );
|
||||
$notice->add_capability_check( array( 'manage_options', 'wpml_manage_translation_management' ) );
|
||||
$notice->set_flash();
|
||||
$wpml_admin_notices->add_notice( $notice );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_ATE_Job_Data_Fallback_Factory implements IWPML_Backend_Action_Loader, IWPML_REST_Action_Loader {
|
||||
/**
|
||||
* @return WPML_TM_ATE_Job_Data_Fallback
|
||||
*/
|
||||
public function create() {
|
||||
return \WPML\Container\make( '\WPML_TM_ATE_Job_Data_Fallback' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use WPML\TM\ATE\JobRecords;
|
||||
|
||||
class WPML_TM_ATE_Job_Data_Fallback implements IWPML_Action {
|
||||
/** @var WPML_TM_ATE_API */
|
||||
private $ate_api;
|
||||
|
||||
/**
|
||||
* @param WPML_TM_ATE_API $ate_api
|
||||
*/
|
||||
public function __construct( WPML_TM_ATE_API $ate_api ) {
|
||||
$this->ate_api = $ate_api;
|
||||
}
|
||||
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_tm_ate_job_data_fallback', array( $this, 'get_data_from_api' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param int $wpml_job_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data_from_api( array $data, $wpml_job_id ) {
|
||||
$response = $this->ate_api->get_jobs_by_wpml_ids( array( $wpml_job_id ) );
|
||||
if ( ! $response || is_wp_error( $response ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! isset( $response->{$wpml_job_id}->ate_job_id ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return array( JobRecords::FIELD_ATE_JOB_ID => $response->{$wpml_job_id}->ate_job_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
use function WPML\Container\make;
|
||||
use WPML\TM\ATE\ReturnedJobsQueue;
|
||||
|
||||
/**
|
||||
* Factory class for \WPML_TM_ATE_Jobs_Actions.
|
||||
*
|
||||
* @package wpml\tm
|
||||
*
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Jobs_Actions_Factory implements IWPML_Backend_Action_Loader, \IWPML_REST_Action_Loader {
|
||||
/**
|
||||
* The instance of \WPML_Current_Screen.
|
||||
*
|
||||
* @var WPML_Current_Screen
|
||||
*/
|
||||
private $current_screen;
|
||||
|
||||
/**
|
||||
* It returns an instance of \WPML_TM_ATE_Jobs_Actions or null if ATE is not enabled and active.
|
||||
*
|
||||
* @return \WPML_TM_ATE_Jobs_Actions|null
|
||||
* @throws \Auryn\InjectionException
|
||||
*/
|
||||
public function create() {
|
||||
$ams_ate_factories = wpml_tm_ams_ate_factories();
|
||||
|
||||
if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) {
|
||||
$sitepress = $this->get_sitepress();
|
||||
$current_screen = $this->get_current_screen();
|
||||
|
||||
$ate_api = $ams_ate_factories->get_ate_api();
|
||||
$records = wpml_tm_get_ate_job_records();
|
||||
$ate_jobs = new WPML_TM_ATE_Jobs( $records );
|
||||
|
||||
$translator_activation_records = new WPML_TM_AMS_Translator_Activation_Records( new WPML_WP_User_Factory() );
|
||||
|
||||
return new WPML_TM_ATE_Jobs_Actions(
|
||||
$ate_api,
|
||||
$ate_jobs,
|
||||
$sitepress,
|
||||
$current_screen,
|
||||
$translator_activation_records
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The global instance of \Sitepress.
|
||||
*
|
||||
* @return SitePress
|
||||
*/
|
||||
private function get_sitepress() {
|
||||
global $sitepress;
|
||||
|
||||
return $sitepress;
|
||||
}
|
||||
|
||||
/**
|
||||
* It gets the instance of \WPML_Current_Screen.
|
||||
*
|
||||
* @return \WPML_Current_Screen
|
||||
*/
|
||||
private function get_current_screen() {
|
||||
if ( ! $this->current_screen ) {
|
||||
$this->current_screen = new WPML_Current_Screen();
|
||||
}
|
||||
|
||||
return $this->current_screen;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
<?php
|
||||
|
||||
use WPML\API\Sanitize;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Json;
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\FP\Str;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\TM\API\Jobs;
|
||||
use WPML\FP\Wrapper;
|
||||
use WPML\Settings\PostType\Automatic;
|
||||
use WPML\Setup\Option;
|
||||
use WPML\TM\ATE\JobRecords;
|
||||
use WPML\TM\ATE\Log\Storage;
|
||||
use WPML\TM\ATE\Log\Entry;
|
||||
use function WPML\FP\partialRight;
|
||||
use function WPML\FP\pipe;
|
||||
use WPML\TM\API\ATE\LanguageMappings;
|
||||
use WPML\Element\API\Languages;
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Jobs_Actions implements IWPML_Action {
|
||||
const RESPONSE_ATE_NOT_ACTIVE_ERROR = 403;
|
||||
const RESPONSE_ATE_DUPLICATED_SOURCE_ID = 417;
|
||||
const RESPONSE_ATE_UNEXPECTED_ERROR = 500;
|
||||
|
||||
const RESPONSE_ATE_ERROR_NOTICE_ID = 'ate-update-error';
|
||||
const RESPONSE_ATE_ERROR_NOTICE_GROUP = 'default';
|
||||
|
||||
const CREATE_ATE_JOB_CHUNK_WORDS_LIMIT = 2000;
|
||||
|
||||
/**
|
||||
* @var WPML_TM_ATE_API
|
||||
*/
|
||||
private $ate_api;
|
||||
/**
|
||||
* @var WPML_TM_ATE_Jobs
|
||||
*/
|
||||
private $ate_jobs;
|
||||
|
||||
/**
|
||||
* @var WPML_TM_AMS_Translator_Activation_Records
|
||||
*/
|
||||
private $translator_activation_records;
|
||||
|
||||
/** @var bool */
|
||||
private $is_second_attempt_to_get_jobs_data = false;
|
||||
/**
|
||||
* @var SitePress
|
||||
*/
|
||||
private $sitepress;
|
||||
/**
|
||||
* @var WPML_Current_Screen
|
||||
*/
|
||||
private $current_screen;
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_Jobs_Actions constructor.
|
||||
*
|
||||
* @param \WPML_TM_ATE_API $ate_api
|
||||
* @param \WPML_TM_ATE_Jobs $ate_jobs
|
||||
* @param \SitePress $sitepress
|
||||
* @param \WPML_Current_Screen $current_screen
|
||||
* @param \WPML_TM_AMS_Translator_Activation_Records $translator_activation_records
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_TM_ATE_API $ate_api,
|
||||
WPML_TM_ATE_Jobs $ate_jobs,
|
||||
SitePress $sitepress,
|
||||
WPML_Current_Screen $current_screen,
|
||||
WPML_TM_AMS_Translator_Activation_Records $translator_activation_records
|
||||
|
||||
) {
|
||||
$this->ate_api = $ate_api;
|
||||
$this->ate_jobs = $ate_jobs;
|
||||
$this->sitepress = $sitepress;
|
||||
$this->current_screen = $current_screen;
|
||||
$this->translator_activation_records = $translator_activation_records;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_added_translation_job', [ $this, 'added_translation_job' ], 10, 2 );
|
||||
add_action( 'wpml_added_translation_jobs', [ $this, 'added_translation_jobs' ], 10, 2 );
|
||||
add_action( 'admin_notices', [ $this, 'handle_messages' ] );
|
||||
|
||||
add_filter( 'wpml_tm_ate_jobs_data', [ $this, 'get_ate_jobs_data_filter' ], 10, 2 );
|
||||
add_filter( 'wpml_tm_ate_jobs_editor_url', [ $this, 'get_editor_url' ], 10, 3 );
|
||||
}
|
||||
|
||||
public function handle_messages() {
|
||||
if ( $this->current_screen->id_ends_with( WPML_TM_FOLDER . '/menu/translations-queue' ) ) {
|
||||
|
||||
if ( array_key_exists( 'message', $_GET ) ) {
|
||||
if ( array_key_exists( 'ate_job_id', $_GET ) ) {
|
||||
$ate_job_id = filter_var( $_GET['ate_job_id'], FILTER_SANITIZE_NUMBER_INT );
|
||||
|
||||
$this->resign_job_on_error( $ate_job_id );
|
||||
}
|
||||
$message = Sanitize::stringProp( 'message', $_GET );
|
||||
?>
|
||||
|
||||
<div class="error notice-error notice otgs-notice">
|
||||
<p><?php echo $message; ?></p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $job_id
|
||||
* @param string $translation_service
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function added_translation_job( $job_id, $translation_service ) {
|
||||
$this->added_translation_jobs( array( $translation_service => array( $job_id ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $jobs
|
||||
* @param int|null $sentFrom
|
||||
*
|
||||
* @return bool|void
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function added_translation_jobs( array $jobs, $sentFrom = null ) {
|
||||
$oldEditor = wpml_tm_load_old_jobs_editor();
|
||||
$job_ids = Fns::reject( [ $oldEditor, 'shouldStickToWPMLEditor' ], Obj::propOr( [], 'local', $jobs ) );
|
||||
|
||||
if ( ! $job_ids ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$jobs = Fns::map( 'wpml_tm_create_ATE_job_creation_model', $job_ids );
|
||||
|
||||
$responses = Fns::map(
|
||||
Fns::unary( partialRight( [ $this, 'create_jobs' ], $sentFrom ) ),
|
||||
$this->getChunkedJobs( $jobs )
|
||||
);
|
||||
$created_jobs = $this->getResponsesJobs( $responses, $jobs );
|
||||
|
||||
if ( $created_jobs ) {
|
||||
|
||||
$created_jobs = $this->map_response_jobs( $created_jobs );
|
||||
|
||||
$this->ate_jobs->warm_cache( array_keys( $created_jobs ) );
|
||||
|
||||
foreach ( $created_jobs as $wpml_job_id => $ate_job_id ) {
|
||||
$this->ate_jobs->store( $wpml_job_id, [ JobRecords::FIELD_ATE_JOB_ID => $ate_job_id ] );
|
||||
$oldEditor->set( $wpml_job_id, WPML_TM_Editors::ATE );
|
||||
$translationJob = wpml_tm_load_job_factory()->get_translation_job( $wpml_job_id, false, 0, true );
|
||||
$jobType = $this->getJobType( $translationJob );
|
||||
wpml_tm_load_job_factory()->update_job_data(
|
||||
$wpml_job_id,
|
||||
[ 'automatic' => $jobType === 'auto' ? 1 : 0 ]
|
||||
);
|
||||
|
||||
if ( $sentFrom === Jobs::SENT_RETRY ) {
|
||||
Jobs::setStatus( $wpml_job_id, ICL_TM_WAITING_FOR_TRANSLATOR );
|
||||
}
|
||||
}
|
||||
|
||||
$message = __( '%1$s jobs added to the Advanced Translation Editor.', 'wpml-translation-management' );
|
||||
$this->add_message( 'updated', sprintf( $message, count( $created_jobs ) ), 'wpml_tm_ate_create_job' );
|
||||
} else {
|
||||
if ( Lst::includes( $sentFrom, [ Jobs::SENT_AUTOMATICALLY, Jobs::SENT_RETRY ] ) ) {
|
||||
if ( $sentFrom === Jobs::SENT_RETRY ) {
|
||||
$updateJob = function ($jobId) {
|
||||
Jobs::incrementRetryCount($jobId);
|
||||
$this->logRetryError( $jobId );
|
||||
};
|
||||
} else {
|
||||
$updateJob = function ( $jobId ) use ( $oldEditor ) {
|
||||
$this->logError( $jobId );
|
||||
|
||||
$translationJob = wpml_tm_load_job_factory()->get_translation_job( $jobId, false, 0, true );
|
||||
$jobType = $this->getJobType( $translationJob );
|
||||
if ( $jobType === 'auto' ) {
|
||||
Jobs::setStatus( $jobId, ICL_TM_ATE_NEEDS_RETRY );
|
||||
$oldEditor->set( $jobId, WPML_TM_Editors::ATE );
|
||||
wpml_tm_load_job_factory()->update_job_data( $jobId, [ 'automatic' => 1 ] );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
wpml_collect( $job_ids )->map( $updateJob );
|
||||
}
|
||||
|
||||
$this->add_message(
|
||||
'error',
|
||||
__(
|
||||
'Jobs could not be created in Advanced Translation Editor. Please try again or contact the WPML support for help.',
|
||||
'wpml-translation-management'
|
||||
),
|
||||
'wpml_tm_ate_create_job'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function map_response_jobs( $responseJobs ) {
|
||||
$result = [];
|
||||
foreach ( $responseJobs as $rid => $ate_job_id ) {
|
||||
$jobId = \WPML\TM\API\Job\Map::fromRid( $rid );
|
||||
if ( $jobId ) {
|
||||
$result[ $jobId ] = $ate_job_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $message
|
||||
* @param string|null $id
|
||||
*/
|
||||
private function add_message( $type, $message, $id = null ) {
|
||||
do_action( 'wpml_tm_basket_add_message', $type, $message, $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $jobsData
|
||||
* @param int|null $sentFrom
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function create_jobs( array $jobsData, $sentFrom ) {
|
||||
$setJobType = Logic::ifElse( Fns::always( $sentFrom ), Obj::assoc( 'job_type', $sentFrom ), Fns::identity() );
|
||||
|
||||
list( $existing, $new ) = Lst::partition(
|
||||
pipe( Obj::propOr( null, 'existing_ate_id' ), Logic::isNotNull() ),
|
||||
$jobsData['jobs']
|
||||
);
|
||||
|
||||
$isAuto = Relation::propEq( 'type', 'auto', $jobsData );
|
||||
|
||||
return Wrapper::of( [ 'jobs' => $new, 'existing_jobs' => Lst::pluck( 'existing_ate_id', $existing ) ] )
|
||||
->map( Obj::assoc( 'auto_translate', $isAuto && Option::shouldTranslateEverything() ) )
|
||||
->map( Obj::assoc( 'preview', $isAuto && Option::shouldBeReviewed() ) )
|
||||
->map( $setJobType )
|
||||
->map( 'wp_json_encode' )
|
||||
->map( Json::toArray() )
|
||||
->map( [ $this->ate_api, 'create_jobs' ] )
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* After implementation of wpmltm-3211 and wpmltm-3391, we should not find missing ATE IDs anymore.
|
||||
* Some code below seems dead but we'll keep it for now in case we are missing a specific context.
|
||||
*
|
||||
* @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3211
|
||||
* @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3391
|
||||
*/
|
||||
private function get_ate_jobs_data( array $translation_jobs ) {
|
||||
$ate_jobs_data = array();
|
||||
$skip_getting_data = false;
|
||||
$ate_jobs_to_create = array();
|
||||
|
||||
$this->ate_jobs->warm_cache( wpml_collect( $translation_jobs )->pluck( 'job_id' )->toArray() );
|
||||
|
||||
foreach ( $translation_jobs as $translation_job ) {
|
||||
if ( $this->is_ate_translation_job( $translation_job ) ) {
|
||||
$ate_job_id = $this->get_ate_job_id( $translation_job->job_id );
|
||||
// Start of possibly dead code.
|
||||
if ( ! $ate_job_id ) {
|
||||
$ate_jobs_to_create[] = $translation_job->job_id;
|
||||
$skip_getting_data = true;
|
||||
}
|
||||
// End of possibly dead code.
|
||||
|
||||
if ( ! $skip_getting_data ) {
|
||||
$ate_jobs_data[ $translation_job->job_id ] = [ 'ate_job_id' => $ate_job_id ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start of possibly dead code.
|
||||
if (
|
||||
! $this->is_second_attempt_to_get_jobs_data &&
|
||||
$ate_jobs_to_create &&
|
||||
$this->added_translation_jobs( array( 'local' => $ate_jobs_to_create ) )
|
||||
) {
|
||||
$ate_jobs_data = $this->get_ate_jobs_data( $translation_jobs );
|
||||
$this->is_second_attempt_to_get_jobs_data = true;
|
||||
}
|
||||
// End of possibly dead code.
|
||||
|
||||
return $ate_jobs_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $default_url
|
||||
* @param int $job_id
|
||||
* @param null|string $return_url
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function get_editor_url( $default_url, $job_id, $return_url = null ) {
|
||||
$isUserActivated = $this->translator_activation_records->is_current_user_activated();
|
||||
|
||||
if ( $isUserActivated || is_admin() ) {
|
||||
$ate_job_id = $this->ate_jobs->get_ate_job_id( $job_id );
|
||||
if ( $ate_job_id ) {
|
||||
if ( ! $return_url ) {
|
||||
$return_url = add_query_arg(
|
||||
array(
|
||||
'page' => WPML_TM_FOLDER . '/menu/translations-queue.php',
|
||||
'ate-return-job' => $job_id,
|
||||
),
|
||||
admin_url( '/admin.php' )
|
||||
);
|
||||
}
|
||||
$ate_job_url = $this->ate_api->get_editor_url( $ate_job_id, $return_url );
|
||||
if ( $ate_job_url && ! is_wp_error( $ate_job_url ) ) {
|
||||
return $ate_job_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $default_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ignore
|
||||
* @param array $translation_jobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_ate_jobs_data_filter( $ignore, array $translation_jobs ) {
|
||||
return $this->get_ate_jobs_data( $translation_jobs );
|
||||
}
|
||||
|
||||
private function get_ate_job_id( $job_id ) {
|
||||
return $this->ate_jobs->get_ate_job_id( $job_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $response
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function check_response_error( $response ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$code = 0;
|
||||
$message = $response->get_error_message();
|
||||
if ( $response->error_data && is_array( $response->error_data ) ) {
|
||||
foreach ( $response->error_data as $http_code => $error_data ) {
|
||||
$code = $error_data[0]['status'];
|
||||
$message = '';
|
||||
|
||||
switch ( (int) $code ) {
|
||||
case self::RESPONSE_ATE_NOT_ACTIVE_ERROR:
|
||||
$wp_admin_url = admin_url( 'admin.php' );
|
||||
$mcsetup_page = add_query_arg(
|
||||
array(
|
||||
'page' => WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS,
|
||||
'sm' => 'mcsetup',
|
||||
),
|
||||
$wp_admin_url
|
||||
);
|
||||
$mcsetup_page .= '#ml-content-setup-sec-1';
|
||||
|
||||
$resend_link = '<a href="' . $mcsetup_page . '">'
|
||||
. esc_html__( 'Resend that email', 'wpml-translation-management' )
|
||||
. '</a>';
|
||||
$message .= '<p>'
|
||||
. esc_html__( 'WPML cannot send these documents to translation because the Advanced Translation Editor is not fully set-up yet.', 'wpml-translation-management' )
|
||||
. '</p><p>'
|
||||
. esc_html__( 'Please open the confirmation email that you received and click on the link inside it to confirm your email.', 'wpml-translation-management' )
|
||||
. '</p><p>'
|
||||
. $resend_link
|
||||
. '</p>';
|
||||
break;
|
||||
case self::RESPONSE_ATE_DUPLICATED_SOURCE_ID:
|
||||
case self::RESPONSE_ATE_UNEXPECTED_ERROR:
|
||||
default:
|
||||
$message = '<p>'
|
||||
. __( 'Advanced Translation Editor error:', 'wpml-translation-management' )
|
||||
. '</p><p>'
|
||||
. $error_data[0]['message']
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
$message = '<p>' . $message . '</p>';
|
||||
}
|
||||
}
|
||||
/** @var WP_Error $response */
|
||||
throw new RuntimeException( $message, $code );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ate_job_id
|
||||
*/
|
||||
private function resign_job_on_error( $ate_job_id ) {
|
||||
$job_id = $this->ate_jobs->get_wpml_job_id( $ate_job_id );
|
||||
if ( $job_id ) {
|
||||
wpml_load_core_tm()->resign_translator( $job_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $translation_job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_ate_translation_job( $translation_job ) {
|
||||
return 'local' === $translation_job->translation_service
|
||||
&& WPML_TM_Editors::ATE === $translation_job->editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $responses
|
||||
* @param \WPML_TM_ATE_Models_Job_Create[] $sentJobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getResponsesJobs( $responses, $sentJobs ) {
|
||||
$jobs = [];
|
||||
|
||||
foreach ( $responses as $response ) {
|
||||
try {
|
||||
$this->check_response_error( $response );
|
||||
|
||||
if ( $response && isset( $response->jobs ) ) {
|
||||
$jobs = $jobs + (array) $response->jobs;
|
||||
}
|
||||
} catch ( RuntimeException $ex ) {
|
||||
do_action( 'wpml_tm_basket_add_message', 'error', $ex->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
$existingJobs = wpml_collect( $sentJobs )
|
||||
->filter( Obj::prop( 'existing_ate_id' ) )
|
||||
->map( Obj::pick( [ 'source_id', 'existing_ate_id' ] ) )
|
||||
->keyBy( 'source_id' )
|
||||
->map( Obj::prop( 'existing_ate_id' ) )
|
||||
->toArray();
|
||||
|
||||
return $jobs + $existingJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_TM_ATE_Models_Job_Create[] $jobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getChunkedJobs( $jobs ) {
|
||||
$chunkedJobs = [];
|
||||
$currentChunk = -1;
|
||||
$currentWordCount = 0;
|
||||
$chunkType = 'auto';
|
||||
|
||||
$newChunk = function( $chunkType ) use ( &$chunkedJobs, &$currentChunk, &$currentWordCount ) {
|
||||
$currentChunk ++;
|
||||
$currentWordCount = 0;
|
||||
$chunkedJobs[ $currentChunk ] = [ 'type' => $chunkType, 'jobs' => [] ];
|
||||
};
|
||||
|
||||
$newChunk( $chunkType );
|
||||
|
||||
foreach ( $jobs as $job ) {
|
||||
/** @var WPML_Element_Translation_Job $translationJob */
|
||||
|
||||
$translationJob = wpml_tm_load_job_factory()->get_translation_job( $job->id, false, 0, true );
|
||||
if ( $translationJob ) {
|
||||
|
||||
if ( ! Obj::prop( 'existing_ate_id', $job ) ) {
|
||||
$currentWordCount += $translationJob->estimate_word_count();
|
||||
}
|
||||
|
||||
$jobType = $this->getJobType( $translationJob );
|
||||
if ( $jobType !== $chunkType ) {
|
||||
$chunkType = $jobType;
|
||||
$newChunk( $chunkType );
|
||||
}
|
||||
if ( $currentWordCount > self::CREATE_ATE_JOB_CHUNK_WORDS_LIMIT && count( $chunkedJobs[ $currentChunk ] ) > 0 ) {
|
||||
$newChunk( $chunkType );
|
||||
}
|
||||
}
|
||||
|
||||
$chunkedJobs[ $currentChunk ]['jobs'] [] = $job;
|
||||
|
||||
}
|
||||
|
||||
$hasJobs = pipe( Obj::prop( 'jobs' ), Lst::length() );
|
||||
|
||||
return Fns::filter( $hasJobs, $chunkedJobs );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $jobId
|
||||
*/
|
||||
private function logRetryError( $jobId ) {
|
||||
$job = Jobs::get( $jobId );
|
||||
if ( $job && $job->ate_comm_retry_count ) {
|
||||
Storage::add( Entry::retryJob( $jobId,
|
||||
[
|
||||
'retry_count' => $job->ate_comm_retry_count
|
||||
]
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $jobId
|
||||
*/
|
||||
private function logError( $jobId ) {
|
||||
$job = Jobs::get( $jobId );
|
||||
if ( $job ) {
|
||||
Storage::add( Entry::retryJob( $jobId, [
|
||||
'retry_count' => 0,
|
||||
'comment' => 'Sending job to ate failed, queued to be sent again.',
|
||||
]
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
private function getJobType( $translationJob ) {
|
||||
$document = $translationJob->get_original_document();
|
||||
if ( ! $document || $document instanceof WPML_Package ) {
|
||||
return 'manual';
|
||||
} else {
|
||||
return $translationJob->get_source_language_code() === Languages::getDefaultCode() &&
|
||||
Jobs::isEligibleForAutomaticTranslations( $translationJob->get_id() ) ? 'auto' : 'manual';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo Perhaps this class is redundant
|
||||
*
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Jobs_Store_Actions_Factory implements IWPML_Backend_Action_Loader {
|
||||
|
||||
/**
|
||||
* @return IWPML_Action|IWPML_Action[]|null
|
||||
*/
|
||||
public function create() {
|
||||
if ( WPML_TM_ATE_Status::is_enabled() ) {
|
||||
|
||||
$ate_jobs_records = wpml_tm_get_ate_job_records();
|
||||
$ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records );
|
||||
|
||||
return new WPML_TM_ATE_Jobs_Store_Actions( $ate_jobs );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @todo The hook 'wpml_tm_ate_jobs_store' seems to be never used so this class and its factory may be obsolete
|
||||
*
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Jobs_Store_Actions implements IWPML_Action {
|
||||
/**
|
||||
* @var WPML_TM_ATE_Jobs
|
||||
*/
|
||||
private $ate_jobs;
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_Jobs_Actions constructor.
|
||||
*
|
||||
* @param WPML_TM_ATE_Jobs $ate_jobs
|
||||
*/
|
||||
public function __construct( WPML_TM_ATE_Jobs $ate_jobs ) {
|
||||
$this->ate_jobs = $ate_jobs;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_tm_ate_jobs_store', array( $this, 'store' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $wpml_job_id
|
||||
* @param array $ate_job_data
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function store( $wpml_job_id, $ate_job_data ) {
|
||||
return $this->ate_jobs->store( $wpml_job_id, $ate_job_data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Post_Edit_Actions_Factory implements IWPML_Backend_Action_Loader {
|
||||
|
||||
/**
|
||||
* @return IWPML_Action|IWPML_Action[]|null
|
||||
*/
|
||||
public function create() {
|
||||
$tm_ate = new WPML_TM_ATE();
|
||||
$endpoints = WPML\Container\make( 'WPML_TM_ATE_AMS_Endpoints' );
|
||||
|
||||
if ( $tm_ate->is_translation_method_ate_enabled() ) {
|
||||
return new WPML_TM_ATE_Post_Edit_Actions( $endpoints );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
|
||||
class WPML_TM_ATE_Post_Edit_Actions implements IWPML_Action {
|
||||
private $endpoints;
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_Jobs_Actions constructor.
|
||||
*
|
||||
* @param WPML_TM_ATE_AMS_Endpoints $endpoints
|
||||
*/
|
||||
public function __construct( WPML_TM_ATE_AMS_Endpoints $endpoints ) {
|
||||
$this->endpoints = $endpoints;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
|
||||
}
|
||||
|
||||
public function allowed_redirect_hosts( $hosts ) {
|
||||
$hosts[] = $this->endpoints->get_AMS_host();
|
||||
$hosts[] = $this->endpoints->get_ATE_host();
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Required_Actions_Base {
|
||||
private $ate_enabled;
|
||||
|
||||
protected function is_ate_enabled() {
|
||||
if ( null === $this->ate_enabled ) {
|
||||
$tm_settings = wpml_get_setting_filter( null, 'translation-management' );
|
||||
$doc_translation_method = null;
|
||||
if ( array_key_exists( 'doc_translation_method', $tm_settings ) ) {
|
||||
$doc_translation_method = $tm_settings['doc_translation_method'];
|
||||
}
|
||||
$this->ate_enabled = $doc_translation_method === ICL_TM_TMETHOD_ATE;
|
||||
}
|
||||
|
||||
return $this->ate_enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* \WPML_TM_ATE_Translator_Login factory.
|
||||
*
|
||||
* @author OnTheGo Systems
|
||||
*
|
||||
* NOTE: This uses the Frontend loader because is_admin() returns false during wp_login
|
||||
*/
|
||||
class WPML_TM_ATE_Translator_Login_Factory implements IWPML_Frontend_Action_Loader {
|
||||
|
||||
/**
|
||||
* It returns an instance of WPML_TM_ATE_Translator_Login is ATE is enabled and active.
|
||||
*
|
||||
* @return \WPML_TM_ATE_Translator_Logine|\IWPML_Frontend_Action_Loader|null
|
||||
*/
|
||||
public function create() {
|
||||
if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) {
|
||||
return WPML\Container\make( WPML_TM_ATE_Translator_Login::class );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class WPML_TM_ATE_Translator_Login implements IWPML_Action {
|
||||
|
||||
/** @var WPML_TM_AMS_Translator_Activation_Records */
|
||||
private $translator_activation_records;
|
||||
|
||||
/** @var WPML_Translator_Records */
|
||||
private $translator_records;
|
||||
|
||||
/** @var WPML_TM_AMS_API */
|
||||
private $ams_api;
|
||||
|
||||
public function __construct(
|
||||
WPML_TM_AMS_Translator_Activation_Records $translator_activation_records,
|
||||
WPML_Translator_Records $translator_records,
|
||||
WPML_TM_AMS_API $ams_api
|
||||
) {
|
||||
$this->translator_activation_records = $translator_activation_records;
|
||||
$this->translator_records = $translator_records;
|
||||
$this->ams_api = $ams_api;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wp_login', array( $this, 'wp_login' ), 10, 2 );
|
||||
}
|
||||
|
||||
public function wp_login( $user_login, $user ) {
|
||||
if ( $this->translator_records->does_user_have_capability( $user->ID ) ) {
|
||||
$result = $this->ams_api->is_subscription_activated( $user->user_email );
|
||||
if ( ! is_wp_error( $result ) ) {
|
||||
$this->translator_activation_records->set_activated(
|
||||
$user->user_email,
|
||||
$result
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
use WPML\API\Sanitize;
|
||||
|
||||
class WPML_TM_ATE_Translator_Message_Classic_Editor_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader {
|
||||
|
||||
/**
|
||||
* @return \WPML_TM_ATE_Translator_Message_Classic_Editor|\IWPML_Action|null
|
||||
*/
|
||||
public function create() {
|
||||
global $wpdb;
|
||||
|
||||
if ( $this->is_ajax_or_translation_queue() && $this->is_ate_enabled_and_manager_wizard_completed() && ! $this->is_editing_old_translation_and_te_is_used_for_old_translation() ) {
|
||||
|
||||
$email_twig_factory = wpml_tm_get_email_twig_template_factory();
|
||||
|
||||
return new WPML_TM_ATE_Translator_Message_Classic_Editor(
|
||||
new WPML_Translation_Manager_Records(
|
||||
$wpdb,
|
||||
wpml_tm_get_wp_user_query_factory(),
|
||||
wp_roles()
|
||||
),
|
||||
wpml_tm_get_wp_user_factory(),
|
||||
new WPML_TM_ATE_Request_Activation_Email(
|
||||
new WPML_TM_Email_Notification_View( $email_twig_factory->create() )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function is_editing_old_translation_and_te_is_used_for_old_translation() {
|
||||
return Sanitize::stringProp( 'job_id', $_GET )
|
||||
&& get_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME ) === WPML_TM_Editors::WPML; }
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function is_ate_enabled_and_manager_wizard_completed() {
|
||||
return WPML_TM_ATE_Status::is_enabled_and_activated() && (bool) get_option( WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function is_ajax_or_translation_queue() {
|
||||
return wpml_is_ajax() || WPML_TM_Page::is_translation_queue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
use WPML\DocPage;
|
||||
|
||||
class WPML_TM_ATE_Translator_Message_Classic_Editor implements IWPML_Action {
|
||||
|
||||
const ACTION = 'wpml_ate_translator_classic_editor';
|
||||
const USER_OPTION = 'wpml_ate_translator_classic_editor_minimized';
|
||||
|
||||
/** @var WPML_Translation_Manager_Records */
|
||||
private $translation_manager_records;
|
||||
|
||||
/** @var WPML_WP_User_Factory */
|
||||
private $user_factory;
|
||||
|
||||
/** @var WPML_TM_ATE_Request_Activation_Email */
|
||||
private $activation_email;
|
||||
|
||||
public function __construct(
|
||||
WPML_Translation_Manager_Records $translation_manager_records,
|
||||
WPML_WP_User_Factory $user_factory,
|
||||
WPML_TM_ATE_Request_Activation_Email $activation_email
|
||||
) {
|
||||
$this->translation_manager_records = $translation_manager_records;
|
||||
$this->user_factory = $user_factory;
|
||||
$this->activation_email = $activation_email;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_tm_editor_messages', array( $this, 'classic_editor_message' ) );
|
||||
add_action( 'wp_ajax_' . self::ACTION, array( $this, 'handle_ajax' ) );
|
||||
}
|
||||
|
||||
public function classic_editor_message() {
|
||||
$main_message = esc_html__( "This site can use WPML's Advanced Translation Editor, but you did not receive permission to use it. You are still translating with WPML's classic translation editor. Please ask your site's Translation Manager to enable the Advanced Translation Editor for you.", 'wpml-translation-management' );
|
||||
$learn_more = esc_html__( "Learn more about WPML's Advanced Translation Editor", 'wpml-translation-management' );
|
||||
$short_message = esc_html__( 'Advanced Translation Editor is disabled.', 'wpml-translation-management' );
|
||||
$more = esc_html__( 'More', 'wpml-translation-management' );
|
||||
$request_activation = esc_html__( 'Request activation from', 'wpml-translation-management' );
|
||||
$link = DocPage::aboutATE();
|
||||
|
||||
$show_minimized = (bool) $this->user_factory->create_current()->get_option( self::USER_OPTION );
|
||||
|
||||
?>
|
||||
<div
|
||||
class="notice notice-info otgs-notice js-classic-editor-notice"
|
||||
data-nonce="<?php echo wp_create_nonce( self::ACTION ); ?>"
|
||||
data-action="<?php echo self::ACTION; ?>"
|
||||
<?php
|
||||
if ( $show_minimized ) {
|
||||
?>
|
||||
style="display: none" <?php } ?>
|
||||
>
|
||||
<p><?php echo $main_message; ?></p>
|
||||
<p><a href="<?php echo esc_attr( $link ); ?>" class="wpml-external-link" target="_blank"><?php echo $learn_more; ?></a></p>
|
||||
<p>
|
||||
<a class="button js-request-activation"><?php echo $request_activation; ?></a> <?php $this->output_translation_manager_list(); ?>
|
||||
</p>
|
||||
<p class="js-email-sent" style="display: none"></p>
|
||||
|
||||
<a class="js-minimize otgs-notice-toggle">
|
||||
<?php esc_html_e( 'Minimize', 'wpml-translation-management' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notice notice-info otgs-notice js-classic-editor-notice-minimized"
|
||||
<?php
|
||||
if ( ! $show_minimized ) {
|
||||
?>
|
||||
style="display: none" <?php } ?>
|
||||
>
|
||||
<p><?php echo $short_message; ?> <a class="js-maximize"><?php echo $more; ?></a></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function output_translation_manager_list() {
|
||||
$translation_managers = $this->translation_manager_records->get_users_with_capability();
|
||||
?>
|
||||
|
||||
<select class="js-translation-managers">
|
||||
<?php
|
||||
foreach ( $translation_managers as $translation_manager ) {
|
||||
$display_name = $translation_manager->user_login . ' (' . $translation_manager->user_email . ')';
|
||||
?>
|
||||
<option
|
||||
value="<?php echo $translation_manager->ID; ?> "><?php echo $display_name; ?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
public function handle_ajax() {
|
||||
if ( wp_verify_nonce( $_POST['nonce'], self::ACTION ) ) {
|
||||
$current_user = $this->user_factory->create_current();
|
||||
|
||||
switch ( $_POST['command'] ) {
|
||||
case 'minimize':
|
||||
$current_user->update_option( self::USER_OPTION, true );
|
||||
wp_send_json_success( array( 'message' => '' ) );
|
||||
|
||||
case 'maximize':
|
||||
$current_user->update_option( self::USER_OPTION, false );
|
||||
wp_send_json_success( array( 'message' => '' ) );
|
||||
|
||||
case 'requestActivation':
|
||||
$manager = $this->user_factory->create( (int) $_POST['manager'] );
|
||||
if ( $this->activation_email->send_email( $manager, $current_user ) ) {
|
||||
$message = sprintf(
|
||||
esc_html__( 'An email has been sent to %s', 'wpml-translation-management' ),
|
||||
$manager->user_login
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
esc_html__( 'Sorry, the email could not be sent to %s for an unknown reason.', 'wpml-translation-management' ),
|
||||
$manager->user_login
|
||||
);
|
||||
}
|
||||
wp_send_json_success( array( 'message' => $message ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Old_Editor_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader {
|
||||
|
||||
public function create() {
|
||||
return new WPML_TM_Old_Editor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Old_Editor implements IWPML_Action {
|
||||
const ACTION = 'icl_ajx_custom_call';
|
||||
|
||||
const CUSTOM_AJAX_CALL = 'icl_doc_translation_method';
|
||||
|
||||
const NOTICE_ID = 'wpml-translation-management-old-editor';
|
||||
|
||||
const NOTICE_GROUP = 'wpml-translation-management';
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( self::ACTION, array( $this, 'handle_custom_ajax_call' ), 10, 2 );
|
||||
}
|
||||
|
||||
public function handle_custom_ajax_call( $call, $data ) {
|
||||
if ( self::CUSTOM_AJAX_CALL === $call ) {
|
||||
if ( ! isset( $data[ WPML_TM_Old_Jobs_Editor::OPTION_NAME ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old_editor = $data[ WPML_TM_Old_Jobs_Editor::OPTION_NAME ];
|
||||
|
||||
if ( ! in_array( $old_editor, array( WPML_TM_Editors::WPML, WPML_TM_Editors::ATE ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME, $old_editor );
|
||||
|
||||
if ( WPML_TM_Editors::WPML === $old_editor && $this->is_ate_enabled_and_manager_wizard_completed() ) {
|
||||
$text = __( 'You activated the Advanced Translation Editor for this site, but you are updating an old translation. WPML opened the Standard Translation Editor, so you can update this translation. When you translate new content, you\'ll get the Advanced Translation Editor with all its features. To change your settings, go to WPML Settings.', 'sitepress' );
|
||||
$notice = new WPML_Notice( self::NOTICE_ID, $text, self::NOTICE_GROUP );
|
||||
$notice->set_css_class_types( 'notice-info' );
|
||||
$notice->set_dismissible( true );
|
||||
$notice->add_display_callback( 'WPML_TM_Page::is_translation_editor_page' );
|
||||
wpml_get_admin_notices()->add_notice( $notice, true );
|
||||
} else {
|
||||
wpml_get_admin_notices()->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function is_ate_enabled_and_manager_wizard_completed() {
|
||||
return WPML_TM_ATE_Status::is_enabled_and_activated() && (bool) get_option( WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER, false );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class JobRecord {
|
||||
|
||||
/** @var int $wpmlJobId */
|
||||
public $wpmlJobId;
|
||||
|
||||
/** @var int $ateJobId */
|
||||
public $ateJobId;
|
||||
|
||||
/**
|
||||
* @todo: Remove this property.
|
||||
*
|
||||
* @var int $editTimestamp
|
||||
*/
|
||||
public $editTimestamp = 0;
|
||||
|
||||
public function __construct( stdClass $dbRow = null ) {
|
||||
if ( $dbRow ) {
|
||||
$this->wpmlJobId = (int) $dbRow->job_id;
|
||||
$this->ateJobId = (int) $dbRow->editor_job_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo: Remove the "$editTimestamp" and "is_editing", not handled on WPML side anymore.
|
||||
*
|
||||
* The job is considered as being edited if
|
||||
* the timestamp is not greater than 1 day.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEditing() {
|
||||
$elapsedTime = time() - $this->editTimestamp;
|
||||
return $elapsedTime < DAY_IN_SECONDS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE;
|
||||
|
||||
use Exception;
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML_TM_ATE_API_Error;
|
||||
use WPML_TM_Editors;
|
||||
|
||||
class JobRecords {
|
||||
|
||||
const FIELD_ATE_JOB_ID = 'ate_job_id';
|
||||
const FIELD_IS_EDITING = 'is_editing';
|
||||
|
||||
/** @var \wpdb $wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/** @var Collection $jobs */
|
||||
private $jobs;
|
||||
|
||||
public function __construct( \wpdb $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->jobs = wpml_collect( [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will retrieve data from the ATE job ID.
|
||||
* Beware of the returned data shape which is not standard.
|
||||
*
|
||||
* @param int $ateJobId
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get_data_from_ate_job_id( $ateJobId ) {
|
||||
$ateJobId = (int) $ateJobId;
|
||||
|
||||
$this->warmCache( [], [ $ateJobId ] );
|
||||
|
||||
$job = $this->jobs->first(
|
||||
function( JobRecord $job ) use ( $ateJobId ) {
|
||||
return $job->ateJobId === $ateJobId;
|
||||
}
|
||||
);
|
||||
|
||||
if ( $job ) {
|
||||
/** @var JobRecord $job */
|
||||
return [
|
||||
'wpml_job_id' => $job->wpmlJobId,
|
||||
'ate_job_data' => [
|
||||
'ate_job_id' => $job->ateJobId,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $wpmlJobId
|
||||
* @param array $ateJobData
|
||||
*/
|
||||
public function store( $wpmlJobId, array $ateJobData ) {
|
||||
$ateJobData['job_id'] = (int) $wpmlJobId;
|
||||
|
||||
$this->warmCache( [ $wpmlJobId ] );
|
||||
$job = $this->jobs->get( $wpmlJobId );
|
||||
|
||||
if ( ! $job ) {
|
||||
$job = new JobRecord();
|
||||
$job->wpmlJobId = (int) $wpmlJobId;
|
||||
}
|
||||
|
||||
if ( isset( $ateJobData[ self::FIELD_ATE_JOB_ID ] ) ) {
|
||||
$job->ateJobId = $ateJobData[ self::FIELD_ATE_JOB_ID ];
|
||||
}
|
||||
|
||||
$this->persist( $job );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JobRecord $job
|
||||
*/
|
||||
public function persist( JobRecord $job ) {
|
||||
$this->jobs->put( $job->wpmlJobId, $job );
|
||||
|
||||
$this->wpdb->update(
|
||||
$this->wpdb->prefix . 'icl_translate_job',
|
||||
[ 'editor_job_id' => $job->ateJobId ],
|
||||
[ 'job_id' => $job->wpmlJobId ],
|
||||
[ '%d' ],
|
||||
[ '%d' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will load in-memory the required jobs.
|
||||
*
|
||||
* @param array $wpmlJobIds
|
||||
* @param array $ateJobIds
|
||||
*/
|
||||
public function warmCache( array $wpmlJobIds, array $ateJobIds = [] ) {
|
||||
$wpmlJobIds = wpml_collect( $wpmlJobIds )->reject( $this->isAlreadyLoaded( 'wpmlJobId' ) )->toArray();
|
||||
$ateJobIds = wpml_collect( $ateJobIds )->reject( $this->isAlreadyLoaded( 'ateJobId' ) )->toArray();
|
||||
|
||||
$where = [];
|
||||
|
||||
if ( $wpmlJobIds ) {
|
||||
$where[] = 'job_id IN(' . wpml_prepare_in( $wpmlJobIds, '%d' ) . ')';
|
||||
}
|
||||
|
||||
if ( $ateJobIds ) {
|
||||
$where[] = 'editor_job_id IN(' . wpml_prepare_in( $ateJobIds, '%d' ) . ')';
|
||||
}
|
||||
|
||||
if ( ! $where ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$whereHasJobIds = implode( ' OR ', $where );
|
||||
|
||||
$rows = $this->wpdb->get_results(
|
||||
"
|
||||
SELECT job_id, editor_job_id
|
||||
FROM {$this->wpdb->prefix}icl_translate_job
|
||||
WHERE editor = '" . WPML_TM_Editors::ATE . "' AND ({$whereHasJobIds})
|
||||
"
|
||||
);
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
$job = new JobRecord( $row );
|
||||
$this->jobs->put( $job->wpmlJobId, $job );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $idPropertyName
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
private function isAlreadyLoaded( $idPropertyName ) {
|
||||
$loadedIds = $this->jobs->pluck( $idPropertyName )->values()->toArray();
|
||||
|
||||
return Lst::includes( Fns::__, $loadedIds );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $wpmlJobId
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_ate_job_id( $wpmlJobId ) {
|
||||
return $this->get( $wpmlJobId )->ateJobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $wpmlJobId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_editing_job( $wpmlJobId ) {
|
||||
return $this->get( $wpmlJobId )->isEditing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $wpmlJobId
|
||||
*
|
||||
* @return JobRecord
|
||||
*/
|
||||
public function get( $wpmlJobId ) {
|
||||
if ( ! $this->jobs->has( $wpmlJobId ) ) {
|
||||
$this->warmCache( [ (int) $wpmlJobId ] );
|
||||
}
|
||||
|
||||
/** @var null|JobRecord $job */
|
||||
$job = $this->jobs->get( $wpmlJobId );
|
||||
|
||||
if ( ! $job || ! $job->ateJobId ) {
|
||||
$this->restoreJobDataFromATE( $wpmlJobId );
|
||||
$job = $this->jobs->get( $wpmlJobId, new JobRecord() );
|
||||
}
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will try to recover the job data from ATE server,
|
||||
* and persist it in the local repository.
|
||||
*
|
||||
* @param int $wpmlJobId
|
||||
*/
|
||||
private function restoreJobDataFromATE( $wpmlJobId ) {
|
||||
$data = apply_filters( 'wpml_tm_ate_job_data_fallback', [], $wpmlJobId );
|
||||
|
||||
if ( $data ) {
|
||||
try {
|
||||
$this->store( $wpmlJobId, $data );
|
||||
} catch ( Exception $e ) {
|
||||
$error_log = new WPML_TM_ATE_API_Error();
|
||||
$error_log->log( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE;
|
||||
|
||||
|
||||
use WPML\Element\API\Languages;
|
||||
use WPML\FP\Cast;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\ATE\Review\ReviewStatus;
|
||||
|
||||
class Jobs {
|
||||
const LONGSTANDING_AT_ATE_SYNC_COUNT = 100;
|
||||
|
||||
/**
|
||||
* @param array $statuses
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getJobsWithStatus( array $statuses ) {
|
||||
if ( ! $statuses ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$needsReviewCondition = '1=0';
|
||||
if ( Lst::includes( ICL_TM_NEEDS_REVIEW, $statuses ) ) {
|
||||
$reviewStatuses = wpml_prepare_in( [ ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ] );
|
||||
$needsReviewCondition = 'translation_status.review_status IN ( ' . $reviewStatuses . ' )';
|
||||
}
|
||||
|
||||
$statuses = \wpml_prepare_in( $statuses, '%d' );
|
||||
$languages = \wpml_prepare_in( Lst::pluck( 'code', Languages::getActive() ) );
|
||||
|
||||
$sql = "
|
||||
SELECT jobs.rid, MAX(jobs.job_id) as jobId, jobs.editor_job_id as ateJobId, jobs.automatic , translation_status.status,
|
||||
translation_status.review_status, jobs.ate_sync_count > " . static::LONGSTANDING_AT_ATE_SYNC_COUNT . " as isLongstanding
|
||||
FROM {$wpdb->prefix}icl_translate_job as jobs
|
||||
INNER JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = jobs.rid
|
||||
INNER JOIN {$wpdb->prefix}icl_translations translations ON translation_status.translation_id = translations.translation_id
|
||||
INNER JOIN {$wpdb->prefix}icl_translations parent_translations ON translations.trid = parent_translations.trid
|
||||
AND parent_translations.source_language_code IS NULL
|
||||
INNER JOIN {$wpdb->prefix}posts posts ON parent_translations.element_id = posts.ID
|
||||
WHERE
|
||||
jobs.editor = %s
|
||||
AND ( translation_status.status IN ({$statuses}) OR $needsReviewCondition )
|
||||
AND translations.language_code IN ({$languages})
|
||||
AND posts.post_status <> 'trash'
|
||||
GROUP BY jobs.rid;
|
||||
";
|
||||
|
||||
return Fns::map( Obj::evolve( [
|
||||
'rid' => Cast::toInt(),
|
||||
'jobId' => Cast::toInt(),
|
||||
'ateJobId' => Cast::toInt(),
|
||||
'automatic' => Cast::toBool(),
|
||||
'status' => Cast::toInt(),
|
||||
'isLongstanding' => Cast::toBool(),
|
||||
] ), $wpdb->get_results( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getJobsToSync() {
|
||||
return self::getJobsWithStatus( [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS, ICL_TM_ATE_NEEDS_RETRY ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getJobsToRetry() {
|
||||
return self::getJobsWithStatus( [ ICL_TM_ATE_NEEDS_RETRY ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getTotal() {
|
||||
global $wpdb;
|
||||
|
||||
$sql = "
|
||||
SELECT COUNT(jobs.job_id)
|
||||
FROM {$wpdb->prefix}icl_translate_job as jobs
|
||||
WHERE jobs.editor = %s
|
||||
";
|
||||
|
||||
return (int) $wpdb->get_var( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isThereJob() {
|
||||
global $wpdb;
|
||||
|
||||
$noOfRowsToFetch = 1;
|
||||
|
||||
$sql = $wpdb->prepare( "SELECT EXISTS(SELECT %d FROM {$wpdb->prefix}icl_translate_job)", $noOfRowsToFetch );
|
||||
|
||||
return boolval( $wpdb->get_var( $sql ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE;
|
||||
|
||||
use WPML\API\Settings;
|
||||
use WPML\DocPage;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\LIB\WP\Hooks;
|
||||
use WPML\LIB\WP\User;
|
||||
use WPML\Setup\Option;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\AutoTranslate;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\CancelJobs;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\EnableATE;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\GetATEJobsToSync;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\GetCredits;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\GetStatus;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\RefreshJobsStatus;
|
||||
use WPML\TM\ATE\AutoTranslate\Endpoint\SyncLock;
|
||||
use WPML\TM\ATE\Download\Queue;
|
||||
use WPML\TM\ATE\Sync\Trigger;
|
||||
use WPML\TM\WP\App\Resources;
|
||||
use WPML\UIPage;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\invoke;
|
||||
|
||||
class Loader implements \IWPML_Backend_Action {
|
||||
|
||||
const JOB_ID_PLACEHOLDER = '###';
|
||||
|
||||
public function add_hooks() {
|
||||
if ( wpml_is_ajax() ) {
|
||||
// Prevent loading this for ajax calls.
|
||||
// All tasks of this class are not relevant for ajax requests. Currently it's loaded by the root plugin.php
|
||||
// which do not separate between ajax and non-ajax calls and loads this whenever is_admin() is true.
|
||||
// Problem: ALL ajax calls return true for is_admin() - also on the frontend and for non logged-in users.
|
||||
// TODO: Remove once wpmltm-4351 is done.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( UIPage::isTMJobs( $_GET ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
\WPML_TM_ATE_Status::is_enabled_and_activated()
|
||||
|| Settings::pathOr( false, [ 'translation-management', 'doc_translation_method' ] ) === ICL_TM_TMETHOD_ATE
|
||||
) {
|
||||
StatusBar::add_hooks();
|
||||
|
||||
Hooks::onAction( 'in_admin_header' )
|
||||
->then( [ self::class, 'showAteConsoleContainer' ] );
|
||||
}
|
||||
|
||||
Hooks::onAction( 'wp_loaded' )
|
||||
->then( [ self::class, 'getData' ] )
|
||||
->then( Resources::enqueueApp( 'ate-jobs-sync' ) )
|
||||
->then( Fns::always( make( \WPML_TM_Scripts_Factory::class ) ) )
|
||||
->then( invoke( 'localize_script' )->with( 'wpml-ate-jobs-sync-ui' ) );
|
||||
|
||||
Hooks::onFilter( 'wpml_tm_get_wpml_auto_translate_container' )
|
||||
->then( [ self::class, 'getWpmlAutoTranslateContainer' ] );
|
||||
}
|
||||
|
||||
public static function getData() {
|
||||
$jobsToSync = Jobs::getJobsToSync();
|
||||
|
||||
$anyJobsExist = Jobs::isThereJob();
|
||||
|
||||
$ateTab = admin_url( UIPage::getTMATE() );
|
||||
|
||||
return [
|
||||
'name' => 'ate_jobs_sync',
|
||||
'data' => [
|
||||
'endpoints' => self::getEndpoints(),
|
||||
'urls' => self::getUrls( $ateTab ),
|
||||
'jobIdPlaceHolder' => self::JOB_ID_PLACEHOLDER,
|
||||
'notices' => StatusBar::getNotices(),
|
||||
'isTranslationManager' => User::getCurrent()->has_cap( \WPML_Manage_Translations_Role::CAPABILITY ),
|
||||
|
||||
'jobsToSync' => $jobsToSync,
|
||||
'anyJobsExist' => $anyJobsExist,
|
||||
'totalJobsCount' => Jobs::getTotal(),
|
||||
'needsReviewCount' => count( Jobs::getJobsWithStatus( [ ICL_TM_NEEDS_REVIEW ] ) ),
|
||||
|
||||
'shouldTranslateEverything' => Option::shouldTranslateEverything() && ! TranslateEverything::isEverythingProcessed( true ),
|
||||
|
||||
'isAutomaticTranslations' => Option::shouldTranslateEverything(),
|
||||
'isSyncRequired' => count( $jobsToSync ),
|
||||
'needsFetchCredit' => Option::shouldTranslateEverything() && UIPage::isTMDashboard( $_GET ),
|
||||
|
||||
'strings' => self::getStrings(),
|
||||
'ateConsole' => self::getAteData( Lst::pluck( 'ateJobId', $jobsToSync ) ),
|
||||
'isAteActive' => \WPML_TM_ATE_Status::is_enabled_and_activated(),
|
||||
'editorMode' => Settings::pathOr( false, [ 'translation-management', 'doc_translation_method' ] ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getNotEnoughCreditPopup() {
|
||||
$isTranslationManager = User::getCurrent()->has_cap( \WPML_Manage_Translations_Role::CAPABILITY );
|
||||
|
||||
$content = $isTranslationManager
|
||||
? __(
|
||||
"There is an issue with automatic translation that needs your attention.",
|
||||
'wpml-translation-management'
|
||||
)
|
||||
: __(
|
||||
" There is an issue with automatic translation that needs attention from a translation manager.",
|
||||
'wpml-translation-management'
|
||||
);
|
||||
|
||||
$fix = __( 'Fix it to continue translating automatically', 'wpml-translation-management' );
|
||||
|
||||
$primaryButton = $isTranslationManager
|
||||
? '<button class="wpml-antd-button wpml-antd-button-primary" onclick="CREDITS_ACTION">' . $fix . '</button>'
|
||||
: '';
|
||||
|
||||
$translate = __( 'Translate content myself', 'wpml-translation-management' );
|
||||
|
||||
$secondaryButton = UIPage::isTMDashboard( $_GET ) || ! $isTranslationManager
|
||||
? ''
|
||||
: '<button class="wpml-antd-button wpml-antd-button-secondary" onclick="window.location.href=\'TRANSLATE_LINK\'">' . $translate . '</button>';
|
||||
|
||||
return '<div class="wpml-not-enough-credit-popup">' .
|
||||
'<p>' . $content . '</p>' .
|
||||
$primaryButton .
|
||||
$secondaryButton .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
public static function showAteConsoleContainer() {
|
||||
echo '<div id="wpml-ate-console-container"></div>';
|
||||
}
|
||||
|
||||
public static function getWpmlAutoTranslateContainer() {
|
||||
return '<div id="wpml-auto-translate" style="display:none">
|
||||
<div class="content"></div>
|
||||
<div class="connect"></div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
private static function getAteData( $ateJobIds ) {
|
||||
$registration_data = make( \WPML_TM_AMS_API::class )->get_registration_data();
|
||||
|
||||
return User::getCurrent()->has_cap( \WPML_Manage_Translations_Role::CAPABILITY )
|
||||
? [
|
||||
'host' => make( \WPML_TM_ATE_AMS_Endpoints::class )->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ),
|
||||
'wpml_host' => get_site_url(),
|
||||
'job_list' => $ateJobIds,
|
||||
'widget_mode' => 'issue_solving',
|
||||
'return_url' => \WPML\TM\API\Jobs::getCurrentUrl(),
|
||||
'secret_key' => Obj::prop( 'secret', $registration_data ),
|
||||
'shared_key' => Obj::prop( 'shared', $registration_data ),
|
||||
'website_uuid' => make( \WPML_TM_ATE_Authentication::class )->get_site_id(),
|
||||
'ui_language' => make( \SitePress::class )->get_user_admin_language( User::getCurrentId() ),
|
||||
'restNonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'container' => '#wpml-ate-console-container',
|
||||
]
|
||||
: false;
|
||||
}
|
||||
|
||||
private static function getEndpoints() {
|
||||
return [
|
||||
'auto-translate' => AutoTranslate::class,
|
||||
'translate-everything' => TranslateEverything::class,
|
||||
'getCredits' => GetCredits::class,
|
||||
'enableATE' => EnableATE::class,
|
||||
'getATEJobsToSync' => GetATEJobsToSync::class,
|
||||
'syncLock' => SyncLock::class,
|
||||
];
|
||||
}
|
||||
|
||||
private static function getUrls( $ateTab ) {
|
||||
return [
|
||||
'editor' => \WPML_TM_Translation_Status_Display::get_link_for_existing_job( self::JOB_ID_PLACEHOLDER ),
|
||||
'ateams' => $ateTab,
|
||||
'automaticSettings' => \admin_url( UIPage::getSettings() ),
|
||||
'translateAutomaticallyDoc' => DocPage::getTranslateAutomatically(),
|
||||
'ateConsole' => make( \WPML_TM_ATE_AMS_Endpoints::class )
|
||||
->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ) . '/mini_app/main.js',
|
||||
'translationQueue' => \add_query_arg(
|
||||
[ 'status' => ICL_TM_NEEDS_REVIEW ],
|
||||
\admin_url( UIPage::getTranslationQueue() )
|
||||
),
|
||||
'currentUrl' => \WPML\TM\API\Jobs::getCurrentUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function getStrings() {
|
||||
return [
|
||||
'tooltip' => __(
|
||||
'Processing translation (could take a few minutes)',
|
||||
'wpml-translation-management'
|
||||
),
|
||||
'refreshing' => __( 'Refreshing translation status', 'wpml-translation-management' ),
|
||||
'inProgress' => __( 'Translation in progress', 'wpml-translation-management' ),
|
||||
'editTranslation' => __( 'Edit translation', 'wpml-translation-management' ),
|
||||
'status' => __( 'Processing translation', 'wpml-translation-management' ),
|
||||
'automaticTranslation' => __( 'This content is being automatically translated. If you want to do something different with it cancel translation jobs first.', 'wpml-translation-management' ),
|
||||
'notEnoughCredit' => self::getNotEnoughCreditPopup(),
|
||||
'cancelled' => __( 'Translation has been cancelled', 'wpml-translation-management' ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
class Entry {
|
||||
|
||||
/**
|
||||
* @var int $timestamp The log's creation timestamp.
|
||||
*/
|
||||
public $timestamp = 0;
|
||||
|
||||
/**
|
||||
* @see EventsTypes
|
||||
*
|
||||
* @var int $eventType The event code that triggered the log.
|
||||
*/
|
||||
public $eventType = 0;
|
||||
|
||||
/**
|
||||
* @var string $description The details of the log (e.g. exception message).
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* @var int $wpmlJobId [Optional] The WPML Job ID (when applies).
|
||||
*/
|
||||
public $wpmlJobId = 0;
|
||||
|
||||
/**
|
||||
* @var int $ateJobId [Optional] The ATE Job ID (when applies).
|
||||
*/
|
||||
public $ateJobId = 0;
|
||||
|
||||
/**
|
||||
* @var array $extraData [Optional] Complementary serialized data (e.g. API request/response data).
|
||||
*/
|
||||
public $extraData = [];
|
||||
|
||||
/**
|
||||
* @param array $item
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public function __construct( array $item = null ) {
|
||||
if ( $item ) {
|
||||
$this->timestamp = (int) $item['timestamp'];
|
||||
$this->eventType = (int) ( isset( $item['eventType'] ) ? $item['eventType'] : $item['event'] );
|
||||
$this->description = $item['description'];
|
||||
$this->wpmlJobId = (int) $item['wpmlJobId'];
|
||||
$this->ateJobId = (int) $item['ateJobId'];
|
||||
$this->extraData = (array) $item['extraData'];
|
||||
}
|
||||
}
|
||||
|
||||
public static function createForType($eventType, $extraData) {
|
||||
$entry = new self();
|
||||
$entry->eventType = $eventType;
|
||||
$entry->extraData = $extraData;
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
public static function retryJob( $wpmlJobId, $extraData ) {
|
||||
$entry = self::createForType(EventsTypes::JOB_RETRY, $extraData);
|
||||
$entry->wpmlJobId = $wpmlJobId;
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormattedDate() {
|
||||
return date_i18n( 'Y/m/d g:i:s A', $this->timestamp );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExtraDataToString() {
|
||||
return json_encode( $this->extraData );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
class EventsTypes {
|
||||
|
||||
/** Communication errors */
|
||||
const SERVER_ATE = 1;
|
||||
const SERVER_AMS = 2;
|
||||
const SERVER_XLIFF = 3;
|
||||
|
||||
/** Internal errors */
|
||||
const JOB_DOWNLOAD = 10;
|
||||
|
||||
/** Retry */
|
||||
const JOB_RETRY = 20;
|
||||
const SITE_REGISTRATION_RETRY = 21;
|
||||
|
||||
/** Sync */
|
||||
const JOBS_SYNC = 30;
|
||||
|
||||
public static function getLabel( $eventType ) {
|
||||
return wpml_collect(
|
||||
[
|
||||
EventsTypes::SERVER_ATE => 'ATE Server Communication',
|
||||
EventsTypes::SERVER_AMS => 'AMS Server Communication',
|
||||
EventsTypes::SERVER_XLIFF => 'XLIFF Server Communication',
|
||||
EventsTypes::JOB_DOWNLOAD => 'Job Download',
|
||||
EventsTypes::JOB_RETRY => 'Job resent to ATE',
|
||||
EventsTypes::SITE_REGISTRATION_RETRY => 'Site registration request resent to ATE',
|
||||
EventsTypes::JOBS_SYNC => 'Jobs sync request sent to ATE failed',
|
||||
]
|
||||
)->get( $eventType, '' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
class Hooks implements \IWPML_Backend_Action, \IWPML_DIC_Action {
|
||||
|
||||
const SUBMENU_HANDLE = 'wpml-tm-ate-log';
|
||||
|
||||
/** @var ViewFactory $viewFactory */
|
||||
private $viewFactory;
|
||||
|
||||
public function __construct( ViewFactory $viewFactory ) {
|
||||
$this->viewFactory = $viewFactory;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_support_page_after', [ $this, 'renderSupportSection' ] );
|
||||
add_action( 'admin_menu', [ $this, 'addLogSubmenuPage' ] );
|
||||
}
|
||||
|
||||
public function renderSupportSection() {
|
||||
$this->viewFactory->create()->renderSupportSection();
|
||||
}
|
||||
|
||||
public function addLogSubmenuPage() {
|
||||
add_submenu_page(
|
||||
WPML_PLUGIN_FOLDER . '/menu/support.php',
|
||||
__( 'Advanced Translation Editor Error Logs', 'wpml-translation-management' ),
|
||||
'ATE logs',
|
||||
'manage_options',
|
||||
self::SUBMENU_HANDLE,
|
||||
[ $this, 'renderPage' ]
|
||||
);
|
||||
}
|
||||
|
||||
public function renderPage() {
|
||||
$this->viewFactory->create()->renderPage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\WP\OptionManager;
|
||||
|
||||
class Storage {
|
||||
|
||||
const OPTION_GROUP = 'TM\ATE\Log';
|
||||
const OPTION_NAME = 'logs';
|
||||
const MAX_ENTRIES = 50;
|
||||
|
||||
public static function add( Entry $entry, $avoidDuplication = false ) {
|
||||
$entry->timestamp = $entry->timestamp ?: time();
|
||||
|
||||
$entries = self::getAll();
|
||||
|
||||
if ( $avoidDuplication ) {
|
||||
$entries = $entries->reject(
|
||||
function( $iteratedEntry ) use ( $entry ) {
|
||||
return (
|
||||
$iteratedEntry->wpmlJobId === $entry->wpmlJobId
|
||||
&& $entry->ateJobId === $iteratedEntry->ateJobId
|
||||
&& $entry->description === $iteratedEntry->description
|
||||
&& $entry->eventType === $iteratedEntry->eventType
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$entries->prepend( $entry );
|
||||
|
||||
$newOptionValue = $entries->forPage( 1, self::MAX_ENTRIES )
|
||||
->map(
|
||||
function( Entry $entry ) {
|
||||
return (array) $entry; }
|
||||
)
|
||||
->toArray();
|
||||
OptionManager::updateWithoutAutoLoad( self::OPTION_NAME, self::OPTION_GROUP, $newOptionValue );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*/
|
||||
public static function remove( Entry $entry ) {
|
||||
$entries = self::getAll();
|
||||
$entries = $entries->reject(
|
||||
function( $iteratedEntry ) use ( $entry ) {
|
||||
return $iteratedEntry->timestamp === $entry->timestamp && $entry->ateJobId === $iteratedEntry->ateJobId;
|
||||
}
|
||||
);
|
||||
$newOptionValue = $entries->forPage( 1, self::MAX_ENTRIES )
|
||||
->map(
|
||||
function( Entry $entry ) {
|
||||
return (array) $entry; }
|
||||
)
|
||||
->toArray();
|
||||
OptionManager::updateWithoutAutoLoad( self::OPTION_NAME, self::OPTION_GROUP, $newOptionValue );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection Collection of Entry objects.
|
||||
*/
|
||||
public static function getAll() {
|
||||
return wpml_collect( OptionManager::getOr( [], self::OPTION_NAME, self::OPTION_GROUP ) )
|
||||
->map(
|
||||
function( array $item ) {
|
||||
return new Entry( $item );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
use WPML\Collect\Support\Collection;
|
||||
|
||||
class View {
|
||||
|
||||
/** @var Collection $logs */
|
||||
private $logs;
|
||||
|
||||
public function __construct( Collection $logs ) {
|
||||
$this->logs = $logs;
|
||||
}
|
||||
|
||||
public function renderSupportSection() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h2 id="ate-log">
|
||||
<?php esc_html_e( 'Advanced Translation Editor', 'wpml-translation-management' ); ?>
|
||||
</h2>
|
||||
<p>
|
||||
<a href="<?php echo admin_url( 'admin.php?page=' . Hooks::SUBMENU_HANDLE ); ?>">
|
||||
<?php echo sprintf( esc_html__( 'Error Logs (%d)', 'wpml-translation-management' ), $this->logs->count() ); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function renderPage() {
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Advanced Translation Editor Error Logs', 'wpml-translation-management' ); ?></h1>
|
||||
<br>
|
||||
<table class="wp-list-table widefat fixed striped posts">
|
||||
<thead><?php $this->renderTableHeader(); ?></thead>
|
||||
|
||||
<tbody id="the-list">
|
||||
<?php
|
||||
if ( $this->logs->isEmpty() ) {
|
||||
$this->renderEmptyTable();
|
||||
} else {
|
||||
$this->logs->each( [ $this, 'renderTableRow' ] );
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
<tfoot><?php $this->renderTableHeader(); ?></tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function renderTableHeader() {
|
||||
?>
|
||||
<tr>
|
||||
<th class="date">
|
||||
<span><?php esc_html_e( 'Date', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
<th class="event">
|
||||
<span><?php esc_html_e( 'Event', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
<th class="description">
|
||||
<span><?php esc_html_e( 'Description', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
<th class="wpml-job-id">
|
||||
<span><?php esc_html_e( 'WPML Job ID', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
<th class="ate-job-id">
|
||||
<span><?php esc_html_e( 'ATE Job ID', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
<th class="extra-data">
|
||||
<span><?php esc_html_e( 'Extra data', 'wpml-translation-management' ); ?></span>
|
||||
</th>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function renderTableRow( Entry $entry ) {
|
||||
?>
|
||||
<tr>
|
||||
<td class="date">
|
||||
<?php echo esc_html( $entry->getFormattedDate() ); ?>
|
||||
</td>
|
||||
<td class="event">
|
||||
<?php echo esc_html( EventsTypes::getLabel( $entry->eventType ) ); ?>
|
||||
</td>
|
||||
<td class="description">
|
||||
<?php echo esc_html( $entry->description ); ?>
|
||||
</td>
|
||||
<td class="wpml-job-id">
|
||||
<?php echo esc_html( $entry->wpmlJobId ); ?>
|
||||
</td>
|
||||
<td class="ate-job-id">
|
||||
<?php echo esc_html( $entry->ateJobId ); ?>
|
||||
</td>
|
||||
<td class="extra-data">
|
||||
<?php echo esc_html( $entry->getExtraDataToString() ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function renderEmptyTable() {
|
||||
?>
|
||||
<tr>
|
||||
<td colspan="6" class="title column-title has-row-actions column-primary">
|
||||
<?php esc_html_e( 'No entries', 'wpml-translation-management' ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\Log;
|
||||
|
||||
use function WPML\Container\make;
|
||||
|
||||
class ViewFactory {
|
||||
|
||||
public function create() {
|
||||
$logs = make( Storage::class )->getAll();
|
||||
|
||||
return new View( $logs );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
|
||||
namespace WPML\TM\ATE\REST;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\Element\API\PostTranslations;
|
||||
use WPML\FP\Cast;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\TM\API\Jobs;
|
||||
use WPML\TM\ATE\Download\Process;
|
||||
use WPML\TM\ATE\Review\PreviewLink;
|
||||
use WPML\TM\ATE\Review\ReviewStatus;
|
||||
use WPML\TM\ATE\Review\StatusIcons;
|
||||
use WPML\TM\ATE\SyncLock;
|
||||
use WPML\TM\REST\Base;
|
||||
use WPML_TM_ATE_AMS_Endpoints;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\pipe;
|
||||
|
||||
class Download extends Base {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_routes() {
|
||||
return [
|
||||
[
|
||||
'route' => WPML_TM_ATE_AMS_Endpoints::DOWNLOAD_JOBS,
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'download' ],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return [
|
||||
'manage_options',
|
||||
'manage_translations',
|
||||
'translate',
|
||||
];
|
||||
}
|
||||
|
||||
public function download( WP_REST_Request $request ) {
|
||||
$lock = make( SyncLock::class );
|
||||
if ( ! $lock->create( $request->get_param( 'lockKey' ) ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$jobs = make( Process::class )->run( $request->get_param( 'jobs' ) );
|
||||
|
||||
return $this->getJobs( $jobs, $request->get_param( 'returnUrl' ) )->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $processedJobs
|
||||
* @param string $returnUrl
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getJobs( Collection $processedJobs, $returnUrl ) {
|
||||
$getLink = Logic::ifElse(
|
||||
ReviewStatus::doesJobNeedReview(),
|
||||
Fns::converge( PreviewLink::getWithSpecifiedReturnUrl( $returnUrl ), [ Obj::prop( 'translatedPostId' ), Obj::prop( 'jobId' ) ] ),
|
||||
pipe( Obj::prop( 'jobId' ), Jobs::getEditUrl( $returnUrl ) )
|
||||
);
|
||||
|
||||
$getLabel = Logic::ifElse(
|
||||
ReviewStatus::doesJobNeedReview(),
|
||||
StatusIcons::getReviewTitle( 'language_code' ),
|
||||
StatusIcons::getEditTitle( 'language_code' )
|
||||
);
|
||||
|
||||
return $processedJobs->pluck( 'jobId' )
|
||||
->map( Jobs::get() )
|
||||
->map( Obj::addProp( 'translatedPostId', Jobs::getTranslatedPostId() ) )
|
||||
->map( Obj::renameProp( 'job_id', 'jobId' ) )
|
||||
->map( Obj::renameProp( 'editor_job_id', 'ateJobId' ) )
|
||||
->map( Obj::addProp( 'viewLink', $getLink ) )
|
||||
->map( Obj::addProp( 'label', $getLabel ) )
|
||||
->map( Obj::pick( [ 'jobId', 'viewLink', 'automatic', 'status', 'label', 'review_status', 'ateJobId' ] ) )
|
||||
->map( Obj::evolve( [
|
||||
'jobId' => Cast::toInt(),
|
||||
'automatic' => Cast::toInt(),
|
||||
'status' => Cast::toInt(),
|
||||
'ateJobId' => Cast::toInt(),
|
||||
] ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\REST;
|
||||
|
||||
use WPML\TM\ATE\ReturnedJobsQueue;
|
||||
use WP_REST_Request;
|
||||
use WPML\Rest\Adaptor;
|
||||
use WPML\TM\REST\Base;
|
||||
use WPML_TM_ATE_AMS_Endpoints;
|
||||
use \WPML_TM_ATE_API;
|
||||
use \WPML_TM_ATE_Jobs;
|
||||
use \WPML_TM_Jobs_Repository;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\ATE\Log\Entry;
|
||||
use WPML\TM\ATE\Log\EventsTypes;
|
||||
|
||||
class FixJob extends Base {
|
||||
|
||||
/**
|
||||
* @var WPML_TM_ATE_Jobs
|
||||
*/
|
||||
private $ateJobs;
|
||||
|
||||
/**
|
||||
* @var WPML_TM_ATE_API
|
||||
*/
|
||||
private $ateApi;
|
||||
|
||||
/**
|
||||
* @var WPML_TM_Jobs_Repository
|
||||
*/
|
||||
private $jobsRepository;
|
||||
|
||||
const PARAM_ATE_JOB_ID = 'ateJobId';
|
||||
const PARAM_WPML_JOB_ID = 'jobId';
|
||||
|
||||
public function __construct( Adaptor $adaptor, WPML_TM_ATE_API $ateApi, WPML_TM_ATE_Jobs $ateJobs ) {
|
||||
parent::__construct( $adaptor );
|
||||
|
||||
$this->ateApi = $ateApi;
|
||||
$this->ateJobs = $ateJobs;
|
||||
$this->jobsRepository = wpml_tm_get_jobs_repository();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_routes() {
|
||||
return [
|
||||
[
|
||||
'route' => WPML_TM_ATE_AMS_Endpoints::FIX_JOB,
|
||||
'args' => [
|
||||
'methods' => 'GET',
|
||||
'callback' => [ $this, 'fix_job' ],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return [
|
||||
'manage_options',
|
||||
'manage_translations',
|
||||
'translate',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return bool[]
|
||||
*/
|
||||
public function fix_job( WP_REST_Request $request ) {
|
||||
try {
|
||||
$ateJobId = $request->get_param( self::PARAM_ATE_JOB_ID );
|
||||
$wpmlJobId = $request->get_param( self::PARAM_WPML_JOB_ID );
|
||||
|
||||
$processedJobResult = $this->process( $ateJobId, $wpmlJobId );
|
||||
|
||||
if ( $processedJobResult ) {
|
||||
return [ 'completed' => true, 'error' => false ];
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
$this->logException( $e, [ 'ateJobId' => $ateJobId, 'wpmlJobId' => $wpmlJobId ] );
|
||||
return [ 'completed' => false, 'error' => true ];
|
||||
}
|
||||
return [ 'completed' => false, 'error' => false ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the job status.
|
||||
*
|
||||
* @param $ateJobId
|
||||
* @param $wpmlJobId
|
||||
*
|
||||
* @return bool
|
||||
* @throws \Requests_Exception
|
||||
*/
|
||||
public function process( $ateJobId, $wpmlJobId ) {
|
||||
$ateJob = $this->ateApi->get_job( $ateJobId )->$ateJobId;
|
||||
$xliffUrl = Obj::prop('translated_xliff', $ateJob);
|
||||
|
||||
if ( $xliffUrl ) {
|
||||
$xliffContent = $this->ateApi->get_remote_xliff_content( $xliffUrl, [ 'jobId' => $wpmlJobId, 'ateJobId' => $ateJobId ] );
|
||||
$receivedWpmlJobId = $this->ateJobs->apply( $xliffContent );
|
||||
|
||||
if ( $receivedWpmlJobId && intval( $receivedWpmlJobId ) !== intval( $wpmlJobId ) ) {
|
||||
$error_message = sprintf( 'The received wpmlJobId (%s) does not match (%s).', $receivedWpmlJobId, $wpmlJobId );
|
||||
throw new \Exception( $error_message );
|
||||
}
|
||||
|
||||
if ( $receivedWpmlJobId ) {
|
||||
ReturnedJobsQueue::remove( $wpmlJobId );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
* @param array|null $job
|
||||
*/
|
||||
private function logException( \Exception $e, $job = null ) {
|
||||
$entry = new Entry();
|
||||
$entry->description = $e->getMessage();
|
||||
|
||||
if ( $job ) {
|
||||
$entry->ateJobId = Obj::prop('ateJobId', $job);
|
||||
$entry->wpmlJobId = Obj::prop('wpmlJobId', $job);
|
||||
$entry->extraData = [ 'downloadUrl' => Obj::prop('url', $job) ];
|
||||
}
|
||||
|
||||
if ( $e instanceof \Requests_Exception ) {
|
||||
$entry->eventType = EventsTypes::SERVER_XLIFF;
|
||||
} else {
|
||||
$entry->eventType = EventsTypes::JOB_DOWNLOAD;
|
||||
}
|
||||
|
||||
wpml_tm_ate_ams_log( $entry );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\REST;
|
||||
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\API\ATE;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\TM\API\Jobs;
|
||||
use WPML\TM\REST\Base;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\pipe;
|
||||
use function WPML\FP\curryN;
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
class PublicReceive extends \WPML_TM_ATE_Required_Rest_Base {
|
||||
|
||||
const CODE_UNPROCESSABLE_ENTITY = 422;
|
||||
const CODE_OK = 200;
|
||||
|
||||
const ENDPOINT_JOBS_RECEIVE = '/ate/jobs/receive/';
|
||||
|
||||
function add_hooks() {
|
||||
$this->register_routes();
|
||||
}
|
||||
|
||||
function register_routes() {
|
||||
parent::register_route(
|
||||
self::ENDPOINT_JOBS_RECEIVE . '(?P<wpmlJobId>\d+)',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'receive_ate_job' ),
|
||||
'args' => array(
|
||||
'wpmlJobId' => array(
|
||||
'required' => true,
|
||||
'type' => 'int',
|
||||
'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ),
|
||||
'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ),
|
||||
),
|
||||
),
|
||||
'permission_callback' => '__return_true',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function get_allowed_capabilities( \WP_REST_Request $request ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public function receive_ate_job( \WP_REST_Request $request ) {
|
||||
$wpmlJobId = $request->get_param( 'wpmlJobId' );
|
||||
|
||||
$ateAPI = make( ATE::class );
|
||||
|
||||
$getXLIFF = pipe(
|
||||
Obj::prop( 'job_id' ),
|
||||
Fns::safe( [ $ateAPI, 'checkJobStatus' ] ),
|
||||
Fns::map( Obj::prop( 'translated_xliff' ) )
|
||||
);
|
||||
|
||||
$applyTranslations = Fns::converge(
|
||||
Fns::liftA3( curryN( 3, [ $ateAPI, 'applyTranslation' ] ) ),
|
||||
[
|
||||
Fns::safe( Obj::prop( 'job_id' ) ),
|
||||
Fns::safe( Obj::prop( 'original_doc_id' ) ),
|
||||
$getXLIFF
|
||||
]
|
||||
);
|
||||
|
||||
return Maybe::of( $wpmlJobId )
|
||||
->map( Jobs::get() )
|
||||
->chain( $applyTranslations )
|
||||
->map( Fns::always( new \WP_REST_Response( null, self::CODE_OK ) ) )
|
||||
->getOrElse( new \WP_Error( self::CODE_UNPROCESSABLE_ENTITY ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $wpml_job_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_receive_ate_job_url( $wpml_job_id ) {
|
||||
return self::get_url( self::ENDPOINT_JOBS_RECEIVE . $wpml_job_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\REST;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WPML\TM\ATE\Retry\Arguments;
|
||||
use WPML\TM\ATE\Retry\Process;
|
||||
use WPML\TM\REST\Base;
|
||||
use WPML_TM_ATE_AMS_Endpoints;
|
||||
use function WPML\Container\make;
|
||||
|
||||
class Retry extends Base {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_routes() {
|
||||
return [
|
||||
[
|
||||
'route' => WPML_TM_ATE_AMS_Endpoints::RETRY_JOBS,
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'retry' ],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return [
|
||||
'manage_options',
|
||||
'manage_translations',
|
||||
'translate',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
* @throws \Auryn\InjectionException
|
||||
*/
|
||||
public function retry( WP_REST_Request $request ) {
|
||||
return (array) make( Process::class )->run( $request->get_param( 'jobsToProcess' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\TM\ATE\REST;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\FP\Cast;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\Rest\Adaptor;
|
||||
use WPML\TM\API\Jobs;
|
||||
use WPML\TM\ATE\Download\Job;
|
||||
use WPML\TM\ATE\Review\PreviewLink;
|
||||
use WPML\TM\ATE\Review\ReviewStatus;
|
||||
use WPML\TM\ATE\Review\StatusIcons;
|
||||
use WPML\TM\ATE\Sync\Arguments;
|
||||
use WPML\TM\ATE\Sync\Factory;
|
||||
use WPML\TM\ATE\Sync\Process;
|
||||
use WPML\TM\ATE\Sync\Result;
|
||||
use WPML\TM\ATE\SyncLock;
|
||||
use WPML\TM\REST\Base;
|
||||
use WPML\Utilities\KeyedLock;
|
||||
use WPML_TM_ATE_AMS_Endpoints;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\pipe;
|
||||
|
||||
class Sync extends Base {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_routes() {
|
||||
return [
|
||||
[
|
||||
'route' => WPML_TM_ATE_AMS_Endpoints::SYNC_JOBS,
|
||||
'args' => [
|
||||
'methods' => 'POST',
|
||||
'callback' => [ $this, 'sync' ],
|
||||
'args' => [
|
||||
'lockKey' => self::getStringType(),
|
||||
'ateToken' => self::getStringType(),
|
||||
'page' => self::getIntType(),
|
||||
'numberOfPages' => self::getIntType(),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_capabilities( WP_REST_Request $request ) {
|
||||
return [
|
||||
'manage_options',
|
||||
'manage_translations',
|
||||
'translate',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return array
|
||||
* @throws \Auryn\InjectionException
|
||||
*/
|
||||
public function sync( WP_REST_Request $request ) {
|
||||
$args = new Arguments();
|
||||
$args->ateToken = $request->get_param( 'ateToken' );
|
||||
$args->page = $request->get_param( 'nextPage' );
|
||||
$args->numberOfPages = $request->get_param( 'numberOfPages' );
|
||||
$args->includeManualAndLongstandingJobs = $request->get_param( 'includeManualAndLongstandingJobs' );
|
||||
|
||||
$lock = make( SyncLock::class );
|
||||
$lockKey = $lock->create( $request->get_param( 'lockKey' ) );
|
||||
if ( $lockKey ) {
|
||||
$result = make( Process::class )->run( $args );
|
||||
$result->lockKey = $lockKey;
|
||||
|
||||
$jobsFromDB = Fns::filter(
|
||||
Logic::complement( $this->findSyncedJob( $result->jobs ) ),
|
||||
$this->getJobStatuses( $request->get_param( 'jobIds' ), $request->get_param( 'returnUrl' ) )
|
||||
);
|
||||
$result = $this->createResultWithJobs( Lst::concat( $result->jobs, $jobsFromDB ), $result );
|
||||
} else {
|
||||
$result = $this->createResultWithJobs( $this->getJobStatuses( $request->get_param( 'jobIds' ), $request->get_param( 'returnUrl' ) ) );
|
||||
}
|
||||
|
||||
return (array) $result;
|
||||
}
|
||||
|
||||
private function getJobStatuses( $wpmlJobIds, $returnUrl ) {
|
||||
if ( ! $wpmlJobIds ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$ids = wpml_prepare_in( $wpmlJobIds, '%d' );
|
||||
$sql = "
|
||||
SELECT jobs.job_id as jobId, statuses.status as status, jobs.editor_job_id as ateJobId FROM {$wpdb->prefix}icl_translate_job as jobs
|
||||
INNER JOIN {$wpdb->prefix}icl_translation_status as statuses ON statuses.rid = jobs.rid
|
||||
WHERE jobs.job_id IN ( {$ids} ) AND 1 = %d
|
||||
"; // I need additional AND condition to utilize prepare function which is required to make writing unit tests easier. It's not perfect but saves a lot of time now
|
||||
|
||||
$result = $wpdb->get_results( $wpdb->prepare( $sql , 1) );
|
||||
if ( ! is_array( $result ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$jobs = Fns::map( Obj::evolve( [
|
||||
'jobId' => Cast::toInt(),
|
||||
'status' => Cast::toInt(),
|
||||
'ateJobId' => Cast::toInt(),
|
||||
] ), $result );
|
||||
|
||||
list( $completed, $notCompleted ) = \wpml_collect( $jobs )->partition( Relation::propEq( 'status', ICL_TM_COMPLETE ) );
|
||||
|
||||
if ( count( $completed ) ) {
|
||||
$completed = Download::getJobs( $completed, $returnUrl )->map( function ( $job ) {
|
||||
return (array) $job;
|
||||
} );
|
||||
}
|
||||
|
||||
return $completed->merge( $notCompleted )->all();
|
||||
}
|
||||
|
||||
private function findSyncedJob( $jobsFromATE ) {
|
||||
return function ( $jobFromDb ) use ( $jobsFromATE ) {
|
||||
return Lst::find( Relation::propEq( 'jobId', Obj::prop( 'jobId', $jobFromDb ) ), $jobsFromATE );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $jobs
|
||||
* @param Result|null $template
|
||||
*
|
||||
* @return Result
|
||||
*/
|
||||
private function createResultWithJobs( array $jobs, Result $template = null ) {
|
||||
$result = $template ? clone $template : new Result();
|
||||
$result->jobs = $jobs;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
abstract class WPML_TM_ATE_Required_Rest_Base extends WPML_REST_Base {
|
||||
|
||||
const REST_NAMESPACE = 'wpml/tm/v1';
|
||||
|
||||
/**
|
||||
* WPML_TM_ATE_Required_Rest_Base constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( self::REST_NAMESPACE );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_permission( WP_REST_Request $request ) {
|
||||
return WPML_TM_ATE_Status::is_enabled() && parent::validate_permission( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $endpoint
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static function get_url( $endpoint ) {
|
||||
return get_rest_url( null, '/' . self::REST_NAMESPACE . $endpoint );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_REST_AMS_Clients_Factory extends WPML_REST_Factory_Loader {
|
||||
|
||||
/**
|
||||
* @return \WPML_TM_REST_AMS_Clients
|
||||
* @throws \Auryn\InjectionException
|
||||
*/
|
||||
public function create() {
|
||||
return \WPML\Container\make( '\WPML_TM_REST_AMS_Clients' );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user