first commit
This commit is contained in:
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
use WPML\TM\ATE\Review\ReviewStatus;
|
||||
|
||||
/**
|
||||
* 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 = (int) $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 = sprintf( ' s.status NOT IN ( %d, %d )', ICL_TM_NOT_TRANSLATED, ICL_TM_ATE_CANCELLED );
|
||||
|
||||
if ( $status ) {
|
||||
if ( $status === ICL_TM_NEEDS_REVIEW ) {
|
||||
$where .= $this->wpdb->prepare( ' AND (s.review_status = %s OR s.review_status = %s) ', ReviewStatus::EDITING, ReviewStatus::NEEDS_REVIEW );
|
||||
} else {
|
||||
$where .= $this->wpdb->prepare( ' AND s.status = %d AND (s.review_status IS NULL OR s.review_status = %s)', (int) $status, ReviewStatus::ACCEPTED );
|
||||
}
|
||||
}
|
||||
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';
|
||||
}
|
||||
|
||||
$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 ) {
|
||||
$review_status = 's.review_status IS NOT NULL';
|
||||
$translator_id_query_parts[] = ' j.translator_id = 0 OR j.translator_id IS NULL OR ' . $review_status;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?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;
|
||||
} else {
|
||||
$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( [
|
||||
'status' => apply_filters( 'wpml_tm_applied_job_status', ICL_TM_COMPLETE, $job, $new_post_id ),
|
||||
'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 );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class WPML_Translation_Job_Helper {
|
||||
|
||||
public function encode_field_data( $data ) {
|
||||
return null === $data ? '' : 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;
|
||||
}
|
||||
}
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Obj;
|
||||
use WPML\TM\API\Job\Map;
|
||||
|
||||
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;
|
||||
|
||||
$previousStatus = \WPML_TM_ICL_Translation_Status::makeByRid( $rid )->previous();
|
||||
if (
|
||||
$previousStatus->map( Obj::prop( 'status' ) )->getOrElse( null ) === (string) ICL_TM_ATE_CANCELLED
|
||||
) {
|
||||
$job_id = Map::fromRid( $rid );
|
||||
} else {
|
||||
|
||||
$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 ) {
|
||||
$job = wpml_tm_load_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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
require_once WPML_TM_PATH . '/inc/translation-jobs/jobs/wpml-translation-job.class.php';
|
||||
|
||||
use WPML\FP\Obj;
|
||||
|
||||
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' );
|
||||
$this->job_factory = $job_factory ?: wpml_tm_load_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'] = Obj::prop('job_id', $this->basic_data);
|
||||
$data_array['translation_id'] = Obj::prop('translation_id', $this->basic_data);
|
||||
$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();
|
||||
|
||||
$status = ! empty( $this->basic_data->translated ) ? ICL_TM_COMPLETE : Obj::prop('status', $this->basic_data);
|
||||
|
||||
return TranslationManagement::get_job_status_string( $status, Obj::prop( 'needs_update', $this->basic_data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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' );
|
||||
}
|
||||
}
|
||||
@@ -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 ) ) : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Obj;
|
||||
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;
|
||||
|
||||
$job_id = $this->get_id();
|
||||
$post_id = $this->get_resultant_element_id();
|
||||
$data['complete'] = 1;
|
||||
$data['job_id'] = $job_id;
|
||||
$job = wpml_tm_load_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
|
||||
{$wpdb->terms}.name,
|
||||
{$wpdb->term_taxonomy}.description,
|
||||
SUBSTR(translate.field_type, 3) original_term_id,
|
||||
{$wpdb->terms}.term_id translated_term_id
|
||||
FROM {$translate_table} translate
|
||||
INNER JOIN {$translations_table} original_translation ON original_translation.element_id = SUBSTR(translate.field_type, 3)
|
||||
INNER JOIN {$translations_table} translation ON translation.trid = original_translation.trid AND translation.language_code = %s
|
||||
INNER JOIN {$wpdb->term_taxonomy} ON {$wpdb->term_taxonomy}.term_taxonomy_id = translation.element_id AND CONCAT('tax_', {$wpdb->term_taxonomy}.taxonomy) = translation.element_type
|
||||
INNER JOIN {$wpdb->terms} ON {$wpdb->terms}.term_id = {$wpdb->term_taxonomy}.term_id
|
||||
WHERE translate.job_id = %d AND translate.field_type LIKE 't\_%'
|
||||
", $this->get_language_code(), $job_id );
|
||||
|
||||
$term_values = $wpdb->get_results( $get_target_terms_for_job_query );
|
||||
foreach ( $term_values as $term ) {
|
||||
if ( $delete ) {
|
||||
$conditions = [
|
||||
"field_type LIKE 'tfield-%-{$term->original_term_id}'", // Term fields
|
||||
"field_type LIKE 'tfield-%-{$term->original_term_id}\_%'", // Term fields as array
|
||||
"field_type = 't_{$term->original_term_id}'",
|
||||
"field_type = 'tdesc_{$term->original_term_id}'",
|
||||
];
|
||||
$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->original_term_id, 'job_id' => $job_id ]
|
||||
);
|
||||
$wpdb->update(
|
||||
$translate_table,
|
||||
[ 'field_data_translated' => base64_encode( $term->description ), 'field_finished' => 1 ],
|
||||
[ 'field_type' => 'tdesc_' . $term->original_term_id, 'job_id' => $job_id ]
|
||||
);
|
||||
|
||||
$meta_values = $wpdb->get_results( "SELECT meta_key, meta_value FROM {$wpdb->termmeta} WHERE term_id = {$term->translated_term_id}" );
|
||||
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->original_term_id ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function filter_is_translator_args( array $args ) {
|
||||
return Obj::assoc( 'post_id', $this->get_original_element_id(), $args );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Obj;
|
||||
|
||||
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_review_status() {
|
||||
$this->maybe_load_basic_data();
|
||||
|
||||
return $this->basic_data->review_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,
|
||||
$this->filter_is_translator_args( [
|
||||
'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 array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function filter_is_translator_args( array $args ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 ( Obj::prop( 'translation_service', $this->basic_data ) == TranslationProxy::get_current_service_id() ) {
|
||||
$this->basic_data->translator_name = TranslationProxy_Translator::get_translator_name( Obj::prop('translator_id', $this->basic_data) );
|
||||
} 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'] = Obj::prop('batch_id', $job_data);
|
||||
$data_array['source_language_code'] = Obj::prop('source_language_code', $this->basic_data);
|
||||
$data_array['language_code'] = Obj::prop('language_code', $this->basic_data);
|
||||
$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 ( Obj::prop( 'translation_service', $job ) !== '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'
|
||||
)
|
||||
. '" /> ';
|
||||
$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( ' » ' )
|
||||
. $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
|
||||
*/
|
||||
public function get_basic_data_property( $name ) {
|
||||
$this->maybe_load_basic_data();
|
||||
|
||||
return Obj::prop( $name, $this->basic_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set_basic_data_property( $name, $value ) {
|
||||
$this->basic_data = Obj::assoc( $name, $value, $this->basic_data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
<?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 ? TranslationProxy_Batch::get_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( TranslationProxy_Batch::get_generic_batch_name() );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user