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,17 @@
<?php
class WPML_TM_AMS_ATE_Console_Section_Factory implements IWPML_TM_Admin_Section_Factory {
/**
* Returns an instance of a class implementing \IWPML_TM_Admin_Section.
*
* @return \IWPML_TM_Admin_Section
*/
public function create() {
if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) {
return WPML\Container\make( 'WPML_TM_AMS_ATE_Console_Section' );
}
return null;
}
}

View File

@@ -0,0 +1,258 @@
<?php
use WPML\API\Sanitize;
use WPML\Element\API\Languages;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\ATE\Proxies\Widget;
/**
* It handles the TM section responsible for displaying the AMS/ATE console.
*
* This class takes care of the following:
* - enqueuing the external script which holds the React APP
* - adding the ID to the enqueued script (as it's required by the React APP)
* - adding an inline script to initialize the React APP
*
* @author OnTheGo Systems
*/
class WPML_TM_AMS_ATE_Console_Section implements IWPML_TM_Admin_Section {
const ATE_APP_ID = 'eate_widget';
const TAB_ORDER = 10000;
const CONTAINER_SELECTOR = '#ams-ate-console';
const TAB_SELECTOR = '.wpml-tabs .nav-tab.nav-tab-active.nav-tab-ate-ams';
const SLUG = 'ate-ams';
/**
* An instance of \SitePress.
*
* @var SitePress The instance of \SitePress.
*/
private $sitepress;
/**
* Instance of WPML_TM_ATE_AMS_Endpoints.
*
* @var WPML_TM_ATE_AMS_Endpoints
*/
private $endpoints;
/**
* Instance of WPML_TM_ATE_Authentication.
*
* @var WPML_TM_ATE_Authentication
*/
private $auth;
/**
* Instance of WPML_TM_AMS_API.
*
* @var WPML_TM_AMS_API
*/
private $ams_api;
/**
* WPML_TM_AMS_ATE_Console_Section constructor.
*
* @param SitePress $sitepress The instance of \SitePress.
* @param WPML_TM_ATE_AMS_Endpoints $endpoints The instance of WPML_TM_ATE_AMS_Endpoints.
* @param WPML_TM_ATE_Authentication $auth The instance of WPML_TM_ATE_Authentication.
* @param WPML_TM_AMS_API $ams_api The instance of WPML_TM_AMS_API.
*/
public function __construct( SitePress $sitepress, WPML_TM_ATE_AMS_Endpoints $endpoints, WPML_TM_ATE_Authentication $auth, WPML_TM_AMS_API $ams_api ) {
$this->sitepress = $sitepress;
$this->endpoints = $endpoints;
$this->auth = $auth;
$this->ams_api = $ams_api;
}
/**
* Returns a value which will be used for sorting the sections.
*
* @return int
*/
public function get_order() {
return self::TAB_ORDER;
}
/**
* Returns the unique slug of the sections which is used to build the URL for opening this section.
*
* @return string
*/
public function get_slug() {
return self::SLUG;
}
/**
* Returns one or more capabilities required to display this section.
*
* @return string|array
*/
public function get_capabilities() {
return array( WPML_Manage_Translations_Role::CAPABILITY, 'manage_options' );
}
/**
* Returns the caption to display in the section.
*
* @return string
*/
public function get_caption() {
return __( 'Automatic Translation', 'wpml-translation-management' );
}
/**
* Returns the callback responsible for rendering the content of the section.
*
* @return callable
*/
public function get_callback() {
return array( $this, 'render' );
}
/**
* Used to extend the logic for displaying/hiding the section.
*
* @return bool
*/
public function is_visible() {
return true;
}
/**
* Outputs the content of the section.
*/
public function render() {
$supportUrl = 'https://wpml.org/forums/forum/english-support/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm';
$supportLink = '<a target="_blank" rel="nofollow" href="' . esc_url( $supportUrl ) . '">'
. esc_html__( 'contact our support team', 'wpml-translation-management' )
. '</a>';
?>
<div id="ams-ate-console">
<div class="notice inline notice-error" style="display:none; padding:20px">
<?php echo sprintf(
// translators: %s is a link with 'contact our support team'
esc_html(
__( 'There is a problem connecting to automatic translation. Please check your internet connection and try again in a few minutes. If you continue to see this message, please %s.', 'wpml-translation-management' )
),
$supportLink
);
?>
</div>
<span class="spinner is-active" style="float:left"></span>
</div>
<script type="text/javascript">
setTimeout(function () {
jQuery('#ams-ate-console .notice').show();
jQuery("#ams-ate-console .spinner").removeClass('is-active');
}, 20000);
</script>
<?php
}
/**
* This method is hooked to the `admin_enqueue_scripts` action.
*
* @param string $hook The current page.
*/
public function admin_enqueue_scripts( $hook ) {
if ( $this->is_ate_console_tab() ) {
$script_url = \add_query_arg(
[
Widget::QUERY_VAR_ATE_WIDGET_SCRIPT => Widget::SCRIPT_NAME,
],
\trailingslashit( \site_url() )
);
\wp_enqueue_script( self::ATE_APP_ID, $script_url, [], WPML_TM_VERSION, true );
}
}
/**
* It returns true if the current page and tab are the ATE Console.
*
* @return bool
*/
private function is_ate_console_tab() {
$sm = Sanitize::stringProp('sm', $_GET );
$page = Sanitize::stringProp( 'page', $_GET );
return $sm && $page && self::SLUG === $sm && WPML_TM_FOLDER . '/menu/main.php' === $page;
}
/**
* It returns the list of all translatable post types.
*
* @return array
*/
private function get_post_types_data() {
$translatable_types = $this->sitepress->get_translatable_documents( true );
$data = [];
if ( $translatable_types ) {
foreach ( $translatable_types as $name => $post_type ) {
$data[ esc_js( $name ) ] = [
'labels' => [
'name' => esc_js( $post_type->labels->name ),
'singular_name' => esc_js( $post_type->labels->singular_name ),
],
'description' => esc_js( $post_type->description ),
];
}
}
return $data;
}
/**
* It returns the current user's language.
*
* @return string
*/
private function get_user_admin_language() {
return $this->sitepress->get_user_admin_language( wp_get_current_user()->ID );
}
/**
* @return array<string,mixed>
*/
public function get_widget_constructor() {
$registration_data = $this->ams_api->get_registration_data();
$language_fields = [ 'code', 'english_name', 'native_name', 'default_locale', 'encode_url', 'tag', 'flag_url' ];
$app_constructor = [
'host' => esc_js( $this->endpoints->get_base_url( WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ) ),
'wpml_host' => esc_js( get_site_url() ),
'wpml_home' => esc_js( get_home_url() ),
'secret_key' => esc_js( $registration_data['secret'] ),
'shared_key' => esc_js( $registration_data['shared'] ),
'status' => esc_js( $registration_data['status'] ),
'tm_email' => esc_js( wp_get_current_user()->user_email ),
'website_uuid' => esc_js( $this->auth->get_site_id() ),
'site_key' => esc_js( apply_filters( 'otgs_installer_get_sitekey_wpml', null ) ),
'tab' => self::TAB_SELECTOR,
'container' => self::CONTAINER_SELECTOR,
'post_types' => $this->get_post_types_data(),
'ui_language' => esc_js( $this->get_user_admin_language() ),
'restNonce' => wp_create_nonce( 'wp_rest' ),
'authCookie' => [
'name' => LOGGED_IN_COOKIE,
'value' => $_COOKIE[ LOGGED_IN_COOKIE ],
],
'languages' => Fns::map( Obj::pick( $language_fields ), Languages::withFlags( Languages::getActive() ) ),
];
return $app_constructor;
}
/**
* @return string
*/
public function getWidgetScriptUrl() {
return $this->endpoints->get_base_url( WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ) . '/mini_app/main.js';
}
}

View File

@@ -0,0 +1,162 @@
<?php
use WPML\FP\Relation;
/**
* It handles the admin sections shown in the TM page.
*
* @author OnTheGo Systems
*/
class WPML_TM_Admin_Sections {
/**
* It stores the tab items.
*
* @var array The tab items.
*/
private $tab_items = array();
/**
* It stores the tab items.
*
* @var IWPML_TM_Admin_Section[] The admin sections.
*/
private $admin_sections = array();
/** @var array */
private $items_urls = array();
/**
* It adds the hooks.
*/
public function init_hooks() {
/**
* We have to defer creating of Admin Section to be sure that `WP_Installer` class is already loaded
*/
add_action( 'init', [ $this, 'init_sections' ] );
}
public function init_sections() {
foreach ( $this->get_admin_sections() as $section ) {
$this->tab_items[ $section->get_slug() ] = [
'caption' => $section->get_caption(),
'current_user_can' => $section->get_capabilities(),
'callback' => $section->get_callback(),
'order' => $section->get_order(),
];
add_action( 'admin_enqueue_scripts', [ $section, 'admin_enqueue_scripts' ] );
}
}
/**
* @return \IWPML_TM_Admin_Section[]
*/
private function get_admin_sections() {
if ( ! $this->admin_sections ) {
foreach ( $this->get_admin_section_factories() as $factory ) {
if ( in_array( 'IWPML_TM_Admin_Section_Factory', class_implements( $factory ), true ) ) {
$sections_factory = new $factory();
/**
* Sections are defined through classes extending `\IWPML_TM_Admin_Section_Factory`.
*
* @var \IWPML_TM_Admin_Section_Factory $sections_factory An instance of the section factory.
*/
$section = $sections_factory->create();
if ( $section && in_array( 'IWPML_TM_Admin_Section', class_implements( $section ), true ) && $section->is_visible() ) {
$this->admin_sections[ $section->get_slug() ] = $section;
}
}
}
}
return $this->admin_sections;
}
/**
* It returns the tab items.
*
* @return array The tab items.
*/
public function get_tab_items() {
return $this->tab_items;
}
/**
* It returns and filters the admin sections in the TM page.
*
* @return array<\WPML\TM\Menu\TranslationServices\SectionFactory|\WPML_TM_AMS_ATE_Console_Section_Factory|\WPML_TM_Translation_Roles_Section_Factory>
*/
private function get_admin_section_factories() {
$admin_sections_factories = array(
WPML_TM_Translation_Roles_Section_Factory::class,
WPML_TM_AMS_ATE_Console_Section_Factory::class,
);
return apply_filters( 'wpml_tm_admin_sections_factories', $admin_sections_factories );
}
/**
* Returns the URL of a tab item or an empty string if it cannot be found.
*
* @param string $slug
*
* @return string
*/
public function get_item_url( $slug ) {
if ( $this->get_section( $slug ) ) {
if ( ! array_key_exists( $slug, $this->items_urls ) ) {
$this->items_urls[ $slug ] = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_MANAGEMENT . '&sm=' . $slug );
}
return $this->items_urls[ $slug ];
}
return '';
}
/**
* Returns an instance of IWPML_TM_Admin_Section from its slug or null if it cannot be found.
*
* @param string $slug
*
* @return \IWPML_TM_Admin_Section|null
*/
public function get_section( $slug ) {
$sections = $this->get_admin_sections();
if ( array_key_exists( $slug, $sections ) ) {
return $sections[ $slug ];
}
return null;
}
/**
* @return bool
*/
public static function is_translation_roles_section() {
return self::is_section( 'translators' );
}
/**
* @return bool
*/
public static function is_translation_services_section() {
return self::is_section( 'translation-services' );
}
/**
* @return bool
*/
public static function is_dashboard_section() {
return self::is_section( 'dashboard' );
}
/**
* @param string $section
*
* @return bool
*/
private static function is_section( $section ) {
return Relation::propEq( 'page', 'tm/menu/main.php', $_GET ) &&
Relation::propEq( 'sm', $section, $_GET );
}
}

View File

