first commit

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

View File

@@ -0,0 +1,156 @@
<?php
class WPML_TM_Action_Helper {
public function get_tm_instance() {
return wpml_load_core_tm();
}
public function create_translation_package( $post ) {
$package_helper = new WPML_Element_Translation_Package();
return $package_helper->create_translation_package( $post );
}
public function add_translation_job( $rid, $translator_id, $translation_package, $batch_options = array() ) {
return $this->get_update_translation_action( $translation_package )
->add_translation_job( $rid, $translator_id, $translation_package, $batch_options );
}
/**
* calculate post md5
*
* @param object|int $post
*
* @return string
* @todo full support for custom posts and custom taxonomies
*/
public function post_md5( $post ) {
$post_key = '';
// TODO: [WPML 3.2] Make it work with PackageTranslation: this is not the right way anymore
if ( isset( $post->external_type ) && $post->external_type ) {
foreach ( $post->string_data as $key => $value ) {
$post_key .= $key . $value;
}
} else {
if ( is_numeric( $post ) ) {
$post = get_post( $post );
}
$post_tags = $this->get_post_terms( $post, 'post_tag' );
$post_categories = $this->get_post_terms( $post, 'category' );
$post_taxonomies = $this->get_post_taxonomies( $post );
$custom_fields_values = $this->get_post_custom_fields( $post );
$content = $post->post_content;
$content = apply_filters( 'wpml_pb_shortcode_content_for_translation', $content, $post->ID );
/**
* Filters the post content used to build the post md5.
*
* @since 2.10.0
* @internal
*
* @param string $content
* @param WP_Post $post
*/
$content = apply_filters( 'wpml_tm_post_md5_content', $content, $post );
$post_key = $post->post_title . ';' . $content . ';' . $post->post_excerpt . ';' . implode( ',', $post_tags ) . ';' . implode( ',', $post_categories ) . ';' . implode( ',', $custom_fields_values );
if ( ! empty( $post_taxonomies ) ) {
$post_key .= ';' . implode( ';', $post_taxonomies );
}
if ( wpml_get_setting_filter( false, 'translated_document_page_url' ) === 'translate' ) {
$post_key .= $post->post_name . ';';
}
}
$post_key = apply_filters( 'wpml_post_md5_key', $post_key, $post );
return md5( $post_key );
}
private function get_post_terms( $post, $taxonomy, $sort = false ) {
global $sitepress;
$terms = array();
// we shouldn't adjust term by current language need get terms by post_id
remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
$post_taxonomy_terms = wp_get_object_terms( $post->ID, $taxonomy );
if ( ! is_wp_error( $post_taxonomy_terms ) ) {
foreach ( $post_taxonomy_terms as $trm ) {
$terms[] = $trm->name;
}
}
if ( $terms ) {
sort( $terms, SORT_STRING );
}
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
return $terms;
}
private function get_post_taxonomies( $post ) {
global $wpdb, $sitepress_settings;
$post_taxonomies = array();
// get custom taxonomies
$taxonomies = $wpdb->get_col(
$wpdb->prepare(
"
SELECT DISTINCT tx.taxonomy
FROM {$wpdb->term_taxonomy} tx JOIN {$wpdb->term_relationships} tr ON tx.term_taxonomy_id = tr.term_taxonomy_id
WHERE tr.object_id =%d ",
$post->ID
)
);
sort( $taxonomies, SORT_STRING );
if ( isset( $sitepress_settings['taxonomies_sync_option'] ) ) {
foreach ( $taxonomies as $t ) {
if ( taxonomy_exists( $t ) && isset( $sitepress_settings['taxonomies_sync_option'][ $t ] ) && $sitepress_settings['taxonomies_sync_option'][ $t ] == 1 ) {
$taxs = $this->get_post_terms( $post, $t );
if ( $taxs ) {
$post_taxonomies[] = '[' . $t . ']:' . implode( ',', $taxs );
}
}
}
}
return $post_taxonomies;
}
private function get_post_custom_fields( $post ) {
$custom_fields_values = array();
foreach ( \WPML\TM\Settings\Repository::getCustomFields() as $cf => $op ) {
if ( in_array( (int) $op, array( WPML_TRANSLATE_CUSTOM_FIELD, WPML_COPY_ONCE_CUSTOM_FIELD ), true ) ) {
$value = get_post_meta( $post->ID, $cf, true );
if ( is_scalar( $value ) ) {
$custom_fields_values[ $cf ] = $value;
} else {
$custom_fields_values[ $cf ] = wp_json_encode( $value );
}
}
}
$custom_fields_values = apply_filters( 'wpml_custom_field_values_for_post_signature', $custom_fields_values, $post->ID );
return $custom_fields_values;
}
private function get_update_translation_action( $translation_package ) {
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-update-external-translation-data-action.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-update-post-translation-data-action.class.php';
return array_key_exists( 'type', $translation_package ) && $translation_package['type'] === 'post'
? new WPML_TM_Update_Post_Translation_Data_Action() : new WPML_TM_Update_External_Translation_Data_Action();
}
}

View File

@@ -0,0 +1,222 @@
<?php
class WPML_TM_Post_Actions extends WPML_Translation_Job_Helper {
/** @var WPML_TM_Action_Helper $action_helper */
private $action_helper;
/** @var WPML_TM_Blog_Translators $blog_translators */
private $blog_translators;
/** @var WPML_TM_Records $tm_records */
private $tm_records;
/**
* WPML_TM_Post_Actions constructor.
*
* @param WPML_TM_Action_Helper $helper
* @param WPML_TM_Blog_Translators $blog_translators
* @param WPML_TM_Records $tm_records
*/
public function __construct( &$helper, &$blog_translators, &$tm_records ) {
$this->action_helper = $helper;
$this->blog_translators = $blog_translators;
$this->tm_records = &$tm_records;
}
public function save_post_actions( $post_id, $post, $force_set_status = false ) {
global $wpdb, $sitepress, $current_user;
$trid = isset( $_POST['icl_trid'] ) && is_numeric( $_POST['icl_trid'] )
? $_POST['icl_trid'] : $sitepress->get_element_trid( $post_id, 'post_' . $post->post_type );
// set trid and lang code if front-end translation creating
$trid = apply_filters( 'wpml_tm_save_post_trid_value', isset( $trid ) ? $trid : '', $post_id );
$lang = apply_filters( 'wpml_tm_save_post_lang_value', '', $post_id );
$trid = $this->maybe_retrive_trid_again( $trid, $post );
$needs_second_update = array_key_exists( 'needs_second_update', $_POST ) ? (bool) $_POST['needs_second_update'] : false;
// is this the original document?
$is_original = empty( $trid )
? false
: ! (bool) $this->tm_records
->icl_translations_by_element_id_and_type_prefix( $post_id, 'post_' . $post->post_type )
->source_language_code();
if( $is_original ){
$this->save_translation_priority( $post_id );
}
if ( ! empty( $trid ) && ! $is_original ) {
$lang = $lang ? $lang : $this->get_save_post_lang( $lang, $post_id );
$res = $wpdb->get_row( $wpdb->prepare( "
SELECT element_id, language_code FROM {$wpdb->prefix}icl_translations WHERE trid=%d AND source_language_code IS NULL
",
$trid ) );
if ( $res ) {
$original_post_id = $res->element_id;
$from_lang = $res->language_code;
$original_post = get_post( $original_post_id );
$md5 = $this->action_helper->post_md5( $original_post );
$translation_id = $this->tm_records
->icl_translations_by_trid_and_lang( $trid, $lang )
->translation_id();
$user_id = $current_user->ID;
$this->maybe_add_as_translator( $user_id, $lang, $from_lang );
if ( $translation_id ) {
$translation_package = $this->action_helper->create_translation_package( $original_post_id );
list( $rid, $update ) = $this->action_helper->get_tm_instance()->update_translation_status( array(
'translation_id' => $translation_id,
'status' => isset( $force_set_status ) && $force_set_status > 0 ? $force_set_status : ICL_TM_COMPLETE,
'translator_id' => $user_id,
'needs_update' => $needs_second_update,
'md5' => $md5,
'translation_service' => 'local',
'translation_package' => serialize( $translation_package )
) );
if ( ! $update ) {
$job_id = $this->action_helper->add_translation_job( $rid, $user_id, $translation_package );
} else {
$job_id_sql = "SELECT MAX(job_id) FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d GROUP BY rid";
$job_id_prepared = $wpdb->prepare( $job_id_sql, $rid );
$job_id = $wpdb->get_var( $job_id_prepared );
if ( ! $job_id ) {
$job_id = $this->action_helper->add_translation_job(
$rid,
$user_id,
$translation_package
);
}
}
wpml_tm_load_old_jobs_editor()->set( $job_id, WPML_TM_Editors::WP );
// saving the translation
do_action( 'wpml_save_job_fields_from_post', $job_id );
}
}
}
if ( ! empty( $trid ) && empty( $_POST['icl_minor_edit'] ) ) {
$is_original = false;
$translations = $sitepress->get_element_translations( $trid, 'post_' . $post->post_type );
foreach ( $translations as $translation ) {
if ( $translation->original == 1 && $translation->element_id == $post_id ) {
$is_original = true;
break;
}
}
if ( $is_original ) {
$md5 = $this->action_helper->post_md5( $post_id );
foreach ( $translations as $translation ) {
if ( ! $translation->original ) {
$emd5 = $this->tm_records->icl_translation_status_by_translation_id( $translation->translation_id )->md5();
if ( $md5 !== $emd5 ) {
$translation_package = $this->action_helper->create_translation_package( $post_id );
$data = array(
'translation_id' => $translation->translation_id,
'needs_update' => 1,
'md5' => $md5,
'translation_package' => serialize( $translation_package ),
);
$this->action_helper->get_tm_instance()->update_translation_status( $data );
}
}
}
}
}
}
/**
* Adds the given language pair to the user.
*
* @param int $user_id
* @param string $target_lang
* @param string $source_lang
*
* @used-by \WPML_TM_Post_Actions::save_post_actions to add language pairs to admin users automatically when saving
* a translation in a given language pair.
*/
private function maybe_add_as_translator( $user_id, $target_lang, $source_lang ) {
$user = new WP_User( $user_id );
if ( $user->has_cap( 'manage_options' ) && $target_lang && ! $this->blog_translators->is_translator( $user_id,
array(
'lang_from' => $source_lang,
'lang_to' => $target_lang,
'admin_override' => false
) )
) {
global $wpdb;
$user->add_cap( WPML_Translator_Role::CAPABILITY );
$language_pair_records = new WPML_Language_Pair_Records( $wpdb, new WPML_Language_Records( $wpdb ) );
$language_pair_records->store( $user_id, array( $source_lang => array( $target_lang ) ) );
}
}
private function get_save_post_lang( $lang, $post_id ) {
if ( ( ! isset( $lang ) || ! $lang ) && ! empty( $_POST['icl_post_language'] ) ) {
$lang = $_POST['icl_post_language'];
} else {
global $wpml_post_translations;
$lang = $wpml_post_translations->get_element_lang_code( $post_id );
}
return $lang;
}
private function maybe_retrive_trid_again( $trid, $post ) {
global $wpdb, $sitepress;
$element_type_from_trid = $wpdb->get_var( $wpdb->prepare( "SELECT element_type FROM {$wpdb->prefix}icl_translations WHERE trid=%d", $trid ) );
if ( $element_type_from_trid && $post->post_type !== $element_type_from_trid ) {
$trid = $sitepress->get_element_trid( $post->ID, 'post_' . $post->post_type );
}
return $trid;
}
/**
* @param int $post_id
*/
public function save_translation_priority( $post_id ) {
$translation_priority = (int) filter_var(
( isset( $_POST['icl_translation_priority'] ) ? $_POST['icl_translation_priority'] : '' ),
FILTER_SANITIZE_NUMBER_INT );
if ( ! $translation_priority ) {
$assigned_priority = $this->get_term_obj( $post_id );
if ( $assigned_priority ) {
$translation_priority = $assigned_priority->term_id;
} else {
$term = \WPML_TM_Translation_Priorities::get_default_term();
if ( $term ) {
$translation_priority = $term->term_id;
};
}
}
if ( $translation_priority ) {
wp_set_post_terms( $post_id, array( $translation_priority ), \WPML_TM_Translation_Priorities::TAXONOMY );
}
}
/**
* @param int $element_id
*
* @return WP_Term|null
*/
private function get_term_obj( $element_id ) {
$terms = wp_get_object_terms( $element_id, \WPML_TM_Translation_Priorities::TAXONOMY );
if ( is_wp_error( $terms ) ) {
return null;
}
return empty( $terms ) ? null : $terms[0];
}
}

View File

@@ -0,0 +1,9 @@
<?php
global $wpdb;
$basket_ajax = new WPML_Basket_Tab_Ajax(
TranslationProxy::get_current_project(),
wpml_tm_load_basket_networking(),
new WPML_Translation_Basket( $wpdb )
);
add_action( 'init', array( $basket_ajax, 'init' ) );

View File

@@ -0,0 +1,46 @@
<?php
define( 'WPML_TM_FOLDER', basename( WPML_TM_PATH ) );
define( 'WPML_TM_URL', plugins_url( '', dirname( __FILE__ ) ) );
define( 'TP_MIGRATION_NOT_STARTED', 0 );
define( 'TP_MIGRATION_REQUESTED', 2 );
define( 'TP_MIGRATION_IN_PROGRESS', 3 );
define( 'TP_MIGRATION_WAITING_CONFIRMATION', 4 );
define( 'TP_MIGRATION_COMPLETED', 1 );
if ( ! defined( 'TRANSLATION_PROXY_XLIFF_VERSION' ) ) {
define( 'TRANSLATION_PROXY_XLIFF_VERSION', '12' );
}
if ( ! defined( 'WPML_XLIFF_TM_URL' ) ) {
define( 'WPML_XLIFF_TM_URL', plugins_url( '', dirname( __FILE__ ) ) );
}
if ( ! defined( 'WPML_XLIFF_TM_NEWLINES_REPLACE' ) ) {
define( 'WPML_XLIFF_TM_NEWLINES_REPLACE', 1 ); }
if ( ! defined( 'WPML_XLIFF_TM_NEWLINES_ORIGINAL' ) ) {
define( 'WPML_XLIFF_TM_NEWLINES_ORIGINAL', 2 ); }
if ( ! defined( 'WPML_XLIFF_DEFAULT_VERSION' ) ) {
define( 'WPML_XLIFF_DEFAULT_VERSION', '12' );
}
if ( ! defined( 'TA_URL_ENDPOINT' ) ) {
define( 'TA_URL_ENDPOINT', 'https://www.icanlocalize.com' );
}
if ( ! defined( 'TA_SCHEDULE_OCCURENCE' ) ) {
define( 'TA_SCHEDULE_OCCURENCE', 'daily' );
}
if ( ! defined( 'JSON_UNESCAPED_UNICODE' ) ) {
define( 'JSON_UNESCAPED_UNICODE', 256 );
}
if ( ! defined( 'JSON_UNESCAPED_SLASHES' ) ) {
define( 'JSON_UNESCAPED_SLASHES', 64 );
}
global $asian_languages;
$asian_languages = array( 'ja', 'ko', 'zh-hans', 'zh-hant', 'mn', 'ne', 'hi', 'pa', 'ta', 'th' );

View File

@@ -0,0 +1,206 @@
<?php
class WPML_TM_Element_Translations extends WPML_TM_Record_User {
/** @var int[] $trid_cache */
private $trid_cache;
/** @var int[] $job_id_cache */
private $job_id_cache;
/** @var int[] $job_id_cache */
private $translation_status_cache;
/** @var bool[] $update_status_cache */
private $update_status_cache;
/** @var string[] $element_type_prefix_cache */
private $element_type_prefix_cache = array();
public function init_hooks() {
add_action( 'wpml_cache_clear', array( $this, 'reload' ) );
add_filter(
'wpml_tm_translation_status',
array(
$this,
'get_translation_status_filter',
),
10,
2
);
}
public function reload() {
$this->trid_cache = array();
$this->job_id_cache = array();
$this->translation_status_cache = array();
$this->update_status_cache = array();
$this->element_type_prefix_cache = array();
}
public function is_update_needed( $trid, $language_code ) {
if ( isset( $this->update_status_cache[ $trid ][ $language_code ] ) ) {
$needs_update = $this->update_status_cache[ $trid ][ $language_code ];
} else {
$this->init_job_id( $trid, $language_code );
$needs_update = isset( $this->update_status_cache[ $trid ][ $language_code ] )
? $this->update_status_cache[ $trid ][ $language_code ] : 0;
}
return (bool) $needs_update;
}
/**
* @param int $trid
* @param string $language_code
*
* @return string
*/
public function get_element_type_prefix( $trid, $language_code ) {
if ( $trid && $language_code && ! isset( $this->element_type_prefix_cache[ $trid ] ) ) {
$this->init_job_id( $trid, $language_code );
}
return $trid && array_key_exists( $trid, $this->element_type_prefix_cache ) ? $this->element_type_prefix_cache[ $trid ] : '';
}
public function get_translation_status_filter( $empty, $args ) {
$trid = $args['trid'];
$language_code = $args['language_code'];
return $this->get_translation_status( $trid, $language_code );
}
/**
* @param int $trid
* @param string $language_code
*
* @return int
*/
public function get_translation_status( $trid, $language_code ) {
if ( isset( $this->translation_status_cache[ $trid ][ $language_code ] ) ) {
$status = $this->translation_status_cache[ $trid ][ $language_code ];
} else {
$this->init_job_id( $trid, $language_code );
$status = isset( $this->translation_status_cache[ $trid ][ $language_code ] )
? $this->translation_status_cache[ $trid ][ $language_code ] : 0;
}
return (int) $status;
}
public function init_job_id( $trid, $target_lang_code ) {
global $wpdb, $wpml_language_resolution;
if ( ! isset( $this->job_id_cache[ $trid ][ $target_lang_code ] ) ) {
$jobs = $wpdb->get_results(
$wpdb->prepare(
"
SELECT
tj.job_id,
ts.status,
ts.needs_update,
t.language_code,
SUBSTRING_INDEX(t.element_type, '_', 1)
AS element_type_prefix
FROM {$wpdb->prefix}icl_translate_job tj
JOIN {$wpdb->prefix}icl_translation_status ts
ON tj.rid = ts.rid
JOIN {$wpdb->prefix}icl_translations t
ON ts.translation_id = t.translation_id
WHERE t.trid = %d
",
$trid
)
);
$active_langs = $wpml_language_resolution->get_active_language_codes();
foreach ( $active_langs as $lang_code ) {
$this->cache_job_in_lang( $jobs, $lang_code, $trid );
}
}
}
/**
* @param object[] $jobs
* @param string $lang
* @param string $trid
*
* @return false|object
*/
private function cache_job_in_lang( $jobs, $lang, $trid ) {
$res = false;
foreach ( $jobs as $job ) {
if ( $job->language_code === $lang ) {
$res = $job;
break;
}
}
if ( (bool) $res === true ) {
$job_id = $res->job_id;
$status = $res->status;
$needs_update = (bool) $res->needs_update;
$element_type_prefix = $res->element_type_prefix;
} else {
$job_id = - 1;
$status = 0;
$needs_update = false;
$element_type_prefix = $this->fallback_type_prefix( $trid );
}
$this->cache_job( $trid, $lang, $job_id, $status, $needs_update, $element_type_prefix );
return $res;
}
private function fallback_type_prefix( $trid ) {
global $wpdb;
if ( isset( $this->element_type_prefix_cache[ $trid ] ) && (bool) $this->element_type_prefix_cache[ $trid ] === true ) {
$prefix = $this->element_type_prefix_cache[ $trid ];
} elseif ( (bool) $this->tm_records->get_post_translations()->get_element_translations( null, $trid ) ) {
$prefix = 'post';
} else {
$prefix = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUBSTRING_INDEX(element_type, '_', 1)
FROM {$wpdb->prefix}icl_translations
WHERE trid = %d
LIMIT 1",
$trid
)
);
}
return $prefix;
}
/**
* @param int $trid
* @param string $language_code
* @param int $job_id
* @param int $status
* @param bool $needs_update
* @param string $element_type_prefix
*/
private function cache_job( $trid, $language_code, $job_id, $status, $needs_update, $element_type_prefix ) {
if ( (bool) $job_id === true && (bool) $trid === true && (bool) $language_code === true ) {
$this->maybe_init_trid_cache( $trid );
$this->job_id_cache[ $trid ][ $language_code ] = $job_id;
$this->translation_status_cache[ $trid ][ $language_code ] = $status;
$this->update_status_cache[ $trid ][ $language_code ] = $needs_update;
$this->element_type_prefix_cache[ $trid ] = isset( $this->element_type_prefix_cache[ $trid ] )
&& (bool) $this->element_type_prefix_cache[ $trid ] === true
? $this->element_type_prefix_cache[ $trid ] : $element_type_prefix;
}
}
private function maybe_init_trid_cache( $trid ) {
foreach (
array(
&$this->job_id_cache,
&$this->trid_cache,
&$this->translation_status_cache,
&$this->update_status_cache,
) as $cache
) {
$cache[ $trid ] = isset( $cache[ $trid ] ) ? $cache[ $trid ] : array();
}
}
}

View File

@@ -0,0 +1,7 @@
<?php
function deprecated_icl_data_from_pro_translation( $translation ) {
return apply_filters( 'icl_data_from_pro_translation', $translation );
}
add_filter( 'wpml_tm_data_from_pro_translation', 'deprecated_icl_data_from_pro_translation', PHP_INT_MAX, 1 );

View File

