first commit

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

View File

@@ -0,0 +1,237 @@
<?php
namespace WPML\TM\Menu\TranslationBasket;
use WPML\FP\Obj;
use WPML\Notices\DismissNotices;
use WPML\TM\API\ATE\Account;
use WPML\WP\OptionManager;
class Strings {
const ATE_AUTOMATIC_TRANSLATION_SUGGESTION = 'wpml-ate-automatic-translation-suggestion';
/** @var Utility */
private $utility;
/** @var DismissNotices */
private $dismissNotices;
/**
* @param Utility $utility
* @param DismissNotices $dismissNotices
*/
public function __construct( Utility $utility, DismissNotices $dismissNotices ) {
$this->utility = $utility;
$this->dismissNotices = $dismissNotices;
}
public function getAll() {
$isCurrentUserOnlyTranslator = $this->utility->isTheOnlyAvailableTranslator();
return [
'jobs_sent_to_local_translator' => $this->jobsSentToLocalTranslator(),
'jobs_emails_local_did_not_sent' => $this->emailNotSentError(),
'jobs_committed' => $isCurrentUserOnlyTranslator ? $this->jobsSentToCurrentUserWhoIsTheOnlyTranslator() : $this->jobsSentDefaultMessage(),
'jobs_committing' => __( 'Working...', 'wpml-translation-management' ),
'error_occurred' => __( 'An error occurred:', 'wpml-translation-management' ),
'error_not_allowed' => __(
'You are not allowed to run this action.',
'wpml-translation-management'
),
'batch' => __( 'Batch', 'wpml-translation-management' ),
'error_no_translators' => __( 'No selected translators!', 'wpml-translation-management' ),
'rollbacks' => __( 'Rollback jobs...', 'wpml-translation-management' ),
'rolled' => __( 'Batch rolled back', 'wpml-translation-management' ),
'errors' => __( 'Errors:', 'wpml-translation-management' ),
'sending_batch' => $isCurrentUserOnlyTranslator ?
__( 'Preparing your content for translation', 'wpml-translation-management' )
: __( 'Sending your jobs to translation', 'wpml-translation-management' ),
'sending_batch_to_ts' => __(
'Sending your jobs to professional translation',
'wpml-translation-management'
),
];
}
/**
* @return string
*/
public function duplicatePostTranslationWarning() {
$message = esc_html_x(
'You are about to translate duplicated posts.',
'1/2 Confirm to disconnect duplicates',
'wpml-translation-management'
);
$message .= "\n";
$message .= esc_html_x(
'These items will be automatically disconnected from originals, so translation is not lost when you update the originals.',
'2/2 Confirm to disconnect duplicates',
'wpml-translation-management'
);
return $message;
}
/**
* @return string
*/
public function jobsSentToLocalTranslator() {
$translation_dashboard_url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php' );
$translation_dashboard_text = esc_html__( 'Translation Dashboard', 'wpml-translation-management' );
$translation_dashboard_link = '<a href="' . esc_url( $translation_dashboard_url ) . '">' . $translation_dashboard_text . '</a>';
$translation_notifications_url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . \WPML_Translation_Management::PAGE_SLUG_SETTINGS . '&sm=notifications' );
$translation_notifications_text = esc_html__(
'WPML → Settings → Translation notifications',
'wpml-translation-management'
);
$translation_notifications_link = '<a href="' . esc_url( $translation_notifications_url ) . '">' . $translation_notifications_text . '</a>';
$template = '
<p>%1$s</p>
<ul>
<li>%2$s</li>
<li>%3$s</li>
<li>%4$s</li>
<li>%5$s</li>
</ul>
';
return sprintf(
$template,
esc_html__( 'All done. What happens next?', 'wpml-translation-management' ),
esc_html__(
'WPML sent emails to the translators, telling them about the new work from you.',
'wpml-translation-management'
),
sprintf(
esc_html__(
'Your translators should log-in to their accounts in this site and go to %1$sWPML → Translations%2$s. There, they will see the jobs that are waiting for them.',
'wpml-translation-management'
),
'<strong>',
'</strong>'
),
sprintf(
esc_html__(
'You can always follow the progress of translation in the %1$s. For a more detailed view and to cancel jobs, visit the %2$s list.',
'wpml-translation-management'
),
$translation_dashboard_link,
$this->getJobsLink()
),
sprintf(
esc_html__(
'You can control email notifications to translators and yourself in %s.',
'wpml-translation-management'
),
$translation_notifications_link
)
);
}
/**
* @return string
*/
public function jobsSentToCurrentUserWhoIsTheOnlyTranslator() {
return sprintf(
'<p>%s</p><p>%s <a href="%s"><strong>%s</strong></a></p>',
__( 'Ready!', 'wpml-translation-management' ),
/* translators: This text is followed by 'Translation Queue'. eg To translate those jobs, go to the Translation Queue */
__( 'To translate those jobs, go to ', 'wpml-translation-management' ),
admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php' ),
__( 'WPML → Translations', 'wpml-translation-management' )
) . $this->automaticTranslationTip();
}
/**
* @return string
*/
private function automaticTranslationTip() {
if (
$this->dismissNotices->isDismissed( self::ATE_AUTOMATIC_TRANSLATION_SUGGESTION ) ||
! \WPML_TM_ATE_Status::is_enabled_and_activated()
|| Account::isAbleToTranslateAutomatically()
) {
return '';
}
$template = "
<div id='wpml-tm-basket-automatic-translations-suggestion' >
<h5>%s</h5>
<p>%s</p>
<p>%s <span>%s</span></p>
</div>
";
return sprintf(
$template,
esc_html__( 'Want to translate your content automatically?', 'wpml-translation-management' ),
sprintf(
esc_html__(
'Go to %s and click the %sAutomatic Translation%s tab to create an account and start each month with 2,000 free translation credits!',
'wpml-translation-management'
),
'<strong>' . $this->getTMLink() . '</strong>',
'<strong>',
'</strong>'
),
$this->dismissNotices->renderCheckbox( self::ATE_AUTOMATIC_TRANSLATION_SUGGESTION ),
__( 'Dont offer this again', 'wpml-translation-management' )
);
}
/**
* @return string
*/
public function jobsSentDefaultMessage() {
$message = '<p>' . esc_html__( 'Ready!', 'wpml-translation-management' ) . '</p>';
$message .= '<p>';
$message .= sprintf(
esc_html__(
'You can check the status of these jobs in %s.',
'wpml-translation-management'
),
$this->getJobsLink()
);
$message .= '</p>';
return $message;
}
/**
* @return string
*/
private function getJobsLink() {
$url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=jobs' );
$text = esc_html__( 'WPML → Translation Jobs', 'wpml-translation-management' );
$link = '<a href="' . esc_url( $url ) . '">' . $text . '</a>';
return $link;
}
private function getTMLink() {
$url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php' );
$text = esc_html__( 'WPML → Translation Management', 'wpml-translation-management' );
$link = '<a href="' . esc_url( $url ) . '">' . $text . '</a>';
return $link;
}
/**
* @return string
*/
public function emailNotSentError() {
return '<li><strong>' . esc_html__(
'WPML could not send notification emails to the translators, telling them about the new work from you.',
'wpml-translation-management'
) . '</strong></li>';
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace WPML\TM\Menu\TranslationBasket;
class Utility {
/** @var \SitePress */
private $sitepress;
/** @var \WPML_Translator_Records */
private $translatorRecords;
/**
* @param \SitePress $sitepress
* @param \WPML_Translator_Records $translatorRecords
*/
public function __construct( \SitePress $sitepress, \WPML_Translator_Records $translatorRecords ) {
$this->sitepress = $sitepress;
$this->translatorRecords = $translatorRecords;
}
/**
* @return array
*/
public function getTargetLanguages() {
$basketLanguages = \TranslationProxy_Basket::get_target_languages();
$targetLanguages = [];
if ( $basketLanguages ) {
$notBasketLanguage = function ( $lang ) use ( $basketLanguages ) {
return ! in_array( $lang['code'], $basketLanguages, true );
};
$isBasketSourceLanguage = function ( $lang ) {
return \TranslationProxy_Basket::get_source_language() === $lang['code'];
};
$addFlag = function ( $lang ) {
$lang['flag'] = $this->sitepress->get_flag_img( $lang['code'] );
return $lang;
};
$targetLanguages = wpml_collect( $this->sitepress->get_active_languages() )
->reject( $notBasketLanguage )
->reject( $isBasketSourceLanguage )
->map( $addFlag )
->toArray();
}
return $targetLanguages;
}
/**
* @param $targetLanguages
*
* @return bool
*/
public function isTheOnlyAvailableTranslatorForTargetLanguages( $targetLanguages ) {
if ( \TranslationProxy::is_current_service_active_and_authenticated() ) {
return false;
}
$translators = $this->translatorRecords->get_users_with_languages(
\TranslationProxy_Basket::get_source_language(),
array_keys( $targetLanguages ),
false
);
return count( $translators ) === 1 && $translators[0]->ID === get_current_user_id();
}
/**
* @return bool
*/
public function isTheOnlyAvailableTranslator() {
return $this->isTheOnlyAvailableTranslatorForTargetLanguages( $this->getTargetLanguages() );
}
}

View File

@@ -0,0 +1,109 @@
<?php
if ( ! defined( 'WPINC' ) ) {
die;
}
class WPML_TM_Translate_Independently {
/** @var TranslationManagement $translation_management */
private $translation_management;
/** @var WPML_Translation_Basket $translation_basket */
private $translation_basket;
/** @var SitePress $sitepress */
private $sitepress;
public function __construct(
TranslationManagement $translation_management,
WPML_Translation_Basket $translation_basket,
SitePress $sitepress
) {
$this->translation_management = $translation_management;
$this->translation_basket = $translation_basket;
$this->sitepress = $sitepress;
}
/**
* Init all plugin actions.
*/
public function init() {
add_action( 'wp_ajax_icl_disconnect_posts', array( $this, 'ajax_disconnect_duplicates' ) );
add_action( 'admin_footer', array( $this, 'add_hidden_field' ) );
}
/**
* Add hidden fields to TM basket.
* #icl_duplicate_post_in_basket with list of duplicated ids in basket target languages.
* #icl_disconnect_nonce nonce for AJAX call.
*/
public function add_hidden_field() {
$basket = $this->translation_basket->get_basket( true );
if ( ! isset( $basket['post'] ) ) {
return;
}
$posts_ids_to_disconnect = $this->duplicates_to_disconnect( $basket['post'] );
if ( $posts_ids_to_disconnect ) :
?>
<input type="hidden" value="<?php echo implode( ',', $posts_ids_to_disconnect ); ?>" id="icl_duplicate_post_in_basket">
<input type="hidden" value="<?php echo wp_create_nonce( 'icl_disconnect_duplicates' ); ?>" id="icl_disconnect_nonce">
<?php
endif;
}
/**
* @param array $basket_posts
*
* @return array
*/
private function duplicates_to_disconnect( $basket_posts ) {
/** @var SitePress $sitepress */
global $sitepress;
$posts_to_disconnect = array();
foreach ( $basket_posts as $from_post => $data ) {
$target_langs = array_keys( $data['to_langs'] );
$element_type = 'post_' . get_post_type( $from_post );
$trid = $sitepress->get_element_trid( $from_post, $element_type );
$translations = $sitepress->get_element_translations( $trid, $element_type );
foreach ( $translations as $translation ) {
if ( ! in_array( $translation->language_code, $target_langs, true ) ) {
continue;
}
$is_duplicate = get_post_meta( $translation->element_id, '_icl_lang_duplicate_of', true );
if ( $is_duplicate ) {
$posts_to_disconnect[] = (int) $translation->element_id;
}
}
}
return $posts_to_disconnect;
}
/**
* AJAX action to bulk disconnect posts before sending them to translation.
*/
public function ajax_disconnect_duplicates() {
// Check nonce.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'icl_disconnect_duplicates' ) ) {
wp_send_json_error( esc_html__( 'Failed to disconnect posts', 'wpml-translation-management' ) );
return;
}
// Get post basket post ids.
$post_ids = isset( $_POST['posts'] ) ? explode( ',', $_POST['posts'] ) : array();
if ( empty( $post_ids ) ) {
wp_send_json_error( esc_html__( 'No duplicate posts found to disconnect.', 'wpml-translation-management' ) );
return;
}
$post_ids = array_map( 'intval', $post_ids );
array_walk( $post_ids, array( $this->translation_management, 'reset_duplicate_flag' ) );
wp_send_json_success( esc_html__( 'Successfully disconnected posts', 'wpml-translation-management' ) );
}
}

