first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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' => '«',
|
||||
'next_text' => '»',
|
||||
'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–%2$s of %3$s', 'wpml-translation-management' ), $page_from, $page_to, $page_total ); ?>
|
||||
</span>
|
||||
<?php echo $page_links; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"/> <?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;
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 ),
|
||||
__( 'Don’t 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>';
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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' ) );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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_' ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
@@ -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 ) {}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user