@@ -0,0 +1,869 @@
<?php
use WPML\TM\Editor\ClassicEditorActions;
use function WPML\Container\make;
use WPML\TM\Jobs\Query\LimitQueryHelper;
use WPML\TM\Jobs\Query\OrderQueryHelper;
use WPML\TM\Jobs\Query\PostQuery;
use WPML\TM\Jobs\Query\QueryBuilder;
use WPML\TM\Jobs\Query\PackageQuery;
use WPML\TM\Jobs\Query\StringQuery;
use WPML\TM\Jobs\Query\StringsBatchQuery;
use WPML\TM\Jobs\Query\CompositeQuery;
/**
* @return WPML_TM_Element_Translations
*/
function wpml_tm_load_element_translations() {
global $wpml_tm_element_translations, $wpdb, $wpml_post_translations, $wpml_term_translations;
if ( ! isset( $wpml_tm_element_translations ) ) {
require_once WPML_TM_PATH . '/inc/core/wpml-tm-element-translations.class.php';
$tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$wpml_tm_element_translations = new WPML_TM_Element_Translations( $tm_records );
$wpml_tm_element_translations->init_hooks();
}
return $wpml_tm_element_translations;
}
function wpml_tm_load_status_display_filter() {
global $wpml_tm_status_display_filter, $iclTranslationManagement, $sitepress, $wpdb;
$blog_translators = wpml_tm_load_blog_translators();
$tm_api = new WPML_TM_API( $blog_translators, $iclTranslationManagement );
$tm_api->init_hooks();
if ( ! isset( $wpml_tm_status_display_filter ) ) {
$status_helper = wpml_get_post_status_helper();
$job_factory = wpml_tm_load_job_factory();
$wpml_tm_status_display_filter = new WPML_TM_Translation_Status_Display(
$wpdb,
$sitepress,
$status_helper,
$job_factory,
$tm_api
);
}
$wpml_tm_status_display_filter->init();
}
/**
* @return \WPML_TM_Page_Builders_Hooks
*/
function wpml_tm_page_builders_hooks() {
static $page_builder_hooks;
if ( ! $page_builder_hooks ) {
global $sitepress;
$page_builder_hooks = new WPML_TM_Page_Builders_Hooks( null, $sitepress );
}
return $page_builder_hooks;
}
/**
* @return \WPML_TM_Custom_XML_Factory
*/
function wpml_tm_custom_xml_factory() {
static $tm_custom_xml_factory;
// @fixme `class_exists` check is most likely not needed anymore
if ( class_exists( 'WPML_TM_Custom_XML_Factory' ) && ! $tm_custom_xml_factory ) {
$tm_custom_xml_factory = new WPML_TM_Custom_XML_Factory();
}
return $tm_custom_xml_factory;
}
/**
* @return \WPML_TM_Custom_XML_UI_Hooks
*/
function wpml_tm_custom_xml_ui_hooks() {
static $tm_custom_xml_ui_hooks;
// @fixme `class_exists` checks are most likely not needed anymore
if ( class_exists( 'WPML_TM_Custom_XML_UI_Hooks' ) && ! $tm_custom_xml_ui_hooks ) {
global $sitepress;
$factory = wpml_tm_custom_xml_factory();
if ( $factory ) {
$tm_custom_xml_ui_hooks = new WPML_TM_Custom_XML_UI_Hooks( $factory->create_ui(), $factory->create_resources( $sitepress->get_wp_api() ), $factory->create_ajax() );
}
}
return $tm_custom_xml_ui_hooks;
}
/**
* @return \WPML_Translations_Queue_Factory
*/
function wpml_tm_translation_queue_factory() {
static $translation_queue_factory;
if ( ! $translation_queue_factory ) {
$translation_queue_factory = new WPML_Translations_Queue_Factory();
}
return $translation_queue_factory;
}
/**
* @return \WPML_UI_Screen_Options_Factory
*/
function wpml_ui_screen_options_factory() {
static $screen_options_factory;
if ( ! $screen_options_factory ) {
global $sitepress;
$screen_options_factory = new WPML_UI_Screen_Options_Factory( $sitepress );
}
return $screen_options_factory;
}
/**
* @return \WPML_TM_Loader
*/
function wpml_tm_loader() {
static $tm_loader;
if ( ! $tm_loader ) {
$tm_loader = new WPML_TM_Loader();
}
return $tm_loader;
}
/**
* @return \WPML_TP_Translator
*/
function wpml_tm_translator() {
static $tm_translator;
if ( ! $tm_translator ) {
$tm_translator = new WPML_TP_Translator();
}
return $tm_translator;
}
/**
* It returns a single instance of \WPML_Translation_Management.
*
* @return \WPML_Translation_Management
*/
function wpml_translation_management() {
global $WPML_Translation_Management;
if ( ! $WPML_Translation_Management ) {
global $sitepress;
$WPML_Translation_Management = new WPML_Translation_Management( $sitepress, wpml_tm_loader(), wpml_load_core_tm(), wpml_tm_translator() );
}
return $WPML_Translation_Management;
}
/**
* @return \WPML_Translation_Basket
*/
function wpml_translation_basket() {
static $translation_basket;
if ( ! $translation_basket ) {
global $wpdb;
$translation_basket = new WPML_Translation_Basket( $wpdb );
}
return $translation_basket;
}
/**
* @return \WPML_TM_Translate_Independently
*/
function wpml_tm_translate_independently() {
static $translate_independently;
if ( ! $translate_independently ) {
global $sitepress;
$translate_independently = new WPML_TM_Translate_Independently( wpml_load_core_tm(), wpml_translation_basket(), $sitepress );
}
return $translate_independently;
}
/**
* @return WPML_Translation_Proxy_Basket_Networking
*/
function wpml_tm_load_basket_networking() {
global $iclTranslationManagement, $wpdb;
require_once WPML_TM_PATH . '/inc/translation-proxy/wpml-translationproxy-basket-networking.class.php';
$basket = new WPML_Translation_Basket( $wpdb );
return new WPML_Translation_Proxy_Basket_Networking( $basket, $iclTranslationManagement );
}
/**
* @return WPML_Translation_Proxy_Networking
*/
function wpml_tm_load_tp_networking() {
global $wpml_tm_tp_networking;
if ( ! isset( $wpml_tm_tp_networking ) ) {
$tp_lock_factory = new WPML_TP_Lock_Factory();
$wpml_tm_tp_networking = new WPML_Translation_Proxy_Networking( new WP_Http(), $tp_lock_factory->create() );
}
return $wpml_tm_tp_networking;
}
/**
* @return WPML_TM_Blog_Translators
*/
function wpml_tm_load_blog_translators() {
global $wpdb, $sitepress, $wpml_post_translations, $wpml_term_translations, $wpml_cache_factory;
static $instance;
if ( ! $instance ) {
$tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$translator_records = new WPML_Translator_Records( $wpdb, new WPML_WP_User_Query_Factory(), wp_roles() );
$instance = new WPML_TM_Blog_Translators( $sitepress, $tm_records, $translator_records, $wpml_cache_factory );
}
return $instance;
}
/**
* @return WPML_TM_Translators_Dropdown
*/
function wpml_tm_get_translators_dropdown() {
static $instance;
if ( ! $instance ) {
$instance = new WPML_TM_Translators_Dropdown( wpml_tm_load_blog_translators() );
}
return $instance;
}
/**
* @return WPML_TM_Mail_Notification
*/
function wpml_tm_init_mail_notifications() {
global $wpml_tm_mailer, $sitepress, $wpdb, $iclTranslationManagement, $wpml_translation_job_factory, $wp_api;
if ( null === $wp_api ) {
$wp_api = new WPML_WP_API();
}
if ( is_admin() ) {
$blog_translators = wpml_tm_load_blog_translators();
$email_twig_factory = new WPML_TM_Email_Twig_Template_Factory();
$batch_report = new WPML_TM_Batch_Report( $blog_translators );
$batch_report_email_template = new WPML_TM_Email_Jobs_Summary_View(
$email_twig_factory->create(),
$blog_translators,
$sitepress
);
$batch_report_email_builder = new WPML_TM_Batch_Report_Email_Builder(
$batch_report,
$batch_report_email_template
);
$batch_report_email_process = new WPML_TM_Batch_Report_Email_Process(
$batch_report,
$batch_report_email_builder
);
$batch_report_hooks = new WPML_TM_Batch_Report_Hooks( $batch_report, $batch_report_email_process );
$batch_report_hooks->add_hooks();
$wpml_tm_unsent_jobs = new WPML_TM_Unsent_Jobs( $blog_translators, $sitepress );
$wpml_tm_unsent_jobs->add_hooks();
$wpml_tm_unsent_jobs_notice = new WPML_TM_Unsent_Jobs_Notice( $wp_api );
$wpml_tm_unsent_jobs_notice_hooks = new WPML_TM_Unsent_Jobs_Notice_Hooks(
$wpml_tm_unsent_jobs_notice,
$wp_api,
WPML_Notices::DISMISSED_OPTION_KEY
);
$wpml_tm_unsent_jobs_notice_hooks->add_hooks();
$user_jobs_notification_settings = new WPML_User_Jobs_Notification_Settings();
$user_jobs_notification_settings->add_hooks();
$email_twig_factory = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/user-profile/' ) );
$notification_template = new WPML_User_Jobs_Notification_Settings_Template( $email_twig_factory->get_template() );
$user_jobs_notification_settings_render = new WPML_User_Jobs_Notification_Settings_Render( $notification_template );
$user_jobs_notification_settings_render->add_hooks();
}
if ( ! isset( $wpml_tm_mailer ) ) {
$iclTranslationManagement = $iclTranslationManagement ? $iclTranslationManagement : wpml_load_core_tm();
if ( empty( $iclTranslationManagement->settings ) ) {
$iclTranslationManagement->init();
}
$settings = isset( $iclTranslationManagement->settings['notification'] ) ? $iclTranslationManagement->settings['notification'] : array();
$email_twig_factory = new WPML_TM_Email_Twig_Template_Factory();
$email_notification_view = new WPML_TM_Email_Notification_View( $email_twig_factory->create() );
$has_active_remote_service = TranslationProxy::is_current_service_active_and_authenticated();
$wpml_tm_mailer = new WPML_TM_Mail_Notification(
$sitepress,
$wpdb,
$wpml_translation_job_factory,
$email_notification_view,
$settings,
$has_active_remote_service
);
}
$wpml_tm_mailer->init();
return $wpml_tm_mailer;
}
/**
* It returns a single instance of the class.
*
* @return WPML_Dashboard_Ajax
*/
function wpml_tm_load_tm_dashboard_ajax() {
global $wpml_tm_dashboard_ajax, $sitepress;
if ( ! isset( $wpml_tm_dashboard_ajax ) ) {
require_once WPML_TM_PATH . '/menu/dashboard/wpml-tm-dashboard-ajax.class.php';
$wpml_tm_dashboard_ajax = new WPML_Dashboard_Ajax( new WPML_Super_Globals_Validation() );
if ( defined( 'OTG_TRANSLATION_PROXY_URL' ) && defined( 'ICL_SITEPRESS_VERSION' ) ) {
$wpml_tp_api = wpml_tm_get_tp_project_api();
$wpml_tp_api_ajax = new WPML_TP_Refresh_Language_Pairs( $wpml_tp_api );
$wpml_tp_api_ajax->add_hooks();
$sync_jobs_ajax_handler = new WPML_TP_Sync_Ajax_Handler(
wpml_tm_get_tp_sync_jobs(),
new WPML_TM_Sync_Installer_Wrapper(),
new WPML_TM_Last_Picked_Up( $sitepress )
);
$sync_jobs_ajax_handler->add_hooks();
}
}
return $wpml_tm_dashboard_ajax;
}
function wpml_tm_load_and_intialize_dashboard_ajax() {
if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
if ( defined( 'DOING_AJAX' ) ) {
$wpml_tm_dashboard_ajax = wpml_tm_load_tm_dashboard_ajax();
add_action( 'init', array( $wpml_tm_dashboard_ajax, 'init_ajax_actions' ) );
} elseif (
is_admin() && isset( $_GET['page'] ) && WPML_TM_FOLDER . '/menu/main.php' === $_GET['page'] && ( ! isset( $_GET['sm'] ) || $_GET['sm'] === 'dashboard' )
) {
$wpml_tm_dashboard_ajax = wpml_tm_load_tm_dashboard_ajax();
add_action( 'wpml_tm_scripts_enqueued', array( $wpml_tm_dashboard_ajax, 'enqueue_js' ) );
}
}
}
add_action( 'plugins_loaded', 'wpml_tm_load_and_intialize_dashboard_ajax' );
/**
* It returns a single instance of the class.
*
* @return WPML_Translation_Job_Factory
*/
function wpml_tm_load_job_factory() {
global $wpml_translation_job_factory, $wpdb, $wpml_post_translations, $wpml_term_translations;
if ( ! $wpml_translation_job_factory ) {
$tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$wpml_translation_job_factory = new WPML_Translation_Job_Factory( $tm_records );
$wpml_translation_job_factory->init_hooks();
}
return $wpml_translation_job_factory;
}
/**
* It returns a single instance of the class.
*
* @return WPML_TM_XLIFF_Factory
*/
function wpml_tm_xliff_factory() {
static $xliff_factory;
if ( ! $xliff_factory ) {
$xliff_factory = new WPML_TM_XLIFF_Factory();
}
return $xliff_factory;
}
/**
* It returns a single instance of the class.
*
* @return WPML_TM_XLIFF_Shortcodes
*/
function wpml_tm_xliff_shortcodes() {
static $xliff_shortcodes;
if ( ! $xliff_shortcodes ) {
$xliff_shortcodes = new WPML_TM_XLIFF_Shortcodes();
}
return $xliff_shortcodes;
}
/**
* It returns an instance of the class.
*
* @return \WPML_TM_Old_Jobs_Editor
*/
function wpml_tm_load_old_jobs_editor() {
static $instance;
if ( ! $instance ) {
$instance = new WPML_TM_Old_Jobs_Editor( wpml_tm_load_job_factory() );
}
return $instance;
}
function tm_after_load() {
global $wpml_tm_translation_status, $wpdb, $wpml_post_translations, $wpml_term_translations;
if ( ! isset( $wpml_tm_translation_status ) ) {
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy.class.php';
require_once WPML_TM_PATH . '/inc/ajax.php';
( new ClassicEditorActions() )->addHooks();
wpml_tm_load_job_factory();
wpml_tm_init_mail_notifications();
wpml_tm_load_element_translations();
$wpml_tm_translation_status = make( WPML_TM_Translation_Status::class );
$wpml_tm_translation_status->init();
add_action( 'wpml_pre_status_icon_display', 'wpml_tm_load_status_display_filter' );
require_once WPML_TM_PATH . '/inc/wpml-private-actions.php';
}
}
/**
* It returns an instance of the class.
*
* @return WPML_TM_Records
*/
function wpml_tm_get_records() {
global $wpdb, $wpml_post_translations, $wpml_term_translations;
return new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
}
/**
* It returns an instance of the class.
*
* @return WPML_TM_Xliff_Frontend
*/
function setup_xliff_frontend() {
global $xliff_frontend;
$xliff_factory = new WPML_TM_XLIFF_Factory();
$xliff_frontend = $xliff_factory->create_frontend();
add_action( 'init', array( $xliff_frontend, 'init' ), $xliff_frontend->get_init_priority() );
return $xliff_frontend;
}
/**
* It returns an instance of the class.
*
* @param int $job_id The ID of the job.
* @param int $rid
*
* @return WPML_TM_ATE_Models_Job_Create
*/
function wpml_tm_create_ATE_job_creation_model( $job_id, $rid ) {
$job_factory = wpml_tm_load_job_factory();
$translation_job = $job_factory->get_translation_job( $job_id, false, 0, true );
$job = new WPML_TM_ATE_Models_Job_Create();
$job->source_id = $rid;
$job->source_language->code = $translation_job->get_source_language_code();
$job->source_language->name = $translation_job->get_source_language_code( true );
$job->target_language->code = $translation_job->get_language_code();
$job->target_language->name = $translation_job->get_language_code( true );
$job->deadline = strtotime( $translation_job->get_deadline_date() );
$job->permalink = '#';
if ( 'Post' === $translation_job->get_type() ) {
$job->permalink = get_permalink( $translation_job->get_original_element_id() );
}
$job->notify_enabled = true;
$job->notify_url = WPML_TM_REST_ATE_Public::get_receive_ate_job_url( $job_id );
$job->site_identifier = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE );
$encoded_xliff = base64_encode( wpml_tm_get_job_xliff( $job_id ) );
$job->file->type = 'data:application/x-xliff;base64';
$job->file->name = $translation_job->get_title();
$job->file->content = $encoded_xliff;
return $job;
}
/**
* It returns a single instance of the class.
*
* @param int $job_id The ID of the job.
*
* @return string
*/
function wpml_tm_get_job_xliff( $job_id ) {
static $xliff_writer;
if ( ! $xliff_writer ) {
$job_factory = wpml_tm_load_job_factory();
$xliff_writer = new WPML_TM_Xliff_Writer( $job_factory );
}
return $xliff_writer->generate_job_xliff( $job_id );
}
/**
* It returns a single instance of the class.
*
* @return \WPML_Rest
*/
function wpml_tm_get_wpml_rest() {
static $wpml_rest;
if ( ! $wpml_rest ) {
$http = new WP_Http();
$wpml_rest = new WPML_Rest( $http );
}
return $wpml_rest;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_API_Client
*/
function wpml_tm_get_tp_api_client() {
static $client;
if ( ! $client ) {
$client = new WPML_TP_API_Client(
OTG_TRANSLATION_PROXY_URL,
new WP_Http(),
new WPML_TP_Lock( new WPML_WP_API() ),
new WPML_TP_HTTP_Request_Filter()
);
}
return $client;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_Project
*/
function wpml_tm_get_tp_project() {
static $project;
if ( ! $project ) {
global $sitepress;
$translation_service = $sitepress->get_setting( 'translation_service' );
$translation_projects = $sitepress->get_setting( 'icl_translation_projects' );
$project = new WPML_TP_Project( $translation_service, $translation_projects );
}
return $project;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_Jobs_API
*/
function wpml_tm_get_tp_jobs_api() {
static $api;
if ( ! $api ) {
$api = new WPML_TP_Jobs_API(
wpml_tm_get_tp_api_client(),
wpml_tm_get_tp_project(),
new WPML_TM_Log()
);
}
return $api;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_Project_API
*/
function wpml_tm_get_tp_project_api() {
static $api;
if ( ! $api ) {
$api = new WPML_TP_Project_API(
wpml_tm_get_tp_api_client(),
wpml_tm_get_tp_project(),
new WPML_TM_Log()
);
}
return $api;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_XLIFF_API
*/
function wpml_tm_get_tp_xliff_api() {
static $api;
if ( ! $api ) {
$api = new WPML_TP_XLIFF_API(
wpml_tm_get_tp_api_client(),
wpml_tm_get_tp_project(),
new WPML_TM_Log(),
new WPML_TP_Xliff_Parser()
);
}
return $api;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TM_Jobs_Repository
*/
function wpml_tm_get_jobs_repository() {
static $repository;
if ( ! $repository ) {
global $wpdb;
$limit_helper = new LimitQueryHelper();
$order_helper = new OrderQueryHelper();
$subqueries = array(
new PostQuery( $wpdb, new QueryBuilder( $limit_helper, $order_helper ) ),
);
if ( defined( 'WPML_ST_VERSION' ) && get_option( 'wpml-package-translation-db-updates-run' ) ) {
$subqueries[] = new PackageQuery(
$wpdb,
new QueryBuilder( $limit_helper, $order_helper )
);
$subqueries[] = new StringQuery(
$wpdb,
new QueryBuilder( $limit_helper, $order_helper )
);
$subqueries[] = new StringsBatchQuery(
$wpdb,
new QueryBuilder( $limit_helper, $order_helper )
);
}
$repository = new WPML_TM_Jobs_Repository(
$wpdb,
new CompositeQuery(
$subqueries,
$limit_helper,
$order_helper
),
new WPML_TM_Job_Elements_Repository( $wpdb )
);
}
return $repository;
}
/**
* It returns an instance of the class.
*
* @return WPML_TM_ATE_Job_Repository
*/
function wpml_tm_get_ate_jobs_repository() {
static $instance;
if ( ! $instance ) {
return new WPML_TM_ATE_Job_Repository(
wpml_tm_get_jobs_repository()
);
}
return $instance;
}
/**
* @return \WPML\TM\ATE\JobRecords
*/
function wpml_tm_get_ate_job_records() {
global $wpdb;
static $instance;
if ( ! $instance ) {
$instance = new WPML\TM\ATE\JobRecords( $wpdb );
}
return $instance;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_Sync_Jobs
*/
function wpml_tm_get_tp_sync_jobs() {
static $sync_jobs;
if ( ! $sync_jobs ) {
global $wpdb, $sitepress;
$sync_jobs = new WPML_TP_Sync_Jobs(
new WPML_TM_Sync_Jobs_Status( wpml_tm_get_jobs_repository(), wpml_tm_get_tp_jobs_api() ),
new WPML_TM_Sync_Jobs_Revision( wpml_tm_get_jobs_repository(), wpml_tm_get_tp_jobs_api() ),
new WPML_TP_Sync_Update_Job( $wpdb, $sitepress )
);
}
return $sync_jobs;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TP_Translations_Repository
*/
function wpml_tm_get_tp_translations_repository() {
static $repository;
if ( ! $repository ) {
$repository = new WPML_TP_Translations_Repository(
wpml_tm_get_tp_xliff_api(),
wpml_tm_get_jobs_repository()
);
}
return $repository;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_WP_User_Query_Factory
*/
function wpml_tm_get_wp_user_query_factory() {
static $wp_user_query_factory;
if ( ! $wp_user_query_factory ) {
$wp_user_query_factory = new WPML_WP_User_Query_Factory();
}
return $wp_user_query_factory;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_WP_User_Factory
*/
function wpml_tm_get_wp_user_factory() {
static $wp_user_factory;
if ( ! $wp_user_factory ) {
$wp_user_factory = new WPML_WP_User_Factory();
}
return $wp_user_factory;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TM_Email_Twig_Template_Factory
*/
function wpml_tm_get_email_twig_template_factory() {
static $email_twig_template_factory;
if ( ! $email_twig_template_factory ) {
$email_twig_template_factory = new WPML_TM_Email_Twig_Template_Factory();
}
return $email_twig_template_factory;
}
/**
* It returns a single instance of the class.
*
* @return \WPML_TM_AMS_ATE_Factories
*/
function wpml_tm_ams_ate_factories() {
static $tm_ams_ate_factories;
if ( ! $tm_ams_ate_factories ) {
$tm_ams_ate_factories = new WPML_TM_AMS_ATE_Factories();
}
return $tm_ams_ate_factories;
}
/**
* @return string
* @throws \Auryn\InjectionException
*/
function wpml_tm_get_ams_ate_console_url() {
/** @var WPML_TM_Admin_Sections $admin_sections */
$admin_sections = WPML\Container\make( 'WPML_TM_Admin_Sections' );
return $admin_sections->get_item_url( WPML_TM_AMS_ATE_Console_Section::SLUG );
}
function wpml_tm_ate_ams_log( WPML\TM\ATE\Log\Entry $entry ) {
make( WPML\TM\ATE\Log\Storage::class )->add( $entry );
}
/**
* @param string $original
* @param string $translation
* @param bool $finished_state
*
* @return WPML_TM_Translated_Field
*/
function wpml_tm_create_translated_field( $original, $translation, $finished_state ) {
return new WPML_TM_Translated_Field( $original, $translation, $finished_state );
}
/**
* @param int $post_id
* @param \WP_Post $post
* @param bool $force_set_status
*/
function wpml_tm_save_post( $post_id, $post, $force_set_status = false ) {
global $wpdb, $wpml_post_translations, $wpml_term_translations;
if ( false === $force_set_status && get_post_meta( $post_id, '_icl_lang_duplicate_of', true ) ) {
$force_set_status = ICL_TM_DUPLICATE;
}
$action_helper = new WPML_TM_Action_Helper();
$blog_translators = wpml_tm_load_blog_translators();
$tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$save_post_action = new WPML_TM_Post_Actions( $action_helper, $blog_translators, $tm_records );
if ( 'revision' === $post->post_type || 'auto-draft' === $post->post_status || isset( $_POST['autosave'] ) ) {
return;
}
$save_post_action->save_post_actions( $post_id, $post, $force_set_status );
}
add_action( 'wpml_tm_save_post', 'wpml_tm_save_post', 10, 3 );

View File

@@ -0,0 +1,100 @@
<?php
/**
* Registers scripts so that they can be reused throughout WPML plugins
*/
function wpml_tm_register_js_scripts() {
wp_register_script(
'wpml-tm-editor-templates',
WPML_TM_URL . '/res/js/translation-editor/templates.js',
array(),
WPML_TM_VERSION,
true
);
wp_register_script(
'wpml-tm-editor-job',
WPML_TM_URL . '/res/js/translation-editor/wpml-tm-editor-job.js',
array( 'underscore', 'backbone' ),
WPML_TM_VERSION,
true
);
$scripts = array(
'wpml-tm-editor-job-field-view',
'wpml-tm-editor-job-basic-field-view',
'wpml-tm-editor-job-single-line-field-view',
'wpml-tm-editor-job-textarea-field-view',
'wpml-tm-editor-job-wysiwyg-field-view',
'wpml-tm-editor-field-view-factory',
'wpml-tm-editor-section-view',
'wpml-tm-editor-group-view',
'wpml-tm-editor-image-view',
'wpml-tm-editor-main-view',
'wpml-tm-editor-header-view',
'wpml-tm-editor-note-view',
'wpml-tm-editor-footer-view',
'wpml-tm-editor-languages-view',
'wpml-tm-editor-copy-all-dialog',
'wpml-tm-editor-edit-independently-dialog',
'wpml-tm-editor-translation-memory',
);
$additional_requirements = array(
'wpml-tm-editor-footer-view' => array( 'wpml-tm-progressbar' ),
);
foreach ( $scripts as $script ) {
wp_register_script(
$script,
WPML_TM_URL . '/res/js/translation-editor/' . $script . '.js',
array_merge(
array( 'wpml-tm-editor-job' ),
isset( $additional_requirements[ $script ] ) ? $additional_requirements[ $script ] : array()
),
WPML_TM_VERSION,
true
);
}
wp_register_script(
'wpml-tm-editor-scripts',
WPML_TM_URL . '/res/js/translation-editor/translation-editor.js',
array_merge( array( 'jquery', 'jquery-ui-dialog', 'wpml-tm-editor-templates', 'wpml-tm-editor-job' ), $scripts ),
WPML_TM_VERSION,
true
);
wp_register_script(
'wpml-tp-polling-box-populate',
WPML_TM_URL . '/res/js/tp-polling/box-populate.js',
array( 'jquery' ),
WPML_TM_VERSION,
true
);
wp_register_script(
'wpml-tp-polling',
WPML_TM_URL . '/res/js/tp-polling/poll-for-translations.js',
array( 'wpml-tp-polling-box-populate' ),
WPML_TM_VERSION,
true
);
wp_register_script(
'wpml-tm-mcs',
WPML_TM_URL . '/res/js/mcs/wpml-tm-mcs.js',
array( 'wpml-tp-polling' ),
WPML_TM_VERSION,
true
);
wp_register_script(
'wpml-tm-mcs-translate-link-targets',
WPML_TM_URL . '/res/js/mcs/wpml-tm-mcs-translate-link-targets.js',
array(),
WPML_TM_VERSION,
true
);
}
if ( is_admin() ) {
add_action( 'admin_enqueue_scripts', 'wpml_tm_register_js_scripts' );
} else {
add_action( 'wp_enqueue_scripts', 'wpml_tm_register_js_scripts' );
}

View File

@@ -0,0 +1,182 @@
<?php
class WPML_TM_Blog_Translators {
/** @var WPML_TM_Records $tm_records */
private $tm_records;
/**
* @var SitePress;
*/
private $sitepress;
/** @var WPML_Translator_Records $translator_records */
private $translator_records;
/** @var WPML_Cache_Factory */
private $cache_factory;
/**
* @param SitePress $sitepress
* @param WPML_TM_Records $tm_records
* @param WPML_Translator_Records $translator_records
* @param WPML_Cache_Factory $cache_factory
*/
public function __construct(
SitePress $sitepress,
WPML_TM_Records $tm_records,
WPML_Translator_Records $translator_records,
WPML_Cache_Factory $cache_factory
) {
$this->sitepress = $sitepress;
$this->tm_records = $tm_records;
$this->translator_records = $translator_records;
$this->cache_factory = $cache_factory;
}
/**
* It returns true if the site has translators.
*
* @return bool
*/
public function has_translators() {
$cache = $this->cache_factory->get( 'WPML_TM_Blog_Translators::has_translators' );
return $cache->execute_and_cache(
'has-translators',
function () {
return $this->translator_records->has_users_with_capability();
}
);
}
/**
* @param array $args
*
* @return array
*/
function get_blog_translators( $args = array() ) {
$from = isset( $args['from'] ) ? $args['from'] : false;
$to = isset( $args['to'] ) ? $args['to'] : false;
$all_translators = $this->get_raw_blog_translators();
$translators = array();
foreach ( $all_translators as $key => $translator ) {
if ( ! $from || ! $to ) {
$translators[] = isset( $translator->data ) ? $translator->data : $translator;
} elseif ( $this->translator_has_language_pair( $translator->ID, $from, $to ) ) {
$translators[] = isset( $translator->data ) ? $translator->data : $translator;
}
}
return apply_filters( 'blog_translators', $translators, $args );
}
/**
* @param int $translator_id
* @param string $from
* @param string $to
*
* @return bool
*/
private function translator_has_language_pair( $translator_id, $from, $to ) {
$language_pairs = $this->get_language_pairs( $translator_id );
if ( isset( $language_pairs[ $from ][ $to ] ) && (bool) $language_pairs[ $from ][ $to ] ) {
return true;
}
return false;
}
/**
* @return array
*/
public function get_raw_blog_translators() {
$cache = $this->cache_factory->get( 'WPML_TM_Blog_Translators::get_raw_blog_translators' );
return $cache->execute_and_cache(
'has-translators',
function () {
return $this->translator_records->get_users_with_capability();
}
);
}
/**
* @param int $user_id
* @param array $args
*
* @return bool
*/
function is_translator( $user_id, $args = array() ) {
$defaults = [
'lang_from' => null,
'lang_to' => null,
'job_id' => null,
'admin_override' => true,
];
$args = array_merge( $defaults, $args );
$lang_from = $args['lang_from'];
$lang_to = $args['lang_to'];
$job_id = $args['job_id'];
$admin_override = $args['admin_override'];
$is_translator = $this->sitepress->get_wp_api()
->user_can( $user_id, 'translate' );
// check if user is administrator and return true if he is
if ( $admin_override && $this->sitepress->get_wp_api()
->user_can( $user_id, 'manage_options' )
) {
$is_translator = true;
do_action( 'wpml_tm_ate_enable_subscription', $user_id );
} else {
if ( $lang_from && $lang_to ) {
$user_language_pairs = $this->get_language_pairs( $user_id );
if ( ! empty( $user_language_pairs ) ) {
foreach ( $user_language_pairs as $user_lang_from => $user_lang_to ) {
if ( array_key_exists( $lang_to, $user_lang_to ) ) {
$is_translator = true;
break;
} else {
$is_translator = false;
}
}
} else {
$is_translator = false;
}
}
if ( $job_id ) {
$job_record = $this->tm_records->icl_translate_job_by_job_id( $job_id );
$translator_id = in_array(
$job_record->service(),
array(
'local',
0,
)
) ? $job_record->translator_id() : - 1;
$is_translator = $translator_id == $user_id
|| ( $is_translator && empty( $translator_id ) );
}
}
return apply_filters( 'wpml_override_is_translator', $is_translator, $user_id, $args );
}
/**
* @param int $user_id
*
* @return array
*/
public function get_language_pairs( $user_id ) {
return $this->sitepress->get_wp_api()
->get_user_meta(
$user_id,
$this->sitepress->wpdb()->prefix . 'language_pairs',
true
);
}
}

View File

@@ -0,0 +1,16 @@
<?php
class WPML_Translation_Batch_Factory {
/**
* @param int $id
*
* @return WPML_Translation_Batch
*/
public function create( $id ) {
global $sitepress;
$wpdb = $sitepress->get_wpdb();
return new WPML_Translation_Batch( $wpdb, $id );
}
}

View File

@@ -0,0 +1,291 @@
<?php
/**
* Represents a helper class for building the SQL statement which retrieves the job,
* as well as for converting this collection to specific implementations of \WPML_Element_Translation_Job.
*
* @package WPML\TM
*/
class WPML_Abstract_Job_Collection {
/**
* Instance of \wpdb.
*
* @var \wpdb $wpdb
*/
public $wpdb;
/**
* Instance of \SitePress.
*
* @var \SitePress
*/
private $sitepress;
/**
* WPML_Abstract_Job_Collection constructor.
*
* @param WPDB $wpdb An instance of \wpdb.
*/
public function __construct( WPDB $wpdb ) {
$this->wpdb = $wpdb;
global $sitepress;
$this->sitepress = $sitepress;
}
/**
* It gets the (INNER) JOIN clause of the query.
*
* @param bool $single It should only return the last job revision.
* @param string $icl_translate_alias The alias for `{$this->wpdb->prefix}icl_translate_job`.
* @param string $icl_translations_translated_alias The alias for translated documents in `{$this->wpdb->prefix}icl_translations`.
* @param string $icl_translations_original_alias The alias for original documents in `{$this->wpdb->prefix}icl_translations`.
* @param string $icl_translation_status_alias The alias for `{$this->wpdb->prefix}icl_translation_status`.
* @param string $icl_translate_job_alias The alias for `{$this->wpdb->prefix}icl_translate_job`.
*
* @return string
*/
protected function get_table_join(
$single = false,
$icl_translate_alias = 'iclt',
$icl_translations_translated_alias = 't',
$icl_translations_original_alias = 'ito',
$icl_translation_status_alias = 's',
$icl_translate_job_alias = 'j'
) {
$wpdb = &$this->wpdb;
$max_rev_snippet = '';
if ( true !== $single ) {
$max_rev_snippet = "JOIN (SELECT rid, MAX(job_id) job_id FROM {$wpdb->prefix}icl_translate_job GROUP BY rid ) jobmax
ON ( {$icl_translate_job_alias}.revision IS NULL
AND {$icl_translate_job_alias}.rid = jobmax.rid)
OR ( {$icl_translate_job_alias}.job_id = jobmax.job_id
AND {$icl_translate_job_alias}.translated = 1)";
}
return "{$wpdb->prefix}icl_translate_job {$icl_translate_job_alias}
JOIN {$wpdb->prefix}icl_translation_status {$icl_translation_status_alias}
ON {$icl_translate_job_alias}.rid = {$icl_translation_status_alias}.rid
JOIN {$wpdb->prefix}icl_translations {$icl_translations_translated_alias}
ON {$icl_translation_status_alias}.translation_id = {$icl_translations_translated_alias}.translation_id
JOIN {$wpdb->prefix}icl_translate {$icl_translate_alias}
ON {$icl_translate_alias}.job_id = {$icl_translate_job_alias}.job_id
JOIN {$wpdb->prefix}icl_translations {$icl_translations_original_alias}
ON {$icl_translations_original_alias}.element_id = {$icl_translate_alias}.field_data
AND {$icl_translations_original_alias}.trid = {$icl_translations_translated_alias}.trid
{$max_rev_snippet}";
}
/**
* It gets the LEFT JOIN clause of the query.
*
* @param string $icl_translations_original_alias The alias for original documents in `{$this->wpdb->prefix}icl_translations`.
* @param string $posts_alias The alias for `{$this->wpdb->prefix}posts`.
*
* @return array
*/
protected function left_join_post( $icl_translations_original_alias = 'ito', $posts_alias = 'p' ) {
$join = "LEFT JOIN {$this->wpdb->prefix}posts {$posts_alias}
ON {$icl_translations_original_alias}.element_id = {$posts_alias}.ID
AND {$icl_translations_original_alias}.element_type = CONCAT('post_', {$posts_alias}.post_type)";
$select = "SUBSTRING_INDEX({$icl_translations_original_alias}.element_type, '_', 1 ) as element_type_prefix";
return array( $select, $join );
}
/**
* It converts an array of \stdClass jobs into an array of \WPML_Element_Translation_Job instances.
*
* @param array $jobs The array of \stdClass jobs.
*
* @return \WPML_Element_Translation_Job[]|\WPML_Post_Translation_Job[]|\WPML_String_Translation_Job[]|\WPML_External_Translation_Job[]
*/
protected function plain_objects_to_job_instances( $jobs ) {
foreach ( $jobs as $key => $job ) {
if ( ! is_object( $job ) || ! isset( $job->element_type_prefix ) || ! isset( $job->job_id ) ) {
unset( $jobs[ $key ] );
continue;
}
if ( 'post' === $job->element_type_prefix ) {
$post_translation_job = new WPML_Post_Translation_Job( $job->job_id, $job->batch_id );
if ( $post_translation_job->is_translatable_post_type() ) {
$jobs[ $key ] = $post_translation_job;
} else {
unset( $jobs[ $key ] );
}
} elseif ( 'string' === $job->element_type_prefix ) {
$jobs[ $key ] = new WPML_String_Translation_Job( $job->job_id );
} else {
$jobs[ $key ] = new WPML_External_Translation_Job( $job->job_id, $job->batch_id );
}
}
return $jobs;
}
/**
* Optional arguments to filter the results.
*
* @param array $args {
* Optional. An array of arguments.
*
* @type int translator_id
* @type int status
* @type int status__not
* @type bool include_unassigned
* @type int limit_no
* @type array language_pairs
* @type string service
* @type string from
* @type string to
* @type string type
* @type bool overdue
* @type string title
* }
*
* @return string
*/
protected function build_where_clause( array $args ) {
$defaults_args = array(
'translator_id' => 0,
'status' => false,
'status__not' => false,
'include_unassigned' => false,
'language_pairs' => array(),
'service' => 0,
'from' => null,
'to' => null,
'type' => null,
'overdue' => false,
'title' => null,
);
$args = array_merge( $defaults_args, $args );
$translator_id = $args['translator_id'];
$status = $args['status'];
$status__not = $args['status__not'];
$include_unassigned = $args['include_unassigned'];
$language_pairs = $args['language_pairs'];
$service = $args['service'];
$from = $args['from'];
$to = $args['to'];
$type = $args['type'];
$overdue = $args['overdue'];
$title = $args['title'];
$where = ' s.status > ' . ICL_TM_NOT_TRANSLATED;
if ( $status ) {
$where .= $this->wpdb->prepare( ' AND s.status = %d', (int) $status );
}
if ( ICL_TM_DUPLICATE !== $status ) {
$where .= $this->wpdb->prepare( ' AND s.status <> %d ', ICL_TM_DUPLICATE );
}
if ( false !== $status__not ) {
$where .= $this->wpdb->prepare( ' AND s.status <> %d ', $status__not );
}
if ( $from ) {
$where .= $this->wpdb->prepare( ' AND t.source_language_code = %s ', $from );
}
if ( $to ) {
$where .= $this->wpdb->prepare( ' AND t.language_code = %s ', $to );
}
if ( $title ) {
$where .= $this->wpdb->prepare( ' AND p.post_title LIKE %s ', '%' . $title . '%' );
}
if ( '' !== $translator_id ) {
if ( ! is_numeric( $translator_id ) ) {
$_exp = explode( '-', $translator_id );
$service = isset( $_exp[1] ) ? implode( '-', array_slice( $_exp, 1 ) ) : 'local';
$translator_id = isset( $_exp[2] ) ? $_exp[2] : false;
} elseif ( ! $service && ( ! isset( $args['any_translation_service'] ) || ! $args['any_translation_service'] ) ) {
$service = 'local';
}
$language_pairs = empty( $to ) || empty( $from ) ?
get_user_meta( $translator_id, $this->wpdb->prefix . 'language_pairs', true )
: $language_pairs;
$translator_id_query_parts = array();
if ( 0 !== (int) $translator_id ) {
$translator_id_query_parts[] = $this->wpdb->prepare( 'j.translator_id = %d', $translator_id );
if ( $include_unassigned ) {
$translator_id_query_parts[] = ' j.translator_id = 0 OR j.translator_id IS NULL ';
}
if ( true === (bool) $translator_id_query_parts ) {
$where .= ' AND (' . join( ' OR ', $translator_id_query_parts ) . ') ';
}
}
}
$where .= ! empty( $service ) ? $this->wpdb->prepare( ' AND s.translation_service=%s ', $service ) : '';
if ( $this->sitepress ) {
$post_types = array_keys( $this->sitepress->get_translatable_documents() );
if ( $post_types ) {
$where .= ' AND (p.post_type IS NULL OR p.post_type IN (' . wpml_prepare_in( $post_types, '%s' ) . ' )) ';
}
}
if ( empty( $from ) && false !== (bool) $language_pairs && is_array( $language_pairs ) && $translator_id ) {
/**
* Only if we filter by translator, make sure to use just the 'from' languages that apply
* in no translator_id, omit condition and all will be pulled.
*/
if ( ! empty( $to ) ) {
/**
* Get 'from' languages corresponding to $to (to $translator_id).
*/
$from_languages = array();
foreach ( $language_pairs as $fl => $tls ) {
if ( isset( $tls[ $to ] ) ) {
$from_languages[] = $fl;
}
}
if ( $from_languages ) {
$where .= ' AND t.source_language_code IN (' . wpml_prepare_in( $from_languages ) . ') ';
}
} else {
/**
* All to all case.
* Get all possible combinations for $translator_id.
*/
$from_languages = array_keys( $language_pairs );
$where_conditions = array();
foreach ( $from_languages as $fl ) {
$prepared_in_values = wpml_prepare_in( array_keys( $language_pairs[ $fl ] ) );
$where_conditions[] = ' (' . $this->wpdb->prepare( 't.source_language_code = %s', $fl ) . ' AND t.language_code IN (' . $prepared_in_values . ')) ';
}
if ( ! empty( $where_conditions ) ) {
$where .= ' AND ( ' . join( ' OR ', $where_conditions ) . ') ';
}
}
}
if ( empty( $to ) && $translator_id && ! empty( $from ) && isset( $language_pairs[ $from ] ) && false !== (bool) $language_pairs[ $from ] ) {
/**
* Only if we filter by translator, make sure to use just the 'from' languages that apply
* in no translator_id, omit condition and all will be pulled.
* Get languages the user can translate into from $from.
*/
$where .= ' AND t.language_code IN(' . wpml_prepare_in( array_keys( $language_pairs[ $from ] ) ) . ')';
}
$where .= ! empty( $type ) ? $this->wpdb->prepare( ' AND ito.element_type=%s ', $type ) : '';
if ( $overdue ) {
$today_date = date( 'Y-m-d' );
$statusCond = wpml_prepare_in( [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ], '%d' );
$where .= $this->wpdb->prepare( " AND j.deadline_date IS NOT NULL AND s.status IN ({$statusCond}) AND j.deadline_date < %s AND j.deadline_date <> '0000-00-00 00:00:00'", $today_date );
}
return $where;
}
}

View File

@@ -0,0 +1,453 @@
<?php
class WPML_Save_Translation_Data_Action extends WPML_Translation_Job_Helper_With_API {
/** @var WPML_TM_Records $tm_records */
private $tm_records;
/** @var array $data */
private $data;
private $redirect_target = false;
private $translate_link_targets_in_posts;
private $translate_link_targets_in_strings;
public function __construct( $data, $tm_records ) {
global $wpdb, $ICL_Pro_Translation, $sitepress;
parent::__construct();
$this->data = $data;
$this->tm_records = $tm_records;
$translate_link_targets_global_state = new WPML_Translate_Link_Target_Global_State( $sitepress );
$this->translate_link_targets_in_posts = new WPML_Translate_Link_Targets_In_Posts( $translate_link_targets_global_state, $wpdb, $ICL_Pro_Translation );
$this->translate_link_targets_in_strings = new WPML_Translate_Link_Targets_In_Strings( $translate_link_targets_global_state, $wpdb, new WPML_WP_API(), $ICL_Pro_Translation );
}
function save_translation() {
global $wpdb, $sitepress, $iclTranslationManagement, $wpml_post_translations;
$new_post_id = false;
$is_incomplete = false;
$data = $this->data;
/** @var stdClass $job */
$job = ! empty( $data['job_id'] ) ? $this->get_translation_job( $data['job_id'], true ) : null;
$needs_second_update = $job && $job->needs_update ? 1 : 0;
$original_post = null;
$element_type_prefix = null;
if ( is_object( $job ) ) {
$element_type_prefix = $iclTranslationManagement->get_element_type_prefix_from_job( $job );
$original_post = $iclTranslationManagement->get_post( $job->original_doc_id, $element_type_prefix );
}
$is_external = apply_filters( 'wpml_is_external', false, $element_type_prefix );
$data_to_validate = array(
'original_post' => $original_post,
'type_prefix' => $element_type_prefix,
'data' => $data,
'is_external' => $is_external,
);
$validation_results = $this->get_validation_results( $job, $data_to_validate );
if ( ! $validation_results['is_valid'] ) {
$this->handle_failed_validation( $validation_results, $data_to_validate );
$res = false;
} else {
foreach ( $data['fields'] as $fieldname => $field ) {
if ( substr( $fieldname, 0, 6 ) === 'field-' ) {
$field = apply_filters( 'wpml_tm_save_translation_cf', $field, $fieldname, $data );
}
$this->save_translation_field( $field['tid'], $field );
if ( ! isset( $field['finished'] ) || ! $field['finished'] ) {
$is_incomplete = true;
}
}
$icl_translate_job = $this->tm_records->icl_translate_job_by_job_id( $data['job_id'] );
$rid = $icl_translate_job->rid();
$translation_status = $this->tm_records->icl_translation_status_by_rid( $rid );
$translation_id = $translation_status->translation_id();
if ( ( $is_incomplete === true || empty( $data['complete'] ) ) && empty( $data['resign'] ) ) {
$iclTranslationManagement->update_translation_status(
array(
'translation_id' => $translation_id,
'status' => ICL_TM_IN_PROGRESS,
)
);
$icl_translate_job->update( array( 'translated' => 0 ) );
self::notify_job_in_progress( $element_type_prefix, $job );
}
$element_id = $translation_status->element_id();
delete_post_meta( $element_id, '_icl_lang_duplicate_of' );
if ( ! empty( $data['complete'] ) && ! $is_incomplete ) {
$icl_translate_job->update(
array(
'translated' => 1,
'completed_date' => date( 'Y-m-d H:i:s' ),
)
);
$job = $this->get_translation_job( $data['job_id'], true );
if ( $is_external ) {
self::save_external( $element_type_prefix, $job, [ $this, 'decode_field_data' ] );
} else {
if ( $element_id ) {
$postarr['ID'] = $_POST['post_ID'] = $element_id;
}
$postarr['post_status'] = ! $sitepress->get_setting( 'translated_document_status' ) ? 'draft' : $original_post->post_status;
foreach ( $job->elements as $field ) {
switch ( $field->field_type ) {
case 'title':
$postarr['post_title'] = $this->decode_field_data( $field->field_data_translated, $field->field_format );
break;
case 'body':
$postarr['post_content'] = $this->decode_field_data(
$field->field_data_translated,
$field->field_format
);
break;
case 'excerpt':
$postarr['post_excerpt'] = $this->decode_field_data( $field->field_data_translated, $field->field_format );
break;
case 'URL':
$postarr['post_name'] = $this->decode_field_data( $field->field_data_translated, $field->field_format );
break;
default:
break;
}
}
$postarr['post_author'] = $original_post->post_author;
$postarr['post_type'] = $original_post->post_type;
if ( $sitepress->get_setting( 'sync_comment_status' ) ) {
$postarr['comment_status'] = $original_post->comment_status;
}
if ( $sitepress->get_setting( 'sync_ping_status' ) ) {
$postarr['ping_status'] = $original_post->ping_status;
}
if ( $sitepress->get_setting( 'sync_page_ordering' ) ) {
$postarr['menu_order'] = $original_post->menu_order;
}
if ( $sitepress->get_setting( 'sync_private_flag' ) && $original_post->post_status == 'private' ) {
$postarr['post_status'] = 'private';
}
if ( $sitepress->get_setting( 'sync_password' ) && $original_post->post_password ) {
$postarr['post_password'] = $original_post->post_password;
}
if ( $sitepress->get_setting( 'sync_post_date' ) ) {
$postarr['post_date'] = $original_post->post_date;
}
if ( $original_post->post_parent ) {
$parent_id = $wpml_post_translations->element_id_in( $original_post->post_parent, $job->language_code );
}
if ( isset( $parent_id ) && $sitepress->get_setting( 'sync_page_parent' ) ) {
$_POST['post_parent'] = $postarr['post_parent'] = $parent_id;
$_POST['parent_id'] = $postarr['parent_id'] = $parent_id;
}
$_POST['trid'] = $translation_status->trid();
$_POST['lang'] = $job->language_code;
$_POST['skip_sitepress_actions'] = true;
$_POST['needs_second_update'] = $needs_second_update;
/* @deprecated Use `wpml_pre_save_pro_translation` instead */
$postarr = apply_filters( 'icl_pre_save_pro_translation', $postarr );
$postarr = apply_filters( 'wpml_pre_save_pro_translation', $postarr, $job );
// it's an update and user do not want to translate urls so do not change the url
if ( $element_id ) {
if ( $sitepress->get_setting( 'translated_document_page_url' ) !== 'translate' ) {
$postarr['post_name'] = $wpdb->get_var(
$wpdb->prepare(
"SELECT post_name
FROM {$wpdb->posts}
WHERE ID=%d
LIMIT 1",
$element_id
)
);
}
$existing_post = get_post( $element_id );
$postarr['post_date'] = $existing_post->post_date;
$postarr['post_date_gmt'] = $existing_post->post_date_gmt;
}
$new_post_id = wpml_get_create_post_helper()->insert_post( $postarr, $job->language_code );
icl_cache_clear( $postarr['post_type'] . 's_per_language' ); // clear post counter per language in cache
// set taxonomies for users with limited caps
if ( ! current_user_can( 'manage-categories' ) && ! empty( $postarr['tax_input'] ) ) {
foreach ( $postarr['tax_input'] as $taxonomy => $terms ) {
wp_set_post_terms( $new_post_id, $terms, $taxonomy, false ); // true to append to existing tags | false to replace existing tags
}
}
$data['fields'] = apply_filters( 'wpml_tm_job_fields', $data['fields'], $job );
do_action( 'icl_pro_translation_saved', $new_post_id, $data['fields'], $job );
do_action( 'wpml_translation_job_saved', $new_post_id, $data['fields'], $job );
// update body translation with the links fixed
$new_post_content = $wpdb->get_var( $wpdb->prepare( "SELECT post_content FROM {$wpdb->posts} WHERE ID=%d", $new_post_id ) );
foreach ( $job->elements as $job_element ) {
if ( $job_element->field_type === 'body' ) {
$fields_data_translated = apply_filters( 'wpml_tm_job_data_post_content', $new_post_content );
$fields_data_translated = $this->encode_field_data( $fields_data_translated );
$wpdb->update(
$wpdb->prefix . 'icl_translate',
array( 'field_data_translated' => $fields_data_translated ),
array(
'job_id' => $data['job_id'],
'field_type' => 'body',
)
);
break;
}
}
$sitepress->copy_custom_fields( $original_post->ID, $new_post_id );
// set specific custom fields
$copied_custom_fields = array( '_top_nav_excluded', '_cms_nav_minihome' );
foreach ( $copied_custom_fields as $ccf ) {
$val = get_post_meta( $original_post->ID, $ccf, true );
update_post_meta( $new_post_id, $ccf, $val );
}
// sync _wp_page_template
if ( $sitepress->get_setting( 'sync_page_template' ) ) {
$_wp_page_template = get_post_meta( $original_post->ID, '_wp_page_template', true );
if ( ! empty( $_wp_page_template ) ) {
update_post_meta( $new_post_id, '_wp_page_template', $_wp_page_template );
}
}
$this->package_helper->save_job_custom_fields(
$job,
$new_post_id,
\WPML\TM\Settings\Repository::getCustomFields()
);
$link = get_edit_post_link( $new_post_id );
if ( $link == '' ) {
// the current user can't edit so just include permalink
$link = get_permalink( $new_post_id );
}
if ( ! $element_id ) {
$wpdb->delete(
$wpdb->prefix . 'icl_translations',
array(
'element_id' => $new_post_id,
'element_type' => 'post_' . $postarr['post_type'],
)
);
$wpdb->update( $wpdb->prefix . 'icl_translations', array( 'element_id' => $new_post_id ), array( 'translation_id' => $translation_id ) );
$user_message = __( 'Translation added: ', 'wpml-translation-management' ) . '<a href="' . $link . '">' . $postarr['post_title'] . '</a>.';
} else {
$user_message = __( 'Translation updated: ', 'wpml-translation-management' ) . '<a href="' . $link . '">' . $postarr['post_title'] . '</a>.';
}
// set stickiness
// is the original post a sticky post?
$sticky_posts = get_option( 'sticky_posts' );
$is_original_sticky = $original_post->post_type == 'post' && in_array( $original_post->ID, $sticky_posts );
if ( $is_original_sticky && $sitepress->get_setting( 'sync_sticky_flag' ) ) {
stick_post( $new_post_id );
} else {
if ( $original_post->post_type == 'post' && ! is_null( $element_id ) ) {
unstick_post( $new_post_id ); // just in case - if this is an update and the original post stickiness has changed since the post was sent for translation
}
}
$this->add_message(
array(
'type' => 'updated',
'text' => $user_message,
)
);
}
if ( $this->get_tm_setting( array( 'notification', 'completed' ) ) != ICL_TM_NOTIFICATION_NONE
&& $data['job_id']
) {
do_action( 'wpml_tm_complete_job_notification', $data['job_id'], ! is_null( $element_id ) );
}
$iclTranslationManagement->set_page_url( $new_post_id );
if ( isset( $job ) && isset( $job->language_code ) && isset( $job->source_language_code ) ) {
$this->save_terms_for_job( $data['job_id'] );
}
// sync post format
// Must be after save terms otherwise it gets lost.
if ( $sitepress->get_setting( 'sync_post_format' ) ) {
$_wp_post_format = get_post_format( $original_post->ID );
set_post_format( $new_post_id, $_wp_post_format );
}
do_action( 'icl_pro_translation_completed', $new_post_id, $data['fields'], $job );
do_action( 'wpml_pro_translation_completed', $new_post_id, $data['fields'], $job );
$translation_status->update(
array(
'status' => ICL_TM_COMPLETE,
'needs_update' => $needs_second_update,
)
);
$this->translate_link_targets_in_posts->new_content();
$this->translate_link_targets_in_strings->new_content();
if ( ! defined( 'REST_REQUEST' ) && ! defined( 'XMLRPC_REQUEST' ) && ! defined( 'DOING_AJAX' ) && ! isset( $_POST['xliff_upload'] ) ) {
$action_type = is_null( $element_id ) ? 'added' : 'updated';
$element_id = is_null( $element_id ) ? $new_post_id : $element_id;
$this->redirect_target = admin_url( sprintf( 'admin.php?page=%s&%s=%d&element_type=%s', WPML_TM_FOLDER . '/menu/translations-queue.php', $action_type, $element_id, $element_type_prefix ) );
}
} else {
$this->add_message(
array(
'type' => 'updated',
'text' => __( 'Translation (incomplete) saved.', 'wpml-translation-management' ),
)
);
}
$res = true;
}
return $res;
}
/**
* Returns false if after saving the translation no redirection is to happen or the target of the redirection
* in case saving the data is followed by a redirect.
*
* @return false|string
*/
function get_redirect_target() {
return $this->redirect_target;
}
private function save_translation_field( $tid, $field ) {
global $wpdb;
$update = [];
if ( isset( $field['data'] ) ) {
$update['field_data_translated'] = $this->encode_field_data( $field['data'] );
}
$update['field_finished'] = isset( $field['finished'] ) && $field['finished'] ? 1 : 0;
$wpdb->update( $wpdb->prefix . 'icl_translate', $update, array( 'tid' => $tid ) );
}
private function handle_failed_validation( $validation_results, $data_to_validate ) {
if ( isset( $validation_results['messages'] ) ) {
$messages = (array) $validation_results['messages'];
if ( $messages ) {
foreach ( $messages as $message ) {
$this->add_message(
array(
'type' => 'error',
'text' => $message,
)
);
}
} else {
$this->add_message(
array(
'type' => 'error',
'text' => __( 'Submitted data is not valid.', 'wpml-translation-management' ),
)
);
}
}
do_action( 'wpml_translation_validation_failed', $validation_results, $data_to_validate );
}
private function get_validation_results( $job, $data_to_validate ) {
$is_valid = true;
$original_post = $data_to_validate['original_post'];
$element_type_prefix = $data_to_validate['type_prefix'];
$validation_default_results = array(
'is_valid' => $is_valid,
'messages' => array(),
);
if ( ! $job || ! $original_post || ! $element_type_prefix ) {
$is_valid = false;
if ( ! $job ) {
$validation_default_results['messages'][] = __( 'Job ID is missing', 'wpml-translation-management' );
}
if ( ! $original_post ) {
$validation_default_results['messages'][] = __( 'The original post cannot be retrieved', 'wpml-translation-management' );
}
if ( ! $element_type_prefix ) {
$validation_default_results['messages'][] = __( 'The type of the post cannot be retrieved', 'wpml-translation-management' );
}
} elseif ( ! $this->tm_records->icl_translate_job_by_job_id( $job->job_id )->is_open() ) {
$is_valid = false;
$validation_default_results['messages'][] = __( 'This job cannot be edited anymore because a newer job for this element exists.', 'wpml-translation-management' );
}
$validation_default_results['is_valid'] = $is_valid;
$validation_results = apply_filters( 'wpml_translation_validation_data', $validation_default_results, $data_to_validate );
$validation_results = array_merge( $validation_default_results, $validation_results );
if ( ! $is_valid && $validation_results['is_valid'] ) {
$validation_results['is_valid'] = $is_valid;
}
return $validation_results;
}
private function save_terms_for_job( $job_id ) {
require_once WPML_TM_PATH . '/inc/translation-jobs/wpml-translation-jobs-collection.class.php';
$job = new WPML_Post_Translation_Job( $job_id );
$job->save_terms_to_post();
}
private function add_message( $message ) {
global $iclTranslationManagement;
$iclTranslationManagement->add_message( $message );
}
/**
* @param string $element_type_prefix
* @param object $job
* @param callable $decoder
*/
private static function save_external( $element_type_prefix, $job, $decoder ) {
do_action( 'wpml_save_external', $element_type_prefix, $job, $decoder );
}
/**
* @param string $element_type_prefix
* @param object $job
*/
private static function notify_job_in_progress( $element_type_prefix, $job ) {
/**
* The action triggered when a job is marked as in progress
*
* @param string $element_type_prefix
* @param object $job
* @since 2.10.0
*/
do_action( 'wpml_tm_job_in_progress', $element_type_prefix, $job );
}
}

View File

@@ -0,0 +1,31 @@
<?php
class WPML_Translation_Job_Helper_With_API extends WPML_Translation_Job_Helper {
/** @var WPML_Element_Translation_Package $package_helper */
protected $package_helper;
function __construct() {
$this->package_helper = new WPML_Element_Translation_Package();
}
protected function get_translation_job( $job_id, $include_non_translatable_elements = false, $revisions = 0 ) {
return wpml_tm_load_job_factory()->get_translation_job( $job_id, $include_non_translatable_elements, $revisions );
}
protected function get_lang_by_rid( $rid ) {
global $wpdb;
return $wpdb->get_var(
$wpdb->prepare(
"SELECT i.language_code
FROM {$wpdb->prefix}icl_translations i
JOIN {$wpdb->prefix}icl_translation_status s
ON s.translation_id = i.translation_id
WHERE s.rid = %d
LIMIT 1",
$rid
)
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
class WPML_Translation_Job_Helper {
public function encode_field_data( $data ) {
return base64_encode( $data );
}
public function decode_field_data( $data, $format ) {
return $this->get_core_translation_management()->decode_field_data( $data, $format );
}
protected function get_tm_setting( $indexes ) {
$core_tm = $this->get_core_translation_management();
if ( empty( $core_tm->settings ) ) {
$core_tm->init();
}
$settings = $core_tm->get_settings();
foreach ( $indexes as $index ) {
$settings = isset( $settings[ $index ] ) ? $settings[ $index ] : null;
if ( ! isset( $settings ) ) {
break;
}
}
return $settings;
}
/**
* @return TranslationManagement
*/
public static function get_core_translation_management() {
/** TranslationManagement $iclTranslationManagement */
global $iclTranslationManagement;
return $iclTranslationManagement;
}
}

View File

@@ -0,0 +1,24 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-update-translation-data-action.class.php';
class WPML_TM_Update_External_Translation_Data_Action extends WPML_TM_Update_Translation_Data_Action {
protected function populate_prev_translation( $rid, array $package ) {
list( $prev_job_id ) = $this->get_prev_job_data( $rid );
$prev_translation = [];
$prev_job = $this->get_translation_job( $prev_job_id );
/** @var stdClass $prev_job */
if ( isset( $prev_job->original_doc_id ) ) {
foreach ( $prev_job->elements as $element ) {
$prev_translation[ $element->field_type ] = new WPML_TM_Translated_Field(
$element->field_data,
$element->field_data_translated,
$element->field_finished
);
}
}
return apply_filters( 'wpml_tm_populate_prev_translation', $prev_translation, $package, $this->get_lang_by_rid( $rid ) );
}
}

View File

@@ -0,0 +1,62 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-update-translation-data-action.class.php';
class WPML_TM_Update_Post_Translation_Data_Action extends WPML_TM_Update_Translation_Data_Action {
protected function populate_prev_translation( $rid, array $package ) {
global $wpml_post_translations;
$prev_translation = array();
if ( (bool) ( $lang = $this->get_lang_by_rid( $rid ) ) === true ) {
list( $prev_job_id ) = $this->get_prev_job_data( $rid );
$prev_job = $this->get_translation_job( $prev_job_id );
if ( $prev_job ) {
foreach ( $package['contents'] as $field_name => $field ) {
if ( array_key_exists( 'translate', $field ) && $field['translate'] ) {
$element = $this->get_previous_element( $prev_job, $field_name );
if ( $element ) {
$prev_translation[ $field_name ] = new WPML_TM_Translated_Field(
$element->field_data,
$element->field_data_translated,
$element->field_finished
);
}
}
}
}
$translated_post_id = $wpml_post_translations->element_id_in( $package['contents']['original_id']['data'], $lang );
if ( $translated_post_id ) {
$package_trans = $this->package_helper->create_translation_package( $translated_post_id );
$translated_contents = $package_trans['contents'];
foreach ( $package['contents'] as $field_name => $field ) {
if ( ! array_key_exists( $field_name, $prev_translation )
&& array_key_exists( $field_name, $translated_contents )
&& array_key_exists( 'data', $translated_contents[ $field_name ] )
) {
$prev_translation[ $field_name ] = new WPML_TM_Translated_Field(
'',
$translated_contents[ $field_name ]['data'],
false );
}
}
}
$prev_translation = apply_filters( 'wpml_tm_populate_prev_translation', $prev_translation, $package, $lang );
}
return $prev_translation;
}
private function get_previous_element( $prev_job, $field_name ) {
foreach ( $prev_job->elements as $element ) {
if ( $element->field_type == $field_name ) {
return $element;
}
}
return null;
}
}

View File

@@ -0,0 +1,134 @@
<?php
abstract class WPML_TM_Update_Translation_Data_Action extends WPML_Translation_Job_Helper_With_API {
function get_prev_job_data( $rid ) {
global $wpdb;
// if we have a previous job_id for this rid mark it as the top (last) revision
return $wpdb->get_row(
$wpdb->prepare(
"SELECT job_id, translated
FROM {$wpdb->prefix}icl_translate_job
WHERE rid=%d
AND revision IS NULL
LIMIT 1",
$rid
),
ARRAY_N
);
}
/**
* Adds a translation job record in icl_translate_job
*
* @param mixed $rid
* @param mixed $translator_id
* @param $translation_package
* @param array $batch_options
*
* @return bool|int
*/
function add_translation_job( $rid, $translator_id, array $translation_package, array $batch_options ) {
global $wpdb, $current_user;
$translation_status = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}icl_translation_status WHERE rid=%d", $rid ) );
$prev_translation = $this->get_translated_field_values( $rid, $translation_package );
if ( ! $current_user->ID ) {
$manager_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT manager_id FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d ORDER BY job_id DESC LIMIT 1",
$rid
)
);
} else {
$manager_id = $current_user->ID;
}
$translate_job_insert_data = array(
'rid' => $rid,
'translator_id' => $translator_id,
'translated' => 0,
'manager_id' => (int) $manager_id,
);
if ( isset( $batch_options['deadline_date'] ) ) {
$translate_job_insert_data['deadline_date'] = $batch_options['deadline_date'];
}
if ( isset( $translation_package['title'] ) ) {
$translate_job_insert_data['title'] = mb_substr( $translation_package['title'], 0, 160 );
}
$wpdb->insert( $wpdb->prefix . 'icl_translate_job', $translate_job_insert_data );
$job_id = $wpdb->insert_id;
$this->package_helper->save_package_to_job( $translation_package, $job_id, $prev_translation );
if ( (int) $translation_status->status !== ICL_TM_DUPLICATE ) {
$this->fire_notification_actions( $job_id, $translation_status, $translator_id );
}
return $job_id;
}
/**
* @param int $prev_id
* @param array $package
*
* @return mixed
*/
abstract protected function populate_prev_translation( $prev_id, array $package );
/**
* @param int $rid
* @param array $package
*
* @return mixed
*/
protected function get_translated_field_values( $rid, array $package ) {
global $wpdb;
$prev_translations = $this->populate_prev_translation( $rid, $package );
if ( ! $prev_translations ) {
return array();
}
// if we have a previous job_id for this rid mark it as the top (last) revision
list( $prev_job_id, $prev_job_translated ) = $this->get_prev_job_data( $rid );
if ( ! is_null( $prev_job_id ) ) {
$last_rev_prepare = $wpdb->prepare(
"
SELECT MAX(revision)
FROM {$wpdb->prefix}icl_translate_job
WHERE rid=%d
AND ( revision IS NOT NULL OR translated = 1 )
",
$rid
);
$last_rev = $wpdb->get_var( $last_rev_prepare );
$wpdb->update( $wpdb->prefix . 'icl_translate_job', array( 'revision' => $last_rev + 1 ), array( 'job_id' => $prev_job_id ) );
}
return $prev_translations;
}
protected function fire_notification_actions( $job_id, $translation_status, $translator_id ) {
global $wpml_translation_job_factory;
$job = $wpml_translation_job_factory->get_translation_job( $job_id, false, 0, true );
if ( $job && $translation_status->translation_service === 'local' ) {
if ( $this->get_tm_setting( array( 'notification', 'new-job' ) ) == ICL_TM_NOTIFICATION_IMMEDIATELY ) {
if ( $job_id ) {
if ( empty( $translator_id ) ) {
do_action( 'wpml_tm_new_job_notification', $job );
} else {
do_action( 'wpml_tm_assign_job_notification', $job, $translator_id );
}
}
}
do_action( 'wpml_added_local_translation_job', $job_id );
}
}
}

View File

@@ -0,0 +1,398 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-translation-job.class.php';
abstract class WPML_Element_Translation_Job extends WPML_Translation_Job {
protected $original_del_text;
/** @var WPML_Translation_Job_Factory $job_factory */
protected $job_factory;
private $original_doc_id = false;
private $translation_id = false;
/**
* @param int $job_id
* @param null|int $batch_id
* @param null|TranslationManagement $tm_instance
* @param null|WPML_Translation_Job_Factory $job_factory
*/
function __construct( $job_id, $batch_id = null, &$tm_instance = null, &$job_factory = null ) {
parent::__construct( $job_id, $batch_id, $tm_instance );
$this->original_del_text = __( 'The original has been deleted!', 'sitepress' );
if ( ! $job_factory ) {
global $wpml_translation_job_factory;
$job_factory = &$wpml_translation_job_factory;
}
$this->job_factory = $job_factory;
}
function get_type() {
return 'Post';
}
function to_array() {
$this->maybe_load_basic_data();
$data_array = $this->basic_data_to_array( $this->basic_data );
$data_array['id'] = $this->basic_data->job_id;
$data_array['translation_id'] = $this->basic_data->translation_id;
$data_array['status'] = $this->get_status();
$data_array['translation_edit_url'] = $this->get_url();
$data_array['original_url'] = $this->get_url( true );
$data_array['post_title'] = esc_html( $this->get_title() );
return $data_array;
}
function to_xliff_file() {
$xliff = new WPML_TM_Xliff_Writer( $this->job_factory );
return $xliff->get_job_xliff_file( $this->get_id() );
}
function get_original_element_id() {
if ( ! $this->original_doc_id ) {
$this->original_doc_id = $this->get_iclt_field( 'element_id', false );
}
return $this->original_doc_id;
}
function get_translation_id() {
if ( ! $this->translation_id ) {
$translation_id = $this->get_iclt_field( 'translation_id', true );
$this->translation_id = $translation_id;
} else {
$translation_id = $this->translation_id;
}
return $translation_id;
}
/**
* Saves the job data in this object to the database (e.g. to a post)
*
* @param bool $complete whether or not to set the status
* of the target element to complete
*/
public function save_to_element( $complete = false ) {
global $wpdb, $wpml_post_translations, $wpml_term_translations;
$wpml_tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$save_data_action = new WPML_Save_Translation_Data_Action(
array(
'job_id' => $this->get_id(),
'complete' => $complete,
'fields' => array(),
),
$wpml_tm_records
);
$save_data_action->save_translation();
}
/**
* @return int
*/
function estimate_word_count() {
$fields = $this->get_original_fields();
$combined_string = join( ' ', $fields );
$calculator = new WPML_TM_Word_Calculator( new WPML_PHP_Functions() );
return $calculator->count_words( $combined_string, $this->get_source_language_code() );
}
function get_original_fields() {
global $wpdb;
$fields = $wpdb->get_results(
$wpdb->prepare(
"SELECT field_type, field_data, field_format
FROM {$wpdb->prefix}icl_translate
WHERE job_id = %d
AND field_translate = 1",
$this->get_id()
)
);
$res = array();
foreach ( $fields as $field ) {
$res[ $field->field_type ] = base64_decode( $field->field_data );
}
return $res;
}
public function cancel() {
global $wpdb;
$deleted = false;
$rid_query = "SELECT rid FROM {$wpdb->prefix}icl_translate_job WHERE job_id=%d";
$rid_prepare = $wpdb->prepare( $rid_query, array( $this->job_id ) );
$rid = $wpdb->get_var( $rid_prepare );
$translation_id_query = "SELECT translation_id FROM {$wpdb->prefix}icl_translation_status WHERE rid=%d";
$translation_id_prepare = $wpdb->prepare( $translation_id_query, array( $rid ) );
$translation_id = $wpdb->get_var( $translation_id_prepare );
if ( $rid ) {
$wpdb->delete( $wpdb->prefix . 'icl_translate_job', array( 'job_id' => $this->job_id ) );
$wpdb->delete( $wpdb->prefix . 'icl_translate', array( 'job_id' => $this->job_id ) );
$deleted = true;
}
if ( $translation_id ) {
$wpdb->delete( $wpdb->prefix . 'icl_translations', array( 'translation_id' => $translation_id ) );
if ( $rid ) {
$wpdb->delete(
$wpdb->prefix . 'icl_translation_status',
array(
'translation_id' => $translation_id,
'rid' => $rid,
)
);
}
}
return $deleted;
}
/**
* @param TranslationProxy_Project $project
* @param int $translator_id
* @param WPML_TM_CMS_ID $cms_id_helper
* @param TranslationManagement $tm_instance
* @param null|string $note
*
* @return array
*/
function send_to_tp( $project, $translator_id, &$cms_id_helper, &$tm_instance, $note = null ) {
global $wpdb;
$this->maybe_load_basic_data();
$file = $this->to_xliff_file();
$title = $this->get_title();
$cms_id = $cms_id_helper->cms_id_from_job_id( $this->get_id() );
$url = $this->get_url( true );
$word_count = $this->estimate_word_count();
$note = isset( $note ) ? $note : '';
$source_language = $this->get_source_language_code();
$target_language = $this->get_language_code();
$uuid = $this->get_uuid();
try {
$tp_job_id = $project->send_to_translation_batch_mode( $file, $title, $cms_id, $url, $source_language, $target_language, $word_count, $translator_id, $note, $uuid );
} catch ( Exception $err ) {
// The translation entry will be removed
$project->errors[] = $err;
$tp_job_id = 0;
}
$translation_id = $this->get_translation_id();
if ( $tp_job_id ) {
$tm_instance->update_translation_status(
array(
'translation_id' => $translation_id,
'translator_id' => $translator_id,
'status' => ICL_TM_IN_PROGRESS,
'needs_update' => 0,
)
);
} else {
$previous_state = $wpdb->get_var(
$wpdb->prepare(
" SELECT _prevstate
FROM {$wpdb->prefix}icl_translation_status
WHERE translation_id=%d
LIMIT 1",
$translation_id
)
);
if ( ! empty( $previous_state ) ) {
$previous_state = unserialize( $previous_state );
$data = array(
'status' => $previous_state['status'],
'translator_id' => $previous_state['translator_id'],
'needs_update' => $previous_state['needs_update'],
'md5' => $previous_state['md5'],
'translation_service' => $previous_state['translation_service'],
'translation_package' => $previous_state['translation_package'],
'timestamp' => $previous_state['timestamp'],
'links_fixed' => $previous_state['links_fixed'],
);
$data_where = array( 'translation_id' => $translation_id );
$wpdb->update( $wpdb->prefix . 'icl_translation_status', $data, $data_where );
} else {
$data = array(
'status' => ICL_TM_NOT_TRANSLATED,
'needs_update' => 0,
);
$data_where = array( 'translation_id' => $translation_id );
$wpdb->update( $wpdb->prefix . 'icl_translation_status', $data, $data_where );
}
$err = true;
}
return array( isset( $err ) ? $err : false, $project, $tp_job_id );
}
/**
* @param bool|false $original
*
* @return string
*/
abstract function get_url( $original = false );
/**
* @return WP_Post|WPML_Package|mixed
*/
abstract function get_original_document();
protected function load_status() {
$this->maybe_load_basic_data();
$this->basic_data->status = ! empty( $this->basic_data->translated ) ? ICL_TM_COMPLETE : $this->basic_data->status;
return TranslationManagement::get_job_status_string(
$this->basic_data->status,
$this->basic_data->needs_update
);
}
/**
* @param int $job_id
*
* @return bool|stdClass|WPML_Element_Translation_Job
*/
protected function load_job_data( $job_id ) {
if ( $this->job_factory ) {
return $this->job_factory->get_translation_job( $job_id, false, 1 );
}
return false;
}
protected function save_updated_assignment() {
global $wpdb;
$job_id = $this->get_id();
$service = $this->get_translation_service();
list( $prev_translator_id, $rid ) = $wpdb->get_row( $wpdb->prepare( "SELECT translator_id, rid FROM {$wpdb->prefix}icl_translate_job WHERE job_id=%d", $job_id ), ARRAY_N );
$translator_id = $this->get_translator_id();
$assigned_correctly = $translator_id == $prev_translator_id;
$assigned_correctly = apply_filters( 'wpml_job_assigned_to_after_assignment', $assigned_correctly, $job_id, $translator_id, $service );
if ( $assigned_correctly ) {
return true;
}
$data = array(
'translator_id' => $translator_id,
'status' => ICL_TM_WAITING_FOR_TRANSLATOR,
'translation_service' => $service,
);
$data_where = array( 'rid' => $rid );
$wpdb->update( $wpdb->prefix . 'icl_translation_status', $data, $data_where );
$wpdb->update( $wpdb->prefix . 'icl_translate_job', array( 'translator_id' => $translator_id ), array( 'job_id' => $job_id ) );
return true;
}
/**
* Retrieves the batch ID for job elements using the
* `icl_translation_status` and `icl_translate_job` tables
*/
protected function load_batch_id() {
global $wpdb;
$this->batch_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT batch_id
FROM {$wpdb->prefix}icl_translation_status as ts
LEFT JOIN {$wpdb->prefix}icl_translate_job as tj ON tj.rid = ts.rid
WHERE tj.job_id = %d AND tj.revision IS NULL
LIMIT 1",
$this->job_id
)
);
}
private function get_iclt_field( $field_name, $translation ) {
global $wpdb;
$column_name = ( $translation === true ? 'i' : 'o' ) . '.' . $field_name;
$query = " SELECT {$column_name}
FROM {$wpdb->prefix}icl_translations o
JOIN {$wpdb->prefix}icl_translations i
ON i.trid = o.trid
AND i.source_language_code = o.language_code
JOIN {$wpdb->prefix}icl_translation_status s
ON s.translation_id = i.translation_id
JOIN {$wpdb->prefix}icl_translate_job j
ON j.rid = s.rid
WHERE j.job_id = %d
LIMIT 1";
$args = array( $this->get_id() );
$prepared_query = $wpdb->prepare( $query, $args );
return $wpdb->get_var( $prepared_query );
}
/**
* If the job does not have deadline date,
* we consider that the job was completed on time.
*
* @return bool
*/
public function is_completed_on_time() {
return $this->get_number_of_days_overdue() <= 0;
}
/**
* @return false|int Negative integer if the job was completed before the deadline, or positive either.
* False is the job has no deadline date
*/
public function get_number_of_days_overdue() {
$deadline = $this->get_deadline_date();
$completed = $this->get_completed_date();
if ( ! $deadline ) {
return false;
}
if ( ! $completed ) {
$completed = strtotime( 'now' );
} else {
$completed = strtotime( $completed );
}
$deadline = strtotime( $deadline );
return (int) floor( ( $completed - $deadline ) / DAY_IN_SECONDS );
}
/** @return string|null */
public function get_deadline_date() {
return $this->get_basic_data_property( 'deadline_date' );
}
/** @return string|null */
public function get_completed_date() {
return $this->get_basic_data_property( 'completed_date' );
}
/** @return string|null */
public function get_manager_id() {
return $this->get_basic_data_property( 'manager_id' );
}
/** @return string|null */
protected function get_title_from_db() {
return $this->get_basic_data_property( 'title' );
}
/** @return string|null */
protected function get_uuid() {
return $this->get_basic_data_property( 'uuid' );
}
}

View File

@@ -0,0 +1,88 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-translation-job.class.php';
class WPML_External_Translation_Job extends WPML_Element_Translation_Job {
function get_original_document() {
return apply_filters(
'wpml_get_translatable_item',
null,
$this->get_original_element_id(),
isset( $this->basic_data->original_post_type ) ? $this->basic_data->original_post_type : null
);
}
/**
* @param bool|false $original
*
* @return string
*/
public function get_url( $original = false ) {
$url = null;
$element_id = null;
if ( $original ) {
$element_id = $this->get_original_element_id();
$url = apply_filters( 'wpml_external_item_url', '', $element_id );
}
return apply_filters( 'wpml_element_translation_job_url', $url, $original, $element_id, $this->get_original_document() );
}
/**
* @return string
*/
public function get_title() {
$title = $this->get_title_from_db();
if ( $title ) {
return $title;
}
$original_element = $this->get_original_document();
return $original_element
? apply_filters( 'wpml_tm_external_translation_job_title', $this->title_from_job_fields(), $original_element->ID )
: $this->original_del_text;
}
/**
* @return string
*/
public function get_type_title() {
$original_element = $this->get_original_document();
return $original_element->kind;
}
protected function load_resultant_element_id() {
return 0;
}
private function title_from_job_fields() {
global $wpdb;
$title_and_name = $wpdb->get_row(
$wpdb->prepare(
"
SELECT n.field_data AS name, t.field_data AS title
FROM {$wpdb->prefix}icl_translate AS n
JOIN {$wpdb->prefix}icl_translate AS t
ON n.job_id = t.job_id
WHERE n.job_id = %d
AND n.field_type = 'name'
AND t.field_type = 'title'
LIMIT 1
",
$this->get_id()
)
);
return $title_and_name !== null ? ( $title_and_name->name ?
base64_decode( $title_and_name->name )
: base64_decode( $title_and_name->title ) ) : '';
}
}

View File

@@ -0,0 +1,291 @@
<?php
use WPML\TM\Jobs\FieldId;
use WPML\TM\Jobs\TermMeta;
use WPML\FP\Lst;
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-translation-job.class.php';
class WPML_Post_Translation_Job extends WPML_Element_Translation_Job {
function get_original_document() {
return get_post( $this->get_original_element_id() );
}
/**
* @param bool|false $original
*
* @return string
*/
public function get_url( $original = false ) {
$url = null;
$element_id = null;
if ( $original ) {
$element_id = $this->get_original_element_id();
$url = get_permalink( $element_id );
} else {
$element_id = $this->get_resultant_element_id();
$url = get_edit_post_link( $element_id );
}
return apply_filters( 'wpml_element_translation_job_url', $url, $original, $element_id, $this->get_original_document() );
}
/**
* It checks that the post type is translatable.
*
* @return bool
*/
function is_translatable_post_type() {
$post_type = $this->get_post_type();
if ( $post_type ) {
/** @var SitePress $sitepress */
global $sitepress;
if ( $sitepress ) {
$post_types = array_keys( $sitepress->get_translatable_documents() );
return in_array( $post_type, $post_types, true );
}
}
return false;
}
function update_fields_from_post() {
global $iclTranslationManagement, $wpdb, $wpml_translation_job_factory;
$job_id = $this->get_id();
$post_id = $this->get_resultant_element_id();
$data['complete'] = 1;
$data['job_id'] = $job_id;
$job = $wpml_translation_job_factory->get_translation_job( $job_id, 1 );
$term_names = $this->get_term_field_array_for_post();
$post = get_post( $post_id );
if ( is_object( $job ) && is_array( $job->elements ) && is_object( $post ) ) {
foreach ( $job->elements as $element ) {
$field_data = '';
switch ( $element->field_type ) {
case 'title':
$field_data = $this->encode_field_data( $post->post_title);
break;
case 'body':
$field_data = $this->encode_field_data( $post->post_content);
break;
case 'excerpt':
$field_data = $this->encode_field_data( $post->post_excerpt);
break;
case 'URL':
$field_data = $this->encode_field_data( $post->post_name);
break;
default:
if ( isset( $term_names[ $element->field_type ] ) ) {
$field_data = $this->encode_field_data( $term_names[ $element->field_type ]);
}
}
if ( $field_data ) {
$wpdb->update( $wpdb->prefix . 'icl_translate',
array(
'field_data_translated' => $field_data,
'field_finished' => 1
),
array( 'tid' => $element->tid )
);
}
}
$iclTranslationManagement->mark_job_done( $job_id );
}
}
function save_terms_to_post() {
/** @var SitePress $sitepress */
global $sitepress, $wpdb;
$lang_code = $this->get_language_code();
if ( $sitepress->get_setting( 'tm_block_retranslating_terms' ) ) {
$this->load_terms_from_post_into_job( true );
}
$terms = $this->get_terms_in_job_rows();
foreach ( $terms as $term ) {
$new_term_action = new WPML_Update_Term_Action( $wpdb, $sitepress, [
'term' => base64_decode( $term->field_data_translated ),
'description' => TermMeta::getTermDescription( $this->get_id(), $term->term_taxonomy_id ),
'lang_code' => $lang_code,
'trid' => $term->trid,
'taxonomy' => $term->taxonomy
] );
$new_term = $new_term_action->execute();
foreach ( TermMeta::getTermMeta( $this->get_id(), $term->term_taxonomy_id ) as $meta ) {
update_term_meta( $new_term['term_taxonomy_id'], FieldId::getTermMetaKey( $meta->field_type ), $meta->field_data_translated );
}
}
$term_helper = wpml_get_term_translation_util();
$term_helper->sync_terms( $this->get_original_element_id(), $this->get_language_code() );
}
function load_terms_from_post_into_job( $delete = null ) {
global $sitepress;
$delete = isset( $delete ) ? $delete : $sitepress->get_setting( 'tm_block_retranslating_terms' );
$this->set_translated_term_values( $delete );
}
/**
* @return string
*/
public function get_title() {
$title = $this->get_title_from_db();
if ( $title ) {
return $title;
}
$original_post = $this->get_original_document();
return is_object( $original_post ) && isset( $original_post->post_title )
? $original_post->post_title : $this->original_del_text;
}
/**
* @return string
*/
public function get_type_title() {
$post_type = get_post_type_object( $this->get_post_type() );
return $post_type->labels->singular_name;
}
/**
* @return string
*/
public function get_post_type() {
$original_post = $this->get_original_document();
return $original_post->post_type;
}
protected function load_resultant_element_id() {
global $wpdb;
$this->maybe_load_basic_data();
return $wpdb->get_var( $wpdb->prepare( "SELECT element_id
FROM {$wpdb->prefix}icl_translations
WHERE translation_id = %d
LIMIT 1",
$this->basic_data->translation_id ) );
}
protected function get_terms_in_job_rows(){
global $wpdb;
$query_for_terms_in_job = $wpdb->prepare(" SELECT
tt.taxonomy,
tt.term_taxonomy_id,
iclt.trid,
j.field_data_translated
FROM {$wpdb->term_taxonomy} tt
JOIN {$wpdb->prefix}icl_translations iclt
ON iclt.element_id = tt.term_taxonomy_id
AND CONCAT('tax_', tt.taxonomy) = iclt.element_type
JOIN {$wpdb->prefix}icl_translate j
ON j.field_type = CONCAT('t_', tt.term_taxonomy_id)
WHERE j.job_id = %d ", $this->get_id());
return $wpdb->get_results( $query_for_terms_in_job );
}
/**
* Retrieves an array of all terms associated with a post. This array is indexed by indexes of the for {t_}{term_taxonomy_id}.
*
* @return array
*/
protected function get_term_field_array_for_post() {
global $wpdb;
$post_id = $this->get_resultant_element_id();
$query = $wpdb->prepare( "SELECT o.term_taxonomy_id, t.name
FROM {$wpdb->term_relationships} o
JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = o.term_taxonomy_id
JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
WHERE o.object_id = %d",
$post_id );
$res = $wpdb->get_results( $query );
$result = array();
foreach ( $res as $term ) {
$result[ 't_' . $term->term_taxonomy_id ] = $term->name;
}
return $result;
}
protected function set_translated_term_values( $delete ) {
global $wpdb;
$translations_table = $wpdb->prefix . 'icl_translations';
$translate_table = $wpdb->prefix . 'icl_translate';
$job_id = $this->get_id();
$get_target_terms_for_job_query = $wpdb->prepare( "
SELECT
t.name,
tt.description,
iclt_original.element_id ttid,
t.term_id tr_ttid
FROM {$wpdb->terms} t
JOIN {$wpdb->term_taxonomy} tt
ON t.term_id = tt.term_id
JOIN {$translations_table} iclt_translation
ON iclt_translation.element_id = tt.term_taxonomy_id
AND CONCAT('tax_', tt.taxonomy) = iclt_translation.element_type
JOIN {$translations_table} iclt_original
ON iclt_original.trid = iclt_translation.trid
JOIN {$translate_table} jobs
ON jobs.field_type = CONCAT('t_', iclt_original.element_id)
WHERE jobs.job_id = %d
AND iclt_translation.language_code = %s",
$job_id, $this->get_language_code() );
$term_values = $wpdb->get_results( $get_target_terms_for_job_query );
foreach ( $term_values as $term ) {
if ( $delete ) {
$conditions = [
"field_type LIKE 'tfield-%-{$term->ttid}'", // Term fields
"field_type LIKE 'tfield-%-{$term->ttid}\_%'", // Term fields as array
"field_type = 't_{$term->ttid}'",
"field_type = 'tdesc_{$term->ttid}'",
];
$wpdb->query(
"DELETE FROM {$translate_table} WHERE job_id = $job_id AND "
. "(" . Lst::join( ' OR ', $conditions ) . ")"
);
} else {
$wpdb->update(
$translate_table,
[ 'field_data_translated' => base64_encode( $term->name ), 'field_finished' => 1 ],
[ 'field_type' => 't_' . $term->ttid, 'job_id' => $job_id ]
);
$wpdb->update(
$translate_table,
[ 'field_data_translated' => base64_encode( $term->description ), 'field_finished' => 1 ],
[ 'field_type' => 'tdesc_' . $term->ttid, 'job_id' => $job_id ]
);
$meta_values = $wpdb->get_results( "SELECT meta_key, meta_value FROM {$wpdb->termmeta} WHERE term_id = {$term->tr_ttid}" );
foreach ( $meta_values as $meta ) {
$wpdb->update(
$translate_table,
[ 'field_finished' => 1, 'field_data_translated' => base64_encode( $meta->meta_value ) ],
[ 'job_id' => $job_id, 'field_type' => 'tfield-' . $meta->meta_key . '-' . $term->ttid ]
);
}
}
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-translation-job.class.php';
class WPML_String_Translation_Job extends WPML_Translation_Job {
protected function load_job_data( $string_translation_id ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT st.id,
s.language AS source_language_code,
st.language AS language_code,
IF(cs.status IS NULL, st.status, cs.status) as status,
st.string_id,
s.name,
s.value,
tb.id AS batch_id,
st.translation_service,
st.translator_id,
u.display_name as translator_name,
COUNT( st.id ) as strings_count
FROM {$wpdb->prefix}icl_string_translations AS st
INNER JOIN {$wpdb->prefix}icl_strings AS s
ON st.string_id = s.id
INNER JOIN {$wpdb->prefix}icl_translation_batches AS tb
ON tb.id = st.batch_id
LEFT JOIN {$wpdb->users} u
ON st.translator_id = u.ID
LEFT JOIN {$wpdb->prefix}icl_string_status ss ON ss.string_translation_id = st.id
LEFT JOIN {$wpdb->prefix}icl_core_status cs ON cs.rid = ss.rid
WHERE st.id = %d
LIMIT 1",
$string_translation_id
);
return $wpdb->get_row( $query );
}
public function get_title() {
$this->maybe_load_basic_data();
return esc_html( $this->basic_data->value );
}
/**
* @return string
*/
public function get_id() {
return 'string|' . parent::get_id();
}
public function get_type() {
return 'String';
}
public function get_original_element_id() {
if ( ! $this->basic_data ) {
$this->maybe_load_basic_data();
}
return $this->basic_data->string_id;
}
public function cancel() {
global $WPML_String_Translation, $wpdb;
/** @var WPML_String_Translation $WPML_String_Translation */
if ( $WPML_String_Translation ) {
$rid = $wpdb->get_var(
$wpdb->prepare(
"SELECT rid
FROM {$wpdb->prefix}icl_string_status
WHERE string_translation_id = %d
LIMIT 1",
$this->job_id
)
);
if ( $rid ) {
$WPML_String_Translation->cancel_remote_translation( $rid );
}
}
}
protected function load_status() {
$this->maybe_load_basic_data();
$this->status = WPML_Remote_String_Translation::get_string_status_label( $this->basic_data->status );
return $this->status;
}
public function to_array() {
$this->maybe_load_basic_data();
$this->basic_data->value = $this->get_title();
$data_array = $this->basic_data_to_array( $this->basic_data );
$data_array['job_id'] = 'string|' . $this->job_id;
$data_array['translation_id'] = $this->basic_data->id;
$data_array['status'] = $this->get_status();
$data_array['id'] = $this->get_id();
return $data_array;
}
protected function load_resultant_element_id() {
global $wpdb;
return $wpdb->get_var(
$wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}icl_string_translations
WHERE string_id = %d AND language = %s",
$this->get_original_element_id(),
$this->get_language_code()
)
);
}
protected function save_updated_assignment() {
global $wpdb;
return $wpdb->update(
$wpdb->prefix . 'icl_string_translations',
array(
'translator_id' => $this->get_translator_id(),
'translation_service' => $this->get_translation_service(),
),
array(
'string_id' => $this->get_original_element_id(),
'language' => $this->get_language_code(),
)
);
}
/**
* Retrieves the batch ID for a string job
*/
protected function load_batch_id() {
global $wpdb;
$this->batch_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT batch_id
FROM {$wpdb->prefix}icl_string_translations
WHERE id = %d
LIMIT 1",
$this->job_id
)
);
}
}

View File

@@ -0,0 +1,411 @@
<?php
abstract class WPML_Translation_Job extends WPML_Translation_Job_Helper {
protected $basic_data;
protected $element_id = - 1;
protected $status = - 1;
protected $job_id;
protected $batch_id;
/** @var WPML_TM_Blog_Translators $blog_translators */
protected $blog_translators;
/**
* @param int $job_id
* @param int|null $batch_id
* @param WPML_TM_Blog_Translators $blog_translators
*/
function __construct( $job_id, $batch_id = null, &$blog_translators = null ) {
$this->job_id = $job_id;
$batch_id = $batch_id ? $batch_id : $this->get_batch_id();
$this->batch_id = $batch_id ? $batch_id : TranslationProxy_Batch::update_translation_batch();
$this->blog_translators = $blog_translators ? $blog_translators : wpml_tm_load_blog_translators();
}
abstract public function cancel();
abstract public function get_original_element_id();
abstract public function to_array();
/**
* @return string
*/
abstract function get_title();
public function get_status() {
if ( $this->status == - 1 ) {
$this->status = $this->load_status();
}
return $this->status;
}
public function get_status_value() {
$this->maybe_load_basic_data();
return $this->basic_data->status;
}
public function get_id() {
return $this->job_id;
}
public function get_resultant_element_id( $force = false ) {
if ( $this->element_id == - 1 || $force === true ) {
$this->element_id = $this->load_resultant_element_id();
}
return $this->element_id;
}
/**
* Checks whether the input user is allowed to edit this job
*
* @param WP_User $user
*
* @return bool
*/
public function user_can_translate( $user ) {
$translator_id = $this->get_translator_id();
$user_can_take_this_job = 0 === $translator_id
|| $this->is_current_user_allowed_to_translate(
$user,
$translator_id
);
$translator_has_job_language_pairs = $this->blog_translators->is_translator(
$user->ID,
array(
'lang_from' => $this->get_source_language_code(),
'lang_to' => $this->get_language_code(),
)
);
$user_can_translate = ( $user_can_take_this_job && $translator_has_job_language_pairs )
|| user_can( $user, 'manage_options' );
return apply_filters( 'wpml_user_can_translate', $user_can_translate, $user );
}
/**
* @param WP_User $user
* @param int $translator_id
*
* @return bool
*/
private function is_current_user_allowed_to_translate( WP_User $user, $translator_id ) {
$allowed_translators = apply_filters( 'wpml_tm_allowed_translators_for_job', array(), $this );
$allowed_translators[] = $translator_id;
return in_array( (int) $user->ID, $allowed_translators, true );
}
public function get_batch_id() {
if ( ! isset( $this->batch_id ) ) {
$this->load_batch_id();
}
return $this->batch_id;
}
/**
* @param bool|false $as_name if true will return the language's display name if applicable
*
* @return bool|string
*/
public function get_language_code( $as_name = false ) {
$this->maybe_load_basic_data();
$code = isset( $this->basic_data->language_code ) ? $this->basic_data->language_code : false;
return $code && $as_name ? $this->lang_code_to_name( $code ) : $code;
}
/**
* @param bool|false $as_name if true will return the language's display name if applicable
*
* @return bool|string
*/
function get_source_language_code( $as_name = false ) {
$this->maybe_load_basic_data();
$code = isset( $this->basic_data->source_language_code ) ? $this->basic_data->source_language_code : false;
return $code && $as_name ? $this->lang_code_to_name( $code ) : $code;
}
/**
* @return string|false
*/
public function get_translator_name() {
$this->maybe_load_basic_data();
if ( $this->basic_data->translation_service == TranslationProxy::get_current_service_id() ) {
$this->basic_data->translator_name = TranslationProxy_Translator::get_translator_name( $this->basic_data->translator_id );
} else {
$this->basic_data->translator_name = false;
}
return $this->basic_data->translator_name;
}
/**
* Returns the id of the assigned translator or 0 if no translator is assigned to the job
*
* @return int
*/
public function get_translator_id() {
$this->maybe_load_basic_data();
$this->basic_data->translator_id = ! empty( $this->basic_data->translator_id )
? $this->basic_data->translator_id : 0;
return (int) $this->basic_data->translator_id;
}
public function get_basic_data() {
$this->maybe_load_basic_data();
return $this->basic_data;
}
/**
* @param int $translator_id
* @param string $service
*
* @return bool true on success false on failure
*/
public function assign_to( $translator_id, $service = 'local' ) {
$this->maybe_load_basic_data();
$prev_translator_id = $this->get_translator_id();
$prev_service = $this->get_translation_service();
if ( $translator_id == $prev_translator_id && $service = $this->get_translation_service() ) {
return true;
}
$this->basic_data->translator_id = $translator_id;
$this->basic_data->translation_service = $service;
if ( $this->save_updated_assignment() === false ) {
$this->basic_data->translator_id = $prev_translator_id;
$this->basic_data->translation_service = $prev_service;
return false;
}
$job_id = $this->get_id();
if ( $this->get_tm_setting( array( 'notification', 'resigned' ) ) == ICL_TM_NOTIFICATION_IMMEDIATELY
&& ! empty( $prev_translator_id )
&& $prev_translator_id != $translator_id
&& $job_id ) {
do_action( 'wpml_tm_remove_job_notification', $prev_translator_id, $this );
}
if ( $this->get_tm_setting( array( 'notification', 'new-job' ) ) == ICL_TM_NOTIFICATION_IMMEDIATELY ) {
if ( empty( $translator_id ) ) {
do_action( 'wpml_tm_new_job_notification', $this );
} else {
do_action( 'wpml_tm_assign_job_notification', $this, $translator_id );
}
}
return true;
}
/**
* Returns either the translation service id for the job or 'local' for local jobs
*
* @return int|string
*/
public function get_translation_service() {
$this->maybe_load_basic_data();
$this->basic_data->translation_service = ! empty( $this->basic_data->translation_service )
? $this->basic_data->translation_service : 'local';
return $this->basic_data->translation_service;
}
abstract protected function save_updated_assignment();
abstract protected function load_resultant_element_id();
abstract protected function load_status();
abstract protected function load_job_data( $id );
abstract function get_type();
protected function basic_data_to_array( $job_data ) {
$this->maybe_load_basic_data();
$data_array = (array) $job_data;
if ( isset( $data_array['post_title'] ) ) {
$data_array['post_title'] = esc_html( $data_array['post_title'] );
}
$data_array['translator_name'] = $this->get_translator_name();
$data_array['batch_id'] = $job_data->batch_id;
$data_array['source_language_code'] = $this->basic_data->source_language_code;
$data_array['language_code'] = $this->basic_data->language_code;
$data_array['translator_html'] = $this->get_translator_html( $this->basic_data );
$data_array['type'] = $this->get_type();
$data_array['lang_text'] = $this->generate_lang_text();
return $data_array;
}
protected function maybe_load_basic_data() {
if ( ! $this->basic_data ) {
$this->basic_data = $this->load_job_data( $this->job_id );
$this->basic_data = $this->basic_data ? $this->basic_data : new stdClass();
}
}
private function get_inactive_translation_service( $translation_service_id ) {
$cache_key = $translation_service_id;
$cache_group = 'get_inactive_translation_service';
$cache_found = false;
$service = wp_cache_get( $cache_key, $cache_group, false, $cache_found );
if ( ! $cache_found ) {
try {
$service = TranslationProxy_Service::get_service( $translation_service_id );
} catch ( WPMLTranslationProxyApiException $ex ) {
$service = false;
}
if ( ! $service ) {
$service = new stdClass();
$service->name = __( '(inactive and unknown service)', 'wpml-translation-management' );
}
wp_cache_set( $cache_key, $service, $cache_group );
}
return $service;
}
protected function get_translator_html( $job ) {
$job = (object) $job;
$current_service_name = TranslationProxy::get_current_service_name();
$translation_services = array( 'local', TranslationProxy::get_current_service_id() );
if ( isset( $job->translation_service ) && ! in_array( $job->translation_service, $translation_services ) ) {
$inactive_service = $this->get_inactive_translation_service( $job->translation_service );
$current_service_name = $inactive_service->name;
}
$translator = '';
if ( $job->translation_service && $job->translation_service !== 'local' ) {
try {
$project = TranslationProxy::get_current_project();
if ( $project ) {
$translator .= $current_service_name;
} else {
$translator .= esc_html( $job->translator_name );
}
} catch ( Exception $e ) {
// Just doesn't create the output
}
} elseif ( $job->status == ICL_TM_COMPLETE ) {
$translator_data = get_userdata( $job->translator_id );
$translator_name = $translator_data ? $translator_data->display_name : '';
$translator = '<span class="icl-finished-local-name">' . $translator_name . '</span>';
} else {
$translator .= '<span class="icl_tj_select_translator">';
$selected_translator = isset( $job->translator_id ) ? $job->translator_id : false;
$disabled = false;
if ( $job->translation_service
&& $job->translation_service !== 'local'
&& is_numeric( $job->translation_service ) ) {
$selected_translator = TranslationProxy_Service::get_wpml_translator_id(
$job->translation_service,
$job->translator_id
);
$disabled = true;
}
$job_id = isset( $job->job_id ) ? $job->job_id : $job->id;
$local_only = isset( $job->local_only ) ? $job->local_only : true;
$args = array(
'id' => 'icl_tj_translator_for_' . $job_id,
'name' => 'icl_tj_translator_for_' . ( $job_id ),
'from' => $job->source_language_code,
'to' => $job->language_code,
'selected' => $selected_translator,
'services' => $translation_services,
'disabled' => $disabled,
'echo' => false,
'local_only' => $local_only,
);
$translator .= wpml_tm_get_translators_dropdown()->render( $args );
$translator .= '<input type="hidden" id="icl_tj_ov_'
. $job_id
. '" value="'
. (int) $job->translator_id
. '" />';
$translator .= '<input type="hidden" id="icl_tj_ty_'
. $job_id
. '" value="'
. strtolower( $this->get_type() )
. '" />';
$translator .= '<span class="icl_tj_select_translator_controls" id="icl_tj_tc_' . ( $job_id ) . '">';
$translator .= '<input type="button" class="button-secondary icl_tj_ok" value="'
. __(
'Send',
'wpml-translation-management'
)
. '" />&nbsp;';
$translator .= '<input type="button" class="button-secondary icl_tj_cancel" value="'
. __(
'Cancel',
'wpml-translation-management'
)
. '" />';
$translator .= '</span>';
}
return $translator;
}
/**
* Retrieves the batch ID associated to the job ID
*/
abstract protected function load_batch_id();
/**
* @return string
*/
protected function generate_lang_text() {
$this->maybe_load_basic_data();
return $this->lang_code_to_name( $this->get_source_language_code() )
. html_entity_decode( ' &raquo; ' )
. $this->lang_code_to_name( $this->get_language_code() );
}
/**
* @param string $code
*
* @return string
*/
private function lang_code_to_name( $code ) {
global $sitepress;
$lang_details = $sitepress->get_language_details( $code );
return isset( $lang_details['display_name'] ) ? $lang_details['display_name'] : $code;
}
/**
* @param string $name
*
* @return mixed
*/
protected function get_basic_data_property( $name ) {
$value = null;
$this->maybe_load_basic_data();
if ( isset( $this->basic_data->{$name} ) ) {
$value = $this->basic_data->{$name};
}
return $value;
}
}

View File

@@ -0,0 +1,204 @@
<?php
class WPML_Translation_Batch extends WPML_Abstract_Job_Collection {
private $name = false;
private $id = false;
private $url = false;
/** @var WPML_Translation_Job[] $job_objects */
private $job_objects = array();
/**
* @param wpdb $wpdb
* @param int $batch_id
*/
public function __construct( &$wpdb, $batch_id = 0 ) {
parent::__construct( $wpdb );
$this->id = $batch_id > 0 ? $batch_id : $this->retrieve_generic_batch_id();
$this->name = $batch_id <= 0 ? $this->generate_generic_batch_name() : false;
}
public function reload() {
global $wpdb;
list( $type_select, $post_join ) = $this->left_join_post();
$jobs = $wpdb->get_results(
$wpdb->prepare(
" SELECT j.job_id,
s.batch_id,
{$type_select}
FROM " . $this->get_table_join() . "
{$post_join}
WHERE s.batch_id = %d
AND j.revision IS NULL",
$this->id
)
);
$jobs = $this->plain_objects_to_job_instances( $jobs );
foreach ( $jobs as $job ) {
$this->add_job( $job );
}
}
public function get_batch_url() {
if ( $this->url === false ) {
$this->url = TranslationManagement::get_batch_url( $this->id );
}
return $this->url;
}
public function get_batch_meta_array() {
$in_active_ts = $this->belongs_to_active_ts();
$notifications = $this->ts_supports_notifications();
$batch_id = $this->get_batch_tp_id();
$batch_url = $this->get_batch_url();
$batch_name = $batch_url ? $this->get_batch_name() : '';
$item_count = $this->get_item_count();
return array(
'in_active_ts' => $in_active_ts,
'notifications' => $notifications,
'batch_id' => $batch_id,
'batch_url' => $batch_url,
'batch_name' => $batch_name,
'item_count' => $item_count,
'last_update' => $this->get_last_update(),
'status_array' => $this->get_status_array(),
'display_from' => 1,
'display_to' => $item_count,
);
}
/**
* Cancels all translation jobs in this batch
*/
public function cancel_all_jobs() {
/**
* @var wpdb $wpdb
* @var TranslationManagement $iclTranslationManagement
* @var WPML_String_Translation $WPML_String_Translation
*/
global $wpdb, $iclTranslationManagement, $WPML_String_Translation;
$translation_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT translation_id
FROM {$wpdb->prefix}icl_translation_status
WHERE batch_id = %d",
$this->id
)
);
foreach ( $translation_ids as $translation_id ) {
$iclTranslationManagement->cancel_translation_request( $translation_id );
}
$string_translation_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}icl_string_translations WHERE batch_id = %d",
$this->id
)
);
foreach ( $string_translation_ids as $st_trans_id ) {
$rid = $wpdb->get_var(
$wpdb->prepare(
"SELECT MAX(rid)
FROM {$wpdb->prefix}icl_string_status
WHERE string_translation_id = %d",
$st_trans_id
)
);
if ( $rid ) {
$WPML_String_Translation->cancel_remote_translation( $rid );
}
}
}
// todo: [WPML 3.2.1] This method and other similar methods can likely be removed
public function get_last_update() {
return TranslationManagement::get_batch_last_update( $this->id );
}
/**
* @param WPML_Translation_Job $job
*/
public function add_job( $job ) {
$this->job_objects[ $job->get_id() ] = $job;
}
public function get_jobs_as_array() {
$res = array();
krsort( $this->job_objects );
foreach ( $this->job_objects as $job ) {
$res[] = $job->to_array();
}
return $res;
}
public function get_item_count() {
return count( $this->job_objects );
}
public function get_id() {
return $this->id;
}
public function get_batch_name() {
if ( $this->name == false ) {
$this->name = TranslationManagement::get_batch_name( $this->get_id() );
}
return $this->name;
}
public function get_batch_tp_id() {
return TranslationManagement::get_batch_tp_id( $this->id );
}
public function get_status_array() {
$status_array = array();
foreach ( $this->job_objects as $job ) {
if ( ! isset( $status_array[ $job->get_status() ] ) ) {
$status_array[ $job->get_status() ] = 0;
}
$status_array[ $job->get_status() ] ++;
}
return $status_array;
}
private function retrieve_generic_batch_id() {
return TranslationProxy_Batch::update_translation_batch( $this->generate_generic_batch_name() );
}
private function generate_generic_batch_name() {
return 'Manual Translations from ' . date( 'F \t\h\e jS\, Y' );
}
private function belongs_to_active_ts() {
global $wpdb;
$service_id = TranslationProxy::get_current_service_id();
$batch_id = $this->get_id();
$result = false;
if ( $service_id ) {
$result = $wpdb->get_var( $wpdb->prepare( "SELECT rid FROM {$wpdb->prefix}icl_translation_status WHERE batch_id = %d AND translation_service = %s LIMIT 1", array( $batch_id, $service_id ) ) );
$result |= $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}icl_string_translations WHERE batch_id = %d AND translation_service = %s LIMIT 1", array( $batch_id, $service_id ) ) );
}
return $result;
}
private function ts_supports_notifications() {
$translation_service = TranslationProxy::get_current_service();
return $supports_notifications = isset( $translation_service->notification ) ? $translation_service->notification : true;
}
public function clear_batch_data() {
TranslationProxy_Basket::set_batch_data( null );
}
}

View File

@@ -0,0 +1,268 @@
<?php
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-element-translation-job.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-external-translation-job.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-post-translation-job.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-string-translation-job.class.php';
class WPML_Translation_Jobs_Collection extends WPML_Abstract_Job_Collection {
/** @var WPML_Translation_Batch[] $translation_batches */
private $translation_batches = array();
private $count = 0;
private $first_count;
private $last_count;
private $before_count = 0;
private $after_count = 0;
/** @var array $filter */
private $filter;
/** @var string $jobs_union_table_sql */
private $jobs_union_table_sql;
/**
* @param wpdb $wpdb
* @param array $icl_translation_filter
*/
public function __construct( &$wpdb, $icl_translation_filter ) {
parent::__construct( $wpdb );
$this->filter = $icl_translation_filter;
}
/**
* @param int $page
* @param int $per_page
*
* @return array
*/
public function get_paginated_batches( $page, $per_page ) {
$this->load_translation_jobs( $page, $per_page );
$metrics = array();
$batches = array();
if ( $this->translation_batches ) {
krsort( $this->translation_batches );
foreach ( $this->translation_batches as $id => $batch ) {
$metrics[ $id ] = $batch->get_batch_meta_array();
$batches[ $id ] = $batch;
}
}
$first_batch_metric = array_shift( $metrics );
$first_batch_metric['display_from'] = $this->before_count + 1;
$first_batch_metric['item_count'] = $this->first_count;
array_unshift( $metrics, $first_batch_metric );
$last_batch_metric = array_pop( $metrics );
$last_batch_metric['display_to'] = $this->last_count - $this->after_count;
$last_batch_metric['item_count'] = $this->last_count;
$metrics[] = $last_batch_metric;
return array(
'batches' => $batches,
'metrics' => $metrics,
);
}
/**
* Returns the number of jobs that meet the filter \WPML_Translation_Jobs_Collection::$filter in the database
*
* @return int
*/
public function get_count() {
return $this->count;
}
/**
* @param WPML_Translation_Job $job
*/
public function add_job( $job ) {
$batch_id = $job->get_batch_id();
$batch = array_key_exists( $batch_id, $this->translation_batches )
? $this->translation_batches[ $batch_id ] : new WPML_Translation_Batch( $this->wpdb, $batch_id );
$batch->add_job( $job );
$this->translation_batches[ $batch->get_id() ] = $batch;
}
private function load_translation_jobs( $page, $per_page ) {
$this->translation_batches = array();
list( $jobs, $count, $before_count, $after_count, $first_count, $last_count ) = $this->get_jobs_table(
$this->filter,
array(
'page' => $page,
'per_page' => $per_page,
)
);
$this->count = $count;
$this->after_count = $after_count;
$this->before_count = $before_count;
$this->first_count = $first_count;
$this->last_count = $last_count;
if ( is_array( $jobs ) ) {
foreach ( $jobs as $job ) {
$this->add_job( $job );
}
}
}
/**
* @param array $args
* @param array $pagination_args
*
* @return array
*/
private function get_jobs_table( array $args = array(), array $pagination_args = array(
'page' => 1,
'per_page' => 10,
) ) {
list( $data, $found_rows ) = $this->get_jobs_in_db( $args, $pagination_args );
if ( $data ) {
$result = $this->calculate_batch_counts( $data, $found_rows, $pagination_args );
} else {
$result = $this->get_default_batch_counts( $found_rows );
}
return $result;
}
/**
* @param int $found_rows
*
* @return array
*/
private function get_default_batch_counts( $found_rows ) {
return array( array(), $found_rows, 0, 0, 0, 0 );
}
private function get_jobs_in_db( array $args = array(), array $pagination_args = null ) {
$where_jobs = $this->build_where_clause( $args );
$jobs_table_union = $this->get_jobs_union_table_sql( $where_jobs, $args );
$only_ids_query = 'SELECT SQL_CALC_FOUND_ROWS ';
$only_ids_query .= 'jobs.job_id, jobs.translator_id, jobs.job_id, jobs.batch_id, jobs.element_type_prefix ';
$only_ids_query .= 'FROM ' . $jobs_table_union . ' ';
if ( $pagination_args ) {
$only_ids_query .= 'LIMIT %d, %d';
$prepare_args[] = max( ( $pagination_args['page'] - 1 ), 0 ) * $pagination_args['per_page'];
$prepare_args[] = $pagination_args['per_page'];
$only_ids_query = $this->wpdb->prepare( $only_ids_query, $prepare_args );
}
$data = $this->wpdb->get_results( $only_ids_query );
$found_rows = (int) $this->wpdb->get_var( 'SELECT FOUND_ROWS()' );
return array( $data, $found_rows );
}
public function get_jobs( array $args = array() ) {
list( $jobs_in_db, $found_rows ) = $this->get_jobs_in_db( $args );
$this->count = $found_rows;
return $this->plain_objects_to_job_instances( $jobs_in_db );
}
/**
* @param array $data
* @param int $count
* @param array $pagination_args
*
* @return array
*/
private function calculate_batch_counts( $data, $count, $pagination_args ) {
$first_job = reset( $data );
$last_job = end( $data );
$first_batch = $first_job->batch_id;
$last_batch = $last_job->batch_id;
$count_select_from_snippet = 'SELECT COUNT(jdata.job_id) FROM (SELECT job_id, batch_id FROM ' . $this->jobs_union_table_sql;
$count_where_snippet = ' ) AS jdata WHERE jdata.batch_id = %d';
$before_count_query = $count_select_from_snippet . ' LIMIT %d' . $count_where_snippet;
$page = $pagination_args['page'];
$per_page = $pagination_args['per_page'];
$count_before = $page > 1 ? $this->wpdb->get_var(
$this->wpdb->prepare(
$before_count_query,
array(
( $page - 1 ) * $per_page,
$first_batch,
)
)
) : 0;
$count_first = $this->wpdb->get_var(
$this->wpdb->prepare(
$before_count_query,
array( PHP_INT_MAX, $first_batch )
)
);
$after_count_query = $count_select_from_snippet . ' LIMIT %d, %d' . $count_where_snippet;
$count_after = $page * $per_page > $count ? 0 : $this->wpdb->get_var(
$this->wpdb->prepare(
$after_count_query,
array(
$page * $per_page,
PHP_INT_MAX,
$last_batch,
)
)
);
$count_last = $this->wpdb->get_var(
$this->wpdb->prepare(
$after_count_query,
array( 0, PHP_INT_MAX, $last_batch )
)
);
return array(
$this->plain_objects_to_job_instances( $data ),
$count,
$count_before,
$count_after,
$count_first,
$count_last,
);
}
private function get_jobs_union_table_sql( $where_jobs, $args ) {
$union_sql = '';
$sql_statements = array();
if ( $where_jobs ) {
$sql_statements[] = "SELECT s.translator_id,
j.job_id,
IF(p.post_type IS NOT NULL, 'post', 'package') AS element_type_prefix,
p.post_type AS post_type,
s.batch_id
FROM {$this->wpdb->prefix}icl_translation_status s
JOIN {$this->wpdb->prefix}icl_translations t
ON t.translation_id = s.translation_id
JOIN {$this->wpdb->prefix}icl_translate_job j
ON j.rid = s.rid
AND j.revision IS NULL
JOIN {$this->wpdb->prefix}icl_translations o
ON o.trid = t.trid
AND o.language_code = t.source_language_code
JOIN " . apply_filters( 'wpml_post_translation_original_table', $this->wpdb->posts ) . " p
ON o.element_id = p.ID
AND ( o.element_type = CONCAT('post_', p.post_type) OR p.post_type IS NULL )
JOIN {$this->wpdb->prefix}icl_translate tr_rows
ON tr_rows.job_id = j.job_id
AND tr_rows.field_type = 'original_id'
AND tr_rows.field_data = o.element_id
WHERE " . $where_jobs;
}
$sql_statements = apply_filters( 'wpml_tm_jobs_union_table_sql', $sql_statements, $args );
if ( count( $sql_statements ) > 0 ) {
$union_sql = '(' . implode( "\nUNION ALL\n", $sql_statements ) . ") jobs
INNER JOIN {$this->wpdb->prefix}icl_translation_batches b
ON b.id = jobs.batch_id
ORDER BY jobs.batch_id DESC, jobs.element_type_prefix, jobs.job_id DESC";
}
$this->jobs_union_table_sql = $union_sql;
return $union_sql;
}
}

View File

@@ -0,0 +1,20 @@
<?php
function translation_service_details( $service, $show_project = false ) {
$service_details = '';
if ( defined( 'OTG_SANDBOX_DEBUG' ) && OTG_SANDBOX_DEBUG ) {
$service_details .= '<h3>Service details:</h3>' . PHP_EOL;
$service_details .= '<pre>' . PHP_EOL;
$service_details .= print_r( $service, true );
$service_details .= '</pre>' . PHP_EOL;
if ( $show_project ) {
$project = TranslationProxy::get_current_project();
echo '<pre>$project' . PHP_EOL;
echo print_r( $project, true );
echo '</pre>';
}
}
return $service_details;
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @package wpml-core
* @subpackage wpml-core
*/
class TranslationProxy_Api {
const API_VERSION = 1.1;
public static function proxy_request(
$path,
$params = array(),
$method = 'GET',
$multi_part = false,
$has_return_value = true
) {
return wpml_tm_load_tp_networking()->send_request(
OTG_TRANSLATION_PROXY_URL . $path,
$params,
$method,
$has_return_value
);
}
public static function proxy_download( $path, $params ) {
return wpml_tm_load_tp_networking()->send_request(
OTG_TRANSLATION_PROXY_URL . $path,
$params,
'GET',
true,
false
);
}
public static function service_request(
$url,
$params = array(),
$method = 'GET',
$has_return_value = true,
$json_response = false,
$has_api_response = false
) {
return wpml_tm_load_tp_networking()->send_request(
$url,
$params,
$method,
$has_return_value,
$json_response,
$has_api_response
);
}
public static function add_parameters_to_url( $url, $params ) {
if ( preg_match_all( '/\{.+?\}/', $url, $symbs ) ) {
foreach ( $symbs[0] as $symb ) {
$without_braces = preg_replace( '/\{|\}/', '', $symb );
if ( preg_match_all( '/\w+/', $without_braces, $indexes ) ) {
foreach ( $indexes[0] as $index ) {
if ( isset( $params[ $index ] ) ) {
$value = $params[ $index ];
$url = preg_replace( preg_quote( "/$symb/" ), $value, $url );
}
}
}
}
}
return $url;
}
}
if ( ! function_exists( 'gzdecode' ) ) {
/**
* Inflates a string enriched with gzip headers. Counterpart to gzencode().
* Extracted from upgradephp
* http://include-once.org/p/upgradephp/
*
* officially available by default in php @since 5.4.
*/
function gzdecode( $gzdata, $maxlen = null ) {
// -- decode header
$len = strlen( $gzdata );
if ( $len < 20 ) {
return;
}
$head = substr( $gzdata, 0, 10 );
$head = unpack( 'n1id/C1cm/C1flg/V1mtime/C1xfl/C1os', $head );
list( $ID, $CM, $FLG, $MTIME, $XFL, $OS ) = array_values( $head );
$FTEXT = 1 << 0;
$FHCRC = 1 << 1;
$FEXTRA = 1 << 2;
$FNAME = 1 << 3;
$FCOMMENT = 1 << 4;
$head = unpack( 'V1crc/V1isize', substr( $gzdata, $len - 8, 8 ) );
list( $CRC32, $ISIZE ) = array_values( $head );
// -- check gzip stream identifier
if ( $ID != 0x1f8b ) {
trigger_error( 'gzdecode: not in gzip format', E_USER_WARNING );
return;
}
// -- check for deflate algorithm
if ( $CM != 8 ) {
trigger_error( 'gzdecode: cannot decode anything but deflated streams', E_USER_WARNING );
return;
}
// -- start of data, skip bonus fields
$s = 10;
if ( $FLG & $FEXTRA ) {
$s += $XFL;
}
if ( $FLG & $FNAME ) {
$s = strpos( $gzdata, "\000", $s ) + 1;
}
if ( $FLG & $FCOMMENT ) {
$s = strpos( $gzdata, "\000", $s ) + 1;
}
if ( $FLG & $FHCRC ) {
$s += 2; // cannot check
}
// -- get data, uncompress
$gzdata = substr( $gzdata, $s, $len - $s );
if ( $maxlen ) {
$gzdata = gzinflate( $gzdata, $maxlen );
return ( $gzdata ); // no checks(?!)
} else {
$gzdata = gzinflate( $gzdata );
}
// -- check+fin
$chk = crc32( $gzdata );
if ( $CRC32 != $chk ) {
trigger_error( "gzdecode: checksum failed (real$chk != comp$CRC32)", E_USER_WARNING );
} elseif ( $ISIZE != strlen( $gzdata ) ) {
trigger_error( 'gzdecode: stream size mismatch', E_USER_WARNING );
} else {
return ( $gzdata );
}
}
}

View File

@@ -0,0 +1,943 @@
<?php
/**
* @package wpml-core
* @subpackage wpml-core
*/
if ( ! class_exists( 'TranslationProxy_Basket' ) ) {
/**
* TranslationProxy_basket collects all static methods to operate on
* translations basket (cart)
*/
class TranslationProxy_Basket {
private static $messages;
private static $dashboard_select;
private static $basket;
// The name of the option stored in wp_options table and that
// stores all the basket items
const ICL_TRANSLATION_JOBS_BASKET = 'icl_translation_jobs_basket';
private static $posts_ids;
private static $translate_from;
private static $translation_action;
public static function add_message( $array ) {
self::$messages[] = $array;
}
public static function remove_message( $text ) {
if ( is_array( self::$messages ) ) {
foreach ( self::$messages as $key => $message ) {
if ( array_key_exists( 'text', $message ) && $message['text'] === $text ) {
unset( self::$messages[ $key ] );
}
}
}
}
public static function get_basket( $force = false ) {
if ( ! isset( self::$basket ) || $force ) {
self::$basket = get_option( self::ICL_TRANSLATION_JOBS_BASKET );
}
return self::$basket;
}
public static function update_basket( $basket_portion = array() ) {
if ( ! empty( $basket_portion ) ) {
if ( ! self::$basket || self::get_basket_items_count() == 0 ) {
self::$basket = $basket_portion;
} else {
self::$basket = self::merge_baskets( self::$basket, $basket_portion );
}
}
if ( self::get_basket_items_count( true ) == 0 ) {
self::$basket = false;
}
self::sync_target_languages();
self::update_basket_option( self::$basket );
self::update_basket_notifications();
}
/**
* @param array $basket
*/
private static function update_basket_option( $basket ) {
update_option( self::ICL_TRANSLATION_JOBS_BASKET, $basket, false );
}
private static function merge_baskets( $from, $to ) {
if ( function_exists( 'array_replace_recursive' ) ) {
return array_replace_recursive( $from, $to );
} else {
return self::array_replace_recursive( $from, $to );
}
}
/**
* Return number of items in translation basket by key
*
* @param string $type
* @param bool $skip_cache
*
* @return int number of items in translation basket
*/
public static function get_basket_items_type_count( $type, $skip_cache = false ) {
$cache_key = $type;
$cache_group = 'get_basket_items_type_count';
$cache_found = false;
if ( ! $skip_cache ) {
$basket_items_number = (int) wp_cache_get( $cache_key, $cache_group, false, $cache_found );
} else {
$basket_items_number = 0;
}
if ( $cache_found ) {
return $basket_items_number;
}
self::get_basket();
if ( self::$basket ) {
if ( isset( self::$basket[ $type ] ) ) {
$posts = self::$basket[ $type ];
$basket_items_number += count( $posts );
}
}
if ( ! $skip_cache ) {
wp_cache_set( $cache_key, $basket_items_number, $cache_group );
}
return $basket_items_number;
}
/**
* Return number of items in translation basket
*
* @param bool $skip_cache
*
* @return int number of items in translation basket
*/
public static function get_basket_items_count( $skip_cache = false ) {
$basket_items_number = 0;
$basket_items_types = self::get_basket_items_types();
foreach ( $basket_items_types as $item_type_name => $item_type ) {
$basket_items_number += self::get_basket_items_type_count( $item_type_name, $skip_cache );
}
return $basket_items_number;
}
/**
* Register notification with number of items in basket and link to basket
*/
public static function update_basket_notifications() {
$positions = self::get_basket_notification_positions();
$basket_link = 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=basket';
foreach ( $positions as $position => $group ) {
ICL_AdminNotifier::remove_message_group( $position );
}
self::get_basket();
$basket_items_count = self::get_basket_items_count( true );
$limit_to_page = array();
if ( defined( 'WPML_ST_FOLDER' ) ) {
$limit_to_page[] = WPML_ST_FOLDER . '/menu/string-translation.php';
}
// if we have something in the basket
if ( self::is_st_page() && $basket_items_count > 0 && ( ! isset( $_GET['clear_basket'] ) || $_GET['clear_basket'] != 1 ) && ( ! isset( $_GET['action'] ) || $_GET['action'] != 'delete' ) ) {
$text = __( 'The items you have selected are now in the translation basket &ndash;', 'wpml-translation-management' );
$text .= ' ' . sprintf( __( '<a href="%s">Send to translation &raquo;</a>', 'wpml-translation-management' ), $basket_link );
// translation management pages
$message_args = array(
'id' => $positions['tm_dashboard_top'],
'text' => $text,
'classes' => 'small',
'type' => 'information small',
'group' => $positions['tm_dashboard_top'],
'admin_notice' => false,
'hide_per_user' => false,
'dismiss_per_user' => false,
'limit_to_page' => $limit_to_page,
'capability' => 'manage_translations',
);
ICL_AdminNotifier::add_message( $message_args );
} else {
ICL_AdminNotifier::remove_message( $positions['tm_dashboard_top'] );
}
$admin_basket_message_id = $positions['admin_notice'];
if ( ( self::$messages || $basket_items_count > 0 ) && self::is_st_page() ) {
$additional_messages = array();
if ( isset( self::$messages ) && is_array( self::$messages ) ) {
foreach ( self::$messages as $message ) {
$additional_messages[] = $message['text'];
}
}
$additional_messages_text = '';
if ( count( $additional_messages ) > 0 ) {
$additional_messages_text = '<ul><li>' . implode( '</li><li>', $additional_messages ) . '</li></ul>';
}
$message_args = array(
'id' => $admin_basket_message_id,
'text' => $additional_messages_text,
'classes' => 'small',
'type' => 'information',
'group' => $admin_basket_message_id,
'admin_notice' => true,
'hide_per_user' => false,
'dismiss_per_user' => false,
'limit_to_page' => $limit_to_page,
'show_once' => true,
);
if ( trim( $additional_messages_text ) != '' ) {
ICL_AdminNotifier::add_message( $message_args );
}
} else {
ICL_AdminNotifier::remove_message( $admin_basket_message_id );
}
}
private static function is_st_page() {
return defined( 'WPML_ST_FOLDER' ) && array_key_exists( 'page', $_GET ) && false !== strpos( $_GET['page'], WPML_ST_FOLDER );
}
/**
* Displays div with number of items in basket and link to basket
* Removes notification if basket is empty
*/
public static function display_basket_items_notification() {
ICL_AdminNotifier::display_messages( 'translation-basket-notification' );
}
public static function is_in_basket( $post_id, $source_language, $target_language, $item_type = 'post' ) {
self::get_basket();
if ( ! self::$basket || ! isset( self::$basket[ $item_type ][ $post_id ] ) ) {
return false;
}
$basket_item = self::$basket[ $item_type ][ $post_id ];
return $basket_item['from_lang'] == $source_language && isset( $basket_item['to_langs'][ $target_language ] ) && $basket_item['to_langs'][ $target_language ];
}
/**
* Checks if post with ID $post_id is in the basket for any language
*
* @param int $post_id
* @param string $element_type
* @param array $check_in_languages
* @param bool $original_language_code
*
* @return bool
*/
public static function anywhere_in_basket( $post_id, $element_type = 'post', $check_in_languages = array(), $original_language_code = false ) {
$basket = self::get_basket();
if ( $post_id && isset( $basket[ $element_type ][ $post_id ] ) ) {
if ( $check_in_languages ) {
if ( ! $original_language_code ) {
$original_language_code = $basket['source_language'];
}
foreach ( $check_in_languages as $language_code => $language_data ) {
if ( $language_code != $original_language_code && isset( $basket[ $element_type ][ $post_id ]['to_langs'][ $language_code ] ) ) {
return true;
}
}
return false;
} else {
return true;
}
}
return false;
}
public static function is_string_in_basket_anywhere( $string_id ) {
return self::anywhere_in_basket( $string_id, 'string' );
}
public static function has_any_string() {
return self::has_any_item_type( 'string' );
}
public static function has_any_item_type( $item_type ) {
self::get_basket();
return isset( self::$basket[ $item_type ] ) && count( self::$basket[ $item_type ] );
}
/**** adding items to basket ****/
/**
* Serves Translation Dashboard form submission and adds posts to basket
*
* @param array $data data submitted from form
*
* @return boolean
*/
public static function add_posts_to_basket( $data ) {
self::get_basket();
global $sitepress, $iclTranslationManagement, $wpml_translation_job_factory;
extract( $data, EXTR_OVERWRITE );
ICL_AdminNotifier::remove_message( 'the_basket_items_notification' );
self::$translation_action = null;
if ( isset( $data['tr_action'] ) ) { // adapt new format
self::$translation_action = $data['tr_action'];
}
if ( ! isset( $data['tr_action'] ) && isset( $data['translate_to'] ) ) { // adapt new format
$data['tr_action'] = $data['translate_to'];
self::$translation_action = $data['tr_action'];
unset( $data['translate_to'] );
}
self::$posts_ids = self::get_elements_ids( $data, 'post' );
self::$translate_from = $data ['translate_from']; // language of the submitted posts transported by hidden field
$data_is_valid = self::validate_data( $data );
if ( ! $data_is_valid ) {
return false;
}
// check tr_action and do what user decided
foreach ( self::$translation_action as $language_code => $status ) {
$language_name = $sitepress->get_display_language_name( $language_code );
// if he decided duplicate or not to translate for this particular language,
// try to remove it from wp_options
$basket_item_type = 'post';
if ( $status == 2 ) {
// iterate posts ids, check if they are in wp_options
// if they are set to translate for this particular language
// end then remove it
foreach ( self::$posts_ids as $id ) {
if ( isset( self::$basket[ $basket_item_type ][ $id ]['to_langs'][ $language_code ] ) ) {
unset( self::$basket[ $basket_item_type ][ $id ]['to_langs'][ $language_code ] );
}
// if user want to duplicate this post, lets do this
if ( $status == 2 ) {
$iclTranslationManagement->make_duplicate( $id, $language_code );
}
}
} elseif ( $status == 1 ) {
foreach ( self::$posts_ids as $id ) {
$send_to_basket = true;
$post = self::get_post( $id );
$post_type = $post->post_type;
$post_title = $post->post_title;
global $wpdb;
$source_language_code = $wpdb->get_var(
$wpdb->prepare(
" SELECT source_language_code
FROM {$wpdb->prefix}icl_translations
WHERE element_type LIKE 'post_%%'
AND element_id = %d",
$post->ID
)
);
if ( $source_language_code != $language_code ) {
$trid = $sitepress->get_element_trid( $id, 'post_' . $post_type );
$job_id = $iclTranslationManagement->get_translation_job_id( $trid, $language_code );
if ( $job_id ) {
/** @var stdClass $job_details */
$job_details = $wpml_translation_job_factory->get_translation_job( $job_id );
if ( ICL_TM_IN_PROGRESS === $job_details->status && ! $job_details->needs_update ) {
self::$messages[] = array(
'type' => 'update',
'text' => sprintf(
__(
'Post "%1$s" will be ignored for %2$s, because translation is already in progress.',
'wpml-translation-management'
),
$post_title,
$language_name
),
);
$send_to_basket = false;
}
}
} else {
self::$messages[] = array(
'type' => 'update',
'text' => sprintf(
__(
'Post "%1$s" will be ignored for %2$s, because it is an original post.',
'wpml-translation-management'
),
$post_title,
$language_name
),
);
$send_to_basket = false;
}
if ( $send_to_basket ) {
self::$basket[ $basket_item_type ][ $id ]['from_lang'] = self::$translate_from;
self::$basket[ $basket_item_type ][ $id ]['to_langs'][ $language_code ] = 1;
// set basket language if not already set
if ( ! isset( self::$basket['source_language'] ) ) {
self::$basket['source_language'] = self::$translate_from;
}
}
}
}
}
self::update_basket();
return true;
}
/**
* Serves WPML > String translation form submission and adds strings to basket
*
* @param array $string_ids identifiers of strings
* @param $source_language
* @param array $target_languages selected target languages
* @return bool
* @todo: [WPML 3.3] move to ST and handle with hooks
*/
public static function add_strings_to_basket( $string_ids, $source_language, $target_languages ) {
global $wpdb, $sitepress;
self::get_basket();
ICL_AdminNotifier::remove_message( 'the_basket_items_notification' );
/*
structure of cart in get_option:
* [posts]
* [element_id]
* [to_langs]
* [language_code] fr | pl | de ... with value 1
* [strings]
* [string_id]
* [to_langs]
* [language_code]
*/
// no post selected ?
if ( empty( $string_ids ) ) {
self::$messages[] = array(
'type' => 'error',
'text' => __( 'Please select at least one document to translate.', 'wpml-translation-management' ),
);
self::update_basket();
return false;
}
// no language selected ?
if ( empty( $target_languages ) ) {
self::$messages[] = array(
'type' => 'error',
'text' => __( 'Please select at least one language to translate into.', 'wpml-translation-management' ),
);
self::update_basket();
return false;
}
if ( self::get_basket() && self::get_source_language() ) {
/*
we do not add items that are not in the source language of the current basket
we cannot yet set its source language though since update_basket would set the basket
to false oso long as we do not have any elements in the basket*/
if ( $source_language != self::get_source_language() ) {
self::$messages[] = array(
'type' => 'update',
'text' => __(
'You cannot add strings in this language to the basket since it already contains posts or strings of another source language!
Either submit the current basket and then add the post or delete the posts of differing language in the current basket',
'wpml-translation-management'
),
);
self::update_basket();
return false;
}
}
foreach ( $target_languages as $target_language => $selected ) {
if ( $target_language == $source_language ) {
continue;
}
$target_language_name = $sitepress->get_display_language_name( $target_language );
foreach ( $string_ids as $id ) {
$send_to_basket = true;
$query = " SELECT {$wpdb->prefix}icl_string_translations.status,
{$wpdb->prefix}icl_strings.value
FROM {$wpdb->prefix}icl_string_translations
INNER JOIN {$wpdb->prefix}icl_strings
ON {$wpdb->prefix}icl_string_translations.string_id = {$wpdb->prefix}icl_strings.id
WHERE {$wpdb->prefix}icl_string_translations.string_id=%d
AND {$wpdb->prefix}icl_string_translations.language=%s";
$string_translation = $wpdb->get_row( $wpdb->prepare( $query, $id, $target_language ) );
if ( ! is_null( $string_translation ) && $string_translation->status == ICL_TM_WAITING_FOR_TRANSLATOR ) {
self::$messages[] = array(
'type' => 'update',
'text' => sprintf(
__(
'String "%1$s" will be ignored for %2$s, because translation is already waiting for translator.',
'wpml-translation-management'
),
$string_translation->value,
$target_language_name
),
);
$send_to_basket = false;
}
if ( $send_to_basket ) {
self::$basket['string'][ $id ]['from_lang'] = $source_language;
self::$basket['string'][ $id ]['to_langs'][ $target_language ] = 1;
// set basket language if not already set
if ( ! isset( self::$basket['source_language'] ) ) {
self::$basket['source_language'] = $source_language;
}
}
}
}
self::update_basket();
return true;
}
/**
* Serves deletion of items from basket, triggered from WPML TM > Translation
* Jobs
*
* @param array $items Array of items ids, in two separate parts: ['post']
* and ['string']
*/
public static function delete_items_from_basket( $items ) {
self::get_basket();
$basket_items_types = self::get_basket_items_types();
foreach ( $basket_items_types as $item_type_name => $item_type ) {
if ( ! empty( $items[ $item_type_name ] ) ) {
foreach ( $items[ $item_type_name ] as $id ) {
self::delete_item_from_basket( $id, $item_type_name, false );
}
}
}
self::update_basket();
}
/**
* Removes one item from basket
*
* @param int $id Item ID
* @param string $type Item type (strings | posts | ...)
* @param bool $update_option do update_option('icl_translation_jobs_cart' ?
*/
public static function delete_item_from_basket( $id, $type = 'post', $update_option = true ) {
self::get_basket();
if ( isset( self::$basket[ $type ][ $id ] ) ) {
unset( self::$basket[ $type ][ $id ] );
if ( count( self::$basket[ $type ] ) == 0 ) {
unset( self::$basket[ $type ] );
}
}
if ( self::get_basket_items_count( true ) == 0 ) {
self::$basket = array();
}
if ( $update_option ) {
self::update_basket( self::$basket );
} else {
self::update_basket_notifications();
}
}
// TODO: [WPML 3.3] implement this in the troubleshooting page
public static function delete_all_items_from_basket() {
self::$basket = false;
delete_option( self::ICL_TRANSLATION_JOBS_BASKET );
self::update_basket();
}
/**
* @param WPML_TP_Batch|null $batch
*/
public static function set_batch_data( $batch ) {
self::get_basket();
self::$basket['batch'] = $batch;
self::update_basket();
}
/**
* @return false|null|WPML_TP_Batch
*/
public static function get_batch_data() {
self::get_basket();
return isset( self::$basket['batch'] ) ? self::$basket['batch'] : false;
}
public static function set_basket_name( $basket_name ) {
self::get_basket();
self::$basket['name'] = $basket_name;
self::update_basket();
}
public static function get_basket_name() {
self::get_basket();
return isset( self::$basket['name'] ) ? self::$basket['name'] : false;
}
public static function set_options( array $options ) {
self::$basket['options'] = $options;
}
/** @return array */
public static function get_options() {
return isset( self::$basket['options'] ) ? self::$basket['options'] : array();
}
public static function get_basket_extra_fields() {
if ( isset( $_REQUEST['extra_fields'] ) ) {
$extra_fields_string = urldecode( $_REQUEST['extra_fields'] );
if ( strlen( $extra_fields_string ) > 0 ) {
$extra_fields_rows = explode( '|', $extra_fields_string );
$result = array();
foreach ( $extra_fields_rows as $row ) {
$row_data = explode( ':', $row );
if ( count( $row_data ) == 2 ) {
$result[ $row_data[0] ] = $row_data[1];
}
}
}
}
if ( isset( $result ) && count( $result ) > 0 ) {
return $result;
}
return false;
}
private static function array_replace_recursive( $array, $array1 ) {
if ( function_exists( 'array_replace_recursive' ) ) {
$array = array_replace_recursive( $array, $array1 );
} else {
// handle the arguments, merge one by one
$args = func_get_args();
$array = $args[0];
if ( ! is_array( $array ) ) {
return $array;
}
for ( $i = 1; $i < count( $args ); $i ++ ) {
if ( is_array( $args[ $i ] ) ) {
$array = self::recurse( $array, $args[ $i ] );
}
}
}
return $array;
}
private static function recurse( $array, $array1 ) {
foreach ( $array1 as $key => $value ) {
// create new key in $array, if it is empty or not an array
if ( ! isset( $array[ $key ] ) || ( isset( $array[ $key ] ) && ! is_array( $array[ $key ] ) ) ) {
$array[ $key ] = array();
}
// overwrite the value in the base array
if ( is_array( $value ) ) {
$value = self::recurse( $array[ $key ], $value );
}
$array[ $key ] = $value;
}
return $array;
}
public static function get_basket_items_types() {
return apply_filters(
'wpml_tm_basket_items_types',
[
'string' => 'core',
'post' => 'core',
'package' => 'custom',
]
);
}
/**
* @param $post_id
*
* @return mixed|null|void|WP_Post
*/
private static function get_post( $post_id ) {
if ( is_string( $post_id ) && strcmp( substr( $post_id, 0, strlen( 'external_' ) ), 'external_' ) === 0 ) {
$item = apply_filters( 'wpml_get_translatable_item', null, $post_id );
} else {
$item = get_post( $post_id );
}
return $item;
}
/**
* @param array $selected_elements
*
* @param bool|string $type
* @return array[]|int[]
*/
public static function get_elements_ids( $selected_elements, $type = false ) {
$element_ids = array();
$legal_item_types = $type ? array( $type ) : array_keys( self::get_basket_items_types() );
foreach ( $legal_item_types as $item_type ) {
if ( ! isset( $selected_elements[ $item_type ] ) ) {
continue;
}
$element_ids[ $item_type ] = isset( $element_ids[ $item_type ] ) ? $element_ids[ $item_type ] : array();
$items = $selected_elements[ $item_type ];
foreach ( $items as $element_id => $action_data ) {
if ( isset( $action_data['checked'] ) && $action_data['checked'] ) {
$element_ids[ $item_type ][] = $element_id;
}
}
}
return $type && isset( $element_ids[ $type ] ) ? $element_ids[ $type ] : $element_ids;
}
public static function get_source_language() {
self::get_basket();
return isset( self::$basket['source_language'] ) ? self::$basket['source_language'] : false;
}
private static function sync_target_languages() {
self::get_basket();
if ( ! isset( self::$basket['target_languages'] ) ) {
self::$basket['target_languages'] = array();
}
$basket_items_types = self::get_basket_items_types();
foreach ( $basket_items_types as $item_type_name => $item_type ) {
if ( isset( self::$basket[ $item_type_name ] ) ) {
$posts_in_basket = self::$basket[ $item_type_name ];
foreach ( (array) $posts_in_basket as $post_in_basket ) {
foreach ( (array) $post_in_basket['to_langs'] as $key => $target_language ) {
if ( $target_language && ! in_array( $key, self::$basket['target_languages'] ) ) {
self::$basket['target_languages'] [] = $key;
}
}
}
}
}
}
/**
* @return bool|array
*/
public static function get_target_languages() {
self::get_basket();
self::sync_target_languages();
return isset( self::$basket['target_languages'] ) ? self::$basket['target_languages'] : false;
}
/**
* Sets target languages for remote service
*
* @param $remote_target_languages
*/
public static function set_remote_target_languages( $remote_target_languages ) {
self::get_basket();
self::$basket['remote_target_languages'] = $remote_target_languages;
self::update_basket();
}
/**
* Get target languages for remote service
*
* @return array | false
*/
public static function get_remote_target_languages() {
self::get_basket();
if ( isset( self::$basket['remote_target_languages'] ) ) {
return self::$basket['remote_target_languages'];
} else {
return self::get_target_languages();
}
}
/**
* @return array
*/
public static function get_basket_notification_positions() {
return array(
'admin_notice' => 'basket_status_update',
'tm_dashboard_top' => 'translation-basket-notification',
'st_dashboard_top' => 'string-translation-top',
'st_dashboard_bottom' => 'string-translation-under',
);
}
public static function get_basket_extra_fields_section() {
$extra_fields = TranslationProxy::get_extra_fields_local();
$html = '';
if ( $extra_fields ) {
$html .= '<h3>3. ' . __( 'Select additional options', 'wpml-translation-management' ) . ' <a href="#" id="basket_extra_fields_refresh">(' . __( 'Refresh', 'wpml-translation-management' ) . ')</a></h3>';
$html .= '<div id="basket_extra_fields_list">';
$html .= self::get_basket_extra_fields_inputs( $extra_fields, false );
$html .= '</div>';
}
return $html;
}
public static function get_basket_extra_fields_inputs( array $extra_fields = array(), $force_refresh = false ) {
if ( ! $extra_fields ) {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
$force_refresh = true;
}
$extra_fields = self::get_basket_extra_fields_array( $force_refresh );
}
return self::extra_fields_build_inputs( $extra_fields );
}
public static function get_basket_extra_fields_array( $force_refresh = false ) {
if ( $force_refresh ) {
$networking = wpml_tm_load_tp_networking();
$project = TranslationProxy::get_current_project();
$extra_fields = $networking->get_extra_fields_remote( $project );
TranslationProxy::save_extra_fields( $extra_fields );
} else {
$extra_fields = TranslationProxy::get_extra_fields_local();
}
return TranslationProxy::maybe_convert_extra_fields( $extra_fields );
}
public static function extra_fields_build_inputs( array $extra_fields ) {
if ( ! $extra_fields ) {
return '';
}
$rows = array();
/** @var WPML_TP_Extra_Field $field */
$field_diplay = new WPML_TP_Extra_Field_Display();
foreach ( $extra_fields as $field ) {
$rows[] = $field_diplay->render( $field );
}
$rows = array_filter( $rows );
$html = '';
if ( $rows ) {
$html = '<table class="form-table">';
$html .= '<tbody>';
$html .= implode( PHP_EOL, $rows );
$html .= '</tbody>';
$html .= '</table>';
}
return $html;
}
/**
* @param $data
*
* @return bool
*/
private static function validate_data( $data ) {
$data_is_valid = true;
if ( self::get_basket() && self::get_source_language() ) {
/*
we do not add items that are not in the source language of the current basket
we cannot yet set its source language though since update_basket would set the basket
to false as long as we do not have any elements in the basket*/
if ( self::$translate_from != self::get_source_language() ) {
self::$messages[] = array(
'type' => 'update',
'text' => __(
'You cannot add posts in this language to the basket since it already contains posts or strings of another source language!
Either submit the current basket and then add the post or delete the posts of differing language in the current basket',
'wpml-translation-management'
),
);
self::update_basket();
$data_is_valid = false;
}
}
// no language selected ?
if ( ! isset( self::$translation_action ) || empty( self::$translation_action ) ) {
self::$messages[] = array(
'type' => 'error',
'text' => __( 'Please select at least one language to translate into.', 'wpml-translation-management' ),
);
self::$dashboard_select = $data; // pre fill dashboard
$data_is_valid = false;
}
if ( $data_is_valid ) {
$data_is_valid = false;
$basket_items_types = self::get_basket_items_types();
// nothing selected ?
foreach ( $basket_items_types as $basket_items_type => $basket_type ) {
if ( isset( $data[ $basket_items_type ] ) && $data[ $basket_items_type ] ) {
$data_is_valid = true;
break;
}
}
}
if ( ! $data_is_valid ) {
self::$messages[] = array(
'type' => 'error',
'text' => __( 'Please select at least one document to translate.', 'wpml-translation-management' ),
);
self::$dashboard_select = $data; // pre-populate dashboard
$data_is_valid = false;
return $data_is_valid;
}
return $data_is_valid;
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
use \WPML\Collect\Support\Traits\Macroable;
use function \WPML\FP\curryN;
use \WPML\LIB\WP\Cache;
use \WPML\FP\Logic;
/**
* Class TranslationProxy_Batch
*
* @method static callable|int getBatchId( ...$name ) :: string → int
*/
class TranslationProxy_Batch {
use Macroable;
public static function update_translation_batch(
$batch_name = false,
$tp_id = false
) {
$batch_name = $batch_name
? $batch_name
: ( ( (bool) $tp_id === false || $tp_id === 'local' )
? self::get_generic_batch_name() : TranslationProxy_Basket::get_basket_name() );
if ( ! $batch_name ) {
return null;
}
$getBatchId = function( $batch_name, $tp_id ) {
$batch_id = self::getBatchId( $batch_name );
return $batch_id ? $batch_id : self::createBatchRecord( $batch_name, $tp_id );
};
$cache = Cache::memorizeWithCheck( 'update_translation_batch', Logic::isNotNull(), $getBatchId );
return $cache( $batch_name, $tp_id );
}
/**
* returns the name of a generic batch
* name is built based on the current's date
*
* @return string
*/
public static function get_generic_batch_name() {
$batch_name = 'Manual Translations from ' . date( 'F \t\h\e jS\, Y' );
return $batch_name;
}
/**
* returns the id of a generic batch
*
* @return int
*/
private static function create_generic_batch() {
$batch_name = self::get_generic_batch_name();
$batch_id = self::update_translation_batch( $batch_name );
return $batch_id;
}
public static function maybe_assign_generic_batch( $data ) {
global $wpdb;
$batch_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT batch_id
FROM {$wpdb->prefix}icl_translation_status
WHERE translation_id=%d",
$data['translation_id']
)
);
// if the batch id is smaller than 1 we assign the translation to the generic manual translations batch for today if the translation_service is local
if ( ( $batch_id < 1 ) && isset( $data ['translation_service'] ) && $data ['translation_service'] == 'local' ) {
// first we retrieve the batch id for today's generic translation batch
$batch_id = self::create_generic_batch();
// then we update the entry in the icl_translation_status table accordingly
$data_where = array( 'rid' => $data['rid'] );
$wpdb->update(
$wpdb->prefix . 'icl_translation_status',
array( 'batch_id' => $batch_id ),
$data_where
);
}
}
/**
* @param $batch_name
* @param $tp_id
*
* @return mixed
*/
private static function createBatchRecord( $batch_name, $tp_id ) {
global $wpdb;
$data = [
'batch_name' => $batch_name,
'last_update' => date( 'Y-m-d H:i:s' ),
];
if ( $tp_id ) {
$data['tp_id'] = $tp_id === 'local' ? 0 : $tp_id;
}
$wpdb->insert( $wpdb->prefix . 'icl_translation_batches', $data );
return $wpdb->insert_id;
}
}
/**
* @param $batch_name
*
* @return mixed
*/
TranslationProxy_Batch::macro(
'getBatchId',
curryN(
1,
function( $batch_name ) {
global $wpdb;
$batch_id_sql = "SELECT id FROM {$wpdb->prefix}icl_translation_batches WHERE batch_name=%s";
$batch_id_prepared = $wpdb->prepare( $batch_id_sql, $batch_name );
return $wpdb->get_var( $batch_id_prepared );
}
)
);

View File

@@ -0,0 +1,73 @@
<?php
class TranslationProxy_Popup {
public static function display() {
include_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-popup.php';
exit;
}
public static function get_link( $link, $args = array(), $just_url = false ) {
$defaults = array(
'title' => null,
'class' => '',
'id' => '',
'ar' => 0, // auto_resize
'unload_cb' => false, // onunload callback
);
$args = array_merge($defaults, $args);
/**
* @var title string
*/
$title = $args['title'];
/**
* @var $class string
*/
$class = $args['class'];
/**
* @var $id int
*/
$id = $args['id'];
/**
* @var $ar int
*/
$ar = $args['ar'];
/**
* @var $unload_cb bool
*/
$unload_cb = $args['unload_cb'];
if ( !empty( $ar ) ) {
$auto_resize = '&amp;auto_resize=1';
} else {
$auto_resize = '';
}
$unload_cb = isset( $unload_cb ) ? '&amp;unload_cb=' . $unload_cb : '';
$url_glue = false !== strpos( $link, '?' ) ? '&' : '?';
$link .= $url_glue . 'compact=1';
$nonce_snippet = '&amp;_icl_nonce=' . wp_create_nonce( 'reminder_popup_nonce' );
$action_and_nonce = 'admin.php?page=' . ICL_PLUGIN_FOLDER
. "/menu/languages.php&amp;icl_action=reminder_popup{$nonce_snippet}{$auto_resize}{$unload_cb}"
. "&amp;target=" . urlencode( $link );
if ( ! empty( $id ) ) {
$id = ' id="' . $id . '"';
}
if ( ! $just_url ) {
return '<a class="icl_thickbox ' . $class . '" title="' . $title . '" href="' . $action_and_nonce . '"' . $id . '>';
} else {
if ( ! $just_url ) {
return '<a class="icl_thickbox ' . $class . '" href="' . $action_and_nonce . '"' . $id . '>';
} else {
return $action_and_nonce;
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
define( 'ICL_LANGUAGE_NOT_SUPPORTED', 3 );
global $wpdb, $sitepress;
$target = filter_input( INPUT_GET, 'target', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$auto_resize = filter_input( INPUT_GET, 'auto_resize', FILTER_VALIDATE_BOOLEAN | FILTER_NULL_ON_FAILURE );
$unload_cb = filter_input( INPUT_GET, 'unload_cb', FILTER_SANITIZE_FULL_SPECIAL_CHARS | FILTER_NULL_ON_FAILURE );
// Adding a translator
if ( preg_match( '|^@select-translators;([^;]+);([^;]+)@|', $target, $matches ) ) {
$source_language = $matches[1];
$target_language = $matches[2];
$project = TranslationProxy::get_current_project();
try {
$lp_setting_index = 'language_pairs';
$language_pairs = $sitepress->get_setting( $lp_setting_index, array() );
if ( ! isset( $language_pairs[ $source_language ][ $target_language ] ) || $language_pairs[ $source_language ][ $target_language ] == 0 ) {
$language_pairs[ $source_language ][ $target_language ] = 1;
TranslationProxy_Translator::update_language_pairs( $project, $language_pairs );
$sitepress->set_setting( $lp_setting_index, $language_pairs, true );
}
$target = $project->select_translator_iframe_url( $source_language, $target_language );
} catch ( Exception $e ) {
if ( $e->getCode() == ICL_LANGUAGE_NOT_SUPPORTED ) {
printf( __( '<p>Requested languages are not supported by the translation service (%s). Please <a%s>contact us</a> for support. </p>', 'wpml-translation-management' ), $e->getMessage(), ' target="_blank" href="http://wpml.org/?page_id=5255"' );
} else {
printf( __( '<p>Could not add the requested languages. Please <a%s>contact us</a> for support. </p><p>Show <a%s>debug information</a>.</p>', 'wpml-translation-management' ), ' target="_blank" href="http://wpml.org/?page_id=5255"',
' a href="admin.php?page=' .
ICL_PLUGIN_FOLDER .
'/menu/troubleshooting.php&icl_action=icl-connection-test' .
'#icl-connection-test"' );
}
exit;
}
}
$target .= ( strpos( $target, '?' ) === false ) ? '?' : '&';
$target .= "lc=" . $sitepress->get_admin_language();
?>
<iframe src="<?php echo $target; ?>" style="width:100%; height:92%" onload=" var TB_window = jQuery('#TB_window');
<?php if ( $auto_resize ): ?>
TB_window.css('width','90%').css('margin-left', '-45%');
<?php endif; ?>
<?php if ( $unload_cb ){
$unload_cb = esc_js($unload_cb);
?>
TB_window.unbind('unload').bind('tb_unload', function(){<?php echo $unload_cb; ?>});
<?php } ?>
">

View File

@@ -0,0 +1,510 @@
<?php
/**
* @package wpml-core
* @subpackage wpml-core
*/
require_once dirname( __FILE__ ) . '/translationproxy-api.class.php';
require_once dirname( __FILE__ ) . '/translationproxy-service.class.php';
require_once dirname( __FILE__ ) . '/translationproxy-batch.class.php';
/**
* Class TranslationProxy_Project
*/
class TranslationProxy_Project {
public $id;
/**
* @var string
*
* `access_key` used when sending **any request** to TP
*/
public $access_key;
/**
* @var int
*
* `ts_id` (aka `website_id`) is used **exclusively** when sending request directly to ICL
*/
public $ts_id;
/**
* @var string
*
* `ts_access_key` is used **exclusively** when sending request directly to ICL
*/
public $ts_access_key;
/**
* @var object
*/
public $service;
/** @var WPML_TP_Client $tp_client */
public $tp_client;
public $errors = array();
/**
* @param TranslationProxy_Service $service
* @param string $delivery
* @param WPML_TP_Client $tp_client
*/
public function __construct( $service, $delivery, WPML_TP_Client $tp_client ) {
$this->service = $service;
$this->tp_client = $tp_client;
$icl_translation_projects = TranslationProxy::get_translation_projects();
$project_index = self::generate_service_index( $service );
if ( $project_index && $icl_translation_projects && isset( $icl_translation_projects [ $project_index ] ) ) {
$project = $icl_translation_projects[ $project_index ];
$this->id = $project['id'];
$this->access_key = $project['access_key'];
$this->ts_id = $project['ts_id'];
$this->ts_access_key = $project['ts_access_key'];
$this->service->delivery_method = $delivery;
}
}
/**
* @return TranslationProxy_Service
*/
public function service() {
return $this->service;
}
/**
* Returns the index by which a translation service can be found in the array returned by
* \TranslationProxy::get_translation_projects
*
* @param $service object
*
* @return bool|string
*/
public static function generate_service_index( $service ) {
$index = false;
if ( $service ) {
$service->custom_fields_data = isset( $service->custom_fields_data ) ? $service->custom_fields_data : array();
if ( isset( $service->id ) ) {
$index = md5( $service->id . serialize( $service->custom_fields_data ) );
}
}
return $index;
}
/**
* Convert WPML language code to service language
*
* @param $language string
*
* @return bool|string
*/
private function service_language( $language ) {
return TranslationProxy_Service::get_language( $this->service, $language );
}
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Get information about the project (Translation Service)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public function custom_text( $location, $locale = 'en' ) {
$response = '';
if ( ! $this->ts_id || ! $this->ts_access_key ) {
return '';
}
// Sending Translation Service (ts_) id and access_key, as we are talking directly to the Translation Service
// Todo: use project->id and project->access_key once this call is moved to TP
$params = array(
'project_id' => $this->ts_id,
'accesskey' => $this->ts_access_key,
'location' => $location,
'lc' => $locale,
);
if ( $this->service->custom_text_url ) {
try {
$response = TranslationProxy_Api::service_request(
$this->service->custom_text_url,
$params,
'GET',
true,
true,
true
);
} catch ( Exception $e ) {
throw new RuntimeException(
'error getting custom text from Translation Service: ' . serialize( $params ) . ' url: ' . $this->service->custom_text_url,
0,
$e
);
}
}
return $response;
}
function current_service_name() {
return TranslationProxy::get_current_service_name();
}
function current_service() {
return TranslationProxy::get_current_service();
}
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* IFrames to display project info (Translation Service)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
public function select_translator_iframe_url( $source_language, $target_language ) {
// Sending Translation Service (ts_) id and access_key, as we are talking directly to the Translation Service
$params['project_id'] = $this->ts_id;
$params['accesskey'] = $this->ts_access_key;
$params['source_language'] = $this->service_language( $source_language );
$params['target_language'] = $this->service_language( $target_language );
$params['compact'] = 1;
return $this->_create_iframe_url( $this->service->select_translator_iframe_url, $params );
}
public function translator_contact_iframe_url( $translator_id ) {
// Sending Translation Service (ts_) id and access_key, as we are talking directly to the Translation Service
$params['project_id'] = $this->ts_id;
$params['accesskey'] = $this->ts_access_key;
$params['translator_id'] = $translator_id;
$params['compact'] = 1;
if ( $this->service->translator_contact_iframe_url ) {
return $this->_create_iframe_url( $this->service->translator_contact_iframe_url, $params );
}
return false;
}
private function _create_iframe_url( $url, $params ) {
if ( $params ) {
$url = TranslationProxy_Api::add_parameters_to_url( $url, $params );
$url .= '?' . http_build_query( $params );
}
return $url;
}
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Jobs handling (Translation Proxy)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @throws WPML_TP_Batch_Exception
*
* @param bool $source_language
* @param bool $target_languages
*
* @internal param bool $name
* @return false|WPML_TP_Batch
*/
function get_batch_job(
$source_language = false,
$target_languages = false
) {
$batch_data = TranslationProxy_Basket::get_batch_data();
if ( ! $batch_data ) {
if ( ! $source_language ) {
$source_language = TranslationProxy_Basket::get_source_language();
}
if ( ! $target_languages ) {
$target_languages = TranslationProxy_Basket::get_remote_target_languages();
}
if ( ! $source_language || ! $target_languages ) {
return false;
}
$batch_data = $this->create_batch_job( $source_language, $target_languages );
if ( $batch_data ) {
TranslationProxy_Basket::set_batch_data( $batch_data );
}
}
return $batch_data;
}
/**
* @throws WPML_TP_Batch_Exception
*
* @return false|int
*/
function get_batch_job_id() {
$ret = false;
$batch_data = $this->get_batch_job();
if ( $batch_data ) {
$ret = $batch_data->get_id();
}
return $ret;
}
/**
* @throws WPML_TP_Batch_Exception
*
* @param bool $source_language
* @param $target_languages
*
* @internal param bool $name
* @return false|WPML_TP_Batch
*/
public function create_batch_job( $source_language, $target_languages ) {
$batch_name = TranslationProxy_Basket::get_basket_name();
$batch_options = TranslationProxy_Basket::get_options();
$extra_fields = TranslationProxy_Basket::get_basket_extra_fields();
$batch_data = array(
'source_language' => $source_language,
'target_languages' => $target_languages,
'name' => $batch_name,
);
if ( ! $batch_data['source_language'] ) {
$batch_data['source_language'] = TranslationProxy_Basket::get_source_language();
}
if ( ! $batch_data['target_languages'] ) {
$batch_data['target_languages'] = TranslationProxy_Basket::get_remote_target_languages();
}
if ( ! $batch_data['source_language'] || ! $batch_data['target_languages'] ) {
return false;
}
if ( ! $batch_data['name'] ) {
$batch_data['name'] = sprintf(
__(
'%s: WPML Translation Jobs',
'wpml-translation-management'
),
get_option( 'blogname' )
);
}
TranslationProxy_Basket::set_basket_name( $batch_data['name'] );
if ( isset( $batch_options['deadline_date'] ) ) {
$batch_data['deadline'] = strtotime( $batch_options['deadline_date'] );
}
return $this->tp_client->batches()->create( $batch_data, $extra_fields );
}
/**
*
* Add Files Batch Job
*
* @throws WPML_TP_Batch_Exception
*
* @param string $file
* @param string $title
* @param string $cms_id
* @param string $url
* @param string $source_language
* @param string $target_language
* @param int $word_count
* @param int $translator_id
* @param string $note
*
* @return bool
*/
public function send_to_translation_batch_mode(
$file,
$title,
$cms_id,
$url,
$source_language,
$target_language,
$word_count,
$translator_id = 0,
$note = '',
$uuid = null
) {
$batch_id = $this->get_batch_job_id();
if ( ! $batch_id ) {
return false;
}
$job_data = array(
'file' => $file,
'word_count' => $word_count,
'title' => $title,
'cms_id' => $cms_id,
'udid' => $uuid,
'url' => $url,
'translator_id' => $translator_id,
'note' => $note,
'source_language' => $source_language,
'target_language' => $target_language,
);
$tp_job = $this->tp_client->batches()->add_job( $batch_id, $job_data );
if ( $tp_job ) {
return $tp_job->get_id();
}
}
/**
* @param bool|int $tp_batch_id
*
* @link http://git.icanlocalize.com/onthego/translation_proxy/wikis/commit_batch_job
*
* @return array|bool|mixed|null|stdClass|string
*/
function commit_batch_job( $tp_batch_id = false ) {
$tp_batch_id = $tp_batch_id ? $tp_batch_id : $this->get_batch_job_id();
if ( ! $tp_batch_id ) {
return true;
}
$params = array(
'api_version' => TranslationProxy_Api::API_VERSION,
'project_id' => $this->id,
'accesskey' => $this->access_key,
'batch_id' => $tp_batch_id,
);
$response = TranslationProxy_Api::proxy_request( '/batches/{batch_id}/commit.json', $params, 'PUT', false );
$basket_name = TranslationProxy_Basket::get_basket_name();
if ( $basket_name ) {
global $wpdb;
$batch_id_sql = "SELECT id FROM {$wpdb->prefix}icl_translation_batches WHERE batch_name=%s";
$batch_id_prepared = $wpdb->prepare(
$batch_id_sql,
array( $basket_name )
);
$batch_id = $wpdb->get_var( $batch_id_prepared );
$batch_data = array(
'batch_name' => $basket_name,
'tp_id' => $tp_batch_id,
'last_update' => date( 'Y-m-d H:i:s' ),
);
if ( isset( $response ) && $response ) {
$batch_data['ts_url'] = serialize( $response );
}
if ( ! $batch_id ) {
$wpdb->insert(
$wpdb->prefix . 'icl_translation_batches',
$batch_data
);
} else {
$wpdb->update(
$wpdb->prefix . 'icl_translation_batches',
$batch_data,
array( 'id' => $batch_id )
);
}
}
return isset( $response ) ? $response : false;
}
/**
*
* @return object[]
*/
public function jobs() {
return $this->get_jobs( 'any' );
}
/**
* @return object[]
*/
public function finished_jobs() {
return $this->get_jobs( 'translation_ready' );
}
public function set_delivery_method( $method ) {
$params = array(
'project_id' => $this->id,
'accesskey' => $this->access_key,
'project' => array( 'delivery_method' => $method ),
);
TranslationProxy_Api::proxy_request( '/projects.json', $params, 'put' );
return true;
}
public function fetch_translation( $job_id ) {
$params = array(
'project_id' => $this->id,
'accesskey' => $this->access_key,
'job_id' => $job_id,
);
return TranslationProxy_Api::proxy_download(
'/jobs/{job_id}/xliff.json',
$params
);
}
public function update_job( $job_id, $url = null, $state = 'delivered' ) {
$params = array(
'job_id' => $job_id,
'project_id' => $this->id,
'accesskey' => $this->access_key,
'job' => array(
'state' => $state,
),
);
if ( $url ) {
$params['job']['url'] = $url;
}
TranslationProxy_Api::proxy_request(
'/jobs/{job_id}.json',
$params,
'PUT'
);
}
/**
* @param string $state
*
* @return mixed
*/
private function get_jobs( $state = 'any' ) {
$batch = TranslationProxy_Basket::get_batch_data();
$params = array(
'project_id' => $this->id,
'accesskey' => $this->access_key,
'state' => $state,
);
if ( $batch ) {
$params['batch_id'] = $batch ? $batch->get_id() : false;
return TranslationProxy_Api::proxy_request(
'/batches/{batch_id}/jobs.json',
$params
);
} else {
// FIXME: remove this once TP will accept the TP Project ID: https://icanlocalize.basecamphq.com/projects/11113143-translation-proxy/todo_items/182251206/comments
$params['project_id'] = $this->id;
}
return TranslationProxy_Api::proxy_request( '/jobs.json', $params );
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* @package wpml-core
* @subpackage wpml-core
*/
require_once dirname( __FILE__ ) . '/translationproxy-api.class.php';
class TranslationProxy_Service {
public $id;
public $name;
public $description;
public $default_service;
public $has_translator_selection = true; // Todo: read this from service properties
public $delivery_method;
public $project_details_url;
public $custom_text_url;
public $has_language_pairs;
public $languages_map;
public $url;
public $logo_url;
public $create_project_url;
public $add_language_pair_url;
public $new_job_url;
public $custom_fields;
public $custom_fields_data;
public $select_translator_iframe_url;
public $translator_contact_iframe_url;
public $quote_iframe_url;
public $batch_name_max_length;
public static function is_authenticated( $service ) {
// for services that do not require authentication return true by default
if ( ! TranslationProxy::service_requires_authentication( $service ) ) {
return true;
}
return isset( $service->custom_fields_data ) && $service->custom_fields_data ? true : false;
}
public static function list_services() {
return TranslationProxy_Api::proxy_request( '/services.json' );
}
public static function get_service( $service_id ) {
$service = TranslationProxy_Api::proxy_request( "/services/$service_id.json" );
$service->languages_map = self::languages_map( $service );
return $service;
}
public static function get_service_by_suid( $suid ) {
$service = TranslationProxy_Api::proxy_request( "/services/$suid.json" );
$service->languages_map = self::languages_map( $service );
return $service;
}
public static function languages_map( $service ) {
$languages_map = array();
$languages = TranslationProxy_Api::proxy_request( "/services/{$service->id}/language_identifiers.json" );
foreach ( $languages as $language ) {
$languages_map[ $language->iso_code ] = $language->value;
}
return $languages_map;
}
public static function get_language( $service, $language ) {
if ( ! empty( $service->languages_map ) and array_key_exists( $language, $service->languages_map ) ) {
$language = $service->languages_map[ $language ];
}
return $language;
}
/**
* Returns a WPML readable string that allows to tell translation service and translator id
* (typically used for translators dropdowns)
*
* @param int|bool $translation_service_id
* @param int|bool $translator_id
*
* @return string
*/
public static function get_wpml_translator_id( $translation_service_id = false, $translator_id = false ) {
if ( $translation_service_id === false ) {
$translation_service_id = TranslationProxy::get_current_service_id();
}
$result = 'ts-' . $translation_service_id;
if ( $translator_id !== false ) {
$result .= '-' . $translator_id;
}
return $result;
}
/**
* @param string $translator_id
*
* @return array Returns a two elements array, respectively containing translation_service and translator_id
*/
public static function get_translator_data_from_wpml( $translator_id ) {
$result = array();
if ( is_numeric( $translator_id ) ) {
$result['translation_service'] = 'local';
$result['translator_id'] = $translator_id;
} else {
$translator_data = explode( '-', $translator_id );
$result = array();
$result['translation_service'] = $translator_data[1];
$result['translator_id'] = isset( $translator_data[2] ) ? $translator_data[2] : 0;
}
return $result;
}
}

View File

@@ -0,0 +1,266 @@
<?php
class TranslationProxy_Translator {
/**
* Get information about translators from current project. Works only for ICL as a Translation Service
*
* @param bool $force
*
* @return array|bool
*/
public static function get_icl_translator_status( $force = false ) {
/** @var SitePress $sitepress */
/** @var WPML_Pro_Translation $ICL_Pro_Translation */
global $sitepress, $ICL_Pro_Translation;
if ( ! $ICL_Pro_Translation ) {
$job_factory = wpml_tm_load_job_factory();
$ICL_Pro_Translation = new WPML_Pro_Translation( $job_factory );
}
if ( ! TranslationProxy::translator_selection_available() ) {
return array();
}
$project = TranslationProxy::get_current_project();
if ( ! $project ) {
return array();
}
$cache_key = md5( serialize( $project ) );
$cache_group = 'get_icl_translator_status';
$found = false;
$result = wp_cache_get( $cache_key, $cache_group, false, $found );
if ( $found ) {
return $result;
}
$translator_status = array();
$website_details = self::get_website_details(
new TranslationProxy_Project( TranslationProxy::get_current_service(), 'xmlrpc', TranslationProxy::get_tp_client() ),
$force
);
if ( false === (bool) $website_details ) {
return array();
}
$language_pairs = array();
if ( isset( $website_details['translation_languages']['translation_language'] ) ) {
$translation_languages = $website_details['translation_languages']['translation_language'];
if ( ! isset( $translation_languages[0] ) ) {
$buf = $translation_languages;
$translation_languages = array( 0 => $buf );
}
foreach ( $translation_languages as $lang ) {
$translators = $_tr = array();
$max_rate = false;
if ( isset( $lang['translators'], $lang['translators']['translator'] ) && ! empty( $lang['translators'] ) ) {
if ( ! isset( $lang['translators']['translator'][0] ) ) {
$_tr[0] = $lang['translators']['translator'];
} else {
$_tr = $lang['translators']['translator'];
}
foreach ( $_tr as $t ) {
if ( false === $max_rate || $t['attr']['amount'] > $max_rate ) {
$max_rate = $t['attr']['amount'];
}
$translators[] = array(
'id' => $t['attr']['id'],
'nickname' => $t['attr']['nickname'],
'contract_id' => $t['attr']['contract_id'],
);
}
}
$language_pairs[] = array(
'from' => $sitepress->get_language_code( $ICL_Pro_Translation->server_languages_map( $lang['attr']['from_language_name'], true ) ),
'to' => $sitepress->get_language_code( $ICL_Pro_Translation->server_languages_map( $lang['attr']['to_language_name'], true ) ),
'have_translators' => $lang['attr']['have_translators'],
'available_translators' => $lang['attr']['available_translators'],
'applications' => $lang['attr']['applications'],
'contract_id' => $lang['attr']['contract_id'],
'id' => $lang['attr']['id'],
'translators' => $translators,
'max_rate' => $max_rate,
);
}
}
$translator_status['icl_lang_status'] = $language_pairs;
$translator_status['icl_support_ticket_id'] = null;
wp_cache_set( $cache_key, $translator_status, $cache_group );
return $translator_status;
}
private static function get_popup_link( $matches ) {
global $sitepress;
return TranslationProxy_Popup::get_link( $matches[2] );
}
/**
*
* Get information about language pairs (including translators). Works only for ICL as a Translation Service
*
* @return array
*/
public static function get_language_pairs() {
global $sitepress;
$icl_lang_status = $sitepress->get_setting( 'icl_lang_status', array() );
if ( ! empty( $icl_lang_status ) ) {
$missing_translators = false;
foreach ( $icl_lang_status as $lang ) {
if ( empty( $lang['translators'] ) ) {
$missing_translators = true;
break;
}
}
if ( ! $missing_translators ) {
$icl_lang_sub_status = $icl_lang_status;
}
}
if ( ! isset( $icl_lang_sub_status ) ) {
$translator_status = self::get_icl_translator_status();
$icl_lang_sub_status = isset( $translator_status['icl_lang_status'] )
? $translator_status['icl_lang_status'] : array();
}
foreach ( $icl_lang_sub_status as $key => $status ) {
if ( ! isset( $status['from'] ) ) {
unset( $icl_lang_sub_status[ $key ] );
}
}
array_filter( $icl_lang_sub_status );
return $icl_lang_sub_status;
}
/**
* Sends request to ICL to get website details (including language pairs)
*
* @param TranslationProxy_Project $project
* @param bool $force
*
* @return array
*/
private static function get_website_details( $project, $force = false ) {
require_once ICL_PLUGIN_PATH . '/inc/utilities/xml2array.php';
require_once ICL_PLUGIN_PATH . '/lib/icl_api.php';
$site_id = $project->ts_id;
$access_key = $project->ts_access_key;
$default = array();
if ( ! $site_id ) {
return $default;
}
$icl_query = new ICanLocalizeQuery( $site_id, $access_key );
return $icl_query->get_website_details( $force );
}
/**
* @param $translator_id
*
* @return string|false
*/
public static function get_translator_name( $translator_id ) {
if ( TranslationProxy::translator_selection_available() ) {
$lang_status = self::get_language_pairs();
if ( $lang_status ) {
foreach ( $lang_status as $lp ) {
$lp_trans = ! empty( $lp['translators'] ) ? $lp['translators'] : array();
foreach ( $lp_trans as $tr ) {
$translators[ $tr['id'] ] = $tr['nickname'];
}
}
}
}
return isset( $translators[ $translator_id ] ) ? $translators[ $translator_id ] : false;
}
/**
* Synchronizes language pairs with ICL
*
* @global object $sitepress
*
* @param $project
* @param $language_pairs
*/
public static function update_language_pairs( $project, $language_pairs ) {
/** @var WPML_Pro_Translation $ICL_Pro_Translation */
global $sitepress, $ICL_Pro_Translation;
$params = array(
'site_id' => $project->ts_id,
'accesskey' => $project->ts_access_key,
'create_account' => 0,
);
$lang_server = array();
foreach ( $sitepress->get_active_languages() as $lang ) {
$lang_server[ $lang['code'] ] = $ICL_Pro_Translation->server_languages_map( $lang['english_name'] );
}
// update account - add language pair
$incr = 0;
foreach ( $language_pairs as $k => $v ) {
if ( ! array_key_exists( $k, $lang_server ) ) {
unset( $language_pairs[ $k ] );
continue;
}
foreach ( $v as $k2 => $v2 ) {
if ( ! array_key_exists( $k2, $lang_server ) ) {
unset( $language_pairs[ $k ][ $k2 ] );
if ( (bool) $language_pairs[ $k ] === false ) {
unset( $language_pairs[ $k ] );
}
continue;
}
$incr ++;
$params[ 'from_language' . $incr ] = $lang_server[ $k ];
$params[ 'to_language' . $incr ] = $lang_server[ $k2 ];
}
}
require_once ICL_PLUGIN_PATH . '/inc/utilities/xml2array.php';
require_once ICL_PLUGIN_PATH . '/lib/icl_api.php';
$icl_query = new ICanLocalizeQuery();
$icl_query->updateAccount( $params );
}
public static function flush_website_details_cache() {
delete_transient( WEBSITE_DETAILS_TRANSIENT_KEY );
}
public static function flush_website_details_cache_action() {
$nonce = array_key_exists( 'nonce', $_POST ) ? $_POST['nonce'] : null;
$action = array_key_exists( 'action', $_POST ) ? $_POST['action'] : null;
$nonce_is_valid = wp_verify_nonce( $nonce, $action );
if ( $nonce_is_valid ) {
self::flush_website_details_cache();
$query_args = array(
'page' => urlencode( WPML_TM_FOLDER . '/menu/main.php' ),
'sm' => urlencode( 'translators' ),
);
$link_url = add_query_arg( $query_args, get_admin_url( null, 'admin.php' ) );
wp_send_json_success( array( 'redirectTo' => $link_url ) );
} else {
wp_send_json_error( 'Nonce is not valid.' );
}
}
}

View File

@@ -0,0 +1,662 @@
<?php
/**
* @package wpml-core
* @subpackage wpml-core
*/
use WPML\TM\TranslationProxy\Services\AuthorizationFactory;
require_once WPML_TM_PATH . '/inc/translation-proxy/functions.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-basket.class.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-api.class.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-project.class.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-service.class.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-popup.class.php';
require_once WPML_TM_PATH . '/inc/translation-proxy/translationproxy-translator.class.php';
define( 'CUSTOM_TEXT_MAX_LENGTH', 1000 );
class TranslationProxy {
private static $tp_client;
/**
* @param bool $reload
*
* @return WPML_TP_Service[]
*/
public static function services( $reload = true ) {
return self::get_tp_client()->services()->get_all( $reload );
}
public static function get_tp_default_suid() {
$tp_default_suid = false;
if ( defined( 'WPML_TP_DEFAULT_SUID' ) ) {
$tp_default_suid = WPML_TP_DEFAULT_SUID;
}
$preferred_translation_service = self::get_preferred_translation_service();
if ( $preferred_translation_service && ! $tp_default_suid ) {
$tp_default_suid = $preferred_translation_service;
}
return $tp_default_suid;
}
public static function has_preferred_translation_service() {
return self::get_tp_default_suid() !== false;
}
public static function clear_preferred_translation_service() {
WP_Installer_API::set_preferred_ts( 'clear' );
}
/**
* @param int $service_id
*
* @return stdClass
*/
public static function get_service( $service_id ) {
// @todo: implement usage of WPML_TP_Service for the active service
return (object) (array) self::get_tp_client()->services()->get_service( $service_id, true );
}
/**
* @param int $service_id
*
* @return TranslationProxy_Project|WP_Error
* @throws \WPMLTranslationProxyApiException
* @throws \InvalidArgumentException
*/
public static function select_service( $service_id, $credentials = null ) {
global $sitepress;
$service_selected = false;
$error = false;
/** @var TranslationProxy_Service $service */
$service = self::get_service( $service_id );
if ( $service ) {
self::deselect_active_service();
$service = self::build_and_store_active_translation_service( $service, $credentials );
$result = $service;
$service_selected = true;
// Force authentication if no user input is needed
if ( ! self::service_requires_authentication( $service ) ) {
( new AuthorizationFactory() )->create()->authorize( new \stdClass() );
}
} else {
$result = new WP_Error(
'0',
'No service selected',
array( 'service_id' => $service_id )
);
}
// Do not store selected service if this operation failed;
if ( $error || ! $service_selected ) {
$sitepress->set_setting( 'translation_service', false, true );
}
$sitepress->save_settings();
return $result;
}
public static function deselect_active_service() {
global $sitepress;
$sitepress->set_setting( 'translation_service', false );
$sitepress->set_setting( 'translator_choice', false );
$sitepress->set_setting( 'icl_lang_status', false );
$sitepress->set_setting( 'icl_html_status', false );
$sitepress->set_setting( 'icl_current_session', false );
$sitepress->set_setting( 'last_icl_reminder_fetch', false );
$sitepress->set_setting( 'translators_management_info', false );
$sitepress->set_setting( 'language_pairs', false );
$sitepress->save_settings();
do_action( 'wpml_tp_service_dectivated', self::get_current_service() );
}
/**
* @param $service
* @param bool $custom_fields_data
*
* @return mixed
* @throws \WPMLTranslationProxyApiException
*/
public static function build_and_store_active_translation_service( $service, $custom_fields_data = false ) {
global $sitepress;
// set language map
$service->languages_map = self::languages_map( $service );
// set information about custom fields
$service->custom_fields = self::get_custom_fields( $service->id, true );
$service->custom_fields_data = $custom_fields_data;
$service->last_refresh = time();
$sitepress->set_setting( 'translation_service', $service, true );
return $service;
}
/**
* @return bool|TranslationProxy_Project
*/
public static function get_current_project() {
$translation_service = self::get_current_service();
if ( $translation_service ) {
return new TranslationProxy_Project( $translation_service, 'xmlrpc', self::get_tp_client() );
}
return false;
}
public static function get_current_service_info( array $info = array() ) {
global $sitepress;
if ( ! $sitepress->get_setting( 'translation_service' ) ) {
$sitepress->set_setting( 'translation_service', false, true );
}
$service = self::get_current_service();
if ( $service ) {
$service_info = array();
if ( icl_do_not_promote() ) {
$service_info['name'] = __( 'Translation Service', 'wpml-translation-management' );
$service_info['logo'] = false;
$service_info['header'] = __( 'Translation Service', 'wpml-translation-management' );
$service_info['description'] = false;
$service_info['contact_url'] = false;
} else {
$service_info['name'] = $service->name;
$service_info['logo'] = $service->logo_url;
$service_info['header'] = $service->name;
$service_info['description'] = $service->description;
$service_info['contact_url'] = $service->url;
}
$service_info['setup_url'] = TranslationProxy_Popup::get_link( '@select-translators;from_replace;to_replace@', array( 'ar' => 1 ), true );
$service_info['has_quote'] = $service->quote_iframe_url !== '';
$service_info['has_translator_selection'] = $service->has_translator_selection;
$info[ $service->id ] = $service_info;
}
return $info;
}
public static function get_service_promo() {
global $sitepress;
if ( icl_do_not_promote() ) {
return '';
}
$cache_key = 'get_service_promo';
$cache_found = false;
$output = wp_cache_get( $cache_key, '', false, $cache_found );
if ( $cache_found ) {
return $output;
}
$icl_translation_services = apply_filters( 'icl_translation_services', array() );
$icl_translation_services = array_merge( $icl_translation_services, self::get_current_service_info() );
$output = '';
if ( ! empty( $icl_translation_services ) ) {
$sitepress_settings = $sitepress->get_settings();
$icl_dashboard_settings = isset( $sitepress_settings['dashboard'] ) ? $sitepress_settings['dashboard'] : array();
if ( empty( $icl_dashboard_settings['hide_icl_promo'] ) ) {
$exp_hidden = '';
$col_hidden = ' hidden';
} else {
$exp_hidden = ' hidden';
$col_hidden = '';
}
$output .= '<div class="icl-translation-services' . $exp_hidden . '">';
foreach ( $icl_translation_services as $service ) {
$output .= '<div class="icl-translation-services-inner">';
$output .= '<p class="icl-translation-services-logo"><span><img src="' . $service['logo'] . '" alt="' . $service['name'] . '" /></span></p>';
$output .= '<h3 class="icl-translation-services-header"> ' . $service['header'] . '</h3>';
$output .= '<div class="icl-translation-desc"> ' . $service['description'] . '</div>';
$output .= '</div>';
$output .= '<p class="icl-translation-links">';
$output .= '<a class="icl-mail-ico" href="' . $service['contact_url'] . '" target="_blank">' . __( 'Contact', 'wpml-translation-management' ) . " {$service['name']}</a>";
$output .= '<a id="icl_hide_promo" href="#">' . __( 'Hide this', 'wpml-translation-management' ) . '</a>';
$output .= '</p>';
}
$output .= '</div>';
$output .= '<a class="' . $col_hidden . '" id="icl_show_promo" href="#">' . __( 'Need translators?', 'wpml-translation-management' ) . '</a>';
}
wp_cache_set( $cache_key, $output );
return $output;
}
public static function get_service_dashboard_info() {
global $sitepress;
return self::get_custom_html(
'dashboard',
$sitepress->get_current_language(),
array(
'TranslationProxy_Popup',
'get_link',
)
);
}
public static function get_service_translators_info() {
global $sitepress;
return self::get_custom_html(
'translators',
$sitepress->get_current_language(),
array(
'TranslationProxy_Popup',
'get_link',
)
);
}
/**
* @param string $location
* @param string $locale
* @param callable $popup_link_callback
* @param int $max_count
* @param bool $paragraph
*
* @return string
*/
public static function get_custom_html(
$location,
$locale,
$popup_link_callback,
$max_count = 1000,
$paragraph = true
) {
/** @var $project TranslationProxy_Project */
$project = self::get_current_project();
if ( ! $project ) {
return '';
}
$cache_key = $project->id . ':' . md5(
serialize(
array(
$location,
$locale,
serialize( $popup_link_callback ),
$max_count,
$paragraph,
)
)
);
$cache_group = 'get_custom_html';
$cache_found = false;
$output = wp_cache_get( $cache_key, $cache_group, false, $cache_found );
if ( $cache_found ) {
return $output;
}
try {
$text = $project->custom_text( $location, $locale );
} catch ( Exception $e ) {
return 'Error getting custom text from Translation Service: ' . $e->getMessage();
}
$count = 0;
if ( $text ) {
foreach ( $text as $string ) {
$format_string = self::sanitize_custom_text( $string->format_string );
if ( $paragraph ) {
$format = '<p>' . $format_string . '</p>';
} else {
$format = '<div>' . $format_string . '</div>';
}
$links = array();
/** @var array $string_links */
$string_links = $string->links;
foreach ( $string_links as $link ) {
$url = self::sanitize_custom_text( $link->url );
$text = self::sanitize_custom_text( $link->text );
if ( isset( $link->dismiss ) && (int) $link->dismiss === 1 ) {
$links[] = '<a href="' . $url . '" class="wpml_tp_custom_dismiss_able">' . $text . '</a>';
} else {
$links[] = call_user_func(
$popup_link_callback,
$url
) . $text . '</a>';
}
}
$output .= vsprintf( $format, $links );
$count ++;
if ( $count >= $max_count ) {
break;
}
}
}
return $output;
}
public static function get_current_service_name() {
if ( icl_do_not_promote() ) {
return __( 'Translation Service', 'wpml-translation-management' );
}
$translation_service = self::get_current_service();
if ( $translation_service ) {
return $translation_service->name;
}
return false;
}
public static function get_current_service_id() {
$translation_service = self::get_current_service();
if ( $translation_service ) {
return $translation_service->id;
}
return false;
}
public static function get_current_service_batch_name_max_length() {
$translation_service = self::get_current_service();
if ( $translation_service && isset( $translation_service->batch_name_max_length )
&& null
!== $translation_service->batch_name_max_length ) {
return $translation_service->batch_name_max_length;
}
return 40;
}
/**
* @param bool|TranslationProxy_Service|WP_Error $service
*
* @return bool
* @throws \InvalidArgumentException
* @throws \WPMLTranslationProxyApiException
*/
public static function service_requires_authentication( $service = false ) {
if ( ! $service ) {
$service = self::get_current_service();
}
$custom_fields = false;
if ( false !== (bool) $service ) {
$custom_fields = self::get_custom_fields( $service->id );
}
return $custom_fields && isset( $custom_fields->custom_fields ) && count( $custom_fields->custom_fields ) > 0;
}
/**
* Return true if $service has been successfully authenticated
* Services that do not require authentication are by default authenticated
*
* @param bool|WP_Error|TranslationProxy_Service $service
*
* @return bool
* @throws \InvalidArgumentException
*/
public static function is_service_authenticated( $service = false ) {
if ( ! $service ) {
$service = self::get_current_service();
}
if ( ! $service ) {
return false;
}
if ( ! self::service_requires_authentication( $service ) ) {
return true;
}
$has_custom_fields = self::has_custom_fields();
$custom_fields_data = self::get_custom_fields_data();
return $has_custom_fields && $custom_fields_data;
}
/**
* @return bool|TranslationProxy_Service|WP_Error
*/
public static function get_current_service() {
global $sitepress;
/** @var TranslationProxy_Service $ts */
$ts = $sitepress->get_setting( 'translation_service' );
if ( is_array( $ts ) ) {
return new WP_Error( 'translation-proxy-service-misconfiguration', 'translation_service is stored as array!', $ts );
}
return $ts;
}
/**
*
* @return bool
* @throws \InvalidArgumentException
*/
public static function is_current_service_active_and_authenticated() {
$active_service = self::get_current_service();
return $active_service && ( ! self::service_requires_authentication() || TranslationProxy_Service::is_authenticated( $active_service ) );
}
/**
* @return mixed
*/
public static function get_translation_projects() {
global $sitepress;
return $sitepress->get_setting( 'icl_translation_projects', null );
}
public static function get_service_name( $service_id = false ) {
if ( $service_id ) {
$name = false;
$services = self::services( false );
foreach ( $services as $service ) {
if ( $service->id === $service_id ) {
$name = $service->name;
}
}
} else {
$name = self::get_current_service_name();
}
return $name;
}
public static function has_custom_fields( $service_id = false ) {
$custom_fields = self::get_custom_fields( $service_id );
if ( $custom_fields ) {
return isset( $custom_fields->custom_fields ) && is_array( $custom_fields->custom_fields ) && count( $custom_fields->custom_fields );
}
return false;
}
/**
* @param int|bool $service_id If not given, will use the current service ID (if any)
* @param bool $force_reload Force reload custom fields from Translation Service
*
* @throws WPMLTranslationProxyApiException
* @throws InvalidArgumentException
* @return array|mixed|null|string
*/
public static function get_custom_fields( $service_id = false, $force_reload = false ) {
if ( ! $service_id ) {
$service_id = self::get_current_service_id();
}
if ( ! $service_id ) {
return false;
}
$translation_service = self::get_current_service();
if ( $translation_service && ! $force_reload ) {
return null !== $translation_service->custom_fields ? $translation_service->custom_fields : false;
}
return self::get_tp_client()->services()->get_custom_fields( $service_id );
}
/**
* @return array
*/
public static function get_extra_fields_local() {
global $sitepress;
$service = self::get_current_service();
$icl_translation_projects = $sitepress->get_setting( 'icl_translation_projects' );
if ( isset( $icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'] ) && ! empty( $icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'] ) ) {
return $icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'];
}
return array();
}
/**
* @param $extra_fields
*/
public static function save_extra_fields( $extra_fields ) {
global $sitepress;
$service = self::get_current_service();
$icl_translation_projects = $sitepress->get_setting( 'icl_translation_projects' );
if ( isset( $icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'] ) && ! empty( $icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'] ) ) {
$icl_translation_projects[ TranslationProxy_Project::generate_service_index( $service ) ]['extra_fields'] = $extra_fields;
$sitepress->set_setting( 'icl_translation_projects', $icl_translation_projects );
$sitepress->save_settings();
}
}
public static function maybe_convert_extra_fields( $extra_fields ) {
$extra_fields_typed = array();
if ( $extra_fields && is_array( $extra_fields ) ) {
/** @var array $extra_fields */
/** @var stdClass $extra_field */
foreach ( $extra_fields as $extra_field ) {
if ( $extra_field instanceof WPML_TP_Extra_Field ) {
$extra_field_typed = $extra_field;
} else {
$extra_field_typed = new WPML_TP_Extra_Field();
if ( isset( $extra_field->type ) ) {
$extra_field_typed->type = $extra_field->type;
}
if ( isset( $extra_field->label ) ) {
$extra_field_typed->label = $extra_field->label;
}
if ( isset( $extra_field->name ) ) {
$extra_field_typed->name = $extra_field->name;
}
if ( isset( $extra_field->items ) ) {
$extra_field_typed->items = $extra_field->items;
}
}
$extra_fields_typed[] = $extra_field_typed;
}
}
return $extra_fields_typed;
}
public static function get_custom_fields_data() {
$service = self::get_current_service();
return null !== $service->custom_fields_data ? $service->custom_fields_data : false;
}
/**
* @return bool true if the current translation service allows selection of specific translators
* @throws \InvalidArgumentException
*/
public static function translator_selection_available() {
$res = false;
$translation_service = self::get_current_service();
if ( $translation_service && $translation_service->has_translator_selection && self::is_service_authenticated() ) {
$res = true;
}
return $res;
}
private static function sanitize_custom_text( $text ) {
$text = substr( $text, 0, CUSTOM_TEXT_MAX_LENGTH );
$text = esc_html( $text );
// Service sends html tags as [tag]
$text = str_replace( array( '[', ']' ), array( '<', '>' ), $text );
return $text;
}
private static function languages_map( $service ) {
$languages_map = array();
$languages = self::get_tp_client()->services()->get_languages_map( $service->id );
if ( ! empty( $languages ) ) {
foreach ( $languages as $language ) {
$languages_map[ $language->iso_code ] = $language->value;
}
}
return $languages_map;
}
private static function get_preferred_translation_service() {
$tp_default_suid = false;
$preferred_translation_service_from_installer = self::get_preferred_translation_service_from_installer();
if ( 'clear' !== $preferred_translation_service_from_installer ) {
$tp_default_suid = $preferred_translation_service_from_installer;
}
return $tp_default_suid;
}
private static function get_preferred_translation_service_from_installer() {
return WP_Installer_API::get_preferred_ts();
}
public static function get_tp_client() {
if ( ! self::$tp_client ) {
$tp_api_factory = new WPML_TP_Client_Factory();
self::$tp_client = $tp_api_factory->create();
}
return self::$tp_client;
}
}

View File

@@ -0,0 +1,611 @@
<?php
/**
* @package wpml-core
* @package wpml-core-pro-translation
*/
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Str;
use function WPML\Container\make;
use function WPML\FP\pipe;
/**
* Class WPML_Pro_Translation
*/
class WPML_Pro_Translation extends WPML_TM_Job_Factory_User {
public $errors = array();
/** @var TranslationManagement $tmg */
private $tmg;
/** @var WPML_TM_CMS_ID $cms_id_helper */
private $cms_id_helper;
/** @var WPML_TM_Xliff_Reader_Factory $xliff_reader_factory */
private $xliff_reader_factory;
private $sitepress;
private $update_pm;
/**
* WPML_Pro_Translation constructor.
*
* @param WPML_Translation_Job_Factory $job_factory
*/
function __construct( &$job_factory ) {
parent::__construct( $job_factory );
global $iclTranslationManagement, $wpdb, $sitepress, $wpml_post_translations, $wpml_term_translations;
$this->tmg =& $iclTranslationManagement;
$this->xliff_reader_factory = new WPML_TM_Xliff_Reader_Factory( $this->job_factory );
$wpml_tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations );
$this->cms_id_helper = new WPML_TM_CMS_ID( $wpml_tm_records, $job_factory );
$this->sitepress = $sitepress;
add_filter( 'xmlrpc_methods', array( $this, 'custom_xmlrpc_methods' ) );
add_action(
'post_submitbox_start',
array(
$this,
'post_submitbox_start',
)
);
add_action(
'icl_ajx_custom_call',
array(
$this,
'ajax_calls',
),
10,
2
);
add_action( 'wpml_minor_edit_for_gutenberg', array( $this, 'gutenberg_minor_edit' ), 10, 0 );
$this->update_pm = new WPML_Update_PickUp_Method( $this->sitepress );
}
/**
* @return WPML_TM_CMS_ID
*/
public function &get_cms_id_helper() {
return $this->cms_id_helper;
}
/**
* @param string $call
* @param array $data
*/
function ajax_calls( $call, $data ) {
switch ( $call ) {
case 'set_pickup_mode':
$response = $this->update_pm->update_pickup_method( $data, $this->get_current_project() );
if ( 'no-ts' === $response ) {
wp_send_json_error( array( 'message' => __( 'Please activate translation service first.', 'wpml-translation-management' ) ) );
}
if ( 'cant-update' === $response ) {
wp_send_json_error( array( 'message' => __( 'Could not update the translation pickup mode.', 'wpml-translation-management' ) ) );
}
wp_send_json_success( array( 'message' => __( 'Ok', 'wpml-translation-management' ) ) );
break;
}
}
public function get_current_project() {
return TranslationProxy::get_current_project();
}
/**
* @param WP_Post|WPML_Package $post
* @param array $target_languages
* @param int $translator_id
* @param int $job_id
*
* @return bool|int
*/
function send_post( $post, $target_languages, $translator_id, $job_id ) {
/** @var TranslationManagement $iclTranslationManagement */
global $sitepress, $iclTranslationManagement;
$this->maybe_init_translation_management( $iclTranslationManagement );
if ( is_numeric( $post ) ) {
$post = get_post( $post );
}
if ( ! $post ) {
return false;
}
$post_id = $post->ID;
$post_type = $post->post_type;
$element_type_prefix = $iclTranslationManagement->get_element_type_prefix_from_job_id( $job_id );
$element_type = $element_type_prefix . '_' . $post_type;
$note = WPML_TM_Translator_Note::get( $post_id );
if ( ! $note ) {
$note = null;
}
$err = false;
$tp_job_id = false;
$source_language = $sitepress->get_language_for_element( $post_id, $element_type );
$target_language = is_array( $target_languages ) ? end( $target_languages ) : $target_languages;
if ( empty( $target_language ) || $target_language === $source_language ) {
return false;
}
$translation = $this->tmg->get_element_translation( $post_id, $target_language, $element_type );
if ( ! $translation ) { // translated the first time
$err = true;
}
if ( ! $err && ( $translation->needs_update || $translation->status == ICL_TM_NOT_TRANSLATED || $translation->status == ICL_TM_WAITING_FOR_TRANSLATOR ) ) {
$project = TranslationProxy::get_current_project();
if ( $iclTranslationManagement->is_external_type( $element_type_prefix ) ) {
$job_object = new WPML_External_Translation_Job( $job_id );
} else {
$job_object = new WPML_Post_Translation_Job( $job_id );
$job_object->load_terms_from_post_into_job();
}
list( $err, $project, $tp_job_id ) = $job_object->send_to_tp( $project, $translator_id, $this->cms_id_helper, $this->tmg, $note );
if ( $err ) {
$this->enqueue_project_errors( $project );
}
}
return $err ? false : $tp_job_id; // last $ret
}
function server_languages_map( $language_name, $server2plugin = false ) {
if ( is_array( $language_name ) ) {
return array_map( array( $this, 'server_languages_map' ), $language_name );
}
$map = array(
'Norwegian Bokmål' => 'Norwegian',
'Portuguese, Brazil' => 'Portuguese',
'Portuguese, Portugal' => 'Portugal Portuguese',
);
$map = $server2plugin ? array_flip( $map ) : $map;
return isset( $map[ $language_name ] ) ? $map[ $language_name ] : $language_name;
}
/**
* @param $methods
*
* @return array
*/
public function custom_xmlrpc_methods( $methods ) {
$icl_methods['translationproxy.test_xmlrpc'] = '__return_true';
$icl_methods['translationproxy.updated_job_status'] = array(
$this,
'xmlrpc_updated_job_status',
);
$methods = array_merge( $methods, $icl_methods );
if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST && preg_match( '#<methodName>([^<]+)</methodName>#i', $this->sitepress->get_wp_api()->get_raw_post_data(), $matches ) ) {
$method = $matches[1];
if ( array_key_exists( $method, $icl_methods ) ) {
set_error_handler( array( $this, 'translation_error_handler' ), E_ERROR | E_USER_ERROR );
}
}
return $methods;
}
/**
* @param array $args
*
* @return int|IXR_Error
*/
public function xmlrpc_updated_job_status( $args ) {
global $wpdb;
$tp_id = isset( $args[0] ) ? $args[0] : 0;
$cms_id = isset( $args[1] ) ? $args[1] : 0;
$status = isset( $args[2] ) ? $args[2] : '';
$signature = isset( $args[3] ) ? $args[3] : '';
if ( ! $this->authenticate_request( $tp_id, $cms_id, $status, $signature ) ) {
return new IXR_Error( 401, 'Wrong signature' );
}
try {
/** @var WPML_TM_Jobs_Repository $jobs_repository */
$jobs_repository = wpml_tm_get_jobs_repository();
$job_match = $jobs_repository->get(
new WPML_TM_Jobs_Search_Params(
array(
'scope' => 'remote',
'tp_id' => $tp_id,
)
)
);
if ( $job_match ) {
$jobs_array = $job_match->toArray();
$job = $jobs_array[0];
$job->set_status( WPML_TP_Job_States::map_tp_state_to_local( $status ) );
$tp_sync_updated_job = new WPML_TP_Sync_Update_Job( $wpdb, $this->sitepress );
$job_updated = $tp_sync_updated_job->update_state( $job );
if ( $job_updated && WPML_TP_Job_States::CANCELLED !== $status ) {
$apply_tp_translation = new WPML_TP_Apply_Single_Job(
wpml_tm_get_tp_translations_repository(),
new WPML_TP_Apply_Translation_Strategies( $wpdb )
);
$apply_tp_translation->apply( $job );
}
return 1;
}
} catch ( Exception $e ) {
return new IXR_Error( $e->getCode(), $e->getMessage() );
}
return 0;
}
/**
* @return bool
*/
private function authenticate_request( $tp_id, $cms_id, $status, $signature ) {
$project = TranslationProxy::get_current_project();
return sha1( $project->id . $project->access_key . $tp_id . $cms_id . $status ) === $signature;
}
/**
* @return WPML_WP_API
*/
function get_wpml_wp_api() {
return $this->sitepress->get_wp_api();
}
/**
*
* Cancel translation for given cms_id
*
* @param $rid
* @param $cms_id
*
* @return bool
*/
function cancel_translation( $rid, $cms_id ) {
/**
* @var WPML_String_Translation $WPML_String_Translation
* @var TranslationManagement $iclTranslationManagement
*/
global $WPML_String_Translation, $iclTranslationManagement;
$res = false;
if ( empty( $cms_id ) ) { // it's a string
if ( isset( $WPML_String_Translation ) ) {
$res = $WPML_String_Translation->cancel_remote_translation( $rid );
}
} else {
$translation_id = $this->cms_id_helper->get_translation_id( $cms_id );
if ( $translation_id ) {
$iclTranslationManagement->cancel_translation_request( $translation_id );
$res = true;
}
}
return $res;
}
/**
*
* Downloads translation from TP and updates its document
*
* @param $translation_proxy_job_id
* @param $cms_id
*
* @return bool|string
*/
function download_and_process_translation( $translation_proxy_job_id, $cms_id ) {
global $wpdb;
if ( empty( $cms_id ) ) { // it's a string
// TODO: [WPML 3.3] this should be handled as any other element type in 3.3
$target = $wpdb->get_var( $wpdb->prepare( "SELECT target FROM {$wpdb->prefix}icl_core_status WHERE rid=%d", $translation_proxy_job_id ) );
return $this->process_translated_string( $translation_proxy_job_id, $target );
} else {
$translation_id = $this->cms_id_helper->get_translation_id( $cms_id, TranslationProxy::get_current_service() );
return ! empty( $translation_id ) && $this->add_translated_document( $translation_id, $translation_proxy_job_id );
}
}
/**
* @param int $translation_id
* @param int $translation_proxy_job_id
*
* @return bool
*/
function add_translated_document( $translation_id, $translation_proxy_job_id ) {
global $wpdb, $sitepress;
$project = TranslationProxy::get_current_project();
$translation_info = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}icl_translations WHERE translation_id=%d", $translation_id ) );
$translation = $project->fetch_translation( $translation_proxy_job_id );
if ( ! $translation ) {
$this->errors = array_merge( $this->errors, $project->errors );
} else {
$translation = apply_filters( 'icl_data_from_pro_translation', $translation );
}
$ret = true;
if ( ! empty( $translation ) && strpos( $translation, 'xliff' ) !== false ) {
try {
/** @var $job_xliff_translation WP_Error|array */
$job_xliff_translation = $this->xliff_reader_factory
->general_xliff_import()->import( $translation, $translation_id );
if ( is_wp_error( $job_xliff_translation ) ) {
$this->add_error( $job_xliff_translation->get_error_message() );
return false;
}
kses_remove_filters();
wpml_tm_save_data( $job_xliff_translation );
kses_init();
$translations = $sitepress->get_element_translations( $translation_info->trid, $translation_info->element_type, false, true, true );
if ( isset( $translations[ $translation_info->language_code ] ) ) {
$translation = $translations[ $translation_info->language_code ];
if ( isset( $translation->element_id ) && $translation->element_id ) {
$translation_post_type_prepared = $wpdb->prepare( "SELECT post_type FROM $wpdb->posts WHERE ID=%d", array( $translation->element_id ) );
$translation_post_type = $wpdb->get_var( $translation_post_type_prepared );
} else {
$translation_post_type = implode( '_', array_slice( explode( '_', $translation_info->element_type ), 1 ) );
}
if ( $translation_post_type == 'page' ) {
$url = get_option( 'home' ) . '?page_id=' . $translation->element_id;
} else {
$url = get_option( 'home' ) . '?p=' . $translation->element_id;
}
$project->update_job( $translation_proxy_job_id, $url );
} else {
$project->update_job( $translation_proxy_job_id );
}
} catch ( Exception $e ) {
$ret = false;
}
}
return $ret;
}
private static function content_get_link_paths( $body ) {
$regexp_links = array(
"/<a[^>]*href\s*=\s*([\"\']??)([^\"^>]+)[\"\']??([^>]*)>/i",
);
$links = array();
foreach ( $regexp_links as $regexp ) {
if ( preg_match_all( $regexp, $body, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
$links[] = $match;
}
}
}
return $links;
}
public function fix_links_to_translated_content( $element_id, $target_lang_code, $element_type = 'post' ) {
global $wpdb, $sitepress;
$sitepress->switch_lang( $target_lang_code );
$wpml_element_type = $element_type;
$body = false;
$string_type = null;
if ( strpos( $element_type, 'post' ) === 0 ) {
$post_prepared = $wpdb->prepare( "SELECT * FROM {$wpdb->posts} WHERE ID=%d", array( $element_id ) );
$post = $wpdb->get_row( $post_prepared );
$body = $post->post_content;
$wpml_element_type = 'post_' . $post->post_type;
} elseif ( $element_type == 'string' ) {
$string_prepared = $wpdb->prepare( "SELECT string_id, value FROM {$wpdb->prefix}icl_string_translations WHERE id=%d", array( $element_id ) );
$data = $wpdb->get_row( $string_prepared );
$body = $data->value;
$original_string_id = $data->string_id;
$string_type = $wpdb->get_var( $wpdb->prepare( "SELECT type FROM {$wpdb->prefix}icl_strings WHERE id=%d", $original_string_id ) );
if ( 'LINK' === $string_type ) {
$body = '<a href="' . $body . '">removeit</a>';
}
}
$translate_link_targets = make( 'WPML_Translate_Link_Targets' );
$absolute_links = make( 'AbsoluteLinks' );
$getTranslatedLink = function ( $link ) use ( $translate_link_targets, $absolute_links, $element_type, $target_lang_code ) {
if ( $absolute_links->is_home( $link[2] ) ) {
$translatedLink = $absolute_links->convert_url( $link[2], $target_lang_code );
$translatedLink = Str::replace( $link[2], $translatedLink, $link[0] );
} else {
add_filter( 'wpml_force_translated_permalink', '__return_true' );
$translatedLink = $translate_link_targets->convert_text( $link[0] );
remove_filter( 'wpml_force_translated_permalink', '__return_true' );
if ( self::should_links_be_converted_back_to_sticky( $element_type ) ) {
$translatedLink = $absolute_links->convert_text( $translatedLink );
}
}
return $translatedLink !== $link[0]
? [
'from' => $link[0],
'to' => $translatedLink,
]
: null;
};
$getTranslatedLinks = pipe(
Fns::map( $getTranslatedLink ),
Fns::filter( Fns::identity() )
);
$links = self::content_get_link_paths( $body );
$translatedLinks = $getTranslatedLinks( $links );
$replaceLink = function ( $body, $link ) {
return str_replace( $link['from'], $link['to'], $body );
};
$new_body = Fns::reduce( $replaceLink, $body, $translatedLinks );
if ( $new_body != $body ) {
if ( strpos( $element_type, 'post' ) === 0 ) {
$wpdb->update( $wpdb->posts, array( 'post_content' => $new_body ), array( 'ID' => $element_id ) );
} elseif ( $element_type == 'string' ) {
if ( 'LINK' === $string_type ) {
$new_body = str_replace( array( '<a href="', '">removeit</a>' ), array( '', '' ), $new_body );
$wpdb->update(
$wpdb->prefix . 'icl_string_translations',
array(
'value' => $new_body,
'status' => ICL_TM_COMPLETE,
),
array( 'id' => $element_id )
);
do_action( 'icl_st_add_string_translation', $element_id );
} else {
$wpdb->update( $wpdb->prefix . 'icl_string_translations', array( 'value' => $new_body ), array( 'id' => $element_id ) );
}
}
}
$links_fixed_status_factory = new WPML_Links_Fixed_Status_Factory( $wpdb, new WPML_WP_API() );
$links_fixed_status = $links_fixed_status_factory->create( $element_id, $wpml_element_type );
$links_fixed_status->set( Lst::length( $links ) === Lst::length( $translatedLinks ) );
$sitepress->switch_lang();
return sizeof( $translatedLinks );
}
function translation_error_handler( $error_number, $error_string, $error_file, $error_line ) {
switch ( $error_number ) {
case E_ERROR:
case E_USER_ERROR:
throw new Exception( $error_string . ' [code:e' . $error_number . '] in ' . $error_file . ':' . $error_line );
case E_WARNING:
case E_USER_WARNING:
return true;
default:
return true;
}
}
private static function should_links_be_converted_back_to_sticky( $element_type ) {
return 'string' !== $element_type && ! empty( $GLOBALS['WPML_Sticky_Links'] );
}
function post_submitbox_start() {
$show_box_style = $this->get_show_minor_edit_style();
if ( false !== $show_box_style ) {
?>
<p id="icl_minor_change_box" style="float:left;padding:0;margin:3px;<?php echo $show_box_style; ?>">
<label><input type="checkbox" name="icl_minor_edit" value="1" style="min-width:15px;"/>&nbsp;
<?php esc_html_e( 'Minor edit - don\'t update translation', 'wpml-translation-management' ); ?>
</label>
<br clear="all"/>
</p>
<?php
}
}
public function gutenberg_minor_edit() {
$show_box_style = $this->get_show_minor_edit_style();
if ( false !== $show_box_style ) {
?>
<div id="icl_minor_change_box" style="<?php echo $show_box_style; ?>" class="icl_box_paragraph">
<p>
<strong><?php esc_html_e( 'Minor edit', 'wpml-translation-management' ); ?></strong>
</p>
<label><input type="checkbox" name="icl_minor_edit" value="1" style="min-width:15px;"/>&nbsp;
<?php esc_html_e( "Don't update translation", 'wpml-translation-management' ); ?>
</label>
</div>
<?php
}
}
private function get_show_minor_edit_style() {
global $post, $iclTranslationManagement;
if ( empty( $post ) || ! $post->ID ) {
return false;
}
$translations = $iclTranslationManagement->get_element_translations( $post->ID, 'post_' . $post->post_type );
$show_box_style = 'display:none';
foreach ( $translations as $t ) {
if ( $t->element_id == $post->ID ) {
return false;
}
if ( $t->status == ICL_TM_COMPLETE && ! $t->needs_update ) {
$show_box_style = '';
break;
}
}
return $show_box_style;
}
private function process_translated_string( $translation_proxy_job_id, $language ) {
$project = TranslationProxy::get_current_project();
$translation = $project->fetch_translation( $translation_proxy_job_id );
$translation = apply_filters( 'icl_data_from_pro_translation', $translation );
$ret = false;
$translation = $this->xliff_reader_factory->string_xliff_reader()->get_data( $translation );
if ( $translation ) {
$ret = icl_translation_add_string_translation( $translation_proxy_job_id, $translation, $language );
if ( $ret ) {
$project->update_job( $translation_proxy_job_id );
}
}
return $ret;
}
private function add_error( $project_error ) {
$this->errors[] = $project_error;
}
/**
* @param $project TranslationProxy_Project
*/
function enqueue_project_errors( $project ) {
if ( isset( $project ) && isset( $project->errors ) && $project->errors ) {
foreach ( $project->errors as $project_error ) {
$this->add_error( $project_error );
}
}
}
/**
* @param TranslationManagement $iclTranslationManagement
*/
private function maybe_init_translation_management( $iclTranslationManagement ) {
if ( empty( $this->tmg->settings ) ) {
$iclTranslationManagement->init();
}
}
}

View File

@@ -0,0 +1,283 @@
<?php
use WPML\LIB\WP\Cache;
use \WPML\Collect\Support\Traits\Macroable;
use WPML\FP\Str;
use function \WPML\FP\System\sanitizeString;
use function \WPML\FP\pipe;
use function \WPML\FP\curryN;
/**
* @method static int get_batch_id_from_name( string $basket_name )
*/
class WPML_Translation_Basket {
use Macroable;
/** @var wpdb $wpdb */
private $wpdb;
public function __construct( \wpdb $wpdb ) {
$this->wpdb = $wpdb;
}
/**
* Returns an array representation of the current translation basket
*
* @param bool|false $force if true reloads the baskets contents from the database
*
* @return array
*/
function get_basket( $force = false ) {
$basket = TranslationProxy_Basket::get_basket( $force );
$basket = $basket ? $basket : array();
return $basket;
}
/**
* @return bool|TranslationProxy_Project
*/
public function get_project() {
return TranslationProxy::get_current_project();
}
function get_item_types() {
return TranslationProxy_Basket::get_basket_items_types();
}
/**
* Returns a batch instance by basket- or batch-name
*
* @param string $basket_name
*
* @return WPML_Translation_Batch
*/
function get_basket_batch( $basket_name ) {
return new WPML_Translation_Batch( $this->wpdb, self::get_batch_id_from_name( $basket_name ) );
}
/**
* Sets the remote target languages before committing the basket to a translation service.
*
* @param array $remote_languages
*/
function set_remote_target_languages( $remote_languages ) {
TranslationProxy_Basket::set_remote_target_languages( $remote_languages );
}
/**
* Removes all items from the current translation basket.
*/
function delete_all_items() {
TranslationProxy_Basket::delete_all_items_from_basket();
}
/**
* Returns the name of the current translation basket.
*
* @return bool|string
*/
function get_name() {
return TranslationProxy_Basket::get_basket_name();
}
function set_name( $basket_name ) {
TranslationProxy_Basket::set_basket_name( $basket_name );
}
function set_options( array $batch_options ) {
TranslationProxy_Basket::set_options( $batch_options );
}
/** @return array */
function get_options() {
return TranslationProxy_Basket::get_options();
}
/**
* @param string $basket_name
* @param int $basket_name_max_length
*
* @return array
*/
function check_basket_name( $basket_name, $basket_name_max_length ) {
$result = array(
'modified' => false,
'valid' => true,
'message' => '',
'new_value' => '',
);
$old_value = $basket_name;
$basket_name = strip_tags( $basket_name );
if ( mb_strlen( $basket_name ) > $basket_name_max_length ) {
$result['valid'] = true;
$result['message'] = sprintf(
__( 'The length of the batch name exceeds the maximum length of %s', 'wpml-translation-management' ),
$basket_name_max_length
);
$result['new_value'] = $this->get_unique_basket_name( $basket_name, $basket_name_max_length );
} elseif ( self::get_batch_id_from_name( $basket_name ) ) {
$result['valid'] = true;
$result['new_value'] = $this->get_unique_basket_name( $basket_name, $basket_name_max_length );
$result['message'] = __(
'This batch name already exists and was modified to ensure unique naming',
'wpml-translation-management'
);
} elseif ( count( $basket_name_array = explode( '|', $basket_name ) ) === 1 ) {
$result['valid'] = true;
$result['new_value'] = $this->get_unique_basket_name( $basket_name, $basket_name_max_length );
$result['message'] = __(
'The batch name was appended with the source language of its elements.',
'wpml-translation-management'
);
}
$result['modified'] = $result['new_value'] !== '' && $result['new_value'] !== $old_value;
$result['message'] = $result['modified'] || ! $result['valid'] ? $result['message'] : '';
return $result;
}
/**
* Returns a unique name derived from an input name for a Translation Proxy Basket
*
* @param bool $name
* @param bool|int $max_length
*
* @return bool|string
*/
function get_unique_basket_name( $name, $max_length ) {
$basket_name_array = explode( '|', $name );
$name = count( $basket_name_array ) === 1
|| ( ! is_numeric( $basket_name_array[ count( $basket_name_array ) - 1 ] )
&& $basket_name_array[ count( $basket_name_array ) - 1 ] !== $this->get_source_language() )
|| ( is_numeric( $basket_name_array[ count( $basket_name_array ) - 1 ] )
&& $basket_name_array[ count( $basket_name_array ) - 2 ] !== $this->get_source_language() )
? $name . '|' . $this->get_source_language() : $name;
$name = mb_strlen( $name ) > $max_length
? $this->sanitize_basket_name( $name, $max_length ) : $name;
if ( self::get_batch_id_from_name( $name ) ) {
$suffix = 2;
$name = $this->sanitize_basket_name( $name, $max_length - mb_strlen( (string) $suffix ) - 1 );
while ( self::get_batch_id_from_name( $name . '|' . $suffix ) ) {
$suffix ++;
$name = $this->sanitize_basket_name( $name, $max_length - mb_strlen( (string) $suffix ) - 1 );
}
$name .= '|' . $suffix;
}
return $name;
}
/**
* @return string
*/
public function get_source_language() {
return TranslationProxy_Basket::get_source_language();
}
/**
* @param int $package_id
*/
public function remove_package( $package_id ) {
TranslationProxy_Basket::delete_item_from_basket( $package_id, 'package' );
}
/**
* @param int $id
* @param string $kind
*/
public function remove_item( $id, $kind ) {
TranslationProxy_Basket::delete_item_from_basket( $id, $kind );
}
/**
* Merge the basket portion with the saved basket
*
* @param array $basket_portion
*/
public function update_basket( $basket_portion = array() ) {
TranslationProxy_Basket::update_basket( $basket_portion );
}
private function sanitize_basket_name( $basket_name, $max_length ) {
// input basket name is separated by pipes so we explode it
$to_trim = mb_strlen( $basket_name ) - $max_length;
if ( $to_trim <= 0 ) {
return $basket_name;
}
$basket_name_array = explode( '|', $basket_name );
$wpml_flag = count( $basket_name_array ) < 3;
if ( $wpml_flag === false && count( $basket_name_array ) < 2 ) {
return mb_substr( $basket_name, $max_length - 1 );
}
// first we trim the middle part holding the "WPML"
if ( $wpml_flag ) {
list( $basket_name_array, $to_trim ) = $this->shorten_basket_name( $basket_name_array, 1, $to_trim );
}
// then trim the site name first, if that's not enough move the array index and also trim the language
for ( $i = 0; $i <= 1; $i ++ ) {
if ( $to_trim > 0 ) {
list( $basket_name_array, $to_trim ) = $this->shorten_basket_name( $basket_name_array, 0, $to_trim );
$basket_name_array = array_filter( $basket_name_array );
} else {
break;
}
}
$basket_name_array = array_filter( $basket_name_array );
return implode( '|', $basket_name_array );
}
private function shorten_basket_name( $name_array, $index, $to_trim ) {
if ( mb_strlen( $name_array [ $index ] ) > $to_trim ) {
$name_array[ $index ] = mb_substr( $name_array[ $index ], 0, mb_strlen( $name_array [ $index ] ) - $to_trim - 1 );
$name_array = array_filter( $name_array );
$to_trim = 0;
} else {
$to_trim = $to_trim - mb_strlen( $name_array [ $index ] ) - 1; // subtract one here since we lose a downstroke
unset( $name_array [ $index ] );
}
return array( $name_array, $to_trim );
}
}
/**
* Returns the batch id for a given basket or batch name
*
* @param string $basket_name
*
* @return int|bool
*/
WPML_Translation_Basket::macro(
'get_batch_id_from_name',
curryN(
1,
Cache::memorize(
'get_batch_id_from_name',
pipe(
Str::replace( '"', '\\"' ),
sanitizeString(),
TranslationProxy_Batch::getBatchId()
)
)
)
);

View File

@@ -0,0 +1,156 @@
<?php
/**
* Class WPML_Translation_Proxy_Basket_Networking
*/
class WPML_Translation_Proxy_Basket_Networking {
/** @var WPML_Translation_Basket $basket */
private $basket;
/** @var TranslationManagement $tm_instance */
private $tm_instance;
/**
* @param WPML_Translation_Basket $basket
* @param TranslationManagement $tm_instance
*/
function __construct( $basket, &$tm_instance ) {
$this->basket = $basket;
$this->tm_instance = $tm_instance;
}
/**
* @param WPML_TM_Translation_Batch $batch
*
* @uses \WPML_Translation_Basket::get_basket Gets the array representation of the translation basket
* @uses \WPML_Translation_Proxy_Basket_Networking::generate_batch generates the batch in case no chunk was given for the commit from the basket
* @uses \WPML_Translation_Proxy_Basket_Networking::get_batch_name
* @uses \WPML_Translation_Proxy_Basket_Networking::send_all_jobs
* @uses \WPML_Translation_Proxy_Basket_Networking::rollback_basket_commit rolls back the whole commit action in case of an error.
*
* @return array
*/
function commit_basket_chunk( WPML_TM_Translation_Batch $batch ) {
$result = $this->send_all_jobs( $batch );
$error_messages = $this->tm_instance->messages_by_type( 'error' );
if ( ( $has_error = (bool) $error_messages ) === true ) {
$this->rollback_basket_commit( $batch->get_basket_name() );
$result['message'] = '';
$result['additional_messages'] = $error_messages;
}
return array( $has_error, $result, $error_messages );
}
/**
* Cancels all remote jobs in the requested batch.
*
* @param string $posted_basket_name basket name in the currently handled request
*
* @uses \WPML_Translation_Batch::cancel_all_jobs
*/
function rollback_basket_commit( $posted_basket_name ) {
$this->basket->get_basket( true );
$basket_name = $this->basket->get_name();
$basket_name = $basket_name ? $basket_name : $posted_basket_name;
$batch = $this->basket->get_basket_batch( $basket_name );
$batch->cancel_all_jobs();
$batch->clear_batch_data();
}
/**
* Checks if an array of translators has any remote translators in it.
*
* @param array $translators
*
* @return bool
*/
function contains_remote_translators( array $translators ) {
return count( array_filter( $translators, 'is_numeric' ) ) < count( $translators );
}
/**
* Sends all jobs from basket in batch mode to translation proxy
*
* @param WPML_TM_Translation_Batch $batch
* @param array $translators
* @param array $batch_options
*
* @return bool false in case of errors (read from TranslationManagement::get_messages('error') to get errors details)
*/
private function send_all_jobs( WPML_TM_Translation_Batch $batch ) {
$this->basket->set_options( $batch->get_batch_options() );
$this->basket->set_name( $batch->get_basket_name() );
$this->basket->set_remote_target_languages( $batch->get_remote_target_languages() );
$basket_items_types = $this->basket->get_item_types();
foreach ( $basket_items_types as $item_type_name => $item_type ) {
do_action(
'wpml_tm_send_' . $item_type_name . '_jobs',
$batch,
$item_type_name
);
}
// check if there were no errors
return ! $this->tm_instance->messages_by_type( 'error' );
}
/**
* Generates the batch array for posts in the basket.
*
* @param array $basket
*
* @return array
*/
private function generate_batch( array $basket ) {
$batch = array();
$posts = isset( $basket['post'] ) ? $basket['post'] : array();
foreach ( $posts as $post_id => $post ) {
$batch[] = array(
'type' => 'post',
'post_id' => $post_id,
);
}
return $batch;
}
/**
* Returns the name of the batch that contains the given post_id.
*
* @param int $post_id
*
* @return null|string
*/
private function get_batch_name( $post_id ) {
global $wpdb;
$name = $wpdb->get_var(
$wpdb->prepare(
" SELECT b.batch_name
FROM {$wpdb->prefix}icl_translation_batches b
JOIN {$wpdb->prefix}icl_translation_status s
ON s.batch_id = b.id
JOIN {$wpdb->prefix}icl_translations t
ON t.translation_id = s.translation_id
JOIN {$wpdb->prefix}icl_translations o
ON o.trid = t.trid
AND o.language_code = t.source_language_code
JOIN {$wpdb->posts} p
ON o.element_id = p.ID
AND o.element_type = CONCAT('post_', p.post_type)
WHERE o.element_id = %d
ORDER BY b.id
LIMIT 1",
$post_id
)
);
$this->basket->set_name( $name );
return $name;
}
}

View File

@@ -0,0 +1,97 @@
<?php
use function \WPML\Container\make;
use \WPML\FP\Obj;
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-translation-job-helper.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-translation-job-helper-with-api.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/wpml-translation-jobs-collection.class.php';
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-save-translation-data-action.class.php';
function wpml_tm_save_job_fields_from_post( $job_id ) {
$job = new WPML_Post_Translation_Job( $job_id );
$job->update_fields_from_post();
}
add_action( 'wpml_save_job_fields_from_post', 'wpml_tm_save_job_fields_from_post', 10, 1 );
/**
* @param array $data
* @param bool $redirect_after_saving
*
* @return bool
*/
function wpml_tm_save_data( array $data, $redirect_after_saving = true ) {
$job_factory = wpml_tm_load_job_factory();
$save_factory = new WPML_TM_Job_Action_Factory( $job_factory );
$save_data_action = $save_factory->save_action( $data );
$result = $save_data_action->save_translation();
$redirect_target = $redirect_after_saving ? $save_data_action->get_redirect_target() : false;
if ( (bool) $redirect_target === true ) {
wp_redirect( $redirect_target );
}
return $result;
}
add_action( 'wpml_save_translation_data', 'wpml_tm_save_data', 10, 1 );
function wpml_tm_add_translation_job( $rid, $translator_id, $translation_package, $batch_options ) {
$helper = new WPML_TM_Action_Helper();
$helper->add_translation_job( $rid, $translator_id, $translation_package, $batch_options );
}
add_action( 'wpml_add_translation_job', 'wpml_tm_add_translation_job', 10, 4 );
require_once dirname( __FILE__ ) . '/wpml-private-filters.php';
/**
* @param int $job_id
*/
function wpml_set_job_translated_term_values( $job_id ) {
global $sitepress;
$delete = $sitepress->get_setting( 'tm_block_retranslating_terms' );
$job_object = new WPML_Post_Translation_Job( $job_id );
$job_object->load_terms_from_post_into_job( $delete );
}
add_action( 'wpml_added_local_translation_job', 'wpml_set_job_translated_term_values' );
function wpml_tm_assign_translation_job( $job_id, $translator_id, $service, $type ) {
global $wpml_translation_job_factory;
$job = $type === 'string'
? new WPML_String_Translation_Job( $job_id )
: $wpml_translation_job_factory->get_translation_job(
$job_id,
false,
0,
true
);
if ( $job ) {
return $job->assign_to( $translator_id, $service );
}
return null;
}
add_action( 'wpml_tm_assign_translation_job', 'wpml_tm_assign_translation_job', 10, 4 );
/**
* Potentially handles the request to add strings to the translation basket,
* triggered by String Translation.
*/
function wpml_tm_add_strings_to_basket() {
if (
Obj::prop( 'icl_st_action', $_POST ) === 'send_strings'
&& wpml_is_action_authenticated( 'icl-string-translation' )
) {
WPML_TM_String_Basket_Request::send_to_basket( $_POST, [ TranslationProxy_Basket::class, 'add_strings_to_basket' ] );
}
}
if ( is_admin() ) {
add_action( 'init', 'wpml_tm_add_strings_to_basket' );
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* @param mixed $default
* @param int $rid
*
* @return mixed
*/
function wpml_filter_rid_to_untranslated_job_id( $default, $rid ) {
require_once WPML_TM_PATH . '/inc/translation-jobs/helpers/wpml-update-post-translation-data-action.class.php';
$save_data_action = new WPML_TM_Update_Post_Translation_Data_Action();
list( $job_id, $translated ) = $save_data_action->get_prev_job_data( $rid );
return $job_id && ! $translated ? $job_id : $default;
}
add_filter( 'wpml_rid_to_untranslated_job_id', 'wpml_filter_rid_to_untranslated_job_id', 10, 2 );
/**
* @param int|object $element
*
* @return string
*/
function wpml_tm_element_md5( $element ) {
$helper = new WPML_TM_Action_Helper();
return $helper->post_md5( $element );
}
add_filter( 'wpml_tm_element_md5', 'wpml_tm_element_md5', 10, 1 );
/**
* Filters the possible target languages for creating a new post translation
* on the post edit screen.
*
* @param string[] $allowed_langs
* @param int $element_id
* @param string $element_type_prefix
*
* @return string[]
*/
function wpml_tm_filter_post_target_langs(
$allowed_langs,
$element_id,
$element_type_prefix
) {
global $wpml_tm_translation_status, $wpml_post_translations;
$tm_records = wpml_tm_get_records();
$allowed_langs_filter = new WPML_TM_Post_Target_Lang_Filter(
$tm_records,
$wpml_tm_translation_status,
$wpml_post_translations
);
return $allowed_langs_filter->filter_target_langs(
$allowed_langs,
$element_id,
$element_type_prefix
);
}
add_filter(
'wpml_allowed_target_langs',
'wpml_tm_filter_post_target_langs',
10,
3
);

View File

@@ -0,0 +1,684 @@
<?php
/**
* Class to create and manage a Zip file.
*
* Initially inspired by CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html)
* and
* http://www.pkware.com/documents/casestudies/APPNOTE.TXT Zip file specification.
*
* License: GNU LGPL 2.1.
*
* @author A. Grandt <php@grandt.com>
* @copyright 2009-2014 A. Grandt
* @license GNU LGPL 2.1
* @link http://www.phpclasses.org/package/6110
* @link https://github.com/Grandt/PHPZip
* @version 1.62
*/
class wpml_zip {
const VERSION = 1.62;
const ZIP_LOCAL_FILE_HEADER = "\x50\x4b\x03\x04"; // Local file header signature
const ZIP_CENTRAL_FILE_HEADER = "\x50\x4b\x01\x02"; // Central file header signature
const ZIP_END_OF_CENTRAL_DIRECTORY = "\x50\x4b\x05\x06\x00\x00\x00\x00"; // end of Central directory record
const EXT_FILE_ATTR_DIR = 010173200020; // Permission 755 drwxr-xr-x = (((S_IFDIR | 0755) << 16) | S_DOS_D);
const EXT_FILE_ATTR_FILE = 020151000040; // Permission 644 -rw-r--r-- = (((S_IFREG | 0644) << 16) | S_DOS_A);
const ATTR_VERSION_TO_EXTRACT = "\x14\x00"; // Version needed to extract
const ATTR_MADE_BY_VERSION = "\x1E\x03"; // Made By Version
// UID 1000, GID 0
const EXTRA_FIELD_NEW_UNIX_GUID = "\x75\x78\x0B\x00\x01\x04\xE8\x03\x00\x00\x04\x00\x00\x00\x00";
// Unix file types
const S_IFIFO = 0010000; // named pipe (fifo)
const S_IFCHR = 0020000; // character special
const S_IFDIR = 0040000; // directory
const S_IFBLK = 0060000; // block special
const S_IFREG = 0100000; // regular
const S_IFLNK = 0120000; // symbolic link
const S_IFSOCK = 0140000; // socket
// setuid/setgid/sticky bits, the same as for chmod:
const S_ISUID = 0004000; // set user id on execution
const S_ISGID = 0002000; // set group id on execution
const S_ISTXT = 0001000; // sticky bit
// And of course, the other 12 bits are for the permissions, the same as for chmod:
// When addding these up, you can also just write the permissions as a simgle octal number
// ie. 0755. The leading 0 specifies octal notation.
const S_IRWXU = 0000700; // RWX mask for owner
const S_IRUSR = 0000400; // R for owner
const S_IWUSR = 0000200; // W for owner
const S_IXUSR = 0000100; // X for owner
const S_IRWXG = 0000070; // RWX mask for group
const S_IRGRP = 0000040; // R for group
const S_IWGRP = 0000020; // W for group
const S_IXGRP = 0000010; // X for group
const S_IRWXO = 0000007; // RWX mask for other
const S_IROTH = 0000004; // R for other
const S_IWOTH = 0000002; // W for other
const S_IXOTH = 0000001; // X for other
const S_ISVTX = 0001000; // save swapped text even after use
// Filetype, sticky and permissions are added up, and shifted 16 bits left BEFORE adding the DOS flags.
// DOS file type flags, we really only use the S_DOS_D flag.
const S_DOS_A = 0000040; // DOS flag for Archive
const S_DOS_D = 0000020; // DOS flag for Directory
const S_DOS_V = 0000010; // DOS flag for Volume
const S_DOS_S = 0000004; // DOS flag for System
const S_DOS_H = 0000002; // DOS flag for Hidden
const S_DOS_R = 0000001; // DOS flag for Read Only
private $zipMemoryThreshold = 1048576; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB)
private $zipData = null;
private $zipFile = null;
private $zipComment = null;
private $cdRec = array(); // central directory
private $offset = 0;
private $isFinalized = false;
private $addExtraField = true;
private $streamChunkSize = 65536;
private $streamFilePath = null;
private $streamTimestamp = null;
private $streamFileComment = null;
private $streamFile = null;
private $streamData = null;
private $streamFileLength = 0;
private $streamExtFileAttr = null;
/**
* A custom temporary folder, or a callable that returns a custom temporary file.
*
* @var string|callable
*/
public static $temp = null;
/**
* Constructor.
*
* @param boolean $useZipFile Write temp zip data to tempFile? Default FALSE
*/
function __construct( $useZipFile = false ) {
if ( $useZipFile ) {
$this->zipFile = tmpfile();
} else {
$this->zipData = '';
}
}
function __destruct() {
if ( is_resource( $this->zipFile ) ) {
fclose( $this->zipFile );
}
$this->zipData = null;
}
/**
* Set Zip archive comment.
*
* @param string $newComment New comment. NULL to clear.
* @return bool $success
*/
public function setComment( $newComment = null ) {
if ( $this->isFinalized ) {
return false;
}
$this->zipComment = $newComment;
return true;
}
/**
* Set zip file to write zip data to.
* This will cause all present and future data written to this class to be written to this file.
* This can be used at any time, even after the Zip Archive have been finalized. Any previous file will be closed.
* Warning: If the given file already exists, it will be overwritten.
*
* @param string $fileName
* @return bool $success
*/
public function setZipFile( $fileName ) {
if ( is_file( $fileName ) ) {
unlink( $fileName );
}
$fd = fopen( $fileName, 'x+b' );
if ( is_resource( $this->zipFile ) ) {
rewind( $this->zipFile );
while ( ! feof( $this->zipFile ) ) {
fwrite( $fd, fread( $this->zipFile, $this->streamChunkSize ) );
}
fclose( $this->zipFile );
} else {
fwrite( $fd, $this->zipData );
$this->zipData = null;
}
$this->zipFile = $fd;
return true;
}
/**
* Add an empty directory entry to the zip archive.
* Basically this is only used if an empty directory is added.
*
* @param string $directoryPath Directory Path and name to be added to the archive.
* @param int $timestamp (Optional) Timestamp for the added directory, if omitted or set to 0, the current time will be used.
* @param string $fileComment (Optional) Comment to be added to the archive for this directory. To use fileComment, timestamp must be given.
* @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this.
* @return bool $success
*/
public function addDirectory( $directoryPath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_DIR ) {
if ( $this->isFinalized ) {
return false;
}
$directoryPath = str_replace( '\\', '/', $directoryPath );
$directoryPath = rtrim( $directoryPath, '/' );
if ( strlen( $directoryPath ) > 0 ) {
$this->buildZipEntry( $directoryPath . '/', $fileComment, "\x00\x00", "\x00\x00", $timestamp, "\x00\x00\x00\x00", 0, 0, $extFileAttr );
return true;
}
return false;
}
/**
* Add a file to the archive at the specified location and file name.
*
* @param string $data File data.
* @param string $filePath Filepath and name to be used in the archive.
* @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used.
* @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given.
* @param bool $compress (Optional) Compress file, if set to FALSE the file will only be stored. Default TRUE.
* @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this.
* @return bool $success
*/
public function addFile( $data, $filePath, $timestamp = 0, $fileComment = null, $compress = true, $extFileAttr = self::EXT_FILE_ATTR_FILE ) {
if ( $this->isFinalized ) {
return false;
}
if ( is_resource( $data ) && get_resource_type( $data ) == 'stream' ) {
$this->addLargeFile( $data, $filePath, $timestamp, $fileComment, $extFileAttr );
return false;
}
$gzData = '';
$gzType = "\x08\x00"; // Compression type 8 = deflate
$gpFlags = "\x00\x00"; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression.
$dataLength = strlen( $data );
$fileCRC32 = pack( 'V', crc32( $data ) );
if ( $compress ) {
$gzTmp = gzcompress( $data );
$gzData = substr( substr( $gzTmp, 0, strlen( $gzTmp ) - 4 ), 2 ); // gzcompress adds a 2 byte header and 4 byte CRC we can't use.
// The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag.
$gzLength = strlen( $gzData );
} else {
$gzLength = $dataLength;
}
if ( $gzLength >= $dataLength ) {
$gzLength = $dataLength;
$gzData = $data;
$gzType = "\x00\x00"; // Compression type 0 = stored
$gpFlags = "\x00\x00"; // Compression type 0 = stored
}
if ( ! is_resource( $this->zipFile ) && ( $this->offset + $gzLength ) > $this->zipMemoryThreshold ) {
$this->zipflush();
}
$this->buildZipEntry( $filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr );
$this->zipwrite( $gzData );
return true;
}
/**
* Add a file to the archive at the specified location and file name.
*
* @param string $dataFile File name/path.
* @param string $filePath Filepath and name to be used in the archive.
* @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used.
* @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given.
* @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this.
* @return bool $success
*/
public function addLargeFile( $dataFile, $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE ) {
if ( $this->isFinalized ) {
return false;
}
if ( is_string( $dataFile ) && is_file( $dataFile ) ) {
$this->processFile( $dataFile, $filePath, $timestamp, $fileComment, $extFileAttr );
} elseif ( is_resource( $dataFile ) && get_resource_type( $dataFile ) == 'stream' ) {
$fh = $dataFile;
$this->openStream( $filePath, $timestamp, $fileComment, $extFileAttr );
while ( ! feof( $fh ) ) {
$this->addStreamData( fread( $fh, $this->streamChunkSize ) );
}
$this->closeStream();
}
return true;
}
/**
* Create a stream to be used for large entries.
*
* @param string $filePath Filepath and name to be used in the archive.
* @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used.
* @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given.
* @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this.
* @throws Exception Throws an exception in case of errors
* @return bool $success
*/
public function openStream( $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE ) {
if ( ! function_exists( 'sys_get_temp_dir' ) ) {
throw new Exception( 'Zip ' . self::VERSION . ' requires PHP version 5.2.1 or above if large files are used.' );
}
if ( $this->isFinalized ) {
return false;
}
$this->zipflush();
if ( strlen( $this->streamFilePath ) > 0 ) {
$this->closeStream();
}
$this->streamFile = self::getTemporaryFile();
$this->streamData = fopen( $this->streamFile, 'wb' );
$this->streamFilePath = $filePath;
$this->streamTimestamp = $timestamp;
$this->streamFileComment = $fileComment;
$this->streamFileLength = 0;
$this->streamExtFileAttr = $extFileAttr;
return true;
}
/**
* Add data to the open stream.
*
* @param string $data
* @throws Exception Throws an exception in case of errors
* @return mixed length in bytes added or FALSE if the archive is finalized or there are no open stream.
*/
public function addStreamData( $data ) {
if ( $this->isFinalized || strlen( $this->streamFilePath ) == 0 ) {
return false;
}
$length = fwrite( $this->streamData, $data, strlen( $data ) );
if ( $length != strlen( $data ) ) {
throw new Exception( 'File IO: Error writing; Length mismatch: Expected ' . strlen( $data ) . ' bytes, wrote ' . ( $length === false ? 'NONE!' : $length ) );
}
$this->streamFileLength += $length;
return $length;
}
/**
* Close the current stream.
*
* @return bool $success
*/
public function closeStream() {
if ( $this->isFinalized || strlen( $this->streamFilePath ) == 0 ) {
return false;
}
fflush( $this->streamData );
fclose( $this->streamData );
$this->processFile( $this->streamFile, $this->streamFilePath, $this->streamTimestamp, $this->streamFileComment, $this->streamExtFileAttr );
$this->streamData = null;
$this->streamFilePath = null;
$this->streamTimestamp = null;
$this->streamFileComment = null;
$this->streamFileLength = 0;
$this->streamExtFileAttr = null;
// Windows is a little slow at times, so a millisecond later, we can unlink this.
unlink( $this->streamFile );
$this->streamFile = null;
return true;
}
private function processFile( $dataFile, $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE ) {
if ( $this->isFinalized ) {
return false;
}
$tempzip = self::getTemporaryFile();
$zip = new ZipArchive();
if ( $zip->open( $tempzip ) === true ) {
$zip->addFile( $dataFile, 'file' );
$zip->close();
}
$file_handle = fopen( $tempzip, 'rb' );
$stats = fstat( $file_handle );
$eof = $stats['size'] - 72;
fseek( $file_handle, 6 );
$gpFlags = fread( $file_handle, 2 );
$gzType = fread( $file_handle, 2 );
fread( $file_handle, 4 );
$fileCRC32 = fread( $file_handle, 4 );
$v = unpack( 'Vval', fread( $file_handle, 4 ) );
$gzLength = $v['val'];
$v = unpack( 'Vval', fread( $file_handle, 4 ) );
$dataLength = $v['val'];
$this->buildZipEntry( $filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr );
fseek( $file_handle, 34 );
$pos = 34;
while ( ! feof( $file_handle ) && $pos < $eof ) {
$datalen = $this->streamChunkSize;
if ( $pos + $this->streamChunkSize > $eof ) {
$datalen = $eof - $pos;
}
$data = fread( $file_handle, $datalen );
$pos += $datalen;
$this->zipwrite( $data );
}
fclose( $file_handle );
unlink( $tempzip );
}
/**
* Close the archive.
* A closed archive can no longer have new files added to it.
*
* @return bool $success
*/
public function finalize() {
if ( ! $this->isFinalized ) {
if ( strlen( $this->streamFilePath ) > 0 ) {
$this->closeStream();
}
$cd = implode( '', $this->cdRec );
$cdRecSize = pack( 'v', sizeof( $this->cdRec ) );
$cdRec = $cd . self::ZIP_END_OF_CENTRAL_DIRECTORY
. $cdRecSize . $cdRecSize
. pack( 'VV', strlen( $cd ), $this->offset );
if ( ! empty( $this->zipComment ) ) {
$cdRec .= pack( 'v', strlen( $this->zipComment ) ) . $this->zipComment;
} else {
$cdRec .= "\x00\x00";
}
$this->zipwrite( $cdRec );
$this->isFinalized = true;
$this->cdRec = null;
return true;
}
return false;
}
/**
* Get the zip file contents
* If the zip haven't been finalized yet, this will cause it to become finalized
*
* @return zip data
*/
public function getZipData() {
if ( ! $this->isFinalized ) {
$this->finalize();
}
if ( ! is_resource( $this->zipFile ) ) {
return $this->zipData;
} else {
rewind( $this->zipFile );
$filestat = fstat( $this->zipFile );
return fread( $this->zipFile, $filestat['size'] );
}
}
/**
* Send the archive as a zip download
*
* @param String $fileName The name of the Zip archive, in ISO-8859-1 (or ASCII) encoding, ie. "archive.zip". Optional, defaults to NULL, which means that no ISO-8859-1 encoded file name will be specified.
* @param String $contentType Content mime type. Optional, defaults to "application/zip".
* @param String $utf8FileName The name of the Zip archive, in UTF-8 encoding. Optional, defaults to NULL, which means that no UTF-8 encoded file name will be specified.
* @param bool $inline Use Content-Disposition with "inline" instead of "attached". Optional, defaults to FALSE.
* @throws Exception Throws an exception in case of errors
* @return bool Always returns true (for backward compatibility).
*/
function sendZip( $fileName = null, $contentType = 'application/zip', $utf8FileName = null, $inline = false ) {
if ( ! $this->isFinalized ) {
$this->finalize();
}
$headerFile = null;
$headerLine = null;
if ( headers_sent( $headerFile, $headerLine ) ) {
throw new Exception( "Unable to send file '$fileName'. Headers have already been sent from '$headerFile' in line $headerLine" );
}
if ( ob_get_contents() !== false && strlen( ob_get_contents() ) ) {
throw new Exception( "Unable to send file '$fileName'. Output buffer contains the following text (typically warnings or errors):\n" . ob_get_contents() );
}
if ( @ini_get( 'zlib.output_compression' ) ) {
@ini_set( 'zlib.output_compression', 'Off' );
}
header( 'Pragma: public' );
header( 'Last-Modified: ' . @gmdate( 'D, d M Y H:i:s T' ) );
header( 'Expires: 0' );
header( 'Accept-Ranges: bytes' );
header( 'Connection: close' );
header( 'Content-Type: ' . $contentType );
$cd = 'Content-Disposition: ';
if ( $inline ) {
$cd .= 'inline';
} else {
$cd .= 'attached';
}
if ( $fileName ) {
$cd .= '; filename="' . $fileName . '"';
}
if ( $utf8FileName ) {
$cd .= "; filename*=UTF-8''" . rawurlencode( $utf8FileName );
}
header( $cd );
header( 'Content-Length: ' . $this->getArchiveSize() );
if ( ! is_resource( $this->zipFile ) ) {
echo $this->zipData;
} else {
rewind( $this->zipFile );
while ( ! feof( $this->zipFile ) ) {
echo fread( $this->zipFile, $this->streamChunkSize );
}
}
return true;
}
/**
* Return the current size of the archive
*
* @return $size Size of the archive
*/
public function getArchiveSize() {
if ( ! is_resource( $this->zipFile ) ) {
return strlen( $this->zipData );
}
$filestat = fstat( $this->zipFile );
return $filestat['size'];
}
/**
* Calculate the 2 byte dostime used in the zip entries.
*
* @param int $timestamp
* @return 2-byte encoded DOS Date
*/
private function getDosTime( $timestamp = 0 ) {
$timestamp = (int) $timestamp;
$oldTZ = @date_default_timezone_get();
date_default_timezone_set( 'UTC' );
$date = ( $timestamp == 0 ? getdate() : getdate( $timestamp ) );
date_default_timezone_set( $oldTZ );
if ( $date['year'] >= 1980 ) {
return pack(
'V',
( ( $date['mday'] + ( $date['mon'] << 5 ) + ( ( $date['year'] - 1980 ) << 9 ) ) << 16 ) |
( ( $date['seconds'] >> 1 ) + ( $date['minutes'] << 5 ) + ( $date['hours'] << 11 ) )
);
}
return "\x00\x00\x00\x00";
}
/**
* Build the Zip file structures
*
* @param string $filePath
* @param string $fileComment
* @param string $gpFlags
* @param string $gzType
* @param int $timestamp
* @param string $fileCRC32
* @param int $gzLength
* @param int $dataLength
* @param int $extFileAttr Use self::EXT_FILE_ATTR_FILE for files, self::EXT_FILE_ATTR_DIR for Directories.
*/
private function buildZipEntry( $filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr ) {
$filePath = str_replace( '\\', '/', $filePath );
$fileCommentLength = ( empty( $fileComment ) ? 0 : strlen( $fileComment ) );
$timestamp = (int) $timestamp;
$timestamp = ( $timestamp == 0 ? time() : $timestamp );
$dosTime = $this->getDosTime( $timestamp );
$tsPack = pack( 'V', $timestamp );
if ( ! isset( $gpFlags ) || strlen( $gpFlags ) != 2 ) { //@phpstan-ignore-line
$gpFlags = "\x00\x00";
}
$isFileUTF8 = mb_check_encoding( $filePath, 'UTF-8' ) && ! mb_check_encoding( $filePath, 'ASCII' );
$isCommentUTF8 = ! empty( $fileComment ) && mb_check_encoding( $fileComment, 'UTF-8' ) && ! mb_check_encoding( $fileComment, 'ASCII' );
$localExtraField = '';
$centralExtraField = '';
if ( $this->addExtraField ) {
$localExtraField .= "\x55\x54\x09\x00\x03" . $tsPack . $tsPack . self::EXTRA_FIELD_NEW_UNIX_GUID;
$centralExtraField .= "\x55\x54\x05\x00\x03" . $tsPack . self::EXTRA_FIELD_NEW_UNIX_GUID;
}
if ( $isFileUTF8 || $isCommentUTF8 ) {
$flag = 0;
$gpFlagsV = unpack( 'vflags', $gpFlags );
if ( isset( $gpFlagsV['flags'] ) ) {
$flag = $gpFlagsV['flags'];
}
$gpFlags = pack( 'v', $flag | ( 1 << 11 ) );
if ( $isFileUTF8 ) {
$utfPathExtraField = "\x75\x70"
. pack( 'v', ( 5 + strlen( $filePath ) ) )
. "\x01"
. pack( 'V', crc32( $filePath ) )
. $filePath;
$localExtraField .= $utfPathExtraField;
$centralExtraField .= $utfPathExtraField;
}
if ( $isCommentUTF8 ) {
$centralExtraField .= "\x75\x63" // utf8 encoded file comment extra field
. pack( 'v', ( 5 + strlen( $fileComment ) ) )
. "\x01"
. pack( 'V', crc32( $fileComment ) )
. $fileComment;
}
}
$header = $gpFlags . $gzType . $dosTime . $fileCRC32
. pack( 'VVv', $gzLength, $dataLength, strlen( $filePath ) ); // File name length
$zipEntry = self::ZIP_LOCAL_FILE_HEADER
. self::ATTR_VERSION_TO_EXTRACT
. $header
. pack( 'v', strlen( $localExtraField ) ) // Extra field length
. $filePath // FileName
. $localExtraField; // Extra fields
$this->zipwrite( $zipEntry );
$cdEntry = self::ZIP_CENTRAL_FILE_HEADER
. self::ATTR_MADE_BY_VERSION
. ( $dataLength === 0 ? "\x0A\x00" : self::ATTR_VERSION_TO_EXTRACT )
. $header
. pack( 'v', strlen( $centralExtraField ) ) // Extra field length
. pack( 'v', $fileCommentLength ) // File comment length
. "\x00\x00" // Disk number start
. "\x00\x00" // internal file attributes
. pack( 'V', $extFileAttr ) // External file attributes
. pack( 'V', $this->offset ) // Relative offset of local header
. $filePath // FileName
. $centralExtraField; // Extra fields
if ( ! empty( $fileComment ) ) {
$cdEntry .= $fileComment; // Comment
}
$this->cdRec[] = $cdEntry;
$this->offset += strlen( $zipEntry ) + $gzLength;
}
private function zipwrite( $data ) {
if ( ! is_resource( $this->zipFile ) ) {
$this->zipData .= $data;
} else {
fwrite( $this->zipFile, $data );
fflush( $this->zipFile );
}
}
private function zipflush() {
if ( ! is_resource( $this->zipFile ) ) {
$this->zipFile = tmpfile();
fwrite( $this->zipFile, $this->zipData );
$this->zipData = null;
}
}
/**
* Returns the path to a temporary file.
*
* @return string
*/
private static function getTemporaryFile() {
if ( is_callable( self::$temp ) ) {
$temporaryFile = @call_user_func( self::$temp );
if ( is_string( $temporaryFile ) && strlen( $temporaryFile ) && is_writable( $temporaryFile ) ) {
return $temporaryFile;
}
}
$temporaryDirectory = ( is_string( self::$temp ) && strlen( self::$temp ) ) ? self::$temp : sys_get_temp_dir();
return tempnam( $temporaryDirectory, 'Zip' );
}
}