View File

@@ -0,0 +1,444 @@
<?php
use WPML\API\Sanitize;
use WPML\TM\Menu\TranslationBasket\Strings;
use function WPML\Container\make;
require_once WPML_TM_PATH . '/menu/sitepress-table.class.php';
class SitePress_Table_Basket extends SitePress_Table {
public static function enqueue_js() {
/** @var WP_Locale $wp_locale */
global $wp_locale;
wp_enqueue_script(
'wpml-tm-translation-basket-and-options',
WPML_TM_URL . '/res/js/translation-basket-and-options.js',
array( 'wpml-tm-scripts', 'jquery-ui-progressbar', 'jquery-ui-datepicker', 'wpml-tooltip', 'wpml-tm-progressbar' ),
WPML_TM_VERSION
);
wp_localize_script(
'wpml-tm-translation-basket-and-options',
'wpml_tm_translation_basket_and_options',
array(
'day_names' => array_values( $wp_locale->weekday ),
'day_initials' => array_values( $wp_locale->weekday_initial ),
'month_names' => array_values( $wp_locale->month ),
)
);
wp_enqueue_style(
'wpml-tm-jquery-ui-datepicker',
WPML_TM_URL . '/res/css/jquery-ui/datepicker.css',
array( 'wpml-tooltip' ),
WPML_TM_VERSION
);
/** @var Strings $strings */
$strings = make( Strings::class );
$tm_basket_data = array(
'nonce' => array(),
'strings' => $strings->getAll(),
'tmi_message' => $strings->duplicatePostTranslationWarning(),
);
$tm_basket_data = apply_filters( 'translation_basket_and_options_js_data', $tm_basket_data );
wp_localize_script(
'wpml-tm-translation-basket-and-options',
'tm_basket_data',
$tm_basket_data
);
wp_enqueue_script( 'wpml-tm-translation-basket-and-options' );
}
function prepare_items() {
$this->action_callback();
$this->get_data();
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
if ( $this->items ) {
usort( $this->items, array( &$this, 'usort_reorder' ) );
}
}
function get_columns() {
$columns = array(
'title' => __( 'Title', 'wpml-translation-management' ),
'type' => __( 'Type', 'wpml-translation-management' ),
'status' => __( 'Status', 'wpml-translation-management' ),
'languages' => __( 'Languages', 'wpml-translation-management' ),
'words' => __( 'Words to translate', 'wpml-translation-management' ),
'delete' => '',
);
return $columns;
}
/**
* @param object $item
* @param string $column_name
*
* @return mixed|string
*/
function column_default( $item, $column_name ) {
/**
* WP base class is expecting an object, but we are using an array in our implementation.
* Casting $item into an array prevents IDE warnings.
*/
$item = (array) $item;
switch ( $column_name ) {
case 'title':
case 'notes':
return $item[ $column_name ];
break;
case 'type':
return $this->get_post_type_label( $item[ $column_name ], $item );
break;
case 'status':
return $this->get_post_status_label( $item[ $column_name ] );
break;
case 'words':
return $item[ $column_name ];
break;
case 'languages':
$target_languages_data = $item['target_languages'];
$source_language_data = $item['source_language'];
$target_languages = explode( ',', $target_languages_data );
$languages = sprintf(
__( '%1$s to %2$s', 'wpml-translation-management' ),
$source_language_data,
$target_languages_data
);
if ( count( $target_languages ) > 1 ) {
$last_target_language = $target_languages[ count( $target_languages ) - 1 ];
$first_target_languages = array_slice( $target_languages, 0, count( $target_languages ) - 1 );
$languages = sprintf(
__( '%1$s to %2$s and %3$s', 'wpml-translation-management' ),
$source_language_data,
implode( ',', $first_target_languages ),
$last_target_language
);
}
return $languages;
break;
default:
return print_r( $item, true ); // Show the whole array for troubleshooting purposes
}
}
function column_title( $item ) {
return esc_html( $item['title'] );
}
/**
* @param array $item
*
* @return string
*/
function column_delete( $item ) {
$qs = $_GET;
$qs['page'] = $_REQUEST['page'];
$qs['action'] = 'delete';
$qs['id'] = $item['ID'];
$qs['item_type'] = $item['item_type'];
$new_qs = esc_attr( http_build_query( $qs ) );
return sprintf(
'<a href="?%s" title="%s" class="otgs-ico-cancel wpml-tm-delete"></a>',
$new_qs,
__( 'Remove from Translation Basket', 'wpml-translation-management' )
);
}
function no_items() {
_e( 'The basket is empty', 'wpml-translation-management' );
}
function get_sortable_columns() {
$sortable_columns = array(
'title' => array( 'title', true ),
'type' => array( 'type', false ),
'status' => array( 'status', false ),
'languages' => array( 'languages', false ),
'words' => array( 'words', false ),
);
return $sortable_columns;
}
/**
* @param $post_id
* @param $data
* @param $item_type
*/
private function build_basket_item( $post_id, $data, $item_type ) {
$this->items[ $item_type . '|' . $post_id ]['ID'] = $post_id;
$this->items[ $item_type . '|' . $post_id ]['title'] = $data['post_title'];
$this->items[ $item_type . '|' . $post_id ]['notes'] = isset( $data['post_notes'] ) ? $data['post_notes'] : '';
$this->items[ $item_type . '|' . $post_id ]['type'] = $data['post_type'];
$this->items[ $item_type . '|' . $post_id ]['status'] = isset( $data['post_status'] ) ? $data['post_status'] : '';
$this->items[ $item_type . '|' . $post_id ]['source_language'] = $data['from_lang_string'];
$this->items[ $item_type . '|' . $post_id ]['target_languages'] = $data['to_langs_string'];
$this->items[ $item_type . '|' . $post_id ]['item_type'] = $item_type;
$this->items[ $item_type . '|' . $post_id ]['words'] = $this->get_words_count( $post_id, $item_type, count( $data['to_langs'] ) );
$this->items[ $item_type . '|' . $post_id ]['auto_added'] = isset( $data['auto_added'] ) && $data['auto_added'];
}
/**
* @param $element_id
* @param $element_type
* @param $languages_count
*/
private function get_words_count( $element_id, $element_type, $languages_count ) {
$records_factory = new WPML_TM_Word_Count_Records_Factory();
$single_process_factory = new WPML_TM_Word_Count_Single_Process_Factory();
$st_package_factory = class_exists( 'WPML_ST_Package_Factory' ) ? new WPML_ST_Package_Factory() : null;
$element_provider = new WPML_TM_Translatable_Element_Provider( $records_factory->create(), $single_process_factory->create(), $st_package_factory );
$translatable_element = $element_provider->get_from_type( $element_type, $element_id );
$count = null !== $translatable_element ? $translatable_element->get_words_count() : 0;
return $count * $languages_count;
}
/**
* @param $cart_items
* @param $item_type
*/
private function build_basket_items( $cart_items, $item_type ) {
if ( $cart_items ) {
foreach ( $cart_items as $post_id => $data ) {
$this->build_basket_item( $post_id, $data, $item_type );
}
}
}
private function usort_reorder( $a, $b ) {
$sortable_columns_keys = array_keys( $this->get_sortable_columns() );
$first_column_key = $sortable_columns_keys[0];
// If no sort, default to first column
$orderby = ( ! empty( $_GET['orderby'] ) ) ? $_GET['orderby'] : $first_column_key;
// If no order, default to asc
$order = ( ! empty( $_GET['order'] ) ) ? $_GET['order'] : 'asc';
// Determine sort order
$result = strcmp( $a[ $orderby ], $b[ $orderby ] );
// Send final sort direction to usort
return ( $order === 'asc' ) ? $result : - $result;
}
/**
* @param $post_status
*
* @return string
*/
private function get_post_status_label( $post_status ) {
static $post_status_object;
// Get and store the post status "as verb", if available
if ( ! isset( $post_status_object[ $post_status ] ) ) {
$post_status_object[ $post_status ] = get_post_status_object( $post_status );
}
$post_status_label = ucfirst( $post_status );
if ( isset( $post_status_object[ $post_status ] ) ) {
$post_status_object_item = $post_status_object[ $post_status ];
if ( isset( $post_status_object_item->label ) && $post_status_object_item->label ) {
$post_status_label = $post_status_object_item->label;
}
}
return $post_status_label;
}
/**
* @param string $post_type
* @param array $item
*
* @return string
*/
private function get_post_type_label( $post_type, array $item ) {
static $post_type_object;
if ( ! isset( $post_type_object[ $post_type ] ) ) {
$post_type_object[ $post_type ] = get_post_type_object( $post_type );
}
$post_type_label = ucfirst( $post_type );
if ( isset( $post_type_object[ $post_type ] ) ) {
$post_type_object_item = $post_type_object[ $post_type ];
if ( isset( $post_type_object_item->labels->singular_name ) && $post_type_object_item->labels->singular_name ) {
$post_type_label = $post_type_object_item->labels->singular_name;
}
}
if ( isset( $item['auto_added'] ) && $item['auto_added'] ) {
if ( 'wp_block' === $post_type ) {
$post_type_label = __( 'Reusable Block', 'wpml-translation-management' );
}
$tooltip = '<a class="js-otgs-popover-tooltip otgs-ico-help"
data-tippy-zindex="999999" tabindex="0"
title="' . esc_attr__( "WPML added this item because it's linked to another in the basket. If you wish, you can manually remove it.", 'wpml-translation-management' ) . '"
</a>';
$post_type_label .= '<br><small>'
. sprintf( esc_html__( 'automatically added %s', 'wpml-translation-management' ), $tooltip ) .
'</small>';
}
return $post_type_label;
}
private function action_callback() {
if ( isset( $_GET['clear_basket'] ) && isset( $_GET['clear_basket_nonce'] ) && $_GET['clear_basket'] == 1 ) {
if ( wp_verify_nonce( $_GET['clear_basket_nonce'], 'clear_basket' ) ) {
TranslationProxy_Basket::delete_all_items_from_basket();
}
}
if ( $this->current_action() == 'delete_selected' ) {
// Delete basket items from post action
TranslationProxy_Basket::delete_items_from_basket( $_POST['icl_translation_basket_delete'] );
} elseif ( $this->current_action() == 'delete' && isset( $_GET['id'] ) && isset( $_GET['item_type'] ) ) {
// Delete basket item from post action
$delete_basket_item_id = filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
$delete_basket_item_type = Sanitize::stringProp( 'item_type', $_GET );
if ( $delete_basket_item_id && $delete_basket_item_type ) {
TranslationProxy_Basket::delete_item_from_basket(
$delete_basket_item_id,
$delete_basket_item_type,
true
);
}
}
}
private function get_data() {
global $iclTranslationManagement;
$translation_jobs_basket = TranslationProxy_Basket::get_basket();
$basket_items_types = TranslationProxy_Basket::get_basket_items_types();
foreach ( $basket_items_types as $item_type_name => $item_type ) {
$translation_jobs_cart[ $item_type_name ] = false;
if ( $item_type == 'core' ) {
if ( ! empty( $translation_jobs_basket[ $item_type_name ] ) ) {
$basket_type_items = $translation_jobs_basket[ $item_type_name ];
if ( $item_type_name == 'string' ) {
$translation_jobs_cart[ $item_type_name ] = $iclTranslationManagement->get_translation_jobs_basket_strings( $basket_type_items );
} else {
$translation_jobs_cart[ $item_type_name ] = $iclTranslationManagement->get_translation_jobs_basket_posts( $basket_type_items );
}
$this->build_basket_items( $translation_jobs_cart[ $item_type_name ], $item_type_name );
}
} elseif ( $item_type == 'custom' ) {
$translation_jobs_cart_externals = apply_filters(
'wpml_tm_translation_jobs_basket',
array(),
$translation_jobs_basket,
$item_type_name
);
$this->build_basket_items( $translation_jobs_cart_externals, $item_type_name );
}
}
}
function display_tablenav( $which ) {
return;
}
function display() {
parent::display();
if ( TranslationProxy_Basket::get_basket_items_count() ) {
$clear_basket_nonce = wp_create_nonce( 'clear_basket' );
?>
<a href="admin.php?page=<?php echo WPML_TM_FOLDER; ?>/menu/main.php&sm=basket&clear_basket=1&clear_basket_nonce=<?php echo $clear_basket_nonce; ?>"
class="button-secondary wpml-tm-clear-basket-button" name="clear-basket">
<i class="otgs-ico-cancel"></i>
<?php _e( 'Clear Basket', 'wpml-translation-management' ); ?>
</a>
<?php
}
$this->display_total_word_count_info();
}
private function display_total_word_count_info() {
$grand_total_words_count = 0;
if ( $this->items ) {
foreach ( $this->items as $item ) {
$grand_total_words_count += $item['words'];
}
}
$service = TranslationProxy::get_current_service();
$tm_ate = new WPML_TM_ATE();
$is_ate_enabled = $tm_ate->is_translation_method_ate_enabled();
$display_message = '';
$ate_doc_link = '';
$ate_name = '';
if ( $is_ate_enabled ) {
$ate_name = __( 'Advanced Translation Editor', 'wpml-translation-management' );
$ate_doc_link = 'https://wpml.org/documentation/translating-your-contents/advanced-translation-editor/';
}
if ( $service && $is_ate_enabled ) {
$service_message = __( '%1$s and the %2$s use a translation memory, which can reduce the number of words you need to translate.', 'wpml-translation-management' );
$display_message = sprintf(
$service_message,
'<a class="wpml-external-link" href="' . $service->doc_url . '" target="blank">' . $service->name . '</a>',
'<a class="wpml-external-link" href="' . $ate_doc_link . '" target="blank">' . $ate_name . '</a>'
);
} elseif ( $service ) {
$service_message = __( '%s uses a translation memory, which can reduce the number of words you need to translate.', 'wpml-translation-management' );
$display_message = sprintf( $service_message, '<a class="wpml-external-link" href="' . $service->doc_url . '" target="blank">' . $service->name . '</a>' );
} elseif ( $is_ate_enabled ) {
$service_message = __( 'The %s uses a translation memory, which can reduce the number of words you need to translate.', 'wpml-translation-management' );
$display_message = sprintf( $service_message, '<a class="wpml-external-link" href="' . $ate_doc_link . '" target="blank">' . $ate_name . '</a>' );
}
if ( $service ) {
$words_count_url = 'https://wpml.org/documentation/translating-your-contents/getting-a-word-count-of-your-wordpress-site/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm#differences-in-word-count-between-wpml-and-translation-service-providers';
$words_count_text = '<a class="wpml-external-link" href="' . $words_count_url . '" target="_blank">';
// translators: "%s" is replaced by the name of a translation service.
$words_count_text .= sprintf( __( '%s may produce a different word count', 'wpml-translation-management' ), $service->name );
$words_count_text .= '</a>';
// translators: "%s" is replaced by the the previous string.
$words_count_message = sprintf( __( 'The number of words WPML will send to translation (%s):', 'wpml-translation-management' ), $words_count_text );
} else {
$words_count_message = __( 'The number of words WPML will send to translation:', 'wpml-translation-management' );
}
?>
<div class="words-count-summary">
<p class="words-count-summary-info">
<strong><?php echo $words_count_message; ?></strong>
<span class="words-count-total"><?php echo $grand_total_words_count; ?></span>
</p>
<?php if ( $display_message ) { ?>
<p class="words-count-summary-ts">
<?php echo $display_message; ?>
</p>
<?php } ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,272 @@
<?php
class WPML_Basket_Tab_Ajax {
/** @var TranslationProxy_Project $project */
private $project;
/** @var WPML_Translation_Proxy_Basket_Networking $networking */
private $networking;
/** @var WPML_Translation_Basket $basket */
private $basket;
/**
* @param TranslationProxy_Project $project
* @param WPML_Translation_Proxy_Basket_Networking $networking
* @param WPML_Translation_Basket $basket
*/
function __construct( $project, $networking, $basket ) {
$this->project = $project;
$this->networking = $networking;
$this->basket = $basket;
}
function init() {
$request = filter_input( INPUT_POST, 'action' );
$nonce = filter_input( INPUT_POST, '_icl_nonce' );
if ( $request && $nonce && wp_verify_nonce( $nonce, $request . '_nonce' ) ) {
add_action( 'wp_ajax_send_basket_items', [ $this, 'begin_basket_commit' ] );
add_action( 'wp_ajax_send_basket_item', [ $this, 'send_basket_chunk' ] );
add_action( 'wp_ajax_send_basket_commit', [ $this, 'send_basket_commit' ] );
add_action( 'wp_ajax_check_basket_name', [ $this, 'check_basket_name' ] );
add_action( 'wp_ajax_rollback_basket', [ $this, 'rollback_basket' ] );
}
}
/**
* Handler for the ajax call to commit a chunk of the items in a batch provided in the request.
*
* @uses \WPML_Translation_Proxy_Basket_Networking::commit_basket_chunk
*/
function send_basket_chunk() {
$batch_factory = new WPML_TM_Translation_Batch_Factory( $this->basket );
try {
$batch = $batch_factory->create( $_POST );
list( $has_error, $data, $error ) = $this->networking->commit_basket_chunk( $batch );
} catch ( InvalidArgumentException $e ) {
$has_error = true;
$data = $e->getMessage();
}
if ( $has_error ) {
wp_send_json_error( $data );
} else {
wp_send_json_success( $data );
}
}
/**
* Ajax handler for the first ajax request call in the basket commit workflow, responding with an message
* containing information about the basket's contents.
*
* @uses \WPML_Basket_Tab_Ajax::create_remote_batch_message
*/
function begin_basket_commit() {
$basket_name = filter_input( INPUT_POST, 'basket_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES );
wp_send_json_success( $this->create_remote_batch_message( $basket_name ) );
}
/**
* Last ajax call in the multiple ajax calls made during the commit of a batch.
* Empties the basket in case the commit worked error free responds to the ajax call.
*/
function send_basket_commit() {
$errors = array();
try {
$translators = isset( $_POST['translators'] ) ? $_POST['translators'] : array();
$has_remote_translators = $this->networking->contains_remote_translators( $translators );
$response = $this->project && $has_remote_translators ? $this->project->commit_batch_job() : true;
$response = ! empty( $this->project->errors ) ? false : $response;
if ( $response !== false ) {
if ( is_object( $response ) ) {
$current_service = $this->project->current_service();
if ( $current_service->redirect_to_ts ) {
$message = sprintf(
__(
'You\'ve sent the content for translation to %s. Please continue to their site, to make sure that the translation starts.',
'wpml-translation-management'
),
$current_service->name
);
$link_text = sprintf(
__( 'Continue to %s', 'wpml-translation-management' ),
$current_service->name
);
} else {
$message = sprintf(
__(
'You\'ve sent the content for translation to %1$s. Currently, we are processing it and delivering to %1$s.',
'wpml-translation-management'
),
$current_service->name
);
$link_text = __( 'Check the batch delivery status', 'wpml-translation-management' );
}
$response->call_to_action = $message;
$batch_url = OTG_TRANSLATION_PROXY_URL . sprintf( '/projects/%d/external', $this->project->get_batch_job_id() );
$response->ts_batch_link = array(
'href' => esc_url( $batch_url ),
'text' => $link_text,
);
} elseif ( $this->contains_local_translators_different_than_current_user( $translators ) ) {
$response = new stdClass();
$response->is_local = true;
}
}
$errors = $response === false && $this->project ? $this->project->errors : $errors;
} catch ( Exception $e ) {
$response = false;
$errors[] = $e->getMessage();
}
do_action( 'wpml_tm_basket_committed' );
if ( isset( $response->is_local ) ) {
$batch_jobs = get_option( WPML_TM_Batch_Report::BATCH_REPORT_OPTION );
if ( $batch_jobs ) {
$response->emails_did_not_sent = true;
}
}
$this->send_json_response( $response, $errors );
}
/**
* @param $translators
*
* @return bool
*/
public function contains_local_translators_different_than_current_user( $translators ) {
$is_first_available_translator = function ( $translator ) {
return $translator === '0';
};
return ! \wpml_collect( $translators )
->reject( get_current_user_id() )
->reject( $is_first_available_translator )
->filter(
function ( $translator ) {
return is_numeric( $translator );
}
)
->isEmpty();
}
/**
* Ajax handler for checking if a current basket/batch name is valid for use with the currently used translation
* service.
*
* @uses \WPML_Translation_Basket::check_basket_name
*/
function check_basket_name() {
$basket_name_max_length = TranslationProxy::get_current_service_batch_name_max_length();
wp_send_json_success( $this->basket->check_basket_name( $this->get_basket_name(), $basket_name_max_length ) );
}
public function rollback_basket() {
\WPML\TM\API\Batch::rollback( $this->get_basket_name() );
wp_send_json_success();
}
/** @return string */
private function get_basket_name() {
return filter_input( INPUT_POST, 'basket_name', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES );
}
private static function sanitize_errors( $source ) {
if ( is_array( $source ) ) {
if ( $source && array_key_exists( 'errors', $source ) ) {
foreach ( $source['errors'] as &$error ) {
if ( is_array( $error ) ) {
$error = self::sanitize_errors( $error );
} else {
$error = ICL_AdminNotifier::sanitize_and_format_message( $error );
}
}
unset( $error );
}
} else {
$source = ICL_AdminNotifier::sanitize_and_format_message( $source );
}
return $source;
}
/**
* Sends the response to the ajax for \WPML_Basket_Tab_Ajax::send_basket_commit and rolls back the commit
* in case of any errors.
*
* @see \WPML_Basket_Tab_Ajax::send_basket_commit
* @uses \WPML_Translation_Basket::delete_all_items
*
* @param object|bool $response
* @param array $errors
*/
private function send_json_response( $response, $errors ) {
$result = array(
'result' => $response,
'is_error' => ! ( $response && empty( $errors ) ),
'errors' => $errors,
);
if ( ! empty( $errors ) ) {
\WPML\TM\API\Batch::rollback( $this->get_basket_name() );
wp_send_json_error( self::sanitize_errors( $result ) );
} else {
$this->basket->delete_all_items();
wp_send_json_success( $result );
}
}
/**
* Creates the message that is shown before committing a batch.
*
* @see \WPML_Basket_Tab_Ajax::begin_basket_commit
*
* @param string $basket_name
*
* @return array
*/
private function create_remote_batch_message( $basket_name ) {
if ( $basket_name ) {
$this->basket->set_name( $basket_name );
}
$basket = $this->basket->get_basket();
$basket_items_types = $this->basket->get_item_types();
if ( ! $basket ) {
$message_content = __( 'No items found in basket', 'wpml-translation-management' );
} else {
$total_count = 0;
$message_content_details = '<ul>';
foreach ( $basket_items_types as $item_type_name => $item_type ) {
if ( isset( $basket[ $item_type_name ] ) ) {
$count_item_type = count( $basket[ $item_type_name ] );
$total_count += $count_item_type;
$message_content_details .= '<li>' . $item_type_name . 's: ' . $count_item_type . '</li>';
}
}
$message_content_details .= '</ul>';
$message_content = sprintf( __( '%s items in basket:', 'wpml-translation-management' ), $total_count );
$message_content .= $message_content_details;
}
$container = $message_content;
return array(
'message' => $container,
'basket' => $basket,
'allowed_item_types' => array_keys( $basket_items_types ),
);
}
}