@@ -0,0 +1,277 @@
<?php
use WPML\UIPage;
/**
* @author OnTheGo Systems
*/
class WPML_TM_Scripts_Factory {
private $ate;
private $ams_api;
private $auth;
private $endpoints;
private $http;
private $strings;
public function init_hooks() {
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_filter( 'wpml_tm_translators_view_strings', array( $this, 'filter_translators_view_strings' ), 10, 2 );
}
/**
* @throws \InvalidArgumentException
*/
public function admin_enqueue_scripts() {
$this->register_otgs_notices();
wp_register_script(
'wpml-tm-settings',
WPML_TM_URL . '/dist/js/settings/app.js',
array(),
WPML_TM_VERSION
);
wp_register_script(
'ate-translation-editor-classic',
WPML_TM_URL . '/dist/js/ate-translation-editor-classic/app.js',
array(),
false,
true
);
if ( WPML_TM_Page::is_tm_dashboard() ) {
$this->localize_script( 'wpml-tm-dashboard' );
wp_enqueue_script( 'wpml-tm-dashboard' );
}
if (
WPML_TM_Page::is_tm_translators()
|| UIPage::isTroubleshooting( $_GET )
) {
wp_enqueue_style( 'otgs-notices' );
$this->localize_script( 'wpml-tm-settings' );
wp_enqueue_script( 'wpml-tm-settings' );
$this->create_ate()->init_hooks();
}
if ( WPML_TM_Page::is_settings() ) {
wp_enqueue_style( 'otgs-notices' );
$this->localize_script( 'wpml-settings-ui' );
$this->create_ate()->init_hooks();
}
if ( WPML_TM_Page::is_translation_queue() && WPML_TM_ATE_Status::is_enabled() ) {
$this->localize_script( 'ate-translation-queue' );
wp_enqueue_script( 'ate-translation-queue' );
wp_enqueue_script( 'ate-translation-editor-classic' );
wp_enqueue_style( 'otgs-notices' );
}
if ( WPML_TM_Page::is_dashboard() ) {
$this->load_pick_up_box_scripts();
}
if ( WPML_TM_Page::is_settings() ) {
wp_enqueue_style(
'wpml-tm-multilingual-content-setup',
WPML_TM_URL . '/res/css/multilingual-content-setup.css',
array(),
WPML_TM_VERSION
);
}
if ( WPML_TM_Page::is_notifications_page() ) {
wp_enqueue_style(
'wpml-tm-translation-notifications',
WPML_TM_URL . '/res/css/translation-notifications.css',
array(),
WPML_TM_VERSION
);
}
}
private function load_pick_up_box_scripts() {
wp_enqueue_style( 'otgs-notices' );
wp_register_script(
'wpml-tm-dashboard',
WPML_TM_URL . '/dist/js/translationDashboard/app.js',
array(),
false,
true
);
global $iclTranslationManagement;
$this->localize_script(
'wpml-tm-dashboard',
array(
'strings' => array(
'numberOfTranslationStringsSingle' => __( '%d translation job', 'wpml-translation-management' ),
'numberOfTranslationStringsMulti' => __( '%d translation jobs', 'wpml-translation-management' ),
'stringsSentToTranslationSingle' => __(
'%s has been sent to remote translators',
'wpml-translation-management'
),
'stringsSentToTranslationMulti' => __(
'%s have been sent to remote translators',
'wpml-translation-management'
),
'buttonText' => __( 'Check status and get translations', 'wpml-translation-management' ),
'progressText' => __(
"Checking translation jobs status. Please don't close this page!",
'wpml-translation-management'
),
'progressJobsCount' => __( 'You are downloading %d jobs', 'wpml-translation-management' ),
'statusChecked' => __( 'Status checked:', 'wpml-translation-management' ),
'dismissNotice' => __( 'Dismiss this notice.', 'wpml-translation-management' ),
'noTranslationsDownloaded' => __(
'none of your translation jobs have been completed',
'wpml-translation-management'
),
'translationsDownloaded' => __(
'%d translation jobs have been finished and applied.',
'wpml-translation-management'
),
'errorMessage' => __(
'A communication error has appeared. Please wait a few minutes and try again.',
'wpml-translation-management'
),
'lastCheck' => __( 'Last check: %s', 'wpml-translation-management' ),
'never' => __( 'never', 'wpml-translation-management' ),
),
'debug' => defined( 'WPML_POLLING_BOX_DEBUG_MODE' ) && WPML_POLLING_BOX_DEBUG_MODE,
'statusIcons' => array(
'completed' => $iclTranslationManagement->status2icon_class( ICL_TM_COMPLETE, false ),
'canceled' => $iclTranslationManagement->status2icon_class( ICL_TM_NOT_TRANSLATED, false ),
'progress' => $iclTranslationManagement->status2icon_class( ICL_TM_IN_PROGRESS, false ),
'needsUpdate' => $iclTranslationManagement->status2icon_class( ICL_TM_NEEDS_UPDATE, false ),
),
)
);
wp_enqueue_script( 'wpml-tm-dashboard' );
}
public function register_otgs_notices() {
if ( ! wp_style_is( 'otgs-notices', 'registered' ) ) {
wp_register_style(
'otgs-notices',
ICL_PLUGIN_URL . '/res/css/otgs-notices.css',
array( 'sitepress-style' )
);
}
}
/**
* @param $handle
*
* @throws \InvalidArgumentException
*/
public function localize_script( $handle, $additional_data = array() ) {
wp_localize_script( $handle, 'WPML_TM_SETTINGS', $this->build_localize_script_data( $additional_data ) );
}
public function build_localize_script_data($additional_data = array() ) {
$data = array(
'hasATEEnabled' => WPML_TM_ATE_Status::is_enabled(),
'restUrl' => untrailingslashit( rest_url() ),
'restNonce' => wp_create_nonce( 'wp_rest' ),
'ate' => $this->create_ate()
->get_script_data(),
'currentUser' => null,
);
$data = array_merge( $data, $additional_data );
$current_user = wp_get_current_user();
if ( $current_user && $current_user->ID > 0 ) {
$data['currentUser'] = $current_user;
}
return $data;
}
/**
* @return WPML_TM_MCS_ATE
* @throws \InvalidArgumentException
*/
public function create_ate() {
if ( ! $this->ate ) {
$this->ate = new WPML_TM_MCS_ATE(
$this->get_authentication(),
$this->get_endpoints(),
$this->create_ate_strings()
);
}
return $this->ate;
}
private function get_authentication() {
if ( ! $this->auth ) {
$this->auth = new WPML_TM_ATE_Authentication();
}
return $this->auth;
}
private function get_endpoints() {
if ( ! $this->endpoints ) {
$this->endpoints = WPML\Container\make( 'WPML_TM_ATE_AMS_Endpoints' );
}
return $this->endpoints;
}
private function create_ate_strings() {
if ( ! $this->strings ) {
$this->strings = new WPML_TM_MCS_ATE_Strings( $this->get_authentication(), $this->get_endpoints() );
}
return $this->strings;
}
/**
* @param array $strings
* @param bool $all_users_have_subscription
*
* @return array
*/
public function filter_translators_view_strings( array $strings, $all_users_have_subscription ) {
if ( WPML_TM_ATE_Status::is_enabled() ) {
$strings['ate'] = $this->create_ate_strings()
->get_status_HTML(
$this->get_ate_activation_status(),
$all_users_have_subscription
);
}
return $strings;
}
/**
* @return string
*/
private function get_ate_activation_status() {
$status = $this->create_ate_strings()
->get_status();
if ( $status !== WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE ) {
$status = $this->fetch_and_update_ate_activation_status();
}
return $status;
}
/**
* @return string
*/
private function fetch_and_update_ate_activation_status() {
$ams_api = WPML\Container\make( WPML_TM_AMS_API::class );
$ams_api->get_status();
return $this->create_ate_strings()
->get_status();
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace WPML\TM\Menu\Dashboard;
use WPML\FP\Lst;
use WPML\TM\ATE\Review\ReviewStatus;
class PostJobsRepository {
/**
* @param int $original_element_id
* @param string $element_type
*
* @return array
*/
public function getJobsGroupedByLang( $original_element_id, $element_type ) {
return $this->getJobsFor( $original_element_id, $element_type )
->map( [ $this, 'mapJob' ] )
->keyBy( 'targetLanguage' )
->toArray();
}
/**
* @param int $original_element_id
* @param string $element_type
*
* @return \WPML\Collect\Support\Collection
*/
private function getJobsFor( $original_element_id, $element_type ) {
return \wpml_collect(
wpml_tm_get_jobs_repository()->get( $this->buildSearchParams( $original_element_id, $element_type ) )
);
}
/**
* @param int $original_element_id
* @param string $element_type
*
* @return \WPML_TM_Jobs_Search_Params
*/
private function buildSearchParams( $original_element_id, $element_type ) {
$params = new \WPML_TM_Jobs_Search_Params();
$params->set_original_element_id( $original_element_id );
$params->set_job_types( $element_type );
return $params;
}
/**
* @param \WPML_TM_Post_Job_Entity $job
*
* @return array
*/
public function mapJob( \WPML_TM_Post_Job_Entity $job ) {
return [
'entity_id' => $job->get_id(),
'job_id' => $job->get_translate_job_id(),
'type' => $job->get_type(),
'status' => $this->getJobStatus( $job ),
'targetLanguage' => $job->get_target_language(),
'isLocal' => 'local' === $job->get_translation_service(),
'needsReview' => Lst::includes( $job->get_review_status(), [ ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ] ),
'automatic' => $job->is_automatic(),
'editor' => $job->get_editor(),
];
}
/**
* @param \WPML_TM_Job_Entity $job
*
* @return int
*/
private function getJobStatus( \WPML_TM_Job_Entity $job ) {
if ( $job->does_need_update() ) {
return ICL_TM_NEEDS_UPDATE;
}
if ( $this->postHasTranslationButLatestJobCancelled( $job ) ) {
return ICL_TM_COMPLETE;
}
return $job->get_status();
}
/**
* @param \WPML_TM_Job_Entity $job
*
* @return bool
*/
private function postHasTranslationButLatestJobCancelled( \WPML_TM_Job_Entity $job ) {
return $job->get_status() === ICL_TM_NOT_TRANSLATED && $job->has_completed_translation();
}
}

View File

@@ -0,0 +1,375 @@
<?php
use WPML\FP\Obj;
use WPML\Element\API\Languages;
use WPML\Setup\Option;
use WPML\API\PostTypes;
use WPML\FP\Lst;
class WPML_TM_Dashboard_Display_Filter {
const PARENT_TAXONOMY_CONTAINER = 'parent-taxonomy-container';
const PARENT_SELECT_ID = 'parent-filter-control';
const PARENT_SELECT_NAME = 'filter[parent_type]';
const PARENT_OR_TAXONOMY_ITEM_CONTAINER = 'parent-taxonomy-item-container';
private $active_languages = array();
private $translation_filter;
private $post_types;
private $post_statuses;
private $source_language_code;
private $priorities;
/** @var wpdb $wpdb */
private $wpdb;
public function __construct(
$active_languages,
$source_language_code,
$translation_filter,
$post_types,
$post_statuses,
array $priorities,
wpdb $wpdb
) {
$this->active_languages = $active_languages;
$this->translation_filter = $translation_filter;
$this->source_language_code = $source_language_code;
$this->post_types = $post_types;
$this->post_statuses = $post_statuses;
$this->priorities = $priorities;
$this->wpdb = $wpdb;
}
private function from_lang_select() {
$select_attributes_id = 'icl_language_selector';
$select_attributes_name = 'filter[from_lang]';
$select_attributes_label = __( 'in', 'wpml-translation-management' );
$fromLang = $this->get_language_from();
if ( $fromLang && ! Obj::prop( $fromLang, $this->active_languages ) ) {
$this->active_languages [ $fromLang ] = Languages::getLanguageDetails( $fromLang );
}
?>
<label for="<?php echo esc_attr( $select_attributes_id ); ?>">
<?php echo esc_html( $select_attributes_label ); ?>
</label>
<?php
if ( $this->source_language_code ) {
$tooltip = $this->get_from_language_filter_lock_message_if_required();
?>
<input type="hidden" id="<?php echo esc_attr( $select_attributes_id ); ?>" name="<?php echo esc_attr( $select_attributes_name ); ?>" value="<?php echo esc_attr( $this->get_language_from() ); ?>"/>
<span class="wpml-tm-filter-disabled js-otgs-popover-tooltip" title="<?php echo esc_attr( $tooltip ); ?>"><?php echo esc_html( $this->active_languages[ $this->get_language_from() ]['display_name'] ); ?></span>
<?php
} else {
?>
<select id="<?php echo esc_attr( $select_attributes_id ); ?>" name="<?php echo esc_attr( $select_attributes_name ); ?>" title="<?php echo esc_attr( $select_attributes_label ); ?>">
<?php
foreach ( $this->active_languages as $lang ) {
$selected = '';
if ( $this->get_language_from() && $lang['code'] == $this->get_language_from() ) {
$selected = 'selected="selected"';
}
?>
<option value="<?php echo esc_attr( $lang['code'] ); ?>" <?php echo $selected; ?>>
<?php
echo esc_html( $lang['display_name'] );
?>
</option>
<?php
}
?>
</select>
<?php
}
?>
<?php
}
private function get_language_from() {
$languages_from = $this->source_language_code;
if ( ! $languages_from ) {
$languages_from = $this->get_language_from_filter();
}
return $languages_from;
}
private function get_language_from_filter() {
if ( array_key_exists( 'from_lang', $this->translation_filter ) && $this->translation_filter['from_lang'] ) {
return $this->translation_filter['from_lang'];
}
return null;
}
private function to_lang_select() {
?>
<label for="filter_to_lang">
<?php esc_html_e( 'translated to', 'wpml-translation-management' ); ?>
</label>
<select id="filter_to_lang" name="filter[to_lang]">
<option value=""><?php esc_html_e( 'Any language', 'wpml-translation-management' ); ?></option>
<?php
foreach ( $this->active_languages as $lang ) {
$selected = selected( $this->translation_filter['to_lang'], $lang['code'], false );
?>
<option value="<?php echo esc_attr( $lang['code'] ); ?>" <?php echo $selected; ?>>
<?php echo esc_html( $lang['display_name'] ); ?>
</option>
<?php
}
?>
</select>
<?php
}
private function translation_status_select() {
?>
<select id="filter_tstatus" name="filter[tstatus]" title="<?php esc_attr_e( 'Translation status', 'wpml-translation-management' ); ?>">
<?php
$option_status = array(
-1 => esc_html__( 'All translation statuses', 'wpml-translation-management' ),
ICL_TM_NOT_TRANSLATED => esc_html__( 'Not translated', 'wpml-translation-management' ),
ICL_TM_NOT_TRANSLATED . '_' . ICL_TM_NEEDS_UPDATE => esc_html__(
'Not translated or needs updating',
'wpml-translation-management'
),
ICL_TM_NEEDS_UPDATE => esc_html__( 'Needs updating', 'wpml-translation-management' ),
ICL_TM_IN_PROGRESS => esc_html__( 'Translation in progress', 'wpml-translation-management' ),
ICL_TM_COMPLETE => esc_html__( 'Translation complete', 'wpml-translation-management' ),
);
foreach ( $option_status as $status_key => $status_value ) {
$selected = selected( $this->translation_filter['tstatus'], $status_key, false );
?>
<option value="<?php echo $status_key; ?>" <?php echo $selected; ?>><?php echo $status_value; ?></option>
<?php
}
?>
</select>
<?php
}
private function get_from_language_filter_lock_message_if_required() {
$basket_locked_string = null;
if ( $this->source_language_code && isset( $this->active_languages[ $this->source_language_code ] ) ) {
$language_name = $this->active_languages[ $this->source_language_code ]['display_name'];
$basket_locked_string = '<p>';
$basket_locked_string .= sprintf(
esc_html__(
'Language filtering has been disabled because you already have items in %s in the basket.',
'wpml-translation-management'
),
$language_name
);
$basket_locked_string .= '<br/>';
$basket_locked_string .= esc_html__(
'To re-enable it, please empty the basket or send it for translation.',
'wpml-translation-management'
);
$basket_locked_string .= '</p>';
}
return $basket_locked_string;
}
private function display_post_type_select() {
$selected_type = isset( $this->translation_filter['type'] ) ? $this->translation_filter['type'] : false;
?>
<select id="filter_type" name="filter[type]" title="<?php esc_attr_e( 'Element type', 'wpml-translation-management' ); ?>">
<option value=""><?php esc_html_e( 'All types', 'wpml-translation-management' ); ?></option>
<?php
foreach ( $this->post_types as $post_type_key => $post_type ) {
$filter_type_selected = selected( $selected_type, $post_type_key, false );
$hierarchical = is_post_type_hierarchical( $post_type_key ) ? 'true' : 'false';
$taxonomy_string = '';
foreach ( get_object_taxonomies( $post_type_key, 'objects' ) as $taxonomy => $taxonomy_object ) {
if ( $this->has_taxonomy_terms_in_any_language( $taxonomy ) ) {
if ( $taxonomy_string ) {
$taxonomy_string .= ',';
}
$taxonomy_string .= $taxonomy . '=' . $taxonomy_object->label;
}
}
?>
<option
value="<?php echo $post_type_key; ?>"
data-parent="<?php echo $hierarchical; ?>"
data-taxonomy="<?php echo $taxonomy_string; ?>"
<?php echo $filter_type_selected; ?>
>
<?php
echo $post_type->labels->singular_name != '' ? $post_type->labels->singular_name
: $post_type->labels->name;
?>
</option>
<?php
}
?>
</select>
<?php
}
private function display_parent_taxonomy_controls() {
?>
<span id="<?php echo self::PARENT_TAXONOMY_CONTAINER; ?>" style="display:none;">
<label for="<?php echo self::PARENT_SELECT_ID; ?>">
<?php esc_html_e( 'parent', 'wpml-translation-management' ); ?>
</label>
<select
id="<?php echo self::PARENT_SELECT_ID; ?>"
name="<?php echo self::PARENT_SELECT_NAME; ?>"
data-original="<?php echo isset( $this->translation_filter['parent_type'] ) ? $this->translation_filter['parent_type'] : 'any'; ?>"
>
</select>
<span name="<?php echo self::PARENT_OR_TAXONOMY_ITEM_CONTAINER; ?>" class="<?php echo self::PARENT_OR_TAXONOMY_ITEM_CONTAINER; ?>">
<input type="hidden" name="filter[parent_id]" value="<?php echo isset( $this->translation_filter['parent_id'] ) ? $this->translation_filter['parent_id'] : ''; ?>"/>
</span>
</span>
<?php
}
private function filter_title_textbox() {
$title = isset( $this->translation_filter['title'] ) ? $this->translation_filter['title'] : '';
?>
<input type="text" id="filter_title" name="filter[title]"
value="<?php echo esc_attr( $title ); ?>"
placeholder="<?php esc_attr_e( 'Title', 'wpml-translation-management' ); ?>"
/>
<?php
}
private function display_post_statuses_select() {
$filter_post_status = isset( $this->translation_filter['status'] ) ? $this->translation_filter['status']
: false;
?>
<select id="filter_status" name="filter[status]" title="<?php esc_attr_e( 'Publish status', 'wpml-translation-management' ); ?>">
<option value=""><?php esc_html_e( 'All statuses', 'wpml-translation-management' ); ?></option>
<?php
foreach ( $this->post_statuses as $post_status_k => $post_status ) {
$post_status_selected = selected( $filter_post_status, $post_status_k, false );
?>
<option value="<?php echo $post_status_k; ?>" <?php echo $post_status_selected; ?>>
<?php echo $post_status; ?>
</option>
<?php
}
?>
</select>
<?php
}
private function display_post_translation_priority_select() {
$filter_translation_priority = isset( $this->translation_filter['translation_priority'] ) ? $this->translation_filter['translation_priority']
: false;
?>
<select id="filter_translation_priority" name="filter[translation_priority]" title="<?php esc_attr_e( 'Priority', 'wpml-translation-management' ); ?>">
<option value=""><?php esc_html_e( 'All Translation Priorities', 'wpml-translation-management' ); ?></option>
<?php
foreach ( $this->priorities as $priority ) {
$translation_priority_selected = selected( $filter_translation_priority, $priority->term_id, false );
?>
<option value="<?php echo esc_attr( $priority->term_id ); ?>" <?php echo $translation_priority_selected; ?>>
<?php echo esc_html( $priority->name ); ?>
</option>
<?php
}
?>
</select>
<?php
}
private function display_button() {
$reset_url = $this->get_admin_page_url(
array(
'page' => WPML_TM_FOLDER . '/menu/main.php',
'sm' => 'dashboard',
'icl_tm_action' => 'reset_dashboard_filters',
)
);
?>
<input id="translation_dashboard_filter" name="translation_dashboard_filter"
class="button-secondary" type="submit"
value="<?php esc_attr_e( 'Filter', 'wpml-translation-management' ); ?>"/>
<a type="reset" href="<?php echo esc_url( $reset_url ); ?>" class="wpml-reset-filter"><i class="otgs-ico-close"> </i><?php esc_html_e( 'Reset filter', 'wpml-translation-management' ); ?></a>
<?php
}
public function display() {
$form_url = $this->get_admin_page_url(
array(
'page' => WPML_TM_FOLDER . '/menu/main.php',
'sm' => 'dashboard',
)
);
$postTypes = Lst::joinWithCommasAndAnd( PostTypes::withNames( PostTypes::getAutomaticTranslatable() ) );
?>
<?php echo \WPML\TM\ATE\Loader::getWpmlAutoTranslateContainer(); ?>
<?php if ( Option::shouldTranslateEverything() ): ?>
<h2 class="wpml-tm-dashboard-h2">
<?php _e( 'Translate other content', 'wpml-translation-management' ); ?>
</h2>
<p class="wpml-tm-dashboard-paragraph-extra-space-bottom">
<?php echo sprintf( __( 'WPML is automatically translating published %s.', 'wpml-translation-management' ), $postTypes ); ?>
<br />
<?php _e( 'To translate other content, select your items for translation and your preferred translation options below.', 'wpml-translation-management' ); ?>
</p>
<?php endif; ?>
<form method="post" name="translation-dashboard-filter" class="wpml-tm-dashboard-filter" action="<?php echo esc_url( $form_url ); ?>">
<input type="hidden" name="icl_tm_action" value="dashboard_filter"/>
<?php
do_action( 'display_basket_notification', 'tm_dashboard_top' );
$this->heading( __( '1. Select items for translation', 'wpml-translation-management' ) );
$this->display_post_type_select();
$this->display_parent_taxonomy_controls();
$this->from_lang_select();
$this->to_lang_select();
$this->translation_status_select();
$this->display_post_statuses_select();
$this->display_post_translation_priority_select();
$this->filter_title_textbox();
$this->display_button();
?>
</form>
<?php
}
private function has_taxonomy_terms_in_any_language( $taxonomy ) {
return $this->wpdb->get_var(
$this->wpdb->prepare(
"SELECT COUNT(translation_id) FROM {$this->wpdb->prefix}icl_translations WHERE element_type=%s",
'tax_' . $taxonomy
)
) > 0;
}
private function heading( $text ) {
?>
<h3 class="wpml-tm-section-header wpml-tm-dashboard-h3"><?php echo esc_html( $text ); ?></h3>
<?php
}
private function get_admin_page_url( array $query_args ) {
$base_url = admin_url( 'admin.php' );
return add_query_arg( $query_args, $base_url );
}
}

View File

@@ -0,0 +1,378 @@
<?php
use WPML\TM\Menu\Dashboard\PostJobsRepository;
use WPML\TM\API\Jobs;
class WPML_TM_Dashboard_Document_Row {
/** @var stdClass $data */
private $data;
private $post_types;
private $active_languages;
private $selected;
private $note_text;
private $note_icon_class;
private $post_statuses;
/** @var SitePress $sitepress */
private $sitepress;
/** @var WPML_TM_Translatable_Element_Provider $translatable_element_provider */
private $translatable_element_provider;
public function __construct(
$doc_data,
$post_types,
$post_statuses,
$active_languages,
$selected,
SitePress $sitepress,
WPML_TM_Translatable_Element_Provider $translatable_element_provider
) {
$this->data = $doc_data;
$this->post_statuses = $post_statuses;
$this->selected = $selected;
$this->post_types = $post_types;
$this->active_languages = $active_languages;
$this->sitepress = $sitepress;
$this->translatable_element_provider = $translatable_element_provider;
}
public function get_word_count() {
$current_document = $this->data;
$type = 'post';
if ( $this->is_external_type() ) {
$type = 'package';
}
$translatable_element = $this->translatable_element_provider->get_from_type( $type, $current_document->ID );
return apply_filters(
'wpml_tm_estimated_words_count',
$translatable_element->get_words_count(),
$current_document
);
}
public function get_title() {
return $this->data->title ? $this->data->title : __( '(missing title)', 'wpml-translation-management' );
}
private function is_external_type() {
$doc = $this->data;
return strpos( $doc->translation_element_type, 'post_' ) !== 0;
}
public function get_type_prefix() {
$type = $this->data->translation_element_type;
$type = explode( '_', $type );
if ( count( $type ) > 1 ) {
$type = $type[0];
}
return $type;
}
public function get_type() {
$type = $this->data->translation_element_type;
$type = explode( '_', $type );
if ( count( $type ) > 1 ) {
unset( $type[0] );
}
$type = join( '_', $type );
return $type;
}
public function display() {
global $iclTranslationManagement;
$current_document = $this->data;
$count = $this->get_word_count();
$post_actions = array();
$post_actions_link = '';
$element_type = $this->get_type_prefix();
$check_field_name = $element_type;
$post_title = $this->get_title();
$post_status = $this->get_general_status() === $this->post_statuses['draft'] ? ' — ' . $this->post_statuses['draft'] : '';
$post_view_link = '';
$post_edit_link = '';
if ( ! $this->is_external_type() ) {
$post_link_factory = new WPML_TM_Post_Link_Factory( $this->sitepress );
$post_edit_link = $post_link_factory->edit_link_anchor( $current_document->ID, __( 'Edit', 'wpml-translation-management' ) );
$post_view_link = $post_link_factory->view_link_anchor( $current_document->ID, __( 'View', 'wpml-translation-management' ) );
}
$jobs = ( new PostJobsRepository() )->getJobsGroupedByLang( $current_document->ID, $element_type );
$post_edit_link = apply_filters( 'wpml_document_edit_item_link', $post_edit_link, __( 'Edit', 'wpml-translation-management' ), $current_document, $element_type, $this->get_type() );
if ( $post_edit_link ) {
$post_actions[] = "<span class='edit'>" . $post_edit_link . '</span>';
}
$post_view_link = apply_filters( 'wpml_document_view_item_link', $post_view_link, __( 'View', 'wpml-translation-management' ), $current_document, $element_type, $this->get_type() );
if ( $post_view_link ) {
$post_actions[] = "<span class='view'>" . $post_view_link . '</span>';
}
if ( $post_actions ) {
$post_actions_link .= '<div class="row-actions">' . implode( ' | ', $post_actions ) . '</div>';
}
$row_data = apply_filters( 'wpml_translation_dashboard_row_data', array( 'word_count' => $count ), $this->data );
$row_data_str = '';
foreach ( $row_data as $key => $value ) {
$row_data_str .= 'data-' . esc_attr( $key ) . '="' . esc_attr( $value ) . '" ';
}
?>
<tr id="row_<?php echo sanitize_html_class( $current_document->ID ); ?>" <?php echo $row_data_str; ?>>
<td scope="row">
<?php
$checked = checked( true, isset( $_GET['post_id'] ) || $this->selected, false );
?>
<input type="checkbox" value="<?php echo $current_document->ID; ?>" name="<?php echo $check_field_name; ?>[<?php echo $current_document->ID; ?>][checked]" <?php echo $checked; ?> />
<input type="hidden" value="<?php echo $element_type; ?>" name="<?php echo $check_field_name; ?>[<?php echo $current_document->ID; ?>][type]"/>
</td>
<td scope="row" class="post-title column-title">
<?php
echo esc_html( $post_title );
echo esc_html( $post_status );
echo $post_actions_link;
?>
<div class="icl_post_note" id="icl_post_note_<?php echo $current_document->ID; ?>">
<?php
$note = '';
if ( ! $current_document->is_translation ) {
$note = WPML_TM_Translator_Note::get( $current_document->ID );
$this->note_text = '';
if ( $note ) {
$this->note_text = __( 'Edit note for the translators', 'wpml-translation-management' );
$this->note_icon_class = 'otgs-ico-note-edit-o';
} else {
$this->note_text = __( 'Add note for the translators', 'wpml-translation-management' );
$this->note_icon_class = 'otgs-ico-note-add-o';
}
}
?>
<label for="post_note_<?php echo $current_document->ID; ?>">
<?php _e( 'Note for the translators', 'wpml-translation-management' ); ?>
</label>
<textarea id="post_note_<?php echo $current_document->ID; ?>" rows="5"><?php echo $note; ?></textarea>
<table width="100%">
<tr>
<td style="border-bottom:none">
<input type="button" class="icl_tn_cancel button" value="<?php _e( 'Cancel', 'wpml-translation-management' ); ?>" />
<input class="icl_tn_post_id" type="hidden" value="<?php echo $current_document->ID; ?>"/>
</td>
<td align="right" style="border-bottom:none">
<input type="button" class="icl_tn_save button-primary" value="<?php _e( 'Save', 'wpml-translation-management' ); ?>"/>
</td>
</tr>
</table>
</div>
</td>
<td scope="row" class="manage-column wpml-column-type">
<?php
if ( isset( $this->post_types[ $this->get_type() ] ) ) {
$custom_post_type_labels = $this->post_types[ $this->get_type() ]->labels;
if ( $custom_post_type_labels->singular_name != '' ) {
echo $custom_post_type_labels->singular_name;
} else {
echo $custom_post_type_labels->name;
}
} else {
echo $this->get_type();
}
?>
</td>
<td scope="row" class="manage-column column-active-languages wpml-col-languages">
<?php
$has_jobs_in_progress = false;
foreach ( $this->active_languages as $code => $lang ) {
if ( $code == $this->data->language_code ) {
continue;
}
$needsReview = false;
if ( isset( $jobs[ $code ] ) ) {
$job = $jobs[ $code ];
$status = $job['status'] ?: $this->get_status_in_lang( $code );
$job_entity_id = $job['entity_id'];
$job_id = $job['job_id'];
$needsReview = $job['needsReview'];
$automatic = $job['automatic'];
$shouldBeSynced = Jobs::shouldBeATESynced( $job );
} else {
$status = $this->get_status_in_lang( $code );
$job_entity_id = 0;
$job_id = 0;
$automatic = false;
$shouldBeSynced = false;
}
if ( $needsReview ) {
$translation_status_text = esc_attr( __( 'Needs review', 'wpml-translation-management' ) );
} else {
switch ( $status ) {
case ICL_TM_NOT_TRANSLATED:
$translation_status_text = esc_attr( __( 'Not translated', 'wpml-translation-management' ) );
break;
case ICL_TM_WAITING_FOR_TRANSLATOR:
$translation_status_text = $automatic
? esc_attr( __( 'Waiting for automatic translation', 'wpml-translation-management' ) )
: esc_attr( __( 'Waiting for translator', 'wpml-translation-management' ) );
break;
case ICL_TM_IN_BASKET:
$translation_status_text = esc_attr( __( 'In basket', 'wpml-translation-management' ) );
break;
case ICL_TM_IN_PROGRESS:
$translation_status_text = esc_attr( __( 'Waiting for translation service', 'wpml-translation-management' ) );
$has_jobs_in_progress = true;
break;
case ICL_TM_TRANSLATION_READY_TO_DOWNLOAD:
$translation_status_text = esc_attr(
__(
'Translation ready to download',
'wpml-translation-management'
)
);
$has_jobs_in_progress = true;
break;
case ICL_TM_DUPLICATE:
$translation_status_text = esc_attr( __( 'Duplicate of default language', 'wpml-translation-management' ) );
break;
case ICL_TM_COMPLETE:
$translation_status_text = esc_attr( __( 'Translation completed', 'wpml-translation-management' ) );
break;
case ICL_TM_NEEDS_UPDATE:
$translation_status_text = esc_attr( __( 'Needs update', 'wpml-translation-management' ) );
break;
case ICL_TM_ATE_NEEDS_RETRY:
$translation_status_text = esc_attr( __( 'In progress', 'wpml-translation-management' ) ) . ' - ' . esc_attr( __( 'needs retry', 'wpml-translation-management' ) );
break;
default:
$translation_status_text = '';
}
}
$status_icon_class = $iclTranslationManagement->status2icon_class( $status, ICL_TM_NEEDS_UPDATE === (int) $status, $needsReview );
?>
<span data-document_status="<?php echo $status; ?>"
id="wpml-job-status-<?php echo esc_attr( $job_entity_id ); ?>"
class="js-wpml-translate-link"
data-tm-job-id="<?php echo esc_attr( $job_id ); ?>"
data-automatic="<?php echo esc_attr( $automatic ); ?>"
data-wpmlcc-lang="<?php echo esc_attr( $code ); ?>"
data-should-ate-sync="<?php echo $shouldBeSynced ? '1' : '0' ?>"
>
<i class="<?php echo esc_attr( $status_icon_class ); ?> js-otgs-popover-tooltip"
title="<?php echo esc_attr( $lang['display_name'] ); ?>: <?php echo $translation_status_text; ?>"
data-original-title="<?php echo esc_attr( $lang['display_name'] ); ?>: <?php echo $translation_status_text; ?>"
></i>
</span>
<?php
}
?>
</td>
<td scope="row" class="post-date column-date">
<?php
$element_date = $this->get_date();
if ( $element_date ) {
echo date( 'Y-m-d', strtotime( $element_date ) );
}
echo '<br />';
echo $this->get_general_status();
?>
</td>
<td class="column-actions" scope="row" >
<?php
if ( ! $current_document->is_translation ) {
?>
<a title="<?php echo $this->note_text; ?>" href="#" class="icl_tn_link" id="icl_tn_link_<?php echo $current_document->ID; ?>" >
<i class="<?php echo $this->note_icon_class; ?>"></i>
</a>
<?php
if ( $has_jobs_in_progress && $this->has_remote_jobs( $jobs ) ) {
?>
<a class="otgs-ico-refresh wpml-sync-and-download-translation"
data-element-id="<?php echo $current_document->ID; ?>"
data-element-type="<?php echo esc_attr( $element_type ); ?>"
data-jobs="<?php echo htmlspecialchars( json_encode( array_values( $jobs ) ) ); ?>"
data-icons="
<?php
echo htmlspecialchars(
json_encode(
array(
'completed' => $iclTranslationManagement->status2icon_class( ICL_TM_COMPLETE, false ),
'canceled' => $iclTranslationManagement->status2icon_class( ICL_TM_NOT_TRANSLATED, false ),
'progress' => $iclTranslationManagement->status2icon_class( ICL_TM_IN_PROGRESS, false ),
)
)
)
?>
"
title="<?php esc_attr_e( 'Check status and get translations', 'wpml-translation-management' ); ?>"
</a>
<?php
}
}
?>
</td>
</tr>
<?php
}
private function get_date() {
if ( ! $this->is_external_type() ) {
/** @var WP_Post $post */
$post = get_post( $this->data->ID );
$date = get_post_time( 'U', false, $post );
} else {
$date = apply_filters(
'wpml_tm_dashboard_date',
time(),
$this->data->ID,
$this->data->translation_element_type
);
}
$date = date( 'y-m-d', $date );
return $date;
}
private function has_remote_jobs( $jobs ) {
foreach ( $jobs as $job ) {
if ( ! $job['isLocal'] ) {
return true;
}
}
return false;
}
private function get_general_status() {
if ( ! $this->is_external_type() ) {
$status = get_post_status( $this->data->ID );
$status_text = isset( $this->post_statuses[ $status ] ) ? $this->post_statuses[ $status ] : $status;
} else {
$status_text = apply_filters(
'wpml_tm_dashboard_status',
'external',
$this->data->ID,
$this->data->translation_element_type
);
}
return $status_text;
}
private function get_status_in_lang( $language_code ) {
$status_helper = wpml_get_post_status_helper();
return $status_helper->get_status( false, $this->data->trid, $language_code );
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* Class WPML_TM_Dashboard_Pagination
*/
class WPML_TM_Dashboard_Pagination {
private $postLimitNumber = 0;
public function add_hooks() {
add_action( 'wpml_tm_dashboard_pagination', array( $this, 'add_tm_dashboard_pagination' ), 10, 2 );
add_filter( 'wpml_tm_dashboard_post_query_args', array( $this, 'filter_dashboard_post_query_args_for_pagination' ), 10, 2 );
}
public function filter_dashboard_post_query_args_for_pagination( $query_args, $args ) {
if ( ! empty( $args['type'] ) ) {
unset( $query_args['no_found_rows'] );
}
}
/**
* Sets value for posts limit query to be used in post_limits filter
*
* @param int $value
*
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-616
*/
public function setPostsLimitValue( $value ) {
$this->postLimitNumber = ( is_int( $value ) && $value > 0 ) ? $value : $this->postLimitNumber;
}
/**
* Resets value of posts limit variable.
*
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-616
*/
public function resetPostsLimitValue() {
$this->postLimitNumber = 0;
}
/**
* Custom callback that's hooked into 'post_limits' filter to set custom limit of retrieved posts.
*
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-616
*
* @return string
*/
public function getPostsLimitQueryValue() {
return 'LIMIT ' . $this->postLimitNumber;
}
/**
* @param integer $posts_per_page
* @param integer $found_documents
*/
public function add_tm_dashboard_pagination( $posts_per_page, $found_documents ) {
$found_documents = $found_documents;
$total_pages = ceil( $found_documents / $posts_per_page );
$paged = array_key_exists( 'paged', $_GET ) ? filter_var( $_GET['paged'], FILTER_SANITIZE_NUMBER_INT ) : false;
$paged = $paged ? $paged : 1;
$page_links = paginate_links(
array(
'base' => add_query_arg( 'paged', '%#%' ),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $total_pages,
'current' => $paged,
)
);
if ( $page_links ) {
?>
<div class="tablenav-pages">
<?php
$page_from = number_format_i18n( ( $paged - 1 ) * $posts_per_page + 1 );
$page_to = number_format_i18n( min( $paged * $posts_per_page, $found_documents ) );
$page_total = number_format_i18n( $found_documents );
?>
<span class="displaying-num">
<?php echo sprintf( esc_html__( 'Displaying %1$s&#8211;%2$s of %3$s', 'wpml-translation-management' ), $page_from, $page_to, $page_total ); ?>
</span>
<?php echo $page_links; ?>
</div>
<?php
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* Created by PhpStorm.
* User: bruce
* Date: 20/04/17
* Time: 11:39 AM
*/
class WPML_TM_WP_Query extends WP_Query {
public function get_found_count() {
return $this->found_posts;
}
public function getPostCount() {
return $this->post_count;
}
}

View File

@@ -0,0 +1,11 @@
<?php
interface IWPML_TM_Admin_Section_Factory {
/**
* Returns an instance of a class implementing \IWPML_TM_Admin_Section.
*
* @return \IWPML_TM_Admin_Section
*/
public function create();
}

View File

@@ -0,0 +1,53 @@
<?php
interface IWPML_TM_Admin_Section {
/**
* Returns a value which will be used for sorting the sections.
*
* @return int
*/
public function get_order();
/**
* Returns the unique slug of the sections which is used to build the URL for opening this section.
*
* @return string
*/
public function get_slug();
/**
* Returns one or more capabilities required to display this section.
*
* @return string|array
*/
public function get_capabilities();
/**
* Returns the caption to display in the section.
*
* @return string
*/
public function get_caption();
/**
* Returns the callback responsible for rendering the content of the section.
*
* @return callable
*/
public function get_callback();
/**
* This method is hooked to the `admin_enqueue_scripts` action.
*
* @param string $hook The current page.
*/
public function admin_enqueue_scripts( $hook );
/**
* Used to extend the logic for displaying/hiding the section.
*
* @return bool
*/
public function is_visible();
}

View File

@@ -0,0 +1,219 @@
<?php
use WPML\FP\Obj;
use WPML\FP\Fns;
use WPML\FP\Relation;
use WPML\TM\ATE\AutoTranslate\Endpoint\SyncLock;
use WPML\TM\ATE\Jobs;
use WPML\TM\Menu\TranslationQueue\PostTypeFilters;
use WPML\UIPage;
use WPML\TM\ATE\Review\ApproveTranslations;
use WPML\TM\ATE\Review\Cancel;
use WPML\TM\Jobs\Endpoint\Resign;
use WPML\TM\API\Basket;
use WPML\TM\API\Translators;
use WPML\Element\API\Languages;
use function WPML\FP\pipe;
class WPML_TM_Jobs_List_Script_Data {
const TM_JOBS_PAGE = 'tm-jobs';
const TRANSLATION_QUEUE_PAGE = 'translation-queue';
/** @var WPML_TM_Rest_Jobs_Language_Names */
private $language_names;
/** @var WPML_TM_Jobs_List_Translated_By_Filters */
private $translated_by_filter;
/** @var WPML_TM_Jobs_List_Translators */
private $translators;
/** @var WPML_TM_Jobs_List_Services */
private $services;
/**
* @param WPML_TM_Rest_Jobs_Language_Names|null $language_names
* @param WPML_TM_Jobs_List_Translated_By_Filters|null $translated_by_filters
* @param WPML_TM_Jobs_List_Translators|null $translators
* @param WPML_TM_Jobs_List_Services|null $services
*/
public function __construct(
WPML_TM_Rest_Jobs_Language_Names $language_names = null,
WPML_TM_Jobs_List_Translated_By_Filters $translated_by_filters = null,
WPML_TM_Jobs_List_Translators $translators = null,
WPML_TM_Jobs_List_Services $services = null
) {
if ( ! $language_names ) {
global $sitepress;
$language_names = new WPML_TM_Rest_Jobs_Language_Names( $sitepress );
}
$this->language_names = $language_names;
if ( ! $translators ) {
global $wpdb;
$translators = new WPML_TM_Jobs_List_Translators(
new WPML_Translator_Records(
$wpdb,
new WPML_WP_User_Query_Factory(),
wp_roles()
)
);
}
if ( ! $services ) {
$services = new WPML_TM_Jobs_List_Services( WPML_TM_Rest_Jobs_Translation_Service::create() );
}
if ( ! $translated_by_filters ) {
$translated_by_filters = new WPML_TM_Jobs_List_Translated_By_Filters( $services, $translators );
}
$this->translated_by_filter = $translated_by_filters;
$this->translators = $translators;
$this->services = $services;
}
/**
* @return array
*/
public function get() {
$translation_service = TranslationProxy::get_current_service();
if ( $translation_service ) {
$translation_service = [
'id' => $translation_service->id,
'name' => $translation_service->name,
];
}
$isATEEnabled = \WPML_TM_ATE_Status::is_enabled_and_activated();
$data = [
'isATEEnabled' => $isATEEnabled,
'ateJobsToSync' => $isATEEnabled ? Jobs::getJobsToSync() : [],
'languages' => $this->language_names->get_active_languages(),
'translatedByFilters' => $this->translated_by_filter->get(),
'localTranslators' => $this->translators->get(),
'translationServices' => $this->services->get(),
'isBasketUsed' => Basket::shouldUse(),
'translationService' => $translation_service,
'siteKey' => WP_Installer::instance()->get_site_key( 'wpml' ),
'batchUrl' => OTG_TRANSLATION_PROXY_URL . '/projects/%d/external',
'endpoints' => [
'syncLock' => SyncLock::class,
'approveTranslationsReviews' => ApproveTranslations::class,
'cancelTranslationReviews' => Cancel::class,
'resign' => Resign::class,
],
'types' => $this->getTypesForFilter(),
'queryFilters' => $this->getFiltersFromUrl(),
'page' => UIPage::isTMJobs( $_GET ) ? self::TM_JOBS_PAGE : self::TRANSLATION_QUEUE_PAGE,
'reviewMode' => \WPML\Setup\Option::getReviewMode(),
];
if ( UIPage::isTranslationQueue( $_GET ) ) {
global $sitepress;
$tmXliffVersion = $sitepress->get_setting( 'tm_xliff_version' );
$data['xliffExport'] = [
'nonce' => wp_create_nonce( 'xliff-export' ),
'translationQueueURL' => UIPage::getTranslationQueue(),
'xliffDefaultVersion' => $tmXliffVersion > 0 ? $tmXliffVersion : 12,
];
$data['hasTranslationServiceJobs'] = $this->hasTranslationServiceJobs();
$data['languagePairs'] = $this->buildLanguagePairs( Translators::getCurrent()->language_pairs );
} else {
$data['languagePairs'] = $this->getAllPossibleLanguagePairs();
}
return $data;
}
private function getAllPossibleLanguagePairs() {
$languages = Languages::getActive();
$createPair = function ( $currentLanguage ) use ( $languages ) {
$targets = Fns::reject( Relation::propEq( 'code', Obj::prop( 'code', $currentLanguage ) ), $languages );
return [ 'source' => $currentLanguage, 'targets' => $targets ];
};
$buildEntity = Obj::evolve( [
'source' => $this->extractDesiredPropertiesFromLanguage(),
'targets' => Fns::map( $this->extractDesiredPropertiesFromLanguage() ),
] );
return \wpml_collect( $languages )
->map( $createPair )
->map( $buildEntity )
->values()
->all();
}
private function buildLanguagePairs( $pairs ) {
$getLanguageDetails = Fns::memorize(
pipe(
Languages::getLanguageDetails(),
$this->extractDesiredPropertiesFromLanguage()
)
);
$buildPair = function ( $targetCodes, $sourceCode ) use ( $getLanguageDetails ) {
$source = $getLanguageDetails( $sourceCode );
$targets = Fns::map( $getLanguageDetails, $targetCodes );
return [ 'source' => $source, 'targets' => $targets ];
};
return \wpml_collect( $pairs )->map( $buildPair )->values()->toArray();
}
/**
* @return Closure
*/
private function extractDesiredPropertiesFromLanguage() {
return function ( $language ) {
return [
'code' => Obj::prop( 'code', $language ),
'name' => Obj::prop( 'display_name', $language ),
];
};
}
private function getTypesForFilter() {
$postTypeFilters = new PostTypeFilters( wpml_tm_get_jobs_repository( true, false ) );
return \wpml_collect( $postTypeFilters->get( [ 'include_unassigned' => true ] ) )
->map( function ( $label, $name ) {
return [ 'name' => $name, 'label' => $label ];
} )
->values();
}
private function getFiltersFromUrl() {
$filters = [];
if ( Obj::propOr( false, 'status', $_GET ) ) {
$filters['status'] = [ Obj::prop( 'status', $_GET ) ];
}
return $filters;
}
/**
* @return bool
*/
private function hasTranslationServiceJobs() {
$searchParams = new WPML_TM_Jobs_Search_Params();
$searchParams->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE );
$repository = wpml_tm_get_jobs_repository();
return $repository->get_count( $searchParams ) > 0;
}
}

View File

@@ -0,0 +1,48 @@
<?php
class WPML_TM_Jobs_List_Services {
/** @var wpdb */
private $wpdb;
/** @var WPML_TM_Rest_Jobs_Translation_Service */
private $service_names;
/** @var array|null */
private $cache;
public function __construct( WPML_TM_Rest_Jobs_Translation_Service $service_names ) {
global $wpdb;
$this->wpdb = $wpdb;
$this->service_names = $service_names;
}
public function get() {
if ( $this->cache === null ) {
$sql = "
SELECT *
FROM
(
(
SELECT translation_service
FROM {$this->wpdb->prefix}icl_translation_status
) UNION (
SELECT translation_service
FROM {$this->wpdb->prefix}icl_string_translations
)
) as services
WHERE translation_service != 'local' AND translation_service != ''
";
$this->cache = array_map( array( $this, 'map' ), $this->wpdb->get_col( $sql ) );
}
return $this->cache;
}
private function map( $translation_service_id ) {
return array(
'value' => $translation_service_id,
'label' => $this->service_names->get_name( $translation_service_id ),
);
}
}

View File

@@ -0,0 +1,52 @@
<?php
class WPML_TM_Jobs_List_Translated_By_Filters {
/** @var WPML_TM_Jobs_List_Services */
private $services;
/** @var WPML_TM_Jobs_List_Translators */
private $translators;
/**
* @param WPML_TM_Jobs_List_Services $services
* @param WPML_TM_Jobs_List_Translators $translators
*/
public function __construct( WPML_TM_Jobs_List_Services $services, WPML_TM_Jobs_List_Translators $translators ) {
$this->services = $services;
$this->translators = $translators;
}
/**
* @return array
*/
public function get() {
$options = array(
array(
'value' => 'any',
'label' => __( 'Anyone', 'wpml-translation-management' ),
),
);
$services = $this->services->get();
if ( $services ) {
$options[] = array(
'value' => 'any-service',
'label' => __( 'Any Translation Service', 'wpml-translation-management' ),
);
}
$translators = $this->translators->get();
if ( $translators ) {
$options[] = array(
'value' => 'any-local-translator',
'label' => __( 'Any WordPress Translator', 'wpml-translation-management' ),
);
}
return array(
'options' => $options,
'services' => $services,
'translators' => $translators,
);
}
}

View File

@@ -0,0 +1,65 @@
<?php
use \WPML\FP\Fns;
use \WPML\FP\Lst;
use \WPML\Element\API\Languages;
use function \WPML\FP\flip;
use function \WPML\FP\curryN;
class WPML_TM_Jobs_List_Translators {
/** @var WPML_Translator_Records */
private $translator_records;
/**
* @param WPML_Translator_Records $translator_records
*/
public function __construct( WPML_Translator_Records $translator_records ) {
$this->translator_records = $translator_records;
}
public function get() {
$translators = $this->translator_records->get_users_with_capability();
return array_map( [ $this, 'getTranslatorData' ], $translators );
}
private function getTranslatorData( $translator ) {
return [
'value' => $translator->ID,
'label' => $translator->display_name,
'languagePairs' => $this->getLanguagePairs( $translator ),
];
}
private function getLanguagePairs( $translator ) {
$isValidLanguage = Lst::includes( Fns::__, Lst::pluck( 'code', Languages::getAll() ) );
$sourceIsValidLanguage = flip( $isValidLanguage );
$getValidTargets = Fns::filter( $isValidLanguage );
$makePair = curryN(
2,
function ( $source, $target ) {
return [
'source' => $source,
'target' => $target,
];
}
);
$getAsPair = curryN(
3,
function ( $makePair, $targets, $source ) {
return Fns::map( $makePair( $source ), $targets );
}
);
return \wpml_collect( $translator->language_pairs )
->filter( $sourceIsValidLanguage )
->map( $getValidTargets )
->map( $getAsPair( $makePair ) )
->flatten( 1 )
->toArray();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace WPML\TM\Menu\McSetup;
use WPML\WP\OptionManager;
class CfMetaBoxOption {
const GROUP = 'core';
const CF_META_BOX_OPTION_KEY = 'show_cf_meta_box';
/**
* @return boolean
*/
public static function get() {
return OptionManager::getOr( false, self::GROUP, self::CF_META_BOX_OPTION_KEY );
}
/**
* @param boolean $value
*/
public static function update( $value ) {
OptionManager::updateWithoutAutoLoad( self::GROUP, self::CF_META_BOX_OPTION_KEY, $value );
}
}

View File

@@ -0,0 +1,156 @@
<?php
/**
* @author OnTheGo Systems
*/
class WPML_TM_MCS_ATE_Strings {
const AMS_STATUS_ACTIVE_NOT_ALL_SUBSCRIBED = 'active-not-all-subscribed';
/**
* @var WPML_TM_ATE_Authentication
*/
private $authentication;
private $authentication_data;
/**
* @var WPML_TM_ATE_AMS_Endpoints
*/
private $endpoints;
private $statuses;
/**
* WPML_TM_MCS_ATE constructor.
*
* @param WPML_TM_ATE_Authentication $authentication
* @param WPML_TM_ATE_AMS_Endpoints $endpoints
*/
public function __construct( WPML_TM_ATE_Authentication $authentication, WPML_TM_ATE_AMS_Endpoints $endpoints ) {
$this->authentication = $authentication;
$this->endpoints = $endpoints;
$this->authentication_data = get_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, array() );
$this->statuses = array(
WPML_TM_ATE_Authentication::AMS_STATUS_NON_ACTIVE => array(
'type' => 'error',
'message' => array(
'status' => __( 'Advanced Translation Editor is not active yet', 'wpml-translation-management' ),
'text' => __(
'Request activation to receive an email with directions to activate the service.',
'wpml-translation-management'
),
),
'button' => __( 'Request activation', 'wpml-translation-management' ),
),
WPML_TM_ATE_Authentication::AMS_STATUS_ENABLED => array(
'type' => 'info',
'message' => array(
'status' => __( 'Advanced Translation Editor is being activated', 'wpml-translation-management' ),
'text' => '',
),
'button' => '',
),
WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE => array(
'type' => 'success',
'message' => array(
'status' => __( 'Advanced Translation Editor is enabled and active', 'wpml-translation-management' ),
'text' => '',
),
'button' => __( 'Advanced Translation Editor is active', 'wpml-translation-management' ),
),
self::AMS_STATUS_ACTIVE_NOT_ALL_SUBSCRIBED => array(
'type' => 'success',
'message' => array(
'status' => __( "WPML's Advanced Translation Editor is enabled, but not all your translators can use it.", 'wpml-translation-management' ),
'text' => '',
),
'button' => __( 'Advanced Translation Editor is active', 'wpml-translation-management' ),
),
);
}
/**
* @return string|WP_Error
* @throws \InvalidArgumentException
*/
public function get_auto_login() {
$shared = null;
if ( array_key_exists( 'shared', $this->authentication_data ) ) {
$shared = $this->authentication_data['shared'];
}
if ( $shared ) {
$url = $this->endpoints->get_ams_auto_login();
$user = wp_get_current_user();
$user_email = $user->user_email;
return $this->authentication->get_signed_url_with_parameters(
'GET',
$url,
array(
'translation_manager' => $user_email,
'return_url' => WPML_TM_Page::get_translators_url( array( 'refresh_subscriptions' => '1' ) ),
)
);
}
return '#';
}
public function get_status_HTML( $status, $all_users_have_subscription = true ) {
if ( $status === WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE && ! $all_users_have_subscription ) {
$status = self::AMS_STATUS_ACTIVE_NOT_ALL_SUBSCRIBED;
}
$message = $this->get_status_attribute( $status, 'message' );
return '<strong>' . $message['status'] . '</strong>' . $message['text'];
}
/**
* @return string
*/
public function get_status() {
$ate_status = WPML_TM_ATE_Authentication::AMS_STATUS_NON_ACTIVE;
if ( array_key_exists( 'status', $this->authentication_data ) ) {
$ate_status = $this->authentication_data['status'];
}
return $ate_status;
}
/**
* @param string $attribute
* @param null|mixed $default
*
* @return mixed
*/
public function get_current_status_attribute( $attribute, $default = null ) {
return $this->get_status_attribute( $this->get_status(), $attribute, $default );
}
/**
* @param string $status
* @param string $attribute
* @param null|mixed $default
*
* @return mixed
*/
public function get_status_attribute( $status, $attribute, $default = null ) {
$status_attributes = $this->statuses[ $status ];
if ( array_key_exists( $attribute, $status_attributes ) ) {
return $status_attributes[ $attribute ];
}
return $default;
}
public function get_statuses() {
return $this->statuses;
}
public function get_synchronize_button_text() {
return __( 'Synchronize translators and translation managers', 'wpml-translation-management' );
}
}

View File

@@ -0,0 +1,127 @@
<?php
use WPML\TM\ATE\ClonedSites\Lock;
/**
* @author OnTheGo Systems
*/
class WPML_TM_MCS_ATE extends WPML_Twig_Template_Loader {
/**
* @var WPML_TM_ATE_Authentication
*/
private $authentication;
private $authentication_data;
/**
* @var WPML_TM_ATE_AMS_Endpoints
*/
private $endpoints;
/**
* @var WPML_TM_MCS_ATE_Strings
*/
private $strings;
private $model = array();
/**
*
* /**
* WPML_TM_MCS_ATE constructor.
*
* @param WPML_TM_ATE_Authentication $authentication
* @param WPML_TM_ATE_AMS_Endpoints $endpoints
*
* @param WPML_TM_MCS_ATE_Strings $strings
*/
public function __construct(
WPML_TM_ATE_Authentication $authentication,
WPML_TM_ATE_AMS_Endpoints $endpoints,
WPML_TM_MCS_ATE_Strings $strings
) {
parent::__construct(
array(
$this->get_template_path(),
)
);
$this->authentication = $authentication;
$this->endpoints = $endpoints;
$this->strings = $strings;
$this->authentication_data = get_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, array() );
$wpml_support = esc_html__( 'WPML support', 'wpml-translation-management' );
$wpml_support_link = '<a target="_blank" rel="noopener" href="https://wpml.org/forums/forum/english-support/">' . $wpml_support . '</a>';
$this->model = [
'status_button_text' => $this->get_status_button_text(),
'synchronize_button_text' => $this->strings->get_synchronize_button_text(),
'is_ate_communication_locked' => Lock::isLocked(),
'strings' => [
'error_help' => sprintf( esc_html__( 'Please try again in a few minutes. If the problem persists, please contact %s.', 'wpml-translation-management' ), $wpml_support_link ),
],
];
}
/**
* @return string
*/
public function get_template_path() {
return WPML_TM_PATH . '/templates/ATE';
}
public function init_hooks() {
add_action( 'wpml_tm_mcs_' . ICL_TM_TMETHOD_ATE, array( $this, 'render' ) );
add_action( 'wpml_tm_mcs_troubleshooting', [ $this, 'renderTroubleshooting' ] );
}
/**
* @param array $args
*
* @return array
*/
public function get_model( array $args = array() ) {
if ( array_key_exists( 'wizard', $args ) ) {
$this->model['strings']['error_help'] = esc_html__( 'You can continue the Translation Management configuration later by going to WPML -> Settings -> Translation Editor.', 'wpml-translation-management' );
}
return $this->model;
}
public function render() {
echo $this->get_template()
->show( $this->get_model(), 'mcs-ate-controls.twig' );
}
public function renderTroubleshooting() {
echo '<div id="synchronize-ate-ams"></div>';
}
public function get_strings() {
return $this->strings;
}
private function has_translators() {
/** @var TranslationManagement $iclTranslationManagement */
global $iclTranslationManagement;
return $iclTranslationManagement->has_translators();
}
/**
* @return mixed
*/
private function get_status_button_text() {
return $this->strings->get_current_status_attribute( 'button' );
}
/**
* @return array
*/
public function get_script_data() {
return array(
'hasTranslators' => $this->has_translators(),
'currentStatus' => $this->strings->get_status(),
'statuses' => $this->strings->get_statuses(),
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
class WPML_TM_MCS_Custom_Field_Settings_Menu_Factory {
/** @var WPML_Custom_Field_Setting_Factory $setting_factory */
private $setting_factory;
/** @var WPML_UI_Unlock_Button $unlock_button */
private $unlock_button;
/** @var WPML_Custom_Field_Setting_Query_Factory $query_factory */
private $query_factory;
/**
* @return WPML_TM_MCS_Post_Custom_Field_Settings_Menu
*/
public function create_post() {
return new WPML_TM_MCS_Post_Custom_Field_Settings_Menu(
$this->get_setting_factory(),
$this->get_unlock_button(),
$this->get_query_factory()
);
}
/**
* @return WPML_TM_MCS_Term_Custom_Field_Settings_Menu
*/
public function create_term() {
return new WPML_TM_MCS_Term_Custom_Field_Settings_Menu(
$this->get_setting_factory(),
$this->get_unlock_button(),
$this->get_query_factory()
);
}
private function get_setting_factory() {
global $iclTranslationManagement;
if ( null === $this->setting_factory ) {
$this->setting_factory = new WPML_Custom_Field_Setting_Factory( $iclTranslationManagement );
$this->setting_factory->show_system_fields = array_key_exists( 'show_system_fields', $_GET )
? (bool) $_GET['show_system_fields'] : false;
}
return $this->setting_factory;
}
private function get_unlock_button() {
if ( null === $this->unlock_button ) {
$this->unlock_button = new WPML_UI_Unlock_Button();
}
return $this->unlock_button;
}
private function get_query_factory() {
if ( null === $this->query_factory ) {
$this->query_factory = new WPML_Custom_Field_Setting_Query_Factory();
}
return $this->query_factory;
}
}

View File

@@ -0,0 +1,302 @@
<?php
use WPML\TM\Menu\McSetup\CfMetaBoxOption;
abstract class WPML_TM_MCS_Custom_Field_Settings_Menu {
/** @var WPML_Custom_Field_Setting_Factory $settings_factory */
protected $settings_factory;
/** @var WPML_UI_Unlock_Button $unlock_button_ui */
private $unlock_button_ui;
/** @var WPML_Custom_Field_Setting_Query_Factory $query_factory */
private $query_factory;
/** @var WPML_Custom_Field_Setting_Query $query */
private $query;
/** @var string[] Custom field keys */
private $custom_fields_keys;
/** @var int $total_keys */
private $total_keys;
/** @var array Custom field options */
private $custom_field_options;
/** @var int Initial setting of items per page */
const ITEMS_PER_PAGE = 20;
public function __construct(
WPML_Custom_Field_Setting_Factory $settings_factory,
WPML_UI_Unlock_Button $unlock_button_ui,
WPML_Custom_Field_Setting_Query_Factory $query_factory
) {
$this->settings_factory = $settings_factory;
$this->unlock_button_ui = $unlock_button_ui;
$this->query_factory = $query_factory;
$this->custom_field_options = array(
WPML_IGNORE_CUSTOM_FIELD => __( "Don't translate", 'wpml-translation-management' ),
WPML_COPY_CUSTOM_FIELD => __( 'Copy from original to translation', 'wpml-translation-management' ),
WPML_COPY_ONCE_CUSTOM_FIELD => __( 'Copy once', 'wpml-translation-management' ),
WPML_TRANSLATE_CUSTOM_FIELD => __( 'Translate', 'wpml-translation-management' ),
);
}
/**
* This will fetch the data from DB
* depending on the user inputs (pagination/search)
*
* @param array $args
*/
public function init_data( array $args = array() ) {
if ( null === $this->custom_fields_keys ) {
$args = array_merge(
array(
'hide_system_fields' => ! $this->settings_factory->show_system_fields,
'items_per_page' => self::ITEMS_PER_PAGE,
'page' => 1,
),
$args
);
$this->custom_fields_keys = $this->get_query()->get( $args );
$this->total_keys = $this->get_query()->get_total_rows();
if ( $this->custom_fields_keys ) {
natcasesort( $this->custom_fields_keys );
}
}
}
/**
* @return string
*/
public function render() {
ob_start();
?>
<div class="wpml-section wpml-section-<?php echo esc_attr( $this->kind_shorthand() ); ?>-translation"
id="ml-content-setup-sec-<?php echo esc_attr( $this->kind_shorthand() ); ?>">
<div class="wpml-section-header">
<h3><?php echo esc_html( $this->get_title() ); ?></h3>
<p>
<?php
// We need htmlspecialchars() here only for testing, as DOMDocument::loadHTML() cannot parse url with '&'.
$toggle_system_fields = array(
'url' => htmlspecialchars(
add_query_arg(
array(
'show_system_fields' => ! $this->settings_factory->show_system_fields,
),
admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS ) . '#ml-content-setup-sec-' . $this->kind_shorthand()
)
),
'text' => $this->settings_factory->show_system_fields ?
__( 'Hide system fields', 'wpml-translation-management' ) :
__( 'Show system fields', 'wpml-translation-management' ),
);
?>
<a href="<?php echo esc_url( $toggle_system_fields['url'] ); ?>"><?php echo esc_html( $toggle_system_fields['text'] ); ?></a>
</p>
</div>
<div class="wpml-section-content wpml-section-content-wide">
<form
id="icl_<?php echo esc_attr( $this->kind_shorthand() ); ?>_translation"
data-type="<?php echo esc_attr( $this->kind_shorthand() ); ?>"
name="icl_<?php echo esc_attr( $this->kind_shorthand() ); ?>_translation"
class="wpml-custom-fields-settings" action="">
<?php wp_nonce_field( 'icl_' . $this->kind_shorthand() . '_translation_nonce', '_icl_nonce' ); ?>
<?php
if ( empty( $this->custom_fields_keys ) ) {
?>
<p class="no-data-found">
<?php echo esc_html( $this->get_no_data_message() ); ?>
</p>
<?php
} else {
if ( esc_attr( $this->kind_shorthand() ) === 'cf' ) {
?>
<label><input type="checkbox" <?php if ( CfMetaBoxOption::get() ) : ?> checked="checked"
<?php endif; ?>id="show_cf_meta_box" name="translate_media"
value="1"/>&nbsp;<?php esc_html_e( 'Show "Multilingual Content Setup" meta box on post edit screen.', 'sitepress' ); ?>
</label>
<?php
}
?>
<div class="wpml-flex-table wpml-translation-setup-table wpml-margin-top-sm">
<?php echo $this->render_heading(); ?>
<div class="wpml-flex-table-body">
<?php
$this->render_body();
?>
</div>
</div>
<?php
$this->render_pagination( self::ITEMS_PER_PAGE, 1 );
?>
<p class="buttons-wrap">
<span class="icl_ajx_response"
id="icl_ajx_response_<?php echo esc_attr( $this->kind_shorthand() ); ?>"></span>
<input type="submit" class="button-primary"
value="<?php echo esc_attr__( 'Save', 'wpml-translation-management' ); ?>"/>
</p>
<?php
}
?>
</form>
</div>
<!-- .wpml-section-content -->
</div> <!-- .wpml-section -->
<?php
return ob_get_clean();
}
/**
* @return string
*/
abstract protected function kind_shorthand();
/**
* @return string
*/
abstract protected function get_title();
abstract protected function get_meta_type();
/**
* @param string $key
*
* @return WPML_Custom_Field_Setting
*/
abstract protected function get_setting( $key );
private function render_radio( $cf_key, $html_disabled, $status, $ref_status ) {
ob_start();
?>
<input type="radio" name="<?php echo $this->get_radio_name( $cf_key ); ?>"
value="<?php echo esc_attr( $ref_status ); ?>"
title="<?php echo esc_attr( $ref_status ); ?>" <?php echo $html_disabled; ?>
<?php
if ( $status == $ref_status ) :
?>
checked<?php endif; ?> />
<?php
return ob_get_clean();
}
private function get_radio_name( $cf_key ) {
return 'cf[' . esc_attr( base64_encode( $cf_key ) ) . ']';
}
private function get_unlock_name( $cf_key ) {
return 'cf_unlocked[' . esc_attr( base64_encode( $cf_key ) ) . ']';
}
/**
* @return string header and footer of the setting table
*/
private function render_heading() {
ob_start();
?>
<div class="wpml-flex-table-header wpml-flex-table-sticky">
<?php $this->render_search(); ?>
<div class="wpml-flex-table-row">
<div class="wpml-flex-table-cell name">
<?php echo esc_html( $this->get_column_header( 'name' ) ); ?>
</div>
<div class="wpml-flex-table-cell text-center">
<?php echo esc_html__( "Don't translate", 'wpml-translation-management' ); ?>
</div>
<div class="wpml-flex-table-cell text-center">
<?php echo esc_html_x( 'Copy', 'Verb', 'wpml-translation-management' ); ?>
</div>
<div class="wpml-flex-table-cell text-center">
<?php echo esc_html__( 'Copy once', 'wpml-translation-management' ); ?>
</div>
<div class="wpml-flex-table-cell text-center">
<?php echo esc_html__( 'Translate', 'wpml-translation-management' ); ?>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* Render search box for Custom Field Settings.
*
* @param string $search_string Search String.
*/
public function render_search( $search_string = '' ) {
$search = new WPML_TM_MCS_Search_Factory();
echo $search->create( $search_string )->render();
}
/**
* Render body of Custom Field Settings.
*/
public function render_body() {
foreach ( $this->custom_fields_keys as $cf_key ) {
$setting = $this->get_setting( $cf_key );
$status = $setting->status();
$html_disabled = $setting->is_read_only() && ! $setting->is_unlocked() ? 'disabled="disabled"' : '';
?>
<div class="wpml-flex-table-row">
<div class="wpml-flex-table-cell name">
<?php
$this->unlock_button_ui->render( $setting->is_read_only(), $setting->is_unlocked(), $this->get_radio_name( $cf_key ), $this->get_unlock_name( $cf_key ) );
echo esc_html( $cf_key );
?>
</div>
<?php
foreach ( $this->custom_field_options as $ref_status => $title ) {
?>
<div class="wpml-flex-table-cell text-center">
<?php
echo $this->render_radio( $cf_key, $html_disabled, $status, $ref_status );
?>
</div>
<?php
}
?>
</div>
<?php
}
}
/**
* Render pagination for Custom Field Settings.
*
* @param int $items_per_page Items per page to display.
* @param int $current_page Which page to display.
*/
public function render_pagination( $items_per_page, $current_page ) {
$pagination = new WPML_TM_MCS_Pagination_Render_Factory( $items_per_page );
echo $pagination->create( $this->total_keys, $current_page )->render();
}
abstract public function get_no_data_message();
abstract public function get_column_header( $id );
/**
* @return WPML_Custom_Field_Setting_Query
*/
private function get_query() {
if ( null === $this->query ) {
$this->query = $this->query_factory->create( $this->get_meta_type() );
}
return $this->query;
}
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Class WPML_TM_MCS_Pagination_Ajax_Factory
*/
class WPML_TM_MCS_Pagination_Ajax_Factory implements IWPML_AJAX_Action_Loader {
/**
* Create MCS Pagination.
*
* @return WPML_TM_MCS_Pagination_Ajax
*/
public function create() {
return new WPML_TM_MCS_Pagination_Ajax( new WPML_TM_MCS_Custom_Field_Settings_Menu_Factory() );
}
}

View File

@@ -0,0 +1,66 @@
<?php
use WPML\TM\Menu\McSetup\CfMetaBoxOption;
/**
* Class WPML_TM_MCS_Pagination_Ajax
*/
class WPML_TM_MCS_Pagination_Ajax {
/** @var WPML_TM_MCS_Custom_Field_Settings_Menu_Factory */
private $menu_factory;
public function __construct( WPML_TM_MCS_Custom_Field_Settings_Menu_Factory $menu_factory ) {
$this->menu_factory = $menu_factory;
}
/**
* Define Ajax hooks.
*/
public function add_hooks() {
add_action( 'wp_ajax_wpml_update_mcs_cf', array( $this, 'update_mcs_cf' ) );
}
/**
* Update custom fields form.
*/
public function update_mcs_cf() {
if ( isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], 'icl_' . $_POST['type'] . '_translation_nonce' ) ) {
$args = array(
'items_per_page' => intval( $_POST['items_per_page'] ),
'page' => intval( $_POST['paged'] ),
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
'hide_system_fields' => ! isset( $_POST['show_system_fields'] ) || ! filter_var( $_POST['show_system_fields'], FILTER_VALIDATE_BOOLEAN ),
'show_cf_meta_box' => isset( $_POST['show_cf_meta_box'] ) && filter_var( $_POST['show_cf_meta_box'], FILTER_VALIDATE_BOOLEAN ),
);
$menu_item = null;
if ( 'cf' === $_POST['type'] ) {
$menu_item = $this->menu_factory->create_post();
CfMetaBoxOption::update( $args['show_cf_meta_box'] );
} elseif ( 'tcf' === $_POST['type'] ) {
$menu_item = $this->menu_factory->create_term();
}
if ( $menu_item ) {
$result = array();
ob_start();
$menu_item->init_data( $args );
$menu_item->render_body();
$result['body'] = ob_get_clean();
ob_start();
$menu_item->render_pagination( $args['items_per_page'], $args['page'] );
$result['pagination'] = ob_get_clean();
wp_send_json_success( $result );
}
}
wp_send_json_error(
array(
'message' => __( 'Invalid Request.', 'wpml-translation-management' ),
)
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Class WPML_TM_MCS_Pagination_Render_Factory
*/
class WPML_TM_MCS_Pagination_Render_Factory {
/**
* @var int Items per page
*/
private $items_per_page;
/**
* WPML_TM_MCS_Pagination_Render_Factory constructor.
*
* @param int $items_per_page
*/
public function __construct( $items_per_page ) {
$this->items_per_page = $items_per_page;
}
/**
* @param $items_per_page
* @param $total_items
* @param int $current_page
*
* @return WPML_TM_MCS_Pagination_Render
*/
public function create( $total_items, $current_page = 1 ) {
$pagination = new WPML_Admin_Pagination();
$pagination->set_items_per_page( $this->items_per_page );
$pagination->set_total_items( $total_items );
$pagination->set_current_page( $current_page );
$template = new WPML_Twig_Template_Loader(
array(
WPML_TM_PATH . '/templates/menus/mcsetup',
)
);
return new WPML_TM_MCS_Pagination_Render( $template->get_template(), $pagination );
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* WPML_TM_MCS_Pagination_Render class file.
*
* @package wpml-translation-management
*/
/**
* Class WPML_TM_MCS_Pagination_Render
*/
class WPML_TM_MCS_Pagination_Render {
/**
* Twig template path.
*/
const TM_MCS_PAGINATION_TEMPLATE = 'tm-mcs-pagination.twig';
/**
* Twig template service.
*
* @var IWPML_Template_Service
*/
private $template;
/**
* Admin pagination instance.
*
* @var WPML_Admin_Pagination
*/
private $pagination;
/**
* Items per page.
*
* @var int Items per page
*/
private $items_per_page;
/**
* Total items.
*
* @var int Total items
*/
private $total_items;
/**
* Current page number.
*
* @var int Current page
*/
private $current_page;
/**
* Total number of pages.
*
* @var int Total pages
*/
private $total_pages;
/**
* WPML_TM_MCS_Pagination_Render constructor.
*
* @param IWPML_Template_Service $template Twig template service.
* @param WPML_Admin_Pagination $pagination Admin pagination object.
*/
public function __construct( IWPML_Template_Service $template, WPML_Admin_Pagination $pagination ) {
$this->template = $template;
$this->pagination = $pagination;
$this->items_per_page = $pagination->get_items_per_page();
$this->total_items = $pagination->get_total_items();
$this->current_page = $pagination->get_current_page();
$this->total_pages = $pagination->get_total_pages();
}
/**
* Get twig model.
*
* @return array
*/
private function get_model() {
$from = min( ( $this->current_page - 1 ) * $this->items_per_page + 1, $this->total_items );
$to = min( $this->current_page * $this->items_per_page, $this->total_items );
if ( - 1 === $this->current_page ) {
$from = min( 1, $this->total_items );
$to = $this->total_items;
}
$model = array(
'strings' => array(
'displaying' => __( 'Displaying', 'wpml-translation-management' ),
'of' => __( 'of', 'wpml-translation-management' ),
'display_all' => __( 'Display all results', 'wpml-translation-management' ),
'display_less' => __( 'Display 20 results per page', 'wpml-translation-management' ),
'nothing_found' => __( 'Nothing found', 'wpml-translation-management' ),
),
'pagination' => $this->pagination,
'from' => number_format_i18n( $from ),
'to' => number_format_i18n( $to ),
'current_page' => $this->current_page,
'total_items' => $this->total_items,
'total_items_i18n' => number_format_i18n( $this->total_items ),
'total_pages' => $this->total_pages,
'paginate_links' => $this->paginate_links(),
'select' => array( 10, 20, 50, 100 ),
'select_value' => $this->items_per_page,
);
return $model;
}
/**
* Render model via twig.
*
* @return mixed
*/
public function render() {
return $this->template->show( $this->get_model(), self::TM_MCS_PAGINATION_TEMPLATE );
}
/**
* Paginate links.
*
* @return array
*/
public function paginate_links() {
$page_links = array();
if ( - 1 === $this->current_page ) {
return $page_links;
}
if ( $this->total_pages < 2 ) {
return $page_links;
}
$end_size = 1;
$mid_size = 2;
$dots = false;
for ( $n = 1; $n <= $this->total_pages; $n ++ ) {
if ( $n === $this->current_page ) {
$page_links[] = array(
'class' => 'current',
'number' => number_format_i18n( $n ),
);
} else {
if (
$n <= $end_size ||
( $this->current_page && $n >= $this->current_page - $mid_size && $n <= $this->current_page + $mid_size ) ||
$n > $this->total_pages - $end_size
) {
$page_links[] = array(
'class' => '',
'number' => number_format_i18n( $n ),
);
$dots = true;
} elseif ( $dots ) {
$page_links[] = array(
'class' => 'dots',
'number' => '…',
);
$dots = false;
}
}
}
return $page_links;
}
}

View File

@@ -0,0 +1,54 @@
<?php
class WPML_TM_MCS_Post_Custom_Field_Settings_Menu extends WPML_TM_MCS_Custom_Field_Settings_Menu {
/**
* @return string
*/
protected function get_meta_type() {
return WPML_Custom_Field_Setting_Query_Factory::TYPE_POSTMETA;
}
/**
* @param string $key
*
* @return WPML_Post_Custom_Field_Setting
*/
protected function get_setting( $key ) {
return $this->settings_factory->post_meta_setting( $key );
}
/**
* @return string
*/
protected function get_title() {
return __( 'Custom Fields Translation', 'wpml-translation-management' );
}
/**
* @return string
*/
protected function kind_shorthand() {
return 'cf';
}
/**
* @return string
*/
public function get_no_data_message() {
return __( 'No custom fields found. It is possible that they will only show up here after you add more posts after installing a new plugin.', 'wpml-translation-management' );
}
/**
* @param string $id
*
* @return string
*/
public function get_column_header( $id ) {
$header = $id;
if('name' === $id) {
$header = __( 'Custom fields', 'wpml-translation-management' );
}
return $header;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* Class WPML_TM_MCS_Search_Factory
*/
class WPML_TM_MCS_Search_Factory {
/**
* Create MCS Search.
*
* @param string $search_string
*
* @return WPML_TM_MCS_Search_Render
*/
public function create( $search_string = '' ) {
$template = new WPML_Twig_Template_Loader(
array(
WPML_TM_PATH . '/templates/menus/mcsetup',
)
);
return new WPML_TM_MCS_Search_Render( $template->get_template(), $search_string );
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Class WPML_TM_MCS_Search_Render
*/
class WPML_TM_MCS_Search_Render {
/**
* Twig template path.
*/
const TM_MCS_SEARCH_TEMPLATE = 'tm-mcs-search.twig';
/**
* @var IWPML_Template_Service
*/
private $template;
/**
* @var string Search string
*/
private $search_string;
/**
* WPML_TM_MCS_Search_Render constructor.
*
* @param IWPML_Template_Service $template Twig template service.
* @param string $search_string Search string.
*/
public function __construct( IWPML_Template_Service $template, $search_string ) {
$this->template = $template;
$this->search_string = $search_string;
}
/**
* Get twig model.
*
* @return array
*/
public function get_model() {
$model = array(
'strings' => array(
'search_for' => __( 'Search for', 'wpml-translation-management' ),
),
'search_string' => $this->search_string,
);
return $model;
}
/**
* Render model via twig.
*
* @return mixed
*/
public function render() {
return $this->template->show( $this->get_model(), self::TM_MCS_SEARCH_TEMPLATE );
}
}

View File

@@ -0,0 +1,50 @@
<?php
abstract class WPML_TM_MCS_Section_UI {
private $id;
private $title;
public function __construct( $id, $title ) {
$this->id = $id;
$this->title = $title;
}
/**
* @return mixed
*/
public function get_id() {
return $this->id;
}
public function add_hooks() {
add_filter( 'wpml_mcsetup_navigation_links', array( $this, 'mcsetup_navigation_links' ) );
}
public function mcsetup_navigation_links( array $mcsetup_sections ) {
$mcsetup_sections[ $this->id ] = esc_html( $this->title );
return $mcsetup_sections;
}
public function render() {
$output = '';
$output .= '<div class="wpml-section" id="' . esc_attr( $this->id ) . '">';
$output .= '<div class="wpml-section-header">';
$output .= '<h3>' . esc_html( $this->title ) . '</h3>';
$output .= '</div>';
$output .= '<div class="wpml-section-content">';
$output .= $this->render_content();
$output .= '</div>';
$output .= '</div>';
return $output;
}
/**
* @return string
*/
abstract protected function render_content();
}

View File

@@ -0,0 +1,55 @@
<?php
class WPML_TM_MCS_Term_Custom_Field_Settings_Menu extends WPML_TM_MCS_Custom_Field_Settings_Menu {
/**
* @return string
*/
protected function get_meta_type() {
return WPML_Custom_Field_Setting_Query_Factory::TYPE_TERMMETA;
}
/**
* @param string $key
*
* @return WPML_Term_Custom_Field_Setting
*/
protected function get_setting( $key ) {
return $this->settings_factory->term_meta_setting( $key );
}
/**
* @return string
*/
protected function get_title() {
return __( 'Custom Term Meta Translation', 'wpml-translation-management' );
}
/**
* @return string
*/
protected function kind_shorthand() {
return 'tcf';
}
/**
* @return string
*/
public function get_no_data_message() {
return __( 'No term meta found. It is possible that they will only show up here after you add/create them.', 'wpml-translation-management' );
}
/**
* @param string $id
*
* @return string
*/
public function get_column_header( $id ) {
$header = $id;
if ( 'name' === $id ) {
$header = __( 'Term Meta', 'wpml-translation-management' );
}
return $header;
}
}

View File

@@ -0,0 +1,52 @@
<?php
class WPML_TM_Options_Ajax {
const NONCE_TRANSLATED_DOCUMENT = 'wpml-translated-document-options-nonce';
private $sitepress;
public function __construct( SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
public function ajax_hooks() {
add_action( 'wp_ajax_wpml_translated_document_options', array( $this, 'wpml_translated_document_options' ) );
}
public function wpml_translated_document_options() {
if ( ! $this->is_valid_request() ) {
wp_send_json_error();
} else {
$settings = $this->sitepress->get_settings();
if ( array_key_exists( 'document_status', $_POST ) ) {
$settings['translated_document_status'] = filter_var( $_POST['document_status'], FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE );
}
if ( array_key_exists( 'page_url', $_POST ) ) {
$settings['translated_document_page_url'] = filter_var( $_POST['page_url'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE );
}
if ( $settings ) {
$this->sitepress->save_settings( $settings );
}
wp_send_json_success();
}
}
private function is_valid_request() {
$valid_request = true;
if ( ! array_key_exists( 'nonce', $_POST ) ) {
$valid_request = false;
}
if ( $valid_request ) {
$nonce = $_POST['nonce'];
$nonce_is_valid = wp_verify_nonce( $nonce, self::NONCE_TRANSLATED_DOCUMENT );
if ( ! $nonce_is_valid ) {
$valid_request = false;
}
}
return $valid_request;
}
}

View File

@@ -0,0 +1,72 @@
<?php
class WPML_TM_Pickup_Mode_Ajax {
const NONCE_PICKUP_MODE = 'wpml_save_translation_pickup_mode';
/**
* @var SitePress
*/
private $sitepress;
/**
* @var WPML_Update_PickUp_Method
*/
private $update_pickup_mode;
/**
* @var WPML_Pro_Translation
*/
private $icl_pro_translation;
public function __construct( SitePress $sitepress, WPML_Pro_Translation $icl_pro_translation ) {
$this->sitepress = $sitepress;
$this->icl_pro_translation = $icl_pro_translation;
$this->update_pickup_mode = new WPML_Update_PickUp_Method( $this->sitepress );
}
public function ajax_hooks() {
add_action( 'wp_ajax_wpml_save_translation_pickup_mode', array( $this, 'wpml_save_translation_pickup_mode' ) );
}
public function wpml_save_translation_pickup_mode() {
try {
if ( ! $this->is_valid_request() ) {
throw new InvalidArgumentException('Request is not valid');
}
if ( ! array_key_exists('pickup_mode', $_POST ) ) {
throw new InvalidArgumentException();
}
$available_pickup_modes = array( 0, 1 );
$pickup_mode = filter_var( $_POST['pickup_mode'], FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE );
if ( ! in_array( (int) $pickup_mode, $available_pickup_modes, true ) ) {
throw new InvalidArgumentException();
}
$data['icl_translation_pickup_method'] = $pickup_mode;
$this->update_pickup_mode->update_pickup_method( $data, $this->icl_pro_translation->get_current_project() );
wp_send_json_success();
} catch ( InvalidArgumentException $e ) {
wp_send_json_error();
}
}
private function is_valid_request() {
$valid_request = true;
if ( ! array_key_exists( 'nonce', $_POST ) ) {
$valid_request = false;
}
if ( $valid_request ) {
$nonce = $_POST['nonce'];
$nonce_is_valid = wp_verify_nonce( $nonce, self::NONCE_PICKUP_MODE );
if ( ! $nonce_is_valid ) {
$valid_request = false;
}
}
return $valid_request;
}
}

View File

@@ -0,0 +1,64 @@
<?php
class WPML_Translate_Link_Targets_UI extends WPML_TM_MCS_Section_UI {
const ID = 'ml-content-setup-sec-links-target';
/** @var WPDB $wpdb */
private $wpdb;
/** @var WPML_Pro_Translation $pro_translation */
private $pro_translation;
/** @var WPML_WP_API $wp_api */
private $wp_api;
/** @var SitePress $sitepress */
private $sitepress;
public function __construct( $title, $wpdb, $sitepress, $pro_translation ) {
parent::__construct( self::ID, $title );
$this->wpdb = $wpdb;
$this->pro_translation = $pro_translation;
$this->sitepress = $sitepress;
}
/**
* @return string
*/
protected function render_content() {
$output = '';
$main_message = __( 'Adjust links in posts so they point to the translated content', 'wpml-translation-management' );
$complete_message = __( 'All posts have been processed. %s links were changed to point to the translated content.', 'wpml-translation-management' );
$string_count = 0;
$posts = new WPML_Translate_Link_Targets_In_Posts_Global( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $this->pro_translation );
$post_count = $posts->get_number_to_be_fixed();
if ( defined( 'WPML_ST_VERSION' ) ) {
$strings = new WPML_Translate_Link_Targets_In_Strings_Global( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $this->wp_api, $this->pro_translation );
$string_count = $strings->get_number_to_be_fixed();
$main_message = __( 'Adjust links in posts and strings so they point to the translated content', 'wpml-translation-management' );
$complete_message = __( 'All posts and strings have been processed. %s links were changed to point to the translated content.', 'wpml-translation-management' );
}
$data_attributes = array(
'post-message' => esc_attr__( 'Processing posts... %1$s of %2$s done.', 'wpml-translation-management' ),
'post-count' => $post_count,
'string-message' => esc_attr__( 'Processing strings... %1$s of %2$s done.', 'wpml-translation-management' ),
'string-count' => $string_count,
'complete-message' => esc_attr( $complete_message ),
);
$output .= '<p>' . $main_message . '</p>';
$output .= '<button id="wpml-scan-link-targets" class="button-secondary"';
foreach ( $data_attributes as $key => $value ) {
$output .= ' data-' . $key . '="' . $value . '"';
}
$output .= '>' . esc_html__( 'Scan now and adjust links', 'wpml-translation-management' ) . '</button>';
$output .= '<span class="spinner"> </span>';
$output .= '<p class="results"> </p>';
$output .= wp_nonce_field( 'WPML_Ajax_Update_Link_Targets', 'wpml-translate-link-targets', true, false );
return $output;
}
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Class WPML_TM_Polling_Box
*/
class WPML_TM_Polling_Box {
/**
* Renders the html for the TM polling pickup box
* @uses $GLOBALS['sitepress']
*
* @return string
*/
public function render() {
return '<div id="wpml-tm-polling-wrap"></div>';
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Class WPML_TM_Last_Picked_Up
*/
class WPML_TM_Last_Picked_Up {
/**
* @var Sitepress $sitepress
*/
private $sitepress;
/**
* WPML_TM_Last_Picked_Up constructor.
*
* @param Sitepress $sitepress
*/
public function __construct( $sitepress ) {
$this->sitepress = $sitepress;
}
/**
* Get last_picked_up setting.
*
* @return bool|mixed
*/
public function get() {
return $this->sitepress->get_setting( 'last_picked_up' );
}
/**
* Get last_picked_up setting as formatted string.
*
* @param string $format
*
* @return string
*/
public function get_formatted( $format = 'Y, F jS @g:i a' ) {
$last_picked_up = $this->get();
$last_time_picked_up = ! empty( $last_picked_up ) ?
date_i18n( $format, $last_picked_up ) :
__( 'never', 'wpml-translation-management' );
return $last_time_picked_up;
}
/**
* Set last_picked_up setting.
*/
public function set() {
$this->sitepress->set_setting( 'last_picked_up', current_time( 'timestamp', true ), true );
}
}

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 ),
);
}
}

View File

@@ -0,0 +1,38 @@
<?php
class WPML_Custom_Field_Editor_Settings {
/** @var WPML_Custom_Field_Setting_Factory */
private $settings_factory;
public function __construct( WPML_Custom_Field_Setting_Factory $settingsFactory ) {
$this->settings_factory = $settingsFactory;
}
public function filter_name( $fieldType, $default ) {
return $this->settings_factory->post_meta_setting( $this->extractTypeName( $fieldType ) )->get_editor_label() ?: $default;
}
public function filter_style( $fieldType, $default ) {
$filtered_style = $this->settings_factory->post_meta_setting( $this->extractTypeName( $fieldType ) )->get_editor_style();
switch ( $filtered_style ) {
case 'line':
return 0;
case 'textarea':
return 1;
case 'visual':
return 2;
}
return $default;
}
public function get_group( $fieldType ) {
return $this->settings_factory->post_meta_setting( $this->extractTypeName( $fieldType ) )->get_editor_group();
}
private function extractTypeName( $fieldType ) {
return substr( $fieldType, strlen( 'field_' ) );
}
}

View File

@@ -0,0 +1,118 @@
<?php
class WPML_Editor_UI_Job {
private $fields = array();
protected $job_id;
private $job_type;
private $job_type_title;
private $title;
private $view_link;
private $source_lang;
private $target_lang;
private $translation_complete;
private $duplicate;
private $note = '';
function __construct(
$job_id,
$job_type,
$job_type_title,
$title,
$view_link,
$source_lang,
$target_lang,
$translation_complete,
$duplicate
) {
$this->job_id = $job_id;
$this->job_type = $job_type;
$this->job_type_title = $job_type_title;
$this->title = $title;
$this->view_link = $view_link;
$this->source_lang = $source_lang;
$this->target_lang = $target_lang;
$this->translation_complete = $translation_complete;
$this->duplicate = $duplicate;
}
public function add_field( $field ) {
$this->fields[] = $field;
}
public function add_note( $note ) {
$this->note = $note;
}
public function get_all_fields() {
$fields = array();
/** @var WPML_Editor_UI_Field $field */
foreach ( $this->fields as $field ) {
$child_fields = $field->get_fields();
/** @var WPML_Editor_UI_Field $child_field */
foreach ( $child_fields as $child_field ) {
$fields[] = $child_field;
}
}
return $fields;
}
public function get_layout_of_fields() {
$layout = array();
/** @var WPML_Editor_UI_Field $field */
foreach ( $this->fields as $field ) {
$layout[] = $field->get_layout();
}
return $layout;
}
public function get_target_language() {
return $this->target_lang;
}
public function is_translation_complete() {
return $this->translation_complete;
}
public function save( $data ) {
$translations = array();
foreach ( $data['fields'] as $id => $field ) {
$translations[ $this->convert_id_to_translation_key( $id ) ] = $field['data'];
}
try {
$this->save_translations( $translations );
return new WPML_Ajax_Response( true, true );
} catch ( Exception $e ) {
return new WPML_Ajax_Response( false, 0 );
}
}
private function convert_id_to_translation_key( $id ) {
// This is to support the old api for saving translations.
return md5( $id );
}
public function requires_translation_complete_for_each_field() {
return true;
}
public function display_hide_completed_switcher() {
return true;
}
public function is_hide_empty_fields() {
return true;
}
public function save_translations( $translations ) {
}
}

View File

@@ -0,0 +1,11 @@
<?php
class WPML_TM_Editor_Job_Save {
public function save( $data ) {
$factory = new WPML_TM_Job_Action_Factory( wpml_tm_load_job_factory() );
$action = new WPML_TM_Editor_Save_Ajax_Action( $factory, $data );
return $action->run();
}
}

View File

@@ -0,0 +1,28 @@
<?php
class WPML_TM_Editor_Save_Ajax_Action extends WPML_TM_Job_Action {
private $data;
/**
* WPML_TM_Editor_Save_Ajax_Action constructor.
*
* @param WPML_TM_Job_Action_Factory $job_action_factory
* @param array $data
*/
public function __construct( &$job_action_factory, array $data ) {
parent::__construct( $job_action_factory );
$this->data = $data;
}
public function run() {
try {
return new WPML_Ajax_Response(
true,
$this->job_action_factory->save_action( $this->data )->save_translation()
);
} catch ( Exception $e ) {
return new WPML_Ajax_Response( false, 0 );
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
class WPML_TM_Field_Type_Sanitizer {
/**
* Get elements custom field `field_type`.
* Removes last character if it's number.
* ex. field-custom_field-0 => field-custom_field
*
* @param $element
*
* @return string
*/
public static function sanitize( $custom_field_type ) {
$element_field_type_parts = explode( '-', $custom_field_type );
$last_part = array_pop( $element_field_type_parts );
if ( empty( $element_field_type_parts ) ) {
return $custom_field_type;
}
// Re-create field.
$field_type = implode( '-', $element_field_type_parts );
if ( is_numeric( $last_part ) ) {
return $field_type;
} else {
return $custom_field_type;
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
class WPML_Translation_Editor_Header {
private $job_instance;
public function __construct( $job_instance ) {
$this->job_instance = $job_instance;
}
public function get_model() {
$type_title = esc_html( $this->job_instance->get_type_title() );
$title = esc_html( $this->job_instance->get_title() );
$data = array();
$data['title'] = sprintf( __( '%1$s translation: %2$s', 'wpml-translation-management' ), $type_title, '<strong>' . $title . '</strong>' );
$data['link_url'] = $this->job_instance->get_url( true );
$data['link_text'] = $this->job_instance instanceof WPML_External_Translation_Job ? '' : sprintf( __( 'View %s', 'wpml-translation-management' ), $type_title );
return $data;
}
}

View File

@@ -0,0 +1,33 @@
<?php
class WPML_Translation_Editor_Languages extends WPML_SP_User {
private $job;
/**
* @param SitePress $sitepress
*/
public function __construct( &$sitepress, $job ) {
parent::__construct( $sitepress );
$this->job = $job;
}
public function get_model() {
$source_lang = $this->sitepress->get_language_details( $this->job->source_language_code );
$target_lang = $this->sitepress->get_language_details( $this->job->language_code );
$data = array(
'source' => $this->job->source_language_code,
'target' => $this->job->language_code,
'source_lang' => $source_lang['display_name'],
'target_lang' => $target_lang['display_name'],
);
$data['img'] = array(
'source_url' => $this->sitepress->get_flag_url( $this->job->source_language_code ),
'target_url' => $this->sitepress->get_flag_url( $this->job->language_code ),
);
return $data;
}
}

View File

@@ -0,0 +1,357 @@
<?php
use \WPML\TM\Jobs\FieldId;
class WPML_Translation_Editor_UI {
const MAX_ALLOWED_SINGLE_LINE_LENGTH = 50;
/** @var SitePress $sitepress */
private $sitepress;
/** @var WPDB $wpdb */
private $wpdb;
/** @var array */
private $all_translations;
/**
* @var WPML_Translation_Editor
*/
private $editor_object;
private $job;
private $original_post;
private $rtl_original;
private $rtl_original_attribute_object;
private $rtl_translation;
private $rtl_translation_attribute;
private $is_duplicate = false;
/**
* @var TranslationManagement
*/
private $tm_instance;
/** @var WPML_Element_Translation_Job|WPML_External_Translation_Job */
private $job_instance;
private $job_factory;
private $job_layout;
/** @var array */
private $fields;
function __construct( wpdb $wpdb, SitePress $sitepress, TranslationManagement $iclTranslationManagement, WPML_Element_Translation_Job $job_instance, WPML_TM_Job_Action_Factory $job_factory, WPML_TM_Job_Layout $job_layout ) {
$this->sitepress = $sitepress;
$this->wpdb = $wpdb;
$this->tm_instance = $iclTranslationManagement;
$this->job_instance = $job_instance;
$this->job = $job_instance->get_basic_data();
$this->job_factory = $job_factory;
$this->job_layout = $job_layout;
if ( $job_instance->get_translator_id() <= 0 ) {
$job_instance->assign_to( $sitepress->get_wp_api()->get_current_user_id() );
}
add_action( 'admin_print_footer_scripts', [ $this, 'force_uncompressed_tinymce' ], 1 );
}
/**
* Force using uncompressed version tinymce which solves:
* https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-191
*
* Seams the compressed and uncompressed have some difference, because even WP has a force_uncompressed_tinymce
* method, which is triggered whenever a custom theme on TinyMCE is used.
*
* @return void
*/
public function force_uncompressed_tinymce() {
if( ! function_exists( 'wp_scripts' ) || ! function_exists( 'wp_register_tinymce_scripts' ) ) {
// WP is below 5.0.
return;
}
$wp_scripts = wp_scripts();
$wp_scripts->remove( 'wp-tinymce' );
wp_register_tinymce_scripts( $wp_scripts, true );
}
function render() {
list( $this->rtl_original, $this->rtl_translation ) = $this->init_rtl_settings();
require_once ABSPATH . 'wp-admin/includes/image.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
?>
<div class="wrap icl-translation-editor wpml-dialog-translate">
<h1 id="wpml-translation-editor-header" class="wpml-translation-title"></h1>
<?php
do_action( 'icl_tm_messages' );
do_action( 'wpml_tm_editor_messages' );
$this->init_original_post();
$this->init_editor_object();
$this->output_model();
$this->output_ate_notice();
$this->output_gutenberg_notice();
$this->output_wysiwyg_editors();
$this->output_copy_all_dialog();
if ( $this->is_duplicate ) {
$this->output_edit_independently_dialog();
}
$this->output_editor_form();
?>
</div>
<?php
}
/**
* @return array
*/
private function init_rtl_settings() {
$this->rtl_original = $this->sitepress->is_rtl( $this->job->source_language_code );
$this->rtl_translation = $this->sitepress->is_rtl( $this->job->language_code );
$this->rtl_original_attribute_object = $this->rtl_original ? ' dir="rtl"' : ' dir="ltr"';
$this->rtl_translation_attribute = $this->rtl_translation ? ' dir="rtl"' : ' dir="ltr"';
return array( $this->rtl_original, $this->rtl_translation );
}
private function init_original_post() {
// we do not need the original document of the job here
// but the document with the same trid and in the $this->job->source_language_code
$this->all_translations = $this->sitepress->get_element_translations( $this->job->trid, $this->job->original_post_type );
$this->original_post = false;
foreach ( (array) $this->all_translations as $t ) {
if ( $t->language_code === $this->job->source_language_code ) {
$this->original_post = $this->tm_instance->get_post( $t->element_id, $this->job->element_type_prefix );
// if this fails for some reason use the original doc from which the trid originated
break;
}
}
if ( ! $this->original_post ) {
$this->original_post = $this->tm_instance->get_post( $this->job_instance->get_original_element_id(), $this->job->element_type_prefix );
}
if ( isset( $this->all_translations[ $this->job->language_code ] ) ) {
$post_status = new WPML_Post_Status( $this->wpdb, $this->sitepress->get_wp_api() );
$this->is_duplicate = $post_status->is_duplicate( $this->all_translations[ $this->job->language_code ]->element_id );
}
return $this->original_post;
}
private function init_editor_object() {
global $wpdb;
$this->editor_object = new WPML_Translation_Editor( $this->sitepress, $wpdb, $this->job_instance );
}
private function output_model() {
$model = array(
'requires_translation_complete_for_each_field' => true,
'hide_empty_fields' => true,
'translation_is_complete' => ICL_TM_COMPLETE === (int) $this->job->status,
'show_media_button' => false,
'is_duplicate' => $this->is_duplicate,
'display_hide_completed_switcher' => true,
);
if ( ! empty( $_GET['return_url'] ) ) {
$model['return_url'] = filter_var( $_GET['return_url'], FILTER_SANITIZE_URL );
} else {
$model['return_url'] = 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php';
}
$languages = new WPML_Translation_Editor_Languages( $this->sitepress, $this->job );
$model['languages'] = $languages->get_model();
$header = new WPML_Translation_Editor_Header( $this->job_instance );
$model['header'] = $header->get_model();
$model['note'] = $this->sitepress->get_wp_api()->get_post_meta(
$this->job_instance->get_original_element_id(),
WPML_TM_Translator_Note::META_FIELD_KEY,
true
);
$this->fields = $this->job_factory->field_contents( (int) $this->job_instance->get_id() )->run();
$this->fields = $this->add_titles_and_adjust_styles( $this->fields );
$this->fields = $this->add_rtl_attributes( $this->fields );
$model['fields'] = $this->fields;
$model['layout'] = $this->job_layout->run( $model['fields'], $this->tm_instance );
$model['rtl_original'] = $this->rtl_original;
$model['rtl_translation'] = $this->rtl_translation;
$model['translation_memory'] = (bool) $this->sitepress->get_setting( 'translation_memory', 1 );
$model = $this->filter_the_model( $model );
?>
<script type="text/javascript">
var WpmlTmEditorModel = <?php echo wp_json_encode( $model ); ?>;
</script>
<?php
}
private function output_ate_notice() {
$html_fields = array_filter(
$this->fields,
function ( $field ) {
return $field['field_style'] === '1' && strpos( $field['field_data'], '<' ) !== false;
}
);
if ( count( $html_fields ) > 0 ) {
$link = 'https://wpml.org/documentation/translating-your-contents/advanced-translation-editor/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm#html-markers';
$notice_text = esc_html__( 'We see you\'re translating content that contains HTML. Switch to the Advanced Translation Editor to translate content without the risk of breaking your HTML code.', 'wpml-translation-management' );
echo '<div class="notice notice-info">
<p>' . $notice_text . ' <a href="' . $link . '" class="wpml-external-link" target="_blank" rel="noopener">' . esc_html__( 'Read more...', 'wpml-translation-management' ) . '</a></p>
</div>';
}
}
private function output_gutenberg_notice() {
$has_gutenberg_block = false;
foreach ( $this->fields as $field ) {
if ( preg_match( '#<!-- wp:#', $field['field_data'] ) ) {
$has_gutenberg_block = true;
break;
}
}
if ( $has_gutenberg_block ) {
echo '<div class="notice notice-info">
<p>' . esc_html__( 'This content came from the Block editor and you need to translate it carefully so that formatting is not broken.', 'wpml-translation-management' ) . '</p>
<p><a href="https://wpml.org/documentation/getting-started-guide/translating-content-created-using-gutenberg-editor/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm" class="wpml-external-link" target="_blank" rel="noopener">' . esc_html__( 'Learn how to translate content that comes from Block editor', 'wpml-translation-management' ) . '</a></p>
</div>';
}
}
private function output_wysiwyg_editors() {
echo '<div style="display: none">';
foreach ( $this->fields as $field ) {
if ( 2 === (int) $field['field_style'] ) {
$this->editor_object->output_editors( $field );
}
}
echo '</div>';
}
private function output_copy_all_dialog() {
?>
<div id="wpml-translation-editor-copy-all-dialog" class="wpml-dialog" style="display:none"
title="<?php echo esc_attr__( 'Copy all fields from original', 'wpml-translation-management' ); ?>">
<p class="wpml-dialog-cols-icon">
<i class="otgs-ico-copy wpml-dialog-icon-xl"></i>
</p>
<div class="wpml-dialog-cols-content">
<p>
<strong><?php echo esc_html__( 'Some fields in translation are already filled!', 'wpml-translation-management' ); ?></strong>
<br/>
<?php echo esc_html__( 'You have two ways to copy content from the original language:', 'wpml-translation-management' ); ?>
</p>
<ul>
<li><?php echo esc_html__( 'copy to empty fields only', 'wpml-translation-management' ); ?></li>
<li><?php echo esc_html__( 'copy and overwrite all fields', 'wpml-translation-management' ); ?></li>
</ul>
</div>
<div class="wpml-dialog-footer">
<div class="alignleft">
<button
class="cancel wpml-dialog-close-button js-copy-cancel"><?php echo esc_html__( 'Cancel', 'wpml-translation-management' ); ?></button>
</div>
<div class="alignright">
<button
class="button-secondary js-copy-not-translated"><?php echo esc_html__( 'Copy to empty fields only', 'wpml-translation-management' ); ?></button>
<button
class="button-secondary js-copy-overwrite"><?php echo esc_html__( 'Copy & Overwrite all fields', 'wpml-translation-management' ); ?></button>
</div>
</div>
</div>
<?php
}
private function output_edit_independently_dialog() {
?>
<div id="wpml-translation-editor-edit-independently-dialog" class="wpml-dialog" style="display:none"
title="<?php echo esc_attr__( 'Edit independently', 'wpml-translation-management' ); ?>">
<p class="wpml-dialog-cols-icon">
<i class="otgs-ico-unlink wpml-dialog-icon-xl"></i>
</p>
<div class="wpml-dialog-cols-content">
<p><?php esc_html_e( 'This document is a duplicate of:', 'wpml-translation-management' ); ?>
<span class="wpml-duplicated-post-title">
<img class="wpml-title-flag" src="<?php echo esc_attr( $this->sitepress->get_flag_url( $this->job->source_language_code ) ); ?>">
<?php echo esc_html( $this->job_instance->get_title() ); ?>
</span>
</p>
<p>
<?php echo esc_html( sprintf( __( 'WPML will no longer synchronize this %s with the original content.', 'wpml-translation-management' ), $this->job_instance->get_type_title() ) ); ?>
</p>
</div>
<div class="wpml-dialog-footer">
<div class="alignleft">
<button class="cancel wpml-dialog-close-button js-edit-independently-cancel"><?php echo esc_html__( 'Cancel', 'wpml-translation-management' ); ?></button>
</div>
<div class="alignright">
<button class="button-secondary js-edit-independently"><?php echo esc_html__( 'Edit independently', 'wpml-translation-management' ); ?></button>
</div>
</div>
</div>
<?php
}
private function output_editor_form() {
?>
<form id="icl_tm_editor" method="post" action="">
<input type="hidden" name="job_post_type" value="<?php echo esc_attr( $this->job->original_post_type ); ?>"/>
<input type="hidden" name="job_post_id" value="<?php echo esc_attr( $this->job->original_doc_id ); ?>"/>
<input type="hidden" name="job_id" value="<?php echo esc_attr( $this->job_instance->get_id() ); ?>"/>
<div id="wpml-translation-editor-wrapper"></div>
</form>
<?php
}
private function add_titles_and_adjust_styles( array $fields ) {
return apply_filters( 'wpml_tm_adjust_translation_fields', $fields, $this->job, $this->original_post );
}
private function add_rtl_attributes( array $fields ) {
foreach ( $fields as &$field ) {
$field['original_direction'] = $this->rtl_original ? 'dir="rtl"' : 'dir="ltr"';
$field['translation_direction'] = $this->rtl_translation ? 'dir="rtl"' : 'dir="ltr"';
}
return $fields;
}
private function filter_the_model( array $model ) {
$job_details = array(
'job_type' => $this->job->original_post_type,
'job_id' => $this->job->original_doc_id,
'target' => $model['languages']['target'],
);
$job = apply_filters( 'wpml-translation-editor-fetch-job', null, $job_details );
if ( $job ) {
$model['requires_translation_complete_for_each_field'] = $job->requires_translation_complete_for_each_field();
$model['hide_empty_fields'] = $job->is_hide_empty_fields();
$model['show_media_button'] = $job->show_media_button();
$model['display_hide_completed_switcher'] = $job->display_hide_completed_switcher();
$model['fields'] = $this->add_rtl_attributes( $job->get_all_fields() );
$this->fields = $model['fields'];
$model['layout'] = $job->get_layout_of_fields();
}
return $model;
}
}

View File

@@ -0,0 +1,175 @@
<?php
if ( ! class_exists( '_WP_Editors', false ) ) {
require ABSPATH . WPINC . '/class-wp-editor.php';
}
class WPML_Translation_Editor extends WPML_WPDB_And_SP_User {
/**
* @var WPML_Element_Translation_Job $job
*/
private $job;
/**
* @param SitePress $sitepress
* @param wpdb $wpdb
* @param WPML_Element_Translation_Job $job
*/
public function __construct(
&$sitepress,
&$wpdb,
$job
) {
parent::__construct( $wpdb, $sitepress );
$this->job = $job;
$this->add_hooks();
$this->enqueue_js();
}
public function add_hooks() {
add_filter( 'tiny_mce_before_init', [ $this, 'filter_original_editor_buttons' ], 10, 2 );
}
/**
* Enqueues the JavaScript used by the TM editor.
*/
public function enqueue_js() {
wp_enqueue_script( 'wpml-tm-editor-scripts' );
wp_localize_script(
'wpml-tm-editor-scripts',
'tmEditorStrings',
$this->get_translation_editor_strings()
);
}
/**
* @return string[]
*/
private function get_translation_editor_strings() {
$translation_memory_endpoint = apply_filters( 'wpml_st_translation_memory_endpoint', '' );
return array(
'dontShowAgain' => __(
"Don't show this again.",
'wpml-translation-management'
),
'learnMore' => __(
'<p>The administrator has disabled term translation from the translation editor. </p>
<p>If your access permissions allow you can change this under "Translation Management" - "Multilingual Content Setup" - "Block translating taxonomy terms that already got translated". </p>
<p>Please note that editing terms from the translation editor will affect all posts that have the respective terms associated.</p>',
'wpml-translation-management'
),
'warning' => __(
"Please be advised that editing this term's translation here will change the value of the term in general. The changes made here, will not only affect this post!",
'wpml-translation-management'
),
'title' => __(
'Terms translation is disabled',
'wpml-translation-management'
),
'confirm' => __(
'You have unsaved work. Are you sure you want to close without saving?',
'wpml-translation-management'
),
'cancel' => __(
'Cancel',
'wpml-translation-management'
),
'save' => __(
'Save',
'wpml-translation-management'
),
'hide_translated' => __(
'Hide completed',
'wpml-translation-management'
),
'save_and_close' => __(
'Save & Close',
'wpml-translation-management'
),
'loading_url' => ICL_PLUGIN_URL . '/res/img/ajax-loader.gif',
'saving' => __(
'Saving...',
'wpml-translation-management'
),
'translation_complete' => __(
'Translation is complete',
'wpml-translation-management'
),
'contentNonce' => wp_create_nonce( 'wpml_save_job_nonce' ),
'translationMemoryNonce' => \WPML\LIB\WP\Nonce::create( $translation_memory_endpoint ),
'translationMemoryEndpoint' => $translation_memory_endpoint,
'source_lang' => __(
'Original',
'wpml-translation-management'
),
'target_lang' => __(
'Translation to',
'wpml-translation-management'
),
'copy_all' => __(
'Copy all fields from original',
'wpml-translation-management'
),
'resign' => __(
'Resign',
'wpml-translation-management'
),
'resign_translation' => __(
'Are you sure you want to resign from this job?',
'wpml-translation-management'
),
'resign_url' => admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php&icl_tm_action=save_translation&resign=1&job_id=' . $this->job->get_id() ),
'confirmNavigate' => __(
'You have unsaved changes!',
'wpml-translation-management'
),
'copy_from_original' => __(
'Copy from original',
'wpml-translation-management'
),
'show_diff' => __( 'Show differences', 'wpml-translation-management' ),
);
}
public function filter_original_editor_buttons( $config, $editor_id ) {
if ( strpos( $editor_id, '_original' ) > 0 ) {
$config['toolbar1'] = ' ';
$config['toolbar2'] = ' ';
$config['readonly'] = '1';
}
return $config;
}
public function output_editors( $field ) {
echo '<div id="' . $field['field_type'] . '_original_editor" class="original_value mce_editor_origin">';
wp_editor(
$field['field_data'],
$field['field_type'] . '_original',
array(
'textarea_rows' => 4,
'editor_class' => 'wpml_content_tr original_value mce_editor_origin',
'media_buttons' => false,
'quicktags' => array( 'buttons' => 'empty' ),
)
);
echo '</div>';
echo '<div id="' . $field['field_type'] . '_translated_editor" class="mce_editor translated_value">';
wp_editor(
$field['field_data_translated'],
$field['field_type'],
array(
'textarea_rows' => 4,
'editor_class' => 'wpml_content_tr translated_value',
'media_buttons' => true,
'textarea_name' => 'fields[' . $field['field_type'] . '][data]',
)
);
echo '</div>';
}
}

View File

@@ -0,0 +1,27 @@
<?php
class WPML_Editor_UI_Field_Group extends WPML_Editor_UI_Fields {
private $title;
private $divider;
function __construct( $title = '', $divider = true ) {
$this->title = $title;
$this->divider = $divider;
}
public function get_layout() {
$data = array(
'title' => $this->title,
'divider' => $this->divider,
'field_type' => 'tm-group',
);
$data['fields'] = parent::get_layout();
return $data;
}
}

View File

@@ -0,0 +1,38 @@
<?php
class WPML_Editor_UI_Field_Image extends WPML_Editor_UI_Fields {
private $image_id;
private $divider;
private $group;
function __construct( $id, $image_id, $data, $divider = true ) {
$this->image_id = $image_id;
$this->divider = $divider;
$this->group = new WPML_Editor_UI_Field_Group( '', false );
$this->group->add_field( new WPML_Editor_UI_Single_Line_Field( $id . '-title', __( 'Title', 'wpml-translation-management' ), $data, false ) );
$this->group->add_field( new WPML_Editor_UI_Single_Line_Field( $id . '-caption', __('Caption', 'wpml-translation-management' ), $data, false ) );
$this->group->add_field( new WPML_Editor_UI_Single_Line_Field( $id . '-alt-text', __('Alt Text', 'wpml-translation-management' ), $data, false ) );
$this->group->add_field( new WPML_Editor_UI_Single_Line_Field( $id . '-description', __('Description', 'wpml-translation-management' ), $data, false ) );
$this->add_field( $this->group );
}
public function get_layout() {
$image = wp_get_attachment_image_src( $this->image_id, array( 100, 100 ) );
$data = array(
'field_type' => 'wcml-image',
'divider' => $this->divider,
'image_src' => isset( $image[0] ) ? $image[0] : '',
);
$data['fields'] = parent::get_layout();
return $data;
}
}

View File

@@ -0,0 +1,28 @@
<?php
class WPML_Editor_UI_Field_Section extends WPML_Editor_UI_Fields {
private $title;
private $sub_title;
function __construct( $title = '', $sub_title = '' ) {
$this->title = $title;
$this->sub_title = $sub_title;
}
public function get_layout() {
$data = array(
'empty_message' => '',
'empty' => false,
'title' => $this->title,
'sub_title' => $this->sub_title,
'field_type' => 'tm-section',
);
$data['fields'] = parent::get_layout();
return $data;
}
}

View File

@@ -0,0 +1,42 @@
<?php
class WPML_Editor_UI_Field {
protected $id;
protected $title;
protected $original;
protected $translation;
private $requires_complete;
protected $is_complete;
function __construct( $id, $title, $data, $requires_complete = false ) {
$this->id = $id;
$this->title = $title ? $title : '';
$this->original = $data[ $id ]['original'];
$this->translation = isset( $data[ $id ]['translation'] ) ? $data[ $id ]['translation'] : '';
$this->requires_complete = $requires_complete;
$this->is_complete = isset( $data[ $id ]['is_complete'] ) ? $data[ $id ]['is_complete'] : false;
}
public function get_fields() {
$field = array();
$field['field_type'] = $this->id;
$field['field_data'] = $this->original;
$field['field_data_translated'] = $this->translation;
$field['title'] = $this->title;
$field['field_finished'] = $this->is_complete ? '1' : '0';
$field['tid'] = '0';
return $field;
}
public function get_layout() {
// This is a field with no sub fields so just return the id
return $this->id;
}
}

View File

@@ -0,0 +1,36 @@
<?php
class WPML_Editor_UI_Fields {
private $fields = array();
public function add_field( $field ) {
$this->fields[] = $field;
}
public function get_fields() {
$fields = array();
/** @var WPML_Editor_UI_Field $field */
foreach ( $this->fields as $field ) {
$child_fields = $field->get_fields();
foreach ( $child_fields as $child_field ) {
$fields[] = $child_field;
}
}
return $fields;
}
public function get_layout() {
$layout = array();
/** @var WPML_Editor_UI_Field $field */
foreach ( $this->fields as $field ) {
$layout[] = $field->get_layout();
}
return $layout;
}
}

View File

@@ -0,0 +1,21 @@
<?php
class WPML_Editor_UI_Single_Line_Field extends WPML_Editor_UI_Field {
private $include_copy_button;
function __construct( $id, $title, $data, $include_copy_button, $requires_complete = false ) {
parent::__construct( $id, $title, $data, $requires_complete );
$this->include_copy_button = $include_copy_button;
}
public function get_fields() {
$field = parent::get_fields();
$field['field_style'] = '0';
return array( $field );
}
}

View File

@@ -0,0 +1,21 @@
<?php
class WPML_Editor_UI_TextArea_Field extends WPML_Editor_UI_Field {
private $include_copy_button;
function __construct( $id, $title, $data, $include_copy_button, $requires_complete = false ) {
parent::__construct( $id, $title, $data, $requires_complete );
$this->include_copy_button = $include_copy_button;
}
public function get_fields() {
$field = parent::get_fields();
$field['field_style'] = '1';
return array( $field );
}
}

View File

@@ -0,0 +1,22 @@
<?php
class WPML_Editor_UI_WYSIWYG_Field extends WPML_Editor_UI_Field {
private $include_copy_button;
function __construct( $id, $title, $data, $include_copy_button, $requires_complete = false ) {
parent::__construct( $id, $title, $data, $requires_complete );
$this->include_copy_button = $include_copy_button;
}
public function get_fields() {
$field = parent::get_fields();
$field['field_style'] = '2';
return array( $field );
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace WPML\TM\Menu\TranslationMethod;
use WPML\API\PostTypes;
use WPML\API\Settings;
use WPML\DocPage;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
use function WPML\FP\partial;
use WPML\LIB\WP\Hooks;
use WPML\LIB\WP\Nonce;
use WPML\LIB\WP\PostType;
use WPML\Posts\UntranslatedCount;
use WPML\TranslationMode\Endpoint\SetTranslateEverything;
use WPML\Setup\Option;
use WPML\TM\API\ATE\Account;
use WPML\TM\ATE\Jobs;
use WPML\TM\Menu\TranslationServices\ActiveServiceRepository;
use WPML\TM\WP\App\Resources;
use WPML\UIPage;
class TranslationMethodSettings {
public static function addHooks() {
if ( UIPage::isMainSettingsTab( $_GET ) ) {
Hooks::onAction( 'admin_enqueue_scripts' )
->then( [ self::class, 'localize' ] )
->then( Resources::enqueueApp( 'translation-method' ) );
if ( Obj::prop( 'disable_translate_everything', $_GET ) ) {
Hooks::onAction( 'wp_loaded' )
->then( Fns::tap( partial( [ Option::class, 'setTranslateEverything' ], false ) ) )
->then( Fns::tap( partial( 'do_action', 'wpml_set_translate_everything', false ) ) );
}
}
}
public static function localize() {
$getPostTypeName = function ( $postType ) {
return PostType::getPluralName( $postType )->getOrElse( $postType );
};
$editor = (string) Settings::pathOr( ICL_TM_TMETHOD_MANUAL, [ 'translation-management', 'doc_translation_method' ] );
return [
'name' => 'wpml_translation_method',
'data' => [
'translateEverything' => Option::shouldTranslateEverything(),
'reviewMode' => Option::getReviewMode(),
'endpoints' => [
'setTranslateEverything' => SetTranslateEverything::class,
'untranslatedCount' => UntranslatedCount::class,
],
'urls' => [
'tmDashboard' => UIPage::getTMDashboard(),
],
'disableTranslateEverything' => (bool) Obj::prop( 'disable_translate_everything', $_GET ),
'hasSubscription' => Account::isAbleToTranslateAutomatically(),
'createAccountLink' => UIPage::getTMATE() . '&widget_action=wpml_signup',
'translateAutomaticallyDoc' => DocPage::getTranslateAutomatically(),
'postTypes' => Fns::map( $getPostTypeName, PostTypes::getAutomaticTranslatable() ),
'hasTranslationService' => ActiveServiceRepository::get() !== null,
'translatorsTabLink' => UIPage::getTMTranslators(),
'hasJobsInProgress' => count( Jobs::getJobsToSync() ),
'isClassicEditor' => Lst::includes( $editor, [
(string) ICL_TM_TMETHOD_EDITOR,
(string) ICL_TM_TMETHOD_MANUAL
] ),
],
];
}
public static function render() {
echo '<div id="translation-method-settings"></div>';
}
}

View File

@@ -0,0 +1,25 @@
<?php
class WPML_TM_Emails_Settings_Factory implements IWPML_Backend_Action_Loader {
/**
* @return WPML_TM_Emails_Settings
*/
public function create() {
global $iclTranslationManagement;
$hooks = null;
if ( $this->is_tm_settings_page() ) {
$template_service = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/settings' ) );
$hooks = new WPML_TM_Emails_Settings( $template_service->get_template(), $iclTranslationManagement );
}
return $hooks;
}
private function is_tm_settings_page() {
return isset( $_GET['page'] )
&& WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS === filter_var( $_GET['page'], FILTER_SANITIZE_FULL_SPECIAL_CHARS );
}
}

View File

@@ -0,0 +1,99 @@
<?php
class WPML_TM_Emails_Settings {
const TEMPLATE = 'emails-settings.twig';
const COMPLETED_JOB_FREQUENCY = 'completed_frequency';
const NOTIFY_IMMEDIATELY = 1;
const NOTIFY_DAILY = 2;
const NOTIFY_WEEKLY = 3;
/**
* @var IWPML_Template_Service
*/
private $template_service;
/**
* @var array
*/
private $tm;
public function __construct( IWPML_Template_Service $template_service, TranslationManagement $tm ) {
$this->template_service = $template_service;
$this->tm = $tm;
}
public function add_hooks() {
add_action( 'wpml_tm_translation_notification_setting_after', array( $this, 'render' ) );
add_action( 'wpml_tm_notification_settings_saved', array( $this, 'remove_scheduled_summary_email' ) );
}
public function render() {
echo $this->template_service->show( $this->get_model(), self::TEMPLATE );
}
private function get_model() {
return array(
'strings' => array(
'section_title_translator' => __( 'Notification emails to translators', 'wpml-translation-management' ),
'label_new_job' => __( 'Notify translators when new jobs are waiting for them', 'wpml-translation-management' ),
'label_include_xliff' => __( 'Include XLIFF files in the notification emails', 'wpml-translation-management' ),
'label_resigned_job' => __( 'Notify translators when they are removed from jobs', 'wpml-translation-management' ),
'section_title_manager' => __( 'Notification emails to the translation manager', 'wpml-translation-management' ),
'label_completed_job' => esc_html__( 'Notify the translation manager when jobs are completed %s', 'wpml-translation-management' ),
'label_overdue_job' => esc_html__( 'Notify the translation manager when jobs are late by %s days', 'wpml-translation-management' ),
),
'settings' => array(
'new_job' => array(
'value' => self::NOTIFY_IMMEDIATELY,
'checked' => checked( self::NOTIFY_IMMEDIATELY, $this->tm->settings['notification']['new-job'], false ),
),
'include_xliff' => array(
'value' => 1,
'checked' => checked( 1, $this->tm->settings['notification']['include_xliff'], false ),
'disabled' => disabled( 0, $this->tm->settings['notification']['new-job'], false ),
),
'resigned' => array(
'value' => self::NOTIFY_IMMEDIATELY,
'checked' => checked( self::NOTIFY_IMMEDIATELY, $this->tm->settings['notification']['resigned'], false ),
),
'completed' => array(
'value' => 1,
'checked' => checked( self::NOTIFY_IMMEDIATELY, $this->tm->settings['notification']['completed'], false ),
),
'completed_frequency' => array(
'options' => array(
array(
'label' => __( 'immediately', 'wpml-translation-management' ),
'value' => self::NOTIFY_IMMEDIATELY,
'checked' => selected( self::NOTIFY_IMMEDIATELY, $this->tm->settings['notification'][ self::COMPLETED_JOB_FREQUENCY ], false ),
),
array(
'label' => __( 'once a day', 'wpml-translation-management' ),
'value' => self::NOTIFY_DAILY,
'checked' => selected( self::NOTIFY_DAILY, $this->tm->settings['notification'][ self::COMPLETED_JOB_FREQUENCY ], false ),
),
array(
'label' => __( 'once a week', 'wpml-translation-management' ),
'value' => self::NOTIFY_WEEKLY,
'checked' => selected( self::NOTIFY_WEEKLY, $this->tm->settings['notification'][ self::COMPLETED_JOB_FREQUENCY ], false ),
),
),
'disabled' => disabled( 0, $this->tm->settings['notification']['completed'], false ),
),
'overdue' => array(
'value' => 1,
'checked' => checked( self::NOTIFY_IMMEDIATELY, $this->tm->settings['notification']['overdue'], false ),
),
'overdue_offset' => array(
'value' => $this->tm->settings['notification']['overdue_offset'],
'disabled' => disabled( 0, $this->tm->settings['notification']['overdue'], false ),
),
),
);
}
public function remove_scheduled_summary_email() {
wp_clear_scheduled_hook( WPML_TM_Jobs_Summary_Report_Hooks::EVENT_HOOK );
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace WPML\TM\Menu\TranslationQueue;
use WPML\FP\Either;
use WPML\FP\Obj;
use WPML\TM\API\Job\Map;
use WPML_Element_Translation_Job;
use WPML_TM_Editors;
use WPML_TM_ATE_Jobs;
use WPML\TM\ATE\JobRecords;
use WPML_TM_ATE_API;
class CloneJobs {
/**
* @var WPML_TM_ATE_Jobs
*/
private $ateJobs;
/**
* @var WPML_TM_ATE_API
*/
private $apiClient;
/**
* Number of microseconds to wait until an API call is repeated again in the case of failure.
*
* @var int
*/
private $repeatInterval;
/**
* @param WPML_TM_ATE_Jobs $ateJobs
* @param WPML_TM_ATE_API $apiClient
* @param int $repeatInterval
*/
public function __construct( WPML_TM_ATE_Jobs $ateJobs, WPML_TM_ATE_API $apiClient, $repeatInterval = 5000000 ) {
$this->ateJobs = $ateJobs;
$this->apiClient = $apiClient;
$this->repeatInterval = $repeatInterval;
}
/**
* @param WPML_Element_Translation_Job $jobObject
* @param int|null $sentFrom
* @param bool $hasBeenAlreadyRepeated
*
* @return Either<WPML_Element_Translation_Job>
*/
public function cloneCompletedATEJob( WPML_Element_Translation_Job $jobObject, $sentFrom = null, $hasBeenAlreadyRepeated = false ) {
$ateJobId = (int) $jobObject->get_basic_data_property('editor_job_id');
$result = $this->apiClient->clone_job( $ateJobId, $jobObject, $sentFrom );
if ( $result ) {
$this->ateJobs->store( $jobObject->get_id(), [ JobRecords::FIELD_ATE_JOB_ID => $result['id'] ] );
return Either::of( $jobObject );
} elseif ( ! $hasBeenAlreadyRepeated ) {
usleep( $this->repeatInterval );
return $this->cloneCompletedATEJob( $jobObject, $sentFrom, true );
} else {
return Either::left( $jobObject );
}
}
/**
* It creates a corresponding ATE job for WPML Job if such ATE job does not exist yet
*
* @param int $wpmlJobId
* @return bool
*/
public function cloneWPMLJob( $wpmlJobId ) {
$params = json_decode( wp_json_encode( [
'jobs' => [ wpml_tm_create_ATE_job_creation_model( $wpmlJobId ) ]
] ), true );
$response = $this->apiClient->create_jobs( $params );
if ( ! is_wp_error( $response ) && Obj::prop( 'jobs', $response ) ) {
$this->ateJobs->store( $wpmlJobId, [ JobRecords::FIELD_ATE_JOB_ID => Obj::path( [ 'jobs', Map::fromJobId( $wpmlJobId ) ], $response ) ] );
wpml_tm_load_old_jobs_editor()->set( $wpmlJobId, WPML_TM_Editors::ATE );
$this->ateJobs->warm_cache( [ $wpmlJobId ] );
return true;
}
return false;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace WPML\TM\Menu\TranslationQueue;
use WPML\FP\Obj;
class PostTypeFilters {
/** @var \WPML_TM_Jobs_Repository */
private $jobsRepository;
/**
* @param \WPML_TM_Jobs_Repository $jobsRepository
*/
public function __construct( \WPML_TM_Jobs_Repository $jobsRepository ) {
$this->jobsRepository = $jobsRepository;
}
public function get( array $filters ) {
global $sitepress;
$searchParams = new \WPML_TM_Jobs_Search_Params();
$searchParams = $this->addFilteringConditions( $searchParams, $filters );
$searchParams->set_columns_to_select([
"DISTINCT SUBSTRING_INDEX(translations.element_type, '_', 1) AS element_type_prefix",
"translations.element_type AS original_post_type"
]);
$job_types = $this->jobsRepository->get($searchParams);
$post_types = $sitepress->get_translatable_documents( true );
$post_types = apply_filters( 'wpml_get_translatable_types', $post_types );
$output = [];
foreach ( $job_types as $job_type ) {
$type = $job_type->original_post_type;
$name = $type;
switch ( $job_type->element_type_prefix ) {
case 'post':
$type = substr( $type, 5 );
break;
case 'package':
$type = substr( $type, 8 );
break;
case 'st-batch':
$type = 'strings';
$name = __( 'Strings', 'wpml-translation-management' );
break;
}
$output[ $job_type->element_type_prefix . '_' . $type ] =
Obj::pathOr( $name, [ $type, 'labels', 'singular_name' ], $post_types );
}
return $output;
}
/**
* @param \WPML_TM_Jobs_Search_Params $searchParams
* @param array $filters
*
* @return \WPML_TM_Jobs_Search_Params
*/
private function addFilteringConditions( \WPML_TM_Jobs_Search_Params $searchParams, array $filters ) {
global $wpdb;
$where[] = $wpdb->prepare( ' status NOT IN ( %d, %d )', ICL_TM_NOT_TRANSLATED, ICL_TM_ATE_CANCELLED );
$translator = (int) Obj::prop( 'translator_id', $filters );
if ( $translator ) {
$where[] = $wpdb->prepare( '(translate_job.translator_id = %d OR translate_job.translator_id = 0 OR translate_job.translator_id IS NULL)', $translator );
}
$status = (int) Obj::prop( 'status', $filters );
if ( $status ) {
if ( $status === ICL_TM_NEEDS_REVIEW ) {
$searchParams->set_needs_review( true );
} else {
$searchParams->set_status( [ $status ] );
$searchParams->set_needs_review( false );
}
}
$from = Obj::prop( 'from', $filters );
if ( $from ) {
$searchParams->set_source_language( $from );
}
$to = Obj::prop( 'to', $filters );
if ( $to ) {
$searchParams->set_target_language( $to );
}
$type = Obj::prop( 'type', $filters );
if ( $type ) {
$searchParams->set_element_type( $type );
}
$searchParams->set_custom_where_conditions( $where );
return $searchParams;
}
}

View File

@@ -0,0 +1,141 @@
<?php
use WPML\API\Sanitize;
use WPML\Element\API\Languages;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\Setup\Option;
use WPML\TM\API\Translators;
use WPML\TM\ATE\Review\ApproveTranslations;
use WPML\TM\ATE\Review\Cancel;
use WPML\TM\ATE\Review\ReviewStatus;
use WPML\FP\Str;
use WPML\API\PostTypes;
use WPML\TM\Editor\Editor;
use WPML\TM\API\Jobs;
use WPML\TM\Menu\TranslationQueue\PostTypeFilters;
use function WPML\FP\pipe;
class WPML_Translations_Queue {
/** @var SitePress $sitepress */
private $sitepress;
private $must_render_the_editor = false;
/** @var WPML_Translation_Editor_UI */
private $translation_editor;
/**
* @var Editor
*/
private $editor;
/**
* @param SitePress $sitepress
* @param Editor $editor
*/
public function __construct( SitePress $sitepress, Editor $editor ) {
$this->sitepress = $sitepress;
$this->editor = $editor;
}
public function init_hooks() {
add_action( 'current_screen', array( $this, 'load' ) );
}
public function load() {
if ( $this->must_open_the_editor() ) {
$response = $this->editor->open( $_GET );
if ( Relation::propEq( 'editor', WPML_TM_Editors::ATE, $response ) ) {
wp_safe_redirect( Obj::prop('url', $response), 302, 'WPML' );
return;
} elseif (Relation::propEq( 'editor', WPML_TM_Editors::WPML, $response )) {
$this->openClassicTranslationEditor( Obj::prop('jobObject', $response) );
}
}
}
private function openClassicTranslationEditor( $job_object ) {
global $wpdb;
$this->must_render_the_editor = true;
$this->translation_editor = new WPML_Translation_Editor_UI(
$wpdb,
$this->sitepress,
wpml_load_core_tm(),
$job_object,
new WPML_TM_Job_Action_Factory( wpml_tm_load_job_factory() ),
new WPML_TM_Job_Layout( $wpdb, $this->sitepress->get_wp_api() )
);
}
public function display() {
if ( $this->must_render_the_editor ) {
$this->translation_editor->render();
return;
}
?>
<div class="wrap">
<h2><?php echo __( 'Translations queue', 'wpml-translation-management' ); ?></h2>
<div class="js-wpml-abort-review-dialog"></div>
<div id='wpml-remote-jobs-container'></div>
</div>
<?php
}
/**
* @return bool
*/
private function must_open_the_editor() {
return Obj::prop( 'job_id', $_GET ) > 0 || Obj::prop( 'trid', $_GET ) > 0;
}
/**
* @todo this method should be removed but we have to check firts the logic in NextTranslationLink
* @return array
*/
public static function get_cookie_filters() {
$filters = [];
if ( isset( $_COOKIE['wp-translation_ujobs_filter'] ) ) {
parse_str( $_COOKIE['wp-translation_ujobs_filter'], $filters );
$filters = filter_var_array(
$filters,
[
'type' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
'from' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
'to' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
'status' => FILTER_SANITIZE_NUMBER_INT,
]
);
$isTypeValid = Logic::anyPass(
[
Str::startsWith( 'package_' ),
Str::includes( 'st-batch_strings' ),
pipe( Str::replace( 'post_', '' ), Lst::includes( Fns::__, PostTypes::getTranslatable() ) ),
]
);
$activeLanguageCodes = Obj::keys( Languages::getActive() );
if (
$filters['from'] && ! Lst::includes( $filters['from'], $activeLanguageCodes ) ||
$filters['to'] && ! Lst::includes( $filters['to'], $activeLanguageCodes ) ||
( $filters['type'] && ! $isTypeValid( $filters['type'] ) )
) {
$filters = [];
}
}
return $filters;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace WPML\TM\Menu\TranslationRoles;
use WPML\FP\Obj;
class RoleValidator {
/**
* Checks if a specific role is valid.
*
* @param string $roleName
* @return bool
*/
public static function isValid( $roleName ) {
$wp_role = get_role( $roleName );
return $wp_role instanceof \WP_Role;
}
/**
* @param string $roleName
*
* @return string|null
*/
public static function getTheHighestPossibleIfNotValid( $roleName ) {
$wp_role = get_role( $roleName );
$user = wp_get_current_user();
if ( \WPML_WP_Roles::get_highest_level( $wp_role->capabilities ) > \WPML_WP_Roles::get_user_max_level( $user ) ) {
$wp_role = current( \WPML_WP_Roles::get_roles_up_to_user_level( $user ) );
if ( ! $wp_role ) {
return null;
}
$roleName = Obj::prop( 'name', $wp_role );
}
return $roleName;
}
}

View File

@@ -0,0 +1,14 @@
<?php
use WPML\TM\Menu\TranslationServices\SectionFactory;
class WPML_TM_Translation_Roles_Section_Factory implements IWPML_TM_Admin_Section_Factory {
/**
* @return \WPML_TM_Admin_Section|\WPML_TM_Translation_Roles_Section
*/
public function create() {
return new WPML_TM_Translation_Roles_Section( ( new SectionFactory() )->create() );
}
}

View File

@@ -0,0 +1,97 @@
<?php
use WPML\TranslationRoles\UI\Initializer;
use WPML\TM\Menu\TranslationServices\Section;
use WPML\TM\Menu\TranslationMethod\TranslationMethodSettings;
use WPML\FP\Relation;
class WPML_TM_Translation_Roles_Section implements IWPML_TM_Admin_Section {
const SLUG = 'translators';
/**
* @var Section
*/
private $translation_services_section;
public function __construct( Section $translation_services_section ) {
$this->translation_services_section = $translation_services_section;
TranslationMethodSettings::addHooks();
}
/**
* Returns a value which will be used for sorting the sections.
*
* @return int
*/
public function get_order() {
return 300;
}
/**
* Returns the unique slug of the sections which is used to build the URL for opening this section.
*
* @return string
*/
public function get_slug() {
return self::SLUG;
}
/**
* Returns one or more capabilities required to display this section.
*
* @return string|array
*/
public function get_capabilities() {
return [ WPML_Manage_Translations_Role::CAPABILITY, 'manage_options' ];
}
/**
* Returns the caption to display in the section.
*
* @return string
*/
public function get_caption() {
return __( 'Translators', 'wpml-translation-management' );
}
/**
* Returns the callback responsible for rendering the content of the section.
*
* @return callable
*/
public function get_callback() {
return [ $this, 'render' ];
}
/**
* This method is hooked to the `admin_enqueue_scripts` action.
*
* @param string $hook The current page.
*/
public function admin_enqueue_scripts( $hook ) {
if ( Relation::propEq( 'sm', 'translators', $_GET ) ) {
Initializer::loadJS();
}
}
/**
* Used to extend the logic for displaying/hiding the section.
*
* @return bool
*/
public function is_visible() {
return true;
}
/**
* Outputs the content of the section.
*/
public function render() {
?>
<div id="wpml-translation-roles-ui-container"></div>
<?php
$this->translation_services_section->render();
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use TranslationProxy;
class ActivationAjax {
const NONCE_ACTION = 'translation_service_toggle';
const REFRESH_TS_INFO_ACTION = 'refresh_ts_info';
/** @var \WPML_TP_Client */
private $tp_client;
public function __construct( \WPML_TP_Client $tp_client ) {
$this->tp_client = $tp_client;
}
public function add_hooks() {
add_action( 'wp_ajax_translation_service_toggle', array( $this, 'translation_service_toggle' ) );
add_action( 'wp_ajax_refresh_ts_info', array( $this, 'refresh_ts_info' ) );
}
public function translation_service_toggle() {
if ( $this->is_valid_request( self::NONCE_ACTION ) ) {
if ( ! isset( $_POST['service_id'] ) ) {
return;
}
$service_id = (int) filter_var( $_POST['service_id'], FILTER_SANITIZE_NUMBER_INT );
$enable = false;
$response = false;
if ( isset( $_POST['enable'] ) ) {
$enable = filter_var( $_POST['enable'], FILTER_SANITIZE_NUMBER_INT );
}
if ( $enable ) {
if ( $service_id !== TranslationProxy::get_current_service_id() ) {
$response = $this->activate_service( $service_id );
} else {
$response = array( 'activated' => true );
}
}
if ( ! $enable && $service_id === TranslationProxy::get_current_service_id() ) {
TranslationProxy::clear_preferred_translation_service();
$response = $this->deactivate_service();
}
wp_send_json_success( $response );
return;
}
$this->send_invalid_nonce_error();
}
public function refresh_ts_info() {
if ( $this->is_valid_request( self::REFRESH_TS_INFO_ACTION ) ) {
$active_service = $this->tp_client->services()->get_active( true );
if ( $active_service ) {
$active_service = (object) (array) $active_service;
TranslationProxy::build_and_store_active_translation_service( $active_service, $active_service->custom_fields_data );
$activeServiceTemplateRender = ActiveServiceTemplateFactory::createRenderer();
wp_send_json_success( [ 'active_service_block' => $activeServiceTemplateRender() ] );
return;
}
wp_send_json_error(
array( 'message' => __( 'It was not possible to refresh the active translation service information.', 'wpml-translation-management' ) )
);
return;
}
$this->send_invalid_nonce_error();
}
/**
* @param int $service_id
*
* @return array
* @throws \InvalidArgumentException
*/
private function activate_service( $service_id ) {
$result = TranslationProxy::select_service( $service_id );
$message = '';
if ( is_wp_error( $result ) ) {
$message = $result->get_error_message();
}
return array(
'message' => $message,
'reload' => 1,
'activated' => 1,
);
}
private function deactivate_service() {
TranslationProxy::deselect_active_service();
return array(
'message' => '',
'reload' => 1,
'activated' => 0,
);
}
/**
* @param string $action
*
* @return bool
*/
private function is_valid_request( $action ) {
if ( ! isset( $_POST['nonce'] ) ) {
return false;
}
return wp_verify_nonce( filter_var( $_POST['nonce'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ), $action );
}
private function send_invalid_nonce_error() {
$response = array(
'message' => __( 'You are not allowed to perform this action.', 'wpml-translation-management' ),
'reload' => 0,
);
wp_send_json_error( $response );
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class ActivationAjaxFactory implements \IWPML_Backend_Action_Loader {
/**
* @return ActivationAjax
*/
public function create() {
$tp_client_factory = new \WPML_TP_Client_Factory();
return new ActivationAjax( $tp_client_factory->create() );
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use WPML\FP\Maybe;
use function WPML\FP\invoke;
class ActiveServiceRepository {
/**
* @return \WPML_TP_Service|null
*/
public static function get() {
global $sitepress;
$active_service = $sitepress->get_setting( 'translation_service' );
return $active_service ? new \WPML_TP_Service( $active_service ) : null;
}
public static function getId() {
return Maybe::fromNullable( self::get() )
->map( invoke( 'get_id' ) )
->getOrElse( null );
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class ActiveServiceTemplate {
const ACTIVE_SERVICE_TEMPLATE = 'active-service.twig';
const HOURS_BEFORE_TS_REFRESH = 24;
/**
* @param callable $templateRenderer
* @param \WPML_TP_Service $active_service
*
* @return string
*/
public static function render( $templateRenderer, \WPML_TP_Service $active_service ) {
return $templateRenderer( self::getModel( $active_service ), self::ACTIVE_SERVICE_TEMPLATE );
}
/**
* @return array
*/
private static function getModel( \WPML_TP_Service $active_service ) {
$model = [
'strings' => [
'title' => __( 'Active service:', 'wpml-translation-management' ),
'deactivate' => __( 'Deactivate', 'wpml-translation-management' ),
'modal_header' => sprintf(
__(
'Enter here your %s authentication details',
'wpml-translation-management'
),
$active_service->get_name()
),
'modal_tip' => $active_service->get_popup_message() ?
$active_service->get_popup_message() :
__( 'You can find API token at %s site', 'wpml-translation-management' ),
'modal_title' => sprintf(
__( '%s authentication', 'wpml-translation-management' ),
$active_service->get_name()
),
'refresh_language_pairs' => __( 'Refresh language pairs', 'wpml-translation-management' ),
'refresh_ts_info' => __( 'Refresh information', 'wpml-translation-management' ),
'documentation_lower' => __( 'documentation', 'wpml-translation-management' ),
'refreshing_ts_message' => __(
'Refreshing translation service information...',
'wpml-translation-management'
),
],
'active_service' => $active_service,
'nonces' => [
\WPML_TP_Refresh_Language_Pairs::AJAX_ACTION => wp_create_nonce( \WPML_TP_Refresh_Language_Pairs::AJAX_ACTION ),
ActivationAjax::REFRESH_TS_INFO_ACTION => wp_create_nonce( ActivationAjax::REFRESH_TS_INFO_ACTION ),
],
'needs_info_refresh' => self::shouldRefreshData( $active_service ),
];
$authentication_message = [];
/* translators: sentence 1/3: create account with the translation service ("%1$s" is the service name) */
$authentication_message[] = __(
'To send content for translation to %1$s, you need to have an %1$s account.',
'wpml-translation-management'
);
/* translators: sentence 2/3: create account with the translation service ("one" is "one account) */
$authentication_message[] = __(
"If you don't have one, you can create it after clicking the authenticate button.",
'wpml-translation-management'
);
/* translators: sentence 3/3: create account with the translation service ("%2$s" is "documentation") */
$authentication_message[] = __(
'Please, check the %2$s page for more details.',
'wpml-translation-management'
);
$model['strings']['authentication'] = [
'description' => implode( ' ', $authentication_message ),
'authenticate_button' => __( 'Authenticate', 'wpml-translation-management' ),
'de_authorize_button' => __( 'De-authorize', 'wpml-translation-management' ),
'update_credentials_button' => __( 'Update credentials', 'wpml-translation-management' ),
'is_authorized' => self::isAuthorizedText( $active_service->get_name() ),
];
return $model;
}
private static function isAuthorizedText( $serviceName ) {
$query_args = [
'page' => WPML_TM_FOLDER . \WPML_Translation_Management::PAGE_SLUG_MANAGEMENT,
'sm' => 'dashboard',
];
$href = add_query_arg( $query_args, admin_url( 'admin.php' ) );
$dashboard = '<a href="' . $href . '">' .
__( 'Translation Dashboard', 'wpml-translation-management' ) .
'</a>';
$isAuthorized = sprintf(
__( 'Success! You can now send content to %s.', 'wpml-translation-management' ),
$serviceName
);
$isAuthorized .= '<br/>';
// translators: "%s" is replaced with the link to the "Translation Dashboard"
$isAuthorized .= sprintf(
__( 'Go to the %s to choose the content and send it to translation.', 'wpml-translation-management' ),
$dashboard
);
return $isAuthorized;
}
private static function shouldRefreshData( \WPML_TP_Service $active_service ) {
$refresh_time = time() - ( self::HOURS_BEFORE_TS_REFRESH * HOUR_IN_SECONDS );
return ! $active_service->get_last_refresh() || $active_service->get_last_refresh() < $refresh_time;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use function WPML\Container\make;
use function WPML\FP\partial;
class ActiveServiceTemplateFactory {
/**
* @return \Closure
*/
public static function createRenderer() {
$activeService = ActiveServiceRepository::get();
if ( $activeService ) {
$templateRenderer = self::getTemplateRenderer();
return partial( ActiveServiceTemplate::class . '::render', [ $templateRenderer, 'show' ], $activeService );
}
return function () {
return null;
};
}
/**
* @return \WPML_Twig_Template
*/
private static function getTemplateRenderer() {
$paths = [ WPML_TM_PATH . '/templates/menus/translation-services/' ];
$twigLoader = make( \WPML_Twig_Template_Loader::class, [ ':paths' => $paths ] );
return $twigLoader->get_template();
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use WPML\TM\TranslationProxy\Services\AuthorizationFactory;
class AuthenticationAjax {
const AJAX_ACTION = 'translation_service_authentication';
/** @var AuthorizationFactory */
protected $authorize_factory;
/**
* @param AuthorizationFactory $authorize_factory
*/
public function __construct( AuthorizationFactory $authorize_factory ) {
$this->authorize_factory = $authorize_factory;
}
public function add_hooks() {
add_action( 'wp_ajax_translation_service_authentication', [ $this, 'authenticate_service' ] );
add_action( 'wp_ajax_translation_service_update_credentials', [ $this, 'update_credentials' ] );
add_action( 'wp_ajax_translation_service_invalidation', [ $this, 'invalidate_service' ] );
}
/**
* @return bool
*/
public function authenticate_service() {
return $this->handle_action(
function () {
$this->authorize_factory->create()->authorize(
json_decode( stripslashes( $_POST['custom_fields'] ) )
);
},
[ $this, 'is_valid_request_with_params' ],
__( 'Service activated.', 'wpml-translation-management' ),
__(
'The authentication didn\'t work. Please make sure you entered your details correctly and try again.',
'wpml-translation-management'
)
);
}
/**
* @return bool
*/
public function update_credentials() {
return $this->handle_action(
function () {
$this->authorize_factory->create()->updateCredentials(
json_decode( stripslashes( $_POST['custom_fields'] ) )
);
},
[ $this, 'is_valid_request_with_params' ],
__( 'Service credentials updated.', 'wpml-translation-management' ),
__(
'The authentication didn\'t work. Please make sure you entered your details correctly and try again.',
'wpml-translation-management'
)
);
}
/**
* @return bool
*/
public function invalidate_service() {
return $this->handle_action(
function () {
$this->authorize_factory->create()->deauthorize();
},
[ $this, 'is_valid_request' ],
__( 'Service invalidated.', 'wpml-translation-management' ),
__( 'Unable to invalidate this service. Please contact WPML support.', 'wpml-translation-management' )
);
}
/**
* @param callable $action
* @param callable $request_validation
* @param string $success_message
* @param string $failure_message
*
* @return bool
*/
private function handle_action(
callable $action,
callable $request_validation,
$success_message,
$failure_message
) {
if ( $request_validation() ) {
try {
$action();
return $this->send_success_response( $success_message );
} catch ( \Exception $e ) {
return $this->send_error_message( $failure_message );
}
} else {
return $this->send_error_message( __( 'Invalid Request', 'wpml-translation-management' ) );
}
}
/**
* @param string $msg
*
* @return bool
*/
private function send_success_response( $msg ) {
wp_send_json_success(
[
'errors' => 0,
'message' => $msg,
'reload' => 1,
]
);
return true;
}
/**
* @param string $msg
*
* @return bool
*/
private function send_error_message( $msg ) {
wp_send_json_error(
[
'errors' => 1,
'message' => $msg,
'reload' => 0,
]
);
return false;
}
/**
* @return bool
*/
public function is_valid_request() {
return isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], self::AJAX_ACTION );
}
/**
* @return bool
*/
public function is_valid_request_with_params() {
return isset( $_POST['service_id'], $_POST['custom_fields'] ) && $this->is_valid_request();
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use function WPML\Container\make;
class AuthenticationAjaxFactory implements \IWPML_Backend_Action_Loader {
/**
* @return AuthenticationAjax
*/
public function create() {
return make( AuthenticationAjax::class );
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use WPML\DocPage;
use WPML\LIB\WP\Nonce;
use WPML\Setup\Option;
use WPML\TM\Menu\TranslationServices\Endpoints\Activate;
use WPML\TM\Menu\TranslationServices\Endpoints\Deactivate;
use WPML\TM\Menu\TranslationServices\Endpoints\Select;
use WPML\UIPage;
class MainLayoutTemplate {
const SERVICES_LIST_TEMPLATE = 'services-layout.twig';
/**
* @param callable $templateRenderer
* @param callable $activeServiceRenderer
* @param bool $hasPreferredService
* @param callable $retrieveServiceTabsData
*/
public static function render(
$templateRenderer,
$activeServiceRenderer,
$hasPreferredService,
$retrieveServiceTabsData
) {
echo $templateRenderer(
self::getModel( $activeServiceRenderer, $hasPreferredService, $retrieveServiceTabsData ),
self::SERVICES_LIST_TEMPLATE
);
}
/**
* @param callable $activeServiceRenderer
* @param bool $hasPreferredService
* @param callable $retrieveServiceTabsData
*
* @return array
*/
private static function getModel( $activeServiceRenderer, $hasPreferredService, $retrieveServiceTabsData ) {
$services = $retrieveServiceTabsData();
$translationServicesUrl = 'https://wpml.org/documentation/translating-your-contents/professional-translation-via-wpml/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm';
/* Translators: %s is documentation link for Translation Services */
$sectionDescription = sprintf(
'WPML integrates with dozens of professional <a target="_blank" href="%s">translation services</a>. Connect to your preferred service to send and receive translation jobs from directly within WPML.',
$translationServicesUrl
);
return [
'active_service' => $activeServiceRenderer(),
'services' => $services,
'has_preferred_service' => $hasPreferredService,
'has_services' => ! empty( $services ),
'translate_everything' => Option::shouldTranslateEverything(),
'nonces' => [
ActivationAjax::NONCE_ACTION => wp_create_nonce( ActivationAjax::NONCE_ACTION ),
AuthenticationAjax::AJAX_ACTION => wp_create_nonce( AuthenticationAjax::AJAX_ACTION ),
],
'settings_url' => UIPage::getSettings(),
'lsp_logo_placeholder' => WPML_TM_URL . '/res/img/lsp-logo-placeholder.png',
'strings' => [
'translation_services' => __( 'Translation Services', 'wpml-translation-management' ),
'translation_services_description' => __( $sectionDescription, 'wpml-translation-management' ),
'ts' => [
'different' => __( 'Looking for a different translation service?', 'wpml-translation-management' ),
'tell_us_url' => DocPage::addTranslationServiceForm(),
'tell_us' => __( 'Tell us which one', 'wpml-translation-management' ),
],
],
'endpoints' => [
'selectService' => [
'endpoint' => Select::class,
'nonce' => Nonce::create( Select::class )
],
'deactivateService' => [
'nonce' => Nonce::create( Deactivate::class ),
'endpoint' => Deactivate::class
],
'activateService' => [
'nonce' => Nonce::create( Activate::class ),
'endpoint' => Activate::class
],
],
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class NoSiteKeyTemplate {
const TEMPLATE = 'no-site-key.twig';
/**
* @param callable $templateRenderer
*/
public static function render( $templateRenderer ) {
echo $templateRenderer( self::get_no_site_key_model(), self::TEMPLATE );
}
/**
* @return array
*/
private static function get_no_site_key_model() {
return [
'registration' => [
'link' => admin_url( 'plugin-install.php?tab=commercial#repository-wpml' ),
'text' => __(
'Please register WPML to enable the professional translation option',
'wpml-translation-management'
),
],
];
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class Resources implements \IWPML_Backend_Action {
public function add_hooks() {
if ( $this->is_active() ) {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
}
public function enqueue_styles() {
wp_enqueue_style(
'wpml-tm-ts-admin-section',
WPML_TM_URL . '/res/css/admin-sections/translation-services.css',
array(),
WPML_TM_VERSION
);
wp_enqueue_style(
'wpml-tm-translation-services',
WPML_TM_URL . '/dist/css/translationServices/styles.css',
[],
WPML_TM_VERSION
);
}
public function enqueue_scripts() {
wp_enqueue_script(
'wpml-tm-ts-admin-section',
WPML_TM_URL . '/res/js/translation-services.js',
array(),
WPML_TM_VERSION
);
wp_enqueue_script(
'wpml-tm-translation-services',
WPML_TM_URL . '/dist/js/translationServices/app.js',
array(),
WPML_TM_VERSION
);
wp_enqueue_script(
'wpml-tp-api',
WPML_TM_URL . '/res/js/wpml-tp-api.js',
array( 'jquery', 'wp-util' ),
WPML_TM_VERSION
);
}
private function is_active() {
return isset( $_GET['sm'] ) && 'translators' === $_GET['sm'];
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class Section implements \IWPML_TM_Admin_Section {
const SLUG = 'translators';
/**
* The SitePress instance.
*
* @var \SitePress
*/
private $sitepress;
/**
* The WPML_WP_API instance.
*
* @var \WPML_WP_API
*/
private $wp_api;
/**
* The template to use.
*
* @var mixed $template
*/
private $template;
/**
* WPML_TM_Translation_Services_Admin_Section constructor.
*
* @param \SitePress $sitepress The SitePress instance.
* @param callable $template The template to use.
*/
public function __construct(
\SitePress $sitepress,
$template
) {
$this->sitepress = $sitepress;
$this->wp_api = $sitepress->get_wp_api();
$this->template = $template;
}
/**
* Returns a value which will be used for sorting the sections.
*
* @return int
*/
public function get_order() {
return 400;
}
/**
* Outputs the content of the section.
*/
public function render() {
call_user_func( $this->template );
}
/**
* Used to extend the logic for displaying/hiding the section.
*
* @return bool
*/
public function is_visible() {
return ! $this->wp_api->constant( 'ICL_HIDE_TRANSLATION_SERVICES' ) && ( $this->wp_api->constant( 'WPML_BYPASS_TS_CHECK' ) || ! $this->sitepress->get_setting( 'translation_service_plugin_activated' ) );
}
/**
* Returns the unique slug of the sections which is used to build the URL for opening this section.
*
* @return string
*/
public function get_slug() {
return self::SLUG;
}
/**
* Returns one or more capabilities required to display this section.
*
* @return string|array
*/
public function get_capabilities() {
return [ \WPML_Manage_Translations_Role::CAPABILITY, 'manage_options' ];
}
/**
* Returns the caption to display in the section.
*
* @return string
*/
public function get_caption() {
return __( 'Translation Services', 'wpml-translation-management' );
}
/**
* Returns the callback responsible for rendering the content of the section.
*
* @return callable
*/
public function get_callback() {
return array( $this, 'render' );
}
/**
* This method is hooked to the `admin_enqueue_scripts` action.
*
* @param string $hook The current page.
*/
public function admin_enqueue_scripts( $hook ) {}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use WPML\LIB\WP\Http;
use WPML\TM\Geolocalization;
use function WPML\Container\make;
use function WPML\FP\partial;
use function WPML\FP\partialRight;
class SectionFactory implements \IWPML_TM_Admin_Section_Factory {
/**
* @return Section
*/
public function create() {
global $sitepress;
return new Section(
$sitepress,
$this->site_key_exists() ?
$this->createServicesListRenderer() :
partial( NoSiteKeyTemplate::class . '::render', $this->getTemplateRenderer() )
);
}
/**
* @return bool|string
*/
private function site_key_exists() {
$site_key = false;
if ( class_exists( 'WP_Installer' ) ) {
$repository_id = 'wpml';
$site_key = \WP_Installer()->get_site_key( $repository_id );
}
return $site_key;
}
/**
* @param \WPML_Twig_Template_Loader $twig_loader
* @param \WPML_TP_Client $tp_client
*
* @return callable
*/
private function createServicesListRenderer() {
/**
* Section: "Partner services", "Other services" and "Translation Management Services"
*/
$getServicesTabs = partial(
ServicesRetriever::class . '::get',
$this->getTpApiServices(),
Geolocalization::getCountryByIp( Http::post() ),
partialRight(
[ ServiceMapper::class, 'map' ],
[ ActiveServiceRepository::class, 'getId' ]
)
);
return partial(
MainLayoutTemplate::class . '::render',
$this->getTemplateRenderer(),
ActiveServiceTemplateFactory::createRenderer(),
\TranslationProxy::has_preferred_translation_service(),
$getServicesTabs
);
}
/**
* @return callable
*/
private function getTemplateRenderer() {
$template = make(
\WPML_Twig_Template_Loader::class,
[
':paths' => [
WPML_TM_PATH . '/templates/menus/translation-services/',
WPML_PLUGIN_PATH . '/templates/pagination/',
],
]
)->get_template();
return [ $template, 'show' ];
}
/**
* @return \WPML_TP_API_Services
*/
private function getTpApiServices() {
return make( \WPML_TP_Client_Factory::class )->create()->services();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
class ServiceMapper {
/**
* @param \WPML_TP_Service $service
* @param callable $getActiveServiceId
*
* @return array
*/
public static function map( \WPML_TP_Service $service, $getActiveServiceId ) {
$isActive = $service->get_id() === $getActiveServiceId();
if ( $isActive ) {
$service->set_custom_fields_data();
}
return [
'id' => $service->get_id(),
'logo_url' => $service->get_logo_preview_url(),
'name' => $service->get_name(),
'description' => $service->get_description(),
'doc_url' => $service->get_doc_url(),
'active' => $isActive ? 'active' : 'inactive',
'rankings' => $service->get_rankings(),
'how_to_get_credentials_desc' => $service->get_how_to_get_credentials_desc(),
'how_to_get_credentials_url' => $service->get_how_to_get_credentials_url(),
'is_authorized' => ! empty( $service->get_custom_fields_data() ),
'client_create_account_page_url' => $service->get_client_create_account_page_url(),
'custom_fields' => $service->get_custom_fields(),
'countries' => $service->get_countries(),
'url' => $service->get_url(),
];
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace WPML\TM\Menu\TranslationServices;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\curryN;
use function WPML\FP\invoke;
use function WPML\FP\partialRight;
use function WPML\FP\pipe;
class ServicesRetriever {
public static function get( \WPML_TP_API_Services $servicesAPI, $getUserCountry, $mapService ) {
$userCountry = $getUserCountry( isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null );
// $buildSection :: $services -> $header -> $showPopularity -> string
$buildSection = self::buildSection( $mapService );
// $otherSection :: $services -> $header -> string
$otherSection = $buildSection( Fns::__, Fns::__, false, true );
$buildPartnerServicesSections = self::buildPartnerServicesSections( $buildSection, $userCountry );
$partnerServices = $servicesAPI->get_translation_services( true );
$services = $buildPartnerServicesSections( $partnerServices );
$services[] = $otherSection(
$servicesAPI->get_translation_services( false ),
__( 'Other Translation Services', 'wpml-translation-management' )
);
$services[] = $otherSection(
$servicesAPI->get_translation_management_systems(),
__( 'Translation Management System', 'wpml-translation-management' )
);
return $services;
}
// buildPartnerServicesSections :: \WPML_TP_Services[] -> string[]
private static function buildPartnerServicesSections( $buildSection, $userCountry ) {
$headers = [
'regular' => __( 'Partner Translation Services', 'wpml-translation-management' ),
'inCountry' => __(
sprintf(
'Partner Translation Services in %s',
isset( $userCountry['name'] ) ? $userCountry['name'] : ''
),
'wpml-translation-management'
),
'otherCountries' => __(
'Other Partner Translation Services from Around the World',
'wpml-translation-management'
),
];
// $partnerSection :: $services -> $header -> string
$partnerSection = $buildSection( Fns::__, Fns::__, true, Fns::__ );
// $regularPartnerSection :: $services -> string
$regularPartnerSection = $partnerSection( Fns::__, $headers['regular'], true );
// $partnersInCountry :: $services -> string
$partnersInCountry = $partnerSection( Fns::__, $headers['inCountry'], false );
// $partnersOther :: $services -> string
$partnersOther = $partnerSection( Fns::__, $headers['otherCountries'], true );
// $getServicesFromCountry :: [$servicesFromCountry, $otherServices] -> $servicesFromCountry
$inUserCountry = Lst::nth( 0 );
// $getServicesFromOtherCountries :: [$servicesFromCountry, $otherServices] -> $otherServices
$inOtherCountries = Lst::nth( 1 );
// $splitSections :: [$servicesFromCountry, $otherServices] -> [string, string]
$splitSections = Fns::converge(
Lst::make(),
[
pipe( $inUserCountry, $partnersInCountry ),
pipe( $inOtherCountries, $partnersOther ),
]
);
// $hasUserCountry :: [$servicesFromCountry, $otherServices] -> bool
$hasUserCountry = pipe( $inUserCountry, Logic::isEmpty(), Logic::not() );
return pipe(
Lst::partition( self::belongToUserCountry( $userCountry ) ),
Logic::ifElse(
$hasUserCountry,
$splitSections,
pipe( $inOtherCountries, $regularPartnerSection, Lst::make() )
)
);
}
/**
* @param callable $mapService
*
* @return callable
*/
private static function buildSection( $mapService ) {
return curryN(
4,
function ( $services, $header, $showPopularity, $pagination ) use ( $mapService ) {
return [
'services' => Fns::map( Fns::unary( $mapService ), $services ),
'header' => $header,
'showPopularity' => $showPopularity,
'pagination' => $pagination,
];
}
);
}
// belongToUserCountry :: \WPML_TP_Service -> bool
private static function belongToUserCountry( $userCountry ) {
return pipe(
invoke( 'get_countries' ),
Fns::map( Obj::prop( 'code' ) ),
Lst::find( Relation::equals( Obj::prop( 'code', $userCountry ) ) ),
Logic::isNotNull()
);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace WPML\TM\Menu\TranslationServices\Endpoints;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\TM\TranslationProxy\Services\AuthorizationFactory;
class Activate implements IHandler {
public function run( Collection $data ) {
$serviceId = $data->get( 'service_id' );
$apiTokenData = $data->get( 'api_token' );
$authorize = function ( $serviceId ) use ( $apiTokenData ) {
$authorization = ( new AuthorizationFactory )->create();
try {
$authorization->authorize( (object) Obj::pickBy( Logic::isNotEmpty(), $apiTokenData ) );
return Either::of( $serviceId );
} catch ( \Exception $e ) {
$authorization->deauthorize();
return Either::left( $e->getMessage() );
}
};
return Either::of( $serviceId )
->chain( [ Select::class, 'select' ] )
->chain( $authorize );
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace WPML\TM\Menu\TranslationServices\Endpoints;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
class Deactivate implements IHandler {
public function run( Collection $data ) {
if ( \TranslationProxy::get_current_service_id() ) {
\TranslationProxy::clear_preferred_translation_service();
\TranslationProxy::deselect_active_service();
}
return Either::of( 'OK' );
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace WPML\TM\Menu\TranslationServices\Endpoints;
use WPML\Ajax\IHandler;
use WPML\Collect\Support\Collection;
use WPML\FP\Either;
class Select implements IHandler {
public function run( Collection $data ) {
$serviceId = $data->get( 'service_id' );
return self::select( $serviceId );
}
public static function select( $serviceId ) {
$deactivateOldService = function () {
\TranslationProxy::clear_preferred_translation_service();
\TranslationProxy::deselect_active_service();
};
$activateService = function ( $serviceId ) {
$result = \TranslationProxy::select_service( $serviceId );
return \is_wp_error( $result ) ? Either::left( $result->get_error_message() ) : Either::of( $serviceId );
};
$currentServiceId = \TranslationProxy::get_current_service_id();
if ( $currentServiceId ) {
if ( $currentServiceId === $serviceId ) {
return Either::of( $serviceId );
} else {
$deactivateOldService();
}
}
return $activateService( $serviceId );
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace WPML\TM\Menu\TranslationServices\Troubleshooting;
class RefreshServices {
const TEMPLATE = 'refresh-services.twig';
const AJAX_ACTION = 'wpml_tm_refresh_services';
/**
* @var \IWPML_Template_Service
*/
private $template;
/**
* @var \WPML_TP_API_Services
*/
private $tp_services;
public function __construct( \IWPML_Template_Service $template, \WPML_TP_API_Services $tp_services ) {
$this->template = $template;
$this->tp_services = $tp_services;
}
public function add_hooks() {
add_action( 'after_setup_complete_troubleshooting_functions', array( $this, 'render' ), 1 );
add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'refresh_services_ajax_handler' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
public function render() {
echo $this->template->show( $this->get_model(), self::TEMPLATE );
}
/**
* @return array
*/
private function get_model() {
return array(
'button_text' => __( 'Refresh Translation Services', 'wpml-translation-management' ),
'nonce' => wp_create_nonce( self::AJAX_ACTION ),
);
}
public function refresh_services_ajax_handler() {
if ( $this->is_valid_request() ) {
if ( $this->refresh_services() ) {
wp_send_json_success(
array(
'message' => __( 'Services Refreshed.', 'wpml-translation-management' ),
)
);
} else {
wp_send_json_error(
array(
'message' => __(
'WPML cannot load the list of translation services. This can be a connection problem. Please wait a minute and reload this page.
If the problem continues, please contact WPML support.',
'wpml-translation-management'
),
)
);
}
} else {
wp_send_json_error(
array(
'message' => __( 'Invalid Request.', 'wpml-translation-management' ),
)
);
}
}
/**
* @return bool
*/
public function refresh_services() {
return $this->tp_services->refresh_cache() && $this->refresh_active_service();
}
private function refresh_active_service() {
$active_service = $this->tp_services->get_active();
if ( $active_service ) {
$active_service = (object) (array) $active_service; // Cast to stdClass
\TranslationProxy::build_and_store_active_translation_service( $active_service, $active_service->custom_fields_data );
}
return true;
}
/**
* @return bool
*/
private function is_valid_request() {
return isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], self::AJAX_ACTION );
}
public function enqueue_scripts() {
wp_enqueue_script(
'wpml-tm-refresh-services',
WPML_TM_URL . '/res/js/refresh-services.js',
array(),
WPML_TM_VERSION
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace WPML\TM\Menu\TranslationServices\Troubleshooting;
use function WPML\Container\make;
class RefreshServicesFactory implements \IWPML_Backend_Action_Loader {
/**
* @return RefreshServices|null
* @throws \Auryn\InjectionException
*/
public function create() {
$hooks = null;
if ( $this->is_visible() ) {
$hooks = $this->create_an_instance();
}
return $hooks;
}
/**
* @return RefreshServices
* @throws \Auryn\InjectionException
*/
public function create_an_instance() {
$templateService = make(
\WPML_Twig_Template_Loader::class,
[ ':paths' => [ WPML_TM_PATH . '/templates/menus/translation-services' ] ]
);
$tpClientFactory = make( \WPML_TP_Client_Factory::class );
return new RefreshServices( $templateService->get_template(), $tpClientFactory->create()->services() );
}
/**
* @return string
*/
private function is_visible() {
return ( isset( $_GET['page'] ) && 'sitepress-multilingual-cms/menu/troubleshooting.php' === $_GET['page'] ) ||
( isset( $_POST['action'] ) && RefreshServices::AJAX_ACTION === $_POST['action'] );
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_TM_Admin_Menus_Factory implements IWPML_Backend_Action_Loader {
public function create() {
if ( isset( $_GET['page'], $_GET['sm'] ) ) {
return new WPML_TM_Admin_Menus_Hooks();
}
}
}

View File

@@ -0,0 +1,39 @@
<?php
use WPML\UIPage;
class WPML_TM_Admin_Menus_Hooks implements IWPML_Action {
public function add_hooks() {
add_action( 'init', array( $this, 'init_action' ) );
}
public function init_action() {
$this->redirect_settings_menu();
$this->redirect_from_empty_basket_page();
}
public function redirect_settings_menu() {
if ( isset( $_GET['page'], $_GET['sm'] )
&& WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_MANAGEMENT === $_GET['page']
&& in_array( $_GET['sm'], array( 'mcsetup', 'notifications', 'custom-xml-config' ), true )
) {
$query = $_GET;
$query['page'] = WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS;
wp_safe_redirect( add_query_arg( $query ), 302, 'WPML' );
}
}
public function redirect_from_empty_basket_page() {
if ( $this->is_tm_basket_empty() ) {
$query = $_GET;
$url = add_query_arg( $query );
wp_safe_redirect( remove_query_arg( 'sm', $url ), 302, 'WPML' );
}
}
public static function is_tm_basket_empty() {
return UIPage::isTMBasket( $_GET ) && TranslationProxy_Basket::get_basket_items_count( true ) === 0;
}
}