Файловый менеджер - Редактировать - /var/www/xthruster/html/wp-content/uploads/flags/classes.tar
Назад
locale.php 0000644 00000001101 14717654505 0006523 0 ustar 00 <?php namespace ImageOptimization\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Locale { private $original_locale; public function set_utf_locale( int $category = LC_CTYPE ) { setlocale( $category, 'en_US.UTF-8' ); } public function get_locale( int $category = LC_CTYPE ) { return setlocale( $category, 0 ); } public function reset_to_original( int $category = LC_CTYPE ) { return setlocale( $category, $this->original_locale ); } public function __construct() { $this->original_locale = $this->get_locale(); } } file-utils.php 0000644 00000005040 14717654505 0007347 0 ustar 00 <?php namespace ImageOptimization\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class File_Utils { public static function get_extension( string $path ): string { $locale = new Locale(); $locale->set_utf_locale(); $extension = pathinfo( $path, PATHINFO_EXTENSION ); $locale->reset_to_original(); return $extension; } public static function get_basename( string $path ): string { $locale = new Locale(); $locale->set_utf_locale(); $basename = pathinfo( $path, PATHINFO_BASENAME ); $locale->reset_to_original(); return $basename; } public static function replace_extension( string $path, string $new_extension, bool $unique_filename = false ): string { $locale = new Locale(); $locale->set_utf_locale(); $path = pathinfo( $path ); $basename = sprintf( '%s.%s', $path['filename'], $new_extension ); if ( $unique_filename ) { $basename = wp_unique_filename( $path['dirname'], $basename ); } $locale->reset_to_original(); return sprintf( '%s/%s', $path['dirname'], $basename ); } public static function get_unique_path( string $path ): string { $locale = new Locale(); $locale->set_utf_locale(); $path = pathinfo( $path ); $basename = sprintf( '%s.%s', $path['filename'], $path['extension'] ); $locale->reset_to_original(); return sprintf( '%s/%s', $path['dirname'], wp_unique_filename( $path['dirname'], $basename ) ); } public static function get_relative_upload_path( string $path ): string { $locale = new Locale(); $locale->set_utf_locale(); $path = _wp_relative_upload_path( $path ); $locale->reset_to_original(); return $path; } public static function get_url_from_path( string $full_path ): string { $locale = new Locale(); $locale->set_utf_locale(); $upload_info = wp_upload_dir(); $url_base = $upload_info['baseurl']; $parts = preg_split( '/\/wp-content\/uploads/', $full_path ); $locale->reset_to_original(); return $url_base . $parts[1]; } public static function format_file_size( int $file_size_in_bytes, $decimals = 2 ): string { $sizes = [ __( '%s Bytes', 'image-optimization' ), __( '%s Kb', 'image-optimization' ), __( '%s Mb', 'image-optimization' ), __( '%s Gb', 'image-optimization' ), ]; if ( ! $file_size_in_bytes ) { return sprintf( $sizes[0], 0 ); } $current_scale = floor( log( $file_size_in_bytes ) / log( 1024 ) ); $formatted_value = number_format( $file_size_in_bytes / pow( 1024, $current_scale ), $decimals ); return sprintf( $sizes[ $current_scale ], $formatted_value ); } } client/client.php 0000644 00000020257 14717654505 0010035 0 ustar 00 <?php namespace ImageOptimization\Classes\Client; use ImageOptimization\Classes\Exceptions\Client_Exception; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\Image; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; use WP_Error; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Client */ class Client { const BASE_URL = 'https://my.elementor.com/api/v2/image-optimizer/'; const STATUS_CHECK = 'status/check'; private bool $refreshed = false; public static ?Client $instance = null; /** * get_instance * @return Client|null */ public static function get_instance(): ?Client { if ( ! self::$instance ) { self::$instance = new self(); } return self::$instance; } public static function get_site_info(): array { return [ // Which API version is used. 'app_version' => IMAGE_OPTIMIZATION_VERSION, // Which language to return. 'site_lang' => get_bloginfo( 'language' ), // site to connect 'site_url' => trailingslashit( home_url() ), // current user 'local_id' => get_current_user_id(), // Media library stats 'media_data' => base64_encode( wp_json_encode( self::get_request_stats() ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode ]; } private static function get_request_stats(): array { $optimization_stats = Optimization_Stats::get_image_stats(); $image_sizes = []; foreach ( wp_get_registered_image_subsizes() as $image_size_key => $image_size_data ) { $image_sizes[] = [ 'label' => $image_size_key, 'size' => "{$image_size_data['width']}x{$image_size_data['height']}", ]; } return [ 'not_optimized_images' => $optimization_stats['total_image_count'] - $optimization_stats['optimized_image_count'], 'optimized_images' => $optimization_stats['optimized_image_count'], 'images_sizes' => $image_sizes, ]; } public function make_request( $method, $endpoint, $body = [], array $headers = [], $file = false, $file_name = '' ) { $headers = array_replace_recursive([ 'x-elementor-image-optimizer' => IMAGE_OPTIMIZATION_VERSION, ], $headers); $headers = array_replace_recursive( $headers, $this->is_connected() ? $this->generate_authentication_headers( $endpoint ) : [] ); $body = array_replace_recursive( $body, $this->get_site_info() ); try { if ( $file ) { $boundary = wp_generate_password( 24, false ); $body = $this->get_upload_request_body( $body, $file, $boundary, $file_name ); // add content type header $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary; } } catch ( Client_Exception $ce ) { return new WP_Error( 500, $ce->getMessage() ); } $response = $this->request( $method, $endpoint, [ 'timeout' => 100, 'headers' => $headers, 'body' => $body, ] ); return ( new Client_Response( $response ) )->handle(); } private static function get_remote_url( $endpoint ): string { return self::BASE_URL . $endpoint; } protected function is_connected(): bool { return Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->is_connected(); } protected function generate_authentication_headers( $endpoint ): array { $connect_instance = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance; if ( ! $connect_instance->get_is_connect_on_fly() ) { $headers = [ 'data' => base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode wp_json_encode( [ 'app' => 'library' ] ) ), 'access_token' => $connect_instance->get_access_token() ?? '', 'client_id' => $connect_instance->get_client_id() ?? '', ]; if ( $connect_instance->is_activated() ) { $headers['key'] = $connect_instance->get_activation_state() ?? ''; } } else { $headers = $this->add_bearer_token([ 'x-elementor-apps-connect' => true, ]); } $headers['endpoint'] = $endpoint; $headers['x-elementor-apps'] = 'image-optimizer'; return $headers; } protected function request( $method, $endpoint, $args = [] ) { $args['method'] = $method; $response = wp_remote_request( self::get_remote_url( $endpoint ), $args ); if ( is_wp_error( $response ) ) { $message = $response->get_error_message(); return new WP_Error( $response->get_error_code(), is_array( $message ) ? join( ', ', $message ) : $message ); } $body = wp_remote_retrieve_body( $response ); $response_code = (int) wp_remote_retrieve_response_code( $response ); if ( ! $response_code ) { return new WP_Error( 500, 'No Response' ); } // Server sent a success message without content. if ( 'null' === $body ) { $body = true; } $body = json_decode( $body ); if ( false === $body ) { return new WP_Error( 422, 'Wrong Server Response' ); } if ( Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_is_connect_on_fly() ) { // If the token is invalid, refresh it and try again once only. if ( ! $this->refreshed && ! empty( $body->message ) && ( false !== strpos( $body->message, 'Invalid Token' ) ) ) { Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->refresh_token(); $this->refreshed = true; $args['headers'] = $this->add_bearer_token( $args['headers'] ); return $this->request( $method, $endpoint, $args ); } } if ( 200 !== $response_code ) { // In case $as_array = true. $message = $body->message ?? wp_remote_retrieve_response_message( $response ); $message = is_array( $message ) ? join( ', ', $message ) : $message; $code = isset( $body->code ) ? (int) $body->code : $response_code; return new WP_Error( $code, $message ); } return $body; } public function add_bearer_token( $headers ) { if ( $this->is_connected() ) { $headers['Authorization'] = 'Bearer ' . Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_access_token(); } return $headers; } /** * get_upload_request_body * * @param array $body * @param $file * @param string $boundary * @param string $file_name * * @return string * @throws Client_Exception */ private function get_upload_request_body( array $body, $file, string $boundary, string $file_name = '' ): string { $payload = ''; // add all body fields as standard POST fields: foreach ( $body as $name => $value ) { $payload .= '--' . $boundary; $payload .= "\r\n"; $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n"; $payload .= $value; $payload .= "\r\n"; } if ( is_array( $file ) ) { foreach ( $file as $key => $file_data ) { $payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary ); } } else { $image_mime = image_type_to_mime_type( exif_imagetype( $file ) ); if ( ! in_array( $image_mime, Image::get_supported_mime_types(), true ) && ( 'application/octet-stream' === $image_mime && 'avif' !== File_Utils::get_extension( $file ) ) ) { throw new Client_Exception( "Unsupported mime type `$image_mime`" ); } if ( empty( $file_name ) ) { $file_name = basename( $file ); } $payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary ); } $payload .= '--' . $boundary . '--'; return $payload; } /** * get_file_payload * @param string $filename * @param string $file_type * @param string $file_path * @param string $boundary * @return string */ private function get_file_payload( string $filename, string $file_type, string $file_path, string $boundary ): string { $name = $filename ?? basename( $file_path ); $mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type; $payload = ''; // Upload the file $payload .= '--' . $boundary; $payload .= "\r\n"; $payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n"; $payload .= 'Content-Type: ' . $mine_type . "\r\n"; $payload .= "\r\n"; $payload .= file_get_contents( $file_path ); $payload .= "\r\n"; return $payload; } } client/client-response.php 0000644 00000002536 14717654505 0011671 0 ustar 00 <?php namespace ImageOptimization\Classes\Client; use Exception; use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Bulk_Token_Expired_Error; use ImageOptimization\Modules\Optimization\Classes\Exceptions\Image_Already_Optimized_Error; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Client_Response { private array $known_errors; /** * @var mixed */ private $response; /** * @throws Throwable|Quota_Exceeded_Error|Bulk_Token_Expired_Error|Image_Already_Optimized_Error */ public function handle() { if ( ! is_wp_error( $this->response ) ) { return $this->response; } $message = $this->response->get_error_message(); if ( isset( $this->known_errors[ $message ] ) ) { throw $this->known_errors[ $message ]; } throw new Exception( $message ); } public function __construct( $response ) { $this->known_errors = [ 'user reached limit' => new Quota_Exceeded_Error( esc_html__( 'Plan quota reached', 'image-optimization' ) ), 'Bulk token expired' => new Bulk_Token_Expired_Error( esc_html__( 'Bulk token expired', 'image-optimization' ) ), 'Image already optimized' => new Image_Already_Optimized_Error( esc_html__( 'Image already optimized', 'image-optimization' ) ), ]; $this->response = $response; } } logger.php 0000644 00000001555 14717654505 0006560 0 ustar 00 <?php namespace ImageOptimization\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Logger { public const LEVEL_ERROR = 'error'; public const LEVEL_WARN = 'warn'; public const LEVEL_INFO = 'info'; public static function log( string $log_level, $message ): void { $backtrace = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace $class = $backtrace[1]['class'] ?? null; $type = $backtrace[1]['type'] ?? null; $function = $backtrace[1]['function']; if ( $class ) { $message = '[Image Optimizer]: ' . $log_level . ' in ' . "$class$type$function()" . ': ' . $message; } else { $message = '[Image Optimizer]: ' . $log_level . ' in ' . "$function()" . ': ' . $message; } error_log( $message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } file-system/exceptions/file-system-operation-error.php 0000644 00000000411 14717654505 0017277 0 ustar 00 <?php namespace ImageOptimization\Classes\File_System\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class File_System_Operation_Error extends Exception { protected $message = 'File system operation error'; } file-system/file-system.php 0000644 00000007450 14717654505 0012003 0 ustar 00 <?php namespace ImageOptimization\Classes\File_System; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); /** * A wrapper class under WP_Filesystem that throws clear exceptions in case if something went wrong. */ class File_System { /** * Writes a string to a file. * * @param string $file Remote path to the file where to write the data. * @param string $contents The data to write. * @param int|false $mode The file permissions as octal number, usually 0644. * * @return true * * @throws File_System_Operation_Error */ public static function put_contents( string $file, string $contents, $mode = false ): bool { global $wp_filesystem; $result = $wp_filesystem->put_contents( $file, $contents, $mode ); if ( ! $result ) { throw new File_System_Operation_Error( 'Error while writing ' . $file ); } return true; } /** * Deletes a file or directory. * * @param string $file Path to the file or directory. * @param bool $recursive If set to true, deletes files and folders recursively. * @param string|false $type Type of resource. 'f' for file, 'd' for directory. * * @return true * * @throws File_System_Operation_Error */ public static function delete( string $file, bool $recursive = false, $type = false ): bool { global $wp_filesystem; $result = $wp_filesystem->delete( $file, $recursive, $type ); if ( ! $result ) { throw new File_System_Operation_Error( 'Error while deleting ' . $file ); } return true; } /** * @param string $source Path to the source file. * @param string $destination Path to the destination file. * @param bool $overwrite Whether to overwrite the destination file if it exists. * * @return true * * @throws File_System_Operation_Error */ public static function move( string $source, string $destination, bool $overwrite = false ): bool { global $wp_filesystem; $result = $wp_filesystem->move( $source, $destination, $overwrite ); if ( ! $result ) { throw new File_System_Operation_Error( "Error while moving {$source} to {$destination}" ); } return true; } /** * Copies a file. * * @param string $source Path to the source file. * @param string $destination Path to the destination file. * @param bool $overwrite Whether to overwrite the destination file if it exists. * @param int|false $mode The permissions as octal number, usually 0644 for files, 0755 for dirs. * * @return bool * * @throws File_System_Operation_Error */ public static function copy( string $source, string $destination, bool $overwrite = false, $mode = false ): bool { global $wp_filesystem; $result = $wp_filesystem->copy( $source, $destination, $overwrite, $mode ); if ( ! $result ) { throw new File_System_Operation_Error( "Error while copying {$source} to {$destination}" ); } return true; } /** * Checks if a file or directory exists. * * @param string $path Path to file or directory. * * @return bool Whether $path exists or not. */ public static function exists( string $path ): bool { global $wp_filesystem; return $wp_filesystem->exists( $path ); } /** * Gets the file size (in bytes). * * @param ?string $path Path to file. * * @return int Size of the file in bytes on success, false on failure. * * @throws File_System_Operation_Error */ public static function size( ?string $path ): int { global $wp_filesystem; if ( is_null( $path ) ) { throw new File_System_Operation_Error( 'Null file path provided' ); } $size = $wp_filesystem->size( $path ); if ( ! $size ) { throw new File_System_Operation_Error( "Unable to calculate file size for $path" ); } return $size; } } rest/route.php 0000644 00000032455 14717654505 0007417 0 ustar 00 <?php namespace ImageOptimization\Classes\Rest; use ReflectionClass; use WP_Error; use WP_REST_Request; use WP_REST_Response; use WP_User; abstract class Route { /** * Should the endpoint be validated for user authentication? * If set to TRUE, the default permission callback will make sure the user is logged in and has a valid user id * @var bool */ protected $auth = true; /** * holds current authenticated user id * @var int */ protected $current_user_id; /** * Should the endpoint override an existing one? * @var bool */ protected bool $override = false; /** * Rest Endpoint namespace * @var string */ protected string $namespace = 'image-optimizer/v1'; /** * @var array The valid HTTP methods. The list represents the general REST methods. Do not modify. */ private array $valid_http_methods = [ 'GET', 'PATCH', 'POST', 'PUT', 'DELETE', 'HEAD', ]; /** * Route_Base constructor. */ public function __construct() { add_action( 'rest_api_init', [ $this, 'rest_api_init' ] ); } /** * rest_api_init * * Registers REST endpoints. * Loops through the REST methods for this route, creates an endpoint configuration for * each of them and registers all the endpoints with the WordPress system. */ public function rest_api_init(): void { $methods = $this->get_methods(); if ( empty( $methods ) ) { return; } $callbacks = false; foreach ( (array) $methods as $method ) { if ( ! in_array( $method, $this->valid_http_methods ) ) { continue; } if ( $method && ! $callbacks ) { $callbacks = []; } $callbacks[] = $this->build_endpoint_method_config( $method ); } $arguments = $this->get_arguments(); if ( ! $callbacks && empty( $arguments ) ) { return; } $arguments = array_merge( $arguments, (array) $callbacks ); register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override ); } /** * get_methods * Rest Endpoint methods * * Returns an array of the supported REST methods for this route * @return array<string> REST methods being configured for this route. */ abstract public function get_methods(): array; /** * get_callback * * Returns a reference to the callback function to handle the REST method specified by the /method/ parameter. * @param string $method The REST method name * * @return callable A reference to a member function with the same name as the REST method being passed as a parameter, * or a reference to the default function /callback/. */ public function get_callback_method( string $method ): callable { $method_name = strtolower( $method ); $callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback'; return [ $this, $callback ]; } /** * get_permission_callback_method * * Returns a reference to the permission callback for the method if exists or the default one if it doesn't. * @param string $method The REST method name * * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise * returns a reference to the default member method /permission_callback/. */ public function get_permission_callback_method( string $method ): callable { $method_name = strtolower( $method ); $permission_callback_method = $method_name . '_permission_callback'; $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback'; return [ $this, $permission_callback ]; } /** * maybe_add_args_to_config * * Checks if the class has a method call (rest-method)_args. * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key. * if the function (rest-method)_consumes exists, it will add the response to the config object under the /consumes/ key. * if the function (rest-method)_produces exists, it will add the response to the config object under the /produces/ key. * if the function (rest-method)_summary exists, it will add the response to the config object under the /summary/ key. * if the function (rest-method)_description exists, it will add the response to the config object under the /description/ key. * @param string $method The REST method name being configured * @param array $config The configuration object for the method * * @return array The configuration object for the method, possibly after being amended */ public function maybe_add_args_to_config( string $method, array $config ): array { $method_name = strtolower( $method ); $method_args = $method_name . '_args'; if ( $this->method_exists_in_current_class( $method_args ) ) { $config['args'] = $this->{$method_args}(); } $config['consumes'] =[ 'application/json' ]; if ( $this->method_exists_in_current_class( $method . '_consumes' ) ) { $config['consumes'] = $this->{$method . '_consumes'}(); } $config['produces'] = [ 'application/json' ]; if ( $this->method_exists_in_current_class( $method . '_produces' ) ) { $config['produces'] = $this->{$method . '_produces'}(); } if ( $this->method_exists_in_current_class( $method . '_summary' ) ) { $config['summary'] = $this->{$method . '_summary'}(); } if ( $this->method_exists_in_current_class( $method . '_description' ) ) { $config['description'] = $this->{$method . '_description'}(); } return $config; } /** * maybe_add_response_to_swagger * * If the function method /(rest-method)_response_callback/ exists, adds the filter * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/ * with the aforementioned function method. * This filter is used with the WP API Swagger UI plugin to create documentation for the API. * The value being passed is an array: [ '200' => ['description' => 'OK'], '404' => ['description' => 'Not Found'], '400' => ['description' => 'Bad Request'] ] * @param string $method REST method name */ public function maybe_add_response_to_swagger( string $method ): void { $method_name = strtolower( $method ); $method_response_callback = $method_name . '_response_callback'; if ( $this->method_exists_in_current_class( $method_response_callback ) ) { $response_filter = $method_name . '_' . str_replace( '/', '_', $this->namespace . '/' . $this->get_endpoint() ); add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] ); } } /** * build_endpoint_method_config * * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters, * and response to Swagger member functions. * @param string $method The REST method for the endpoint * * @return array The endpoint configuration for the method specified by the parameter */ private function build_endpoint_method_config( string $method ): array { $config = [ 'methods' => $method, 'callback' => $this->get_callback_method( $method ), 'permission_callback' => $this->get_permission_callback_method( $method ), ]; $this->maybe_add_response_to_swagger( $method ); return $this->maybe_add_args_to_config( $method, $config ); } /** * method_exists_in_current_class * * Uses reflection to check if this class has the /method/ method. * @param string $method The name of the method being checked. * * @return bool TRUE if the class has the /method/ method, FALSE otherwise. */ private function method_exists_in_current_class( string $method ): bool { $class_name = get_class( $this ); try { $reflection = new ReflectionClass( $class_name ); } catch( \ReflectionException $e ) { return false; } if ( ! $reflection->hasMethod( $method ) ) { return false; } $method_ref = $reflection->getMethod( $method ); return ( $method_ref && $class_name === $method_ref->class ); } /** * permission_callback * Permissions callback fallback for the endpoint * Gets the current user ID and sets the /current_user_id/ property. * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0) * * @param WP_REST_Request $request unused * * @return bool TRUE, if permission granted, FALSE otherwise */ public function permission_callback( WP_REST_Request $request ): bool { // try to get current user $this->current_user_id = get_current_user_id(); if ( $this->auth ) { return $this->current_user_id > 0; } return true; } /** * callback * Fallback callback function, returns a response consisting of the string /ok/. * * @param WP_REST_Request $request unused * * @return WP_REST_Response Default Response of the string 'ok'. */ public function callback( WP_REST_Request $request ): WP_REST_Response { return rest_ensure_response( [ 'OK' ] ); } /** * respond_wrong_method * * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults. * @param null $message The error message for the wrong method. * Optional. * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/ * @param int $code The HTTP status code. * Optional. * Defaults to 404 (Not found). * * @return WP_Error The WordPress error object with the error message and status code supplied */ public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error { if ( null === $message ) { $message = __( 'No route was found matching the URL and request method', 'cloud-backup' ); } return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] ); } /** * respond_with_code * Create a new /WP_REST_Response/ object with the specified data and HTTP response code. * * @param array|null $data The data to return in this response * @param int $code The HTTP response code. * Optional. * Defaults to 200 (OK). * * @return WP_REST_Response The WordPress response object loaded with the data and the response code. */ public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response { return new WP_REST_Response( $data, $code ); } /** * get_user_from_request * * Returns the current user object. * Depends on the property /current_user_id/ to be set. * @return WP_User|false The user object or false if not found or on error. */ public function get_user_from_request() { return get_user_by( 'id', $this->current_user_id ); } /** * get_arguments * Rest Endpoint extra arguments * @return array Additional arguments for the route configuration */ public function get_arguments(): array { return []; } /** * get_endpoint * Rest route Endpoint * @return string Endpoint uri component (comes after the route namespace) */ abstract public function get_endpoint(): string; /** * get_name * @return string The name of the route */ abstract public function get_name(): string; /** * get_self_url * * @param string $endpoint * * @return string */ public function get_self_url( string $endpoint = '' ): string { return rest_url( $this->namespace . '/' . $endpoint ); } public function respond_success_json( $data = [] ): WP_REST_Response { return new WP_REST_Response([ 'success' => true, 'data' => $data, ]); } /** * @param array{message: string, code: string} $data * * @return WP_Error */ public function respond_error_json( array $data ): WP_Error { if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) { _doing_it_wrong( __FUNCTION__, esc_html__( 'Both `message` and `code` keys must be provided', 'image-optimization' ), '1.0.0' ); // @codeCoverageIgnore } return new WP_Error( $data['code'] ?? 'internal_server_error', $data['message'] ?? esc_html__( 'Internal server error', 'image-optimization' ), ); } public function verify_nonce( $nonce = '', $name = '' ) { if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid nonce', 'image-optimization' ), 'code' => 'bad_request', ]); } } public function verify_capability( $capability = 'manage_options' ) { if ( ! current_user_can( $capability ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ), 'code' => 'bad_request', ]); } } public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) { $this->verify_nonce( $nonce, $name ); if ( ! current_user_can( $capability ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ), 'code' => 'bad_request', ]); } } public function trigger_internal( $method, $endpoint, $args = [] ): array { $request = new WP_REST_Request( $method, $this->get_self_url( $endpoint ) ); if ( ! empty( $args['body'] ) ) { $request->set_body_params( $args['body'] ); } if ( ! empty( $args['headers'] ) ) { $request->set_headers( $args['headers'] ); } if ( ! empty( $args['params'] ) ) { $request->set_query_params( $args['params'] ); } $response = rest_do_request( $request ); $server = rest_get_server(); return $server->response_to_data( $response, false ); } } async-operation/async-operation.php 0000644 00000007172 14717654505 0013530 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation; use ActionScheduler; use ImageOptimization\Classes\Async_Operation\{ Exceptions\Async_Operation_Exception, Interfaces\Operation_Query_Interface }; use ImageOptimization\Classes\Logger; use Throwable; use TypeError; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Async_Operation { public const OPERATION_STATUS_NOT_STARTED = 'not-started'; public const OPERATION_STATUS_COMPLETE = 'complete'; public const OPERATION_STATUS_PENDING = 'pending'; public const OPERATION_STATUS_RUNNING = 'in-progress'; public const OPERATION_STATUS_FAILED = 'failed'; public const OPERATION_STATUS_CANCELED = 'canceled'; /** * @throws Async_Operation_Exception */ public static function create( string $hook, array $args, string $queue, int $priority = 10, $unique = false ): int { self::check_library_is_registered(); if ( ! in_array( $hook, Async_Operation_Hook::get_values(), true ) ) { Logger::log( Logger::LEVEL_ERROR, "Hook $hook is not a part of Async_Operation_Hook values" ); throw new TypeError( "Hook $hook is not a part of Async_Operation_Hook values" ); } if ( ! in_array( $queue, Async_Operation_Queue::get_values(), true ) ) { Logger::log( Logger::LEVEL_ERROR, "Queue $queue is not a part of Async_Operation_Queue values" ); throw new TypeError( "Queue $queue is not a part of Async_Operation_Queue values" ); } return as_enqueue_async_action( $hook, $args, $queue, $unique, $priority ); } /** * @param Operation_Query_Interface $query * * @return Async_Operation_Item[]|int[] * @throws Async_Operation_Exception */ public static function get( Operation_Query_Interface $query ): array { self::check_library_is_registered(); $actions = []; $store = ActionScheduler::store(); $logger = ActionScheduler::logger(); $action_ids = $store->query_actions( $query->get_query() ); if ( 'ids' === $query->get_return_type() ) { return $action_ids ?? []; } foreach ( $action_ids as $action_id ) { try { $action = $store->fetch_action( $action_id ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, "Unable to fetch an action `$action_id`. Reason: " . $t->getMessage() ); continue; } if ( is_a( $action, 'ActionScheduler_NullAction' ) ) { Logger::log( Logger::LEVEL_WARN, 'ActionScheduler_NullAction found' ); continue; } $item = new Async_Operation_Item(); $actions[] = $item->set_id( $action_id ) ->set_hook( $action->get_hook() ) ->set_status( $store->get_status( $action_id ) ) ->set_args( $action->get_args() ) ->set_queue( $action->get_group() ) ->set_date( $store->get_date( $action_id ) ) ->set_logs( $logger->get_logs( $action_id ) ); } return $actions; } /** * @throws Async_Operation_Exception */ public static function cancel( int $operation_id ): void { self::check_library_is_registered(); $store = ActionScheduler::store(); $store->cancel_action( $operation_id ); } /** * @throws Async_Operation_Exception */ public static function remove( array $operation_ids ): void { self::check_library_is_registered(); $store = ActionScheduler::store(); foreach ( $operation_ids as $operation_id ) { $store->delete_action( $operation_id ); } } /** * @throws Async_Operation_Exception */ private static function check_library_is_registered(): void { if ( ! ActionScheduler::is_initialized() ) { Logger::log( Logger::LEVEL_ERROR, 'ActionScheduler is not initialised though its method was called' ); throw new Async_Operation_Exception(); } } } async-operation/async-operation-hook.php 0000644 00000001725 14717654505 0014464 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation; use ImageOptimization\Classes\Basic_Enum; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } final class Async_Operation_Hook extends Basic_Enum { public const OPTIMIZE_SINGLE = 'image-optimization/optimize/single'; public const OPTIMIZE_ON_UPLOAD = 'image-optimization/optimize/upload'; public const OPTIMIZE_BULK = 'image-optimization/optimize/bulk'; public const REOPTIMIZE_SINGLE = 'image-optimization/reoptimize/single'; public const REOPTIMIZE_BULK = 'image-optimization/reoptimize/bulk'; public const REMOVE_MANY_BACKUPS = 'image-optimization/backup/remove-many'; public const RESTORE_SINGLE_IMAGE = 'image-optimization/restore/single'; public const RESTORE_MANY_IMAGES = 'image-optimization/restore/restore-many'; public const CALCULATE_OPTIMIZATION_STATS = 'image-optimization/optimization-stats/calculate'; public const DB_MIGRATION = 'image-optimization/database/migration'; } async-operation/exceptions/async-operation-exception.php 0000644 00000000425 14717654505 0017677 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Async_Operation_Exception extends Exception { protected $message = 'Async operation library is not loaded'; } async-operation/interfaces/operation-query-interface.php 0000644 00000000411 14717654505 0017626 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation\Interfaces; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } interface Operation_Query_Interface { public function get_query(): array; public function get_return_type(): string; } async-operation/async-operation-queue.php 0000644 00000000767 14717654505 0014655 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation; use ImageOptimization\Classes\Basic_Enum; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } final class Async_Operation_Queue extends Basic_Enum { public const OPTIMIZE = 'image-optimization/optimize'; public const BACKUP = 'image-optimization/backup'; public const RESTORE = 'image-optimization/restore'; public const STATS = 'image-optimization/stats'; public const MIGRATION = 'image-optimization/migration'; } async-operation/queries/image-optimization-operation-query.php 0000644 00000003505 14717654505 0021035 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation\Queries; use ImageOptimization\Classes\Async_Operation\{ Async_Operation_Hook, Async_Operation_Queue, Interfaces\Operation_Query_Interface, }; use TypeError; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Optimization_Operation_Query implements Operation_Query_Interface { private array $query; public function set_hook( string $hook ): self { if ( ! in_array( $hook, Async_Operation_Hook::get_values(), true ) ) { throw new TypeError( "Hook $hook is not a part of Async_Operation_Hook values" ); } $this->query['hook'] = $hook; return $this; } /** * @param string|array $status * @return $this */ public function set_status( $status ): self { $this->query['status'] = $status; return $this; } public function set_image_id( int $image_id ): self { if ( empty( $this->query['args'] ) ) { $this->query['args'] = []; } $this->query['args']['attachment_id'] = $image_id; return $this; } public function set_bulk_operation_id( string $operation_id ): self { if ( empty( $this->query['args'] ) ) { $this->query['args'] = []; } $this->query['args']['operation_id'] = $operation_id; return $this; } public function set_limit( int $limit ): self { $this->query['per_page'] = $limit; return $this; } public function get_return_type(): string { return $this->query['_return_type']; } public function return_ids(): self { $this->query['_return_type'] = 'ids'; return $this; } public function get_query(): array { return $this->query; } public function __construct() { $this->query = [ 'group' => Async_Operation_Queue::OPTIMIZE, 'per_page' => 10, 'orderby' => 'date', 'order' => 'DESC', 'partial_args_matching' => 'json', '_return_type' => 'object', ]; } } async-operation/queries/operation-query.php 0000644 00000003051 14717654505 0015225 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation\Queries; use ImageOptimization\Classes\Async_Operation\Async_Operation_Hook; use ImageOptimization\Classes\Async_Operation\Interfaces\Operation_Query_Interface; use TypeError; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Operation_Query implements Operation_Query_Interface { private array $query; public function set_hook( string $hook ): self { if ( ! in_array( $hook, Async_Operation_Hook::get_values(), true ) ) { throw new TypeError( "Hook $hook is not a part of Async_Operation_Hook values" ); } $this->query['hook'] = $hook; return $this; } /** * @param string|array $status * @return $this */ public function set_status( $status ): self { $this->query['status'] = $status; return $this; } public function set_image_id( int $image_id ): self { $this->query['args']['attachment_id'] = $image_id; return $this; } public function set_limit( int $limit ): self { $this->query['per_page'] = $limit; return $this; } public function get_return_type(): string { return $this->query['_return_type']; } public function return_ids(): self { $this->query['_return_type'] = 'ids'; return $this; } public function get_query(): array { $clone = $this->query; if ( empty( $clone['args'] ) ) { unset( $clone['args'] ); } return $clone; } public function __construct() { $this->query = [ 'args' => [], 'per_page' => 10, 'orderby' => 'date', 'order' => 'DESC', '_return_type' => 'object', ]; } } async-operation/async-operation-item.php 0000644 00000003015 14717654505 0014454 0 ustar 00 <?php namespace ImageOptimization\Classes\Async_Operation; use DateTime; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Async_Operation_Item { private int $id; private string $hook; private string $status; private array $args; private string $queue; private DateTime $date; private array $logs; public function get_id(): int { return $this->id; } public function set_id( int $id ): Async_Operation_Item { $this->id = $id; return $this; } public function get_hook(): string { return $this->hook; } public function set_hook( string $hook ): Async_Operation_Item { $this->hook = $hook; return $this; } public function get_status(): string { return $this->status; } public function set_status( string $status ): Async_Operation_Item { $this->status = $status; return $this; } public function get_args(): array { return $this->args; } public function set_args( array $args ): Async_Operation_Item { $this->args = $args; return $this; } public function get_date(): DateTime { return $this->date; } public function set_date( DateTime $date ): Async_Operation_Item { $this->date = $date; return $this; } public function get_queue(): string { return $this->queue; } public function set_queue( string $queue ): Async_Operation_Item { $this->queue = $queue; return $this; } public function get_logs(): array { return $this->logs; } public function set_logs( array $logs ): Async_Operation_Item { $this->logs = $logs; return $this; } } exceptions/file-operation-error.php 0000644 00000000357 14717654505 0013525 0 ustar 00 <?php namespace ImageOptimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class File_Operation_Error extends Exception { protected $message = 'File operation error'; } exceptions/client-exception.php 0000644 00000000353 14717654505 0012727 0 ustar 00 <?php namespace ImageOptimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Client_Exception extends Exception { protected $message = 'Unknown client error'; } exceptions/quota-exceeded-error.php 0000644 00000000351 14717654505 0013477 0 ustar 00 <?php namespace ImageOptimization\Classes\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Quota_Exceeded_Error extends Exception { protected $message = 'Quota exceeded'; } migration/handlers/fix-mime-type.php 0000644 00000003144 14717654505 0013560 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration\Handlers; use ImageOptimization\Classes\Image\{ Image, Image_Meta, Image_Query_Builder, WP_Image_Meta }; use ImageOptimization\Classes\Migration\Migration; use ImageOptimization\Modules\Stats\Classes\Optimization_Stats; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Fix_Mime_Type extends Migration { public static function get_name(): string { return 'fix_mime_type'; } public static function run(): bool { $query = ( new Image_Query_Builder() ) ->return_optimized_images() ->execute(); if ( ! $query->post_count ) { return true; } foreach ( $query->posts as $attachment_id ) { $stats = Optimization_Stats::get_image_stats( $attachment_id ); $fully_optimized = ( $stats['initial_image_size'] - $stats['current_image_size'] ) <= 0; if ( $fully_optimized ) { $io_meta = new Image_Meta( $attachment_id ); $wp_meta = new WP_Image_Meta( $attachment_id ); $size_data = $wp_meta->get_size_data( Image::SIZE_FULL ); if ( ! str_contains( $size_data['file'], '.webp' ) && 'image/webp' === $size_data['mime-type'] || ! str_contains( $size_data['file'], '.avif' ) && 'image/avif' === $size_data['mime-type'] || ! str_contains( $size_data['file'], '.avif' ) && 'application/octet-stream' === $size_data['mime-type'] ) { $original_mime_type = $io_meta->get_original_mime_type( Image::SIZE_FULL ); foreach ( $wp_meta->get_size_keys() as $size ) { $wp_meta ->set_mime_type( $size, $original_mime_type ) ->save(); } } } } return true; } } migration/handlers/fix-optimized-size-keys.php 0000644 00000001467 14717654505 0015605 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration\Handlers; use ImageOptimization\Classes\Image\{ Image_Meta, Image_Query_Builder, }; use ImageOptimization\Classes\Migration\Migration; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Fix_Optimized_Size_Keys extends Migration { public static function get_name(): string { return 'fix_optimized_size_keys'; } public static function run(): bool { $query = ( new Image_Query_Builder() ) ->return_optimized_images() ->execute(); if ( ! $query->post_count ) { return true; } foreach ( $query->posts as $attachment_id ) { $meta = new Image_Meta( $attachment_id ); $size_keys = array_unique( $meta->get_optimized_sizes() ); $meta ->set_optimized_size( $size_keys ) ->save(); } return true; } } migration/handlers/fix-avif-with-zero-dimensions.php 0000644 00000002467 14717654505 0016702 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration\Handlers; use ImageOptimization\Classes\Image\{ Image, Image_Dimensions, Image_Query_Builder, WP_Image_Meta, }; use ImageOptimization\Classes\Migration\Migration; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Fix_Avif_With_Zero_Dimensions extends Migration { public static function get_name(): string { return 'fix_avif_with_zero_dimensions'; } public static function run(): bool { $query = ( new Image_Query_Builder() ) ->set_mime_types( [ 'image/avif', 'application/octet-stream' ] ) ->return_images_with_non_empty_meta() ->execute(); if ( ! $query->post_count ) { return true; } foreach ( $query->posts as $attachment_id ) { try { $wp_meta = new WP_Image_Meta( $attachment_id ); $image = new Image( $attachment_id ); foreach ( $wp_meta->get_size_keys() as $size_key ) { if ( 0 === $wp_meta->get_width( $size_key ) || 0 === $wp_meta->get_height( $size_key ) ) { $dimensions = Image_Dimensions::get_by_path( $image->get_file_path( $size_key ) ); $wp_meta ->set_width( $size_key, $dimensions->width ) ->set_height( $size_key, $dimensions->height ) ->save(); } } } catch ( Throwable $t ) { continue; } } return true; } } migration/migration.php 0000644 00000000377 14717654505 0011264 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Migration { abstract public static function run(): bool; abstract public static function get_name(): string; } migration/migration-meta.php 0000644 00000003456 14717654505 0012211 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration; use DateTime; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Migration_Meta { public const IMAGE_OPTIMIZER_MIGRATION_KEY = 'image_optimizer_migrations'; private const INITIAL_META_VALUE = [ 'last_run' => null, 'last_wp_version_run' => null, 'migrations_passed' => [], ]; private array $migration_meta; public function get_last_run(): ?string { return $this->migration_meta['last_run']; } public function set_last_run( DateTime $date ): Migration_Meta { $this->migration_meta['last_run'] = $date; return $this; } public function get_last_wp_version(): string { return $this->migration_meta['last_wp_version_run']; } public function set_last_wp_version( ?string $version ): Migration_Meta { if ( ! $version ) { $this->migration_meta['last_wp_version_run'] = get_bloginfo( 'version' ); return $this; } $this->migration_meta['last_wp_version_run'] = $version; return $this; } public function get_migrations_passed(): array { return $this->migration_meta['migrations_passed']; } public function add_migration_passed( string $migration ): Migration_Meta { $this->migration_meta['migrations_passed'][] = $migration; return $this; } public function delete(): bool { return delete_option( self::IMAGE_OPTIMIZER_MIGRATION_KEY ); } public function save(): Migration_Meta { update_option( self::IMAGE_OPTIMIZER_MIGRATION_KEY, $this->migration_meta, false ); $this->query_meta(); return $this; } private function query_meta(): void { $meta = get_option( self::IMAGE_OPTIMIZER_MIGRATION_KEY, [] ); $this->migration_meta = $meta ? array_replace_recursive( self::INITIAL_META_VALUE, $meta ) : self::INITIAL_META_VALUE; } public function __construct() { $this->query_meta(); } } migration/migration-manager.php 0000644 00000003104 14717654505 0012663 0 ustar 00 <?php namespace ImageOptimization\Classes\Migration; use ImageOptimization\Classes\Async_Operation\{ Async_Operation, Async_Operation_Hook, Async_Operation_Queue, Exceptions\Async_Operation_Exception, }; use ImageOptimization\Classes\Logger; use ImageOptimization\Classes\Migration\Handlers\{ Fix_Avif_With_Zero_Dimensions, Fix_Mime_Type, Fix_Optimized_Size_Keys, }; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Migration_Manager { public static function get_migrations(): array { return [ Fix_Optimized_Size_Keys::class, Fix_Mime_Type::class, Fix_Avif_With_Zero_Dimensions::class, ]; } /** * @param $migration_name * * @return \class-string|null */ public static function get_migration( $migration_name ): ?string { foreach ( self::get_migrations() as $migration ) { if ( $migration::get_name() === $migration_name ) { return $migration; } } return null; } public static function init() { $migrations_passed = ( new Migration_Meta() )->get_migrations_passed(); foreach ( self::get_migrations() as $migration ) { if ( in_array( $migration::get_name(), $migrations_passed, true ) ) { continue; } try { Async_Operation::create( Async_Operation_Hook::DB_MIGRATION, [ 'name' => $migration::get_name() ], Async_Operation_Queue::MIGRATION, 0, true ); } catch ( Async_Operation_Exception $aoe ) { $name = $migration::get_name(); Logger::log( Logger::LEVEL_ERROR, "Error while running migration `$name`: " . $aoe->getMessage() ); } } } } module-base.php 0000644 00000016146 14717654505 0007500 0 ustar 00 <?php namespace ImageOptimization\Classes; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Module Base. * * An abstract class providing the properties and methods needed to * manage and handle modules in inheriting classes. * * @abstract */ abstract class Module_Base { /** * Module class reflection. * * Holds the information about a class. * @access private * * @var \ReflectionClass */ private $reflection = null; /** * Module routes. * * Holds the module registered routes. * @access public * * @var array */ public $routes = []; /** * Module components. * * Holds the module components. * @access private * * @var array */ private $components = []; /** * Module instance. * * Holds the module instance. * @access protected * * @var Module_Base[] */ protected static $_instances = []; /** * Get module name. * * Retrieve the module name. * @access public * @abstract * * @return string Module name. */ abstract public function get_name(); /** * Instance. * * Ensures only one instance of the module class is loaded or can be loaded. * @access public * @static * * @return Module_Base An instance of the class. */ public static function instance() { $class_name = static::class_name(); if ( empty( static::$_instances[ $class_name ] ) ) { static::$_instances[ $class_name ] = new static(); // @codeCoverageIgnore } return static::$_instances[ $class_name ]; } /** * is_active * @access public * @static * @return bool */ public static function is_active() { return true; } /** * Class name. * * Retrieve the name of the class. * @access public * @static */ public static function class_name() { return get_called_class(); } /** * Clone. * * Disable class cloning and throw an error on object clone. * * The whole idea of the singleton design pattern is that there is a single * object. Therefore, we don't want the object to be cloned. * * @access public */ public function __clone() { // Cloning instances of the class is forbidden _doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'image-optimization' ), '1.0.0' ); // @codeCoverageIgnore } /** * Wakeup. * * Disable unserializing of the class. * @access public */ public function __wakeup() { // Unserializing instances of the class is forbidden _doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'image-optimization' ), '1.0.0' ); // @codeCoverageIgnore } /** * @access public */ public function get_reflection() { if ( null === $this->reflection ) { try { $this->reflection = new \ReflectionClass( $this ); } catch ( \ReflectionException $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( $e->getMessage() ); } } } return $this->reflection; } /** * Add module component. * * Add new component to the current module. * @access public * * @param string $id Component ID. * @param mixed $instance An instance of the component. */ public function add_component( string $id, $instance ) { $this->components[ $id ] = $instance; } /** * Add module route. * * Add new route to the current module. * @access public * * @param string $id Route ID. * @param mixed $instance An instance of the route. */ public function add_route( string $id, $instance ) { $this->routes[ $id ] = $instance; } /** * @access public * @return string[] */ public function get_components(): array { return $this->components; } /** * Get module component. * * Retrieve the module component. * @access public * * @param string $id Component ID. * * @return mixed An instance of the component, or `false` if the component * doesn't exist. * @codeCoverageIgnore */ public function get_component( string $id ) { if ( isset( $this->components[ $id ] ) ) { return $this->components[ $id ]; } return false; } /** * Retrieve the namespace of the class * * @access public * @static */ public static function namespace_name() { $class_name = static::class_name(); return substr( $class_name, 0, strrpos( $class_name, '\\' ) ); } /** * Get assets url. * * @param string $file_name * @param string $file_extension * @param string $relative_url Optional. Default is null. * * @return string */ final protected function get_assets_url( $file_name, $file_extension, $relative_url = null ): string { static $is_test_mode = null; if ( null === $is_test_mode ) { $is_test_mode = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG; } if ( ! $relative_url ) { $relative_url = $this->get_assets_relative_url(); } $url = $this->get_assets_base_url() . $relative_url . $file_name; return $url . '.' . $file_extension; } /** * Get js assets url * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * * @return string */ final protected function get_js_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default' ): string { return $this->get_assets_url( $file_name, 'js', $relative_url, $add_min_suffix ); } /** * Get css assets url * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * @param bool $add_direction_suffix Optional. Default is `false` * * @return string */ final protected function get_css_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default', $add_direction_suffix = false ): string { static $direction_suffix = null; if ( ! $direction_suffix ) { $direction_suffix = is_rtl() ? '-rtl' : ''; } if ( $add_direction_suffix ) { $file_name .= $direction_suffix; } return $this->get_assets_url( $file_name, 'css', $relative_url, $add_min_suffix ); } /** * Get assets base url * * @return string */ protected function get_assets_base_url(): string { return IMAGE_OPTIMIZATION_URL; } /** * Get assets relative url * * @return string */ protected function get_assets_relative_url(): string { return 'assets/build/'; } public static function routes_list() : array { return []; } public static function component_list() : array { return []; } /** * Adds an array of components. * Assumes namespace structure contains `\Components\` */ public function register_components() { $namespace = static::namespace_name(); $components_ids = static::component_list(); foreach ( $components_ids as $component_id ) { $class_name = $namespace . '\\Components\\' . $component_id; $this->add_component( $component_id, new $class_name() ); } } /** * Adds an array of routes. * Assumes namespace structure contains `\Rest\` */ public function register_routes() { $namespace = static::namespace_name(); $routes_ids = static::routes_list(); foreach ( $routes_ids as $route_id ) { $class_name = $namespace . '\\Rest\\' . $route_id; $this->add_route( $route_id, new $class_name() ); } } } image/image-status.php 0000644 00000001240 14717654505 0010755 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\Basic_Enum; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } final class Image_Status extends Basic_Enum { public const NOT_OPTIMIZED = 'not-optimized'; public const OPTIMIZED = 'optimized'; public const OPTIMIZATION_IN_PROGRESS = 'optimization-in-progress'; public const OPTIMIZATION_FAILED = 'optimization-failed'; public const RESTORING_IN_PROGRESS = 'restoring-in-progress'; public const RESTORING_FAILED = 'restoring-failed'; public const REOPTIMIZING_IN_PROGRESS = 'reoptimizing-in-progress'; public const REOPTIMIZING_FAILED = 'reoptimizing-failed'; } image/image.php 0000644 00000010452 14717654505 0007441 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\Image\Exceptions\Invalid_Image_Exception; use WP_Post; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image { public const SIZE_FULL = 'full'; private const SUPPORTED_MIME_TYPES = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/avif' ]; // Used for error messages private const SUPPORTED_FORMATS = [ 'jpeg', 'png', 'webp', 'gif', 'avif' ]; private const MIME_TYPES_CANNOT_BE_OPTIMIZED = [ 'image/avif' ]; private const FORMATS_CANNOT_BE_OPTIMIZED = [ 'avif' ]; protected int $image_id; protected $attachment_object; /** * Returns attachment post id. * * @return int */ public function get_id(): int { return $this->image_id; } /** * Returns file URL for a specific image size. * * @param string $image_size Image size (e. g. 'full', 'thumbnail', etc) * @return string|null */ public function get_url( string $image_size ): ?string { $image_data = wp_get_attachment_image_src( $this->image_id, $image_size ); if ( empty( $image_data ) ) { return null; } return $image_data[0]; } /** * Returns absolute file path for a specific image size. * * @param string $image_size Image size (e. g. 'full', 'thumbnail', etc) * @return string|null */ public function get_file_path( string $image_size ): ?string { if ( 'full' === $image_size ) { $path = get_attached_file( $this->image_id ); return $path ?? null; } $path_data = image_get_intermediate_size( $this->image_id, $image_size ); if ( empty( $path_data ) ) { return null; } return sprintf( '%s/%s', wp_get_upload_dir()['basedir'], $path_data['path'] ); } /** * Returns true if an image marked as optimized. * * @return bool */ public function is_optimized(): bool { $meta = new Image_Meta( $this->image_id ); return $meta->get_status() === Image_Status::OPTIMIZED; } /** * Returns true if an image can be restored from the backups. * * @return bool */ public function can_be_restored(): bool { $meta = new Image_Meta( $this->image_id ); return (bool) count( $meta->get_image_backup_paths() ); } /** * Returns image's mime type. * * @return string */ public function get_mime_type(): string { return $this->attachment_object->post_mime_type; } /** * Returns an original attachment WP_Post object. * * @return WP_Post */ public function get_attachment_object(): WP_Post { return $this->attachment_object; } /** * Updates WP_Post fields of the attachment. * * @return bool True if the post was updated successfully, false otherwise. */ public function update_attachment( array $update_query ): bool { global $wpdb; // Must use $wpdb here as `wp_update_post()` doesn't allow to rewrite guid. $result = $wpdb->update( $wpdb->posts, $update_query, [ 'ID' => $this->image_id ] ); if ( 0 !== $result ) { $this->attachment_object = get_post( $this->image_id ); return true; } return false; } /** * Returns the list of mime types supported by the plugin. * * @return string[] */ public static function get_supported_mime_types(): array { return self::SUPPORTED_MIME_TYPES; } /** * Returns the list of formats types supported by the plugin. * * @return string[] */ public static function get_supported_formats(): array { return self::SUPPORTED_FORMATS; } /** * Returns the list of mime types that are supported by the plugin, but cannot be optimized * * @return string[] */ public static function get_mime_types_cannot_be_optimized(): array { return self::MIME_TYPES_CANNOT_BE_OPTIMIZED; } /** * Returns the list of formats that are supported by the plugin, but cannot be optimized * * @return string[] */ public static function get_formats_cannot_be_optimized(): array { return self::FORMATS_CANNOT_BE_OPTIMIZED; } /** * @throws Invalid_Image_Exception */ public function __construct( int $image_id ) { $this->image_id = $image_id; $this->attachment_object = get_post( $image_id ); if ( ! $this->attachment_object ) { throw new Invalid_Image_Exception( "There is no entity with id '$image_id'" ); } if ( ! wp_attachment_is_image( $this->attachment_object ) ) { throw new Invalid_Image_Exception( "Post '$image_id' is not an image" ); } } } image/image-db-update.php 0000644 00000001217 14717654505 0011303 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use WP_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_DB_Update { public static function update_posts_table_urls( string $old_url, string $new_url ) { $query = new WP_Query( [ 'post_type' => 'any', 'post_status' => 'any', 's' => $old_url, 'search_columns' => [ 'post_content' ], ] ); if ( ! $query->post_count ) { return; } foreach ( $query->posts as $post ) { $new_content = str_replace( $old_url, $new_url, $post->post_content ); wp_update_post([ 'ID' => $post->ID, 'post_content' => $new_content, ]); } } } image/image-backup.php 0000644 00000007766 14717654505 0010722 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\File_System\Exceptions\File_System_Operation_Error; use ImageOptimization\Classes\File_System\File_System; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\Exceptions\Image_Backup_Creation_Error; use ImageOptimization\Classes\Logger; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * We're saving backup paths in meta even though the backup path could be dynamically generated. * The next reasons affected this decision: * * 1. Possible path conflicts with other plugins and/or custom user logic. We can't guarantee that the image * we find using the dynamically created path will be the same image we saved. * 2. If a backup doesn't exist, but stored in our meta -- we can be almost sure it's not because of us. * Probably, some other plugin removed it or even user manually did it, but it's not our fault. Makes this logic * easier to debug. * 3. We can change backup file name using `wp_unique_filename()` if needed and easily point a backup path to an * image. */ class Image_Backup { /** * Creates a backup of a file by copying it to a new file with the backup extension. * Also, attaches a newly created backup to image's meta. * * @param int $image_id Attachment id. * @param string $image_size Image size (e.g. 'full', 'thumbnail', etc.). * @param string $image_path Path to an image we plan to back up. * * @return string Backup path if successfully created, false otherwise. * * @throws Image_Backup_Creation_Error */ public static function create( int $image_id, string $image_size, string $image_path ): string { $extension = File_Utils::get_extension( $image_path ); $backup_path = File_Utils::replace_extension( $image_path, "backup.$extension" ); try { File_System::copy( $image_path, $backup_path, true ); } catch ( File_System_Operation_Error $e ) { Logger::log( Logger::LEVEL_ERROR, "Error while creating a backup for image {$image_id} and size {$image_size}" ); throw new Image_Backup_Creation_Error( "Error while creating a backup for image {$image_id} and size {$image_size}" ); } $meta = new Image_Meta( $image_id ); $meta->set_image_backup_path( $image_size, $backup_path ); $meta->save(); return $backup_path; } /** * Looks for registered backups and remove all files found. * Also, wipes removed files from image meta. * * @param int[] $image_ids Array of attachment ids. * @return void */ public static function remove_many( array $image_ids ): void { foreach ( $image_ids as $image_id ) { self::remove( $image_id ); } } /** * Removes one or all backups for a specific image. * Also, wipes removed files from image meta. * * @param int $image_id Attachment id. * @param string|null $image_size Image size (e.g. 'full', 'thumbnail', etc.). All backups will be removed if no size provided. * * @return bool Returns true if backups were removed successfully, false otherwise. */ public static function remove( int $image_id, ?string $image_size = null ): bool { $meta = new Image_Meta( $image_id ); $backups = $meta->get_image_backup_paths(); if ( empty( $backups ) ) { return false; } if ( $image_size ) { if ( ! key_exists( $image_size, $backups ) ) { return false; } try { File_System::delete( $backups[ $image_size ], false, 'f' ); } catch ( File_System_Operation_Error $e ) { Logger::log( Logger::LEVEL_ERROR, "Error while removing a backup for image {$image_id} and size {$image_size}" ); } $meta->remove_image_backup_path( $image_size ); $meta->save(); return true; } foreach ( $backups as $image_size => $backup_path ) { try { File_System::delete( $backup_path, false, 'f' ); } catch ( File_System_Operation_Error $e ) { Logger::log( Logger::LEVEL_ERROR, "Error while removing backups {$image_id}" ); } $meta->remove_image_backup_path( $image_size ); } $meta->save(); return true; } } image/image-query-builder.php 0000644 00000004547 14717654505 0012240 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use WP_Query; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Query_Builder { private array $query; public function return_images_only_with_backups(): self { $this->query['meta_query'][] = [ 'compare' => 'NOT LIKE', 'value' => '"backups";a:0', // Serialized empty array of backups 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; return $this; } public function return_not_optimized_images(): self { $this->query['meta_query'][] = [ 'relation' => 'OR', [ 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, 'compare' => 'NOT EXISTS', ], [ 'compare' => 'LIKE', 'value' => '-failed";', 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ], [ 'compare' => 'LIKE', 'value' => ':"not-optimized";', 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ], ]; return $this; } public function return_optimized_images(): self { $this->query['meta_query'][] = [ 'compare' => 'LIKE', 'value' => '"status";s:9:"optimized"', 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, ]; return $this; } public function return_images_with_non_empty_meta(): self { $this->query['meta_query'][] = [ 'key' => Image_Meta::IMAGE_OPTIMIZER_METADATA_KEY, 'compare' => 'EXISTS', ]; return $this; } public function set_paging_size( int $paging_size ): self { $this->query['posts_per_page'] = $paging_size; return $this; } public function set_current_page( int $current_page ): self { $this->query['paged'] = $current_page; return $this; } public function set_image_ids( array $image_ids ): self { $this->query['post__in'] = $image_ids; return $this; } public function set_mime_types( array $mime_types ): self { $this->query['post_mime_type'] = $mime_types; return $this; } public function execute(): WP_Query { return new WP_Query( $this->query ); } public function __construct() { $basic_query = [ 'post_type' => 'attachment', 'post_mime_type' => Image::get_supported_mime_types(), 'post_status' => 'any', 'fields' => 'ids', 'posts_per_page' => -1, 'meta_query' => [ 'relation' => 'AND', [ 'key' => '_wp_attachment_metadata', // Images without this field considered invalid 'compare' => 'EXISTS', ], ], ]; $this->query = $basic_query; } } image/image-optimization-error-type.php 0000644 00000000566 14717654505 0014300 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\Basic_Enum; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } final class Image_Optimization_Error_Type extends Basic_Enum { public const QUOTA_EXCEEDED = 'quota-exceeded'; public const FILE_ALREADY_EXISTS = 'file-already-exists'; public const GENERIC = 'generic'; } image/image-meta.php 0000644 00000010566 14717654505 0010373 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Meta { public const IMAGE_OPTIMIZER_METADATA_KEY = 'image_optimizer_metadata'; private const INITIAL_META_VALUE = [ 'status' => Image_Status::NOT_OPTIMIZED, 'error_type' => null, 'compression_level' => null, 'sizes_optimized' => [], 'backups' => [], 'original_data' => [ 'sizes' => [], ], ]; private int $image_id; private array $image_meta; public function get_status(): string { return $this->image_meta['status']; } public function set_status( string $status ): Image_Meta { $this->image_meta['status'] = $status; return $this; } public function get_error_type(): ?string { return $this->image_meta['error_type']; } public function set_error_type( ?string $type ): Image_Meta { $this->image_meta['error_type'] = $type; return $this; } public function get_compression_level(): ?string { return $this->image_meta['compression_level']; } public function set_compression_level( string $compression_level ): Image_Meta { $this->image_meta['compression_level'] = $compression_level; return $this; } public function get_optimized_sizes(): array { return $this->image_meta['sizes_optimized']; } public function add_optimized_size( string $optimized_size ): Image_Meta { if ( ! in_array( $optimized_size, $this->image_meta['sizes_optimized'], true ) ) { $this->image_meta['sizes_optimized'][] = $optimized_size; } return $this; } public function clear_optimized_sizes(): Image_Meta { $this->image_meta['sizes_optimized'] = []; return $this; } public function set_optimized_size( array $optimized_size ): Image_Meta { $this->image_meta['sizes_optimized'] = $optimized_size; return $this; } public function clear_backups(): Image_Meta { $this->image_meta['backups'] = []; return $this; } public function get_image_backup_paths(): array { return $this->image_meta['backups']; } public function get_image_backup_path( string $image_size ): ?string { return $this->image_meta['backups'][ $image_size ] ?? null; } public function set_image_backup_path( string $image_size, string $backup_path ): Image_Meta { $this->image_meta['backups'][ $image_size ] = $backup_path; return $this; } public function remove_image_backup_path( string $image_size ): Image_Meta { unset( $this->image_meta['backups'][ $image_size ] ); return $this; } public function get_original_file_size( string $image_size ): ?int { if ( ! isset( $this->image_meta['original_data']['sizes'][ $image_size ]['filesize'] ) ) { return null; } return $this->image_meta['original_data']['sizes'][ $image_size ]['filesize']; } public function get_original_mime_type( string $image_size ): ?string { if ( ! isset( $this->image_meta['original_data']['sizes'][ $image_size ] ) ) { return null; } return $this->image_meta['original_data']['sizes'][ $image_size ]['mime-type']; } public function get_original_width( string $image_size ): ?string { if ( ! isset( $this->image_meta['original_data']['sizes'][ $image_size ] ) ) { return null; } return $this->image_meta['original_data']['sizes'][ $image_size ]['width']; } public function get_original_height( string $image_size ): ?string { if ( ! isset( $this->image_meta['original_data']['sizes'][ $image_size ] ) ) { return null; } return $this->image_meta['original_data']['sizes'][ $image_size ]['height']; } public function add_original_data( string $size, array $data ): Image_Meta { $this->image_meta['original_data']['sizes'][ $size ] = $data; return $this; } public function clear_original_data(): Image_Meta { $this->image_meta['original_data'] = [ 'sizes' => [] ]; return $this; } public function delete(): bool { return delete_post_meta( $this->image_id, self::IMAGE_OPTIMIZER_METADATA_KEY ); } public function save(): Image_Meta { update_post_meta( $this->image_id, self::IMAGE_OPTIMIZER_METADATA_KEY, $this->image_meta ); $this->query_meta(); return $this; } private function query_meta(): void { $meta = get_post_meta( $this->image_id, self::IMAGE_OPTIMIZER_METADATA_KEY, true ); $this->image_meta = $meta ? array_replace_recursive( self::INITIAL_META_VALUE, $meta ) : self::INITIAL_META_VALUE; } public function __construct( int $image_id ) { $this->image_id = $image_id; $this->query_meta(); } } image/image-conversion-option.php 0000644 00000000500 14717654505 0013123 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\Basic_Enum; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } final class Image_Conversion_Option extends Basic_Enum { public const ORIGINAL = 'original'; public const WEBP = 'webp'; public const AVIF = 'avif'; } image/exceptions/invalid-image-exception.php 0000644 00000000361 14717654505 0015240 0 ustar 00 <?php namespace ImageOptimization\Classes\Image\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Invalid_Image_Exception extends Exception { protected $message = 'Invalid image'; } image/exceptions/image-backup-creation-error.php 0000644 00000000375 14717654505 0016021 0 ustar 00 <?php namespace ImageOptimization\Classes\Image\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Backup_Creation_Error extends Exception { protected $message = 'Backup creation error'; } image/exceptions/image-restoring-exception.php 0000644 00000000376 14717654505 0015634 0 ustar 00 <?php namespace ImageOptimization\Classes\Image\Exceptions; use Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Restoring_Exception extends Exception { protected $message = 'Image cannot be restored'; } image/wp-image-meta.php 0000644 00000016433 14717654505 0011016 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\Exceptions\Invalid_Image_Exception; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class WP_Image_Meta * * This class is used to manage the metadata of an image in WordPress. */ class WP_Image_Meta { private int $image_id; private array $image_meta; private Image $image; /** * Get the size data of the image. * * @param string $image_size The size of the image. * @return array|null The size data of the image or null if the size does not exist. */ public function get_size_data( string $image_size ): ?array { if ( Image::SIZE_FULL === $image_size ) { $output = []; $output['file'] = $this->image_meta['file']; $output['filesize'] = $this->image_meta['filesize']; $output['width'] = $this->image_meta['width']; $output['height'] = $this->image_meta['height']; $output['mime-type'] = $this->image->get_attachment_object()->post_mime_type; return $output; } if ( ! isset( $this->image_meta['sizes'][ $image_size ] ) ) { return null; } return $this->image_meta['sizes'][ $image_size ]; } /** * Get the keys of the image sizes. * * @return array The keys of the image sizes. */ public function get_size_keys(): array { return [ Image::SIZE_FULL, ...array_keys( $this->image_meta['sizes'] ) ]; } /** * Returns keys of sizes that have the same dimensions as the one provided. * * @param string $image_size The size of the image. * * @return array */ public function get_size_duplicates( string $image_size ): array { $duplicates = []; $width = $this->get_width( $image_size ); $height = $this->get_height( $image_size ); foreach ( $this->image_meta['sizes'] as $size_key => $size_data ) { if ( $size_data['width'] === $width && $size_data['height'] === $height && $image_size !== $size_key ) { $duplicates[] = $size_key; } } return array_unique( $duplicates ); } /** * Get the file size of the image. * * @param string $image_size The size of the image. * @return int|null The file size of the image or null if the size does not exist. */ public function get_file_size( string $image_size ): ?int { if ( Image::SIZE_FULL === $image_size ) { return $this->image_meta['filesize'] ?? null; } if ( ! isset( $this->image_meta['sizes'][ $image_size ]['filesize'] ) ) { return null; } return $this->image_meta['sizes'][ $image_size ]['filesize']; } /** * Set the file size of the image. * * @param string $image_size The size of the image. * @param int $file_size The file size to set. * @return WP_Image_Meta The current instance. */ public function set_file_size( string $image_size, int $file_size ): WP_Image_Meta { if ( Image::SIZE_FULL === $image_size ) { $this->image_meta['filesize'] = $file_size; return $this; } $this->maybe_add_new_size( $image_size ); $this->image_meta['sizes'][ $image_size ]['filesize'] = $file_size; return $this; } /** * Set the file path of the image. * * @param string $image_size The size of the image. * @param string $full_path The full path to set. * @return WP_Image_Meta The current instance. */ public function set_file_path( string $image_size, string $full_path ): WP_Image_Meta { if ( Image::SIZE_FULL === $image_size ) { $this->image_meta['file'] = File_Utils::get_relative_upload_path( $full_path ); return $this; } $this->maybe_add_new_size( $image_size ); $this->image_meta['sizes'][ $image_size ]['file'] = File_Utils::get_basename( $full_path ); return $this; } /** * Get the width of the image. * * @param string $image_size The size of the image. * * @return int|null */ public function get_width( string $image_size ): ?int { if ( Image::SIZE_FULL === $image_size ) { return $this->image_meta['width']; } if ( ! isset( $this->image_meta['sizes'][ $image_size ]['width'] ) ) { return null; } return $this->image_meta['sizes'][ $image_size ]['width']; } /** * Set the width of the image. * * @param string $image_size The size of the image. * @param int $width The width to set. * @return WP_Image_Meta The current instance. */ public function set_width( string $image_size, int $width ): WP_Image_Meta { if ( Image::SIZE_FULL === $image_size ) { $this->image_meta['width'] = $width; return $this; } $this->maybe_add_new_size( $image_size ); $this->image_meta['sizes'][ $image_size ]['width'] = $width; return $this; } /** * Get the height of the image. * * @param string $image_size The size of the image. * * @return int|null */ public function get_height( string $image_size ): ?int { if ( Image::SIZE_FULL === $image_size ) { return $this->image_meta['height']; } if ( ! isset( $this->image_meta['sizes'][ $image_size ]['height'] ) ) { return null; } return $this->image_meta['sizes'][ $image_size ]['height']; } /** * Set the height of the image. * * @param string $image_size The size of the image. * @param int $height The height to set. * @return WP_Image_Meta The current instance. */ public function set_height( string $image_size, int $height ): WP_Image_Meta { if ( Image::SIZE_FULL === $image_size ) { $this->image_meta['height'] = $height; return $this; } $this->maybe_add_new_size( $image_size ); $this->image_meta['sizes'][ $image_size ]['height'] = $height; return $this; } /** * Set the mime type of the image. * * @param string $image_size The size of the image. * @param string $mime_type The mime type to set. * @return WP_Image_Meta The current instance. */ public function set_mime_type( string $image_size, string $mime_type ): WP_Image_Meta { if ( Image::SIZE_FULL === $image_size ) { // WP doesn't store it in meta for the original image return $this; } $this->maybe_add_new_size( $image_size ); $this->image_meta['sizes'][ $image_size ]['mime-type'] = $mime_type; return $this; } /** * Add a new size to the image if it does not exist. * * @param string $image_size The size of the image. */ private function maybe_add_new_size( $image_size ): void { if ( ! isset( $this->image_meta['sizes'][ $image_size ] ) ) { $this->image_meta['sizes'][ $image_size ] = []; } } /** * Save the image metadata. * * @return WP_Image_Meta The current instance. */ public function save(): WP_Image_Meta { wp_update_attachment_metadata( $this->image_id, $this->image_meta ); return $this; } /** * Query the image metadata. * * @throws Invalid_Image_Exception */ private function query_meta(): void { $meta = wp_get_attachment_metadata( $this->image_id ); if ( ! $meta ) { throw new Invalid_Image_Exception( 'Invalid WP image meta' ); } // Handle unsupported formats that WP doesn't create thumbnails for if ( ! isset( $meta['sizes'] ) ) { $meta['sizes'] = []; } $this->image_meta = $meta; } /** * WP_Image_Meta constructor. * * @param int $image_id The ID of the image. * @param Image|null $image The image object. * @throws Invalid_Image_Exception If the image metadata is invalid. */ public function __construct( int $image_id, ?Image $image = null ) { $this->image_id = $image_id; $this->image = $image ?? new Image( $image_id ); $this->query_meta(); } } image/image-restore.php 0000644 00000007736 14717654505 0011135 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\File_System\{ Exceptions\File_System_Operation_Error, File_System, }; use ImageOptimization\Classes\File_Utils; use ImageOptimization\Classes\Image\Exceptions\{ Image_Restoring_Exception, Invalid_Image_Exception, }; use ImageOptimization\Classes\Logger; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Restore { public static function restore_many( array $image_ids, bool $keep_image_meta = false ): void { foreach ( $image_ids as $image_id ) { try { self::restore( $image_id, $keep_image_meta ); } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'Bulk images restoring error: ' . $t->getMessage() ); ( new Image_Meta( $image_id ) ) ->set_status( Image_Status::RESTORING_FAILED ) ->save(); continue; } } } /** * @throws Invalid_Image_Exception|Image_Restoring_Exception|File_System_Operation_Error */ public static function restore( int $image_id, bool $keep_image_meta = false ): void { $image = new Image( $image_id ); if ( ! $image->can_be_restored() ) { throw new Image_Restoring_Exception( "Image $image_id cannot be restored" ); } $meta = new Image_Meta( $image_id ); $wp_meta = new WP_Image_Meta( $image_id ); foreach ( $meta->get_optimized_sizes() as $image_size ) { $backup_path = $meta->get_image_backup_path( $image_size ); $current_path = $image->get_file_path( $image_size ); if ( $backup_path && $current_path ) { $original_path = self::get_path_from_backup_path( $backup_path ); if ( $original_path === $backup_path ) { File_System::delete( $current_path, false, 'f' ); self::update_posts( $current_path, $original_path ); } else { File_System::move( $backup_path, $original_path, $original_path === $current_path ); if ( $original_path !== $current_path ) { File_System::delete( $current_path, false, 'f' ); self::update_posts( $current_path, $original_path ); } } $file_size = $meta->get_original_file_size( $image_size ) ?? File_System::size( $original_path ); $wp_meta ->set_file_path( $image_size, $original_path ) ->set_mime_type( $image_size, $meta->get_original_mime_type( $image_size ) ) ->set_width( $image_size, $meta->get_original_width( $image_size ) ) ->set_height( $image_size, $meta->get_original_height( $image_size ) ) ->set_file_size( $image_size, $file_size ); if ( Image::SIZE_FULL === $image_size ) { self::update_image_post( $image, $original_path, $meta->get_original_mime_type( Image::SIZE_FULL ) ); } } } $wp_meta->save(); if ( $keep_image_meta ) { $meta ->clear_optimized_sizes() ->clear_backups() ->clear_original_data() ->save(); return; } $meta->delete(); } private static function get_path_from_backup_path( string $backup_path ): string { $extension = File_Utils::get_extension( $backup_path ); return str_replace( ".backup.$extension", ".$extension", $backup_path ); } private static function update_image_post( Image $image, string $image_path, string $mime_type ): void { $post_update_query = [ 'post_modified' => current_time( 'mysql' ), 'post_modified_gmt' => current_time( 'mysql', true ), 'post_mime_type' => $mime_type, 'guid' => File_Utils::get_url_from_path( $image_path ), ]; $image->update_attachment( $post_update_query ); update_attached_file( $image->get_id(), $image_path ); } /** * If we change an image extension, we should walk through the wp_posts table and update all the * hardcoded image links to prevent 404s. * * @param string $old_path Previous image path * @param string $new_path Current image path * * @return void */ private static function update_posts( string $old_path, string $new_path ) { Image_DB_Update::update_posts_table_urls( File_Utils::get_url_from_path( $old_path ), File_Utils::get_url_from_path( $new_path ) ); } } image/image-conversion.php 0000644 00000002263 14717654505 0011625 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Modules\Settings\Classes\Settings; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Conversion { protected ?string $convert_to_format; protected array $options; public function is_enabled(): bool { return Image_Conversion_Option::ORIGINAL !== $this->convert_to_format; } public function get_current_conversion_option(): string { return $this->convert_to_format; } public function get_current_file_extension(): ?string { if ( ! $this->is_enabled() ) { return null; } return $this->options[ $this->convert_to_format ]['extension']; } public function get_current_mime_type(): ?string { if ( ! $this->is_enabled() ) { return null; } return $this->options[ $this->convert_to_format ]['mime_type']; } public function __construct() { $this->convert_to_format = Settings::get( Settings::CONVERT_TO_FORMAT_OPTION_NAME ); $this->options = [ Image_Conversion_Option::WEBP => [ 'extension' => 'webp', 'mime_type' => 'image/webp', ], Image_Conversion_Option::AVIF => [ 'extension' => 'avif', 'mime_type' => 'image/avif', ], ]; } } image/image-dimensions.php 0000644 00000002041 14717654505 0011602 0 ustar 00 <?php namespace ImageOptimization\Classes\Image; use ImageOptimization\Classes\Logger; use Imagick; use stdClass; use Throwable; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Image_Dimensions { /** * @param string $file_path * * @return stdClass{width: int, height: int} */ public static function get_by_path( string $file_path ): stdClass { $dimensions = wp_getimagesize( $file_path ); $output = new stdClass(); $output->width = 0; $output->height = 0; if ( $dimensions ) { $output->width = $dimensions[0]; $output->height = $dimensions[1]; return $output; } if ( class_exists( 'Imagick' ) ) { try { $im = new Imagick( $file_path ); $image_geometry = $im->getImageGeometry(); $im->clear(); $output->width = $image_geometry['width']; $output->height = $image_geometry['height']; } catch ( Throwable $t ) { Logger::log( Logger::LEVEL_ERROR, 'AVIF image dimensions calculation error: ' . $t->getMessage() ); } } return $output; } } route.php 0000644 00000026102 14717654505 0006432 0 ustar 00 <?php namespace ImageOptimization\Classes; use ReflectionClass; use WP_Error; use WP_REST_Request; use WP_REST_Response; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Route { /** * Should the endpoint be validated for user authentication? * If set to TRUE, the default permission callback will make sure the user is logged in and has a valid user id * @var bool */ protected $auth = true; /** * holds current authenticated user id * @var int */ protected $current_user_id; protected $override = false; /** * Rest Endpoint namespace * @var string */ protected $namespace = 'image-optimizer/v1'; /** * @var array The valid HTTP methods. The list represents the general REST methods. Do not modify. */ private $valid_http_methods = [ 'GET', 'PATCH', 'POST', 'PUT', 'DELETE', ]; /** * Route_Base constructor. */ public function __construct() { add_action( 'rest_api_init', [ $this, 'rest_api_init' ] ); } /** * rest_api_init * * Registers REST endpoints. * Loops through the REST methods for this route, creates an endpoint configuration for * each of them and registers all the endpoints with the WordPress system. */ public function rest_api_init(): void { $methods = $this->get_methods(); if ( empty( $methods ) ) { return; } $callbacks = []; foreach ( $methods as $method ) { if ( ! in_array( $method, $this->valid_http_methods, true ) ) { continue; } $callbacks[] = $this->build_endpoint_method_config( $method ); } $arguments = $this->get_arguments(); if ( ! $callbacks && empty( $arguments ) ) { return; } $arguments = array_merge( $arguments, $callbacks ); register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override ); } /** * get_methods * Rest Endpoint methods * * Returns an array of the supported REST methods for this route * @return array<string> REST methods being configured for this route. */ abstract public function get_methods(): array; /** * get_callback * * Returns a reference to the callback function to handle the REST method specified by the /method/ parameter. * @param string $method The REST method name * * @return callable A reference to a member function with the same name as the REST method being passed as a parameter, * or a reference to the default function /callback/. */ public function get_callback_method( string $method ): callable { $method_name = strtolower( $method ); $callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback'; return [ $this, $callback ]; } /** * get_permission_callback_method * * Returns a reference to the permission callback for the method if exists or the default one if it doesn't. * @param string $method The REST method name * * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise * returns a reference to the default member method /permission_callback/. */ public function get_permission_callback_method( string $method ): callable { $method_name = strtolower( $method ); $permission_callback_method = $method_name . '_permission_callback'; $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback'; return [ $this, $permission_callback ]; } /** * maybe_add_args_to_config * * Checks if the class has a method call (rest-method)_args. * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key. * @param string $method The REST method name being configured * @param array $config The configuration object for the method * * @return array The configuration object for the method, possibly after being amended */ public function maybe_add_args_to_config( string $method, array $config ): array { $method_name = strtolower( $method ); $method_args = $method_name . '_args'; if ( $this->method_exists_in_current_class( $method_args ) ) { $config['args'] = $this->{$method_args}(); } return $config; } /** * maybe_add_response_to_swagger * * If the function method /(rest-method)_response_callback/ exists, adds the filter * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/ * with the aforementioned function method. * This filter is used with the WP API Swagger UI plugin to create documentation for the API. * The value being passed is an array: [ '200' => ['description' => 'OK'], '404' => ['description' => 'Not Found'], '400' => ['description' => 'Bad Request'] ] * @param string $method REST method name */ public function maybe_add_response_to_swagger( string $method ): void { $method_name = strtolower( $method ); $method_response_callback = $method_name . '_response_callback'; if ( $this->method_exists_in_current_class( $method_response_callback ) ) { $response_filter = $method_name . '_' . str_replace( '/', '_', $this->namespace . '/' . $this->get_endpoint() ); add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] ); } } /** * build_endpoint_method_config * * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters, * and response to Swagger member functions. * @param string $method The REST method for the endpoint * * @return array The endpoint configuration for the method specified by the parameter */ private function build_endpoint_method_config( string $method ): array { $config = [ 'methods' => $method, 'callback' => $this->get_callback_method( $method ), 'permission_callback' => $this->get_permission_callback_method( $method ), ]; $config = $this->maybe_add_args_to_config( $method, $config ); return $config; } /** * method_exists_in_current_class * * Uses reflection to check if this class has the /method/ method. * @param string $method The name of the method being checked. * * @return bool TRUE if the class has the /method/ method, FALSE otherwise. */ private function method_exists_in_current_class( string $method ): bool { $class_name = get_class( $this ); try { $reflection = new ReflectionClass( $class_name ); } catch ( \ReflectionException $e ) { return false; } if ( ! $reflection->hasMethod( $method ) ) { return false; } $method_ref = $reflection->getMethod( $method ); return ( $method_ref && $class_name === $method_ref->class ); } /** * permission_callback * Permissions callback fallback for the endpoint * Gets the current user ID and sets the /current_user_id/ property. * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0) * * @param WP_REST_Request $request unused * * @return bool TRUE, if permission granted, FALSE otherwise */ public function permission_callback( WP_REST_Request $request ): bool { // try to get current user $this->current_user_id = get_current_user_id(); if ( $this->auth ) { return $this->current_user_id > 0; } return true; } /** * callback * Fallback callback function, returns a response consisting of the string /ok/. * * @param WP_REST_Request $request unused * * @return WP_REST_Response Default Response of the string 'ok'. */ public function callback( WP_REST_Request $request ): WP_REST_Response { return rest_ensure_response( [ 'OK' ] ); } /** * respond_wrong_method * * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults. * @param null $message The error message for the wrong method. * Optional. * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/ * @param int $code The HTTP status code. * Optional. * Defaults to 404 (Not found). * * @return WP_Error The WordPress error object with the error message and status code supplied */ public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error { if ( null === $message ) { $message = 'No route was found matching the URL and request method'; } return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] ); } /** * respond_with_code * Create a new /WP_REST_Response/ object with the specified data and HTTP response code. * * @param array|null $data The data to return in this response * @param int $code The HTTP response code. * Optional. * Defaults to 200 (OK). * * @return WP_REST_Response The WordPress response object loaded with the data and the response code. */ public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response { return new WP_REST_Response( $data, $code ); } /** * get_user_from_request * * Returns the current user object. * Depends on the property /current_user_id/ to be set. * @return WP_User|false The user object or false if not found or on error. */ public function get_user_from_request() { return get_user_by( 'id', $this->current_user_id ); } /** * get_arguments * Rest Endpoint extra arguments * @return array Additional arguments for the route configuration */ public function get_arguments(): array { return []; } /** * get_endpoint * Rest route Endpoint * @return string Endpoint uri component (comes after the route namespace) */ abstract public function get_endpoint(): string; /** * get_name * @return string The name of the route */ abstract public function get_name(): string; public function get_self_url( $endpoint = '' ): string { return rest_url( $this->namespace . '/' . $endpoint ); } public function respond_success_json( $data = [] ): WP_REST_Response { return new WP_REST_Response([ 'success' => true, 'data' => $data, ]); } /** * @param array{message: string, code: string} $data * * @return WP_Error */ public function respond_error_json( array $data ): WP_Error { if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) { _doing_it_wrong( __FUNCTION__, esc_html__( 'Both `message` and `code` keys must be provided', 'image-optimization' ), '1.0.0' ); // @codeCoverageIgnore } return new WP_Error( $data['code'] ?? 'internal_server_error', $data['message'] ?? esc_html__( 'Internal server error', 'image-optimization' ), ); } public function verify_nonce( $nonce = '', $name = '' ) { if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'Invalid nonce', 'image-optimization' ), 'code' => 'bad_request', ]); } } public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) { $this->verify_nonce( $nonce, $name ); if ( ! current_user_can( $capability ) ) { return $this->respond_error_json([ 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ), 'code' => 'bad_request', ]); } } } basic-enum.php 0000644 00000001273 14717654505 0007321 0 ustar 00 <?php namespace ImageOptimization\Classes; use ReflectionClass; use ReflectionException; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } abstract class Basic_Enum { private static array $entries = []; /** * @throws ReflectionException */ public static function get_values(): array { return array_values( self::get_entries() ); } /** * @throws ReflectionException */ protected static function get_entries(): array { $caller = get_called_class(); if ( ! array_key_exists( $caller, self::$entries ) ) { $reflect = new ReflectionClass( $caller ); self::$entries[ $caller ] = $reflect->getConstants(); } return self::$entries[ $caller ]; } } utils.php 0000644 00000006207 14717654505 0006440 0 ustar 00 <?php namespace ImageOptimization\Classes; use ImageOptimization\Classes\Client\Client; use ImageOptimization\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } class Utils { /** * get_elementor * @param $instance * * @return \Elementor\Plugin|false|mixed|null */ public static function get_elementor( $instance = false ) { static $_instance = null; if ( false !== $instance ) { $_instance = $instance; return $instance; } if ( null !== $_instance ) { return $_instance; } if ( class_exists( 'Elementor\Plugin' ) ) { return \Elementor\Plugin::instance(); // @codeCoverageIgnore } return false; } public static function get_api_client(): ?Client { return Client::get_instance(); } public static function get_module( $module_name = '' ) { return Plugin::instance()->modules_manager->get_modules( $module_name ); } public static function get_module_component( $module_name, $component ) { $module = self::get_module( $module_name ); if ( $module ) { return $module->get_component( $component ); } return null; } /** * is_elementor_installed * @return bool */ public static function is_elementor_installed(): bool { $plugins = get_plugins(); return isset( $plugins['elementor/elementor.php'] ); } /** * is_elementor_installed_and_active * should be used only after `plugins_loaded` action * @return bool */ public static function is_elementor_installed_and_active(): bool { return did_action( 'elementor/loaded' ); } public static function is_media_page(): bool { $current_screen = get_current_screen(); if ( ! $current_screen ) { return false; } return 'upload' === $current_screen->id && 'attachment' === $current_screen->post_type; } public static function is_media_upload_page(): bool { $current_screen = get_current_screen(); if ( ! $current_screen ) { return false; } return 'media' === $current_screen->id && 'add' === $current_screen->action; } public static function is_single_attachment_page(): bool { $current_screen = get_current_screen(); if ( ! $current_screen ) { return false; } return 'attachment' === $current_screen->id && 'post' === $current_screen->base; } public static function is_plugin_page(): bool { $current_screen = get_current_screen(); return str_contains( $current_screen->id, 'image-optimization-' ); } public static function is_plugin_settings_page(): bool { $current_screen = get_current_screen(); return str_contains( $current_screen->id, 'image-optimization-settings' ); } public static function is_bulk_optimization_page(): bool { $current_screen = get_current_screen(); return str_contains( $current_screen->id, 'image-optimization-bulk-optimization' ); } public static function user_is_admin(): bool { return current_user_can( 'manage_options' ); } public static function is_wp_dashboard_page(): bool { $current_screen = get_current_screen(); return str_contains( $current_screen->id, 'dashboard' ); } public static function is_wp_updates_page(): bool { $current_screen = get_current_screen(); return str_contains( $current_screen->id, 'update' ); } } words-count/wpml-tm-package-element.php 0000755 00000002626 14720342453 0014173 0 ustar 00 <?php class WPML_TM_Package_Element extends WPML_TM_Translatable_Element { /** @var WPML_ST_Package_Factory $st_package_factory */ private $st_package_factory; /** @var WPML_Package $st_package */ private $st_package; /** * @param int $id * @param WPML_TM_Word_Count_Records $word_count_records * @param WPML_TM_Word_Count_Single_Process $single_process * @param WPML_ST_Package_Factory|null $st_package_factory */ public function __construct( $id, WPML_TM_Word_Count_Records $word_count_records, WPML_TM_Word_Count_Single_Process $single_process, WPML_ST_Package_Factory $st_package_factory = null ) { $this->st_package_factory = $st_package_factory; parent::__construct( $id, $word_count_records, $single_process ); } /** @param int $id */ protected function init( $id ) { if ( $this->st_package_factory ) { $this->st_package = $this->st_package_factory->create( $id ); } } protected function get_type() { return 'package'; } /** @return int */ protected function get_total_words() { return $this->word_count_records->get_package_word_count( $this->id )->get_total_words(); } /** * @param null $label * * @return string */ public function get_type_name( $label = null ) { if ( $this->st_package ) { return $this->st_package->kind; } return __( 'Unknown string Package', 'wpml-translation-management' ); } } words-count/class-wpml-tm-translatable-element.php 0000755 00000002417 14720342453 0016355 0 ustar 00 <?php abstract class WPML_TM_Translatable_Element { /** @var WPML_TM_Word_Count_Records $word_count_records */ protected $word_count_records; /** @var WPML_TM_Word_Count_Single_Process $single_process */ protected $single_process; /** @var int $id */ protected $id; /** * @param int|false $id * @param WPML_TM_Word_Count_Records $word_count_records * @param WPML_TM_Word_Count_Single_Process $single_process */ public function __construct( $id, WPML_TM_Word_Count_Records $word_count_records, WPML_TM_Word_Count_Single_Process $single_process ) { $this->word_count_records = $word_count_records; $this->single_process = $single_process; $this->set_id( $id ); } public function set_id( $id ) { if ( ! $id ) { return; } $this->id = $id; $this->init( $id ); } abstract protected function init( $id ); abstract public function get_type_name( $label = null ); abstract protected function get_type(); abstract protected function get_total_words(); /** @return int */ public function get_words_count() { $total_words = $this->get_total_words(); if ( $total_words ) { return $total_words; } $this->single_process->process( $this->get_type(), $this->id ); return $this->get_total_words(); } } words-count/count/wpml-tm-count-composite.php 0000755 00000001425 14720342453 0015425 0 ustar 00 <?php class WPML_TM_Count_Composite implements IWPML_TM_Count { /** @var IWPML_TM_Count[] $counts */ private $counts = array(); public function add_count( IWPML_TM_Count $count ) { $this->counts[] = $count; } /** @var IWPML_TM_Count[] $counts */ public function add_counts( $counts ) { foreach ( $counts as $count ) { $this->add_count( $count ); } } /** * @param string $lang * * @return int */ public function get_words_to_translate( $lang ) { $words = 0; foreach ( $this->counts as $count ) { $words += $count->get_words_to_translate( $lang ); } return $words; } /** @return int */ public function get_total_words() { $words = 0; foreach ( $this->counts as $count ) { $words += $count->get_total_words(); } return $words; } } words-count/count/iwpml-tm-count.php 0000755 00000000175 14720342453 0013577 0 ustar 00 <?php interface IWPML_TM_Count { public function get_total_words(); public function get_words_to_translate( $lang ); } words-count/count/wpml-tm-count.php 0000755 00000002510 14720342453 0013421 0 ustar 00 <?php class WPML_TM_Count implements IWPML_TM_Count { /** @var int $total */ private $total = 0; /** @var array $to_translate */ private $to_translate; /** * @param string|null $json_data */ public function __construct( $json_data = null ) { if ( $json_data ) { $this->set_properties_from_json( $json_data ); } } /** @param string $json_data */ public function set_properties_from_json( $json_data ) { $data = json_decode( $json_data, true ); if ( isset( $data['total'] ) ) { $this->total = (int) $data['total']; } if ( isset( $data['to_translate'] ) ) { $this->to_translate = $data['to_translate']; } } /** @return int */ public function get_total_words() { return $this->total; } /** @param int $total */ public function set_total_words( $total ) { $this->total = $total; } /** * @param string $lang * * @return int|null */ public function get_words_to_translate( $lang ) { if ( isset( $this->to_translate[ $lang ] ) ) { return (int) $this->to_translate[ $lang ]; } return null; } /** @return string */ public function to_string() { return json_encode( array( 'total' => $this->total, 'to_translate' => $this->to_translate, ) ); } public function set_words_to_translate( $lang, $quantity ) { $this->to_translate[ $lang ] = $quantity; } } words-count/class-wpml-tm-translatable-element-provider.php 0000755 00000004174 14720342453 0020207 0 ustar 00 <?php class WPML_TM_Translatable_Element_Provider { /** @var WPML_TM_Word_Count_Records $word_count_records */ private $word_count_records; /** @var WPML_TM_Word_Count_Single_Process $single_process */ private $single_process; /** @var null|WPML_ST_Package_Factory $st_package_factory */ private $st_package_factory; public function __construct( WPML_TM_Word_Count_Records $word_count_records, WPML_TM_Word_Count_Single_Process $single_process, WPML_ST_Package_Factory $st_package_factory = null ) { $this->word_count_records = $word_count_records; $this->single_process = $single_process; $this->st_package_factory = $st_package_factory; } /** * @param WPML_TM_Job_Entity $job * * @return null|WPML_TM_Package_Element|WPML_TM_Post|WPML_TM_String */ public function get_from_job( WPML_TM_Job_Entity $job ) { $id = $job->get_original_element_id(); switch ( $job->get_type() ) { case WPML_TM_Job_Entity::POST_TYPE: return $this->get_post( $id ); case WPML_TM_Job_Entity::STRING_TYPE: return $this->get_string( $id ); case WPML_TM_Job_Entity::PACKAGE_TYPE: return $this->get_package( $id ); } return null; } /** * @param string $type * @param int $id * * @return null|WPML_TM_Package_Element|WPML_TM_Post|WPML_TM_String */ public function get_from_type( $type, $id ) { switch ( $type ) { case 'post': return $this->get_post( $id ); case 'string': return $this->get_string( $id ); case 'package': return $this->get_package( $id ); } return null; } /** * @param int $id * * @return WPML_TM_Post */ private function get_post( $id ) { return new WPML_TM_Post( $id, $this->word_count_records, $this->single_process ); } /** * @param int $id * * @return WPML_TM_String */ private function get_string( $id ) { return new WPML_TM_String( $id, $this->word_count_records, $this->single_process ); } /** * @param int $id * * @return WPML_TM_Package_Element */ private function get_package( $id ) { return new WPML_TM_Package_Element( $id, $this->word_count_records, $this->single_process, $this->st_package_factory ); } } words-count/class-wpml-tm-string.php 0000755 00000000603 14720342453 0013553 0 ustar 00 <?php class WPML_TM_String extends WPML_TM_Translatable_Element { protected function init( $id ) {} protected function get_type() { return 'string'; } protected function get_total_words() { return $this->word_count_records->get_string_word_count( $this->id ); } public function get_type_name( $label = null ) { return __( 'String', 'wpml-translation-management' ); } } words-count/queue/background-process/wpml-tm-word-count-background-process.php 0000755 00000002745 14720342453 0023764 0 ustar 00 <?php abstract class WPML_TM_Word_Count_Background_Process extends WP_Background_Process { /** @var IWPML_TM_Word_Count_Queue_Items $queue */ protected $queue; /** @var IWPML_TM_Word_Count_Set[] $setters */ private $setters; /** * @param IWPML_TM_Word_Count_Queue_Items $queue * @param IWPML_TM_Word_Count_Set[] $setters */ public function __construct( IWPML_TM_Word_Count_Queue_Items $queue, array $setters ) { /** We need to set the prefix and the identifier before constructing the parent class `WP_Async_Request` */ $this->prefix = WPML_TM_Word_Count_Background_Process_Factory::PREFIX; $this->action = WPML_TM_Word_Count_Background_Process_Factory::ACTION_REQUESTED_TYPES; parent::__construct(); $this->queue = $queue; $this->setters = $setters; } /** * This abstract method is not implemented because we override the `handle` method. */ protected function task( $item ) {} protected function handle() { $this->lock_process(); while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->queue->is_completed() ) { list( $id, $type ) = $this->queue->get_next(); if ( $id && $type ) { $this->setters[ $type ]->process( $id ); $this->queue->remove( $id, $type ); } } $this->queue->save(); $this->unlock_process(); if ( $this->queue->is_completed() ) { $this->complete(); } else { $this->dispatch(); } wp_die(); } protected function is_queue_empty() { return $this->queue->is_completed(); } } words-count/queue/background-process/wpml-tm-word-count-background-process-requested-types.php 0000755 00000006201 14720342453 0027114 0 ustar 00 <?php class WPML_TM_Word_Count_Background_Process_Requested_Types extends WPML_TM_Word_Count_Background_Process { /** @var WPML_TM_Word_Count_Queue_Items_Requested_Types $queue */ protected $queue; /** @var WPML_TM_Word_Count_Records $records */ private $records; /** * @param WPML_TM_Word_Count_Queue_Items_Requested_Types $queue_items * @param IWPML_TM_Word_Count_Set[] $setters */ public function __construct( WPML_TM_Word_Count_Queue_Items_Requested_Types $queue_items, array $setters, WPML_TM_Word_Count_Records $records ) { /** We need to set the action before constructing the parent class `WP_Async_Request` */ $this->action = WPML_TM_Word_Count_Background_Process_Factory::ACTION_REQUESTED_TYPES; parent::__construct( $queue_items, $setters ); $this->records = $records; add_filter( 'wpml_tm_word_count_background_process_requested_types_memory_exceeded', array( $this, 'memory_exceeded_filter', ) ); } public function init( $requested_types ) { $this->queue->reset( $requested_types ); $this->records->reset_all( $requested_types ); $this->dispatch(); } public function dispatch() { update_option( WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS, WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS ); parent::dispatch(); } public function complete() { update_option( WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS, WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED ); parent::complete(); } /** * Filter result of memory_exceeded() function in WP_Background_Process class. * Used by it get_memory_limit() function of WP_Background_Process class contains a number of bugs, * producing wrong result when 'memory_limit' setting in php.ini is in human readable format like '1G'. * * @return bool */ public function memory_exceeded_filter() { $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory $current_memory = memory_get_usage( true ); return $current_memory >= $memory_limit; } /** * Get memory limit in bytes. * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { // Sensible default. $memory_limit = '128M'; } if ( ! $memory_limit || - 1 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32000M'; } return $this->convert_shorthand_to_bytes( $memory_limit ); } /** * Converts a shorthand byte value to an integer byte value. * * @param string $value A (PHP ini) byte value, either shorthand or ordinary. * @return int An integer byte value. */ protected function convert_shorthand_to_bytes( $value ) { $value = strtolower( trim( $value ) ); $bytes = (int) $value; if ( false !== strpos( $value, 'g' ) ) { $bytes *= 1024 * 1024 * 1024; } elseif ( false !== strpos( $value, 'm' ) ) { $bytes *= 1024 * 1024; } elseif ( false !== strpos( $value, 'k' ) ) { $bytes *= 1024; } // Deal with large (float) values which run into the maximum integer size. return min( $bytes, PHP_INT_MAX ); } } words-count/queue/background-process/wpml-tm-word-count-background-process-factory.php 0000755 00000001350 14720342453 0025420 0 ustar 00 <?php class WPML_TM_Word_Count_Background_Process_Factory { const PREFIX = 'wpml_tm'; const ACTION_REQUESTED_TYPES = 'word_count_background_process_requested_types'; /** * @return WPML_TM_Word_Count_Background_Process_Requested_Types */ public function create_requested_types() { $records_factory = new WPML_TM_Word_Count_Records_Factory(); $records = $records_factory->create(); $setters_factory = new WPML_TM_Word_Count_Setters_Factory(); $setters = $setters_factory->create(); $requested_types_items = new WPML_TM_Word_Count_Queue_Items_Requested_Types( $records ); return new WPML_TM_Word_Count_Background_Process_Requested_Types( $requested_types_items, $setters, $records ); } } words-count/queue/items/wpml-tm-word-count-queue-items-requested-types.php 0000755 00000010407 14720342453 0023115 0 ustar 00 <?php class WPML_TM_Word_Count_Queue_Items_Requested_Types implements IWPML_TM_Word_Count_Queue_Items { const OPTION_KEY = 'wpml_word_count_queue_items_requested_types'; const STEP_STANDALONE_PACKAGES = 1; const STEP_POST_PACKAGES = 2; const STEP_POSTS = 3; const STEP_COMPLETED = 4; /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var array $requested_types to be processed */ private $requested_types; /** @var string $step */ private $step; /** @var array|null $items */ private $items = array( 'string' => array(), 'package' => array(), 'post' => array(), ); public function __construct( WPML_TM_Word_Count_Records $records ) { $this->records = $records; } /** * @return array|null a tuple containing the element id and type or null if queue is empty */ public function get_next() { $this->init_queue(); foreach ( array( 'string', 'package', 'post' ) as $type ) { if ( $this->items[ $type ] ) { return array( reset( $this->items[ $type ] ), $type ); } } return null; } private function init_queue() { if ( ! $this->step ) { $this->restore_queue_from_db(); } if ( ! $this->has_items() ) { $this->init_step(); } } private function restore_queue_from_db() { $this->step = self::STEP_STANDALONE_PACKAGES; $options = get_option( self::OPTION_KEY, array() ); if ( isset( $options['step'] ) ) { $this->step = $options['step']; } if ( isset( $options['requested_types'] ) ) { $this->requested_types = $options['requested_types']; } if ( isset( $options['items'] ) ) { $this->items = $options['items']; } } private function init_step() { switch ( $this->step ) { case self::STEP_STANDALONE_PACKAGES: $this->add_standalone_packages_to_queue(); break; case self::STEP_POST_PACKAGES: $this->add_post_packages_to_queue(); break; case self::STEP_POSTS: $this->add_posts_to_queue(); break; } $this->make_item_keys_equals_to_id(); $this->maybe_move_to_next_step(); } private function add_standalone_packages_to_queue() { if ( ! empty( $this->requested_types['package_kinds'] ) ) { $this->items['package'] = $this->records->get_package_ids_from_kind_slugs( $this->requested_types['package_kinds'] ); $this->items['string'] = $this->records->get_strings_ids_from_package_ids( $this->items['package'] ); } } private function add_post_packages_to_queue() { if ( ! empty( $this->requested_types['post_types'] ) ) { $this->items['package'] = $this->records->get_package_ids_from_post_types( $this->requested_types['post_types'] ); $this->items['string'] = $this->records->get_strings_ids_from_package_ids( $this->items['package'] ); } } private function add_posts_to_queue() { if ( ! empty( $this->requested_types['post_types'] ) ) { $this->items['post'] = $this->records->get_post_source_ids_from_types( $this->requested_types['post_types'] ); } } private function make_item_keys_equals_to_id() { foreach ( $this->items as $type => $ids ) { if ( $this->items[ $type ] ) { $this->items[ $type ] = array_combine( array_values( $this->items[ $type ] ), $this->items[ $type ] ); } } } private function maybe_move_to_next_step() { if ( ! $this->has_items() && ! $this->is_completed() ) { $this->step++; $this->init_step(); } } /** * @param int $id * @param string $type */ public function remove( $id, $type ) { if ( isset( $this->items[ $type ][ $id ] ) ) { unset( $this->items[ $type ][ $id ] ); } $this->maybe_move_to_next_step(); } /** @return bool */ private function has_items() { return ! empty( $this->items['string'] ) || ! empty( $this->items['package'] ) || ! empty( $this->items['post'] ); } /** @return bool */ public function is_completed() { return $this->step === self::STEP_COMPLETED; } public function save() { $options = array( 'step' => $this->step, 'requested_types' => $this->requested_types, 'items' => $this->items, ); update_option( self::OPTION_KEY, $options, false ); } public function reset( array $requested_types ) { $this->step = self::STEP_STANDALONE_PACKAGES; $this->requested_types = $requested_types; $this->items = null; $this->save(); } } words-count/queue/items/iwpml-tm-word-count-queue-items.php 0000755 00000000553 14720342453 0020126 0 ustar 00 <?php interface IWPML_TM_Word_Count_Queue_Items { /** * @return array|null a tuple containing the element id and type or null if queue is empty */ public function get_next(); /** * @param int $id * @param string $type */ public function remove( $id, $type ); /** @return bool */ public function is_completed(); public function save(); } words-count/report/wpml-tm-word-count-report-view.php 0000755 00000003555 14720342453 0017050 0 ustar 00 <?php class WPML_TM_Word_Count_Report_View { const TEMPLATE_PATH = '/templates/words-count'; const TEMPLATE_FILE = 'report.twig'; /** @var WPML_Twig_Template_Loader $loader */ private $loader; /** @var WPML_WP_Cron_Check $cron_check */ private $cron_check; public function __construct( WPML_Twig_Template_Loader $loader, WPML_WP_Cron_Check $cron_check ) { $this->loader = $loader; $this->cron_check = $cron_check; } public function show( array $model ) { $model['strings'] = self::get_strings(); $model['cron_is_on'] = $this->cron_check->verify(); return $this->loader->get_template()->show( $model, self::TEMPLATE_FILE ); } public static function get_strings() { return array( 'contentType' => __( 'Type', 'wpml-translation-management' ), 'itemsCount' => __( 'Items', 'wpml-translation-management' ), 'wordCount' => __( 'Words', 'wpml-translation-management' ), 'estimatedTime' => __( 'Count time', 'wpml-translation-management' ), 'total' => __( 'Total', 'wpml-translation-management' ), 'recalculate' => __( 'Recalculate', 'wpml-translation-management' ), 'cancel' => __( 'Cancel', 'wpml-translation-management' ), 'needsRefresh' => __( 'Needs refresh - Some items of this type are not counted', 'wpml-translation-management' ), 'inMinute' => __( '%d minute', 'wpml-translation-management' ), 'inMinutes' => __( '%d minutes', 'wpml-translation-management' ), 'cronWarning' => __( 'We detected a possible issue blocking the word count process. Please verify the following settings:', 'wpml-translation-management' ), 'cronTips' => array( __( 'Your site should be publicly accessible or the server should have access to the site.', 'wpml-translation-management' ), __( 'The constant DISABLE_WP_CRON should not be set to true.', 'wpml-translation-management' ), ), ); } } words-count/report/wpml-tm-word-count-report.php 0000755 00000014505 14720342453 0016075 0 ustar 00 <?php class WPML_TM_Word_Count_Report { const OPTION_KEY = 'wpml_word_count_report'; const POSTS_PER_MINUTE = 1200; const PACKAGES_PER_MINUTE = 5000; const POST_TYPES = 'post_types'; const PACKAGE_KINDS = 'package_kinds'; const IS_REQUESTED = 'isRequested'; /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var WPML_TM_Word_Count_Report_View $view */ private $view; /** @var SitePress $sitepress */ private $sitepress; /** @var array $post_types */ private $post_types; /** @var WPML_Package_Helper $st_package_helper */ private $st_package_helper; /** @var array $package_kinds */ private $package_kinds = array(); /** @var bool $requested_types_status */ private $requested_types_status; /** @var array $data */ private $data; /** * WPML_TM_Word_Count_Report constructor. * * @param WPML_TM_Word_Count_Report_View $view * @param WPML_TM_Word_Count_Records $records * @param SitePress $sitepress * @param string|false $requested_types_status * @param WPML_Package_Helper|null $st_package_helper */ public function __construct( WPML_TM_Word_Count_Report_View $view, WPML_TM_Word_Count_Records $records, SitePress $sitepress, $requested_types_status, WPML_Package_Helper $st_package_helper = null ) { $this->view = $view; $this->records = $records; $this->sitepress = $sitepress; $this->st_package_helper = $st_package_helper; $this->requested_types_status = $requested_types_status; } /** * @return string */ public function render() { $this->init_data(); $data = array( self::POST_TYPES => array(), self::PACKAGE_KINDS => array(), ); foreach ( $this->get_post_types() as $post_type_ ) { $data[ self::POST_TYPES ][ $post_type_->name ] = $this->build_type_row( self::POST_TYPES, $post_type_ ); } foreach ( $this->get_package_kinds() as $package_kind ) { $data[ self::PACKAGE_KINDS ][ $package_kind->name ] = $this->build_type_row( self::PACKAGE_KINDS, $package_kind ); } $this->data = $data; $this->save_data(); $model = array( 'countInProgress' => $this->requested_types_status === WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS, 'data' => $this->data, 'totals' => $this->get_totals(), ); return $this->view->show( $model ); } private function is_requested( $group, $type ) { return isset( $this->data[ $group ][ $type ][ self::IS_REQUESTED ] ) && $this->data[ $group ][ $type ][ self::IS_REQUESTED ]; } /** * @param string $group * @param WP_Post_Type|stdClass $type_object * * @return array|null */ private function build_type_row( $group, $type_object ) { $count_items = $this->records->count_items_by_type( $group, $type_object->name ); // Do not include in the report if it has no item if ( ! $count_items ) { return null; } $count_words = ''; $status = WPML_TM_Word_Count_Hooks_Factory::PROCESS_PENDING; $completed_items = $this->records->count_word_counts_by_type( $group, $type_object->name ); if ( $this->is_requested( $group, $type_object->name ) ) { $status = $completed_items < $count_items ? WPML_TM_Word_Count_Hooks_Factory::PROCESS_IN_PROGRESS : WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED; $count_words = $this->records->get_word_counts_by_type( $group, $type_object->name )->get_total_words(); } elseif ( ! empty( $this->data[ $group ][ $type_object->name ]['countWords'] ) ) { $status = WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED; $count_words = $this->data[ $group ][ $type_object->name ]['countWords']; } $label = $type_object->label; $items_per_minute = self::POSTS_PER_MINUTE; if ( self::PACKAGE_KINDS === $group ) { $items_per_minute = self::PACKAGES_PER_MINUTE; } return array( 'group' => $group, 'type' => $type_object->name, 'typeLabel' => $label, 'countItems' => $count_items, 'completedItems' => $completed_items, 'countWords' => $count_words, 'estimatedTime' => ceil( $count_items / $items_per_minute ), 'status' => $status, 'needsRefresh' => $completed_items < $count_items, 'isRequested' => $this->is_requested( $group, $type_object->name ), ); } private function get_totals() { $totals = array( 'completedItems' => 0, 'countItems' => 0, 'countWords' => 0, 'estimatedTime' => 0, 'requestedTypes' => 0, ); foreach ( $this->data as $group ) { foreach ( $group as $type ) { $totals['completedItems'] += (int) $type['completedItems']; $totals['countItems'] += (int) $type['countItems']; $totals['countWords'] += (int) $type['countWords']; $totals['estimatedTime'] += (int) $type['estimatedTime']; $totals['requestedTypes'] += (int) $type['isRequested']; } } return $totals; } public function set_requested_types( array $requested_types ) { $this->init_data(); $this->set_requested_group( self::POST_TYPES, $this->get_post_types(), $requested_types ); $this->set_requested_group( self::PACKAGE_KINDS, $this->get_package_kinds(), $requested_types ); $this->save_data(); } private function set_requested_group( $group, $types, $requested_types ) { foreach ( $types as $type ) { if ( false === array_search( $type->name, (array) $requested_types[ $group ], true ) ) { $this->data[ $group ][ $type->name ][ self::IS_REQUESTED ] = false; } else { $this->data[ $group ][ $type->name ][ self::IS_REQUESTED ] = true; } } } private function init_data() { $this->data = get_option( self::OPTION_KEY, array() ); } private function save_data() { $this->data[ self::POST_TYPES ] = array_filter( $this->data[ self::POST_TYPES ] ); $this->data[ self::PACKAGE_KINDS ] = array_filter( $this->data[ self::PACKAGE_KINDS ] ); update_option( self::OPTION_KEY, $this->data, false ); } private function get_post_types() { if ( ! $this->post_types ) { $this->post_types = $this->sitepress->get_translatable_documents(); } return $this->post_types; } public function get_package_kinds() { if ( $this->st_package_helper && ! $this->package_kinds ) { $this->package_kinds = $this->st_package_helper->get_translatable_types( array() ); } return $this->package_kinds; } } words-count/processor/wpml-tm-word-count-single-process.php 0000755 00000002153 14720342453 0020217 0 ustar 00 <?php class WPML_TM_Word_Count_Single_Process { /** @var IWPML_TM_Word_Count_Set[] $setters */ private $setters; /** @var WPML_ST_String_Dependencies_Builder $dependencies_builder */ private $dependencies_builder; /** * @param IWPML_TM_Word_Count_Set[] $setters * @param WPML_ST_String_Dependencies_Builder $dependencies_builder */ public function __construct( array $setters, WPML_ST_String_Dependencies_Builder $dependencies_builder = null ) { $this->setters = $setters; $this->dependencies_builder = $dependencies_builder; } /** * @param string $element_type * @param int $element_id */ public function process( $element_type, $element_id ) { if ( $this->dependencies_builder ) { $dependencies_tree = $this->dependencies_builder->from( $element_type, $element_id ); while ( ! $dependencies_tree->iteration_completed() ) { $node = $dependencies_tree->get_next(); $this->setters[ $node->get_type() ]->process( $node->get_id() ); $node->detach(); } } elseif ( 'post' === $element_type ) { $this->setters['post']->process( $element_id ); } } } words-count/processor/wpml-tm-word-count-set-string.php 0000755 00000001245 14720342453 0017362 0 ustar 00 <?php class WPML_TM_Word_Count_Set_String { /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var WPML_TM_Word_Calculator $calculator */ private $calculator; public function __construct( WPML_TM_Word_Count_Records $records, WPML_TM_Word_Calculator $calculator ) { $this->records = $records; $this->calculator = $calculator; } /** * @param int $string_id */ public function process( $string_id ) { $string = $this->records->get_string_value_and_language( $string_id ); $word_count = $this->calculator->count_words( $string->value, $string->language ); $this->records->set_string_word_count( $string_id, $word_count ); } } words-count/processor/calculator/post/wpml-tm-word-calculator-post-object.php 0000755 00000002747 14720342453 0023643 0 ustar 00 <?php class WPML_TM_Word_Calculator_Post_Object implements IWPML_TM_Word_Calculator_Post { /** @var WPML_TM_Word_Calculator $calculator */ private $calculator; /** @var WPML_TM_Word_Calculator_Post_Packages $packages_calculator */ private $packages_calculator; public function __construct( WPML_TM_Word_Calculator $calculator, WPML_TM_Word_Calculator_Post_Packages $packages_calculator ) { $this->calculator = $calculator; $this->packages_calculator = $packages_calculator; } /** * @param WPML_Post_Element $post_element * @param string $lang * * @return int */ public function count_words( WPML_Post_Element $post_element, $lang = null ) { $words = 0; $wp_post = $post_element->get_wp_object(); $source_lang = $post_element->get_language_code(); if ( $wp_post ) { $words += $this->calculator->count_words( $wp_post->post_title, $source_lang ); $words += $this->calculator->count_words( $wp_post->post_excerpt, $source_lang ); $words += $this->calculator->count_words( $wp_post->post_name, $source_lang ); if ( $this->has_string_packages( $post_element ) ) { $words += $this->packages_calculator->count_words( $post_element, $lang ); } else { $words += $this->calculator->count_words( $wp_post->post_content, $source_lang ); } } return $words; } private function has_string_packages( WPML_Post_Element $post_element ) { return (bool) $this->packages_calculator->count_words( $post_element, null ); } } words-count/processor/calculator/post/iwpml-tm-word-calculator-post.php 0000755 00000000204 14720342453 0022532 0 ustar 00 <?php interface IWPML_TM_Word_Calculator_Post { public function count_words( WPML_Post_Element $post_element, $lang = null ); } words-count/processor/calculator/post/wpml-tm-word-calculator-post-custom-fields.php 0000755 00000006441 14720342453 0025146 0 ustar 00 <?php class WPML_TM_Word_Calculator_Post_Custom_Fields implements IWPML_TM_Word_Calculator_Post { /** @var WPML_TM_Word_Calculator $calculator */ private $calculator; /** @var array|null $cf_settings from `$sitepress_settings['translation-management']['custom_fields_translation']` */ private $cf_settings; /** @var array $fields_to_count */ private $fields_to_count = array(); /** * WPML_TM_Word_Calculator_Post_Custom_Fields constructor. * * $cf_settings: * * <code> * $array = [ * 'custom-field-1' => WPML_TRANSLATE_CUSTOM_FIELD, * 'custom-field-2' => WPML_COPY_CUSTOM_FIELD, * 'custom-field-3' => WPML_IGNORE_CUSTOM_FIELD, * 'custom-field-4' => WPML_IGNORE_CUSTOM_FIELD, * 'custom-field-5' => WPML_COPY_ONCE_CUSTOM_FIELD, * ] * </code> * * @param \WPML_TM_Word_Calculator $calculator An instance of WPML_TM_Word_Calculator. * @param array|null $cf_settings An associative array where they key is the name of the custom field and the value is an integer representing the translation method. * * @see inc/constants.php for the values of the constsnts */ public function __construct( WPML_TM_Word_Calculator $calculator, array $cf_settings = null ) { $this->calculator = $calculator; $this->cf_settings = $cf_settings; } public function count_words( WPML_Post_Element $post_element, $lang = null ) { $words = 0; $post_id = $post_element->get_id(); $post_lang = $post_element->get_language_code(); if ( ! $post_lang || ! $post_id || ! $this->is_registered_type( $post_element ) || empty( $this->cf_settings ) || ! is_array( $this->cf_settings ) ) { return $words; } $cf_to_count = $this->get_translatable_fields_to_count( $post_id ); foreach ( $cf_to_count as $cf ) { $custom_fields_value = get_post_meta( $post_id, $cf ); if ( ! $custom_fields_value ) { continue; } if ( is_scalar( $custom_fields_value ) ) { // only support scalar values for now $words += $this->calculator->count_words( $custom_fields_value, $post_lang ); } else { foreach ( $custom_fields_value as $custom_fields_value_item ) { if ( $custom_fields_value_item && is_scalar( $custom_fields_value_item ) ) { // only support scalar values for now $words += $this->calculator->count_words( $custom_fields_value_item, $post_lang ); } } } } return (int) $words; } /** @return bool */ private function is_registered_type( WPML_Post_Element $post_element ) { $post_types = get_post_types(); return in_array( $post_element->get_type(), $post_types ); } /** @return array */ private function get_translatable_fields_to_count( $post_id ) { if ( ! $this->fields_to_count ) { foreach ( $this->cf_settings as $cf => $mode ) { if ( (int) $mode === WPML_TRANSLATE_CUSTOM_FIELD ) { $this->fields_to_count[] = $cf; } } } /** * Allow to modify the custom fields whose words will be counted. * * @param array $fields_to_count The fields to include when counting the words. * @param int $post_id The ID of the post for which we are counting the words. * * @see \WPML_TM_Word_Calculator_Post_Custom_Fields::__construct */ return apply_filters( 'wpml_words_count_custom_fields_to_count', $this->fields_to_count, $post_id ); } } words-count/processor/calculator/post/wpml-tm-word-calculator-post-packages.php 0000755 00000002457 14720342453 0024151 0 ustar 00 <?php class WPML_TM_Word_Calculator_Post_Packages implements IWPML_TM_Word_Calculator_Post { /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var WPML_TM_Count_Composite[] $package_counts */ private $package_counts; public function __construct( WPML_TM_Word_Count_Records $records ) { $this->records = $records; } /** * @param WPML_Post_Element $post_element * @param string|null $lang * * @return int */ public function count_words( WPML_Post_Element $post_element, $lang = null ) { if ( $lang ) { return $this->get_package_counts( $post_element )->get_words_to_translate( $lang ); } else { return $this->get_package_counts( $post_element )->get_total_words(); } } /** * @param WPML_Post_Element $post_element * * @return WPML_TM_Count_Composite */ private function get_package_counts( WPML_Post_Element $post_element ) { $post_id = $post_element->get_id(); if ( ! isset( $this->package_counts[ $post_id ] ) ) { $counts = $this->records->get_packages_word_counts( $post_id ); $word_count_composite = new WPML_TM_Count_Composite(); foreach ( $counts as $count ) { $word_count_composite->add_count( $count ); } $this->package_counts[ $post_id ] = $word_count_composite; } return $this->package_counts[ $post_id ]; } } words-count/processor/calculator/wpml-tm-word-calculator.php 0000755 00000004260 14720342453 0020417 0 ustar 00 <?php /** * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-2327 */ class WPML_TM_Word_Calculator { const ASIAN_LANGUAGE_CHAR_SIZE = 6; /** @var WPML_PHP_Functions $php_functions */ private $php_functions; public function __construct( WPML_PHP_Functions $php_functions ) { $this->php_functions = $php_functions; } /** * @param string $string * @param string $language_code * * @return int */ public function count_words( $string, $language_code ) { $sanitized_source = $this->sanitize_string( $string ); $words = 0; if ( in_array( $language_code, self::get_asian_languages() ) ) { $words += strlen( strip_tags( $sanitized_source ) ) / self::ASIAN_LANGUAGE_CHAR_SIZE; } else { $strings = preg_split( '/[\s,:;!\.\?\(\)\[\]\-_\'"\\/]+/', $sanitized_source, 0, PREG_SPLIT_NO_EMPTY ); $words += $strings ? count( $strings ) : 0; } return (int) $words; } /** @return bool */ private function exclude_shortcodes_in_words_count() { if ( $this->php_functions->defined( 'EXCLUDE_SHORTCODES_IN_WORDS_COUNT' ) ) { return (bool) $this->php_functions->constant( 'EXCLUDE_SHORTCODES_IN_WORDS_COUNT' ); } return false; } /** * @param string $source * * @return string */ private function sanitize_string( $source ) { $result = $source; $result = html_entity_decode( $result ); $result = strip_tags( $result ); $result = trim( $result ); $result = $this->strip_urls( $result ); if ( $this->exclude_shortcodes_in_words_count() ) { $result = strip_shortcodes( $result ); } else { $result = $this->extract_content_in_shortcodes( $result ); } return $result; } /** * @param string $string * * @return string */ private function extract_content_in_shortcodes( $string ) { return preg_replace( '#(?:\[/?)[^/\]]+/?\]#s', '', $string ); } /** * @param string $string * * @return string */ private function strip_urls( $string ) { return preg_replace( '/\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i', '', $string ); } public static function get_asian_languages() { return array( 'ja', 'ko', 'zh-hans', 'zh-hant', 'mn', 'ne', 'hi', 'pa', 'ta', 'th' ); } } words-count/processor/wpml-tm-word-count-single-process-factory.php 0000755 00000001024 14720342453 0021660 0 ustar 00 <?php class WPML_TM_Word_Count_Single_Process_Factory { public function create() { /** @var wpdb $wpdb */ global $wpdb; $setters_factory = new WPML_TM_Word_Count_Setters_Factory(); $dependencies_builder = null; if ( class_exists( 'WPML_ST_String_Dependencies_Builder' ) ) { $dependencies_builder = new WPML_ST_String_Dependencies_Builder( new WPML_ST_String_Dependencies_Records( $wpdb ) ); } return new WPML_TM_Word_Count_Single_Process( $setters_factory->create(), $dependencies_builder ); } } words-count/processor/iwpml-tm-word-count-set.php 0000755 00000000117 14720342453 0016224 0 ustar 00 <?php interface IWPML_TM_Word_Count_Set { public function process( $id ); } words-count/processor/wpml-tm-word-count-set-package.php 0000755 00000002214 14720342453 0017444 0 ustar 00 <?php class WPML_TM_Word_Count_Set_Package { /** @var WPML_ST_Package_Factory $package_factory */ private $package_factory; /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var array $active_langs */ private $active_langs; public function __construct( WPML_ST_Package_Factory $package_factory, WPML_TM_Word_Count_Records $records, array $active_langs ) { $this->package_factory = $package_factory; $this->records = $records; $this->active_langs = $active_langs; } /** @param int $package_id */ public function process( $package_id ) { $package = $this->package_factory->create( $package_id ); $word_counts = new WPML_TM_Count(); foreach ( $this->active_langs as $lang ) { if ( $package->get_package_language() === $lang ) { $word_counts->set_total_words( $this->records->get_string_words_to_translate_per_lang( null, $package_id ) ); } else { $word_counts->set_words_to_translate( $lang, $this->records->get_string_words_to_translate_per_lang( $lang, $package_id ) ); } } $this->records->set_package_word_count( $package_id, $word_counts ); } } words-count/processor/wpml-tm-word-count-set-post.php 0000755 00000003433 14720342453 0017042 0 ustar 00 <?php class WPML_TM_Word_Count_Set_Post { /** @var WPML_Translation_Element_Factory $element_factory */ private $element_factory; /** @var WPML_TM_Word_Count_Records $records */ private $records; /** @var IWPML_TM_Word_Calculator_Post[] $post_calculators */ private $post_calculators; /** @var array $active_langs */ private $active_langs; /** @var WPML_Post_Element $post_element */ private $post_element; /** * @param WPML_Translation_Element_Factory $element_factory * @param WPML_TM_Word_Count_Records $records * @param IWPML_TM_Word_Calculator_Post[] $calculators * @param array $active_langs */ public function __construct( WPML_Translation_Element_Factory $element_factory, WPML_TM_Word_Count_Records $records, array $calculators, array $active_langs ) { $this->element_factory = $element_factory; $this->records = $records; $this->post_calculators = $calculators; $this->active_langs = $active_langs; } /** * @param int $post_id */ public function process( $post_id ) { $this->post_element = $this->element_factory->create( $post_id, 'post' ); $word_count = new WPML_TM_Count(); foreach ( $this->active_langs as $lang ) { if ( $this->post_element->get_language_code() === $lang ) { $word_count->set_total_words( $this->calculate_in_lang( null ) ); } else { $word_count->set_words_to_translate( $lang, $this->calculate_in_lang( $lang ) ); } } $this->records->set_post_word_count( $post_id, $word_count ); } /** * @param string|null $lang * * @return int */ private function calculate_in_lang( $lang ) { $words = 0; foreach ( $this->post_calculators as $calculator ) { $words += $calculator->count_words( $this->post_element, $lang ); } return $words; } } words-count/processor/wpml-tm-word-count-setters-factory.php 0000755 00000002145 14720342453 0020421 0 ustar 00 <?php class WPML_TM_Word_Count_Setters_Factory { /** * @return IWPML_TM_Word_Count_Set[] */ public function create() { global $sitepress; $records_factory = new WPML_TM_Word_Count_Records_Factory(); $records = $records_factory->create(); $calculator = new WPML_TM_Word_Calculator( new WPML_PHP_Functions() ); $active_langs = array_keys( $sitepress->get_active_languages() ); $post_calculators = array( new WPML_TM_Word_Calculator_Post_Object( $calculator, new WPML_TM_Word_Calculator_Post_Packages( $records ) ), new WPML_TM_Word_Calculator_Post_Custom_Fields( $calculator, \WPML\TM\Settings\Repository::getCustomFields() ), ); $setters = array( 'post' => new WPML_TM_Word_Count_Set_Post( new WPML_Translation_Element_Factory( $sitepress ), $records, $post_calculators, $active_langs ), ); if ( class_exists( 'WPML_ST_Package_Factory' ) ) { $setters['string'] = new WPML_TM_Word_Count_Set_String( $records, $calculator ); $setters['package'] = new WPML_TM_Word_Count_Set_Package( new WPML_ST_Package_Factory(), $records, $active_langs ); } return $setters; } } words-count/class-wpml-tm-post.php 0000755 00000002421 14720342453 0013232 0 ustar 00 <?php class WPML_TM_Post extends WPML_TM_Translatable_Element { /** @var array|null|WP_Post */ private $wp_post; protected function init( $id ) { $this->wp_post = get_post( $id ); } protected function get_type() { return 'post'; } protected function get_total_words() { return $this->word_count_records->get_post_word_count( $this->id )->get_total_words(); } public function get_type_name( $label = null ) { $post_type = $this->wp_post->post_type; $post_type_label = ucfirst( $post_type ); $post_type_object = get_post_type_object( $post_type ); if ( isset( $post_type_object ) ) { $post_type_object_item = $post_type_object; $temp_post_type_label = ''; if ( isset( $post_type_object_item->labels->$label ) ) { $temp_post_type_label = $post_type_object_item->labels->$label; } if ( trim( $temp_post_type_label ) == '' ) { if ( isset( $post_type_object_item->labels->singular_name ) ) { $temp_post_type_label = $post_type_object_item->labels->singular_name; } elseif ( $label && $post_type_object_item->labels->name ) { $temp_post_type_label = $post_type_object_item->labels->name; } } if ( trim( $temp_post_type_label ) != '' ) { $post_type_label = $temp_post_type_label; } } return $post_type_label; } } words-count/hooks/wpml-tm-word-count-hooks-factory.php 0000755 00000007062 14720342453 0017162 0 ustar 00 <?php class WPML_TM_Word_Count_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { const PROCESS_PENDING = 'pending'; const PROCESS_IN_PROGRESS = 'in-progress'; const PROCESS_COMPLETED = 'completed'; const OPTION_KEY_REQUESTED_TYPES_STATUS = 'wpml_word_count_requested_types_status'; const NONCE_ACTION = 'wpml_tm_word_count_ajax'; private $hooks = array(); private $requested_types_status; private $translation_element_factory; private $words_count_background_process_factory; private $words_count_single_process_factory; public function create() { $this->requested_types_status = get_option( self::OPTION_KEY_REQUESTED_TYPES_STATUS, false ); $this->add_refresh_hooks(); $this->add_process_hooks(); $this->add_admin_hooks(); $this->add_ajax_hooks(); return $this->hooks; } private function add_refresh_hooks() { if ( $this->is_heartbeat_autosave() ) { return; } $this->hooks['refresh'] = new WPML_TM_Word_Count_Refresh_Hooks( $this->get_words_count_single_process_factory(), $this->get_translation_element_factory(), class_exists( 'WPML_ST_Package_Factory' ) ? new WPML_ST_Package_Factory() : null ); } private function add_process_hooks() { if ( $this->requested_types_status === self::PROCESS_IN_PROGRESS ) { $this->hooks['process'] = new WPML_TM_Word_Count_Process_Hooks( $this->get_words_count_background_process_factory() ); } } private function add_admin_hooks() { if ( is_admin() ) { $this->hooks['admin'] = new WPML_TM_Word_Count_Admin_Hooks( new WPML_WP_API() ); } } private function add_ajax_hooks() { if ( isset( $_POST['module'] ) && 'wpml_word_count' === $_POST['module'] && wpml_is_ajax() ) { $report_view = new WPML_TM_Word_Count_Report_View( new WPML_Twig_Template_Loader( array( WPML_TM_PATH . WPML_TM_Word_Count_Report_View::TEMPLATE_PATH ) ), new WPML_WP_Cron_Check( new WPML_PHP_Functions() ) ); $records_factory = new WPML_TM_Word_Count_Records_Factory(); $report = new WPML_TM_Word_Count_Report( $report_view, $records_factory->create(), $this->get_sitepress(), $this->requested_types_status, class_exists( 'WPML_Package_Helper' ) ? new WPML_Package_Helper() : null ); $this->hooks['ajax'] = new WPML_TM_Word_Count_Ajax_Hooks( $report, $this->get_words_count_background_process_factory(), $this->requested_types_status ); } } /** * @return WPML_TM_Word_Count_Single_Process_Factory */ private function get_words_count_single_process_factory() { if ( ! $this->words_count_single_process_factory ) { $this->words_count_single_process_factory = new WPML_TM_Word_Count_Single_Process_Factory(); } return $this->words_count_single_process_factory; } private function get_translation_element_factory() { if ( ! $this->translation_element_factory ) { $this->translation_element_factory = new WPML_Translation_Element_Factory( $this->get_sitepress() ); } return $this->translation_element_factory; } /** * @return WPML_TM_Word_Count_Background_Process_Factory */ private function get_words_count_background_process_factory() { if ( ! $this->words_count_background_process_factory ) { $this->words_count_background_process_factory = new WPML_TM_Word_Count_Background_Process_Factory(); } return $this->words_count_background_process_factory; } /** * @return SitePress */ private function get_sitepress() { global $sitepress; return $sitepress; } private function is_heartbeat_autosave() { return isset( $_POST['data']['wp_autosave'] ); } } words-count/hooks/wpml-tm-word-count-refresh-hooks.php 0000755 00000006430 14720342453 0017147 0 ustar 00 <?php use WPML\FP\Fns; class WPML_TM_Word_Count_Refresh_Hooks implements IWPML_Action { const PRIORITY_AFTER_PB_STRING_REGISTRATION = 50; /** @var WPML_TM_Word_Count_Single_Process_Factory $single_process_factory */ private $single_process_factory; /** @var WPML_Translation_Element_Factory $element_factory */ private $element_factory; /** @var WPML_ST_Package_Factory|null $st_package_factory */ private $st_package_factory; /** @var array $packages_to_refresh */ private $packages_to_refresh = array(); public function __construct( WPML_TM_Word_Count_Single_Process_Factory $single_process_factory, WPML_Translation_Element_Factory $element_factory, WPML_ST_Package_Factory $st_package_factory = null ) { $this->single_process_factory = $single_process_factory; $this->element_factory = $element_factory; $this->st_package_factory = $st_package_factory; } public function add_hooks() { add_action( 'save_post', Fns::memorize( array( $this, 'enqueue_post_word_count_refresh' ) ) ); if ( $this->st_package_factory ) { add_action( 'wpml_register_string', array( $this, 'register_string_action' ), 10, 3 ); add_action( 'wpml_set_translated_strings', array( $this, 'set_translated_strings_action' ), 10, 2 ); add_action( 'wpml_delete_unused_package_strings', array( $this, 'delete_unused_package_strings_action' ) ); } } /** @param int $post_id */ public function enqueue_post_word_count_refresh( $post_id ) { add_action( 'shutdown', $this->get_refresh_post_word_count( $post_id ), self::PRIORITY_AFTER_PB_STRING_REGISTRATION ); } /** @param int $post_id */ public function get_refresh_post_word_count( $post_id ) { return function() use ( $post_id ) { $post_element = $this->element_factory->create( $post_id, 'post' ); if ( ! $post_element->is_translatable() ) { return; } if ( $post_element->get_source_element() ) { $post_id = $post_element->get_source_element()->get_id(); } $this->single_process_factory->create()->process( 'post', $post_id ); }; } /** * @param string $string_value * @param string $string_name * @param array $package */ public function register_string_action( $string_value, $string_name, $package ) { $this->defer_package_refresh( $package ); } /** @param array $package */ public function delete_unused_package_strings_action( $package ) { $this->defer_package_refresh( $package ); } /** * @param array $translations * @param array $package */ public function set_translated_strings_action( $translations, $package ) { $this->defer_package_refresh( $package ); } /** @param array $package_array */ private function defer_package_refresh( $package_array ) { $st_package = $this->st_package_factory->create( $package_array ); if ( $st_package->ID && ! $st_package->post_id && ! in_array( $st_package->ID, $this->packages_to_refresh, true ) ) { $this->packages_to_refresh[] = $st_package->ID; if ( ! has_action( 'shutdown', array( $this, 'refresh_packages_word_count' ) ) ) { add_action( 'shutdown', array( $this, 'refresh_packages_word_count' ) ); } } } public function refresh_packages_word_count() { foreach ( $this->packages_to_refresh as $package_to_refresh ) { $this->single_process_factory->create()->process( 'package', $package_to_refresh ); } } } words-count/hooks/wpml-tm-word-count-ajax-hooks.php 0000755 00000003664 14720342453 0016442 0 ustar 00 <?php class WPML_TM_Word_Count_Ajax_Hooks implements IWPML_Action { /** @var WPML_TM_Word_Count_Report $report */ private $report; /** @var WPML_TM_Word_Count_Background_Process_Factory $process_factory*/ private $process_factory; /** @var bool $requested_types_status */ private $requested_types_status; public function __construct( WPML_TM_Word_Count_Report $report, WPML_TM_Word_Count_Background_Process_Factory $process_factory, $requested_types_status ) { $this->report = $report; $this->process_factory = $process_factory; $this->requested_types_status = $requested_types_status; } public function add_hooks() { add_action( 'wp_ajax_wpml_word_count_get_report', array( $this, 'get_report' ) ); add_action( 'wp_ajax_wpml_word_count_start_count', array( $this, 'start_count' ) ); add_action( 'wp_ajax_wpml_word_count_cancel_count', array( $this, 'cancel_count' ) ); } public function __call( $method_name, $arguments ) { check_admin_referer( WPML_TM_Word_Count_Hooks_Factory::NONCE_ACTION, 'nonce' ); $this->{$method_name}(); wp_send_json_success(); } private function get_report() { $data = array( 'status' => $this->requested_types_status, 'report' => $this->report->render(), ); wp_send_json_success( $data ); } private function start_count() { $requested_types = array(); foreach ( array( 'post_types', 'package_kinds' ) as $group ) { $requested_types[ $group ] = isset( $_POST['requested_types'][ $group ] ) ? filter_var_array( $_POST['requested_types'][ $group ] ) : array(); } $requested_types_process = $this->process_factory->create_requested_types(); $this->report->set_requested_types( $requested_types ); $requested_types_process->init( $requested_types ); } private function cancel_count() { update_option( WPML_TM_Word_Count_Hooks_Factory::OPTION_KEY_REQUESTED_TYPES_STATUS, WPML_TM_Word_Count_Hooks_Factory::PROCESS_COMPLETED ); } } words-count/hooks/wpml-tm-word-count-admin-hooks.php 0000755 00000002754 14720342453 0016606 0 ustar 00 <?php class WPML_TM_Word_Count_Admin_Hooks implements IWPML_Action { /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_WP_API $wp_api ) { $this->wp_api = $wp_api; } public function add_hooks() { if ( $this->wp_api->is_dashboard_tab() ) { add_action( 'wpml_tm_dashboard_word_count_estimation', array( $this, 'display_dialog_open_link' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } add_filter( 'wpml_words_count_url', array( $this, 'words_count_url_filter' ) ); } public function enqueue_scripts() { wp_enqueue_script( 'word-count-report', WPML_TM_URL . '/dist/js/word-count/app.js', array( 'jquery-ui-dialog' ), ICL_SITEPRESS_VERSION ); } /** * @param string $default_url * * @return string */ public function words_count_url_filter( $default_url ) { return $this->wp_api->get_tm_url( 'dashboard', '#words-count' ); } public function display_dialog_open_link() { echo '<a href="#" class="js-word-count-dialog-open" data-nonce="' . wp_create_nonce( WPML_TM_Word_Count_Hooks_Factory::NONCE_ACTION ) . '" data-dialog-title="' . esc_attr__( 'Word count estimation', 'wpml-translation-management' ) . '" data-cancel="' . esc_attr__( 'Cancel', 'wpml-translation-management' ) . '" data-loading-text="' . esc_attr__( 'Initializing...', 'wpml-translation-management' ) . '">' . esc_html__( 'Word count for the entire site', 'wpml-translation-management' ) . '</a>'; } } words-count/hooks/wpml-tm-word-count-process-hooks.php 0000755 00000001104 14720342453 0017160 0 ustar 00 <?php class WPML_TM_Word_Count_Process_Hooks implements IWPML_Action { /** @var WPML_TM_Word_Count_Background_Process_Factory $process_factory */ private $process_factory; /** * @param WPML_TM_Word_Count_Background_Process_Factory $process_factory */ public function __construct( WPML_TM_Word_Count_Background_Process_Factory $process_factory ) { $this->process_factory = $process_factory; } /** * We need to include the hooks located in WP_Async_Request::__construct. */ public function add_hooks() { $this->process_factory->create_requested_types(); } } words-count/records/wpml-tm-word-count-records-factory.php 0000755 00000000655 14720342453 0020017 0 ustar 00 <?php class WPML_TM_Word_Count_Records_Factory { /** * @return \WPML_TM_Word_Count_Records * @throws \Auryn\InjectionException */ public function create() { return \WPML\Container\make( '\WPML_TM_Word_Count_Records', [ ':package_records' => \WPML\Container\make( '\WPML_ST_Word_Count_Package_Records' ), ':string_records' => \WPML\Container\make( '\WPML_ST_Word_Count_String_Records' ), ] ); } } words-count/records/wpml-tm-word-count-records.php 0000755 00000015410 14720342453 0016345 0 ustar 00 <?php class WPML_TM_Word_Count_Records { /** @var WPML_TM_Word_Count_Post_Records $post_records */ private $post_records; /** @var WPML_ST_Word_Count_Package_Records|null $package_records */ private $package_records; /** @var WPML_ST_Word_Count_String_Records|null $string_records */ private $string_records; public function __construct( WPML_TM_Word_Count_Post_Records $post_records, WPML_ST_Word_Count_Package_Records $package_records = null, WPML_ST_Word_Count_String_Records $string_records = null ) { $this->post_records = $post_records; $this->package_records = $package_records; $this->string_records = $string_records; } /** @return array */ public function get_all_post_ids_without_word_count() { return $this->post_records->get_all_ids_without_word_count(); } /** * @param $post_id * * @return WPML_TM_Count */ public function get_post_word_count( $post_id ) { return new WPML_TM_Count( $this->post_records->get_word_count( $post_id ) ); } /** * @param int $post_id * @param WPML_TM_Count $word_count * * @return bool|int */ public function set_post_word_count( $post_id, WPML_TM_Count $word_count ) { return $this->post_records->set_word_count( $post_id, $word_count->to_string() ); } /** @return array */ public function get_all_package_ids() { if ( $this->package_records ) { return $this->package_records->get_all_package_ids(); } return array(); } /** @return array */ public function get_packages_ids_without_word_count() { if ( $this->package_records ) { return $this->package_records->get_packages_ids_without_word_count(); } return array(); } /** * @param int $post_id * * @return WPML_TM_Count[] */ public function get_packages_word_counts( $post_id ) { $counts = array(); if ( $this->package_records ) { $raw_counts = $this->package_records->get_word_counts( $post_id ); foreach ( $raw_counts as $raw_count ) { $counts[] = new WPML_TM_Count( $raw_count ); } } return $counts; } /** * @param int $package_id * @param WPML_TM_Count $word_count */ public function set_package_word_count( $package_id, WPML_TM_Count $word_count ) { if ( $this->package_records ) { $this->package_records->set_word_count( $package_id, $word_count->to_string() ); } } /** * @param int $package_id * * @return WPML_TM_Count */ public function get_package_word_count( $package_id ) { if ( $this->package_records ) { return new WPML_TM_Count( $this->package_records->get_word_count( $package_id ) ); } return new WPML_TM_Count(); } /** @return int */ public function get_strings_total_words() { if ( $this->string_records ) { return $this->string_records->get_total_words(); } return 0; } /** @return array */ public function get_all_string_values_without_word_count() { if ( $this->string_records ) { return $this->string_records->get_all_values_without_word_count(); } return array(); } /** * @param string $lang * @param int|null $package_id * * @return int */ public function get_string_words_to_translate_per_lang( $lang, $package_id = null ) { if ( $this->string_records ) { return $this->string_records->get_words_to_translate_per_lang( $lang, $package_id ); } return 0; } public function get_string_value_and_language( $string_id ) { if ( $this->string_records ) { return $this->string_records->get_value_and_language( $string_id ); } return (object) array( 'value' => null, 'language' => null, ); } /** * @param int $id * @param int $word_count */ public function set_string_word_count( $id, $word_count ) { if ( $this->string_records ) { $this->string_records->set_word_count( $id, $word_count ); } } /** * @param int $id * * @return int */ public function get_string_word_count( $id ) { if ( $this->string_records ) { return $this->string_records->get_word_count( $id ); } return 0; } public function reset_all( array $requested_types ) { if ( $this->package_records && isset( $requested_types['package_kinds'] ) ) { $this->package_records->reset_all( $requested_types['package_kinds'] ); } if ( isset( $requested_types['post_types'] ) ) { $this->post_records->reset_all( $requested_types['post_types'] ); } } /** * @param array $kinds * * @return array */ public function get_package_ids_from_kind_slugs( array $kinds ) { if ( $this->package_records ) { return $this->package_records->get_ids_from_kind_slugs( $kinds ); } return array(); } /** * @param array $post_types * * @return array */ public function get_package_ids_from_post_types( array $post_types ) { if ( $this->package_records ) { return $this->package_records->get_ids_from_post_types( $post_types ); } return array(); } /** * @param array $package_ids * * @return array */ public function get_strings_ids_from_package_ids( array $package_ids ) { if ( $this->string_records ) { return $this->string_records->get_ids_from_package_ids( $package_ids ); } return array(); } /** * @param array $package_ids * * @return array */ public function get_post_source_ids_from_types( array $package_ids ) { return $this->post_records->get_source_ids_from_types( $package_ids ); } /** * @param string $type * * @return int */ public function count_items_by_type( $group, $type ) { if ( $this->package_records && 'package_kinds' === $group ) { return $this->package_records->count_items_by_kind_not_part_of_posts( $type ); } elseif ( 'post_types' === $group ) { return $this->post_records->count_source_items_by_type( $type ); } return 0; } public function count_word_counts_by_type( $group, $type ) { if ( $this->package_records && 'package_kinds' === $group ) { return $this->package_records->count_word_counts_by_kind( $type ); } elseif ( 'post_types' === $group ) { return $this->post_records->count_word_counts_by_type( $type ); } return 0; } public function get_word_counts_by_type( $group, $type ) { if ( $this->package_records && 'package_kinds' === $group ) { $counts = $this->package_records->get_word_counts_by_kind( $type ); return $this->build_count_composite_from_raw_counts( $counts ); } elseif ( 'post_types' === $group ) { $counts = $this->post_records->get_word_counts_by_type( $type ); return $this->build_count_composite_from_raw_counts( $counts ); } return new WPML_TM_Count_Composite(); } /** * @param array $raw_counts * * @return WPML_TM_Count_Composite */ private function build_count_composite_from_raw_counts( array $raw_counts ) { $count_composite = new WPML_TM_Count_Composite(); foreach ( $raw_counts as $raw_count ) { $count = new WPML_TM_Count( $raw_count ); $count_composite->add_count( $count ); } return $count_composite; } } words-count/records/wpml-tm-word-count-post-records.php 0000755 00000006652 14720342453 0017340 0 ustar 00 <?php class WPML_TM_Word_Count_Post_Records { const META_KEY = '_wpml_word_count'; /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * Returns only IDs in the source language * * @return array */ public function get_all_ids_without_word_count() { $query = " SELECT ID FROM {$this->wpdb->posts} AS p LEFT JOIN {$this->wpdb->prefix}icl_translations AS t ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type) WHERE t.source_language_code IS NULL AND t.element_type IS NOT NULL AND p.ID NOT IN( SELECT post_id FROM {$this->wpdb->postmeta} WHERE meta_key = '" . self::META_KEY . "' ); "; return array_map( 'intval', $this->wpdb->get_col( $query ) ); } /** * @param int $post_id * * @return string raw word count */ public function get_word_count( $post_id ) { return get_post_meta( $post_id, self::META_KEY, true ); } /** * @param int $post_id * @param string $word_count raw word count * * @return bool|int */ public function set_word_count( $post_id, $word_count ) { return update_post_meta( $post_id, self::META_KEY, $word_count ); } public function reset_all( array $post_types ) { if ( ! $post_types ) { return; } $query = "DELETE pm FROM {$this->wpdb->postmeta} AS pm INNER JOIN {$this->wpdb->posts} AS p ON p.ID = pm.post_id WHERE pm.meta_key = %s AND p.post_type IN(" . wpml_prepare_in( $post_types ) . ")"; $this->wpdb->query( $this->wpdb->prepare( $query, self::META_KEY ) ); } /** * @param array $post_types * * @return array */ public function get_source_ids_from_types( array $post_types ) { $query = "SELECT ID FROM {$this->wpdb->posts} AS p LEFT JOIN {$this->wpdb->prefix}icl_translations AS t ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type) WHERE t.source_language_code IS NULL AND t.language_code IS NOT NULL AND p.post_type IN (" . wpml_prepare_in( $post_types ) . ")"; return array_map( 'intval', $this->wpdb->get_col( $query ) ); } /** * @param string $post_type * * @return int */ public function count_source_items_by_type( $post_type ) { $query = "SELECT COUNT(*) FROM {$this->wpdb->posts} AS p LEFT JOIN {$this->wpdb->prefix}icl_translations AS t ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type) WHERE t.source_language_code IS NULL AND t.element_type = %s AND p.post_status <> 'trash'"; return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, 'post_' . $post_type ) ); } public function count_word_counts_by_type( $post_type ) { $query = "SELECT COUNT(*) FROM {$this->wpdb->postmeta} AS pm LEFT JOIN {$this->wpdb->posts} AS p ON p.ID = pm.post_id WHERE pm.meta_key = '" . self::META_KEY . "' AND p.post_type = %s AND p.post_status <> 'trash'"; return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $post_type ) ); } /** * @param string $post_type * * @return array */ public function get_word_counts_by_type( $post_type ) { $query = "SELECT meta_value FROM {$this->wpdb->postmeta} AS pm LEFT JOIN {$this->wpdb->posts} AS p ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = %s AND p.post_status <> 'trash'"; return $this->wpdb->get_col( $this->wpdb->prepare( $query, $post_type, self::META_KEY ) ); } } class-wpml-tm-loader.php 0000755 00000001246 14720342453 0011233 0 ustar 00 <?php class WPML_TM_Loader { /** * Sets up the XLIFF class handling the frontend xliff related hooks * and rendering */ public function load_xliff_frontend() { setup_xliff_frontend(); } /** * Wrapper for \tm_after_load() */ public function tm_after_load() { tm_after_load(); } /** * @param WPML_WP_API $wpml_wp_api */ public function load_pro_translation( $wpml_wp_api ) { global $ICL_Pro_Translation; if ( ! isset( $ICL_Pro_Translation ) && ( $wpml_wp_api->is_admin() || defined( 'XMLRPC_REQUEST' ) ) ) { $job_factory = wpml_tm_load_job_factory(); $ICL_Pro_Translation = new WPML_Pro_Translation( $job_factory ); } } } languages/class-wpml-language-collection.php 0000755 00000001701 14720342453 0015225 0 ustar 00 <?php class WPML_Language_Collection { /** @var SitePress $sitepress */ private $sitepress; /** @var array $languages */ private $languages = array(); /** * WPML_Language_Collection constructor. * * @param SitePress $sitepress * @param array $initial_languages Array of language codes */ public function __construct( SitePress $sitepress, $initial_languages = array() ) { $this->sitepress = $sitepress; foreach ( $initial_languages as $lang ) { $this->add( $lang ); } } public function add( $code ) { if ( ! isset( $this->languages[ $code ] ) ) { $language = new WPML_Language( $this->sitepress, $code ); if ( $language->is_valid() ) { $this->languages[ $code ] = $language; } } } public function get( $code ) { if ( ! isset( $this->languages[ $code ] ) ) { $this->add( $code ); } return $this->languages[ $code ]; } public function get_codes() { return array_keys( $this->languages ); } } languages/interface-iwpml-current-language.php 0000755 00000000245 14720342453 0015562 0 ustar 00 <?php interface IWPML_Current_Language { public function get_current_language(); public function get_default_language(); public function get_admin_language(); } languages/class-wpml-languages-ajax.php 0000755 00000010572 14720342453 0014206 0 ustar 00 <?php use WPML\API\Sanitize; /** * @author OnTheGo Systems */ class WPML_Languages_AJAX { private $sitepress; private $default_language; /** * WPML_Languages_AJAX constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $this->default_language = $this->sitepress->get_default_language(); } public function ajax_hooks() { add_action( 'wp_ajax_wpml_set_active_languages', array( $this, 'set_active_languages_action' ) ); add_action( 'wp_ajax_wpml_set_default_language', array( $this, 'set_default_language_action' ) ); } private function validate_ajax_action() { $action = Sanitize::stringProp( 'action', $_POST ); $nonce = Sanitize::stringProp( 'nonce', $_POST ); return $action && $nonce && wp_verify_nonce( $nonce, $action ); } public function set_active_languages_action() { $failed = true; $response = array(); if ( $this->validate_ajax_action() ) { $old_active_languages = $this->sitepress->get_active_languages(); $old_active_languages_count = count( (array) $old_active_languages ); $lang_codes = filter_var( $_POST['languages'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY ); $setup_instance = wpml_get_setup_instance(); if ( $lang_codes && $setup_instance->set_active_languages( $lang_codes ) ) { $active_languages = $this->sitepress->get_active_languages(); $html_response = ''; foreach ( (array) $active_languages as $lang ) { $is_default = ( $this->default_language === $lang['code'] ); $html_response .= '<li '; if ( $is_default ) { $html_response .= 'class="default_language"'; } $html_response .= '><label><input type="radio" name="default_language" value="' . $lang['code'] . '" '; if ( $is_default ) { $html_response .= 'checked="checked"'; } $html_response .= '>' . $lang['display_name']; if ( $is_default ) { $html_response .= ' (' . __( 'default', 'sitepress' ) . ')'; } $html_response .= '</label></li>'; $response['enabledLanguages'] = $html_response; } $response['noLanguages'] = 1; if ( ( count( $lang_codes ) > 1 ) || ( $old_active_languages_count > 1 && count( $lang_codes ) < 2 ) ) { $response['noLanguages'] = 0; } $updated_active_languages = $this->sitepress->get_active_languages(); if ( $updated_active_languages ) { $wpml_localization = new WPML_Download_Localization( $updated_active_languages, $this->default_language ); $wpml_localization->download_language_packs(); $wpml_languages_notices = new WPML_Languages_Notices( wpml_get_admin_notices() ); $wpml_languages_notices->maybe_create_notice_missing_menu_items( count( $lang_codes ) ); $wpml_languages_notices->missing_languages( $wpml_localization->get_not_founds() ); if ( \WPML\Setup\Option::isTMAllowed() && $this->sitepress->is_setup_complete() ) { WPML_TM_Translation_Priorities::insert_missing_default_terms(); } } $failed = false; } icl_cache_clear(); /** @deprecated Use `wpml_update_active_languages` instead */ do_action( 'icl_update_active_languages' ); do_action( 'wpml_update_active_languages', $old_active_languages ); } if ( $failed ) { wp_send_json_error( $response ); } else { wp_send_json_success( $response ); } } public function set_default_language_action() { $failed = true; $response = array(); if ( $this->validate_ajax_action() ) { $previous_default = $this->default_language; $new_default_language = filter_var( $_POST['language'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $active_languages = $this->sitepress->get_active_languages(); $active_languages_codes = array_keys( $active_languages ); if ( $new_default_language && in_array( $new_default_language, $active_languages_codes, true ) ) { $status = $this->sitepress->set_default_language( $new_default_language ); if ( $status ) { $response['previousLanguage'] = $previous_default; $failed = false; } if ( 1 === $status ) { $response['message'] = __( 'WordPress language file (.mo) is missing. Keeping existing display language.', 'sitepress' ); } } } if ( $failed ) { wp_send_json_error( $response ); } else { wp_send_json_success( $response ); } } } languages/class-wpml-language-records.php 0000755 00000003034 14720342453 0014534 0 ustar 00 <?php class WPML_Language_Records { private $wpdb; private $languages; /** @var null|array $locale_lang_map */ private $locale_lang_map; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function is_valid( $code ) { if ( ! $this->languages ) { $this->load(); } return in_array( $code, $this->languages ); } private function load() { $this->languages = $this->wpdb->get_col( "SELECT code FROM {$this->get_table()}" ); } /** * @param string $lang_code * * @return string|null */ public function get_locale( $lang_code ) { $this->init_locale_lang_map(); $locale = array_search( $lang_code, $this->locale_lang_map, true ); return $locale ? $locale : null; } /** * @param string $locale * * @return string|null */ public function get_language_code( $locale ) { $this->init_locale_lang_map(); return isset( $this->locale_lang_map[ $locale ] ) ? $this->locale_lang_map[ $locale ] : null; } private function init_locale_lang_map() { if ( null === $this->locale_lang_map ) { $this->locale_lang_map = array(); $sql = "SELECT default_locale, code FROM {$this->get_table()}"; $rowset = $this->wpdb->get_results( $sql ); foreach ( $rowset as $row ) { $this->locale_lang_map[ $row->default_locale ?: $row->code ] = $row->code; } } } /** * @return array */ public function get_locale_lang_map() { $this->init_locale_lang_map(); return $this->locale_lang_map; } private function get_table() { return $this->wpdb->prefix . 'icl_languages'; } } languages/UI.php 0000755 00000005150 14720342453 0007550 0 ustar 00 <?php namespace WPML\Languages; use WPML\API\PostTypes; use WPML\Core\WP\App\Resources; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\PostType; use WPML\Posts\CountPerPostType; use WPML\Posts\DeleteTranslatedContentOfLanguages; use WPML\Posts\UntranslatedCount; use WPML\Setup\Option; use WPML\TM\ATE\AutoTranslate\Endpoint\ActivateLanguage; use WPML\TM\ATE\AutoTranslate\Endpoint\CheckLanguageSupport; use WPML\UIPage; use function WPML\FP\partial; class UI implements \IWPML_Backend_Action { public function add_hooks() { if ( UIPage::isLanguages( $_GET ) ) { if ( self::isEditLanguagePage() ) { Hooks::onAction( 'admin_enqueue_scripts' ) ->then( Fns::unary( partial( [ self::class, 'getData' ], true ) ) ) ->then( Resources::enqueueApp( 'languages' ) ); } else { Hooks::onAction( 'admin_enqueue_scripts' ) ->then( Fns::unary( partial( [ self::class, 'getData' ], false ) ) ) ->then( Resources::enqueueApp( 'languages' ) ); } } } public static function getData( $editPage ) { $getPostTypeName = function ( $postType ) { return PostType::getPluralName( $postType )->getOrElse( $postType ); }; $data = [ 'endpoints' => [ 'checkSupportOfLanguages' => CheckLanguageSupport::class, 'skipTranslationOfExistingContent' => ActivateLanguage::class, 'postsToTranslatePerTypeCount' => CountPerPostType::class, 'deleteTranslatedContentOfLanguage' => DeleteTranslatedContentOfLanguages::class ], 'postTypes' => Fns::map( $getPostTypeName, PostTypes::getAutomaticTranslatable() ), 'shouldTranslateEverything' => Option::shouldTranslateEverything(), ]; if ( $editPage ) { $existingLanguages = Obj::values( Fns::map( function ( $language ) { return Lst::concat( $language, [ 'mapping' => [ 'targetId' => Obj::pathOr( '', [ 'mapping', 'targetId' ], $language ), 'targetCode' => Obj::pathOr( '', [ 'mapping', 'targetCode' ], $language ), ] ] ); }, \SitePress_EditLanguages::get_active_languages() ) ); $data = Lst::concat( $data, [ 'existingLanguages' => $existingLanguages ] ); } else { $data = Lst::concat( $data, [ 'existingLangs' => Languages::getSecondaryCodes(), 'defaultLang' => Languages::getDefaultCode(), 'settingsUrl' => admin_url( UIPage::getSettings() ), ] ); } return [ 'name' => 'wpmlLanguagesUI', 'data' => $data ]; } private static function isEditLanguagePage() { return (int) Obj::prop( 'trop', $_GET ) === 1; } } languages/class-wpml-language.php 0000755 00000001251 14720342453 0013074 0 ustar 00 <?php class WPML_Language { /** @var SitePress $sitepress */ private $sitepress; /** @var string $code */ private $code; private $lang_details; public function __construct( SitePress $sitepress, $code ) { $this->sitepress = $sitepress; $this->code = $code; $this->lang_details = $sitepress->get_language_details( $code ); } public function is_valid() { return (bool) $this->lang_details; } public function get_code() { return $this->lang_details['code']; } public function get_display_name() { return $this->lang_details['display_name']; } public function get_flag_url() { return $this->sitepress->get_flag_url( $this->code ); } } support/class-wpml-tm-support-info.php 0000755 00000000252 14720342453 0014142 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Support_Info { public function is_simplexml_extension_loaded() { return extension_loaded( 'simplexml' ); } } support/ATE/Hooks.php 0000755 00000000740 14720342453 0010475 0 ustar 00 <?php namespace WPML\Support\ATE; class Hooks implements \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var ViewFactory $viewFactory */ private $viewFactory; public function __construct( ViewFactory $viewFactory ) { $this->viewFactory = $viewFactory; } public function add_hooks() { add_action( 'wpml_support_page_after', [ $this, 'renderSupportSection' ] ); } public function renderSupportSection() { $this->viewFactory->create()->renderSupportSection(); } } support/ATE/ViewFactory.php 0000755 00000000474 14720342453 0011660 0 ustar 00 <?php namespace WPML\Support\ATE; use WPML\TM\ATE\ClonedSites\SecondaryDomains; use function WPML\Container\make; use WPML\TM\ATE\Log\Storage; class ViewFactory { public function create() { $logCount = make( Storage::class )->getCount(); return new View( $logCount, make( SecondaryDomains::class ) ); } } support/ATE/View.php 0000755 00000002556 14720342453 0010333 0 ustar 00 <?php namespace WPML\Support\ATE; use WPML\TM\ATE\ClonedSites\SecondaryDomains; use WPML\TM\ATE\Log\Hooks; class View { /** @var int */ private $logCount; /** @var SecondaryDomains */ private $secondaryDomains; public function __construct( int $logCount, SecondaryDomains $secondaryDomains ) { $this->logCount = $logCount; $this->secondaryDomains = $secondaryDomains; } public function renderSupportSection() { ?> <div class="wrap"> <h2 id="ate-log"> <?php esc_html_e( 'Advanced Translation Editor', 'wpml-translation-management' ); ?> </h2> <p> <a href="<?php echo admin_url( 'admin.php?page=' . Hooks::SUBMENU_HANDLE ); ?>"> <?php echo sprintf( esc_html__( 'Error Logs (%d)', 'wpml-translation-management' ), $this->logCount ); ?> </a> </p> <?php $secondaryDomains = $this->secondaryDomains->getInfo(); if ( $secondaryDomains ) { ?> <div id="wpml-support-ate-alias-domains"> <strong> <?php printf( __( 'Alias domains to %s domain:', 'sitepress-multilingual-cms' ), $secondaryDomains['originalSiteUrl'] ) ?> </strong> <ul style="list-style: square; padding-left: 15px;"> <?php foreach ( $secondaryDomains['aliasDomains'] as $aliasDomain ) { ?> <li> <?php echo $aliasDomain ?> </li> <?php } ?> </ul> </div> <?php } ?> </div> <?php } } support/class-wpml-tm-support-info-filter.php 0000755 00000002065 14720342453 0015431 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Support_Info_Filter { /** @var WPML_TM_Support_Info */ private $support_info; function __construct( WPML_TM_Support_Info $support_info ) { $this->support_info = $support_info; } /** * @param array $blocks * * @return array */ public function filter_blocks( array $blocks ) { $is_simplexml_extension_loaded = $this->support_info->is_simplexml_extension_loaded(); $blocks['php']['data']['simplexml'] = array( 'label' => __( 'SimpleXML extension', 'wpml-translation-management' ), 'value' => $is_simplexml_extension_loaded ? __( 'Loaded', 'wpml-translation-management' ) : __( 'Not loaded', 'wpml-translation-management' ), 'url' => 'http://php.net/manual/book.simplexml.php', 'messages' => array( __( 'SimpleXML extension is required for using XLIFF files in WPML Translation Management.', 'wpml-translation-management' ) => 'https://wpml.org/home/minimum-requirements/', ), 'is_warning' => ! $is_simplexml_extension_loaded, ); return $blocks; } } support/class-wpml-support-info-ui-factory.php 0000755 00000000706 14720342453 0015610 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Support_Info_UI_Factory { function create() { global $wpdb; $support_info = new WPML_Support_Info( $wpdb ); $template_paths = array( WPML_PLUGIN_PATH . '/templates/support/info/', ); $template_loader = new WPML_Twig_Template_Loader( $template_paths ); $template_service = $template_loader->get_template(); return new WPML_Support_Info_UI( $support_info, $template_service ); } } support/class-wpml-support-info-ui.php 0000755 00000021407 14720342453 0014144 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Support_Info_UI { /** @var WPML_Support_Info */ private $support_info; /** @var IWPML_Template_Service */ private $template_service; function __construct( WPML_Support_Info $support_info, IWPML_Template_Service $template_service ) { $this->support_info = $support_info; $this->template_service = $template_service; } /** * @return string */ public function show() { $model = $this->get_model(); return $this->template_service->show( $model, 'main.twig' ); } /** @return array */ private function get_model() { $minimum_required_memory = '128M'; $minimum_required_php_version = '5.6'; $minimum_recommended_php_version = '7.3'; $minimum_required_wp_version = '3.9.0'; $php_version = $this->support_info->get_php_version(); $php_memory_limit = $this->support_info->get_php_memory_limit(); $memory_usage = $this->support_info->get_memory_usage(); $max_execution_time = $this->support_info->get_max_execution_time(); $max_input_vars = $this->support_info->get_max_input_vars(); $blocks = array( 'php' => array( 'strings' => array( 'title' => __( 'PHP', 'sitepress' ), ), 'data' => array( 'version' => array( 'label' => __( 'Version', 'sitepress' ), 'value' => $php_version, 'url' => 'http://php.net/supported-versions.php', 'messages' => array( sprintf( __( 'PHP %1$s and above are recommended. PHP %2$s is the minimum requirement.', 'sitepress' ), $minimum_recommended_php_version, $minimum_required_php_version ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', __( 'Find how you can update PHP.', 'sitepress' ) => 'http://www.wpupdatephp.com/update/', ), 'is_error' => $this->support_info->is_version_less_than( $minimum_required_php_version, $php_version ), 'is_warning' => $this->support_info->is_version_less_than( $minimum_recommended_php_version, $php_version ), ), 'memory_limit' => array( 'label' => __( 'Memory limit', 'sitepress' ), 'value' => $php_memory_limit, 'url' => 'http://php.net/manual/ini.core.php#ini.memory-limit', 'messages' => array( sprintf( __( 'A memory limit of at least %s is required.', 'sitepress' ), $minimum_required_memory ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => $this->support_info->is_memory_less_than( $minimum_required_memory, $php_memory_limit ), ), 'memory_usage' => array( 'label' => __( 'Memory usage', 'sitepress' ), 'value' => $memory_usage, 'url' => 'http://php.net/memory-get-usage', ), 'max_execution_time' => array( 'label' => __( 'Max execution time', 'sitepress' ), 'value' => $max_execution_time, 'url' => 'http://php.net/manual/info.configuration.php#ini.max-execution-time', ), 'max_input_vars' => array( 'label' => __( 'Max input vars', 'sitepress' ), 'value' => $max_input_vars, 'url' => 'http://php.net/manual/info.configuration.php#ini.max-input-vars', ), 'utf8mb4_charset' => array( 'label' => __( 'Utf8mb4 charset', 'sitepress' ), 'value' => $this->support_info->is_utf8mb4_charset_supported() ? __( 'Yes' ) : __( 'No' ), 'url' => 'https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html', 'messages' => array( __( 'Some features related to String Translations may not work correctly without utf8mb4 character.', 'sitepress' ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => ! $this->support_info->is_utf8mb4_charset_supported(), ) , ), ), 'wp' => array( 'strings' => array( 'title' => __( 'WordPress', 'sitepress' ), ), 'data' => array( 'wp_version' => array( 'label' => __( 'Version', 'sitepress' ), 'value' => $this->support_info->get_wp_version(), 'messages' => array( __( 'WordPress 3.9 or later is required.', 'sitepress' ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => $this->support_info->is_version_less_than( $minimum_required_wp_version, $this->support_info->get_wp_version() ), ), 'multisite' => array( 'label' => __( 'Multisite', 'sitepress' ), 'value' => $this->support_info->get_wp_multisite() ? __( 'Yes' ) : __( 'No' ), ), 'memory_limit' => array( 'label' => __( 'Memory limit', 'sitepress' ), 'value' => $this->support_info->get_wp_memory_limit(), 'url' => 'https://codex.wordpress.org/Editing_wp-config.php#Increasing_memory_allocated_to_PHP', 'messages' => array( __( 'A memory limit of at least 128MB is required.', 'sitepress' ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => $this->support_info->is_memory_less_than( $minimum_required_memory, $this->support_info->get_wp_memory_limit() ), ), 'max_memory_limit' => array( 'label' => __( 'Max memory limit', 'sitepress' ), 'value' => $this->support_info->get_wp_max_memory_limit(), 'url' => 'https://codex.wordpress.org/Editing_wp-config.php#Increasing_memory_allocated_to_PHP', 'messages' => array( __( 'A memory limit of at least 128MB is required.', 'sitepress' ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => $this->support_info->is_memory_less_than( $minimum_required_memory, $this->support_info->get_wp_max_memory_limit() ), ), 'rest_enabled' => array( 'label' => __( 'REST enabled', 'sitepress' ), 'value' => wpml_is_rest_enabled() ? __( 'Yes' ) : __( 'No' ), 'url' => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', 'messages' => array( __( 'REST API is disabled, blocking some features of WPML', 'sitepress' ) => 'https://wpml.org/documentation/support/rest-api-dependencies/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'is_error' => ! wpml_is_rest_enabled(), ), ), ), ); if ( $this->support_info->is_suhosin_active() ) { $blocks['php']['data']['eval_suhosin'] = array( 'label' => __( 'eval() availability from Suhosin', 'sitepress' ), 'value' => $this->support_info->eval_disabled_by_suhosin() ? __( 'Not available', 'sitepress' ) : __( 'Available', 'sitepress' ), 'url' => 'https://suhosin.org/stories/configuration.html#suhosin-executor-disable-eval', 'messages' => array( __( 'The eval() PHP function must be enabled.', 'sitepress' ) => 'https://wpml.org/home/minimum-requirements/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore#eval-usage', ), 'is_error' => $this->support_info->eval_disabled_by_suhosin(), ); } /** * Allows to extend the data shown in the WPML > Support > Info * * This filter is for internal use. * You can add items to the `$blocks` array, however, it is strongly * recommended to not modify existing data. * * You can see how `$block` is structured by scrolling at the beginning of this method. * * The "messages" array can contain just a string (the message) or a string (the message) * and an URL (message linked to that URL). * That is, you can have: * ``` * 'messages' => array( * 'Some message A' => 'https://domain.tld', * 'Some message B' => 'https://domain.tld', * 'Some message C', * ), * ``` * * @since 3.8.0 * * @param array $blocks */ $blocks = apply_filters( 'wpml_support_info_blocks', $blocks ); $this->set_has_messages( $blocks, 'is_error' ); $this->set_has_messages( $blocks, 'is_warning' ); $model = array( 'title' => __( 'Info', 'sitepress' ), 'blocks' => $blocks, ); return $model; } /** * @param array $blocks * @param string $type */ private function set_has_messages( array &$blocks, $type ) { /** * @var string $id * @var array $content */ foreach ( $blocks as $id => $content ) { if ( ! array_key_exists( 'has_messages', $content ) ) { $content['has_messages'] = false; } foreach ( (array) $content['data'] as $key => $item_data ) { if ( array_key_exists( $type, $item_data ) && (bool) $item_data[ $type ] ) { $content['has_messages'] = true; break; } } $blocks[ $id ] = $content; } } } support/class-wpml-support-info.php 0000755 00000004746 14720342453 0013540 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Support_Info { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function is_suhosin_active() { return extension_loaded( 'suhosin' ); } public function eval_disabled_by_suhosin() { return (bool) ini_get( 'suhosin.executor.disable_eval' ); } public function get_max_execution_time() { return ini_get( 'max_execution_time' ); } public function get_max_input_vars() { return ini_get( 'max_input_vars' ); } public function get_php_memory_limit() { return ini_get('memory_limit'); } public function get_memory_usage() { return $this->format_size_units( memory_get_usage() ); } public function get_php_version() { return PHP_VERSION; } public function get_wp_memory_limit() { return constant('WP_MEMORY_LIMIT'); } public function get_wp_max_memory_limit() { return constant('WP_MAX_MEMORY_LIMIT'); } public function get_wp_multisite() { return is_multisite(); } public function get_wp_version() { return $GLOBALS['wp_version']; } public function is_memory_less_than( $reference, $memory ) { if ( (int) $reference === - 1 ) { return false; } $reference_in_bytes = $this->return_bytes( $reference ); $memory_in_bytes = $this->return_bytes( $memory ); return $memory_in_bytes < $reference_in_bytes; } public function is_version_less_than( $reference, $version ) { return version_compare( $version, $reference, '<' ); } public function is_utf8mb4_charset_supported() { return $this->wpdb->has_cap( 'utf8mb4' ); } private function return_bytes( $val ) { $val = trim( $val ); $exponents = array( 'k' => 1, 'm' => 2, 'g' => 3, ); $last = strtolower( substr( $val, - 1 ) ); if ( ! is_numeric( $last ) ) { $val = (int) substr( $val, 0, - 1 ); if ( array_key_exists( $last, $exponents ) ) { $val *= pow( 1024, $exponents[ $last ] ); } } else { $val = (int) $val; } return $val; } private function format_size_units( $bytes ) { if ( $bytes >= 1073741824 ) { $bytes = number_format( $bytes / 1073741824, 2 ) . ' GB'; } elseif ( $bytes >= 1048576 ) { $bytes = number_format( $bytes / 1048576, 2 ) . ' MB'; } elseif ( $bytes >= 1024 ) { $bytes = number_format( $bytes / 1024, 2 ) . ' KB'; } elseif ( $bytes > 1 ) { $bytes .= ' bytes'; } elseif ( $bytes === 1 ) { $bytes .= ' byte'; } else { $bytes = '0 bytes'; } return $bytes; } } class-wpml-browser-redirect.php 0000755 00000010012 14720342453 0012620 0 ustar 00 <?php // adapted from http://wordpress.org/extend/plugins/black-studio-wpml-javascript-redirect/ // thanks to Blank Studio - http://www.blackstudio.it/ class WPML_Browser_Redirect { /** * @var SitePress */ private $sitepress; public function __construct( $sitepress ) { $this->sitepress = $sitepress; } public function init_hooks() { add_action( 'init', array( $this, 'init' ) ); } public function init() { if ( ! isset( $_GET['redirect_to'] ) && ! is_admin() && ! is_customize_preview() && ( ! isset( $_SERVER['REQUEST_URI'] ) || ! preg_match( '#wp-login\.php$#', preg_replace( '@\?(.*)$@', '', $_SERVER['REQUEST_URI'] ) ) ) ) { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } } public function enqueue_scripts() { wp_register_script( 'wpml-browser-redirect', ICL_PLUGIN_URL . '/dist/js/browser-redirect/app.js', array(), ICL_SITEPRESS_VERSION ); $args['skip_missing'] = intval( $this->sitepress->get_setting( 'automatic_redirect' ) == 1 ); // Build multi language urls array $languages = $this->sitepress->get_ls_languages( $args ); $language_urls = []; foreach ( $languages as $language ) { if ( isset( $language['default_locale'] ) && $language['default_locale'] ) { $default_locale = strtolower( $language['default_locale'] ); $language_urls[ $default_locale ] = $language['url']; $language_parts = explode( '_', $default_locale ); if ( count( $language_parts ) > 1 ) { foreach ( $language_parts as $language_part ) { if ( ! isset( $language_urls[ $language_part ] ) ) { $language_urls[ $language_part ] = $language['url']; } } } } $language_urls[ $language['language_code'] ] = $language['url']; } // Cookie parameters $http_host = $_SERVER['HTTP_HOST'] == 'localhost' ? '' : $_SERVER['HTTP_HOST']; $cookie = array( 'name' => '_icl_visitor_lang_js', 'domain' => ( defined( 'COOKIE_DOMAIN' ) && COOKIE_DOMAIN ? COOKIE_DOMAIN : $http_host ), 'path' => ( defined( 'COOKIEPATH' ) && COOKIEPATH ? COOKIEPATH : '/' ), 'expiration' => $this->sitepress->get_setting( 'remember_language' ), ); // Send params to javascript $params = array( 'pageLanguage' => defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : get_bloginfo( 'language' ), 'languageUrls' => $language_urls, 'cookie' => $cookie, ); /** * Filters the data sent to the browser redirection script. * * If ´$param´ is empty or ´$params['pageLanguage']´ or ´$params['languageUrls']´ are not set, the script won't be enqueued. * * @since 4.0.6 * * @param array $params { * Data sent to the script as `wpml_browser_redirect_params` object. * * @type string $pageLanguage The language of the current page. * @type array $languageUrls Associative array where the key is the language code and the value the translated URLs of the current page. * @type array $cookie Associative array containing information to use for creating the cookie. * } */ $params = apply_filters( 'wpml_browser_redirect_language_params', $params ); $enqueue = false; if ( $params && isset( $params['pageLanguage'], $params['languageUrls'] ) ) { wp_localize_script( 'wpml-browser-redirect', 'wpml_browser_redirect_params', $params ); /** * Prevents the `wpml-browser-redirect` from being enqueued. * * If the filter returns as falsy value, the script won't be enqueued. * * @since 4.0.6 * * @param bool $enqueue Defaults to `true` */ $enqueue = apply_filters( 'wpml_enqueue_browser_redirect_language', true ); if ( $enqueue ) { wp_enqueue_script( 'wpml-browser-redirect' ); } } /** * Fires after the browser redirection logic runs, even if the script is not enqueued. * * @since 4.0.6 * * @param bool $enqueue Defaults to `true` * @param array $params @see `wpml_browser_redirect_language_params` */ do_action( 'wpml_enqueued_browser_redirect_language', $enqueue, $params ); } } REST-hooks/class-wpml-rest-posts-hooks-factory.php 0000755 00000000715 14720342453 0016156 0 ustar 00 <?php class WPML_REST_Posts_Hooks_Factory implements IWPML_Deferred_Action_Loader, IWPML_Backend_Action_Loader, IWPML_REST_Action_Loader { public function get_load_action() { if ( wpml_is_rest_request() ) { return WPML_REST_Factory_Loader::REST_API_INIT_ACTION; } return 'plugins_loaded'; } public function create() { global $sitepress, $wpml_term_translations; return new WPML_REST_Posts_Hooks( $sitepress, $wpml_term_translations ); } } REST-hooks/class-wpml-rest-posts-hooks.php 0000755 00000012422 14720342453 0014507 0 ustar 00 <?php use \WPML\FP\Fns; class WPML_REST_Posts_Hooks implements IWPML_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Term_Translation $term_translations */ private $term_translations; public function __construct( SitePress $sitepress, WPML_Term_Translation $term_translations ) { $this->sitepress = $sitepress; $this->term_translations = $term_translations; } public function add_hooks() { $post_types = $this->sitepress->get_translatable_documents(); foreach ( $post_types as $post_type => $post_object ) { add_filter( "rest_prepare_$post_type", array( $this, 'prepare_post' ), 10, 2 ); } add_filter( 'rest_request_before_callbacks', array( $this, 'reload_wpml_post_translation' ), 10, 3 ); } /** * @param WP_REST_Response $response The response object. * @param WP_Post $post Post object. * * @return WP_REST_Response */ public function prepare_post( $response, $post ) { if ( $this->sitepress->get_setting( 'sync_post_taxonomies' ) ) { $response = $this->preset_terms_in_new_translation( $response, $post ); } $response = $this->adjust_sample_links( $response, $post ); return $response; } /** * @param WP_REST_Response $response The response object. * @param WP_Post $post Post object. * * @return WP_REST_Response */ private function preset_terms_in_new_translation( $response, $post ) { if ( ! isset( $_GET['trid'] ) ) { return $response; } $trid = filter_var( $_GET['trid'], FILTER_SANITIZE_NUMBER_INT ); $source_lang = isset( $_GET['source_lang'] ) ? filter_var( $_GET['source_lang'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : $this->sitepress->get_default_language(); $element_type = 'post_' . $post->post_type; if ( $this->sitepress->get_element_trid( $post->ID, $element_type ) ) { return $response; } $translations = $this->sitepress->get_element_translations( $trid, $element_type ); if ( ! isset( $translations[ $source_lang ] ) ) { return $response; } $current_lang = $this->sitepress->get_current_language(); $translatable_taxs = $this->sitepress->get_translatable_taxonomies( true, $post->post_type ); $all_taxs = wp_list_filter( get_object_taxonomies( $post->post_type, 'objects' ), array( 'show_in_rest' => true ) ); $data = $response->get_data(); $this->sitepress->switch_lang( $source_lang ); foreach ( $all_taxs as $tax ) { $tax_rest_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; if ( ! isset( $data[ $tax_rest_base ] ) ) { continue; } $terms = get_the_terms( $translations[ $source_lang ]->element_id, $tax->name ); if ( ! is_array( $terms ) ) { continue; } $term_ids = $this->get_translated_term_ids( $terms, $tax, $translatable_taxs, $current_lang ); wp_set_object_terms( $post->ID, $term_ids, $tax->name ); $data[ $tax_rest_base ] = $term_ids; } $this->sitepress->switch_lang( null ); $response->set_data( $data ); return $response; } /** * @param array $terms * @param stdClass $tax * @param array $translatable_taxs * @param string $current_lang * * @return array */ private function get_translated_term_ids( array $terms, $tax, array $translatable_taxs, $current_lang ) { $term_ids = array(); foreach ( $terms as $term ) { if ( in_array( $tax->name, $translatable_taxs ) ) { $term_ids[] = $this->term_translations->term_id_in( $term->term_id, $current_lang, false ); } else { $term_ids[] = $term->term_id; } } return wpml_collect( $term_ids ) ->filter() ->values() ->map( Fns::unary( 'intval' ) ) ->toArray(); } /** * @param WP_REST_Response $response The response object. * @param WP_Post $post Post object. * * @return WP_REST_Response */ private function adjust_sample_links( $response, $post ) { $data = $response->get_data(); if ( ! isset( $data['link'], $data['permalink_template'] ) ) { return $response; } $lang_details = $this->sitepress->get_element_language_details( $post->ID, 'post_' . $post->post_type ); if ( empty( $lang_details->language_code ) ) { $lang = $this->sitepress->get_current_language(); $data['link'] = $this->sitepress->convert_url( $data['link'], $lang ); $data['permalink_template'] = $this->sitepress->convert_url( $data['permalink_template'], $lang ); $response->set_data( $data ); } return $response; } /** * @param WP_HTTP_Response|WP_Error $response Result to send to the client. Usually a WP_REST_Response or WP_Error. * @param array $handler Route handler used for the request. * @param WP_REST_Request $request Request used to generate the response. * * @return WP_HTTP_Response|WP_Error */ public function reload_wpml_post_translation( $response, array $handler, WP_REST_Request $request ) { if ( ! is_wp_error( $response ) && $this->is_saving_reusable_block( $request ) ) { wpml_load_post_translation( is_admin(), $this->sitepress->get_settings() ); } return $response; } private function is_saving_reusable_block( WP_REST_Request $request ) { return in_array( $request->get_method(), array( 'POST', 'PUT', 'PATCH' ) ) && preg_match( '#\/wp\/v2\/blocks(?:\/\d+)*#', $request->get_route() ); } } taxonomy-term-translation/Hooks.php 0000755 00000005507 14720342453 0013535 0 ustar 00 <?php namespace WPML\TaxonomyTermTranslation; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Lst; use WPML\FP\Relation; use WPML\LIB\WP\Hooks as WpHooks; use function WPML\FP\spreadArgs; class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action { const KEY_SKIP_FILTERS = 'wpml_skip_filters'; /** @var \SitePress */ private $sitepress; /** @var \wpdb */ private $wpdb; /** @var array */ private $post_term_taxonomy_ids_before_sync = []; /** * WHooks constructor. * * @param \SitePress $sitepress * @param \wpdb $wpdb */ public function __construct( \SitePress $sitepress, \wpdb $wpdb ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; } public function add_hooks() { WpHooks::onFilter( 'term_exists_default_query_args' ) ->then( spreadArgs( function( $args ) { return array_merge( $args, [ 'cache_domain' => microtime(), // Prevent caching of the query self::KEY_SKIP_FILTERS => true, ] ); } ) ); add_action( 'wpml_pro_translation_after_post_save', array( $this, 'beforeSyncCustomTermFieldsTranslations' ), 10, 1 ); add_action( 'wpml_pro_translation_completed', array( $this, 'syncCustomTermFieldsTranslations' ), 10, 1 ); } public function remove_hooks() { remove_action( 'wpml_pro_translation_after_post_save', array( $this, 'beforeSyncCustomTermFieldsTranslations' ), 10 ); remove_action( 'wpml_pro_translation_completed', array( $this, 'syncCustomTermFieldsTranslations' ), 10 ); } /** * @param array $args * * @return bool */ public static function shouldSkip( $args ) { return (bool) Relation::propEq( self::KEY_SKIP_FILTERS, true, (array) $args ); } /** * @param int|false $postId */ public function beforeSyncCustomTermFieldsTranslations( $postId ) { $this->post_term_taxonomy_ids_before_sync = $postId ? $this->getAllPostTermTaxonomyIds( $postId ) : []; } /** * @param int|false $postId */ public function syncCustomTermFieldsTranslations( $postId ) { if ( ! $postId ) { return; } $postTermTaxonomyIds = $this->getAllPostTermTaxonomyIds( $postId ); $newPostTermTaxonomyIds = Lst::diff( $postTermTaxonomyIds, $this->post_term_taxonomy_ids_before_sync ); foreach ( $postTermTaxonomyIds as $termTaxonomyId ) { $isNewTerm = (bool) Lst::includes( $termTaxonomyId, $newPostTermTaxonomyIds ); $sync = new \WPML_Sync_Term_Meta_Action( $this->sitepress, (int) $termTaxonomyId, $isNewTerm ); $sync->run(); } } /** * @param int $postId * * @return array */ private function getAllPostTermTaxonomyIds( $postId ) { $wpdb = $this->wpdb; $termRels = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'term_relationships WHERE object_id = %d', $postId ) ); return Fns::map( Obj::prop( 'term_taxonomy_id' ), $termRels ); } } taxonomy-term-translation/class-wpml-term-language-synchronization.php 0000755 00000022157 14720342453 0022521 0 ustar 00 <?php /** * @since 3.1.8.4 * * Class WPML_Term_Language_Synchronization * * @package wpml-core * @subpackage taxonomy-term-translation */ class WPML_Term_Language_Synchronization extends WPML_WPDB_And_SP_User { /** @var string $taxonomy */ private $taxonomy; /** @var array $data */ private $data; /** @var array $missing_terms */ private $missing_terms = array(); /** @var WPML_Terms_Translations $term_utils */ private $term_utils; /** * @param SitePress $sitepress * @param WPML_Terms_Translations $term_utils * @param string $taxonomy */ public function __construct( &$sitepress, &$term_utils, $taxonomy ) { $wpdb = $sitepress->wpdb(); parent::__construct( $wpdb, $sitepress ); $this->term_utils = $term_utils; $this->taxonomy = $taxonomy; $this->data = $this->set_affected_ids(); $this->prepare_missing_terms_data(); } /** * Wrapper for the two database actions performed by this object. * First those terms are created that lack translations and then following that, * the assignment of posts and languages is corrected, taking advantage of the newly created terms * and resulting in a state of no conflicts in the form of a post language being different from * an assigned terms language, remaining. */ public function set_translated() { $this->prepare_missing_originals(); $this->reassign_terms(); $this->set_initial_term_language(); } /** * Helper function for the installation process, * finds all terms missing an entry in icl_translations and then * assigns them the default language. */ public function set_initial_term_language() { $element_ids = $this->wpdb->get_col( $this->wpdb->prepare( " SELECT tt.term_taxonomy_id FROM {$this->wpdb->term_taxonomy} AS tt LEFT JOIN {$this->wpdb->prefix}icl_translations AS i ON tt.term_taxonomy_id = i.element_id AND CONCAT('tax_', tt.taxonomy) = i.element_type WHERE taxonomy = %s AND i.element_id IS NULL", $this->taxonomy ) ); $default_language = $this->sitepress->get_default_language(); foreach ( $element_ids as $id ) { $this->sitepress->set_element_language_details( $id, 'tax_' . $this->taxonomy, false, $default_language ); } } /** * Performs an SQL query assigning all terms to their correct language equivalent if it exists. * This should only be run after the previous functionality in here has finished. * Afterwards the term counts are recalculated globally, since term assignments bypassing the WordPress Core, * will not trigger any sort of update on those. */ private function reassign_terms() { $update_query = $this->wpdb->prepare( "UPDATE {$this->wpdb->term_relationships} AS o, {$this->wpdb->prefix}icl_translations AS ic, {$this->wpdb->prefix}icl_translations AS iw, {$this->wpdb->prefix}icl_translations AS ip, {$this->wpdb->posts} AS p SET o.term_taxonomy_id = ic.element_id WHERE ic.trid = iw.trid AND ic.element_type = iw.element_type AND iw.element_id = o.term_taxonomy_id AND ic.language_code = ip.language_code AND ip.element_type = CONCAT('post_', p.post_type) AND ip.element_id = p.ID AND o.object_id = p.ID AND o.term_taxonomy_id != ic.element_id AND iw.element_type = %s", 'tax_' . $this->taxonomy ); $rows_affected = $this->wpdb->query( $update_query ); if ( $rows_affected ) { $term_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT term_taxonomy_id FROM {$this->wpdb->term_taxonomy} WHERE taxonomy = %s", $this->taxonomy ) ); // Do not run the count update on taxonomies that are not actually registered as proper taxonomy objects, e.g. WooCommerce Product Attributes. $taxonomy_object = $this->sitepress->get_wp_api()->get_taxonomy( $this->taxonomy ); if ( $taxonomy_object && isset( $taxonomy_object->object_type ) ) { $this->sitepress->get_wp_api()->wp_update_term_count( $term_ids, $this->taxonomy ); } } } /** * @param object[] $sql_result holding the information retrieved in \self::set_affected_ids * * @return array The associative array to be returned by \self::set_affected_ids */ private function format_data( $sql_result ) { $res = array(); foreach ( $sql_result as $pair ) { $res[ $pair->ttid ] = isset( $res[ $pair->ttid ] ) ? $res[ $pair->ttid ] : array( 'tlang' => array(), 'plangs' => array(), ); if ( $pair->term_lang && $pair->trid ) { $res[ $pair->ttid ]['tlang'] = array( 'lang' => $pair->term_lang, 'trid' => $pair->trid, ); } if ( $pair->post_lang ) { $res[ $pair->ttid ]['plangs'][ $pair->post_id ] = $pair->post_lang; } } return $res; } /** * Uses the API provided in \WPML_Terms_Translations to create missing term translations. * These arise when a term, previously having been untranslated, is set to be translated * and assigned to posts in more than one language. * * @param int $trid The trid value for which term translations are missing. * @param string $source_lang The source language of this trid. * @param array $langs The languages' codes for which term translations are missing. */ private function prepare_missing_translations( $trid, $source_lang, $langs ) { $existing_translations = $this->sitepress->term_translations()->get_element_translations( false, $trid ); foreach ( $langs as $lang ) { if ( ! isset( $existing_translations[ $lang ] ) ) { $this->term_utils->create_automatic_translation( array( 'lang_code' => $lang, 'source_language' => $source_lang, 'trid' => $trid, 'taxonomy' => $this->taxonomy, ) ); } } } /** * Retrieves all term_ids, and if applicable, their language and assigned to posts, * in an associative array, * which are in the situation of not being assigned to any language or in which a term * is assigned to a post in a language different from its own. * * @return array */ private function set_affected_ids() { $query_for_post_ids = $this->wpdb->prepare( " SELECT tl.trid AS trid, tl.ttid AS ttid, tl.tlang AS term_lang, tl.pid AS post_id, pl.plang AS post_lang FROM ( SELECT o.object_id AS pid, tt.term_taxonomy_id AS ttid, i.language_code AS tlang, i.trid AS trid FROM {$this->wpdb->term_relationships} AS o JOIN {$this->wpdb->term_taxonomy} AS tt ON o.term_taxonomy_id = tt.term_taxonomy_id LEFT JOIN {$this->wpdb->prefix}icl_translations AS i ON i.element_id = tt.term_taxonomy_id AND i.element_type = CONCAT('tax_', tt.taxonomy) WHERE tt.taxonomy = %s) AS tl LEFT JOIN ( SELECT p.ID AS pid, i.language_code AS plang FROM {$this->wpdb->posts} AS p JOIN {$this->wpdb->prefix}icl_translations AS i ON i.element_id = p.ID AND i.element_type = CONCAT('post_', p.post_type) ) AS pl ON tl.pid = pl.pid ", $this->taxonomy ); /** @var array<object>|null $ttid_pid_pairs */ $ttid_pid_pairs = $this->wpdb->get_results( $query_for_post_ids ); return is_array( $ttid_pid_pairs ) ? $this->format_data( $ttid_pid_pairs ) : []; } /** * Assigns language information to terms that are to be treated as originals at the time of * their taxonomy being set to translated instead of 'do nothing'. */ private function prepare_missing_originals() { foreach ( $this->missing_terms as $ttid => $missing_lang_data ) { if ( ! isset( $this->data[ $ttid ]['tlang']['trid'] ) ) { foreach ( $missing_lang_data as $lang => $post_ids ) { $this->sitepress->set_element_language_details( $ttid, 'tax_' . $this->taxonomy, null, $lang ); $trid = $this->sitepress->term_translations()->get_element_trid( $ttid ); if ( $trid ) { $this->data[ $ttid ]['tlang']['trid'] = $trid; $this->data[ $ttid ]['tlang']['lang'] = $lang; unset( $this->missing_terms[ $ttid ][ $lang ] ); break; } } } if ( isset( $this->data[ $ttid ]['tlang']['trid'] ) ) { $this->prepare_missing_translations( $this->data[ $ttid ]['tlang']['trid'], $this->data[ $ttid ]['tlang']['lang'], array_keys( $this->missing_terms[ $ttid ] ) ); } } } /** * Uses the data retrieved from the database and saves information about, * in need of fixing terms to this object. */ private function prepare_missing_terms_data() { $default_lang = $this->sitepress->get_default_language(); $data = $this->data; $missing = array(); foreach ( $data as $ttid => $data_item ) { if ( empty( $data_item['plangs'] ) && empty( $data_item['tlang'] ) ) { $missing[ $ttid ][ $default_lang ] = - 1; } else { $affected_languages = array_diff( $data_item['plangs'], $data_item['tlang'] ); if ( ! empty( $affected_languages ) ) { foreach ( $data_item['plangs'] as $post_id => $lang ) { if ( ! isset( $missing[ $ttid ][ $lang ] ) ) { $missing[ $ttid ][ $lang ] = array( $post_id ); } else { $missing[ $ttid ][ $lang ][] = $post_id; } } } } } $this->missing_terms = $missing; } } taxonomy-term-translation/class-wpml-update-term-count.php 0000755 00000002202 14720342453 0020074 0 ustar 00 <?php class WPML_Update_Term_Count { const CACHE_GROUP = __CLASS__; /** @var WPML_WP_API $wp_api */ private $wp_api; /** * WPML_Update_Term_Count constructor. * * @param WPML_WP_API $wp_api */ public function __construct( $wp_api ) { $this->wp_api = $wp_api; } /** * Triggers an update to the term count of all terms associated with the * input post_id * * @param int $post_id */ public function update_for_post( $post_id ) { static $taxonomies; if ( ! $taxonomies ) { $taxonomies = $this->wp_api->get_taxonomies(); } if ( is_wp_error( $taxonomies ) ) { return; } $found = false; $terms = WPML_Non_Persistent_Cache::get( $post_id, self::CACHE_GROUP, $found ); if ( ! $found ) { $terms = $this->wp_api->wp_get_post_terms( $post_id, $taxonomies ); WPML_Non_Persistent_Cache::set( $post_id, $terms, self::CACHE_GROUP ); } if ( is_wp_error( $terms ) ) { return; } foreach ( $taxonomies as $taxonomy ) { foreach ( $terms as $term ) { if ( $term->taxonomy === $taxonomy ) { $this->wp_api->wp_update_term_count( $term->term_taxonomy_id, $taxonomy ); } } } } } taxonomy-term-translation/AutoSync.php 0000755 00000003460 14720342453 0014213 0 ustar 00 <?php namespace WPML\TaxonomyTermTranslation; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Str; use WPML\LIB\WP\Hooks; use WPML\Settings\PostType\Automatic; use WPML\Setup\Option; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class AutoSync implements \IWPML_Backend_Action, \IWPML_REST_Action, \IWPML_AJAX_Action { public function add_hooks() { if ( Option::shouldTranslateEverything() ) { Hooks::onAction( 'wpml_pro_translation_completed', 10, 3 ) ->then( spreadArgs( self::syncTaxonomyHierarchy() ) ); remove_action( 'save_post', 'display_tax_sync_message' ); } } /** * @return \Closure (int, array, object) -> void */ private static function syncTaxonomyHierarchy() { return function( $newPostId, $fields, $job ) { // $isPostJob :: object -> bool $isPostJob = Relation::propEq( 'element_type_prefix', 'post' ); // $getPostType :: object -> string $getPostType = pipe( Obj::prop( 'original_post_type' ), Str::replace( 'post_', '' ) ); // $isAutomaticPostType :: string -> bool $isAutomaticPostType = [ Automatic::class, 'isAutomatic' ]; // $isTranslatableTax :: string -> bool $isTranslatableTax = function( $taxonomy ) { return \WPML_Element_Sync_Settings_Factory::createTax()->is_sync( $taxonomy ); }; // $syncTaxonomyHierarchies :: array -> void $syncTaxonomyHierarchies = function( $taxonomies ) { wpml_get_hierarchy_sync_helper( 'term' )->sync_element_hierarchy( $taxonomies, Languages::getDefaultCode() ); }; Maybe::of( $job ) ->filter( $isPostJob ) ->map( $getPostType ) ->filter( $isAutomaticPostType ) ->map( 'get_object_taxonomies' ) ->map( Fns::filter( $isTranslatableTax ) ) ->map( $syncTaxonomyHierarchies ); }; } } taxonomy-term-translation/class-wpml-sync-term-meta-action.php 0000755 00000010161 14720342453 0020642 0 ustar 00 <?php class WPML_Sync_Term_Meta_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var int $term_taxonomy_id */ private $term_taxonomy_id; /** @var bool $is_new_term */ private $is_new_term; /** * WPML_Sync_Term_Meta_Action constructor. * * @param SitePress $sitepress * @param int $term_taxonomy_id just saved term's term_taxonomy_id * @param bool $is_new_term */ public function __construct( $sitepress, $term_taxonomy_id, $is_new_term = false ) { $this->sitepress = $sitepress; $this->term_taxonomy_id = $term_taxonomy_id; $this->is_new_term = $is_new_term; } /** * Copies to be synchronized term meta data to the translations of the term. */ public function run() { $term_taxonomy_id_from = $this->sitepress->term_translations()->get_original_element( $this->term_taxonomy_id ); if ( ! $term_taxonomy_id_from ) { $term_taxonomy_id_from = $this->term_taxonomy_id; } $translations = $this->sitepress ->term_translations() ->get_element_translations( $term_taxonomy_id_from, false, true ); if ( ! empty( $translations ) ) { foreach ( $translations as $term_taxonomy_id_to ) { $this->copy_custom_fields( (int) $term_taxonomy_id_to, $term_taxonomy_id_from ); } } } /** * @param int $term_taxonomy_id_to * @param int $term_taxonomy_id_from */ private function copy_custom_fields( $term_taxonomy_id_to, $term_taxonomy_id_from ) { $cf_copy = array(); $setting_factory = $this->sitepress->core_tm()->settings_factory(); $meta_keys = $setting_factory->get_term_meta_keys(); foreach ( $meta_keys as $meta_key ) { $meta_key_status = $setting_factory->term_meta_setting( $meta_key )->status(); if ( WPML_COPY_CUSTOM_FIELD === $meta_key_status || $this->should_copy_once( $meta_key_status, $term_taxonomy_id_to ) ) { $cf_copy[] = $meta_key; } } $term_id_to = $this->sitepress->term_translations()->adjust_term_id_for_ttid( $term_taxonomy_id_to ); $term_id_from = $this->sitepress->term_translations()->adjust_term_id_for_ttid( $term_taxonomy_id_from ); foreach ( $cf_copy as $meta_key ) { $meta_from = $this->sitepress->get_wp_api()->get_term_meta( $term_id_from, $meta_key ); $meta_to = $this->sitepress->get_wp_api()->get_term_meta( $term_id_to, $meta_key ); if ( $meta_from || $meta_to ) { $this->sync_custom_field( $term_id_from, $term_id_to, $meta_key ); } } } private function sync_custom_field( $term_id_from, $term_id_to, $meta_key ) { $wpdb = $this->sitepress->wpdb(); $sql = "SELECT meta_value FROM {$wpdb->termmeta} WHERE term_id=%d AND meta_key=%s"; $values_from = $wpdb->get_col( $wpdb->prepare( $sql, array( $term_id_from, $meta_key ) ) ); $values_to = $wpdb->get_col( $wpdb->prepare( $sql, array( $term_id_to, $meta_key ) ) ); $removed = array_diff( $values_to, $values_from ); foreach ( $removed as $v ) { $delete_prepared = $wpdb->prepare( "DELETE FROM {$wpdb->termmeta} WHERE term_id=%d AND meta_key=%s AND meta_value=%s", array( $term_id_to, $meta_key, $v ) ); $wpdb->query( $delete_prepared ); } $added = array_diff( $values_from, $values_to ); foreach ( $added as $v ) { $insert_prepared = $wpdb->prepare( "INSERT INTO {$wpdb->termmeta}(term_id, meta_key, meta_value) VALUES(%d, %s, %s)", array( $term_id_to, $meta_key, $v ) ); $wpdb->query( $insert_prepared ); } /** * @param int $term_id_from The term_id of the source term. * @param int $term_id_to The term_id of the destination term. * @param string $meta_key The key of the term meta being copied. * * @since 4.7.0 */ do_action( 'wpml_after_copy_term_field', $term_id_from, $term_id_to, $meta_key ); wp_cache_init(); } /** * @param int $meta_key_status * @param int $term_taxonomy_id_to * * @return bool */ private function should_copy_once( $meta_key_status, $term_taxonomy_id_to ) { return $this->is_new_term && WPML_COPY_ONCE_CUSTOM_FIELD === $meta_key_status && $term_taxonomy_id_to === $this->term_taxonomy_id; } } taxonomy-term-translation/class-wpml-term-actions.php 0000755 00000040240 14720342453 0017130 0 ustar 00 <?php /** * WPML_Term_Actions Class * * @package wpml-core * @subpackage taxonomy-term-translation */ class WPML_Term_Actions extends WPML_Full_Translation_API { /** @var bool $delete_recursion_flag */ private $delete_recursion_flag = false; /** * Handle AJAX request to generate unique slug. */ public function generate_unique_term_slug_ajax_handler() { if ( $this->sitepress->get_wp_api()->is_ajax() && wp_verify_nonce( $_POST['nonce'], 'wpml_generate_unique_slug_nonce' ) ) { $term = array_key_exists( 'term', $_POST ) ? sanitize_text_field( $_POST['term'] ) : ''; $taxonomy = array_key_exists( 'taxonomy', $_POST ) ? sanitize_text_field( $_POST['taxonomy'] ) : ''; $language_code = array_key_exists( 'language_code', $_POST ) ? sanitize_text_field( $_POST['language_code'] ) : ''; wp_send_json_success( array( 'slug' => urldecode( $this->term_translations->generate_unique_term_slug( $term, '', $taxonomy, $language_code ) ), ) ); } else { wp_send_json_error(); } } /** * @param int $tt_id Taxonomy Term ID of the saved Term * @param string $taxonomy Taxonomy of the saved Term */ function save_term_actions( $tt_id, $taxonomy ) { if ( ! $this->sitepress->is_translated_taxonomy( $taxonomy ) ) { return; }; $post_action = filter_input( INPUT_POST, 'action' ); $term_lang = $this->get_term_lang( $tt_id, $post_action, $taxonomy ); $trid = $this->get_saved_term_trid( $tt_id, $post_action ); $src_language = $this->term_translations->get_source_lang_code( $tt_id ); $this->sitepress->set_element_language_details( $tt_id, 'tax_' . $taxonomy, $trid, $term_lang, $src_language ); add_action( 'created_term', array( $this, 'sync_term_meta' ), 10, 2 ); add_action( 'edited_term', array( $this, 'sync_term_meta' ), 10, 2 ); } /** * @param int $term_id * @param int $tt_id */ public function sync_term_meta( $term_id, $tt_id ) { $is_new_term = 'created_term' === current_filter(); $sync_meta_action = new WPML_Sync_Term_Meta_Action( $this->sitepress, $tt_id, $is_new_term ); $sync_meta_action->run(); } /** * @param int $term_taxonomy_id term taxonomy id of the deleted term * @param string $taxonomy_name taxonomy of the deleted term */ function delete_term_actions( $term_taxonomy_id, $taxonomy_name ) { $element_type = 'tax_' . $taxonomy_name; $lang_details = $this->sitepress->get_element_language_details( $term_taxonomy_id, $element_type ); if ( ! $lang_details ) { return; } $trid = $lang_details->trid; if ( empty( $lang_details->source_language_code ) ) { if ( ! $this->delete_recursion_flag && $this->sitepress->get_setting( 'sync_delete_tax' ) ) { $this->delete_recursion_flag = true; $translations = $this->sitepress->get_element_translations( $trid, $element_type ); $this->delete_translations( $term_taxonomy_id, $taxonomy_name, $translations ); $this->delete_recursion_flag = false; } else { $this->set_new_original_term( $trid, $lang_details->language_code ); } } $update_args = array( 'element_id' => $term_taxonomy_id, 'element_type' => $element_type, 'context' => 'tax', ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'before_delete' ) ) ); $this->wpdb->delete( $this->wpdb->prefix . 'icl_translations', array( 'element_type' => $element_type, 'element_id' => $term_taxonomy_id, ) ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'after_delete' ) ) ); } /** * @param int $trid * @param string $deleted_language_code */ public function set_new_original_term( $trid, $deleted_language_code ) { if ( $trid && $deleted_language_code ) { $order_languages = $this->sitepress->get_setting( 'languages_order' ); $this->term_translations->reload(); $translations = $this->term_translations->get_element_translations( false, $trid ); $new_source_lang_code = false; foreach ( $order_languages as $lang_code ) { if ( isset( $translations[ $lang_code ] ) ) { $new_source_lang_code = $lang_code; break; } } if ( $new_source_lang_code ) { $rows_updated = $this->wpdb->update( $this->wpdb->prefix . 'icl_translations', array( 'source_language_code' => $new_source_lang_code ), array( 'trid' => $trid, 'source_language_code' => $deleted_language_code, ) ); if ( 0 < $rows_updated ) { do_action( 'wpml_translation_update', array( 'trid' => $trid ) ); } $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translations SET source_language_code = NULL WHERE language_code = source_language_code" ); } } } /** * Whether the term is original or not, there should be a post with same lang code as this term. * If the term is original, the post translation with same language code as term should be an unoriginal post (translation) because the original post already has its related term relation deleted. * * @param stdClass $term * @param array $postTranslations * * @return bool * * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-673 */ public function isTranslatedTermValidForRelationDeletion( $term, $postTranslations ) { $valid = isset( $postTranslations[ $term->language_code ] ); if ( $term->original && $valid ) { $valid = ! $postTranslations[ $term->language_code ]->original; } return $valid; } /** * This action is hooked to the 'deleted_term_relationships' hook. * It removes terms from translated posts as soon as they are removed from the original post. * It only fires, if the setting 'sync_post_taxonomies' is activated. * * @param int $post_id ID of the post the deleted terms were attached to * @param array $delete_terms Array of terms ids that were deleted from the post. * @param string $taxonomy */ public function deleted_term_relationships( $post_id, $delete_terms, $taxonomy ) { $post = get_post( $post_id ); $postTrid = $this->sitepress->get_element_trid( $post_id, 'post_' . $post->post_type ); if ( ! $postTrid ) { return; } $isOriginalPost = function ( $translation ) use ( $post_id ) { return $translation->original == 1 && $translation->element_id == $post_id; }; $postTranslations = $this->sitepress->get_element_translations( $postTrid, 'post_' . $post->post_type ); $originalPost = wpml_collect( $postTranslations )->filter( $isOriginalPost )->first(); if ( ! $originalPost ) { // only try to delete term relations if the target post is original. return; } /** * @param int $termId * * @return bool|mixed|string|null */ $getTermsTrids = function ( $termId ) use ( $taxonomy ) { return $this->sitepress->get_element_trid( $termId, 'tax_' . $taxonomy ); }; /** * @param int $trid * * @return bool */ $onlyValidTermsTrids = function ( $trid ) { return \WPML\FP\Logic::isTruthy( $trid ); }; /** * @param int $trid * * @return stdClass[] */ $getDeletedTermsTranslations = function ( $trid ) use ( $taxonomy ) { return $this->sitepress->get_element_translations( $trid, 'tax_' . $taxonomy ); }; /** * Performs DB query to delete term relations only if term translation is valid for relation deletion. * * @param stdClass $deletedTermTranslation * * @return void */ $deleteTermTranslationsRelations = function ( $deletedTermTranslation ) use ( $postTranslations ) { if ( $this->isTranslatedTermValidForRelationDeletion( $deletedTermTranslation, $postTranslations ) ) { $translated_post = $postTranslations[ $deletedTermTranslation->language_code ]; $this->wpdb->delete( $this->wpdb->term_relationships, array( 'object_id' => $translated_post->element_id, 'term_taxonomy_id' => $deletedTermTranslation->element_id, ) ); } }; $deletedTermsTranslations = wpml_collect( $delete_terms ) ->map( $getTermsTrids ) ->filter( $onlyValidTermsTrids ) ->map( $getDeletedTermsTranslations ) ->flatten() ->toArray(); \WPML\FP\Fns::map( \WPML\FP\Fns::tap( $deleteTermTranslationsRelations ), $deletedTermsTranslations ); } /** * Copies taxonomy terms from original posts to their translation, if the translations of these terms exist * and the option 'sync_post_taxonomies' is set. * * @param int $object_id ID of the object, that terms have just been added to. */ public function added_term_relationships( $object_id ) { $i = $this->wpdb->prefix . 'icl_translations'; $current_ttids_sql = $this->wpdb->prepare( "SELECT ctt.taxonomy, ctt.term_id, p.ID FROM {$this->wpdb->posts} p JOIN {$i} i ON p.ID = i.element_id AND i.element_type = CONCAT('post_', p.post_type) JOIN {$i} ip ON ip.trid = i.trid AND i.source_language_code = ip.language_code JOIN {$i} it JOIN {$this->wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = it.element_id AND CONCAT('tax_', tt.taxonomy) = it.element_type JOIN {$this->wpdb->term_relationships} objrel ON objrel.object_id = ip.element_id AND objrel.term_taxonomy_id = tt.term_taxonomy_id JOIN {$i} itt ON itt.trid = it.trid AND itt.language_code = i.language_code JOIN {$this->wpdb->term_taxonomy} ctt ON ctt.term_taxonomy_id = itt.element_id LEFT JOIN {$this->wpdb->term_relationships} trans_rel ON trans_rel.object_id = p.ID AND trans_rel.term_taxonomy_id = ctt.term_taxonomy_id WHERE ip.element_id = %d AND trans_rel.object_id IS NULL", $object_id ); $corrections = $this->wpdb->get_results( $current_ttids_sql ); is_array( $corrections ) && $this->apply_added_term_changes( $corrections ); } /** * @param array $corrections * * @uses \WPML_WP_API::wp_set_object_terms to add terms to posts, always appending terms */ private function apply_added_term_changes( $corrections ) { $changes = array(); foreach ( $corrections as $correction ) { if ( ! $this->sitepress->is_translated_taxonomy( $correction->taxonomy ) ) { continue; } if ( ! isset( $changes[ $correction->taxonomy ] ) ) { $changes[ $correction->ID ][ $correction->taxonomy ] = array(); } $changes[ $correction->ID ][ $correction->taxonomy ][] = (int) $correction->term_id; } foreach ( $changes as $post_id => $tax_changes ) { foreach ( $tax_changes as $taxonomy => $term_ids ) { remove_action( 'set_object_terms', array( 'WPML_Terms_Translations', 'set_object_terms_action' ), 10 ); $this->sitepress->get_wp_api()->wp_set_object_terms( $post_id, $term_ids, $taxonomy, true ); add_action( 'set_object_terms', array( 'WPML_Terms_Translations', 'set_object_terms_action' ), 10, 6 ); } } } /** * Gets the language under which a term is to be saved from the HTTP request and falls back on existing data in * case the HTTP request does not contain the necessary data. * If no language can be determined for the term to be saved under the default language is used as a fallback. * * @param int $tt_id Taxonomy Term ID of the saved term * @param string $post_action * @param string $taxonomy * * @return null|string */ private function get_term_lang( $tt_id, $post_action, $taxonomy ) { $term_lang = filter_input( INPUT_POST, 'icl_tax_' . $taxonomy . '_language', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); $term_lang = $term_lang ? $term_lang : $this->get_term_lang_ajax( $taxonomy, $post_action ); $term_lang = $term_lang ? $term_lang : $this->get_lang_from_post( $post_action, $tt_id ); $term_lang = $term_lang ? $term_lang : $this->sitepress->get_current_language(); $term_lang = apply_filters( 'wpml_create_term_lang', $term_lang ); $term_lang = $this->sitepress->is_active_language( $term_lang ) ? $term_lang : $this->sitepress->get_default_language(); return $term_lang; } /** * If no language could be set from the WPML $_POST variables as well as from the HTTP Referrer, then this function * uses fallbacks to determine the language from the post the the term might be associated to. * A post language determined from $_POST['icl_post_language'] will be used as term language. * Also a check for whether the publishing of the term happens via quickpress is performed in which case the term * is always associated with the default language. * Next a check for the 'inline-save-tax' and the 'editedtag' action is performed. In case the check returns true * the language of the term is not changed from what is saved for it in the database. * If no term language can be determined from the above the $_POST['post_ID'] is checked as a last resort and in * case it contains a valid post_ID the posts language is associated with the term. * * @param string $post_action * @param int $tt_id * * @return string|null Language code of the term */ private function get_lang_from_post( $post_action, $tt_id ) { $icl_post_lang = filter_input( INPUT_POST, 'icl_post_language' ); $term_lang = $post_action === 'editpost' && $icl_post_lang ? $icl_post_lang : null; $term_lang = $post_action === 'post-quickpress-publish' ? $this->sitepress->get_default_language() : $term_lang; $term_lang = ! $term_lang && $post_action === 'inline-save-tax' || $post_action === 'editedtag' ? $this->term_translations->get_element_lang_code( $tt_id ) : $term_lang; $term_lang = ! $term_lang && $post_action === 'inline-save' ? $this->post_translations->get_element_lang_code( filter_input( INPUT_POST, 'post_ID', FILTER_SANITIZE_NUMBER_INT ) ) : $term_lang; return $term_lang; } /** * This function tries to determine the terms language from the HTTP Referer. This is used in case of ajax actions * that save the term. * * @param string $taxonomy * @param string $post_action * * @return null|string */ public function get_term_lang_ajax( $taxonomy, $post_action ) { if ( isset( $_POST['_ajax_nonce'] ) && filter_var( $_POST['_ajax_nonce'] ) !== false && $post_action === 'add-' . $taxonomy ) { $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ''; parse_str( (string) wpml_parse_url( $referrer, PHP_URL_QUERY ), $qvars ); $term_lang = ! empty( $qvars['post'] ) && $this->sitepress->is_translated_post_type( get_post_type( (int) $qvars['post'] ) ) ? $this->post_translations->get_element_lang_code( $qvars['post'] ) : ( isset( $qvars['lang'] ) ? $qvars['lang'] : null ); } return isset( $term_lang ) ? $term_lang : null; } private function get_saved_term_trid( $tt_id, $post_action ) { if ( $post_action === 'editpost' ) { $trid = $this->term_translations->get_element_trid( $tt_id ); } elseif ( $post_action === 'editedtag' ) { $translation_of = filter_input( INPUT_POST, 'icl_translation_of', FILTER_VALIDATE_INT ); $translation_of = $translation_of ? $translation_of : filter_input( INPUT_POST, 'icl_translation_of' ); $trid = $translation_of === 'none' ? false : ( $translation_of ? $this->term_translations->get_element_trid( $translation_of ) : $trid = filter_input( INPUT_POST, 'icl_trid', FILTER_SANITIZE_NUMBER_INT ) ); } else { $trid = filter_input( INPUT_POST, 'icl_trid', FILTER_SANITIZE_NUMBER_INT ); $trid = $trid ? $trid : $this->term_translations->get_element_trid( filter_input( INPUT_POST, 'icl_translation_of', FILTER_VALIDATE_INT ) ); $trid = $trid ? $trid : $this->term_translations->get_element_trid( $tt_id ); } return $trid; } /** * @param int $term_taxonomy_id * @param string $taxonomy * @param array $translations */ private function delete_translations( $term_taxonomy_id, $taxonomy, array $translations ) { $has_filter = remove_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1 ); foreach ( $translations as $translation ) { if ( (int) $translation->element_id !== (int) $term_taxonomy_id ) { wp_delete_term( $translation->term_id, $taxonomy ); } } if ( $has_filter ) { add_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1, 1 ); } } } taxonomy-term-translation/class-wpml-taxonomy-translation-help-notice.php 0000755 00000012267 14720342453 0023152 0 ustar 00 <?php /** * Class WPML_Taxonomy_Translation_Help_Notice */ class WPML_Taxonomy_Translation_Help_Notice { const NOTICE_GROUP = 'taxonomy-term-help-notices'; /** * @var WPML_Notices */ private $wpml_admin_notices; /** * @var WPML_Notice */ private $notice = false; /** * @var SitePress */ private $sitepress; /** * WPML_Taxonomy_Translation_Help_Notice constructor. * * @param WPML_Notices $wpml_admin_notices * @param SitePress $sitepress */ public function __construct( WPML_Notices $wpml_admin_notices, SitePress $sitepress ) { $this->wpml_admin_notices = $wpml_admin_notices; $this->sitepress = $sitepress; } public function __sleep() { return array( 'wpml_admin_notices', 'notice' ); } public function __wakeup() { global $sitepress; $this->sitepress = $sitepress; } public function add_hooks() { add_action( 'admin_init', array( $this, 'add_help_notice' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } /** * @return bool */ public function should_display_help_notice() { $display_notice = false; $taxonomy = $this->get_current_translatable_taxonomy(); $notice = $this->get_notice(); if ( $notice && $taxonomy && $taxonomy->name === $notice->get_id() && $this->taxonomy_term_screen() ) { $display_notice = true; } return $display_notice; } /** * Create and add notice. */ public function add_help_notice() { $notice = $this->create_and_set_term_translation_help_notice(); if ( false !== $notice ) { $this->add_term_help_notice_to_admin_notices(); } } /** * @return WP_Taxonomy|false */ private function get_current_translatable_taxonomy() { $taxonomy = false; if ( array_key_exists( 'taxonomy', $_GET ) && ! empty( $_GET['taxonomy'] ) && $this->is_translatable_taxonomy( $_GET['taxonomy'] ) ) { $taxonomy = get_taxonomy( $_GET['taxonomy'] ); } return $taxonomy; } /** * @return WPML_Notice */ private function create_and_set_term_translation_help_notice() { $taxonomy = $this->get_current_translatable_taxonomy(); if ( false !== $taxonomy ) { $link_to_taxonomy_translation_screen = $this->build_tag_to_taxonomy_translation( $taxonomy ); $text = sprintf( esc_html__( 'Translating %1$s? Use the %2$s table for easier translation.', 'sitepress' ), $taxonomy->labels->name, $link_to_taxonomy_translation_screen ); $this->set_notice( new WPML_Notice( $taxonomy->name, $text, self::NOTICE_GROUP ) ); } return $this->get_notice(); } private function add_term_help_notice_to_admin_notices() { $notice = $this->get_notice(); $notice->set_css_class_types( 'info' ); $notice->add_display_callback( array( $this, 'should_display_help_notice' ) ); $action = $this->wpml_admin_notices->get_new_notice_action( esc_html__( 'Dismiss', 'sitepress' ), '#', false, false, false ); $action->set_js_callback( 'wpml_dismiss_taxonomy_translation_notice' ); $action->set_group_to_dismiss( $notice->get_group() ); $notice->add_action( $action ); $this->wpml_admin_notices->add_notice( $notice ); } /** * @param \WP_Taxonomy $taxonomy * * @return string */ private function build_tag_to_taxonomy_translation( $taxonomy ) { $url = add_query_arg( array( 'page' => WPML_PLUGIN_FOLDER . '/menu/taxonomy-translation.php', 'taxonomy' => $taxonomy->name, ), admin_url( 'admin.php' ) ); $url = apply_filters( 'wpml_taxonomy_term_translation_url', $url, $taxonomy->name ); return '<a href="' . esc_url( $url ) . '">' . sprintf( esc_html__( ' %s translation', 'sitepress' ), $taxonomy->labels->singular_name ) . '</a>'; } private function taxonomy_term_screen() { $screen = get_current_screen(); $is_taxonomy_term_screen = false; if ( 'edit-tags' === $screen->base || 'term' === $screen->base ) { $is_taxonomy_term_screen = true; } return $is_taxonomy_term_screen; } /** * @param WPML_Notice $notice */ public function set_notice( WPML_Notice $notice ) { $this->notice = $notice; } /** * @return WPML_Notice */ public function get_notice() { return $this->notice; } /** * Enqueue JS callback script. */ public function enqueue_scripts() { $notice = $this->get_notice(); if ( $notice ) { wp_register_script( 'wpml-dismiss-taxonomy-help-notice', ICL_PLUGIN_URL . '/res/js/dismiss-taxonomy-help-notice.js', array( 'jquery' ) ); wp_localize_script( 'wpml-dismiss-taxonomy-help-notice', 'wpml_notice_information', array( 'notice_id' => $notice->get_id(), 'notice_group' => $notice->get_group(), ) ); wp_enqueue_script( 'wpml-dismiss-taxonomy-help-notice' ); } } /** * @param string $taxonomy * * @return bool */ private function is_translatable_taxonomy( $taxonomy ) { $is_translatable = false; $taxonomy_object = get_taxonomy( $taxonomy ); if ( $taxonomy_object ) { $post_type = isset( $taxonomy_object->object_type[0] ) ? $taxonomy_object->object_type[0] : 'post'; $translatable_taxonomies = $this->sitepress->get_translatable_taxonomies( true, $post_type ); $is_translatable = in_array( $taxonomy, $translatable_taxonomies ); } return $is_translatable; } } class-wpml-tm-resources-factory.php 0000755 00000001057 14720342453 0013444 0 ustar 00 <?php abstract class WPML_TM_Resources_Factory { protected $ajax_actions; /** * @var WPML_WP_API */ protected $wpml_wp_api; /** * @param WPML_WP_API $wpml_wp_api */ public function __construct( &$wpml_wp_api ) { $this->wpml_wp_api = &$wpml_wp_api; add_action( 'admin_enqueue_scripts', array( $this, 'register_resources' ), 10 ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_resources' ), 20 ); } abstract public function enqueue_resources( $hook_suffix ); abstract public function register_resources( $hook_suffix ); } tp-client/wpml-tp-tm-jobs.php 0000755 00000001767 14720342453 0012147 0 ustar 00 <?php /** * Class WPML_TP_TM_Jobs * * @author OnTheGoSystems */ class WPML_TP_TM_Jobs { const CACHE_BATCH_ID = 'wpml_tp_tm_jobs_batch_id'; /** @var wpdb $wpdb */ private $wpdb; /** * WPML_TF_Rating_TP_API constructor. * * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param int $job_id * * @return null|string */ public function get_batch_id( $job_id ) { $cached_batch_id = wp_cache_get( $job_id, self::CACHE_BATCH_ID ); if ( $cached_batch_id ) { return $cached_batch_id; } $query = "SELECT tb.tp_id FROM {$this->wpdb->prefix}icl_translation_batches AS tb LEFT JOIN {$this->wpdb->prefix}icl_translation_status AS ts ON tb.id = ts.batch_id LEFT JOIN {$this->wpdb->prefix}icl_translate_job AS tj ON ts.rid = tj.rid WHERE tj.job_id = %d"; $batch_id = $this->wpdb->get_var( $this->wpdb->prepare( $query, $job_id ) ); wp_cache_set( $job_id, $batch_id, self::CACHE_BATCH_ID ); return $batch_id; } } tp-client/exceptions/wpml-tp-batch-exception.php 0000755 00000000104 14720342453 0016012 0 ustar 00 <?php class WPML_TP_Batch_Exception extends WPML_TP_Exception { } tp-client/exceptions/wpml-tp-exception.php 0000755 00000000066 14720342453 0014742 0 ustar 00 <?php class WPML_TP_Exception extends Exception { } tp-client/class-wpml-tp-jobs-collection.php 0000755 00000002332 14720342453 0014752 0 ustar 00 <?php class WPML_TP_Jobs_Collection { private $project; private $job_factory; private $batch_factory; private $jobs; public function __construct( TranslationProxy_Project $project, WPML_TP_Job_Factory $job_factory, WPML_Translation_Batch_Factory $batch_factory ) { $this->project = $project; $this->job_factory = $job_factory; $this->batch_factory = $batch_factory; } /** * @return WPML_TP_Job[] */ public function get_all() { $jobs_obj = array(); if ( ! $this->jobs ) { $jobs = $this->project->jobs(); foreach ( $jobs as $job ) { $jobs_obj[] = $this->job_factory->create( $job ); } $this->jobs = $jobs_obj; } return $this->jobs; } /** * @param WPML_Translation_Job $job * * @return bool */ public function is_job_canceled( WPML_Translation_Job $job ) { $canceled = false; $batch = $this->batch_factory->create( $job->get_batch_id() ); foreach ( $this->get_all() as $tp_job ) { if ( (int) $batch->get_batch_tp_id() === $tp_job->get_batch()->id && (int) $job->get_original_element_id() === $tp_job->get_original_element_id() && WPML_TP_Job::CANCELLED === $tp_job->get_job_state() ) { $canceled = true; } } return $canceled; } } tp-client/wpml-tp-client.php 0000755 00000002654 14720342453 0012046 0 ustar 00 <?php /** * Class WPML_TP_Client * * @author OnTheGoSystems */ class WPML_TP_Client { /** @var WPML_TP_Project $project */ private $project; /** @var WPML_TP_TM_Jobs $tm_jobs */ private $tm_jobs; /** @var WPML_TP_API_Services $services */ private $services; /** @var WPML_TP_API_Batches $batches */ private $batches; /** @var WPML_TP_API_TF_Ratings $ratings */ private $ratings; /** @var WPML_TP_API_TF_Feedback $feedback */ private $feedback; public function __construct( WPML_TP_Project $project, WPML_TP_TM_Jobs $tm_jobs ) { $this->project = $project; $this->tm_jobs = $tm_jobs; } public function services() { if ( ! $this->services ) { $this->services = new WPML_TP_API_Services( $this ); } return $this->services; } public function batches() { if ( ! $this->batches ) { $this->batches = new WPML_TP_API_Batches( $this ); } return $this->batches; } /** @return WPML_TP_API_TF_Ratings */ public function ratings() { if ( ! $this->ratings ) { $this->ratings = new WPML_TP_API_TF_Ratings( $this ); } return $this->ratings; } /** @return WPML_TP_API_TF_Feedback */ public function feedback() { if ( ! $this->feedback ) { $this->feedback = new WPML_TP_API_TF_Feedback( $this ); } return $this->feedback; } /** @return WPML_TP_Project */ public function get_project() { return $this->project; } public function get_tm_jobs() { return $this->tm_jobs; } } tp-client/wpml-tp-project.php 0000755 00000003075 14720342453 0012234 0 ustar 00 <?php /** * Class WPML_TP_Project * * @author OnTheGoSystems */ class WPML_TP_Project { /** @var false|stdClass $translation_service */ private $translation_service; /** @var false|array $translation_projects */ private $translation_projects; /** @var array $project */ private $project; /** * WPML_TP_Project constructor. * * @param false|stdClass $translation_service * @param false|array $translation_projects */ public function __construct( $translation_service, $translation_projects ) { $this->translation_service = $translation_service; $this->translation_projects = $translation_projects; } private function init() { if ( ! $this->project ) { $project_index = TranslationProxy_Project::generate_service_index( $this->translation_service ); if ( isset( $this->translation_projects [ $project_index ] ) ) { $this->project = $this->translation_projects[ $project_index ]; } } } /** @return int|null */ public function get_translation_service_id() { return isset( $this->translation_service->id ) ? (int) $this->translation_service->id : null; } /** @return string|null */ public function get_access_key() { return $this->get_project_property( 'access_key' ); } /** @return int|null */ public function get_id() { return (int) $this->get_project_property( 'id' ); } /** * @param string $project_property * * @return mixed */ private function get_project_property( $project_property ) { $this->init(); return isset( $this->project[ $project_property ] ) ? $this->project[ $project_property ] : null; } } tp-client/wpml-tp-jobs-collection-factory.php 0000755 00000000670 14720342453 0015317 0 ustar 00 <?php class WPML_TP_Jobs_Collection_Factory { /** * @return WPML_TP_Jobs_Collection */ public function create() { $tp_jobs_collection = null; $current_project = TranslationProxy::get_current_project(); if ( $current_project ) { $tp_jobs_collection = new WPML_TP_Jobs_Collection( $current_project, new WPML_TP_Job_Factory(), new WPML_Translation_Batch_Factory() ); } return $tp_jobs_collection; } } tp-client/wpml-tp-client-factory.php 0000755 00000001027 14720342453 0013504 0 ustar 00 <?php /** * Class WPML_TP_Client_Factory * * @author OnTheGoSystems */ class WPML_TP_Client_Factory { /** @return WPML_TP_Client */ public function create() { global $sitepress, $wpdb; $translation_service = $sitepress->get_setting( 'translation_service' ); $translation_projects = $sitepress->get_setting( 'icl_translation_projects' ); $project = new WPML_TP_Project( $translation_service, $translation_projects ); $tm_jobs = new WPML_TP_TM_Jobs( $wpdb ); return new WPML_TP_Client( $project, $tm_jobs ); } } tp-client/api/wpml-tp-api-batches.php 0000755 00000004233 14720342453 0013514 0 ustar 00 <?php /** * Class WPML_TP_API_Batches */ class WPML_TP_API_Batches extends WPML_TP_Abstract_API { const API_VERSION = 1.1; const CREATE_BATCH_ENDPOINT = '/projects/{project_id}/batches.json'; const ADD_JOB_ENDPOINT = '/batches/{batch_id}/jobs.json'; private $endpoint_uri; protected function get_endpoint_uri() { return $this->endpoint_uri; } protected function is_authenticated() { return true; } /** * @throws WPML_TP_Batch_Exception * * @param array $batch_data * @param false|array $extra_fields * * @return false|stdClass * * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/create_batch_job */ public function create( array $batch_data, $extra_fields ) { $batch = false; $this->endpoint_uri = self::CREATE_BATCH_ENDPOINT; $params = array( 'api_version' => self::API_VERSION, 'project_id' => $this->tp_client->get_project()->get_id(), 'batch' => $batch_data, ); if ( $extra_fields ) { $params['extra_fields'] = $extra_fields; } $response = $this->post( $params ); if ( $this->get_exception() ) { throw new WPML_TP_Batch_Exception( $this->get_error_message() ); } if ( $response ) { $batch = new WPML_TP_Batch( $response->batch ); } return $batch; } /** * @param int $batch_id * @param array $job_data * * @return false|WPML_TP_Job * * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/add_files_batch_job */ public function add_job( $batch_id, array $job_data ) { $job = false; $this->endpoint_uri = self::ADD_JOB_ENDPOINT; $params = array( 'api_version' => self::API_VERSION, 'batch_id' => $batch_id, 'job' => $job_data, ); $response = $this->post( $params ); if ( $response ) { $job = new WPML_TP_Job( $response->job ); } return $job; } /** * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/commit_batch_job */ public function commit() { // To be implemented } /** * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/send-preview-bundle-job */ public function send_preview_bundle() { // To be implemented } } tp-client/api/wpml-tp-abstract-api.php 0000755 00000005234 14720342453 0013710 0 ustar 00 <?php /** * Class WPML_TP_Abstract_API * * @author OnTheGoSystems */ abstract class WPML_TP_Abstract_API { /** @var WPML_TP_Client $tp_client */ protected $tp_client; /** @var null|Exception $exception */ protected $exception; /** @var null|string $error_message */ protected $error_message; public function __construct( WPML_TP_Client $tp_client ) { $this->tp_client = $tp_client; } /** @return string */ abstract protected function get_endpoint_uri(); /** @return bool */ abstract protected function is_authenticated(); /** * @param array $params * * @return mixed */ protected function get( array $params = array() ) { return $this->remote_call( $params, 'GET' ); } /** * @param array $params * * @return mixed */ protected function post( array $params = array() ) { return $this->remote_call( $params, 'POST' ); } protected function put( array $params = array() ) { // @todo: Implement put } protected function delete( array $params = array() ) { // @todo: Implement delete } /** * @param array $params * @param string $method * * @return mixed */ private function remote_call( array $params, $method ) { $response = false; try { $params = $this->pre_process_params( $params ); $response = TranslationProxy_Api::proxy_request( $this->get_endpoint_uri(), $params, $method ); } catch ( Exception $e ) { $this->exception = $e; } return $response; } /** * @param array $params * * @return array */ private function pre_process_params( array $params ) { if ( $this->is_authenticated() ) { $params['accesskey'] = $this->tp_client->get_project()->get_access_key(); } return $params; } /** * WPML does not store the Translation Proxy Job ID * We have to identify the job somehow. * This is why we are using `original_file_id`. * It is the same as used in the XLIFF file as a value of `original` attribute. * The combination of `original_file_id` and `batch_id` will be always unique. * Translation Proxy provides this call, with these arguments, for this specific reason. * * @see https://git.onthegosystems.com/tp/translation-proxy/wikis/rate_translation * @see https://git.onthegosystems.com/tp/translation-proxy/wikis/send_feedback * * @param int $job_id * @param int $document_source_id * * @return string */ protected function get_original_file_id( $job_id, $document_source_id ) { return $job_id . '-' . md5( $job_id . $document_source_id ); } /** @return null|Exception */ public function get_exception() { return $this->exception; } /** @return null|string */ public function get_error_message() { return $this->exception->getMessage(); } } tp-client/api/wpml-tp-api-tf-feedback.php 0000755 00000003734 14720342453 0014243 0 ustar 00 <?php /** * Class WPML_TP_API_TF_Feedback * * @author OnTheGoSystems */ class WPML_TP_API_TF_Feedback extends WPML_TP_Abstract_API { const URI_SEND = '/batches/{batch_id}/jobs/{original_file_id}/feedbacks'; const URI_GET_STATUS = '/feedbacks/{feedback_id}'; /** @var string $endpoint_uri */ private $endpoint_uri; /** @return string */ protected function get_endpoint_uri() { return $this->endpoint_uri; } /** @return bool */ protected function is_authenticated() { return true; } /** * @param WPML_TF_Feedback $feedback * @param array $args * * @return int|false */ public function send( WPML_TF_Feedback $feedback, array $args ) { $previous_sent_feedback_id = $feedback->get_tp_responses()->get_feedback_id(); if ( $previous_sent_feedback_id ) { return $previous_sent_feedback_id; } $this->endpoint_uri = self::URI_SEND; $ret = false; $feedback_parameters = array( 'message' => $feedback->get_content(), ); if ( array_key_exists( 'email', $args ) ) { $feedback_parameters['email'] = $args['email']; } $params = array( 'batch_id' => $this->tp_client->get_tm_jobs()->get_batch_id( $feedback->get_job_id() ), 'original_file_id' => $this->get_original_file_id( $feedback->get_job_id(), $feedback->get_document_information()->get_source_id() ), 'feedback' => $feedback_parameters, ); $response = $this->post( $params ); if ( isset( $response->feedback->id ) ) { $ret = (int) $response->feedback->id; } return $ret; } /** * @param WPML_TF_Feedback $feedback * * @return false[string */ public function status( WPML_TF_Feedback $feedback ) { $this->endpoint_uri = self::URI_GET_STATUS; $status = false; $params = array( 'feedback_id' => $feedback->get_tp_responses()->get_feedback_id(), ); $response = $this->get( $params ); if ( isset( $response->status ) ) { $status = $response->status; } return $status; } } tp-client/api/wpml-tp-api-tf-ratings.php 0000755 00000001717 14720342453 0014165 0 ustar 00 <?php /** * Class WPML_TP_API_TF_Ratings * * @author OnTheGoSystems */ class WPML_TP_API_TF_Ratings extends WPML_TP_Abstract_API { /** @return string */ protected function get_endpoint_uri() { return '/batches/{batch_id}/jobs/{original_file_id}/ratings'; } /** @return bool */ protected function is_authenticated() { return true; } /** * @param WPML_TF_Feedback $feedback * * @return int|false */ public function send( WPML_TF_Feedback $feedback ) { $params = array( 'batch_id' => $this->tp_client->get_tm_jobs()->get_batch_id( $feedback->get_job_id() ), 'original_file_id' => $this->get_original_file_id( $feedback->get_job_id(), $feedback->get_document_information()->get_source_id() ), 'rating' => array( 'rating' => $feedback->get_rating(), ), ); $response = $this->post( $params ); if ( isset( $response->rating->id ) ) { return (int) $response->rating->id; } return false; } } tp-client/api/wpml-tp-api-services.php 0000755 00000013077 14720342453 0013734 0 ustar 00 <?php use WPML\FP\Fns; /** * Class WPML_TP_API_Services * * @author OnTheGoSystems */ class WPML_TP_API_Services extends WPML_TP_Abstract_API { const ENDPOINT_SERVICES = '/services.json'; const ENDPOINT_SERVICE = '/services/{service_id}.json'; const ENDPOINT_LANGUAGES_MAP = '/services/{service_id}/language_identifiers.json'; const ENDPOINT_CUSTOM_FIELDS = '/services/{service_id}/custom_fields.json'; const TRANSLATION_MANAGEMENT_SYSTEM = 'tms'; const PARTNER = 'partner'; const TRANSLATION_SERVICE = 'ts'; const CACHED_SERVICES_KEY_DATA = 'wpml_translation_services'; const CACHED_SERVICES_TRANSIENT_KEY = 'wpml_translation_services_list'; const CACHED_SERVICES_KEY_TIMESTAMP = 'wpml_translation_services_timestamp'; private $endpoint; /** @return string */ protected function get_endpoint_uri() { return $this->endpoint; } /** @return bool */ protected function is_authenticated() { return false; } /** * @param bool $reload * * @return array */ public function get_all( $reload = false ) { $this->endpoint = self::ENDPOINT_SERVICES; $translation_services = $reload ? null : $this->get_cached_services(); if ( ! $translation_services || $this->has_cache_services_expired() ) { $fresh_translation_services = parent::get(); if ( $fresh_translation_services ) { $translation_services = $this->convert_to_tp_services( $fresh_translation_services ); $this->cache_services( $translation_services ); } } return apply_filters( 'otgs_translation_get_services', $translation_services ? $translation_services : array() ); } /** * @return bool */ public function refresh_cache() { update_option( self::CACHED_SERVICES_KEY_TIMESTAMP, strtotime( '-2 day', $this->get_cached_services_timestamp() ) ); return (bool) $this->get_all(); } /** * @return mixed */ private function get_cached_services() { return get_option( self::CACHED_SERVICES_KEY_DATA ); } /** * @return mixed */ private function get_cached_services_timestamp() { return get_option( self::CACHED_SERVICES_KEY_TIMESTAMP ); } /** * @param $services */ private function cache_services( $services ) { update_option( self::CACHED_SERVICES_KEY_DATA, $services, 'no' ); update_option( self::CACHED_SERVICES_KEY_TIMESTAMP, time() ); } /** * @return bool */ private function has_cache_services_expired() { return time() >= strtotime( '+1 day', $this->get_cached_services_timestamp() ); } /** * @param array $translation_services * * @return array */ private function convert_to_tp_services( $translation_services ) { return Fns::map( Fns::constructN( 1, \WPML_TP_Service::class ), $translation_services ); } /** * @param bool $partner * @return array */ public function get_translation_services( $partner = true ) { return array_values( wp_list_filter( $this->get_all(), array( self::TRANSLATION_MANAGEMENT_SYSTEM => false, self::PARTNER => $partner, ) ) ); } /** * @return array */ public function get_translation_management_systems() { return array_values( wp_list_filter( $this->get_all(), array( self::TRANSLATION_MANAGEMENT_SYSTEM => true ) ) ); } /** * @param bool $reload * * @return null|WPML_TP_Service */ public function get_active( $reload = false ) { return $this->get_one( $this->tp_client->get_project()->get_translation_service_id(), $reload ); } /** * @param int $service_id * @param bool $reload * * @return null|string */ public function get_name( $service_id, $reload = false ) { $translator_name = null; /** @var array $translation_services */ $translation_service = $this->get_one( $service_id, $reload ); if ( null !== $translation_service && isset( $translation_service->name ) ) { $translator_name = $translation_service->name; } return $translator_name; } public function get_service( $service_id, $reload = false ) { return $this->get_one( $service_id, $reload ); } /** * @param int $translation_service_id * @param bool $reload * * @return null|WPML_TP_Service */ private function get_one( $translation_service_id, $reload = false ) { $translation_service = null; if ( ! $translation_service_id ) { return $translation_service; } /** @var array $translation_services */ $translation_services = $this->get_all( $reload ); $translation_services = wp_list_filter( $translation_services, array( 'id' => (int) $translation_service_id, ) ); if ( $translation_services ) { $translation_service = current( $translation_services ); } else { $translation_service = $this->get_unlisted_service( $translation_service_id ); } return $translation_service; } /** * @param string|int $translation_service_id * * @return null|WPML_TP_Service */ private function get_unlisted_service( $translation_service_id ) { $this->endpoint = self::ENDPOINT_SERVICE; $service = parent::get( array( 'service_id' => $translation_service_id ) ); if ( $service instanceof stdClass ) { return new WPML_TP_Service( $service ); } return null; } /** * @param $service_id * * @return array */ public function get_languages_map( $service_id ) { $this->endpoint = self::ENDPOINT_LANGUAGES_MAP; $args = array( 'service_id' => $service_id, ); return parent::get( $args ); } /** * @param $service_id * * @return mixed */ public function get_custom_fields( $service_id ) { $this->endpoint = self::ENDPOINT_CUSTOM_FIELDS; $args = array( 'service_id' => $service_id, ); return parent::get( $args ); } } tp-client/tp-rest-objects/wpml-tp-batch.php 0000755 00000000551 14720342453 0014670 0 ustar 00 <?php /** * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/create_batch_job */ class WPML_TP_Batch extends WPML_TP_REST_Object { private $id; public function get_id() { return $this->id; } public function set_id( $id ) { $this->id = (int) $id; } protected function get_properties() { return array( 'id' => 'id', ); } } tp-client/tp-rest-objects/wpml-tp-job.php 0000755 00000002531 14720342453 0014361 0 ustar 00 <?php /** * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/add_files_batch_job */ class WPML_TP_Job extends WPML_TP_REST_Object { const CANCELLED = 'cancelled'; /** @var int */ private $id; private $cms_id; private $batch; private $job_state; /** @param int $id */ public function set_id( $id ) { $this->id = (int) $id; } /** @return int */ public function get_id() { return $this->id; } /** @return string */ public function get_cms_id() { return $this->cms_id; } /** @return string */ public function get_job_state() { return $this->job_state; } /** * @return int|null */ public function get_original_element_id() { preg_match_all( '/\d+/', $this->get_cms_id(), $matches ); return isset( $matches[0][0] ) ? (int) $matches[0][0] : null; } /** @return stdClass */ public function get_batch() { return $this->batch; } /** * @param int $id */ public function set_cms_id( $id ) { $this->cms_id = $id; } /** * @param string $state */ public function set_job_state( $state ) { $this->job_state = $state; } public function set_batch( stdClass $batch ) { $this->batch = $batch; } /** @return array */ protected function get_properties() { return array( 'id' => 'id', 'batch' => 'batch', 'cms_id' => 'cms_id', 'job_state' => 'job_state', ); } } tp-client/tp-rest-objects/wpml-tp-job-factory.php 0000755 00000000265 14720342453 0016030 0 ustar 00 <?php class WPML_TP_Job_Factory { /** * @param stdClass $job * * @return WPML_TP_Job */ public function create( stdClass $job ) { return new WPML_TP_Job( $job ); } } tp-client/tp-rest-objects/wpml-tp-rest-object.php 0000755 00000001104 14720342453 0016023 0 ustar 00 <?php abstract class WPML_TP_REST_Object { public function __construct( stdClass $object = null ) { $this->populate_properties_from_object( $object ); } abstract protected function get_properties(); /** * @param stdClass|null $object */ protected function populate_properties_from_object( $object ) { if ( $object ) { $properties = $this->get_properties(); foreach ( $properties as $object_property => $new_property ) { if ( isset( $object->{$object_property} ) ) { $this->{"set_$new_property"}( $object->{$object_property} ); } } } } } tp-client/tp-rest-objects/wpml-tp-service.php 0000755 00000035575 14720342453 0015265 0 ustar 00 <?php /** * @link https://git.onthegosystems.com/tp/translation-proxy/wikis/translation_services */ class WPML_TP_Service extends WPML_TP_REST_Object implements Serializable { /** * @var int */ public $id; /** * @var string */ public $logo_url; /** @var string */ public $logo_preview_url; /** * @var string */ public $name; /** * @var string */ public $description; /** * @var string */ public $doc_url; /** * @var bool */ public $tms; /** * @var bool */ public $partner; /** * @var stdClass */ public $custom_fields; /** * @var array * @deprecated */ public $custom_fields_data; /** * @var bool * @deprecated */ public $requires_authentication; /** * @var stdClass */ public $rankings; /** * @var bool */ public $has_language_pairs; /** * @var string */ public $url; /** @var string */ public $project_details_url; /** @var string */ public $add_language_pair_url; /** @var string */ public $custom_text_url; /** @var string */ public $select_translator_iframe_url; /** @var string */ public $translator_contact_iframe_url; /** @var string */ public $quote_iframe_url; /** @var bool */ public $has_translator_selection; /** @var int */ public $project_name_length; /** @var string */ public $suid; /** @var bool */ public $notification; /** @var bool */ public $preview_bundle; /** @var bool */ public $deadline; /** @var bool */ public $oauth; /** @var string */ public $oauth_url; /** @var int */ public $default_service; /** @var bool */ public $translation_feedback; /** @var string */ public $feedback_forward_method; /** @var int */ public $last_refresh; /** @var string */ public $popup_message; /** @var string */ public $how_to_get_credentials_desc; /** @var string */ public $how_to_get_credentials_url; /** @var string */ public $client_create_account_page_url; /** bool */ public $redirect_to_ts; /** @var \stdClass[] */ public $countries = []; public function __construct( stdClass $object = null ) { parent::__construct( $object ); $this->set_custom_fields_data(); $this->set_requires_authentication(); } /** * @return int */ public function get_id() { return $this->id; } /** * @return string */ public function get_logo_url() { return $this->logo_url; } public function get_logo_preview_url() { return $this->logo_preview_url; } /** * @return string */ public function get_name() { return $this->name; } /** * @return string */ public function get_description() { return $this->description; } /** * @return string */ public function get_doc_url() { return $this->doc_url; } /** * @return string */ public function get_tms() { return $this->tms; } /** * @return bool */ public function is_partner() { return $this->partner; } /** * @param bool $partner */ public function set_partner( $partner ) { $this->partner = $partner; } /** * @return array */ public function get_custom_fields() { $custom_fields = array(); /** @TODO: This is odd. It appears that if it's the active service then it's * stored with an extra custom_fields property eg. $this->custom_fields->custom_fields * It looks like we call the api to get the custom field when we activate the service and store * it directly here */ if ( is_object( $this->custom_fields ) && isset( $this->custom_fields->custom_fields ) ) { $custom_fields = $this->custom_fields->custom_fields; } elseif ( isset( $this->custom_fields ) ) { $custom_fields = $this->custom_fields; } return $custom_fields; } /** * @return array */ public function get_custom_fields_data() { return $this->custom_fields_data; } /** * @return bool */ public function get_requires_authentication() { return $this->requires_authentication; } /** * @return bool */ public function get_url() { return $this->url; } /** * @return bool */ public function get_has_language_pairs() { return (bool) $this->has_language_pairs; } /** * @return stdClass */ public function get_rankings() { return $this->rankings; } /** * @return string */ public function get_popup_message() { return $this->popup_message; } /** * @param int $id */ public function set_id( $id ) { $this->id = (int) $id; } /** * @param string $logo_url */ public function set_logo_url( $logo_url ) { $this->logo_url = $logo_url; } /** * @param string $logo_preview_url */ public function set_logo_preview_url( $logo_preview_url ) { $this->logo_preview_url = $logo_preview_url; } /** * @param string $url */ public function set_url( $url ) { $this->url = $url; } /** * @param string $name */ public function set_name( $name ) { $this->name = $name; } /** * @param string $description */ public function set_description( $description ) { $this->description = $description; } /** * @param string $doc_url */ public function set_doc_url( $doc_url ) { $this->doc_url = $doc_url; } /** * @param bool $tms */ public function set_tms( $tms ) { $this->tms = (bool) $tms; } /** * @param stdClass $rankings */ public function set_rankings( $rankings ) { $this->rankings = $rankings; } /** * @param stdClass $custom_fields */ public function set_custom_fields( $custom_fields ) { $this->custom_fields = $custom_fields; } /** * @param string $popup_message */ public function set_popup_message( $popup_message ) { $this->popup_message = $popup_message; } public function set_custom_fields_data() { global $sitepress; $active_service = $sitepress->get_setting( 'translation_service' ); if ( isset( $active_service->custom_fields_data, $active_service->id ) && $this->id === $active_service->id ) { $this->custom_fields_data = $active_service->custom_fields_data; } } public function set_requires_authentication() { $this->requires_authentication = (bool) $this->custom_fields; } /** * @param bool $value */ public function set_has_language_pairs( $value ) { $this->has_language_pairs = (bool) $value; } /** * @return string */ public function get_project_details_url() { return $this->project_details_url; } /** * @param string $project_details_url */ public function set_project_details_url( $project_details_url ) { $this->project_details_url = $project_details_url; } /** * @return string */ public function get_add_language_pair_url() { return $this->add_language_pair_url; } /** * @param string $add_language_pair_url */ public function set_add_language_pair_url( $add_language_pair_url ) { $this->add_language_pair_url = $add_language_pair_url; } /** * @return string */ public function get_custom_text_url() { return $this->custom_text_url; } /** * @param string $custom_text_url */ public function set_custom_text_url( $custom_text_url ) { $this->custom_text_url = $custom_text_url; } /** * @return string */ public function get_select_translator_iframe_url() { return $this->select_translator_iframe_url; } /** * @param string $select_translator_iframe_url */ public function set_select_translator_iframe_url( $select_translator_iframe_url ) { $this->select_translator_iframe_url = $select_translator_iframe_url; } /** * @return string */ public function get_translator_contact_iframe_url() { return $this->translator_contact_iframe_url; } /** * @param string $translator_contact_iframe_url */ public function set_translator_contact_iframe_url( $translator_contact_iframe_url ) { $this->translator_contact_iframe_url = $translator_contact_iframe_url; } /** * @return string */ public function get_quote_iframe_url() { return $this->quote_iframe_url; } /** * @param string $quote_iframe_url */ public function set_quote_iframe_url( $quote_iframe_url ) { $this->quote_iframe_url = $quote_iframe_url; } /** * @return bool */ public function get_has_translator_selection() { return $this->has_translator_selection; } /** * @param bool $has_translator_selection */ public function set_has_translator_selection( $has_translator_selection ) { $this->has_translator_selection = (bool) $has_translator_selection; } /** * @return int */ public function get_project_name_length() { return $this->project_name_length; } /** * @param int $project_name_length */ public function set_project_name_length( $project_name_length ) { $this->project_name_length = (int) $project_name_length; } /** * @return string */ public function get_suid() { return $this->suid; } /** * @param string $suid */ public function set_suid( $suid ) { $this->suid = $suid; } /** * @return bool */ public function get_notification() { return $this->notification; } /** * @param bool $notification */ public function set_notification( $notification ) { $this->notification = (bool) $notification; } /** * @return bool */ public function get_preview_bundle() { return $this->preview_bundle; } /** * @param bool $preview_bundle */ public function set_preview_bundle( $preview_bundle ) { $this->preview_bundle = (bool) $preview_bundle; } /** * @return bool */ public function get_deadline() { return $this->deadline; } /** * @param bool $deadline */ public function set_deadline( $deadline ) { $this->deadline = (bool) $deadline; } /** * @return bool */ public function get_oauth() { return $this->oauth; } /** * @param bool $oauth */ public function set_oauth( $oauth ) { $this->oauth = (bool) $oauth; } /** * @return string */ public function get_oauth_url() { return $this->oauth_url; } /** * @param string $oauth_url */ public function set_oauth_url( $oauth_url ) { $this->oauth_url = $oauth_url; } /** * @return int */ public function get_default_service() { return $this->default_service; } /** * @param int $default_service */ public function set_default_service( $default_service ) { $this->default_service = (int) $default_service; } /** * @return bool */ public function get_translation_feedback() { return $this->translation_feedback; } /** * @param bool $translation_feedback */ public function set_translation_feedback( $translation_feedback ) { $this->translation_feedback = (bool) $translation_feedback; } /** * @return string */ public function get_feedback_forward_method() { return $this->feedback_forward_method; } /** * @param string $feedback_forward_method */ public function set_feedback_forward_method( $feedback_forward_method ) { $this->feedback_forward_method = $feedback_forward_method; } /** @return null|int */ public function get_last_refresh() { return $this->last_refresh; } /** @param int */ public function set_last_refresh( $timestamp ) { $this->last_refresh = $timestamp; } /** @return null|string */ public function get_how_to_get_credentials_desc() { return $this->how_to_get_credentials_desc; } /** @param string */ public function set_how_to_get_credentials_desc( $desc ) { $this->how_to_get_credentials_desc = $desc; } /** @return null|string */ public function get_how_to_get_credentials_url() { return $this->how_to_get_credentials_url; } /** @param string */ public function set_how_to_get_credentials_url( $url ) { $this->how_to_get_credentials_url = $url; } /** @return null|string */ public function get_client_create_account_page_url() { return $this->client_create_account_page_url; } /** @param string */ public function set_client_create_account_page_url( $url ) { $this->client_create_account_page_url = $url; } /** * @return mixed */ public function get_redirect_to_ts() { return $this->redirect_to_ts; } /** * @param mixed $redirect_to_ts */ public function set_redirect_to_ts( $redirect_to_ts ) { $this->redirect_to_ts = $redirect_to_ts; } /** * @return stdClass[] */ public function get_countries() { return $this->countries; } /** * @param stdClass[] $countries */ public function set_countries( array $countries ) { $this->countries = $countries; } public function serialize() { return serialize( $this->__serialize() ); } public function unserialize( $serialized ) { $simple_array = unserialize( $serialized ); $this->__unserialize( $simple_array ); } /* phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound */ public function __serialize() { return get_object_vars( $this ); } /* phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound */ public function __unserialize( $simple_array ) { foreach ( $simple_array as $property => $value ) { if ( property_exists( $this, $property ) ) { $this->$property = $value; } } } /** * @return array */ protected function get_properties() { return [ 'id' => 'id', 'logo_url' => 'logo_url', 'logo_preview_url' => 'logo_preview_url', 'url' => 'url', 'name' => 'name', 'description' => 'description', 'doc_url' => 'doc_url', 'tms' => 'tms', 'partner' => 'partner', 'custom_fields' => 'custom_fields', 'custom_fields_data' => 'custom_fields_data', 'requires_authentication' => 'requires_authentication', 'has_language_pairs' => 'has_language_pairs', 'rankings' => 'rankings', 'popup_message' => 'popup_message', 'project_details_url' => 'project_details_url', 'add_language_pair_url' => 'add_language_pair_url', 'custom_text_url' => 'custom_text_url', 'select_translator_iframe_url' => 'select_translator_iframe_url', 'translator_contact_iframe_url' => 'translator_contact_iframe_url', 'quote_iframe_url' => 'quote_iframe_url', 'has_translator_selection' => 'has_translator_selection', 'project_name_length' => 'project_name_length', 'suid' => 'suid', 'notification' => 'notification', 'preview_bundle' => 'preview_bundle', 'deadline' => 'deadline', 'oauth' => 'oauth', 'oauth_url' => 'oauth_url', 'default_service' => 'default_service', 'translation_feedback' => 'translation_feedback', 'feedback_forward_method' => 'feedback_forward_method', 'last_refresh' => 'last_refresh', 'how_to_get_credentials_desc' => 'how_to_get_credentials_desc', 'how_to_get_credentials_url' => 'how_to_get_credentials_url', 'client_create_account_page_url' => 'client_create_account_page_url', 'redirect_to_ts?' => 'redirect_to_ts', 'countries' => 'countries', ]; } } notices/wpml-tm-post-edit-notices-factory.php 0000755 00000002027 14720342453 0015343 0 ustar 00 <?php class WPML_TM_Post_Edit_Notices_Factory { const TEMPLATES_PATH = '/templates/notices/post-edit/'; public function create() { /** * @var SitePress $sitepress * @var WPML_TM_Translation_Status_Display $wpml_tm_status_display_filter */ global $sitepress, $wpml_tm_status_display_filter; $status_helper = wpml_get_post_status_helper(); $paths = array( WPML_TM_PATH . self::TEMPLATES_PATH ); $template_service_loader = new WPML_Twig_Template_Loader( $paths ); $template_service = $template_service_loader->get_template(); $super_globals = new WPML_Super_Globals_Validation(); if ( ! $wpml_tm_status_display_filter ) { wpml_tm_load_status_display_filter(); } return new WPML_TM_Post_Edit_Notices( $status_helper, $sitepress, $template_service, $super_globals, $wpml_tm_status_display_filter, new WPML_Translation_Element_Factory( $sitepress, new WPML_WP_Cache() ), new WPML_TM_ATE(), new WPML_TM_Rest_Job_Translator_Name(), new WPML_TM_Rest_Jobs_Translation_Service() ); } } notices/class-wpml-notice.php 0000755 00000021046 14720342453 0012274 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Notice { private $display_callbacks = array(); private $id; private $text; private $collapsed_text; private $group = 'default'; private $restricted_to_user_ids = array(); private $actions = array(); /** * @see \WPML_Notice::set_css_class_types * @var array */ private $css_class_types = array(); private $css_classes = array(); private $dismissible = false; private $exclude_from_pages = array(); private $hideable = false; private $collapsable = false; private $restrict_to_pages = array(); private $restrict_to_page_prefixes = array(); private $restrict_to_screen_ids = array(); private $hide_if_notice_exists = null; private $dismissible_for_different_text = true; private $default_group_name = 'default'; private $capabilities = array(); private $dismiss_reset = false; /* * @var bool * @since 4.1.0 */ private $flash = false; /** * @var string */ private $nonce_action; /** @var bool */ private $text_only = false; /** * WPML_Admin_Notification constructor. * * @param int|string $id * @param string $text * @param string $group */ public function __construct( $id, $text, $group = 'default' ) { $this->id = $id; $this->text = $text; $this->group = $group ? $group : $this->default_group_name; } public function add_action( WPML_Notice_Action $action ) { $this->actions[] = $action; if ( $action->can_dismiss() ) { $this->dismissible = true; } if ( ! $action->can_dismiss_different_text() ) { $this->dismissible_for_different_text = false; } if ( $action->can_hide() ) { $this->hideable = true; } } public function add_exclude_from_page( $page ) { $this->exclude_from_pages[] = $page; } public function add_restrict_to_page( $page ) { $this->restrict_to_pages[] = $page; } /** @param int $user_id */ public function add_user_restriction( $user_id ) { $user_id = (int) $user_id; $this->restricted_to_user_ids[ $user_id ] = $user_id; } /** @param int $user_id */ public function remove_user_restriction( $user_id ) { unset( $this->restricted_to_user_ids[ (int) $user_id ] ); } /** @return array */ public function get_restricted_user_ids() { return $this->restricted_to_user_ids; } /** @return bool */ public function is_user_restricted() { return (bool) $this->restricted_to_user_ids; } /** @return bool */ public function is_for_current_user() { return ! $this->restricted_to_user_ids || array_key_exists( get_current_user_id(), $this->restricted_to_user_ids ); } /** * @return bool */ public function is_user_cap_allowed() { $user_can = true; foreach ( $this->capabilities as $cap ) { $user_can = current_user_can( $cap ); if ( $user_can ) { break; } } return $user_can; } public function can_be_dismissed() { return $this->dismissible; } public function can_be_dismissed_for_different_text() { return $this->dismissible_for_different_text; } public function can_be_hidden() { return $this->hideable; } /** * @return bool */ public function can_be_collapsed() { return $this->collapsable; } /** * As the notice is supposed to be serialized and stored into the DB, * the callback should be only a function or a static method. * * Before to use a callback, please check the existing options with: * - add_exclude_from_page * - add_restrict_to_page * - add_user_restriction * - add_capability_check * * @param callable $callback */ public function add_display_callback( $callback ) { if ( ! is_callable( $callback ) ) { throw new UnexpectedValueException( '\WPML_Notice::add_display_callback expects a callable', 1 ); } $this->display_callbacks[] = $callback; } public function add_capability_check( array $cap ) { $this->capabilities = $cap; } public function get_display_callbacks() { return $this->display_callbacks; } /** * @return array<\WPML_Notice_Action> */ public function get_actions() { return $this->actions; } public function get_css_classes() { return $this->css_classes; } /** * @param string|array $css_classes */ public function set_css_classes( $css_classes ) { if ( ! is_array( $css_classes ) ) { $css_classes = explode( ' ', $css_classes ); } $this->css_classes = $css_classes; } public function get_exclude_from_pages() { return $this->exclude_from_pages; } /** * @return string */ public function get_group() { return $this->group; } /** * @return int|string */ public function get_id() { return $this->id; } public function set_restrict_to_page_prefixes( array $page_prefixes ) { $this->restrict_to_page_prefixes = $page_prefixes; } /** * @return array */ public function get_restrict_to_page_prefixes() { return $this->restrict_to_page_prefixes; } public function get_restrict_to_pages() { return $this->restrict_to_pages; } public function set_restrict_to_screen_ids( array $screens ) { $this->restrict_to_screen_ids = $screens; } /** * @return array */ public function get_restrict_to_screen_ids() { return $this->restrict_to_screen_ids; } public function get_nonce_action() { return $this->nonce_action; } /** * @return string */ public function get_text() { $notice = array( 'id' => $this->get_id(), 'group' => $this->get_group(), ); $this->text = apply_filters( 'wpml_notice_text', $this->text, $notice ); return $this->text; } public function get_css_class_types() { return $this->css_class_types; } /** * @return string */ public function get_collapsed_text() { return $this->collapsed_text; } /** * Use this to set the look of the notice. * WordPress recognize these values: * - notice-error * - notice-warning * - notice-success * - notice-info * You can use the above values with or without the "notice-" prefix: * the prefix will be added automatically in the HTML, if missing. * * @see https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices for more details * * @param string|array $types Accepts either a space separated values string, or an array of values. * @return WPML_Notice */ public function set_css_class_types( $types ) { $this->css_class_types = is_array( $types ) ? $types : explode( ' ', $types ); return $this; } /** * @param bool $dismissible */ public function set_dismissible( $dismissible ) { $this->dismissible = $dismissible; } public function set_exclude_from_pages( array $pages ) { $this->exclude_from_pages = $pages; } public function set_hide_if_notice_exists( $notice_id, $notice_group = null ) { $this->hide_if_notice_exists = array( 'id' => $notice_id, 'group' => $notice_group, ); } public function get_hide_if_notice_exists() { return $this->hide_if_notice_exists; } /** * @param bool $hideable */ public function set_hideable( $hideable ) { $this->hideable = $hideable; } /** * @param bool $collapsable */ public function set_collapsable( $collapsable ) { $this->collapsable = $collapsable; } /** * @param string $action */ public function set_nonce_action( $action ) { $this->nonce_action = $action; } /** * @param string $collapsed_text */ public function set_collapsed_text( $collapsed_text ) { $this->collapsed_text = $collapsed_text; } public function set_restrict_to_pages( array $pages ) { $this->restrict_to_pages = $pages; } public function reset_dismiss() { $this->dismiss_reset = true; } public function must_reset_dismiss() { return $this->dismiss_reset; } public function is_different( WPML_Notice $other_notice ) { return serialize( $this ) !== serialize( $other_notice ); } /** * Set notice to only display once. * * @param bool $flash * * @return WPML_Notice * @since 4.1.0 */ public function set_flash( $flash = true ) { $this->flash = (bool) $flash; return $this; } /** * @return bool * @since 4.1.0 */ public function is_flash() { return $this->flash; } /** * @return bool */ public function should_be_text_only() { return $this->text_only; } /** * @param bool $text_only */ public function set_text_only( $text_only ) { $this->text_only = $text_only; } /** * @param int|string $id * @param string $text * @param string $group * * @return WPML_Notice */ public static function make( $id, $text, $group = 'default' ) { return new WPML_Notice( $id, $text, $group ); } } notices/DismissNotices.php 0000755 00000002371 14720342453 0011673 0 ustar 00 <?php namespace WPML\Notices; class DismissNotices implements \IWPML_Backend_Action { const OPTION = 'wpml_dismiss_notice'; const CSS_CLASS = 'wpml_dismiss_notice'; public function add_hooks() { add_action( 'wp_ajax_wpml_dismiss_notice', [ $this, 'toggleDismiss' ] ); add_action( 'admin_enqueue_scripts', function () { wp_enqueue_script( 'wpml-dismiss-notice', ICL_PLUGIN_URL . '/dist/js/notices/app.js', [], ICL_SITEPRESS_VERSION ); } ); } public function toggleDismiss() { $postData = wpml_collect( $_POST ); $id = $postData->get( 'id', null ); if ( ! $id ) { return wp_send_json_error( 'ID of notice is not defined' ); } $options = get_option( self::OPTION, [] ); $options[ $id ] = $postData->get( 'dismiss', false ) === 'true'; update_option( self::OPTION, $options ); return wp_send_json_success(); } /** * @param int $id * * @return bool */ public function isDismissed( $id ) { return wpml_collect( get_option( self::OPTION, [] ) )->get( $id, false ); } /** * @param int $id * * @return string */ public function renderCheckbox( $id ) { return sprintf( '<input type="checkbox" class="%s" data-id="%s" />', self::CSS_CLASS, $id ); } } notices/wpml-tm-post-edit-notices.php 0000755 00000044071 14720342453 0013703 0 ustar 00 <?php use WPML\API\Sanitize; use WPML\TM\API\Jobs; class WPML_TM_Post_Edit_Notices { const TEMPLATE_TRANSLATION_IN_PROGRESS = 'translation-in-progress.twig'; const TEMPLATE_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS = 'edit-original-translation-in-progress.twig'; const TEMPLATE_USE_PREFERABLY_TM_DASHBOARD = 'use-preferably-tm-dashboard.twig'; const TEMPLATE_USE_PREFERABLY_TE = 'use-preferably-translation-editor.twig'; const DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION = 'wpml_dismiss_post_edit_original_te_notice'; const DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION = 'wpml_dismiss_post_edit_te_notice'; const DISPLAY_LIMIT_TRANSLATIONS_IN_PROGRESS = 5; /** @var WPML_Post_Status $post_status */ private $post_status; /** @var SitePress $sitepress */ private $sitepress; /** @var IWPML_Template_Service $template_render */ private $template_render; /** @var WPML_Super_Globals_Validation $super_globals */ private $super_globals; /** @var WPML_TM_Translation_Status_Display $status_display */ private $status_display; /** @var WPML_Translation_Element_Factory $element_factory */ private $element_factory; /** @var WPML_TM_ATE $tm_ate */ private $tm_ate; /** @var WPML_TM_Rest_Job_Translator_Name $translator_name */ private $translator_name; /** @var WPML_TM_Rest_Jobs_Translation_Service $translation_service */ private $translation_service; /** * @param WPML_Post_Status $post_status * @param SitePress $sitepress * @param IWPML_Template_Service $template_render * @param WPML_Super_Globals_Validation $super_globals * @param WPML_TM_Translation_Status_Display $status_display * @param WPML_Translation_Element_Factory $element_factory */ public function __construct( WPML_Post_Status $post_status, SitePress $sitepress, IWPML_Template_Service $template_render, WPML_Super_Globals_Validation $super_globals, WPML_TM_Translation_Status_Display $status_display, WPML_Translation_Element_Factory $element_factory, WPML_TM_ATE $tm_ate, WPML_TM_Rest_Job_Translator_Name $translator_name, WPML_TM_Rest_Jobs_Translation_Service $translation_service ) { $this->post_status = $post_status; $this->sitepress = $sitepress; $this->template_render = $template_render; $this->super_globals = $super_globals; $this->status_display = $status_display; $this->element_factory = $element_factory; $this->tm_ate = $tm_ate; $this->translator_name = $translator_name; $this->translation_service = $translation_service; } public function add_hooks() { $request_get_trid = isset( $_GET['trid'] ) ? filter_var( $_GET['trid'], FILTER_SANITIZE_NUMBER_INT ) : ''; $request_get_post = isset( $_GET['post'] ) ? filter_var( $_GET['post'], FILTER_SANITIZE_NUMBER_INT ) : ''; if ( $request_get_trid || $request_get_post ) { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_action( 'admin_notices', array( $this, 'display_notices' ) ); } add_action( 'wp_ajax_' . self::DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION, array( $this, 'do_not_display_it_again_to_user' ) ); add_action( 'wp_ajax_' . self::DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION, array( $this, 'do_not_display_it_again' ) ); } public function enqueue_assets() { wp_enqueue_script( 'wpml-tm-post-edit-alert', WPML_TM_URL . '/res/js/post-edit-alert.js', array( 'jquery', 'jquery-ui-dialog' ), ICL_SITEPRESS_VERSION ); } public function display_notices() { $trid = $this->super_globals->get( 'trid', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $post_id = $this->super_globals->get( 'post', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $lang = $this->super_globals->get( 'lang', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); if ( ! $post_id ) { return; } $post_element = $this->element_factory->create( $post_id, 'post' ); $is_original = ! $post_element->get_source_language_code(); if ( ! $trid ) { $trid = $post_element->get_trid(); } if ( $trid ) { $translations_in_progress = $is_original ? $this->get_translations_in_progress( $post_element ) : []; if ( ! empty( $translations_in_progress ) && $this->should_display_it_to_user( self::DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION ) ) { $translations_in_progress = $this->prepare_translations_for_gui ( $translations_in_progress ); $msg_stale_job = $this->prepare_stale_jobs_for_gui( $translations_in_progress ); $model = array( 'warning' => sprintf( __( '%sTranslation in progress - wait before editing%s', 'wpml-translation-management' ), '<strong>', '</strong>' ), 'message' => __( 'This page that you are editing is being translated right now. If you edit now, some or all of the translation for this page may be missing. It\'s best to wait until translation completes, then edit and update the translation.', 'wpml-translation-management' ), 'translations_in_progress' => [ 'display_limit' => self::DISPLAY_LIMIT_TRANSLATIONS_IN_PROGRESS, 'translations' => $translations_in_progress, 'title' => __( 'Waiting for translators...', 'sitepress' ), /* translators: %d is the number of translations. */ 'more' => __( '...and %d more translations.', 'sitepress' ), 'no_translator' => __( 'First available translator', 'sitepress' ), 'msg_stale_job' => $msg_stale_job, ], 'go_back_button' => __( 'Take me back', 'wpml-translation-management' ), 'edit_anyway_button' => __( 'I understand - continue editing', 'wpml-translation-management' ), 'do_not_show_again' => __( "Don't show this warning again", 'wpml-translation-management' ), 'do_not_show_again_action' => self::DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION, 'nonce' => wp_nonce_field( self::DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION, self::DO_NOT_SHOW_AGAIN_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS_ACTION, true, false ), ); echo $this->template_render->show( $model, self::TEMPLATE_EDIT_ORIGINAL_TRANSLATION_IN_PROGRESS ); }elseif ( $this->is_waiting_for_a_translation( (int) $this->post_status->get_status( $post_id, $trid, $lang ) ) ) { $model = array( 'warning' => sprintf( __( '%sWarning:%s You are trying to edit a translation that is currently in the process of being added using WPML.', 'wpml-translation-management' ), '<strong>', '</strong>' ), 'check_dashboard' => sprintf( __( 'Please refer to the <a href="%s">Translation Management dashboard</a> for the exact status of this translation.', 'wpml-translation-management' ), admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&' ) ), ); echo $this->template_render->show( $model, self::TEMPLATE_TRANSLATION_IN_PROGRESS ); } elseif ( ! $is_original && WPML_TM_Post_Edit_TM_Editor_Mode::is_using_tm_editor( $this->sitepress, $post_id ) && apply_filters( 'wpml_tm_show_page_builders_translation_editor_warning', true, $post_id ) && $this->should_display_it( self::DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION ) ) { $model = array( 'warning' => sprintf( __( '%sWarning:%s You are trying to edit a translation using the standard WordPress editor but your site is configured to use the WPML Translation Editor.', 'wpml-translation-management' ), '<strong>', '</strong>' ), 'go_back_button' => __( 'Go back', 'wpml-translation-management' ), 'edit_anyway_button' => __( 'Edit anyway', 'wpml-translation-management' ), 'open_in_te_button' => __( 'Open in Translation Editor', 'wpml-translation-management' ), 'translation_editor_url' => $this->get_translation_editor_link( $post_element ), 'do_not_show_again' => __( "Don't show this warning again", 'wpml-translation-management' ), 'do_not_show_again_action' => self::DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION, 'nonce' => wp_nonce_field( self::DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION, self::DO_NOT_SHOW_AGAIN_USE_PREFERABLY_TE_ACTION, true, false ), ); echo $this->template_render->show( $model, self::TEMPLATE_USE_PREFERABLY_TE ); } } elseif ( $post_element->is_translatable() && WPML_TM_Post_Edit_TM_Editor_Mode::is_using_tm_editor( $this->sitepress, $post_id ) ){ $model = array( 'warning' => sprintf( __('%sWarning:%s You are trying to add a translation using the standard WordPress editor but your site is configured to use the WPML Translation Editor.' , 'wpml-translation-management'), '<strong>', '</strong>' ), 'use_tm_dashboard' => sprintf( __( 'You should use <a href="%s">Translation management dashboard</a> to send the original document to translation.' , 'wpml-translation-management' ), admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php' ) ), ); echo $this->template_render->show( $model, self::TEMPLATE_USE_PREFERABLY_TM_DASHBOARD ); } } public function do_not_display_it_again_to_user() { $action = Sanitize::stringProp( 'action', $_POST ); if( is_string( $action ) && $this->is_valid_request( $action ) ){ update_user_option( get_current_user_id(), $action, 1 ); } } public function do_not_display_it_again() { $action = Sanitize::stringProp( 'action', $_POST ); if( is_string( $action ) && $this->is_valid_request( $action ) ){ update_option( $action, 1, false ); } } /** * @return bool */ private function is_valid_request( $action ) { return isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], $action ); } /** * @return bool */ private function should_display_it_to_user( $action ) { return false === get_user_option( $action ); } /** * @return bool */ private function should_display_it( $action ) { return false === get_option( $action ); } /** * @param WPML_Translation_Element $post_element * * @return bool */ private function get_translations_in_progress( $post_element ) { $translations = $this->sitepress->get_element_translations( $post_element->get_trid(), $post_element->get_wpml_element_type() ); if ( ! is_array( $translations ) || empty( $translations ) ) { return []; } $wpml_element_translations = wpml_tm_load_element_translations(); $translations_in_progress = []; foreach ( $translations as $translation ) { if ( $translation->original ) { continue; } $job = Jobs::getTridJob( $post_element->get_trid(), $translation->language_code ); if ( $job && ! $this->is_waiting_for_a_translation( $job->status ) ) { // Translation is completed - no need for further checks. continue; } // For the case that the user opened ATE from post edit screen // and comes back to the post edit screen, WPML needs to fetch // the ATE status directly from ATE API as it's not in the DB yet. // // The check for HTTP_REFERER is quite fragile (can be manipulated) // but totally fine for this case as it's only about showing // a warning or not. In addition the ATE UI "Complete" and "Back" // links are baked in JS so it's not possible to open them in a // new tab/window by usual browser controls. if ( $job && 'ate' === $job->editor && array_key_exists( 'referer', $_GET ) && 'ate' === $_GET['referer'] && $this->tm_ate->is_translation_method_ate_enabled() && $this->tm_ate->is_translation_ready_for_post( $post_element->get_trid(), $translation->language_code ) ) { continue; } if ( $this->is_waiting_for_a_translation( $wpml_element_translations->get_translation_status( $post_element->get_trid(), $translation->language_code ) ) ) { $translations_in_progress[] = $job; } } return $translations_in_progress; } private function prepare_translations_for_gui( $translations ) { // Prepare data for GUI. $translations = array_map( [ $this, 'prepare_translation_for_gui' ], $translations ); // Sort languages by language name (as usual). usort( $translations, function ( $a, $b ) { if ( $a['to_language'] == $b['to_language'] ) { return 0; } return $a['to_language'] > $b['to_language'] ? 1 : - 1; } ); return $translations; } private function prepare_translation_for_gui( $job ) { if ( ! is_object( $job ) || ! property_exists( $job, 'language_code' ) || ! property_exists( $job, 'to_language' ) ) { return; } $since = null; if ( property_exists( $job, 'elements' ) && is_array( $job->elements ) ) { foreach ( $job->elements as $element ) { if ( ! property_exists( $element, 'timestamp' ) || ! property_exists( $element, 'field_finished' ) || 0 !== (int) $element->field_finished ) { // No valid element or already finished. continue; } $element_since = strtotime( $element->timestamp ); $since = null === $since || $element_since < $since ? $element_since : $since; } } $is_automatic = property_exists( $job, 'automatic' ) ? (bool) $job->automatic : false; $editor_job_id = property_exists( $job, 'editor_job_id' ) ? (int) $job->editor_job_id : null; return [ 'to_language' => $job->to_language, 'is_automatic' => $is_automatic, 'flag' => $this->sitepress->get_flag_image( $job->language_code ), 'translator' => $this->translator_name_by_job( $job ), 'since' => $since, 'waiting_for' => $this->waiting_for_x_time( $since ), 'editor_job_id' => $editor_job_id, ]; } private function translator_name_by_job( $job ) { if ( property_exists( $job, 'automatic' ) && 1 === (int) $job->automatic ) { // Automatic Translation. return __( 'Automatic translation', 'sitepress' ); } if ( property_exists( $job, 'translation_service' ) && is_numeric( $job->translation_service ) ) { // Translation Service. return $this->translation_service ->get_name( $job->translation_service ); } // Translator. return property_exists( $job, 'translator_id' ) ? $this->translator_name->get( $job->translator_id ) : null; } private function waiting_for_x_time( $since_timestamp ) { if ( empty( $since_timestamp ) ) { return ''; } $since = new \DateTime(); $since->setTimestamp( $since_timestamp ); $interval = $since->diff( new \DateTime() ); // Use: x day(s) if translation is longer than 24 hours in progress. if ( $interval->days > 0 ) { return sprintf( /* translators: %d is for the number of day(s). */ _n( '%d day', '%d days', $interval->days, 'sitepress' ), $interval->days ); } // Use: x hour(s) if translation is longer than 1 hour in progress. if ( $interval->h > 0 ) { return sprintf( /* translators: %d is for the number of hour(s). */ _n( '%d hour', '%d hours', $interval->h, 'sitepress' ), $interval->h ); } // Use: x minute(s) if translation is less than a hour in progress. return sprintf( /* translators: %d is for the number of minute(s). */ _n( '%d minute', '%d minutes', $interval->i, 'sitepress' ), $interval->i ); } /** * Stale jobs are automatic translated jobs, which are in progress for * 1 or more days. As there is a limit of jobs being shown, this method * makes sure to move the stale job to the top of the list and returns * an error message, asking the user to contact support with all * stale job ate ids. * * @param array $translations * @return string */ private function prepare_stale_jobs_for_gui( &$translations ) { $stale_ids = []; foreach ( $translations as $k => $translation ) { if ( $translation === null || ! $translation['is_automatic'] ) { continue; } $since = new \DateTime(); $since->setTimestamp( $translation['since'] ); $interval = $since->diff( new \DateTime() ); if ( 0 === $interval->days ) { // All good with this translation. continue; } $stale_ids[] = $translation['editor_job_id']; if ( count( $translations ) > self::DISPLAY_LIMIT_TRANSLATIONS_IN_PROGRESS ) { // More translations in progress as the dialog shows. // Move this stale automatic translation to top. unset( $translations[ $k ] ); array_unshift( $translations, $translation ); } } if ( empty( $stale_ids ) ) { return ''; } return sprintf( /* translators: %1$1s and %2$2s is used for adding html tags to make WPML support a link. %3$s is a list of numeric ids. */ _n( 'Something went wrong with automatic translation. Please contact %1$1sWPML support%2$2s and report that the following automatic translation is stuck: %3$3s', 'Something went wrong with automatic translation. Please contact %1$1sWPML support%2$2s and report that the following automatic translations are stuck: %3$3s', count( $stale_ids ), 'sitepress' ), '<a href="https://wpml.org/forums/forum/english-support/?utm_source=wpmlplugin&utm_campaign=content-editing&utm_medium=post-editor&utm_term=translation-in-progress/" target="_blank">', '</a>', implode( ', ', $stale_ids ) ); } /** * @param int|null $translation_status * * @return bool */ private function is_waiting_for_a_translation( $translation_status ) { return ! is_null( $translation_status ) && $translation_status > 0 && $translation_status != ICL_TM_DUPLICATE && $translation_status < ICL_TM_COMPLETE; } /** * @param WPML_Translation_Element $post_element * * @return string */ private function get_translation_editor_link( $post_element ) { $post_id = $post_element->get_id(); $source_post_element = $post_element->get_source_element(); if ( $source_post_element ) { $post_id = $source_post_element->get_id(); } $url = $this->status_display->filter_status_link( '#', $post_id, $post_element->get_language_code(), $post_element->get_trid() ); return remove_query_arg( 'return_url', $url ); } } notices/pages/class-wpml-notice-show-on-dashboard-and-wpml-pages.php 0000755 00000001012 14720342453 0021511 0 ustar 00 <?php class WPML_Notice_Show_On_Dashboard_And_WPML_Pages { public static function is_on_page() { if ( function_exists( 'get_current_screen' ) ) { $screen = get_current_screen(); if ( 'dashboard' === $screen->id ) { return true; } } $current_page = array_key_exists( 'page', $_GET ) ? $_GET['page'] : null; foreach ( array( 'sitepress-multilingual-cms', 'wpml-translation-management' ) as $page ) { if ( strpos( $current_page, $page ) === 0 ) { return true; } } return false; } } notices/class-wpml-notice-render.php 0000755 00000024767 14720342453 0013566 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Notice_Render { private $dismiss_html_added; private $hide_html_added; private $collapse_html_added; public function render( WPML_Notice $notice ) { echo $this->get_html( $notice ); } /** * @param WPML_Notice $notice * * @return string */ public function get_html( WPML_Notice $notice ) { $result = ''; if ( $this->must_display_notice( $notice ) ) { if ( $notice->should_be_text_only() ) { return $notice->get_text(); } $actions_html = $this->get_actions_html( $notice ); $temp_types = $notice->get_css_class_types(); foreach ( $temp_types as $temp_type ) { if ( strpos( $temp_type, 'notice-' ) === false ) { $temp_types[] = 'notice-' . $temp_type; } if ( strpos( $temp_type, 'notice-' ) === 0 ) { $temp_types[] = substr( $temp_type, 0, strlen( 'notice-' ) ); } } $temp_classes = $notice->get_css_classes(); $classes = array_merge( $temp_classes, $temp_types ); if ( $this->hide_html_added || $this->dismiss_html_added || $notice->can_be_hidden() || $notice->can_be_dismissed() ) { $classes[] = 'is-dismissible'; } $classes[] = 'notice'; $classes[] = 'otgs-notice'; $classes = array_unique( $classes ); $class = implode( ' ', $classes ); $result .= '<div class="' . $class . '" data-id="' . esc_attr( (string) $notice->get_id() ) . '" data-group="' . esc_attr( $notice->get_group() ) . '"'; $result .= $this->get_data_nonce_attribute(); if ( $this->hide_html_added || $notice->can_be_hidden() ) { $result .= ' data-hide-text="' . __( 'Hide', 'sitepress' ) . '" '; } $result .= '>'; if ( $notice->can_be_collapsed() ) { $result .= $this->sanitize_and_format_text( $this->get_collapsed_html( $notice ) ); } else { $result .= '<p>' . $this->sanitize_and_format_text( $notice->get_text() ) . '</p>'; } $this->dismiss_html_added = false; $this->hide_html_added = false; $this->collapse_html_added = false; $result .= $actions_html; if ( $notice->can_be_hidden() ) { $result .= $this->get_hide_html(); } if ( $notice->can_be_dismissed() ) { $result .= $this->get_dismiss_html(); } if ( $notice->can_be_collapsed() ) { $result .= $this->get_collapse_html(); } if ( $notice->get_nonce_action() ) { $result .= $this->add_nonce( $notice ); } $result .= '</div>'; } return $result; } /** * @param WPML_Notice $notice * * @return string */ private function add_nonce( $notice ) { return wp_nonce_field( $notice->get_nonce_action(), $notice->get_nonce_action(), true, false ); } public function must_display_notice( WPML_Notice $notice ) { if ( ! $notice->is_for_current_user() || ! $notice->is_user_cap_allowed() ) { return false; } return $this->is_current_page_allowed( $notice ) && $this->is_allowed_by_callback( $notice ); } /** * @param WPML_Notice $notice * * @return string */ private function get_actions_html( WPML_Notice $notice ) { $actions_html = ''; if ( $notice->get_actions() ) { $actions_html .= '<div class="otgs-notice-actions">'; foreach ( $notice->get_actions() as $action ) { $actions_html .= $this->get_action_html( $action ); } $actions_html .= '</div>'; return $actions_html; } return $actions_html; } private function sanitize_and_format_text( $text ) { $backticks_pattern = '|`(.*)`|U'; preg_match_all( $backticks_pattern, $text, $matches ); $sanitized_notice = $text; if ( 2 === count( $matches ) ) { /** @var array<string> $matches_to_sanitize */ $matches_to_sanitize = $matches[1]; foreach ( $matches_to_sanitize as &$match_to_sanitize ) { $match_to_sanitize = '<pre>' . esc_html( $match_to_sanitize ) . '</pre>'; } unset( $match_to_sanitize ); $sanitized_notice = str_replace( $matches[0], $matches_to_sanitize, $sanitized_notice ); } return stripslashes( $sanitized_notice ); } /** * @param null|string $localized_text * * @return string */ private function get_hide_html( $localized_text = null ) { $hide_html = ''; $hide_html .= '<span class="otgs-notice-hide notice-hide"><span class="screen-reader-text">'; if ( $localized_text ) { $hide_html .= esc_html( $localized_text ); } else { $hide_html .= esc_html__( 'Hide this notice.', 'sitepress' ); } $hide_html .= '</span></span>'; return $hide_html; } /** * @param null|string $localized_text * * @return string */ private function get_dismiss_html( $localized_text = null ) { $dismiss_html = ''; $dismiss_html .= '<span class="otgs-notice-dismiss notice-dismiss">'; $dismiss_html .= '<span class="screen-reader-text"><input class="otgs-notice-dismiss-check" type="checkbox" value="1" />'; if ( $localized_text ) { $dismiss_html .= esc_html( $localized_text ); } else { $dismiss_html .= esc_html__( 'Dismiss this notice.', 'sitepress' ); } $dismiss_html .= '</span></span>'; return $dismiss_html; } /** * @param string|null $localized_text * * @return string */ private function get_collapse_html( $localized_text = null ) { $hide_html = '<span class="otgs-notice-collapse-hide"><span class="screen-reader-text">'; if ( $localized_text ) { $hide_html .= esc_html( $localized_text ); } else { $hide_html .= esc_html__( 'Hide this notice.', 'sitepress' ); } $hide_html .= '</span></span>'; return $hide_html; } /** * @param WPML_Notice $notice * @param string|null $localized_text * * @return string */ private function get_collapsed_html( WPML_Notice $notice, $localized_text = null ) { $content = ' <div class="otgs-notice-collapsed-text"> <p>%s <span class="otgs-notice-collapse-show notice-collapse"><span class="screen-reader-text"> %s </span></span> </p> </div> <div class="otgs-notice-collapse-text"> %s </div> '; $content = sprintf( $content, $notice->get_collapsed_text(), $localized_text ? esc_html( $localized_text ) : esc_html__( 'Show this notice.', 'sitepress' ), $notice->get_text() ); return $content; } /** * @param WPML_Notice_Action $action * * @return string */ private function get_action_html( $action ) { $action_html = ''; if ( $action->can_hide() ) { $action_html .= $this->get_hide_html( $action->get_text() ); $this->hide_html_added = true; } elseif ( $action->can_dismiss() ) { $action_html .= $this->get_dismiss_html( $action->get_text() ); $this->dismiss_html_added = true; } else { if ( $action->get_url() ) { $action_html .= $this->get_action_anchor( $action ); } else { $action_html .= $action->get_text(); } } return $action_html; } /** * @param WPML_Notice_Action $action * * @return string */ private function get_action_anchor( WPML_Notice_Action $action ) { $anchor_attributes = array(); $action_url = '<a'; $anchor_attributes['href'] = esc_url_raw( $action->get_url() ); if ( $action->get_link_target() ) { $anchor_attributes['target'] = $action->get_link_target(); } $action_url_classes = array( 'notice-action' ); if ( $action->must_display_as_button() ) { $button_style = 'button-secondary'; if ( is_string( $action->must_display_as_button() ) ) { $button_style = $action->must_display_as_button(); } $action_url_classes[] = esc_attr( $button_style ); $action_url_classes[] = 'notice-action-' . esc_attr( $button_style ); } else { $action_url_classes[] = 'notice-action-link'; } $anchor_attributes['class'] = implode( ' ', $action_url_classes ); if ( $action->get_group_to_dismiss() ) { $anchor_attributes['data-dismiss-group'] = esc_attr( $action->get_group_to_dismiss() ); } if ( $action->get_js_callback() ) { $anchor_attributes['data-js-callback'] = esc_attr( $action->get_js_callback() ) . '"'; } foreach ( $anchor_attributes as $name => $value ) { $action_url .= ' ' . $name . '="' . $value . '"'; } $action_url .= $this->get_data_nonce_attribute(); $action_url .= '>'; $action_url .= $action->get_text(); $action_url .= '</a>'; return $action_url; } /** * @return string */ private function get_data_nonce_attribute() { return ' data-nonce="' . wp_create_nonce( WPML_Notices::NONCE_NAME ) . '"'; } /** * @param WPML_Notice $notice * * @return bool */ private function is_current_screen_allowed( WPML_Notice $notice ) { $allow_current_screen = true; $restrict_to_screen_ids = $notice->get_restrict_to_screen_ids(); if ( $restrict_to_screen_ids && function_exists( 'get_current_screen' ) ) { $screen = get_current_screen(); $allow_current_screen = $screen && in_array( $screen->id, $restrict_to_screen_ids, true ); } return $allow_current_screen; } /** * @param WPML_Notice $notice * @param string $current_page * * @return bool */ private function is_current_page_prefix_allowed( WPML_Notice $notice, $current_page ) { $restrict_to_page_prefixes = $notice->get_restrict_to_page_prefixes(); if ( $current_page && $restrict_to_page_prefixes ) { $allow_current_page_prefix = false; foreach ( $restrict_to_page_prefixes as $restrict_to_prefix ) { if ( stripos( $current_page, $restrict_to_prefix ) === 0 ) { $allow_current_page_prefix = true; break; } } return $allow_current_page_prefix; } return true; } /** * @param WPML_Notice $notice * * @return bool */ private function is_current_page_allowed( WPML_Notice $notice ) { $current_page = array_key_exists( 'page', $_GET ) ? $_GET['page'] : null; if ( ! $this->is_current_screen_allowed( $notice ) ) { return false; } if ( $current_page ) { $exclude_from_pages = $notice->get_exclude_from_pages(); if ( $exclude_from_pages && in_array( $current_page, $exclude_from_pages, true ) ) { return false; } if ( ! $this->is_current_page_prefix_allowed( $notice, $current_page ) ) { return false; } $restrict_to_pages = $notice->get_restrict_to_pages(); if ( $restrict_to_pages && ! in_array( $current_page, $restrict_to_pages, true ) ) { return false; } } return true; } /** * @param WPML_Notice $notice * * @return bool */ private function is_allowed_by_callback( WPML_Notice $notice ) { $allow_by_callback = true; $display_callbacks = $notice->get_display_callbacks(); if ( $display_callbacks ) { $allow_by_callback = false; foreach ( $display_callbacks as $callback ) { if ( is_callable( $callback ) && call_user_func( $callback ) ) { $allow_by_callback = true; break; } } } return $allow_by_callback; } } notices/class-wpml-notice-action.php 0000755 00000003624 14720342453 0013551 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Notice_Action { private $dismiss; private $display_as_button; private $hide; private $text; private $url; private $group_to_dismiss; private $js_callback; private $dismiss_different_text; private $link_target; /** * WPML_Admin_Notice_Action constructor. * * @param string $text * @param string $url * @param bool $dismiss * @param bool $hide * @param bool|string $display_as_button * @param bool $dismiss_different_text */ public function __construct( $text, $url = '#', $dismiss = false, $hide = false, $display_as_button = false, $dismiss_different_text = true ) { $this->text = $text; $this->url = $url; $this->dismiss = $dismiss; $this->hide = $hide; $this->display_as_button = $display_as_button; $this->dismiss_different_text = $dismiss_different_text; } public function get_text() { return $this->text; } public function get_url() { return $this->url; } public function can_dismiss() { return $this->dismiss; } public function can_dismiss_different_text() { return $this->dismiss_different_text; } public function can_hide() { return $this->hide; } public function must_display_as_button() { return $this->display_as_button; } public function set_group_to_dismiss( $group_name ) { $this->group_to_dismiss = $group_name; } public function get_group_to_dismiss() { return $this->group_to_dismiss; } public function set_js_callback( $js_callback ) { $this->js_callback = $js_callback; } public function get_js_callback() { return $this->js_callback; } /** * @return mixed */ public function get_link_target() { return $this->link_target; } /** * @param mixed $link_target */ public function set_link_target( $link_target ) { $this->link_target = $link_target; } } notices/translation-service-instruction/class-wpml-tm-ts-instructions-hooks.php 0000755 00000004522 14720342453 0024315 0 ustar 00 <?php class WPML_TM_TS_Instructions_Hooks implements IWPML_Action { /** @var WPML_TM_TS_Instructions_Notice */ private $notice; /** * WPML_TM_TS_Instructions_Hooks constructor. * * @param WPML_TM_TS_Instructions_Notice $notice */ public function __construct( WPML_TM_TS_Instructions_Notice $notice ) { $this->notice = $notice; } public function add_hooks() { add_action( 'wpml_tp_project_created', array( $this, 'display_message' ), 10, 3 ); add_action( 'init', array( $this, 'add_hooks_on_init' ), 10, 0 ); add_action( 'wpml_tp_service_de_authorized', array( $this, 'dismiss' ), 10, 0 ); add_action( 'wpml_tp_service_dectivated', array( $this, 'dismiss' ), 10, 0 ); } /** * @param stdClass $service * @param stdClass $project * @param array $icl_translation_projects */ public function display_message( $service, $project, array $icl_translation_projects ) { $is_first_project_ever = empty( $icl_translation_projects ); if ( $is_first_project_ever || ! $this->has_completed_remote_jobs() ) { $this->notice->add_notice( $service ); } } public function add_hooks_on_init() { if ( $this->notice->exists() ) { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 0 ); add_action( 'wp_ajax_translation_service_instruction_dismiss', array( $this, 'dismiss' ), 10, 0 ); } } public function enqueue_scripts() { $handle = 'wpml-tm-translation-service-instruction'; wp_register_script( $handle, WPML_TM_URL . '/dist/js/translationServiceInstruction/app.js', array(), ICL_SITEPRESS_VERSION ); $data = array( 'restUrl' => untrailingslashit( rest_url() ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'syncJobStatesNonce' => wp_create_nonce( 'sync-job-states' ), 'ate' => null, 'currentUser' => wp_get_current_user(), ); wp_localize_script( $handle, 'WPML_TM_SETTINGS', $data ); wp_enqueue_script( $handle ); } public function dismiss() { $this->notice->remove_notice(); } /** * @return bool */ private function has_completed_remote_jobs() { $search_params = new WPML_TM_Jobs_Search_Params(); $search_params->set_status( array( ICL_TM_COMPLETE ) ); $search_params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE ); return wpml_tm_get_jobs_repository()->get_count( $search_params ) > 0; } } notices/translation-service-instruction/class-wpml-tm-ts-instructions-notice.php 0000755 00000006155 14720342453 0024457 0 ustar 00 <?php class WPML_TM_TS_Instructions_Notice { const NOTICE_ID = 'translation-service-instructions'; const NOTICE_GROUP_ID = 'translation-service-instructions'; const TEMPLATE = 'translation-service-instructions.twig'; /** @var WPML_Notices */ private $admin_notices; /** @var IWPML_Template_Service */ private $template_service; public function __construct( WPML_Notices $admin_notices, IWPML_Template_Service $template_service ) { $this->admin_notices = $admin_notices; $this->template_service = $template_service; } /** * @param stdClass $service */ public function add_notice( $service ) { $notice = $this->admin_notices->create_notice( self::NOTICE_ID, $this->get_notice_content( $service ), self::NOTICE_GROUP_ID ); $notice->add_display_callback( array( 'WPML_TM_Page', 'is_tm_dashboard' ) ); $notice->set_text_only( true ); $this->admin_notices->add_notice( $notice ); } public function remove_notice() { $this->admin_notices->remove_notice( self::NOTICE_GROUP_ID, self::NOTICE_ID ); } /** * @return bool */ public function exists() { return ( WPML_TM_Page::is_dashboard() || wpml_is_ajax() ) && (bool) $this->admin_notices->get_notice( self::NOTICE_ID, self::NOTICE_GROUP_ID ); } /** * @param stdClass $service * * @return string */ private function get_notice_content( $service ) { $model = $this->get_model( $service ); return $this->template_service->show( $model, self::TEMPLATE ); } /** * @param stdClass $service * * @return array */ private function get_model( $service ) { return array( 'strings' => array( 'title' => sprintf( __( 'How to work correctly with %s', 'wpml-translation-management' ), $service->name ), 'description' => sprintf( __( "Congratulations for choosing %s to translate your site's content. To avoid high costs and wasted time, please watch our short video.", 'wpml-translation-management' ), $service->name ), 'need_help' => __( 'Need help? See ', 'wpml-translation-management' ), 'help_caption' => __( 'how to translate different parts of the site.', 'wpml-translation-management' ), 'this_stuff_is_important' => __( 'This stuff is actually important. Please follow the video to send a test translation. Then, you can dismiss this message. Thank you!', 'wpml-translation-management' ), 'my_test_translation_went_fine' => __( 'My test translation went fine.', 'wpml-translation-management' ), 'dismiss' => __( 'Dismiss this message.', 'wpml-translation-management' ), ), 'image_url' => WPML_TM_URL . '/res/img/ts-instruction-video.png', 'help_link' => 'https://wpml.org/documentation/translating-your-contents/professional-translation-via-wpml/doing-test-translation/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm', 'video_link' => 'https://wpml.org/documentation/translating-your-contents/professional-translation-via-wpml/doing-test-translation/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm', ); } } notices/translation-service-instruction/class-wpml-tm-ts-instructions-hooks-factory.php 0000755 00000001141 14720342453 0025754 0 ustar 00 <?php class WPML_TM_TS_Instructions_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { /** * @return WPML_TM_TS_Instructions_Hooks */ public function create() { return new WPML_TM_TS_Instructions_Hooks( $this->create_notice() ); } /** * @return WPML_TM_TS_Instructions_Notice */ private function create_notice() { $template_service = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/notices/translation-service-instruction/' ) ); return new WPML_TM_TS_Instructions_Notice( wpml_get_admin_notices(), $template_service->get_template() ); } } notices/class-wpml-notices.php 0000755 00000034372 14720342453 0012465 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Notices { const NOTICES_OPTION_KEY = 'wpml_notices'; const DISMISSED_OPTION_KEY = '_wpml_dismissed_notices'; const USER_DISMISSED_KEY = '_wpml_user_dismissed_notices'; const NONCE_NAME = 'wpml-notices'; const DEFAULT_GROUP = 'default'; private $notice_render; /** * @var array<string,array<\WPML_Notice>> */ private $notices; /** * @var array<string,array<int>> */ private $notices_to_remove = array(); private $dismissed; private $user_dismissed; private $original_notices_md5; /** * WPML_Notices constructor. * * @param WPML_Notice_Render $notice_render */ public function __construct( WPML_Notice_Render $notice_render ) { $this->notice_render = $notice_render; } public function init_notices() { if ( null !== $this->notices ) { // Already initialized. return; } $this->notices = $this->filter_invalid_notices( $this->get_all_notices() ); $this->dismissed = $this->get_all_dismissed(); $this->original_notices_md5 = md5( maybe_serialize( $this->notices ) ); } /** * @return int */ public function count() { $this->init_notices(); $all_notices = $this->get_all_notices(); $count = 0; foreach ( $all_notices as $group => $group_notices ) { $count += count( $group_notices ); } return $count; } /** * @return array */ public function get_all_notices() { $all_notices = get_option( self::NOTICES_OPTION_KEY ); if ( ! is_array( $all_notices ) ) { $all_notices = array(); } return $all_notices; } /** * @return array */ private function get_all_dismissed() { $dismissed = get_option( self::DISMISSED_OPTION_KEY ); if ( ! is_array( $dismissed ) ) { $dismissed = array(); } return $dismissed; } private function init_all_user_dismissed() { if ( null === $this->user_dismissed ) { $this->user_dismissed = get_user_meta( get_current_user_id(), self::USER_DISMISSED_KEY, true ); if ( ! is_array( $this->user_dismissed ) ) { $this->user_dismissed = array(); } } } /** * @param string $id * @param string $group * * @return null|WPML_Notice */ public function get_notice( $id, $group = 'default' ) { $this->init_notices(); $notice = null; if ( isset( $this->notices[ $group ][ $id ] ) ) { $notice = $this->notices[ $group ][ $id ]; } return $notice; } /** * @param string $id * @param string $text * @param string $group * * @return WPML_Notice */ public function create_notice( $id, $text, $group = 'default' ) { return new WPML_Notice( $id, $text, $group ); } public function add_notice( WPML_Notice $notice, $force_update = false ) { $this->init_notices(); $existing_notice = $this->notice_exists( $notice ) ? $this->notices[ $notice->get_group() ][ $notice->get_id() ] : null; $new_notice_is_different = null === $existing_notice || $notice->is_different( $existing_notice ); if ( $notice->must_reset_dismiss() && $this->is_notice_dismissed( $notice ) ) { $this->undismiss_notice( $notice ); } if ( ! $existing_notice || ( $new_notice_is_different || $force_update ) ) { $this->notices[ $notice->get_group() ][ $notice->get_id() ] = $notice; $this->save_notices(); } } /** * @param string $id * @param string $text * @param string $group * * @return WPML_Notice */ public function get_new_notice( $id, $text, $group = 'default' ) { return new WPML_Notice( $id, $text, $group ); } /** * @param string $text * @param string $url * @param bool $dismiss * @param bool $hide * @param bool $display_as_button * * @return WPML_Notice_Action */ public function get_new_notice_action( $text, $url = '#', $dismiss = false, $hide = false, $display_as_button = false ) { return new WPML_Notice_Action( $text, $url, $dismiss, $hide, $display_as_button ); } /** * @param WPML_Notice $notice * * @return bool */ private function notice_exists( WPML_Notice $notice ) { $notice_id = $notice->get_id(); $notice_group = $notice->get_group(); return $this->group_and_id_exist( $notice_group, $notice_id ); } private function get_notices_for_group( $group ) { if ( array_key_exists( $group, $this->notices ) ) { return $this->notices[ $group ]; } return array(); } private function save_notices() { $this->remove_notices(); if ( ! has_action( 'shutdown', array( $this, 'save_to_option' ) ) ) { add_action( 'shutdown', array( $this, 'save_to_option' ), 1000 ); } } public function save_to_option() { if ( null === $this->notices ) { // Nothing to save. return; } if ( $this->original_notices_md5 !== md5( maybe_serialize( $this->notices ) ) ) { update_option( self::NOTICES_OPTION_KEY, $this->notices, false ); } } private function save_dismissed() { update_user_meta( get_current_user_id(), self::USER_DISMISSED_KEY, $this->user_dismissed ); if ( is_array( $this->dismissed ) ) { update_option( self::DISMISSED_OPTION_KEY, $this->dismissed, false ); } } public function remove_notices() { if ( $this->notices_to_remove ) { $this->init_notices(); foreach ( $this->notices_to_remove as $group => &$group_notices ) { foreach ( $group_notices as $id ) { if ( array_key_exists( $group, $this->notices ) && array_key_exists( $id, $this->notices[ $group ] ) ) { unset( $this->notices[ $group ][ $id ] ); $group_notices = array_diff( $this->notices_to_remove[ $group ], array( $id ) ); } } if ( array_key_exists( $group, $this->notices_to_remove ) && ! $this->notices_to_remove[ $group ] ) { unset( $this->notices_to_remove[ $group ] ); } if ( array_key_exists( $group, $this->notices ) && ! $this->notices[ $group ] ) { unset( $this->notices[ $group ] ); } } } } public function admin_enqueue_scripts() { if ( WPML_Block_Editor_Helper::is_edit_post() ) { wp_enqueue_script( 'block-editor-notices', ICL_PLUGIN_URL . '/dist/js/blockEditorNotices/app.js', array( 'wp-edit-post' ), ICL_SITEPRESS_VERSION, true ); } if ( $this->must_display_notices() ) { wp_enqueue_style( 'otgs-notices', ICL_PLUGIN_URL . '/res/css/otgs-notices.css', array( 'sitepress-style' ) ); wp_enqueue_script( 'otgs-notices', ICL_PLUGIN_URL . '/res/js/otgs-notices.js', array( 'underscore' ), ICL_SITEPRESS_VERSION, true ); do_action( 'wpml-notices-scripts-enqueued' ); } } private function must_display_notices() { if ( $this->notices ) { foreach ( $this->notices as $group => $notices ) { foreach ( $notices as $notice ) { if ( $this->notice_render->must_display_notice( $notice ) && ! $this->must_hide_if_notice_exists( $notice ) ) { return true; } } } } return false; } private function must_hide_if_notice_exists( WPML_Notice $notice ) { $hide_if_notice_exists = $notice->get_hide_if_notice_exists(); if ( $hide_if_notice_exists ) { $other_notice = $this->get_notice( $hide_if_notice_exists['id'], $hide_if_notice_exists['group'] ); return $other_notice; } return false; } public function admin_notices() { $this->init_notices(); if ( $this->notices && $this->must_display_notices() ) { foreach ( $this->notices as $group => $notices ) { foreach ( $notices as $notice ) { if ( $notice instanceof WPML_Notice && ! $this->is_notice_dismissed( $notice ) ) { $this->notice_render->render( $notice ); if ( $notice->is_flash() ) { $this->remove_notice( $notice->get_group(), $notice->get_id() ); } } } } } } public function wp_ajax_hide_notice() { $this->init_notices(); list( $notice_group, $notice_id ) = $this->parse_group_and_id(); if ( ! $notice_group ) { $notice_group = self::DEFAULT_GROUP; } if ( $this->has_valid_nonce() && $this->group_and_id_exist( $notice_group, $notice_id ) ) { $this->remove_notice( $notice_group, $notice_id ); wp_send_json_success( true ); } wp_send_json_error( __( 'Notice does not exists.', 'sitepress' ) ); } public function wp_ajax_dismiss_notice() { $this->init_notices(); list( $notice_group, $notice_id ) = $this->parse_group_and_id(); if ( $this->has_valid_nonce() && $this->dismiss_notice_by_id( $notice_id, $notice_group ) ) { wp_send_json_success( true ); } wp_send_json_error( __( 'Notice does not exist.', 'sitepress' ) ); } /** * @param string $notice_id * @param null|string $notice_group * * @return bool */ private function dismiss_notice_by_id( $notice_id, $notice_group = null ) { if ( ! $notice_group ) { $notice_group = self::DEFAULT_GROUP; } if ( $this->group_and_id_exist( $notice_group, $notice_id ) ) { $notice = $this->get_notice( $notice_id, $notice_group ); if ( $notice ) { $this->dismiss_notice( $notice ); $this->remove_notice( $notice_group, $notice_id ); return true; } } return false; } public function wp_ajax_dismiss_group() { $this->init_notices(); list( $notice_group ) = $this->parse_group_and_id(); if ( $notice_group && $this->has_valid_nonce() && $this->dismiss_notice_group( $notice_group ) ) { wp_send_json_success( true ); } wp_send_json_error( __( 'Group does not exist.', 'sitepress' ) ); } /** * @param null|string $notice_group * * @return bool */ private function dismiss_notice_group( $notice_group ) { if ( $notice_group ) { $notices = $this->get_notices_for_group( $notice_group ); if ( $notices ) { /** @var WPML_Notice $notice */ foreach ( $notices as $notice ) { $this->dismiss_notice( $notice, false ); $this->remove_notice( $notice_group, $notice->get_id() ); } $this->save_dismissed(); return true; } } return false; } /** * @return array */ private function parse_group_and_id() { $group = isset( $_POST['group'] ) ? sanitize_text_field( $_POST['group'] ) : false; $id = isset( $_POST['id'] ) ? sanitize_text_field( $_POST['id'] ) : false; return array( $group, $id ); } /** * @return false|int */ private function has_valid_nonce() { $nonce = isset( $_POST['nonce'] ) ? $_POST['nonce'] : null; return wp_verify_nonce( $nonce, self::NONCE_NAME ); } private function group_and_id_exist( $group, $id ) { return array_key_exists( $group, $this->notices ) && array_key_exists( $id, $this->notices[ $group ] ); } /** * @param string $notice_group * @param string|int $notice_id */ public function remove_notice( $notice_group, $notice_id ) { $this->notices_to_remove[ $notice_group ][] = $notice_id; $this->notices_to_remove[ $notice_group ] = array_unique( $this->notices_to_remove[ $notice_group ] ); $this->save_notices(); if ( ! is_array( $this->notices_to_remove ) ) { $this->notices_to_remove = array(); } if ( isset( $this->notices_to_remove[ $notice_group ] ) ) { foreach ( $this->notices_to_remove[ $notice_group ] as $key => $notice ) { if ( $notice === $notice_id ) { unset( $this->notices_to_remove[ $notice_group ][ $key ] ); } } } } /** * @param string $notice_group */ public function remove_notice_group( $notice_group ) { $this->init_notices(); $notices = $this->get_notices_for_group( $notice_group ); $notices_ids = array_keys( $notices ); foreach ( $notices_ids as $notices_id ) { $this->remove_notice( $notice_group, $notices_id ); } } /** * @param WPML_Notice $notice * @param bool $persist */ public function dismiss_notice( WPML_Notice $notice, $persist = true ) { if ( method_exists( $notice, 'is_user_restricted' ) && $notice->is_user_restricted() ) { $this->init_all_user_dismissed(); $this->user_dismissed[ $notice->get_group() ][ $notice->get_id() ] = md5( $notice->get_text() ); } else { $this->init_notices(); $this->dismissed[ $notice->get_group() ][ $notice->get_id() ] = md5( $notice->get_text() ); } if ( $persist ) { $this->save_dismissed(); } } /** * @param WPML_Notice $notice * @param bool $persist */ public function undismiss_notice( WPML_Notice $notice, $persist = true ) { if ( method_exists( $notice, 'is_user_restricted' ) && $notice->is_user_restricted() ) { $this->init_all_user_dismissed(); unset( $this->user_dismissed[ $notice->get_group() ][ $notice->get_id() ] ); } else { $this->init_notices(); unset( $this->dismissed[ $notice->get_group() ][ $notice->get_id() ] ); } if ( $persist ) { $this->save_dismissed(); } } /** * @param WPML_Notice $notice * * @return bool */ public function is_notice_dismissed( WPML_Notice $notice ) { $this->init_notices(); $group = $notice->get_group(); $id = $notice->get_id(); $is_dismissed = isset( $this->dismissed[ $group ][ $id ] ) && $this->dismissed[ $group ][ $id ]; if ( ! $is_dismissed ) { $this->init_all_user_dismissed(); $is_dismissed = isset( $this->user_dismissed[ $group ][ $id ] ) && $this->user_dismissed[ $group ][ $id ]; } if ( $is_dismissed && method_exists( $notice, 'can_be_dismissed_for_different_text' ) && ! $notice->can_be_dismissed_for_different_text() ) { $is_dismissed = md5( $notice->get_text() ) === $this->dismissed[ $group ][ $id ]; } return $is_dismissed; } public function init_hooks() { $this->add_admin_notices_action(); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), \WPML_Admin_Scripts_Setup::PRIORITY_ENQUEUE_SCRIPTS + 1 ); add_action( 'wp_ajax_otgs-hide-notice', array( $this, 'wp_ajax_hide_notice' ) ); add_action( 'wp_ajax_otgs-dismiss-notice', array( $this, 'wp_ajax_dismiss_notice' ) ); add_action( 'wp_ajax_otgs-dismiss-group', array( $this, 'wp_ajax_dismiss_group' ) ); add_action( 'otgs_add_notice', array( $this, 'add_notice' ), 10, 2 ); add_action( 'otgs_remove_notice', array( $this, 'remove_notice' ), 10, 2 ); add_action( 'otgs_remove_notice_group', array( $this, 'remove_notice_group' ), 10, 1 ); } /** * @return void */ public function add_admin_notices_action() { add_action( 'admin_notices', [ $this, 'admin_notices' ] ); } private function filter_invalid_notices( $notices ) { foreach ( $notices as $group => $notices_in_group ) { if ( ! is_array( $notices_in_group ) ) { unset( $notices[ $group ] ); continue; } foreach ( $notices_in_group as $index => $notice ) { if ( ! $notice instanceof WPML_Notice ) { unset( $notices[ $group ][ $index ] ); } } } return $notices; } } notices/export-import/Notice.php 0000755 00000015606 14720342453 0013012 0 ustar 00 <?php namespace WPML\Notices\ExportImport; class Notice implements \IWPML_Backend_Action, \IWPML_DIC_Action { // Cascade of priorities before 10. // 7: WPML. // 8: WCML. // 9: WPML Export and Import. const PRIORITY = 7; const GROUP = 'wpml-import-notices'; const NOTICE_CLASSES = [ 'wpml-import-notice', 'wpml-import-notice-from-wpml', ]; const WPML_IMPORT_URL = 'https://wpml.org/documentation/related-projects/wpml-export-and-import/?utm_source=plugin&utm_medium=gui&utm_campaign=wpml-export-import&utm_term=admin-notice'; const WCML_URL = 'https://wpml.org/documentation/related-projects/woocommerce-multilingual/?utm_source=plugin&utm_medium=gui&utm_campaign=wcml&utm_term=admin-notice'; const EXPORT_NOTICES = [ 'wordpress-export' => '/export.php', 'wp-import-export-export' => '/admin.php?page=wpie-new-export', 'wp-all-export' => '/admin.php?page=pmxe-admin-export', 'woocommerce-export' => '/edit.php?post_type=product&page=product_exporter', ]; const IMPORT_NOTICES = [ 'wordpress-import' => '/admin.php?import=wordpress', 'wp-import-export-import' => '/admin.php?page=wpie-new-import', 'wp-all-import' => '/admin.php?page=pmxi-admin-import', 'woocommerce-import' => '/edit.php?post_type=product&page=product_importer', ]; /** @var \WPML_Notices $notices */ private $wpmlNotices; public function __construct( \WPML_Notices $wpmlNotices ) { $this->wpmlNotices = $wpmlNotices; } public function add_hooks() { if ( defined( 'WPML_IMPORT_VERSION' ) ) { // WPML Export and Import will take care of this. return; } add_action( 'admin_head', [ $this, 'enforceNoticeStyle' ] ); add_action( 'admin_init', [ $this, 'manageNotice' ], self::PRIORITY ); } public function manageNotice() { if ( ! self::isOnMigrationPages() ) { return; } $this->wpmlNotices->remove_notice_group( self::GROUP ); $exportNotices = self::EXPORT_NOTICES; array_walk( $exportNotices, function( $path, $id ) { $this->maybeAddNotice( $id, $path, $this->getExportMessage( $id ) ); } ); $importNotices = self::IMPORT_NOTICES; array_walk( $importNotices, function( $path, $id ) { $this->maybeAddNotice( $id, $path, $this->getImportMessage( $id ) ); } ); } /** * @param string $id * @param string $path * @param string $message */ private function maybeAddNotice( $id, $path, $message ) { if ( ! self::isOnPage( $path ) ) { return; } $notice = $this->wpmlNotices->get_new_notice( $id, $message, self::GROUP ); $notice->set_css_class_types( 'info' ); $notice->set_css_classes( self::NOTICE_CLASSES ); $notice->add_display_callback( [ \WPML\Notices\ExportImport\Notice::class, 'isOnMigrationPages' ] ); $notice->set_dismissible( true ); $this->wpmlNotices->add_notice( $notice, true ); } public function enforceNoticeStyle() { if ( ! self::isOnMigrationPages() ) { return; } echo '<style> .notice.wpml-import-notice { display: block !important; margin: 25px 25px 25px 0; padding: 24px 24px 24px 62px; } .notice.wpml-import-notice::before { line-height: 20px; } .notice.wpml-import-notice .otgs-notice-actions { margin-top: 24px; } .notice.wpml-import-notice .notice-action { margin-right: 12px; } </style>'; } /** * @param string $path * * @return bool */ private static function isOnPage( $path ) { if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { return false; } if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return false; } if ( false !== strpos( $_SERVER['REQUEST_URI'], $path ) ) { return true; } return false; } /** * @return bool */ public static function isOnMigrationPages() { $exportNotices = self::EXPORT_NOTICES; $importNotices = self::IMPORT_NOTICES; $migrationPaths = array_values( array_merge( $exportNotices, $importNotices ) ); foreach ( $migrationPaths as $path ) { if ( false !== self::isOnPage( $path ) ) { return true; } } return false; } /** * @param string $id * * @return bool */ private function isNoticeForShop( $id ) { if ( in_array( $id, [ 'woocommerce-export', 'woocommerce-import' ] ) ) { return true; } if ( 'wp-all-export' === $id && defined ( 'PMWE_VERSION' ) ) { return true; } if ( 'wp-all-import' === $id && defined ( 'PMWI_VERSION' ) ) { return true; } return false; } /** * @param string $id * * @return string */ private function getExportMessage( $id ) { if ( $this->isNoticeForShop( $id ) ) { return $this->getShopExportMessage(); } return sprintf( /* translators: %s is a link. */ __( 'Migrating your multilingual site? Install %s for the simplest way to export your translated content and import it on your new site.', 'sitepress' ), $this->getWpmlImportLink() ); } /** * @return string */ private function getShopExportMessage() { return sprintf( /* translators: %s is a set of one or two links. */ __( 'Migrating your multilingual shop? With %s you can transfer your translated content to a new site, including cross-sells, up-sells, and product attributes.', 'sitepress' ), $this->getShopLink() ); } /** * @param string $id * * @return string */ private function getImportMessage( $id ) { if ( $this->isNoticeForShop( $id ) ) { return $this->getShopImportMessage(); } return sprintf( /* translators: %s is a link. */ __( 'Looking to import your multilingual content? Install %s in both your original and new site for the easiest way to export and import your content.', 'sitepress' ), $this->getWpmlImportLink() ); } /** * @return string */ private function getShopImportMessage() { return sprintf( /* translators: %1$s and %2$s are both links. */ __( 'Looking to import your multilingual shop? With %1$s and %2$s in both your original and new site, you can export and import your translations automatically.', 'sitepress' ), $this->getWcmlLink(), $this->getWpmlImportLink() ); } /** * @return string */ private function getWpmlImportLink() { $url = self::WPML_IMPORT_URL; $title = __( 'WPML Export and Import', 'sitepress' ); return '<a class="wpml-external-link" href="' . esc_url( $url ) . '" title="' . esc_attr( $title ) . '" target="_blank">' . esc_html( $title ) . '</a>'; } /** * @return string */ private function getWcmlLink() { $url = self::WCML_URL; $title = __( 'WooCommerce Multilingual', 'sitepress' ); return '<a class="wpml-external-link" href="' . esc_url( $url ) . '" title="' . esc_attr( $title ) . '" target="_blank">' . esc_html( $title ) . '</a>'; } /** * @return string */ private function getShopLink() { if ( defined( 'WCML_VERSION' ) ) { return $this->getWpmlImportLink(); } return sprintf( /* translators: %1$s and %2$s are links. */ __( '%1$s and %2$s', 'sitepress' ), $this->getWcmlLink(), $this->getWpmlImportLink() ); } } notices/translation-jobs-migration/class-wpml-tm-translation-jobs-fix-summary-notice.php 0000755 00000001415 14720342453 0025736 0 ustar 00 <?php class WPML_TM_Translation_Jobs_Fix_Summary_Notice extends WPML_Translation_Jobs_Migration_Notice { protected function get_model() { $tm_url = '<a href="' . admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php' ) . '">' . __( 'Translation Dashboard', 'sitepress' ) . '</a>'; $description = sprintf( __( 'WPML completed updating the translations history. Next, please visit the %s and click on the button to "Check status and get translations".', 'wpml-translation-management' ), $tm_url ); return array( 'strings' => array( 'title' => __( 'Problem receiving translation jobs?', 'sitepress' ), 'description' => $description, ), ); } protected function get_notice_id() { return 'translation-jobs-migration-fix-summary'; } } notices/translation-jobs-migration/class-wpml-translation-jobs-migration-ajax.php 0000755 00000003152 14720342453 0024472 0 ustar 00 <?php class WPML_Translation_Jobs_Migration_Ajax { const ACTION = 'wpml_translation_jobs_migration'; const JOBS_MIGRATED_PER_REQUEST = 100; /** @var WPML_Translation_Jobs_Migration */ private $jobs_migration; /** @var WPML_Translation_Jobs_Migration_Repository */ private $jobs_repository; /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; public function __construct( WPML_Translation_Jobs_Migration $jobs_migration, WPML_Translation_Jobs_Migration_Repository $jobs_repository, WPML_TM_Jobs_Migration_State $migration_state ) { $this->jobs_migration = $jobs_migration; $this->jobs_repository = $jobs_repository; $this->migration_state = $migration_state; } public function run_migration() { if ( ! $this->is_valid_request() ) { wp_send_json_error(); return; } $jobs = $this->jobs_repository->get(); $jobs_chunk = array_slice( $jobs, 0, self::JOBS_MIGRATED_PER_REQUEST ); try { $this->jobs_migration->migrate_jobs( $jobs_chunk ); } catch ( Exception $e ) { wp_send_json_error( $e->getMessage(), 500 ); return; } $done = count( $jobs ) === count( $jobs_chunk ); $total_jobs = count( $jobs ); $jobs_chunk_total = count( $jobs_chunk ); $result = array( 'totalJobs' => $total_jobs, 'jobsMigrated' => $jobs_chunk_total, 'done' => $done, ); if ( $jobs_chunk_total === $total_jobs ) { $this->migration_state->mark_migration_as_done(); } wp_send_json_success( $result ); } /** * @return bool */ private function is_valid_request() { return wp_verify_nonce( $_POST['nonce'], self::ACTION ); } } notices/translation-jobs-migration/class-wpml-translation-jobs-migration-hooks.php 0000755 00000005653 14720342453 0024702 0 ustar 00 <?php class WPML_Translation_Jobs_Migration_Hooks { private $notice; private $ajax_handler; /** @var WPML_Translation_Jobs_Migration_Repository */ private $jobs_migration_repository; /** @var WPML_Upgrade_Schema $schema */ private $schema; /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; public function __construct( WPML_Translation_Jobs_Migration_Notice $notice, $ajax_handler, WPML_Translation_Jobs_Migration_Repository $jobs_migration_repository, WPML_Upgrade_Schema $schema, WPML_TM_Jobs_Migration_State $migration_state ) { $this->notice = $notice; $this->ajax_handler = $ajax_handler; $this->jobs_migration_repository = $jobs_migration_repository; $this->schema = $schema; $this->migration_state = $migration_state; } public function add_hooks() { add_action( 'init', array( $this, 'add_hooks_on_init' ), PHP_INT_MAX ); } public function add_hooks_on_init() { if ( $this->new_columns_are_not_added_yet() ) { add_filter( 'wpml_tm_lock_ui', array( $this, 'lock_tm_ui' ) ); } elseif ( $this->needs_migration() ) { $this->notice->add_notice(); add_filter( 'wpml_tm_lock_ui', array( $this, 'lock_tm_ui' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_ajax_' . WPML_Translation_Jobs_Migration_Ajax::ACTION, array( $this->ajax_handler, 'run_migration' ) ); } } /** * @see * `WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Core_Status` * `WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Translation_Status` * `WPML_TM_Add_TP_ID_Column_To_Translation_Status` * * @return bool */ private function new_columns_are_not_added_yet() { $has_columns = $this->schema->does_column_exist( 'icl_core_status', 'tp_revision' ) && $this->schema->does_column_exist( 'icl_core_status', 'ts_status' ) && $this->schema->does_column_exist( 'icl_translation_status', 'tp_revision' ) && $this->schema->does_column_exist( 'icl_translation_status', 'ts_status' ) && $this->schema->does_column_exist( 'icl_translation_status', 'tp_id' ); return ! $has_columns; } public function enqueue_scripts() { wp_enqueue_script( 'wpml-tm-translation-jobs-migration', WPML_TM_URL . '/dist/js/translationJobsMigration/app.js', array(), ICL_SITEPRESS_VERSION ); } /** * @return bool */ private function needs_migration() { if ( $this->jobs_migration_repository->get_count() ) { return ! $this->skip_migration_if_service_is_not_active(); } $this->migration_state->mark_migration_as_done(); return false; } /** * @return bool */ private function skip_migration_if_service_is_not_active() { if ( ! TranslationProxy::is_current_service_active_and_authenticated() ) { $this->migration_state->skip_migration( true ); return true; } return false; } /** * @return bool */ public function lock_tm_ui() { return true; } } translation-jobs-migration/class-wpml-tm-troubleshooting-fix-translation-jobs-tp-id-factory.php 0000755 00000001523 14720342453 0030612 0 ustar 00 notices <?php class WPML_TM_Troubleshooting_Fix_Translation_Jobs_TP_ID_Factory implements IWPML_Backend_Action_Loader { public function create() { global $wpml_post_translations, $wpml_term_translations, $wpdb; $jobs_migration_repository = new WPML_Translation_Jobs_Migration_Repository( wpml_tm_get_jobs_repository(), true ); $job_factory = wpml_tm_load_job_factory(); $wpml_tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations ); $cms_id_helper = new WPML_TM_CMS_ID( $wpml_tm_records, $job_factory ); $jobs_migration = new WPML_Translation_Jobs_Migration( $jobs_migration_repository, $cms_id_helper, $wpdb, wpml_tm_get_tp_jobs_api() ); return new WPML_TM_Troubleshooting_Fix_Translation_Jobs_TP_ID( $jobs_migration, wpml_tm_get_jobs_repository() ); } } notices/translation-jobs-migration/class-wpml-translation-jobs-migration-notice.php 0000755 00000003647 14720342453 0025041 0 ustar 00 <?php abstract class WPML_Translation_Jobs_Migration_Notice { const NOTICE_GROUP_ID = 'translation-jobs'; const TEMPLATE = 'translation-jobs-migration.twig'; /** * The instance of \WPML_Notices. * * @var \WPML_Notices */ private $admin_notices; /** * The instance of \IWPML_Template_Service. * * @var \IWPML_Template_Service */ private $template_service; /** * WPML_Translation_Jobs_Migration_Notice constructor. * * @param \WPML_Notices $admin_notices An instance of \WPML_Notices. * @param \IWPML_Template_Service $template_service A class implementing \IWPML_Template_Service. */ public function __construct( WPML_Notices $admin_notices, IWPML_Template_Service $template_service ) { $this->admin_notices = $admin_notices; $this->template_service = $template_service; } /** * It adds the notice to be shown when conditions meet. */ public function add_notice() { $notice = $this->admin_notices->create_notice( $this->get_notice_id(), $this->get_notice_content(), self::NOTICE_GROUP_ID ); $notice->set_css_class_types( 'notice-error' ); $this->admin_notices->add_notice( $notice ); } /** * It removes the notice. */ public function remove_notice() { $this->admin_notices->remove_notice( self::NOTICE_GROUP_ID, $this->get_notice_id() ); } /** * It checks is the notice exists. * * @return bool */ public function exists() { return (bool) $this->admin_notices->get_notice( $this->get_notice_id(), self::NOTICE_GROUP_ID ); } /** * It gets the notice content. * * @return string */ private function get_notice_content() { return $this->template_service->show( $this->get_model(), self::TEMPLATE ); } /** * It gets the definition of the notice's content. * * @return array */ abstract protected function get_model(); /** * It gets the ID of the notice. * * @return string */ abstract protected function get_notice_id(); } notices/translation-jobs-migration/class-wpml-translation-jobs-migration-repository.php 0000755 00000001530 14720342453 0025764 0 ustar 00 <?php class WPML_Translation_Jobs_Migration_Repository { private $jobs_repository; private $all_jobs = false; public function __construct( WPML_TM_Jobs_Repository $jobs_repository, $all_jobs = false ) { $this->jobs_repository = $jobs_repository; $this->all_jobs = $all_jobs; } public function get() { return $this->jobs_repository->get( $this->get_params() )->getIterator()->getArrayCopy(); } public function get_count() { return $this->jobs_repository->get_count( $this->get_params() ); } private function get_params() { $params = new WPML_TM_Jobs_Search_Params(); $params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE ); $params->set_job_types( array( WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE ) ); if ( ! $this->all_jobs ) { $params->set_tp_id( null ); } return $params; } } notices/translation-jobs-migration/class-wpml-tm-translation-jobs-fix-summary.php 0000755 00000003015 14720342453 0024455 0 ustar 00 <?php class WPML_TM_Translation_Jobs_Fix_Summary { const INVALID_JOBS_SYNCED_KEY = 'wpml_tm_migration_invalid_jobs_already_synced'; /** @var WPML_TM_Translation_Jobs_Fix_Summary_Notice */ private $notice; /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; public function __construct( WPML_TM_Translation_Jobs_Fix_Summary_Notice $notice, WPML_TM_Jobs_Migration_State $migration_state ) { $this->notice = $notice; $this->migration_state = $migration_state; } public function add_hooks() { add_action( 'init', array( $this, 'display_summary' ) ); add_action( 'wp_ajax_' . WPML_TP_Sync_Ajax_Handler::AJAX_ACTION, array( $this, 'mark_invalid_jobs_as_synced', ) ); } public function display_summary() { if ( $this->should_display_summary_notice() ) { $this->notice->add_notice(); } elseif ( $this->notice->exists() ) { $this->notice->remove_notice(); } } private function should_display_summary_notice() { if ( ! $this->migration_state->is_fixing_migration_done() ) { return false; } $jobs_with_new_status = get_option( WPML_Translation_Jobs_Migration::MIGRATION_FIX_LOG_KEY ); $jobs_with_new_status = isset( $jobs_with_new_status['status_changed'] ) ? $jobs_with_new_status['status_changed'] : null; $jobs_already_synced = (int) get_option( self::INVALID_JOBS_SYNCED_KEY ) > 1; return $jobs_with_new_status && ! $jobs_already_synced; } public function mark_invalid_jobs_as_synced() { update_option( self::INVALID_JOBS_SYNCED_KEY, 2 ); } } notices/translation-jobs-migration/class-wpml-tm-restore-skipped-migration-hook.php 0000755 00000001466 14720342453 0024762 0 ustar 00 <?php /** * The class adds the hook which is triggered in the moment of Translation Service authorization. * It checks if the migration has been skipped due to lack of activated service and if so, it turns on the migration. */ class WPML_TM_Restore_Skipped_Migration implements IWPML_Action { /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; /** * @param WPML_TM_Jobs_Migration_State $migration_state */ public function __construct( WPML_TM_Jobs_Migration_State $migration_state ) { $this->migration_state = $migration_state; } public function add_hooks() { add_action( 'wpml_tm_translation_service_authorized', array( $this, 'restore' ) ); } public function restore() { if ( $this->migration_state->is_skipped() ) { $this->migration_state->skip_migration( false ); } } } notices/translation-jobs-migration/class-wpml-tm-jobs-migration-state.php 0000755 00000004133 14720342453 0022751 0 ustar 00 <?php class WPML_TM_Jobs_Migration_State { const MIGRATION_DONE_KEY = 'wpml-tm-translation-jobs-migration'; const FIXING_MIGRATION_STATE_KEY = 'wpml-tm-all-translation-jobs-migration'; const MIGRATION_SKIPPED = 'wpml-tm-translation-jobs-migration-skipped'; /** * The fixing migration has already been run but it contained errors */ const FIRST_MIGRATION_FIX_HAS_RUN = 1; /** * We've already cleaned logs of the first fixing migration and are ready to run another this time flawless version */ const READY_TO_RUN_SECOND_MIGRATION_FIX = 2; /** * The final flawless fixing migration has been run */ const SECOND_MIGRATION_FIX_HAS_RUN = 3; /** * Checks if the original migration has been finished * * @return bool */ public function is_migrated() { return (bool) get_option( self::MIGRATION_DONE_KEY ); } /** * Checks if the fixing migration ( migration which fixes the flaws of the original migration ) has been run * * @return bool */ public function is_fixing_migration_done() { $option = (int) get_option( self::FIXING_MIGRATION_STATE_KEY ); if ( $option === self::FIRST_MIGRATION_FIX_HAS_RUN ) { // clear previous log update_option( WPML_Translation_Jobs_Migration::MIGRATION_FIX_LOG_KEY, array(), false ); update_option( self::FIXING_MIGRATION_STATE_KEY, self::READY_TO_RUN_SECOND_MIGRATION_FIX, true ); } return (int) get_option( self::FIXING_MIGRATION_STATE_KEY ) === self::SECOND_MIGRATION_FIX_HAS_RUN; } public function mark_migration_as_done() { update_option( self::MIGRATION_DONE_KEY, 1, true ); // a user has never run the original migration so it does not need the fixing migration $this->mark_fixing_migration_as_done(); } public function mark_fixing_migration_as_done() { update_option( self::FIXING_MIGRATION_STATE_KEY, self::SECOND_MIGRATION_FIX_HAS_RUN, true ); } /** * @param bool $flag */ public function skip_migration( $flag = true ) { update_option( self::MIGRATION_SKIPPED, $flag, true ); } /** * @return bool */ public function is_skipped() { return (bool) get_option( self::MIGRATION_SKIPPED ); } } notices/translation-jobs-migration/class-wpml-tm-translation-jobs-fix-summary-factory.php 0000755 00000001012 14720342453 0026115 0 ustar 00 <?php class WPML_TM_Translation_Jobs_Fix_Summary_Factory implements IWPML_Backend_Action_Loader { /** * @return WPML_TM_Translation_Jobs_Fix_Summary */ public function create() { $template_service = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/translation-jobs-migration/' ) ); return new WPML_TM_Translation_Jobs_Fix_Summary( new WPML_TM_Translation_Jobs_Fix_Summary_Notice( wpml_get_admin_notices(), $template_service->get_template() ), new WPML_TM_Jobs_Migration_State() ); } } notices/translation-jobs-migration/class-wpml-translation-jobs-migration-hooks-factory.php 0000755 00000006306 14720342453 0026343 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_Translation_Jobs_Migration_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { /** * It creates an instance of WPML_Translation_Jobs_Migration_Notice. * * @return null|WPML_Translation_Jobs_Migration_Hooks|WPML_TM_Restore_Skipped_Migration */ public function create() { $fixing_migration = false; $wpml_notices = wpml_get_admin_notices(); $wpml_notices->remove_notice( WPML_Translation_Jobs_Migration_Notice::NOTICE_GROUP_ID, 'all-translation-jobs-migration' ); $wpml_notices->remove_notice( WPML_Translation_Jobs_Migration_Notice::NOTICE_GROUP_ID, 'translation-jobs-migration' ); if ( ! $this->should_add_migration_hooks() ) { return null; } $migration_state = new WPML_TM_Jobs_Migration_State(); if ( $migration_state->is_skipped() ) { return new WPML_TM_Restore_Skipped_Migration( $migration_state ); } if ( $migration_state->is_migrated() ) { if ( $migration_state->is_fixing_migration_done() ) { return null; } $fixing_migration = true; } $template_service = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/translation-jobs-migration/' ) ); if ( $fixing_migration ) { $notice = new WPML_All_Translation_Jobs_Migration_Notice( $wpml_notices, $template_service->get_template() ); } else { $notice = new WPML_Translation_Jobs_Missing_TP_ID_Migration_Notice( $wpml_notices, $template_service->get_template() ); } $jobs_migration_repository = new WPML_Translation_Jobs_Migration_Repository( wpml_tm_get_jobs_repository(), $fixing_migration ); global $wpml_post_translations, $wpml_term_translations, $wpdb; $job_factory = wpml_tm_load_job_factory(); $wpml_tm_records = new WPML_TM_Records( $wpdb, $wpml_post_translations, $wpml_term_translations ); $cms_id_helper = new WPML_TM_CMS_ID( $wpml_tm_records, $job_factory ); $jobs_migration = new WPML_Translation_Jobs_Migration( $jobs_migration_repository, $cms_id_helper, $wpdb, wpml_tm_get_tp_jobs_api() ); if ( $fixing_migration ) { $ajax_handler = new WPML_Translation_Jobs_Fixing_Migration_Ajax( $jobs_migration, $jobs_migration_repository, $migration_state ); } else { $ajax_handler = new WPML_Translation_Jobs_Migration_Ajax( $jobs_migration, $jobs_migration_repository, $migration_state ); } return new WPML_Translation_Jobs_Migration_Hooks( $notice, $ajax_handler, $jobs_migration_repository, wpml_get_upgrade_schema(), $migration_state ); } /** * Check if location is allowed to add migration hooks. */ private function should_add_migration_hooks() { $allowed_uris = array( '/.*page=sitepress-multilingual-cms.*/', '/.*page=wpml-string-translation.*/', '/.*page=tm\/menu\/main.*/', ); if ( wp_doing_ajax() ) { return true; } $uri = rawurldecode( $this->get_request_uri() ); foreach ( $allowed_uris as $pattern ) { if ( preg_match( $pattern, $uri ) ) { return true; } } return false; } /** * Get request uri. * * @return string */ private function get_request_uri() { if ( isset( $_SERVER['REQUEST_URI'] ) ) { return wp_unslash( Sanitize::stringProp( 'REQUEST_URI', $_SERVER ) ); } return ''; } } notices/translation-jobs-migration/class-wpml-translation-jobs-fixing-migration-ajax.php 0000755 00000004170 14720342453 0025755 0 ustar 00 <?php class WPML_Translation_Jobs_Fixing_Migration_Ajax { const ACTION = 'wpml_translation_jobs_migration'; const JOBS_MIGRATED_PER_REQUEST = 100; const PAGINATION_OPTION = 'wpml_translation_jobs_migration_processed'; /** @var WPML_Translation_Jobs_Migration */ private $jobs_migration; /** @var WPML_Translation_Jobs_Migration_Repository */ private $jobs_repository; /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; public function __construct( WPML_Translation_Jobs_Migration $jobs_migration, WPML_Translation_Jobs_Migration_Repository $jobs_repository, WPML_TM_Jobs_Migration_State $migration_state ) { $this->jobs_migration = $jobs_migration; $this->jobs_repository = $jobs_repository; $this->migration_state = $migration_state; } public function run_migration() { if ( ! $this->is_valid_request() ) { wp_send_json_error(); } $jobs = $this->jobs_repository->get(); $total_jobs = count( $jobs ); $offset = $this->get_already_processed(); if ( $offset < $total_jobs ) { $jobs_chunk = array_slice( $jobs, $offset, self::JOBS_MIGRATED_PER_REQUEST ); try { $this->jobs_migration->migrate_jobs( $jobs_chunk, true ); } catch ( Exception $e ) { wp_send_json_error( $e->getMessage(), 500 ); return; } $done = $total_jobs <= $offset + self::JOBS_MIGRATED_PER_REQUEST; $jobs_chunk_total = count( $jobs_chunk ); update_option( self::PAGINATION_OPTION, $offset + self::JOBS_MIGRATED_PER_REQUEST ); } else { $done = true; $jobs_chunk_total = 0; } $result = array( 'totalJobs' => $total_jobs, 'jobsMigrated' => $jobs_chunk_total, 'done' => $done, ); if ( $done ) { $this->migration_state->mark_fixing_migration_as_done(); delete_option( self::PAGINATION_OPTION ); } wp_send_json_success( $result ); } /** * @return bool */ private function is_valid_request() { return wp_verify_nonce( $_POST['nonce'], self::ACTION ); } /** * @return int */ private function get_already_processed() { return (int) get_option( self::PAGINATION_OPTION, 0 ); } } notices/translation-jobs-migration/class-wpml-all-translation-jobs-migration-notice.php 0000755 00000002704 14720342453 0025600 0 ustar 00 <?php class WPML_All_Translation_Jobs_Migration_Notice extends WPML_Translation_Jobs_Migration_Notice { /** * It gets the definition of the notice's content. * * @return array */ protected function get_model() { return array( 'strings' => array( 'title' => __( 'Problem receiving translation jobs?', 'wpml-translation-management' ), 'description' => __( 'WPML needs to update its table of translation jobs, so that your site can continue receiving completed translations. This process will take a few minutes and does not modify content or translations in your site.', 'wpml-translation-management' ), 'button' => __( 'Start update', 'wpml-translation-management' ), /* translators: this is shown between two number: processed items and total number of items to process */ 'of' => __( 'of', 'wpml-translation-management' ), 'jobs_migrated' => __( 'jobs fixed', 'wpml-translation-management' ), 'communicationError' => __( 'The communication error with Translation Proxy has appeared. Please try later.', 'wpml-translation-management' ), ), 'nonce' => wp_nonce_field( WPML_Translation_Jobs_Migration_Ajax::ACTION, WPML_Translation_Jobs_Migration_Ajax::ACTION, false, false ), ); } /** * It gets the ID of the notice. * * @return string */ protected function get_notice_id() { return 'all-translation-jobs-migration'; } } notices/translation-jobs-migration/class-wpml-translation-jobs-migration.php 0000755 00000012752 14720342453 0023557 0 ustar 00 <?php class WPML_Translation_Jobs_Migration { const MIGRATION_FIX_LOG_KEY = 'wpml_fixing_migration_log'; private $jobs_repository; private $cms_id_builder; private $wpdb; private $jobs_api; public function __construct( WPML_Translation_Jobs_Migration_Repository $jobs_repository, WPML_TM_CMS_ID $cms_id_builder, wpdb $wpdb, WPML_TP_Jobs_API $jobs_api ) { $this->jobs_repository = $jobs_repository; $this->cms_id_builder = $cms_id_builder; $this->wpdb = $wpdb; $this->jobs_api = $jobs_api; } /** * @param WPML_TM_Post_Job_Entity[] $jobs * @param bool $recover_status * * @throws WPML_TP_API_Exception */ public function migrate_jobs( array $jobs, $recover_status = false ) { $mapped_jobs = $this->map_cms_id_job_id( $jobs ); if ( $mapped_jobs ) { $tp_jobs = $this->get_tp_jobs( $mapped_jobs ); foreach ( $jobs as $job ) { $cms_id = array_key_exists( $job->get_id(), $mapped_jobs ) ? $mapped_jobs[ $job->get_id() ] : ''; list( $tp_id, $revision_id ) = $this->get_tp_id_revision_id( $cms_id, $tp_jobs ); if ( $recover_status ) { $this->recovery_mode( $job, $tp_id, $revision_id ); } else { $this->first_migration_mode( $job, $tp_id, $revision_id ); } } } } /** * @param array $mapped_jobs * * @throws WPML_TP_API_Exception * @return array */ private function get_tp_jobs( array $mapped_jobs ) { return $this->get_latest_jobs_grouped_by_cms_id( $this->jobs_api->get_jobs_per_cms_ids( array_values( $mapped_jobs ), true ) ); } /** * @param WPML_TM_Post_Job_Entity $job * @param int $tp_id * @param int $revision_id */ private function recovery_mode( WPML_TM_Post_Job_Entity $job, $tp_id, $revision_id ) { if ( $tp_id !== $job->get_tp_id() ) { $new_status = $this->get_new_status( $job, $tp_id ); $this->log( $job->get_id(), $job->get_tp_id(), $tp_id, $job->get_status(), $new_status ); $this->fix_job_fields( $tp_id, $revision_id, $new_status, $job->get_id() ); } } /** * @param WPML_TM_Post_Job_Entity $job * @param int $tp_id * @param int $revision_id */ private function first_migration_mode( WPML_TM_Post_Job_Entity $job, $tp_id, $revision_id ) { $this->fix_job_fields( $tp_id, $revision_id, false, $job->get_id() ); } /** * @param array $tp_jobs * * @return array */ private function get_latest_jobs_grouped_by_cms_id( $tp_jobs ) { $result = array(); foreach ( $tp_jobs as $tp_job ) { if ( ! isset( $result[ $tp_job->cms_id ] ) ) { $result[ $tp_job->cms_id ] = $tp_job; } elseif ( $tp_job->id > $result[ $tp_job->cms_id ]->id ) { $result[ $tp_job->cms_id ] = $tp_job; } } return $result; } /** * @param WPML_TM_Post_Job_Entity $job * @param int $new_tp_id * * @return int|false */ private function get_new_status( WPML_TM_Post_Job_Entity $job, $new_tp_id ) { $new_status = false; if ( $job->get_tp_id() !== null && $new_tp_id ) { if ( $job->get_status() === ICL_TM_NOT_TRANSLATED || $this->has_been_completed_after_release( $job ) ) { $new_status = ICL_TM_IN_PROGRESS; } } return $new_status; } /** * @param WPML_TM_Post_Job_Entity $job * * @return bool * @throws Exception */ private function has_been_completed_after_release( WPML_TM_Post_Job_Entity $job ) { return $job->get_status() === ICL_TM_COMPLETE && $job->get_completed_date() && $job->get_completed_date() > $this->get_4_2_0_release_date(); } /** * @param int $cms_id * @param array $tp_jobs * * @return array */ private function get_tp_id_revision_id( $cms_id, $tp_jobs ) { $result = array( 0, 0 ); if ( isset( $tp_jobs[ $cms_id ] ) ) { $result = array( $tp_jobs[ $cms_id ]->id, $tp_jobs[ $cms_id ]->translation_revision, ); } return $result; } /** * @param int $tp_id * @param int $revision_id * @param int|false $status * @param int $job_id */ private function fix_job_fields( $tp_id, $revision_id, $status, $job_id ) { $new_data = array( 'tp_id' => $tp_id, 'tp_revision' => $revision_id, ); if ( $status ) { $new_data['status'] = $status; } $this->wpdb->update( $this->wpdb->prefix . 'icl_translation_status', $new_data, array( 'rid' => $job_id ) ); } /** * @param WPML_TM_Post_Job_Entity[] $jobs * * @return array */ private function map_cms_id_job_id( $jobs ) { $mapped_jobs = array(); foreach ( $jobs as $job ) { $cms_id = $this->cms_id_builder->cms_id_from_job_id( $job->get_translate_job_id() ); $mapped_jobs[ $job->get_id() ] = $cms_id; } return $mapped_jobs; } /** * @return DateTime * @throws Exception */ private function get_4_2_0_release_date() { return new DateTime( defined( 'WPML_4_2_0_RELEASE_DATE' ) ? WPML_4_2_0_RELEASE_DATE : '2019-01-20' ); } /** * @param int $rid * @param int $old_tp_id * @param int $new_tp_id * @param int $old_status * @param int|false $new_status */ private function log( $rid, $old_tp_id, $new_tp_id, $old_status, $new_status ) { $log = get_option( self::MIGRATION_FIX_LOG_KEY, array() ); $key = $new_status ? 'status_changed' : 'status_not_changed'; $log[ $key ][ $rid ] = array( 'rid' => $rid, 'old_tp_id' => $old_tp_id, 'new_tp_id' => $new_tp_id, 'old_status' => $old_status, 'new_status' => $new_status, ); update_option( self::MIGRATION_FIX_LOG_KEY, $log, false ); } } notices/translation-jobs-migration/class-wpml-tm-troubleshooting-fix-translation-jobs-tp-id.php 0000755 00000005025 14720342453 0027225 0 ustar 00 <?php class WPML_TM_Troubleshooting_Fix_Translation_Jobs_TP_ID { const AJAX_ACTION = 'wpml-fix-translation-jobs-tp-id'; private $jobs_migration; private $jobs_repository; public function __construct( WPML_Translation_Jobs_Migration $jobs_migration, WPML_TM_Jobs_Repository $jobs_repository ) { $this->jobs_migration = $jobs_migration; $this->jobs_repository = $jobs_repository; } public function add_hooks() { add_action( 'wpml_troubleshooting_after_fix_element_type_collation', array( $this, 'render_troubleshooting_section', ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'fix_tp_id_ajax' ) ); } public function fix_tp_id_ajax() { if ( isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], self::AJAX_ACTION ) ) { $job_ids = isset( $_POST['job_ids'] ) ? array_map( 'intval', explode( ',', filter_var( $_POST['job_ids'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) ) : array(); $jobs = array(); foreach ( $job_ids as $job_id ) { if ( ! $job_id ) { continue; } $params = new WPML_TM_Jobs_Search_Params(); $params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE ); $params->set_job_types( array( WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE ) ); $params->set_local_job_id( $job_id ); $jobs[] = current( $this->jobs_repository->get( $params )->getIterator()->getArrayCopy() ); } if ( $jobs ) { $this->jobs_migration->migrate_jobs( $jobs, true ); } wp_send_json_success(); } else { wp_send_json_error(); } } public function enqueue_scripts( $hook ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $hook ) { wp_enqueue_script( 'wpml-fix-tp-id', WPML_TM_URL . '/res/js/fix-tp-id.js', array( 'jquery' ), ICL_SITEPRESS_VERSION ); } } public function render_troubleshooting_section() { ?> <p> <input id="wpml_fix_tp_id_text" type="text" value=""/><input id="wpml_fix_tp_id_btn" type="button" class="button-secondary" value="<?php esc_attr_e( 'Fix WPML Translation Jobs "tp_id" field', 'sitepress' ); ?>"/><br/> <?php wp_nonce_field( self::AJAX_ACTION, 'wpml-fix-tp-id-nonce' ); ?> <small style="margin-left:10px;"><?php esc_attr_e( 'Fixes the "tp_id" field of WPML ranslation jobs and set the status to "in progress" (it requires manual action to re-sync translation status + download translations). It accepts comma separated values of translation job IDs (rid).', 'sitepress' ); ?></small> </p> <?php } } notices/translation-jobs-migration/class-wpml-translation-jobs-missing-tp-id-migration-notice.php 0000755 00000003040 14720342453 0027506 0 ustar 00 <?php class WPML_Translation_Jobs_Missing_TP_ID_Migration_Notice extends WPML_Translation_Jobs_Migration_Notice { /** * It gets the definition of the notice's content. * * @return array */ protected function get_model() { return array( 'strings' => array( 'title' => __( 'WPML Translation Jobs Migration', 'wpml-translation-management' ), /* translators: the placeholder is replaced with the current version of WPML */ 'description' => sprintf( __( 'WPML found some remote jobs on your site that must be migrated in order to work with WPML %s. You might not be able to access some of the WPML administration pages until this migration is fully completed.', 'wpml-translation-management' ), ICL_SITEPRESS_VERSION ), 'button' => __( 'Run now', 'wpml-translation-management' ), /* translators: this is shown between two number: processed items and total number of items to process */ 'of' => __( 'of', 'wpml-translation-management' ), 'jobs_migrated' => __( 'jobs migrated', 'wpml-translation-management' ), 'communicationError' => __( 'The communication error with Translation Proxy has appeared. Please try later.', 'wpml-translation-management' ), ), 'nonce' => wp_nonce_field( WPML_Translation_Jobs_Migration_Ajax::ACTION, WPML_Translation_Jobs_Migration_Ajax::ACTION, false, false ), ); } /** * It gets the ID of the notice. * * @return string */ protected function get_notice_id() { return 'translation-jobs-migration'; } } emails/ATE/class-wpml-tm-ate-request-activation-email.php 0000755 00000003211 14720342453 0017325 0 ustar 00 <?php class WPML_TM_ATE_Request_Activation_Email { const REQUEST_ACTIVATION_TEMPLATE = 'notification/request-ate-activation.twig'; /** @var WPML_TM_Email_Notification_View */ private $email_view; public function __construct( WPML_TM_Email_Notification_View $email_view ) { $this->email_view = $email_view; } public function send_email( $to_manager, $from_user ) { $site_name = get_option( 'blogname' ); $translators_tab_url = WPML_TM_Page::get_translators_url(); $model = array( 'setup_url' => esc_url( $translators_tab_url ), 'casual_name' => $to_manager->user_firstname, 'username' => $to_manager->display_name, 'intro_message_1' => sprintf( __( 'The translator %1$s is requesting to use the Advanced Translation Editor on site %2$s.', 'wpml-translation-management' ), $from_user->display_name, $site_name ), 'setup' => __( 'Advanced Translation Editor settings', 'wpml-translation-management' ), 'reminder' => sprintf( __( '* Remember, your login name for %1$s is %2$s. If you need help with your password, use the password reset in the login page.', 'wpml-translation-management' ), $site_name, $to_manager->user_login ), ); $to = $to_manager->display_name . ' <' . $to_manager->user_email . '>'; $message = $this->email_view->render_model( $model, self::REQUEST_ACTIVATION_TEMPLATE ); $subject = sprintf( __( "Request to use WPML's Advanced Translation Editor by %s", 'wpml-translation-management' ), $site_name ); $headers = array( 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', ); return wp_mail( $to, $subject, $message, $headers ); } } emails/overdue-report/wpml-tm-overdue-jobs-report.php 0000755 00000011110 14720342453 0017022 0 ustar 00 <?php class WPML_TM_Overdue_Jobs_Report { const OVERDUE_JOBS_REPORT_TEMPLATE = 'notification/overdue-jobs-report.twig'; /** @var WPML_Translation_Jobs_Collection $jobs_collection */ private $jobs_collection; /** @var WPML_TM_Email_Notification_View $email_view */ private $email_view; /** @var bool $has_active_remote_service */ private $has_active_remote_service; /** @var array $notification_settings */ private $notification_settings; private $sitepress; private $tp_jobs; /** * @param WPML_Translation_Jobs_Collection $jobs_collection * @param WPML_TM_Email_Notification_View $email_view * @param bool $has_active_remote_service * @param array $notification_settings * @param SitePress $sitepress * @param WPML_TP_Jobs_Collection|null $tp_jobs */ public function __construct( WPML_Translation_Jobs_Collection $jobs_collection, WPML_TM_Email_Notification_View $email_view, $has_active_remote_service, array $notification_settings, SitePress $sitepress, WPML_TP_Jobs_Collection $tp_jobs = null ) { $this->jobs_collection = $jobs_collection; $this->email_view = $email_view; $this->has_active_remote_service = $has_active_remote_service; $this->notification_settings = $notification_settings; $this->sitepress = $sitepress; $this->tp_jobs = $tp_jobs; } public function send() { $jobs_by_manager_id = $this->get_overdue_jobs_by_manager_id(); if ( $jobs_by_manager_id ) { $current_language = $this->sitepress->get_current_language(); $this->sitepress->switch_lang( $this->sitepress->get_default_language() ); foreach ( $jobs_by_manager_id as $manager_id => $jobs ) { $this->send_email( $manager_id, $jobs ); } $this->sitepress->switch_lang( $current_language ); } } /** @return array */ private function get_overdue_jobs_by_manager_id() { $args = array( 'overdue' => true, 'translator_id' => '', ); $jobs = $this->jobs_collection->get_jobs( $args ); $jobs_by_manager_id = array(); foreach ( $jobs as $key => $job ) { if ( $this->tp_jobs && $this->tp_jobs->is_job_canceled( $job ) ) { continue; } if ( ! $job instanceof WPML_Element_Translation_Job ) { continue; } if ( $job->get_number_of_days_overdue() < $this->notification_settings['overdue_offset'] ) { continue; } $job->get_basic_data(); $jobs_by_manager_id[ $job->get_manager_id() ][] = $job; } return $jobs_by_manager_id; } /** * @param string $manager_id * @param array $jobs */ private function send_email( $manager_id, array $jobs ) { $manager = get_user_by( 'id', $manager_id ); $translation_jobs_url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=jobs' ); /* translators: List of translation jobs: %s replaced by "list of translation jobs" */ $message_to_translation_jobsA = esc_html_x( 'You can see all the jobs that you sent and their deadlines in the %s.', 'List of translation jobs: %s replaced by "list of translation jobs"', 'wpml-translation-management' ); /* translators: List of translation jobs: used to build a link to the translation jobs page */ $message_to_translation_jobsB = esc_html_x( 'list of translation jobs', 'List of translation jobs: used to build a link to the translation jobs page', 'wpml-translation-management' ); $message_to_translation_jobs = sprintf( $message_to_translation_jobsA, '<a href="' . esc_url( $translation_jobs_url ) . '">' . $message_to_translation_jobsB . '</a>' ); $model = array( 'username' => $manager->display_name, 'intro_message_1' => __( 'This is a quick reminder about translation jobs that you sent and are behind schedule.', 'wpml-translation-management' ), 'intro_message_2' => __( 'The deadline that you set for the following jobs has passed:', 'wpml-translation-management' ), 'jobs' => $jobs, 'job_deadline_details' => __( 'deadline: %1$s, late by %2$d days', 'wpml-translation-management' ), 'message_to_translation_jobs' => $message_to_translation_jobs, 'promote_translation_services' => ! $this->has_active_remote_service, ); $to = $manager->display_name . ' <' . $manager->user_email . '>'; $subject = esc_html__( 'Overdue translation jobs report', 'wpml-translation-management' ); $message = $this->email_view->render_model( $model, self::OVERDUE_JOBS_REPORT_TEMPLATE ); $headers = array( 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', ); wp_mail( $to, $subject, $message, $headers ); } } emails/overdue-report/wpml-tm-overdue-jobs-report-factory.php 0000755 00000001650 14720342453 0020477 0 ustar 00 <?php class WPML_TM_Overdue_Jobs_Report_Factory { public function create() { global $wpdb, $iclTranslationManagement, $sitepress; $jobs_collection = new WPML_Translation_Jobs_Collection( $wpdb, array() ); $email_template_service_factory = wpml_tm_get_email_twig_template_factory(); $report_email_view = new WPML_TM_Email_Notification_View( $email_template_service_factory->create() ); $has_active_remote_service = TranslationProxy::is_current_service_active_and_authenticated(); $notification_settings = $iclTranslationManagement->settings['notification']; $tp_jobs_factory = new WPML_TP_Jobs_Collection_Factory(); $tp_jobs = $tp_jobs_factory->create(); return new WPML_TM_Overdue_Jobs_Report( $jobs_collection, $report_email_view, $has_active_remote_service, $notification_settings, $sitepress, $tp_jobs ); } } emails/wpml-tm-email-twig-template-factory.php 0000755 00000000364 14720342453 0015451 0 ustar 00 <?php class WPML_TM_Email_Twig_Template_Factory { /** @return WPML_Twig_Template */ public function create() { $loader = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/emails/' ) ); return $loader->get_template(); } } emails/wpml-tm-mail-notification.php 0000755 00000036424 14720342453 0013550 0 ustar 00 <?php /** * Class WPML_TM_Mail_Notification */ class WPML_TM_Mail_Notification { const JOB_REVISED_TEMPLATE = 'notification/job-revised.twig'; const JOB_CANCELED_TEMPLATE = 'notification/job-canceled.twig'; private $mail_cache = array(); private $process_mail_queue; /** @var wpdb $wpdb */ private $wpdb; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Translation_Job_Factory $job_factory */ private $job_factory; /** @var WPML_TM_Email_Notification_View $email_view */ private $email_view; /** @var array $notification_settings */ private $notification_settings; /** @var bool $has_active_remote_service */ private $has_active_remote_service; public function __construct( SitePress $sitepress, wpdb $wpdb, WPML_Translation_Job_Factory $job_factory, WPML_TM_Email_Notification_View $email_view, array $notification_settings, $has_active_remote_service ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->job_factory = $job_factory; $this->email_view = $email_view; $this->notification_settings = array_merge( array( 'resigned' => 0, 'completed' => 0, ), $notification_settings ); $this->has_active_remote_service = $has_active_remote_service; } public function init() { add_action( 'wpml_tm_empty_mail_queue', array( $this, 'send_queued_mails' ), 10, 0 ); if ( $this->should_send_email_on_update() ) { add_action( 'wpml_tm_revised_job_notification', array( $this, 'action_revised_job_email' ), 10 ); add_action( 'wpml_tm_canceled_job_notification', array( $this, 'action_canceled_job_email' ), 10 ); } add_action( 'wpml_tm_remove_job_notification', array( $this, 'action_translator_removed_mail' ), 10, 2 ); add_action( 'wpml_tm_resign_job_notification', array( $this, 'action_translator_resign_mail' ), 10, 2 ); add_action( 'icl_ajx_custom_call', array( $this, 'send_queued_mails' ), 10, 0 ); add_action( 'icl_pro_translation_completed', array( $this, 'send_queued_mails' ), 10, 0 ); } /** * @return bool */ private function should_send_email_on_update() { return ! isset( $this->notification_settings[ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ) || ( isset( $this->notification_settings[ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ) && WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY === (int) $this->notification_settings[ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ); } public function send_queued_mails() { $tj_url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php' ); foreach ( $this->mail_cache as $type => $mail_to_send ) { foreach ( $mail_to_send as $to => $subjects ) { $headers = ''; $body_to_send = ''; foreach ( $subjects as $subject => $content ) { $body = $content['body']; $home_url = get_home_url(); if ( 'completed' === $type ) { $headers = array( 'Content-type: text/html; charset=UTF-8', ); $body_to_send .= $body[0]; } else { $body_to_send .= $body_to_send . "\n\n" . implode( "\n\n\n\n", $body ) . "\n\n\n\n"; if ( $type === 'translator' ) { $footer = sprintf( __( 'You can view your other translation jobs here: %s', 'wpml-translation-management' ), $tj_url ) . "\n\n--\n"; $footer .= sprintf( __( "This message was automatically sent by Translation Management running on %1\$s. To stop receiving these notifications contact the system administrator at %2\$s.\n\nThis email is not monitored for replies.", 'wpml-translation-management' ), get_bloginfo( 'name' ), $home_url ); } else { $footer = "\n--\n" . sprintf( __( "This message was automatically sent by Translation Management running on %1\$s. To stop receiving these notifications, go to Notification Settings, or contact the system administrator at %2\$s.\n\nThis email is not monitored for replies.", 'wpml-translation-management' ), get_bloginfo( 'name' ), $home_url ); } $body_to_send .= $footer; } $attachments = isset( $content['attachment'] ) ? $content['attachment'] : array(); $attachments = apply_filters( 'wpml_new_job_notification_attachments', $attachments ); /** * @deprecated Use 'wpml_new_job_notification_attachments' instead */ $attachments = apply_filters( 'WPML_new_job_notification_attachments', $attachments ); $this->sitepress->get_wp_api()->wp_mail( $to, $subject, $body_to_send, $headers, $attachments ); } } } $this->mail_cache = array(); $this->process_mail_queue = false; } /** * @param int|WPML_Translation_Job $job_id * * @return array|null */ private function get_basic_mail_data( $job_id ) { $manager_id = false; /** @var WPML_Translation_Job|false $job */ if ( is_object( $job_id ) ) { $job = $job_id; } else { $job = $this->job_factory->get_translation_job( $job_id, false, 0, true ); } if ( is_object( $job ) ) { $data = $job->get_basic_data(); $manager_id = isset( $data->manager_id ) ? $data->manager_id : -1; } if ( ! $job instanceof WPML_Translation_Job || ( $manager_id && (int) $manager_id === (int) $job->get_translator_id() ) ) { return null; } $manager = new WP_User( $manager_id ); $translator = new WP_User( $job->get_translator_id() ); $user_language = $this->sitepress->get_user_admin_language( $manager->ID ); $mail = array( 'to' => $manager->display_name . ' <' . $manager->user_email . '>', ); $this->sitepress->switch_locale( $user_language ); list( $lang_from, $lang_to ) = $this->get_lang_to_from( $job, $user_language ); $model = array( 'view_jobs_text' => __( 'View translation jobs', 'wpml-translation-management' ), 'username' => $manager->display_name, 'lang_from' => $lang_from, 'lang_to' => $lang_to, ); $document_title = $job->get_title(); if ( 'string' !== strtolower( $job->get_type() ) ) { /** @var WPML_Post_Translation_Job $job */ $model['translation_jobs_url'] = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=jobs' ); $document_title = '<a href="' . $job->get_url( true ) . '">' . $document_title . '</a>'; $model = $this->update_model_for_deadline( $model, $job ); } return array( 'mail' => $mail, 'document_title' => $document_title, 'job' => $job, 'manager' => $manager, 'translator' => $translator, 'model' => $model, ); } /** * @param int $job_id * * @return void */ public function action_revised_job_email( $job_id ) { $this->revised_job_email( $job_id ); } /** * @param int $job_id * * @return array|bool */ public function revised_job_email( $job_id ) { if ( ! $this->should_send_immediate_notification( 'completed' ) ) { return false; } $subject = sprintf( __( 'Translator job updated for %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); $placeholder = esc_html__( 'A new translation for %1$s from %2$s to %3$s was created on the Translation Service. It’s ready to download and will be applied next time the Translation Service delivers completed translations to your site or when you manually fetch them.', 'wpml-translation-management' ); return $this->generic_update_notification_email( $job_id, $subject, $placeholder, self::JOB_REVISED_TEMPLATE ); } /** * @param WPML_TM_Job_Entity $job * * @return void */ public function action_canceled_job_email( WPML_TM_Job_Entity $job ) { $this->canceled_job_email( $job ); } /** * @param WPML_TM_Job_Entity $job * * @return array|bool */ public function canceled_job_email( WPML_TM_Job_Entity $job ) { if ( ! $job instanceof WPML_TM_Post_Job_Entity ) { return false; } if ( ! $this->should_send_immediate_notification( 'completed' ) ) { return false; } $subject = sprintf( __( 'Translator job canceled for %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); $placeholder = esc_html__( 'The translation for %1$s from %2$s to %3$s was canceled on the Translation Service. You can send this document to translation again from the Translation Dashboard.', 'wpml-translation-management' ); return $this->generic_update_notification_email( $job->get_translate_job_id(), $subject, $placeholder, self::JOB_CANCELED_TEMPLATE ); } private function generic_update_notification_email( $job_id, $mail_subject, $body_placeholder, $template ) { $basic_mail_data = $this->get_basic_mail_data( $job_id ); if ( null === $basic_mail_data ) { return null; } $mail = $basic_mail_data['mail']; $document_title = $basic_mail_data['document_title']; $model = $basic_mail_data['model']; $lang_from = $model['lang_from']; $lang_to = $model['lang_to']; $mail['subject'] = $mail_subject; $model['message'] = sprintf( $body_placeholder, $document_title, $lang_from, $lang_to ); $mail['body'] = $this->email_view->render_model( $model, $template ); $mail['type'] = 'completed'; $this->enqueue_mail( $mail ); $this->sitepress->switch_locale(); return $mail; } private function should_send_immediate_notification( $type ) { return isset( $this->notification_settings[ $type ] ) && (int) $this->notification_settings[ $type ] === WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY; } /** * @param array $model * @param WPML_Element_Translation_Job $job * * @return array */ private function update_model_for_deadline( array $model, WPML_Element_Translation_Job $job ) { if ( $job->is_completed_on_time() ) { $model['deadline_status'] = __( 'The translation job was completed on time.', 'wpml-translation-management' ); return $model; } $overdue_days = $job->get_number_of_days_overdue(); if ( ! $overdue_days ) { $model['deadline_status'] = ''; return $model; } $model['deadline_status'] = sprintf( _n( 'This translation job is overdue by %s day.', 'This translation job is overdue by %s days.', $overdue_days, 'wpml-translation-management' ), $overdue_days ); if ( $overdue_days >= 7 ) { $model['promote_translation_services'] = ! $this->has_active_remote_service; } return $model; } /** * @param int $translator_id * @param WPML_Translation_Job|int $job * * @return void */ public function action_translator_removed_mail( $translator_id, $job ) { $this->translator_removed_mail( $translator_id, $job ); } /** * @param int $translator_id * @param WPML_Translation_Job|int $job * * @return bool */ public function translator_removed_mail( $translator_id, $job ) { /** @var WPML_Translation_Job $job */ list( $manager_id, $job ) = $this->get_mail_elements( $job ); if ( ! $job || $manager_id == $translator_id ) { return false; } $translator = new WP_User( $translator_id ); $manager = new WP_User( $manager_id ); $user_language = $this->sitepress->get_user_admin_language( $manager->ID ); $doc_title = $job->get_title(); $this->sitepress->switch_locale( $user_language ); list( $lang_from, $lang_to ) = $this->get_lang_to_from( $job, $user_language ); $mail['to'] = $translator->display_name . ' <' . $translator->user_email . '>'; $mail['subject'] = sprintf( __( 'Removed from translation job on %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); $mail['body'] = sprintf( __( 'You have been removed from the translation job "%1$s" for %2$s to %3$s.', 'wpml-translation-management' ), $doc_title, $lang_from, $lang_to ); $mail['type'] = 'translator'; $this->enqueue_mail( $mail ); $this->sitepress->switch_locale(); return $mail; } /** * @param int $translator_id * @param int|WPML_Translation_Job $job_id * * @return void */ public function action_translator_resign_mail( $translator_id, $job_id ) { $this->translator_resign_mail( $translator_id, $job_id ); } /** * @param int $translator_id * @param int|WPML_Translation_Job $job_id * * @return array|bool */ public function translator_resign_mail( $translator_id, $job_id ) { /** @var WPML_Translation_Job $job */ list( $manager_id, $job ) = $this->get_mail_elements( $job_id ); if ( ! $job || $manager_id == $translator_id ) { return false; } $translator = new WP_User( $translator_id ); $manager = new WP_User( $manager_id ); $tj_url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=jobs' ); $doc_title = $job->get_title(); $user_language = $this->sitepress->get_user_admin_language( $manager->ID ); $this->sitepress->switch_locale( $user_language ); list( $lang_from, $lang_to ) = $this->get_lang_to_from( $job, $user_language ); $mail = array(); if ( $this->notification_settings['resigned'] == ICL_TM_NOTIFICATION_IMMEDIATELY ) { $mail['to'] = $manager->display_name . ' <' . $manager->user_email . '>'; $mail['subject'] = sprintf( __( 'Translator has resigned from job on %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); $original_doc_title = $doc_title ? $doc_title : __( 'Deleted', 'wpml-translation-management' ); $mail['body'] = sprintf( __( 'Translator %1$s has resigned from the translation job "%2$s" for %3$s to %4$s.%5$sView translation jobs: %6$s', 'wpml-translation-management' ), $translator->display_name, $original_doc_title, $lang_from, $lang_to, "\n", $tj_url ); $mail['type'] = 'admin'; $this->enqueue_mail( $mail ); } // restore locale $this->sitepress->switch_locale(); return $mail; } private function enqueue_mail( $mail ) { if ( $mail !== 'empty_queue' ) { $this->mail_cache[ $mail['type'] ][ $mail['to'] ][ $mail['subject'] ]['body'][] = $mail['body']; if ( isset( $mail['attachment'] ) ) { $this->mail_cache[ $mail['type'] ][ $mail['to'] ][ $mail['subject'] ]['attachment'][] = $mail['attachment']; } $this->process_mail_queue = true; } } /** * @param int|WPML_Translation_Job $job_id * * @return array */ private function get_mail_elements( $job_id ) { $job = is_object( $job_id ) ? $job_id : $this->job_factory->get_translation_job( $job_id, false, 0, true ); if ( is_object( $job ) ) { $data = $job->get_basic_data(); $manager_id = isset( $data->manager_id ) ? $data->manager_id : - 1; } else { $job = false; $manager_id = false; } return array( $manager_id, $job ); } /** * @param WPML_Translation_Job $job * @param string $user_language * * @return array */ private function get_lang_to_from( $job, $user_language ) { $sql = "SELECT name FROM {$this->wpdb->prefix}icl_languages_translations WHERE language_code=%s AND display_language_code=%s LIMIT 1"; $lang_from = $this->wpdb->get_var( $this->wpdb->prepare( $sql, $job->get_source_language_code(), $user_language ) ); $lang_to = $this->wpdb->get_var( $this->wpdb->prepare( $sql, $job->get_language_code(), $user_language ) ); return array( $lang_from, $lang_to ); } } emails/wpml-tm-email-view.php 0000755 00000002666 14720342453 0012202 0 ustar 00 <?php abstract class WPML_TM_Email_View { const FOOTER_TEMPLATE = 'email-footer.twig'; const HEADER_TEMPLATE = 'email-header.twig'; /** @var WPML_Twig_Template $template_service */ protected $template_service; public function __construct( WPML_Twig_Template $template_service ) { $this->template_service = $template_service; } /** * @param string $username * * @return string */ public function render_header( $username = '' ) { $model = array( 'greetings' => sprintf( __( 'Dear %s,', 'wpml-translation-management' ), $username ), ); return $this->template_service->show( $model, self::HEADER_TEMPLATE ); } /** * @param string $username * * @return string */ public function render_casual_header( $first_name = '' ) { $model = array( 'greetings' => sprintf( __( 'Hi %s,', 'wpml-translation-management' ), $first_name ), ); return $this->template_service->show( $model, self::HEADER_TEMPLATE ); } /** * @param string $bottom_text * * @return string */ protected function render_email_footer( $bottom_text = '' ) { $site_url = get_bloginfo( 'url' ); $model = array( 'bottom_text' => $bottom_text, 'wpml_footer' => sprintf( esc_html__( 'Generated by WPML plugin, running on %s.', 'wpml-translation-management' ), '<a href="' . $site_url . '" style="color: #ffffff;">' . $site_url . '</a>' ), ); return $this->template_service->show( $model, self::FOOTER_TEMPLATE ); } } emails/report/class-wpml-tm-batch-report-hooks.php 0000755 00000002122 14720342453 0016257 0 ustar 00 <?php /** * Class WPML_TM_Notification_Batch_Hooks */ class WPML_TM_Batch_Report_Hooks { /** * @var WPML_TM_Batch_Report */ private $batch_report; /** * @var WPML_TM_Batch_Report_Email_Process */ private $email_process; /** * WPML_TM_Batch_Report_Hooks constructor. * * @param WPML_TM_Batch_Report $batch_report * @param WPML_TM_Batch_Report_Email_Process $email_process */ public function __construct( WPML_TM_Batch_Report $batch_report, WPML_TM_Batch_Report_Email_Process $email_process ) { $this->batch_report = $batch_report; $this->email_process = $email_process; } public function add_hooks() { add_action( 'wpml_tm_assign_job_notification', array( $this, 'set_job' ) ); add_action( 'wpml_tm_new_job_notification', array( $this, 'set_job' ), 10, 1 ); add_action( 'wpml_tm_local_string_sent', array( $this, 'set_job' ) ); add_action( 'wpml_tm_basket_committed', array( $this->email_process, 'process_emails' ) ); } public function set_job( $job ) { if ( $job instanceof WPML_Translation_Job ) { $this->batch_report->set_job( $job ); } } } emails/report/class-wpml-tm-batch-report.php 0000755 00000004226 14720342453 0015145 0 ustar 00 <?php /** * Class WPML_TM_Batch_Report */ class WPML_TM_Batch_Report { const BATCH_REPORT_OPTION = '_wpml_batch_report'; /** * @var WPML_TM_Blog_Translators */ private $blog_translators; /** * WPML_TM_Batch_Report constructor. * * @param WPML_TM_Blog_Translators $blog_translators */ public function __construct( WPML_TM_Blog_Translators $blog_translators) { $this->blog_translators = $blog_translators; } /** * @param WPML_Translation_Job $job */ public function set_job( WPML_Translation_Job $job ) { $batch_jobs = $batch_jobs_raw = $this->get_jobs(); $job_fields = $job->get_basic_data(); if ( WPML_User_Jobs_Notification_Settings::is_new_job_notification_enabled( $job_fields->translator_id ) ) { $lang_pair = $job_fields->source_language_code . '|' . $job_fields->language_code; $batch_jobs[ (int) $job_fields->translator_id ][$lang_pair][] = array( 'element_id' => isset( $job_fields->original_doc_id ) ? $job_fields->original_doc_id : null, 'type' => strtolower( $job->get_type() ), 'job_id' => $job->get_id(), ); } if ( $batch_jobs_raw !== $batch_jobs ) { update_option( self::BATCH_REPORT_OPTION, $batch_jobs, 'no' ); } } /** * @return array */ public function get_unassigned_jobs() { $batch_jobs = $this->get_jobs(); $unassigned_jobs = array(); if( array_key_exists( 0, $batch_jobs ) ) { $unassigned_jobs = $batch_jobs[0]; } return $unassigned_jobs; } /** * @return array */ public function get_unassigned_translators() { $assigned_translators = array_keys( $this->get_jobs() ); $blog_translators = wp_list_pluck( $this->blog_translators->get_blog_translators() , 'ID'); return array_diff( $blog_translators, $assigned_translators ); } /** * @return array */ public function get_jobs() { return get_option( self::BATCH_REPORT_OPTION ) ? get_option( self::BATCH_REPORT_OPTION ) : array(); } public function reset_batch_report( $translator_id ) { $batch_jobs = $this->get_jobs(); if ( array_key_exists( $translator_id, $batch_jobs ) ) { unset( $batch_jobs[$translator_id] ); } update_option( self::BATCH_REPORT_OPTION, $batch_jobs, 'no' ); } } emails/report/class-wpml-tm-batch-report-email-builder.php 0000755 00000011413 14720342453 0017652 0 ustar 00 <?php /** * Class WPML_TM_Batch_Report_Email */ class WPML_TM_Batch_Report_Email_Builder { /** * @var WPML_TM_Batch_Report */ private $batch_report; /** * @var array */ private $emails; /** * @var WPML_TM_Email_Jobs_Summary_View */ private $email_template; /** * WPML_TM_Notification_Batch_Email constructor. * * @param WPML_TM_Batch_Report $batch_report * @param WPML_TM_Email_Jobs_Summary_View $email_template */ public function __construct( WPML_TM_Batch_Report $batch_report, WPML_TM_Email_Jobs_Summary_View $email_template ) { $this->batch_report = $batch_report; $this->email_template = $email_template; $this->emails = array(); } /** * @param array $batch_jobs */ public function prepare_assigned_jobs_emails( $batch_jobs ) { foreach ( $batch_jobs as $translator_id => $language_pairs ) { if ( 0 !== $translator_id ) { $translator = get_userdata( $translator_id ); $title = __( 'You have been assigned to new translation job(s):', 'wpml-translation-management' ); $render_jobs_list = $this->email_template->render_jobs_list( $language_pairs, $translator_id, $title ); if ( null === $render_jobs_list ) { continue; } $body = $this->email_template->render_header( $translator->display_name ); $body .= $render_jobs_list; $assigned_jobs = $this->email_template->get_assigned_jobs(); $title_singular = __( 'There is 1 job, which you can take (not specifically assigned to you):', 'wpml-translation-management' ); $title_plural = __( 'There are %s jobs, which you can take (not specifically assigned to you):', 'wpml-translation-management' ); $unassigned_jobs_body = $this->email_template->render_jobs_list( $this->batch_report->get_unassigned_jobs(), $translator_id, $title_singular, $title_plural ); if ( null !== $unassigned_jobs_body ) { $body .= $unassigned_jobs_body; } $body .= $this->email_template->render_footer(); $email['body'] = $body; $email = $this->add_attachments( $email, $assigned_jobs ); $this->emails[] = array( 'translator_id' => $translator->ID, 'email' => $translator->user_email, 'subject' => $this->get_subject_assigned_job(), 'body' => $body, 'attachment' => array_key_exists( 'attachment', $email ) ? $email['attachment'] : array(), ); } } } /** * @param array $batch_jobs */ public function prepare_unassigned_jobs_emails( $batch_jobs ) { if ( array_key_exists( 0, $batch_jobs ) ) { $unassigned_jobs = $batch_jobs[0]; $translators = $this->batch_report->get_unassigned_translators(); $title_singular = __( 'There is 1 job waiting for a translator:', 'wpml-translation-management' ); $title_plural = __( 'There are %s jobs waiting for a translator:', 'wpml-translation-management' ); foreach ( $translators as $translator ) { $translator_user = get_userdata( $translator ); $render_jobs_list = $this->email_template->render_jobs_list( $unassigned_jobs, $translator_user->ID, $title_singular, $title_plural ); if ( null !== $render_jobs_list ) { $body = $this->email_template->render_header( $translator_user->display_name ); $body .= $render_jobs_list; $body .= $this->email_template->render_footer(); $this->emails[] = array( 'translator_id' => $translator_user->ID, 'email' => $translator_user->user_email, 'subject' => $this->get_subject_unassigned_job(), 'body' => $body, ); } } } } /** * @param array $email * @param array $jobs * * @return array */ private function add_attachments( $email, $jobs ) { $attachments = array(); foreach ( $jobs as $job ) { if ( 'post' === $job['type'] ) { $email = apply_filters( 'wpml_new_job_notification', $email, $job['job_id'] ); if ( array_key_exists( 'attachment', $email ) ) { $attachments[] = $email['attachment']; } } } if ( $attachments ) { $attachments = apply_filters( 'wpml_new_job_notification_attachments', $attachments ); if ( count( $attachments ) > 0 ) { $attachment_values = array_values( $attachments ); $email['attachment'] = $attachment_values[0]; } } return $email; } /** * @return string */ private function get_subject_assigned_job() { return sprintf( __( 'New translation job from %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); } /** * @return string */ private function get_subject_unassigned_job() { return sprintf( __( 'Job waiting for a translator in %s', 'wpml-translation-management' ), get_bloginfo( 'name' ) ); } /** * @return array */ public function get_emails() { return $this->emails; } } emails/report/class-wpml-tm-email-jobs-summary-view.php 0000755 00000012456 14720342453 0017244 0 ustar 00 <?php /** * Class WPML_TM_Email_Jobs_Summary_View */ class WPML_TM_Email_Jobs_Summary_View extends WPML_TM_Email_View { const JOBS_TEMPLATE = 'batch-report/email-job-pairs.twig'; /** * @var WPML_TM_Blog_Translators */ private $blog_translators; /** * @var SitePress */ private $sitepress; /** * @var array */ private $assigned_jobs; /** * WPML_TM_Batch_Report_Email_Template constructor. * * @param WPML_Twig_Template $template_service * @param WPML_TM_Blog_Translators $blog_translators * @param SitePress $sitepress */ public function __construct( WPML_Twig_Template $template_service, WPML_TM_Blog_Translators $blog_translators, SitePress $sitepress ) { parent::__construct( $template_service ); $this->blog_translators = $blog_translators; $this->sitepress = $sitepress; } /** * @param array $language_pairs * @param int $translator_id * @param string $title_singular * @param string $title_plural * * @return null|string */ public function render_jobs_list( $language_pairs, $translator_id, $title_singular, $title_plural = '' ) { $this->empty_assigned_jobs(); $model = array( 'strings' => array( 'strings_text' => __( 'Strings', 'wpml-translation-management' ), 'start_translating_text' => __( 'start translating', 'wpml-translation-management' ), 'take' => _x( 'take it', 'Take a translation job waiting for a translator', 'wpml-translation-management' ), 'strings_link' => admin_url( 'admin.php?page=wpml-string-translation%2Fmenu%2Fstring-translation.php' ), 'closing_sentence' => $this->get_closing_sentence(), ), ); foreach ( $language_pairs as $lang_pair => $elements ) { $languages = explode( '|', $lang_pair ); $args = array( 'lang_from' => $languages[0], 'lang_to' => $languages[1] ); if ( $this->blog_translators->is_translator( $translator_id, $args ) && WPML_User_Jobs_Notification_Settings::is_new_job_notification_enabled( $translator_id ) ) { $model_elements = array(); $string_added = false; foreach ( $elements as $element ) { if ( ! $string_added || 'string' !== $element['type'] ) { $model_elements[] = array( 'original_link' => get_permalink( $element['element_id'] ), 'original_text' => sprintf( __( 'Link to original document %d', 'wpml-translation-management' ), $element['element_id'] ), 'start_translating_link' => admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '%2Fmenu%2Ftranslations-queue.php&job_id=' . $element['job_id'] ), 'type' => $element['type'], ); if ( 'string' === $element['type'] ) { $string_added = true; } } $this->add_assigned_job( $element['job_id'], $element['type'] ); } $source_lang = $this->sitepress->get_language_details( $languages[0] ); $target_lang = $this->sitepress->get_language_details( $languages[1] ); $model['lang_pairs'][$lang_pair] = array( 'title' => sprintf( __( 'From %1$s to %2$s:', 'wpml-translation-management' ), $source_lang['english_name'], $target_lang['english_name'] ), 'elements' => $model_elements, ); } } $model['strings']['title'] = $title_singular; if ( 1 < count( $this->get_assigned_jobs() ) ) { $model['strings']['title'] = sprintf( $title_plural, count( $this->get_assigned_jobs() ) ); } return count( $this->get_assigned_jobs() ) ? $this->template_service->show( $model, self::JOBS_TEMPLATE ) : null; } /** @return string */ public function render_footer() { $site_url = get_bloginfo( 'url' ); $profile_link = '<a href="' . admin_url( 'profile.php' ) . '" style="color: #ffffff;">' . esc_html__( 'Your Profile', '' ) .'</a>'; $bottom_text = sprintf( __( 'You are receiving this email because you have a translator account in %1$s. To stop receiving notifications, log-in to %2$s and unselect "Send me a notification email when there is something new to translate". Please note that this will take you out of the translators pool.', 'wpml-translation-management' ), $site_url, $profile_link ); return $this->render_email_footer( $bottom_text ); } /** * @param int $job_id * @param string $type */ private function add_assigned_job( $job_id, $type ) { $this->assigned_jobs[] = array( 'job_id' => $job_id, 'type' => $type, ); } /** * @return array */ public function get_assigned_jobs() { $string_counted = false; foreach ( $this->assigned_jobs as $key => $assigned_job ) { if ( 'string' === $assigned_job['type'] ) { if ( $string_counted ) { unset( $this->assigned_jobs[$key] ); } $string_counted = true; } } return $this->assigned_jobs; } private function empty_assigned_jobs() { $this->assigned_jobs = array(); } private function get_closing_sentence() { $sentence = null; if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) { $link = '<a href="https://wpml.org/documentation/translating-your-contents/advanced-translation-editor/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm">' . __( "WPML's Advanced Translation Editor", 'wpml-translation-management' ) . '</a>'; $sentence = sprintf( __( "Need help translating? Read how to use %s.", 'wpml-translation-management' ), $link ); } return $sentence; } } emails/report/class-wpml-tm-batch-report-email-process.php 0000755 00000002611 14720342453 0017702 0 ustar 00 <?php /** * Class WPML_TM_Batch_Report_Email_Process */ class WPML_TM_Batch_Report_Email_Process { /** * @var WPML_TM_Batch_Report */ private $batch_report; /** * @var WPML_TM_Batch_Report_Email_Builder */ private $email_builder; /** * WPML_TM_Batch_Report_Email_Process constructor. * * @param WPML_TM_Batch_Report $batch_report * @param WPML_TM_Batch_Report_Email_Builder $email_builder */ public function __construct( WPML_TM_Batch_Report $batch_report, WPML_TM_Batch_Report_Email_Builder $email_builder ) { $this->batch_report = $batch_report; $this->email_builder = $email_builder; } public function process_emails() { $batch_jobs = $this->batch_report->get_jobs(); $this->email_builder->prepare_assigned_jobs_emails( $batch_jobs ); $this->email_builder->prepare_unassigned_jobs_emails( $batch_jobs ); $this->send_emails(); } private function send_emails() { $headers = array(); $headers[] = 'Content-type: text/html; charset=UTF-8'; foreach ( $this->email_builder->get_emails() as $email ) { $email['attachment'] = isset( $email['attachment'] ) ? $email['attachment'] : array(); $email_sent = wp_mail( $email['email'], $email['subject'], $email['body'], $headers, $email['attachment'] ); if ( $email_sent ) { $this->batch_report->reset_batch_report( $email['translator_id'] ); } } $this->batch_report->reset_batch_report( 0 ); } } emails/notification/summary/interface-wpml-tm-jobs-summary-report-model.php 0000755 00000000267 14720342453 0023316 0 ustar 00 <?php interface WPML_TM_Jobs_Summary_Report_Model { /** * @return string */ public function get_subject(); /** * @return string */ public function get_summary_text(); } emails/notification/summary/class-wpml-tm-jobs-summary-report-view.php 0000755 00000006536 14720342453 0022342 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report_View extends WPML_TM_Email_View { const WEEKLY_SUMMARY_TEMPLATE = 'notification/summary/summary.twig'; /** * @var array */ private $jobs; /** * @var int */ private $manager_id; /** * @var string */ private $summary_text; /** * @return string */ public function get_report_content() { $model = $this->get_model(); $content = $this->render_header( $model['username'] ); $content .= $this->template_service->show( $model, self::WEEKLY_SUMMARY_TEMPLATE ); $content .= $this->render_email_footer(); return $content; } /** * @return array */ private function get_model() { return array( 'username' => get_userdata( $this->manager_id )->display_name, 'jobs' => $this->jobs, 'text' => $this->summary_text, 'site_name' => get_bloginfo( 'name' ), 'number_of_updates' => isset( $this->jobs['completed'] ) ? count( $this->jobs['completed'] ) : 0, 'strings' => array( 'jobs_waiting' => __( 'Jobs that are waiting for translation', 'wpml-translation-management' ), 'original_page' => __( 'Original Page', 'wpml-translation-management' ), 'translation' => __( 'Translation', 'wpml-translation-management' ), 'translator' => __( 'Translator', 'wpml-translation-management' ), 'updated' => __( 'Updated / Translated', 'wpml-translation-management' ), 'date' => __( 'Date', 'wpml-translation-management' ), 'your_deadline' => __( 'Your deadline', 'wpml-translation-management' ), 'translation_languages' => __( 'Translation languages', 'wpml-translation-management' ), 'number_of_pages' => __( 'Number of pages', 'wpml-translation-management' ), 'number_of_strings' => __( 'Number of strings', 'wpml-translation-management' ), 'number_of_words' => __( 'Number of words', 'wpml-translation-management' ), 'undefined' => __( 'Undefined', 'wpml-translation-management' ), ), 'improve_quality' => array( 'title' => __( 'Want to improve the quality of your site’s translation?', 'wpml-translation-management' ), 'options' => array( array( 'link_url' => admin_url( 'admin.php?page=' . WPML_PLUGIN_FOLDER . '/menu/languages.php#wpml-translation-feedback-options' ), 'link_text' => __( 'Translation Feedback', 'wpml-translation-management' ), 'text' => __( 'Allow visitors to tell you about translation issues by enabling %s', 'wpml-translation-management' ), ), array( 'link_url' => admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=translators' ), 'link_text' => __( 'translation services that are integrated with WPML', 'wpml-translation-management' ), 'text' => __( 'Try one of the %s', 'wpml-translation-management' ), ), ) ), ); } /** * @param array $jobs * * @return $this */ public function set_jobs( $jobs ) { $this->jobs = $jobs; return $this; } /** * @param int $manager_id * * @return $this */ public function set_manager_id( $manager_id ) { $this->manager_id = $manager_id; return $this; } /** * @param string $summary_text * * @return $this */ public function set_summary_text( $summary_text ) { $this->summary_text = $summary_text; return $this; } } emails/notification/summary/wpml-tm-jobs-summary-report-hooks.php 0000755 00000004311 14720342453 0021375 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report_Hooks { const EVENT_HOOK = 'wpml_tm_send_summary_report'; const EVENT_CALLBACK = 'send_summary_report'; /** * @var WPML_TM_Jobs_Summary_Report_Process_Factory */ private $process_factory; /** * @var TranslationManagement */ private $tm; public function __construct( WPML_TM_Jobs_Summary_Report_Process_Factory $process_factory, TranslationManagement $tm ) { $this->process_factory = $process_factory; $this->tm = $tm; } public function add_hooks() { if ( $this->notification_setting_allow_scheduling() ) { add_action( self::EVENT_HOOK, array( $this, self::EVENT_CALLBACK ) ); add_action( 'init', array( $this, 'schedule_email' ) ); } } /** * @return bool */ private function notification_setting_allow_scheduling() { $schedulable_settings = array( WPML_TM_Emails_Settings::NOTIFY_DAILY, WPML_TM_Emails_Settings::NOTIFY_WEEKLY ); return isset( $this->tm->settings['notification'][ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ) && in_array( (int) $this->tm->settings['notification'][ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ], $schedulable_settings, true ); } public function send_summary_report() { if ( WPML_TM_Emails_Settings::NOTIFY_DAILY === (int) $this->tm->settings['notification'][ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ) { $summary_report_process = $this->process_factory->create_daily_report(); } else { $summary_report_process = $this->process_factory->create_weekly_report(); } if ( $summary_report_process ) { $summary_report_process->send(); } } public function schedule_email() { if ( ! wp_next_scheduled( self::EVENT_HOOK ) ) { wp_schedule_single_event( $this->get_schedule_time(), self::EVENT_HOOK ); } } /** * @return int */ private function get_schedule_time() { $schedule_time = strtotime( '+ ' . WPML_TM_Jobs_Summary::DAILY_SCHEDULE ); if ( WPML_TM_Emails_Settings::NOTIFY_WEEKLY === (int) $this->tm->settings['notification'][ WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ] ) { $schedule_time = strtotime( '+ ' . WPML_TM_Jobs_Summary::WEEKLY_SCHEDULE ); } return $schedule_time; } } emails/notification/summary/wpml-tm-jobs-summary-report-hooks-factory.php 0000755 00000000604 14720342453 0023043 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report_Hooks_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { /** * @return WPML_TM_Jobs_Summary_Report_Hooks */ public function create() { global $iclTranslationManagement; return new WPML_TM_Jobs_Summary_Report_Hooks( new WPML_TM_Jobs_Summary_Report_Process_Factory(), $iclTranslationManagement ); } } emails/notification/summary/class-wpml-tm-jobs-summary.php 0000755 00000000415 14720342453 0020047 0 ustar 00 <?php class WPML_TM_Jobs_Summary { const WEEKLY_REPORT = 'weekly'; const DAILY_REPORT = 'daily'; const DAILY_SCHEDULE = '1 day'; const WEEKLY_SCHEDULE = '1 week'; const JOBS_COMPLETED_KEY = 'completed'; const JOBS_WAITING_KEY = 'waiting'; } emails/notification/summary/class-wpml-tm-jobs-summary-report-process-factory.php 0000755 00000004377 14720342453 0024514 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report_Process_Factory { /** @var WPML_TM_Jobs_Summary_Report_View $template */ private $template; /** @var WPML_TM_Jobs_Summary_Report_Process $weekly_report */ private $weekly_report; /** @var WPML_TM_Jobs_Summary_Report_Process $daily_report */ private $daily_report; /** * @return WPML_TM_Jobs_Summary_Report_Process */ public function create_weekly_report() { if ( ! $this->weekly_report ) { $summary_report = $this->get_summary_report( WPML_TM_Jobs_Summary::WEEKLY_REPORT ); $this->weekly_report = new WPML_TM_Jobs_Summary_Report_Process( $this->get_template(), new WPML_TM_Jobs_Weekly_Summary_Report_Model(), $summary_report->get_jobs() ); } return $this->weekly_report; } /** * @return WPML_TM_Jobs_Summary_Report_Process */ public function create_daily_report() { if ( ! $this->daily_report ) { $summary_report = $this->get_summary_report( WPML_TM_Jobs_Summary::DAILY_REPORT ); $this->daily_report = new WPML_TM_Jobs_Summary_Report_Process( $this->get_template(), new WPML_TM_Jobs_Daily_Summary_Report_Model(), $summary_report->get_jobs() ); } return $this->daily_report; } /** * @param string $frequency * * @return WPML_TM_Jobs_Summary_Report */ private function get_summary_report( $frequency ) { global $sitepress, $wpdb; $word_count_records_factory = new WPML_TM_Word_Count_Records_Factory(); $word_count_records = $word_count_records_factory->create(); $single_process_factory = new WPML_TM_Word_Count_Single_Process_Factory(); $single_process = $single_process_factory->create(); return new WPML_TM_Jobs_Summary_Report( new WPML_Translation_Jobs_Collection( $wpdb, array() ), new WPML_TM_String( false, $word_count_records, $single_process ), new WPML_TM_Post( false, $word_count_records, $single_process ), $frequency, new WPML_Translation_Element_Factory( $sitepress ) ); } /** * @return WPML_TM_Jobs_Summary_Report_View */ private function get_template() { if ( ! $this->template ) { $template_service_factory = new WPML_TM_Email_Twig_Template_Factory(); $this->template = new WPML_TM_Jobs_Summary_Report_View( $template_service_factory->create() ); } return $this->template; } } emails/notification/summary/class-wpml-tm-jobs-summary-report-process.php 0000755 00000002276 14720342453 0023043 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report_Process { /** * @var WPML_TM_Jobs_Summary_Report_View */ private $view; /** * @var WPML_TM_Jobs_Summary_Report_Model */ private $report_model; /** * @var array */ private $jobs; public function __construct( WPML_TM_Jobs_Summary_Report_View $view, WPML_TM_Jobs_Summary_Report_Model $report_model, array $jobs ) { $this->view = $view; $this->report_model = $report_model; $this->jobs = $jobs; } public function send() { foreach ( $this->jobs as $manager_id => $jobs ) { if ( array_key_exists( WPML_TM_Jobs_Summary::JOBS_COMPLETED_KEY, $jobs ) ) { $this->view ->set_jobs( $jobs ) ->set_manager_id( $manager_id ) ->set_summary_text( $this->report_model->get_summary_text() ); $this->send_email( $manager_id ); } } } /** * @param int $manager_id */ private function send_email( $manager_id ) { wp_mail( get_userdata( $manager_id )->user_email, sprintf( $this->report_model->get_subject(), get_bloginfo( 'name' ), date( 'd/F/Y', time() ) ), $this->view->get_report_content(), array( 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', ) ); } } emails/notification/summary/class-wpml-tm-jobs-summary-report.php 0000755 00000012604 14720342453 0021363 0 ustar 00 <?php class WPML_TM_Jobs_Summary_Report { /** * @var WPML_Translation_Jobs_Collection */ private $jobs_collection; /** * @var array */ private $jobs = array(); /** * @var WPML_TM_String */ private $string_counter; /** * @var WPML_TM_Post */ private $post_counter; /** * @var string */ private $type; /** * @var WPML_Translation_Element_Factory */ private $element_factory; public function __construct( WPML_Translation_Jobs_Collection $jobs_collection, WPML_TM_String $string_counter, WPML_TM_Post $post_counter, $type, WPML_Translation_Element_Factory $element_factory ) { $this->jobs_collection = $jobs_collection; $this->string_counter = $string_counter; $this->post_counter = $post_counter; $this->type = $type; $this->element_factory = $element_factory; $this->build_completed_jobs(); $this->build_waiting_jobs(); } private function build_completed_jobs() { $jobs = $this->jobs_collection->get_jobs( array( 'any_translation_service' => true, 'status' => ICL_TM_COMPLETE ) ); foreach ( $jobs as $job ) { $completed_date = $job instanceof WPML_Post_Translation_Job || $job instanceof WPML_Element_Translation_Job ? $job->get_completed_date() : ''; $out_of_period = strtotime( $completed_date ) < strtotime( '-' . WPML_TM_Jobs_Summary::WEEKLY_SCHEDULE ); if ( WPML_TM_Jobs_Summary::DAILY_REPORT === $this->type ) { $out_of_period = strtotime( $completed_date ) < strtotime( '-' . WPML_TM_Jobs_Summary::DAILY_SCHEDULE ); } if ( ! $completed_date || $out_of_period ) { continue; } $original_element = $this->element_factory->create( $job->get_original_element_id(), $job->get_type() ); $translation_element = $original_element->get_translation( $job->get_language_code() ); if ( is_null( $translation_element ) ) { continue; } $this->jobs[ $job->get_basic_data()->manager_id ][ WPML_TM_Jobs_Summary::JOBS_COMPLETED_KEY ][] = array( 'completed_date' => date_i18n( get_option( 'date_format', 'F d, Y' ), strtotime( $job->get_completed_date() ) ), 'original_page' => array( 'title' => $job->get_title(), 'url' => $job->get_url( true ), ), 'translated_page' => array( 'title' => get_the_title( $translation_element->get_element_id() ) . ' (' . $job->get_language_code() . ')', 'url' => get_the_permalink( $translation_element->get_element_id() ), ), 'translator' => $this->get_translator_name( $job ), 'deadline' => $job->get_deadline_date() ? date_i18n( get_option( 'date_format', 'F d, Y' ), strtotime( $job->get_deadline_date() ) ) : '', 'status' => $job->get_status(), 'overdue' => $job->get_deadline_date() && strtotime( $job->get_deadline_date() ) < strtotime( $job->get_completed_date() ) ); } } private function build_waiting_jobs() { $jobs = $this->jobs_collection->get_jobs( array( 'any_translation_service' => true, 'status' => ICL_TM_WAITING_FOR_TRANSLATOR ) ); $counters = array(); $number_of_strings = 0; $number_of_words_in_strings = 0; foreach ( $jobs as $job ) { $manager_id = isset( $job->get_basic_data()->manager_id ) ? $job->get_basic_data()->manager_id : 0; $lang_pair = $job->get_source_language_code() . '|' . $job->get_language_code(); if ( ! isset( $counters[ $manager_id ][ $lang_pair ]['number_of_strings'], $counters[ $manager_id ][ $lang_pair ]['number_of_words'], $counters[ $manager_id ][ $lang_pair ]['number_of_pages'] ) ) { $counters[ $manager_id ][ $lang_pair ]['number_of_strings'] = 0; $counters[ $manager_id ][ $lang_pair ]['number_of_words'] = 0; $counters[ $manager_id ][ $lang_pair ]['number_of_pages'] = 0; } if ( 'String' === $job->get_type() ) { $this->string_counter->set_id( $job->get_original_element_id() ); $number_of_strings ++; $number_of_words_in_strings += $this->string_counter->get_words_count(); } else { $this->post_counter->set_id( $job->get_original_element_id() ); $counters[ $manager_id ][ $lang_pair ]['number_of_pages'] += 1; $counters[ $manager_id ][ $lang_pair ]['number_of_words'] += $this->post_counter->get_words_count(); $this->jobs[ $manager_id ][ WPML_TM_Jobs_Summary::JOBS_WAITING_KEY ][ $lang_pair ] = array( 'lang_pair' => $job->get_source_language_code( true ) . ' ' . __( 'to', 'wpml-translation-management' ) . ' ' . $job->get_language_code( true ), 'number_of_strings' => $number_of_strings, 'number_of_words' => $counters[ $manager_id ][ $lang_pair ]['number_of_words'] + $number_of_words_in_strings, 'number_of_pages' => $counters[ $manager_id ][ $lang_pair ]['number_of_pages'], ); } } } /** * @param WPML_Element_Translation_Job $job * * @return string */ private function get_translator_name( WPML_Element_Translation_Job $job ) { $translator_name = $job->get_translation_service() ? TranslationProxy::get_service_name( $job->get_translation_service() ) : $job->get_translator_name(); if ( 'local' === $job->get_translation_service() ) { $user = get_userdata( $job->get_translator_id() ); $translator_name = $user->display_name . ' (' . $user->user_login . ')'; } return $translator_name; } /** * @return array */ public function get_jobs() { return $this->jobs; } } emails/notification/summary/class-wpml-tm-jobs-weekly-summary-report-model.php 0000755 00000000663 14720342453 0023761 0 ustar 00 <?php class WPML_TM_Jobs_Weekly_Summary_Report_Model implements WPML_TM_Jobs_Summary_Report_Model { /** * @return string */ public function get_subject() { return __( 'Translation updates for %1$s until %2$s', 'wpml-translation-management' ); } /** * @return string */ public function get_summary_text() { return __( 'This week %1$s had the following %2$s translation updates', 'wpml-translation-management' ); } } emails/notification/summary/class-wpml-tm-jobs-daily-summary-report-model.php 0000755 00000000654 14720342453 0023563 0 ustar 00 <?php class WPML_TM_Jobs_Daily_Summary_Report_Model implements WPML_TM_Jobs_Summary_Report_Model { /** * @return string */ public function get_subject() { return __( 'Translation updates for %1$s for %2$s', 'wpml-translation-management' ); } /** * @return string */ public function get_summary_text() { return __( 'Today %1$s had the following %2$s translation updates', 'wpml-translation-management' ); } } emails/notification/wpml-tm-email-notification-view.php 0000755 00000005243 14720342453 0017346 0 ustar 00 <?php class WPML_TM_Email_Notification_View extends WPML_TM_Email_View { const PROMOTE_TRANSLATION_SERVICES_TEMPLATE = 'notification/promote-translation-services.twig'; /** * @param array $model * @param string $template * * @return string */ public function render_model( array $model, $template ) { if ( isset( $model['casual_name'] ) && $model['casual_name'] ) { $content = $this->render_casual_header( $model['casual_name'] ); } else { $content = $this->render_header( $model['username'] ); } $content .= $this->template_service->show( $model, $template ); $content .= $this->render_promote_translation_services( $model ); $content .= $this->render_footer(); return $content; } /** * @param array $model * * @return string */ private function render_promote_translation_services( array $model ) { $content = ''; if ( isset( $model['promote_translation_services'] ) && $model['promote_translation_services'] ) { $translation_services_url = esc_url( admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=translators' ) ); /* translators: Promote translation services: %s replaced by "professional translation services integrated with WPML" */ $promoteA = esc_html_x( 'Need faster translation work? Try one of the %s.', 'Promote translation services: %s replaced by "professional translation services integrated with WPML"', 'wpml-translation-management' ); /* translators: Promote translation services: Promote translation services: used to build a link to the translation services page */ $promoteB = esc_html_x( 'professional translation services integrated with WPML', 'Promote translation services: used to build a link to the translation services page', 'wpml-translation-management' ); $promote_model['message'] = sprintf( $promoteA, '<a href="' . $translation_services_url . '">' . $promoteB . '</a>' ); $content = $this->template_service->show( $promote_model, self::PROMOTE_TRANSLATION_SERVICES_TEMPLATE ); } return $content; } /** @return string */ private function render_footer() { $notifications_url = esc_url( admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS . '&sm=notifications' ) ); $notifications_text = esc_html__( 'WPML Notification Settings', 'wpml-translation-management' ); $notifications_link = '<a href="' . $notifications_url . '" style="color: #ffffff;">' . $notifications_text . '</a>'; $bottom_text = sprintf( esc_html__( 'To stop receiving notifications, log-in to %s and change your preferences.', 'wpml-translation-management' ), $notifications_link ); return $this->render_email_footer( $bottom_text ); } } Geolocalization.php 0000755 00000002306 14720342453 0010410 0 ustar 00 <?php namespace WPML\TM; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Json; use WPML\FP\Logic; use WPML\FP\Maybe; use WPML\FP\Obj; use function WPML\FP\curryN; /** * @method static callable getCountryByIp( ...$httpPost, ...$ip ) - Curried :: callable->string->array|null */ class Geolocalization { use Macroable; public static function init() { self::macro( 'getCountryByIp', curryN( 2, function ( $httpPost, $ip ) { $ip = defined( 'WPML_TM_Geolocalization_IP' ) ? WPML_TM_Geolocalization_IP : $ip; $url = defined( 'OTGS_INSTALLER_WPML_API_URL' ) ? OTGS_INSTALLER_WPML_API_URL : 'https://api.wpml.org'; $formatRequest = function ( $ip ) { return [ 'body' => [ 'action' => 'geolocalization', 'ip' => $ip, ], ]; }; return Maybe::of( $ip ) ->map( $formatRequest ) ->chain( $httpPost( $url ) ) ->map( Obj::prop( 'body' ) ) ->map( Json::toArray() ) ->map( Obj::prop( 'data' ) ) ->filter( Logic::allPass( [ Obj::prop( 'code' ), Obj::prop( 'name' ) ] ) ) ->map( Obj::pick( [ 'code', 'name' ] ) ) ->getOrElse( null ); } ) ); } } Geolocalization::init(); core-abstract-classes/class-wpml-url-converter-user.php 0000755 00000000504 14720342453 0017312 0 ustar 00 <?php /** * Class WPML_URL_Converter_User * * @since 3.2.3 */ abstract class WPML_URL_Converter_User { /** @var WPML_URL_Converter */ protected $url_converter; /** * @param \WPML_URL_Converter $url_converter */ public function __construct( &$url_converter ) { $this->url_converter = &$url_converter; } } core-abstract-classes/class-wpml-sp-and-pt-user.php 0000755 00000000634 14720342453 0016312 0 ustar 00 <?php abstract class WPML_SP_And_PT_User extends WPML_SP_User { /** @var WPML_Post_Translation $post_translation */ protected $post_translation; /** * @param WPML_Post_Translation $post_translation * @param SitePress $sitepress */ public function __construct( &$post_translation, &$sitepress ) { parent::__construct( $sitepress ); $this->post_translation = &$post_translation; } } core-abstract-classes/class-wpml-tm-user.php 0000755 00000000506 14720342453 0015125 0 ustar 00 <?php class WPML_TM_User { /** @var TranslationManagement $tm_instance */ protected $tm_instance; /** * WPML_Custom_Field_Setting_Factory constructor. * * @param TranslationManagement $tm_instance */ public function __construct( TranslationManagement $tm_instance ) { $this->tm_instance = $tm_instance; } } core-abstract-classes/class-wpml-set-language.php 0000755 00000030532 14720342453 0016107 0 ustar 00 <?php class WPML_Set_Language extends WPML_Full_Translation_API { /** * @param int $el_id the element's ID (for terms we use the `term_taxonomy_id`) * @param string $el_type * @param int|bool|null $trid Trid the element is to be assigned to. Input that is == false will cause the term to * be assigned a new trid and potential translation relations to/from it to disappear. * @param string $language_code * @param null|string $src_language_code * @param bool $check_duplicates * @param bool $check_null * * @return bool|int|null|string */ public function set( $el_id, $el_type, $trid, $language_code, $src_language_code = null, $check_duplicates = true, $check_null = false ) { if ( strlen( $el_type ) > 60 ) { return false; } $this->clear_cache(); if ( $check_duplicates && $el_id && (bool) ( $el_type_db = $this->check_duplicate( $el_type, $el_id ) ) === true ) { return false; } $context = explode( '_', $el_type ); $context = $context[0]; $src_language_code = $src_language_code === $language_code ? null : $src_language_code; if ( $check_null && is_null( $trid ) ) { // Check if there are any existing translations after check_duplicate corrections. $existing = $this->get_existing( $el_type, $el_id ); if ( $existing ) { $trid = $existing->trid; $language_code = $existing->language_code; } } if ( $trid ) { // it's a translation of an existing element /** @var int $trid is an integer if not falsy */ $this->maybe_delete_orphan( $trid, $language_code, $el_id ); if ( $el_id && (bool) ( $translation_id = $this->is_language_change( $el_id, $el_type, $trid ) ) === true && (bool) $this->trid_lang_trans_id( $trid, $language_code ) === false ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_translations', array( 'language_code' => $language_code ), array( 'translation_id' => $translation_id ) ); do_action( 'wpml_translation_update', array( 'type' => 'update', 'trid' => $trid, 'element_id' => $el_id, 'element_type' => $el_type, 'translation_id' => $translation_id, 'context' => $context, ) ); } elseif ( $el_id && (bool) ( $translation_id = $this->existing_element( $el_id, $el_type ) ) === true ) { $this->change_translation_of( $trid, $el_id, $el_type, $language_code, $src_language_code ); } elseif ( (bool) ( $translation_id = $this->is_placeholder_update( $trid, $language_code ) ) === true ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_translations', array( 'element_id' => $el_id ), array( 'translation_id' => $translation_id ) ); do_action( 'wpml_translation_update', array( 'type' => 'update', 'trid' => $trid, 'element_id' => $el_id, 'element_type' => $el_type, 'translation_id' => $translation_id, 'context' => $context, ) ); } elseif ( (bool) ( $translation_id = $this->trid_lang_trans_id( $trid, $language_code ) ) === false ) { $translation_id = $this->insert_new_row( $el_id, $trid, $el_type, $language_code, $src_language_code ); } } else { // it's a new element or we are removing it from a trid $this->delete_existing_row( $el_type, $el_id ); $translation_id = $this->insert_new_row( $el_id, false, $el_type, $language_code, $src_language_code ); } $this->clear_cache(); if ( $translation_id && substr( $el_type, 0, 4 ) === 'tax_' ) { $taxonomy = substr( $el_type, 4 ); do_action( 'created_term_translation', $taxonomy, $el_id, $language_code ); } do_action( 'icl_set_element_language', $translation_id, $el_id, $language_code, $trid ); return $translation_id; } /** * Get the trid & language_code for element type and id * * @param string $element_type * @param int $element_id * * @return null|int */ private function get_existing( $element_type, $element_id ) { return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT trid, language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_type = %s AND element_id = %d LIMIT 1", $element_type, $element_id ) ); } /** * Returns the translation id belonging to a specific trid, language_code combination * * @param int $trid * @param string $lang * * @return null|int */ private function trid_lang_trans_id( $trid, $lang ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT translation_id FROM {$this->wpdb->prefix}icl_translations WHERE trid = %d AND language_code = %s LIMIT 1", $trid, $lang ) ); } /** * Changes the source_language_code of an element * * @param int $trid * @param int $el_id * @param string $el_type * @param string $language_code * @param string $src_language_code */ private function change_translation_of( $trid, $el_id, $el_type, $language_code, $src_language_code ) { $src_language_code = empty( $src_language_code ) ? $this->sitepress->get_source_language_by_trid( $trid ) : $src_language_code; if ( $src_language_code !== $language_code ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_translations', array( 'trid' => $trid, 'language_code' => $language_code, 'source_language_code' => $src_language_code, ), array( 'element_type' => $el_type, 'element_id' => $el_id, ) ); $context = explode( '_', $el_type ); do_action( 'wpml_translation_update', array( 'type' => 'update', 'trid' => $trid, 'element_id' => $el_id, 'element_type' => $el_type, 'context' => $context[0], ) ); } } /** * @param string $el_type * @param int $el_id */ private function delete_existing_row( $el_type, $el_id ) { $context = explode( '_', $el_type ); $update_args = array( 'element_id' => $el_id, 'element_type' => $el_type, 'context' => $context[0], ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'before_delete' ) ) ); $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$this->wpdb->prefix}icl_translations WHERE element_type = %s AND element_id = %d", $el_type, $el_id ) ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'after_delete' ) ) ); } /** * Inserts a new row into icl_translations * * @param int $el_id * @param int|false $trid * @param string $el_type * @param string $language_code * @param string $src_language_code * * @return int Translation ID of the new row */ private function insert_new_row( $el_id, $trid, $el_type, $language_code, $src_language_code ) { $new = array( 'element_type' => $el_type, 'language_code' => $language_code, ); if ( $trid === false ) { $trid = 1 + (int) $this->wpdb->get_var( "SELECT MAX(trid) FROM {$this->wpdb->prefix}icl_translations" ); } else { $src_language_code = empty( $src_language_code ) ? $this->sitepress->get_source_language_by_trid( $trid ) : $src_language_code; $new['source_language_code'] = $src_language_code; } $new['trid'] = $trid; if ( $el_id ) { $new['element_id'] = $el_id; } $this->wpdb->insert( $this->wpdb->prefix . 'icl_translations', $new ); $translation_id = $this->wpdb->insert_id; $context = explode( '_', $el_type ); do_action( 'wpml_translation_update', array( 'type' => 'insert', 'trid' => $trid, 'element_id' => $el_id, 'element_type' => $el_type, 'translation_id' => $translation_id, 'context' => $context[0], ) ); return $translation_id; } /** * Checks if a row exists for a concrete id, type and trid combination * in icl_translations. * * @param int $el_id * @param string $el_type * @param int $trid * * @return null|int */ private function is_language_change( $el_id, $el_type, $trid ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT translation_id FROM {$this->wpdb->prefix}icl_translations WHERE element_type = %s AND element_id = %d AND trid = %d", $el_type, $el_id, $trid ) ); } /** * Checks if a given trid, language_code combination contains a placeholder with NULL element_id * and if so returns the translation id of this row. * * @param int $trid * @param string $language_code * * @return null|string translation id */ private function is_placeholder_update( $trid, $language_code ) { return $this->wpdb->get_var( $this->wpdb->prepare( " SELECT translation_id FROM {$this->wpdb->prefix}icl_translations WHERE trid=%d AND language_code = %s AND element_id IS NULL", $trid, $language_code ) ); } /** * Checks if a row in icl_translations exists for a concrete element type and id combination * * @param int $el_id * @param string $el_type * * @return null|int */ private function existing_element( $el_id, $el_type ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT translation_id FROM {$this->wpdb->prefix}icl_translations WHERE element_type= %s AND element_id= %d LIMIT 1", $el_type, $el_id ) ); } /** * Checks if a trid contains an existing translation other than a specific element id and deletes that row if it * exists. * * @param int $trid * @param string $language_code * @param int $correct_element_id */ private function maybe_delete_orphan( $trid, $language_code, $correct_element_id ) { /** @var \stdClass $result */ $result = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT translation_id, element_type, element_id FROM {$this->wpdb->prefix}icl_translations WHERE trid = %d AND language_code = %s AND element_id <> %d AND source_language_code IS NOT NULL ", $trid, $language_code, $correct_element_id ) ); $translation_id = ( null != $result ? $result->translation_id : null ); if ( $translation_id ) { $context = explode( '_', $result->element_type ); $update_args = array( 'trid' => $trid, 'element_id' => $result->element_id, 'element_type' => $result->element_type, 'translation_id' => $translation_id, 'context' => $context[0], ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'before_delete' ) ) ); $this->wpdb->query( $this->wpdb->prepare( "DELETE FROM {$this->wpdb->prefix}icl_translations WHERE translation_id=%d", $translation_id ) ); do_action( 'wpml_translation_update', array_merge( $update_args, array( 'type' => 'after_delete' ) ) ); } } /** * Checks if a duplicate element_id already exists with a different than the input type. * This only applies to posts and taxonomy terms. * * @param string $el_type * @param int $el_id * * @return null|string null if no duplicate icl translations entry is found * having a different than the input element type, the element type if a * duplicate row is found. */ private function check_duplicate( $el_type, $el_id ) { $res = false; $exp = explode( '_', $el_type ); $_type = $exp[0]; if ( in_array( $_type, array( 'post', 'tax' ) ) ) { $res = $this->duplicate_from_db( $el_id, $el_type, $_type ); if ( $res ) { $fix_assignments = new WPML_Fix_Type_Assignments( $this->sitepress ); $fix_assignments->run(); $res = $this->duplicate_from_db( $el_id, $el_type, $_type ); } } return $res; } private function duplicate_from_db( $el_id, $el_type, $_type ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT element_type FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND element_type <> %s AND element_type LIKE %s", $el_id, $el_type, $_type . '%' ) ); } private function clear_cache() { $this->term_translations->reload(); $this->post_translations->reload(); $this->sitepress->get_translations_cache()->clear(); } } core-abstract-classes/class-wpml-wpdb-and-sp-user.php 0000755 00000000551 14720342453 0016621 0 ustar 00 <?php /** * Class WPML_WPDB_And_SP_User */ abstract class WPML_WPDB_And_SP_User extends WPML_WPDB_User { /** @var SitePress $sitepress */ protected $sitepress; /** * @param wpdb $wpdb * @param SitePress $sitepress */ public function __construct( &$wpdb, &$sitepress ) { parent::__construct( $wpdb ); $this->sitepress = &$sitepress; } } core-abstract-classes/class-wpml-wpdb-user.php 0000755 00000000544 14720342453 0015443 0 ustar 00 <?php /** * Class WPML_WPDB_User * * Superclass for all WPML classes using the @global wpdb $wpdb * * @since 3.2.3 */ abstract class WPML_WPDB_User { /** @var wpdb $wpdb */ public $wpdb; /** * @param wpdb $wpdb */ public function __construct( &$wpdb ) { $this->wpdb = &$wpdb; } public function get_wpdb() { return $this->wpdb; } } core-abstract-classes/class-wpml-sp-user.php 0000755 00000000547 14720342453 0015134 0 ustar 00 <?php /** * Class WPML_SP_User * * Superclass for all WPML classes using the @global SitePress $sitepress directly * * @since 3.2.3 */ abstract class WPML_SP_User { /** @var SitePress $sitepress */ protected $sitepress; /** * @param SitePress $sitepress */ public function __construct( &$sitepress ) { $this->sitepress = &$sitepress; } } core-abstract-classes/class-wpml-full-translation-api.php 0000755 00000001057 14720342453 0017600 0 ustar 00 <?php class WPML_Full_Translation_API extends WPML_Full_PT_API { /** @var WPML_Term_Translation $term_translations */ protected $term_translations; /** * @param SitePress $sitepress * @param wpdb $wpdb * @param WPML_Post_Translation $post_translations * @param WPML_Term_Translation $term_translations */ function __construct( &$sitepress, &$wpdb, &$post_translations, &$term_translations ) { parent::__construct( $wpdb, $sitepress, $post_translations ); $this->term_translations = &$term_translations; } } core-abstract-classes/class-wpml-element-translation.php 0000755 00000024146 14720342453 0017524 0 ustar 00 <?php /** * WPML_Element_Translation Class * * @package wpml-core * @abstract */ abstract class WPML_Element_Translation extends WPML_WPDB_User { /** @var array[] $element_data */ protected $element_data = []; /** @var array[] $translations */ protected $translations = []; /** @var array[] $trid_groups */ protected $trid_groups = []; /** @var array[] $trid_groups */ protected $translation_ids_element = []; /** @var int $type_prefix_length */ private $type_prefix_length; /** * @param wpdb $wpdb */ public function __construct( &$wpdb ) { parent::__construct( $wpdb ); $this->type_prefix_length = strlen( $this->get_type_prefix() ); } abstract protected function get_element_join(); abstract protected function get_type_prefix(); /** * Clears the cached translations. */ public function reload() { $this->element_data = []; $this->translations = []; $this->trid_groups = []; $this->translation_ids_element = []; } public function get_element_trid( $element_id ) { return $this->maybe_populate_cache( $element_id ) ? $this->element_data[ $element_id ]['trid'] : null; } /** * @param int $element_id * @param string $lang * @param bool|false $original_fallback if true will return input $element_id if no translation is found * * @return null|int */ public function element_id_in( $element_id, $lang, $original_fallback = false ) { $result = ( $original_fallback ? (int) $element_id : null ); if ( $this->maybe_populate_cache( $element_id ) && isset( $this->translations[ $element_id ][ $lang ] ) ) { $result = (int) $this->translations[ $element_id ][ $lang ]; } return $result; } /** * @param int $element_id * @param bool $root if true gets the root element of the trid which itself * has no original. Otherwise returns the direct original of the given * element_id. * * @return int|null null if the element has no original */ public function get_original_element( $element_id, $root = false ) { $element_id = (int) $element_id; $source_lang = $this->maybe_populate_cache( $element_id ) ? $this->element_data[ $element_id ]['source_lang'] : null; if ( null === $source_lang ) { return null; } if ( ! $root && isset( $this->translations[ $element_id ][ $source_lang ] ) ) { return (int) $this->translations[ $element_id ][ $source_lang ]; } if ( $root ) { foreach ( $this->translations[ $element_id ] as $trans_id ) { if ( ! $this->element_data[ $trans_id ]['source_lang'] ) { return (int) $trans_id; } } } return null; } public function get_element_id( $lang, $trid ) { $this->maybe_populate_cache( false, $trid ); return isset( $this->trid_groups [ $trid ][ $lang ] ) ? $this->trid_groups [ $trid ][ $lang ] : null; } /** * @param int $element_id * * @return null|string */ public function get_element_lang_code( $element_id ) { $result = null; if ( $this->maybe_populate_cache( $element_id ) ) { $result = $this->element_data[ $element_id ]['lang']; } return $result; } /** * @param int $element_id * @param string $output * * @return array|null|stdClass */ public function get_element_language_details( $element_id, $output = OBJECT ) { $result = null; if ( $element_id && $this->maybe_populate_cache( $element_id ) ) { $result = new stdClass(); $result->element_id = $element_id; $result->trid = $this->element_data[ $element_id ]['trid']; $result->language_code = $this->element_data[ $element_id ]['lang']; $result->source_language_code = $this->element_data[ $element_id ]['source_lang']; } if ( $output == ARRAY_A ) { return $result ? get_object_vars( $result ) : null; } elseif ( $output == ARRAY_N ) { return $result ? array_values( get_object_vars( $result ) ) : null; } else { return $result; } } public function get_source_lang_code( $element_id ) { return $this->maybe_populate_cache( $element_id ) ? $this->element_data[ $element_id ]['source_lang'] : null; } public function get_type( $element_id ) { return $this->maybe_populate_cache( $element_id ) ? $this->element_data[ $element_id ]['type'] : null; } public function get_source_lang_from_translation_id( $translation_id ) { $lang = array( 'code' => null, 'found' => false, ); $element_id = $this->get_element_from_translation_id( $translation_id ); if ( $element_id ) { $lang['code'] = $this->get_source_lang_code( $element_id ); $lang['found'] = true; } return $lang; } public function get_translation_id( $element_id ) { return $this->maybe_populate_cache( $element_id ) ? $this->element_data[ $element_id ]['translation_id'] : null; } public function get_translations_ids() { $translation_ids = array(); foreach ( $this->element_data as $data ) { $translation_ids[] = $data['translation_id']; } return $translation_ids; } /** * @param int $element_id * @param int|false $trid * @param bool $actual_translations_only * * @return array<int,int> */ public function get_element_translations( $element_id, $trid = false, $actual_translations_only = false ) { $valid_element = $this->maybe_populate_cache( $element_id, $trid ); if ( $element_id ) { $res = $valid_element ? ( $actual_translations_only ? $this->filter_for_actual_trans( $element_id ) : $this->translations[ $element_id ] ) : array(); } elseif ( $trid ) { $res = isset( $this->trid_groups[ $trid ] ) ? $this->trid_groups[ $trid ] : array(); } return isset( $res ) ? $res : array(); } public function get_element_from_translation_id( $translation_id ) { return isset( $this->translation_ids_element[ $translation_id ] ) ? $this->translation_ids_element[ $translation_id ] : null; } public function get_trid_from_translation_id( $translation_id ) { $trid = null; $element_id = $this->get_element_from_translation_id( $translation_id ); if ( $element_id ) { $trid = $this->get_element_trid( $element_id ); } return $trid; } public function get_trids() { return array_keys( $this->trid_groups ); } public function prefetch_ids( $element_ids ) { $element_ids = (array) $element_ids; $element_ids = array_diff( $element_ids, array_keys( $this->element_data ) ); if ( (bool) $element_ids === false ) { return; } $elements = $this->wpdb->get_results( $this->get_sql_by_element_ids( $element_ids ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $this->group_and_populate_cache( $elements ); } /** * @return string */ private function get_base_sql() { return " SELECT wpml_translations.translation_id, wpml_translations.element_id, wpml_translations.language_code, wpml_translations.source_language_code, wpml_translations.trid, wpml_translations.element_type FROM {$this->wpdb->prefix}icl_translations wpml_translations " . $this->get_element_join(); } /** * @param array $element_ids * * @return string */ private function get_sql_by_element_ids( $element_ids ) { return $this->get_base_sql() . " JOIN {$this->wpdb->prefix}icl_translations tridt ON tridt.element_type = wpml_translations.element_type AND tridt.trid = wpml_translations.trid WHERE tridt.element_id IN(" . wpml_prepare_in( $element_ids, '%d' ) . ")"; // phpcs:ignore Squiz.Strings.DoubleQuoteUsage.NotRequired } /** * @param int $trid * * @return string */ private function get_sql_by_trid( $trid ) { return $this->get_base_sql() . $this->wpdb->prepare( ' WHERE wpml_translations.trid = %d', $trid ); } private function maybe_populate_cache( $element_id, $trid = false ) { if ( ! $element_id && ! $trid ) { return false; } if ( ! $element_id && isset( $this->trid_groups [ $trid ] ) ) { return true; } if ( ! $element_id || ! isset( $this->translations[ $element_id ] ) ) { if ( ! $element_id ) { $sql = $this->get_sql_by_trid( $trid ); } else { $sql = $this->get_sql_by_element_ids( [ $element_id ] ); } $elements = $this->wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $this->populate_cache( $elements ); if ( $element_id && ! isset( $this->translations[ $element_id ] ) ) { $this->translations[ $element_id ] = array(); } } return ! empty( $this->translations[ $element_id ] ); } private function group_and_populate_cache( $elements ) { $trids = array(); foreach ( $elements as $element ) { $trid = $element['trid']; if ( ! isset( $trids[ $trid ] ) ) { $trids[ $trid ] = array(); } $trids[ $trid ][] = $element; } foreach ( $trids as $trid_group ) { $this->populate_cache( $trid_group ); } } private function populate_cache( $elements ) { if ( ! $elements ) { return; } $element_ids = array(); foreach ( $elements as $element ) { $element_id = $element['element_id']; $language_code = $element['language_code']; $element_ids[ $language_code ] = $element_id; $this->element_data[ $element_id ] = array( 'translation_id' => $element['translation_id'], 'trid' => $element['trid'], 'lang' => $language_code, 'source_lang' => $element['source_language_code'], 'type' => substr( $element['element_type'], $this->type_prefix_length ), ); $this->translation_ids_element[ $element['translation_id'] ] = $element_id; } foreach ( $element_ids as $element_id ) { $trid = $this->element_data[ $element_id ]['trid']; $this->trid_groups[ $trid ] = $element_ids; $this->translations[ $element_id ] = &$this->trid_groups[ $trid ]; } } private function filter_for_actual_trans( $element_id ) { $res = $this->translations[ $element_id ]; foreach ( $res as $lang => $element ) { if ( $this->element_data[ $element ]['source_lang'] !== $this->element_data[ $element_id ]['lang'] ) { unset( $res[ $lang ] ); } } return $res; } /** * @param int $post_id * * @return bool */ public function is_a_duplicate( $post_id ) { return (bool) get_post_meta( $post_id, '_icl_lang_duplicate_of', true ); } } core-abstract-classes/class-wpml-full-pt-api.php 0000755 00000001005 14720342453 0015656 0 ustar 00 <?php /** * Class WPML_WPDB_And_SP_User */ abstract class WPML_Full_PT_API extends WPML_WPDB_And_SP_User { /** @var WPML_Post_Translation $post_translations */ protected $post_translations; /** * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_Post_Translation $post_translations */ public function __construct( &$wpdb, &$sitepress, &$post_translations ) { parent::__construct( $wpdb, $sitepress ); $this->post_translations = &$post_translations; } } core-abstract-classes/class-wpml-hierarchy-sync.php 0000755 00000023625 14720342453 0016470 0 ustar 00 <?php abstract class WPML_Hierarchy_Sync extends WPML_WPDB_User { const CACHE_GROUP = __CLASS__; protected $original_elements_table_alias = 'org'; protected $translated_elements_table_alias = 'tra'; protected $original_elements_language_table_alias = 'iclo'; protected $translated_elements_language_table_alias = 'iclt'; protected $correct_parent_table_alias = 'corr'; protected $correct_parent_language_table_alias = 'iclc'; protected $original_parent_table_alias = 'parents'; protected $original_parent_language_table_alias = 'parent_lang'; protected $element_id_column; protected $parent_element_id_column; protected $parent_id_column; protected $element_type_column; protected $element_type_prefix; protected $elements_table; protected $lang_info_table; /** * @param wpdb $wpdb */ public function __construct( &$wpdb ) { parent::__construct( $wpdb ); $this->lang_info_table = $wpdb->prefix . 'icl_translations'; add_action( 'clean_post_cache', [ $this, 'clean_cache' ] ); add_action( 'set_object_terms', [ $this, 'clean_cache' ] ); add_action( 'wpml_sync_term_hierarchy_done', [ $this, 'clean_cache' ] ); } public function clean_cache() { WPML_Non_Persistent_Cache::flush_group( self::CACHE_GROUP ); } public function get_unsynced_elements( $element_types, $ref_lang_code = false ) { $element_types = (array) $element_types; $results = array(); if ( $element_types ) { $key = md5( (string) wp_json_encode( array( $element_types, $ref_lang_code ) ) ); $found = false; $results = WPML_Non_Persistent_Cache::get( $key, self::CACHE_GROUP, $found ); if ( ! $found ) { $results_sql_parts = array(); $results_sql_parts['source_element_table'] = $this->get_source_element_table(); $results_sql_parts['source_element_join'] = $this->get_source_element_join(); $results_sql_parts['join_translation_language_data'] = $this->get_join_translation_language_data( $ref_lang_code ); $results_sql_parts['translated_element_join'] = $this->get_translated_element_join(); $results_sql_parts['original_parent_join'] = $this->get_original_parent_join(); $results_sql_parts['original_parent_language_join'] = $this->get_original_parent_language_join(); $results_sql_parts['correct_parent_language_join'] = $this->get_correct_parent_language_join(); $results_sql_parts['correct_parent_element_join'] = $this->get_correct_parent_element_join(); $results_sql_parts['where_statement'] = $this->get_where_statement( $element_types, $ref_lang_code ); $results_sql = $this->get_select_statement(); $results_sql .= ' FROM '; $results_sql .= implode( ' ', $results_sql_parts ); $results = $this->wpdb->get_results( $results_sql ); WPML_Non_Persistent_Cache::set( $key, $results, self::CACHE_GROUP ); } } return $results; } /** * @param string|array $element_types * @param bool $ref_lang_code */ public function sync_element_hierarchy( $element_types, $ref_lang_code = false ) { $hierarchical_element_types = wpml_collect( $element_types )->filter( [ $this, 'is_hierarchical' ] ); if ( $hierarchical_element_types->isEmpty() ) { return; } $unsynced = $this->get_unsynced_elements( $hierarchical_element_types->toArray(), $ref_lang_code ); foreach ( $unsynced as $row ) { $this->update_hierarchy_for_element( $row ); } } /** * @param string $element_type * * @return mixed */ abstract public function is_hierarchical( $element_type ); private function update_hierarchy_for_element( $row ) { $update = $this->validate_parent_synchronization( $row ); if ( $update ) { $target_element_id = $row->translated_id; $new_parent = (int) $row->correct_parent; $this->wpdb->update( $this->elements_table, array( $this->parent_id_column => $new_parent ), array( $this->element_id_column => $target_element_id ) ); } } private function validate_parent_synchronization( $row ) { $is_valid = false; $is_for_posts = ( $this->elements_table === $this->wpdb->posts ); if ( ! $is_for_posts ) { $is_valid = true; } if ( $row && $is_for_posts ) { global $sitepress; $target_element_id = $row->translated_id; $target_post = get_post( $target_element_id ); if ( $target_post ) { $parent_must_empty = false; $post_type = $target_post->post_type; $element_type = 'post_' . $post_type; $target_element_language = $sitepress->get_element_language_details( $target_element_id, $element_type ); $original_element_id = $sitepress->get_original_element_id( $target_element_id, $element_type ); if ( $original_element_id ) { $parent_has_translation_in_target_language = false; $original_element = get_post( $original_element_id ); $original_post_parent_id = $original_element->post_parent; if ( $original_post_parent_id ) { $original_post_parent_trid = $sitepress->get_element_trid( $original_post_parent_id, $element_type ); $original_post_parent_translations = $sitepress->get_element_translations( $original_post_parent_trid, $element_type ); foreach ( $original_post_parent_translations as $original_post_parent_translation ) { if ( $original_post_parent_translation->language_code == $target_element_language->language_code ) { $parent_has_translation_in_target_language = true; break; } } } else { $parent_must_empty = true; } /** * Check if the parent of the original post has a translation in the language of the target post or if the parent must be set to 0 */ $is_valid = $parent_has_translation_in_target_language || $parent_must_empty; } } } return $is_valid; } private function get_source_element_join() { return "JOIN {$this->lang_info_table} {$this->original_elements_language_table_alias} ON {$this->original_elements_table_alias}.{$this->element_id_column} = {$this->original_elements_language_table_alias}.element_id AND {$this->original_elements_language_table_alias}.element_type = CONCAT('{$this->element_type_prefix}', {$this->original_elements_table_alias}.{$this->element_type_column})"; } private function get_translated_element_join() { return "JOIN {$this->elements_table} {$this->translated_elements_table_alias} ON {$this->translated_elements_table_alias}.{$this->element_id_column} = {$this->translated_elements_language_table_alias}.element_id "; } private function get_source_element_table() { return " {$this->elements_table} {$this->original_elements_table_alias} "; } private function get_join_translation_language_data( $ref_language_code ) { $res = " JOIN {$this->lang_info_table} {$this->translated_elements_language_table_alias} ON {$this->translated_elements_language_table_alias}.trid = {$this->original_elements_language_table_alias}.trid "; if ( (bool) $ref_language_code === true ) { $res .= "AND {$this->translated_elements_language_table_alias}.language_code != {$this->original_elements_language_table_alias}.language_code "; } else { $res .= " AND {$this->translated_elements_language_table_alias}.source_language_code = {$this->original_elements_language_table_alias}.language_code "; } return $res; } private function get_select_statement() { return " SELECT {$this->translated_elements_table_alias}.{$this->element_id_column} AS translated_id , IFNULL({$this->correct_parent_table_alias}.{$this->parent_element_id_column}, 0) AS correct_parent "; } private function get_original_parent_join() { return " LEFT JOIN {$this->elements_table} {$this->original_parent_table_alias} ON {$this->original_parent_table_alias}.{$this->parent_element_id_column} = {$this->original_elements_table_alias}.{$this->parent_id_column} "; } private function get_original_parent_language_join() { return " LEFT JOIN {$this->lang_info_table} {$this->original_parent_language_table_alias} ON {$this->original_parent_table_alias}.{$this->element_id_column} = {$this->original_parent_language_table_alias}.element_id AND {$this->original_parent_language_table_alias}.element_type = CONCAT('{$this->element_type_prefix}', {$this->original_parent_table_alias}.{$this->element_type_column}) "; } private function get_correct_parent_language_join() { return " LEFT JOIN {$this->lang_info_table} {$this->correct_parent_language_table_alias} ON {$this->correct_parent_language_table_alias}.language_code = {$this->translated_elements_language_table_alias}.language_code AND {$this->original_parent_language_table_alias}.trid = {$this->correct_parent_language_table_alias}.trid "; } private function get_correct_parent_element_join() { return " LEFT JOIN {$this->elements_table} {$this->correct_parent_table_alias} ON {$this->correct_parent_table_alias}.{$this->element_id_column} = {$this->correct_parent_language_table_alias}.element_id "; } private function get_where_statement( $element_types, $ref_lang_code ) { $filter_originals_snippet = $ref_lang_code ? $this->wpdb->prepare( " AND {$this->original_elements_language_table_alias}.language_code = %s ", $ref_lang_code ) : " AND {$this->translated_elements_language_table_alias}.source_language_code IS NOT NULL "; return " WHERE {$this->original_elements_table_alias}.{$this->element_type_column} IN (" . wpml_prepare_in( $element_types ) . ") AND IFNULL({$this->correct_parent_table_alias}.{$this->parent_element_id_column}, 0) != {$this->translated_elements_table_alias}.{$this->parent_id_column} " . $filter_originals_snippet; } } class-wpml-tm-ajax-factory-2.php 0000755 00000003406 14720342453 0012514 0 ustar 00 <?php class WPML_TM_Ajax_Factory extends WPML_Ajax_Factory { private $wpdb; private $sitepress; private $post_data; private $wp_api; public function __construct( $wpdb, $sitepress, $post_data ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->post_data = $post_data; $this->wp_api = $sitepress->get_wp_api(); } public function get_class_names() { return array( 'WPML_Ajax_Scan_Link_Targets', 'WPML_Ajax_Update_Link_Targets_In_Posts', 'WPML_Ajax_Update_Link_Targets_In_Strings', ); } public function create( $class_name ) { global $ICL_Pro_Translation; switch ( $class_name ) { case 'WPML_Ajax_Scan_Link_Targets': return new WPML_Ajax_Scan_Link_Targets( new WPML_Translate_Link_Targets_In_Posts_Global( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $ICL_Pro_Translation ), defined( 'WPML_ST_VERSION' ) ? new WPML_Translate_Link_Targets_In_Strings_Global( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $this->wp_api, $ICL_Pro_Translation ) : null, $this->post_data ); case 'WPML_Ajax_Update_Link_Targets_In_Posts': return new WPML_Ajax_Update_Link_Targets_In_Posts( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $ICL_Pro_Translation, $this->post_data ); case 'WPML_Ajax_Update_Link_Targets_In_Strings': return new WPML_Ajax_Update_Link_Targets_In_Strings( new WPML_Translate_Link_Target_Global_State( $this->sitepress ), $this->wpdb, $this->wp_api, $ICL_Pro_Translation, $this->post_data ); default: throw new Exception( 'Class ' . $class_name . ' not found' ); } } } class-wpml-translate-independently.php 0000755 00000004450 14720342453 0014204 0 ustar 00 <?php class WPML_Translate_Independently { public function __construct() { $this->init(); } public function init() { add_action( 'wpml_scripts_setup', array( $this, 'localize_scripts' ), PHP_INT_MAX ); add_action( 'wp_ajax_check_duplicate', array( $this, 'wpml_translate_independently' ) ); add_filter( 'tiny_mce_before_init', array( $this, 'add_tiny_mce_change_detection' ), 999, 1 ); } public function wpml_translate_independently() { $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : null; $nonce = isset( $_POST['icl_duplciate_nonce'] ) ? sanitize_text_field( $_POST['icl_duplciate_nonce'] ) : ''; if ( wp_verify_nonce( $nonce, 'icl_check_duplicates' ) || null === $post_id ) { if ( delete_post_meta( $post_id, '_icl_lang_duplicate_of' ) ) { wp_send_json_success( true ); } else { wp_send_json_error( false ); } } else { wp_send_json_error( false ); } } public function localize_scripts() { $success = _x( 'You are updating a duplicate post.', '1/2 Confirm to make duplicated translations independent', 'sitepress' ) . "\n"; $success .= _x( 'To not lose your changes, WPML will set this post to be translated independently.', '2/2 Confirm to make duplicated translations independent', 'sitepress' ) . "\n"; $duplicate_data = array( 'icl_duplicate_message' => $success, 'icl_duplicate_fail' => __( 'Unable to remove relationship!', 'sitepress' ), ); $post = isset( $_GET['post'] ) ? filter_var( $_GET['post'], FILTER_SANITIZE_NUMBER_INT ) : 0; if ( $post && '' !== get_post_meta( (int) $post, '_icl_lang_duplicate_of', true ) ) { $duplicate_data['duplicate_post_nonce'] = wp_create_nonce( 'icl_check_duplicates' ); $duplicate_data['duplicate_post'] = $post; $duplicate_data['wp_classic_editor_changed'] = false; } wp_localize_script( 'sitepress-post-edit', 'icl_duplicate_data', $duplicate_data ); } /** * Add callback to detect post editor change. * * @param array $initArray * * @return array */ public function add_tiny_mce_change_detection( $initArray ) { $initArray['setup'] = 'function(ed) { ed.on(\'change\', function() { icl_duplicate_data.wp_classic_editor_changed = true; }); }'; return $initArray; } } query-filtering/class-wpml-attachments-urls-with-identical-slugs-factory.php 0000755 00000000664 14720342453 0023501 0 ustar 00 <?php /** * Class WPML_Attachments_Urls_With_Identical_Slugs_Factory * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4700 */ class WPML_Attachments_Urls_With_Identical_Slugs_Factory implements IWPML_Frontend_Action_Loader, IWPML_Deferred_Action_Loader { public function get_load_action() { return 'init'; } public function create() { return new WPML_Attachments_Urls_With_Identical_Slugs(); } } query-filtering/wpml-query-utils.class.php 0000755 00000011651 14720342453 0015002 0 ustar 00 <?php /** * Class WPML_Query_Utils * * @package wpml-core */ class WPML_Query_Utils { /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_WP_API */ private $wp_api; /** @var array $display_as_translated_post_types */ private $display_as_translated_post_types; /** * WPML_Query_Utils constructor. * * @param wpdb $wpdb * @param WPML_WP_API $wp_api * @param array $display_as_translated_post_types */ public function __construct( wpdb $wpdb, WPML_WP_API $wp_api, $display_as_translated_post_types ) { $this->wpdb = $wpdb; $this->wp_api = $wp_api; $this->display_as_translated_post_types = $display_as_translated_post_types; } /** * Returns the number of posts for a given post_type, author and language combination that is published. * * @param array|string $post_type * @param WP_User $author_data * @param string $lang language code to check * @param string $fallback_lang * * @return bool * * @used-by \WPML_Languages::add_author_url_to_ls_lang to determine what languages to show in the Language Switcher */ public function author_query_has_posts( $post_type, $author_data, $lang, $fallback_lang ) { $post_types = (array) $post_type; $post_type_snippet = (bool) $post_types ? ' AND post_type IN (' . wpml_prepare_in( $post_types ) . ') ' : ''; $language_snippet = $this->get_language_snippet( $lang, $fallback_lang, $post_type ); return (bool) $this->wpdb->get_var( $this->wpdb->prepare( " SELECT COUNT(p.ID) FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON p.ID=wpml_translations.element_id AND wpml_translations.element_type = CONCAT('post_', p.post_type) WHERE p.post_author=%d " . $post_type_snippet . " AND post_status='publish' " . $language_snippet . ' LIMIT 1', $author_data->ID ) ); } /** * Returns the number of posts for a given post_type, date and language combination that is published. * * @param string $lang language code to check * @param string $fallback_lang * @param null|int $year * @param null|int $month * @param null|int $day * @param string|array $post_type * * @return bool * * @used-by \WPML_Languages::add_date_or_cpt_url_to_ls_lang to determine what languages to show in the Language Switcher */ public function archive_query_has_posts( $lang, $fallback_lang, $year = null, $month = null, $day = null, $post_type = 'post' ) { $cache_args = array(); $cache_args['lang'] = $lang; $cache_args['fallback_lang'] = $fallback_lang; $cache_args['year'] = $year; $cache_args['month'] = $month; $cache_args['day'] = $day; $cache_args['post_type'] = $post_type; $cache_key = md5( (string) json_encode( $cache_args ) ); $cache_group = 'archive_query_has_posts'; $cache = new WPML_WP_Cache( $cache_group ); $found = false; $result = $cache->get( $cache_key, $found ); if ( ! $found ) { $post_status_snippet = $this->wp_api->current_user_can( 'read' ) ? 'p.post_status IN (' . wpml_prepare_in( array( 'publish', 'private' ) ) . ') ' : "p.post_status = 'publish'"; $post_type_snippet = is_array( $post_type ) ? ' AND post_type IN (' . wpml_prepare_in( $post_type ) . ') ' : $this->wpdb->prepare( ' AND p.post_type = %s ', $post_type ); $year_snippet = (bool) $year === true ? $this->wpdb->prepare( ' AND year(p.post_date) = %d ', $year ) : ''; $month_snippet = (bool) $month === true ? $this->wpdb->prepare( ' AND month(p.post_date) = %d ', $month ) : ''; $day_snippet = (bool) $day === true ? $this->wpdb->prepare( ' AND day(p.post_date) = %d ', $day ) : ''; $lang_snippet = $this->get_language_snippet( $lang, $fallback_lang, $post_type ); $result = $this->wpdb->get_var( " SELECT p.ID FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON p.ID = wpml_translations.element_id AND wpml_translations.element_type = CONCAT('post_', p.post_type) WHERE " . $post_status_snippet . $year_snippet . $month_snippet . $day_snippet . $post_type_snippet . $lang_snippet . ' LIMIT 1' ); $cache->set( $cache_key, $result ); } return (bool) $result; } private function get_language_snippet( $lang, $fallback_lang, $post_type ) { if ( in_array( $post_type, $this->display_as_translated_post_types ) ) { $display_as_translated_query = new WPML_Display_As_Translated_Posts_Query( $this->wpdb, 'p' ); $display_as_translated_snippet = $display_as_translated_query->get_language_snippet( $lang, $fallback_lang, $this->display_as_translated_post_types ); } else { $display_as_translated_snippet = '0'; } return $this->wpdb->prepare( " AND (wpml_translations.language_code = %s OR {$display_as_translated_snippet}) ", $lang ); } } query-filtering/class-wpml-query-parser.php 0000755 00000043664 14720342453 0015146 0 ustar 00 <?php use WPML\FP\Obj; /** * Class WPML_Query_Parser * * @since 3.2.3 */ class WPML_Query_Parser { const LANG_VAR = 'wpml_lang'; /** @var WPML_Post_Translation $post_translations */ protected $post_translations; /** @var WPML_Term_Translation $post_translations */ protected $term_translations; /** @var SitePress $sitepress */ protected $sitepress; /** @var wpdb $wpdb */ public $wpdb; /** @var WPML_Query_Filter $query_filter */ private $query_filter; /** * @param SitePress $sitepress * @param WPML_Query_Filter $query_filter */ public function __construct( $sitepress, $query_filter ) { $this->sitepress = $sitepress; $this->wpdb = $sitepress->wpdb(); $this->post_translations = $sitepress->post_translations(); $this->term_translations = $sitepress->term_translations(); $this->query_filter = $query_filter; } /** * @param WP_Query $q * @param string $lang * * @return WP_Query */ private function adjust_default_taxonomies_query_vars( $q, $lang ) { $vars = array( 'cat' => array( 'type' => 'ids', 'tax' => 'category', ), 'category_name' => array( 'type' => 'slugs', 'tax' => 'category', ), 'category__and' => array( 'type' => 'ids', 'tax' => 'category', ), 'category__in' => array( 'type' => 'ids', 'tax' => 'category', ), 'category__not_in' => array( 'type' => 'ids', 'tax' => 'category', ), 'tag' => array( 'type' => 'slugs', 'tax' => 'post_tag', ), 'tag_id' => array( 'type' => 'ids', 'tax' => 'post_tag', ), 'tag__and' => array( 'type' => 'ids', 'tax' => 'post_tag', ), 'tag__in' => array( 'type' => 'ids', 'tax' => 'post_tag', ), 'tag__not_in' => array( 'type' => 'ids', 'tax' => 'post_tag', ), 'tag_slug__and' => array( 'type' => 'slugs', 'tax' => 'post_tag', ), 'tag_slug__in' => array( 'type' => 'slugs', 'tax' => 'post_tag', ), ); foreach ( $vars as $key => $args ) { if ( isset( $q->query_vars[ $key ] ) && ! ( empty( $q->query_vars[ $key ] ) || $q->query_vars[ $key ] === 0 ) ) { list( $values, $glue ) = $this->parse_scalar_values_in_query_vars( $q, $key, $args['type'] ); $translated_values = $this->translate_term_values( $values, $args['type'], $args['tax'], $lang ); $q = $this->replace_query_vars_value( $q, $key, $translated_values, $glue ); } } return $q; } /** * @param WP_Query $q * @param string $key * @param string $type * * @return array */ private function parse_scalar_values_in_query_vars( $q, $key, $type ) { $glue = false; $values = array(); if ( is_scalar( $q->query_vars[ $key ] ) ) { if ( is_string( $q->query_vars[ $key ] ) ) { $glue = strpos( $q->query_vars[ $key ], ',' ) !== false ? ',' : $glue; $glue = strpos( $q->query_vars[ $key ], '+' ) !== false ? '+' : $glue; if ( $glue ) { $values = explode( $glue, $q->query_vars[ $key ] ); } } if ( ! $glue ) { $values = array( $q->query_vars[ $key ] ); } /** @phpstan-ignore-next-line trim is not recognised by PHPStan. */ $values = array_map( 'trim', $values ); $values = $type === 'ids' ? array_map( 'intval', $values ) : $values; } elseif ( is_array( $q->query_vars[ $key ] ) ) { $values = $q->query_vars[ $key ]; } return array( $values, $glue ); } /** * @param array $values * @param string $type * @param string $taxonomy * @param string $lang * * @return array */ private function translate_term_values( $values, $type, $taxonomy, $lang ) { $translated_values = array(); if ( $type === 'ids' ) { foreach ( $values as $id ) { $sign = (int) $id < 0 ? - 1 : 1; $id = abs( $id ); $translated_values[] = $sign * (int) $this->term_translations->term_id_in( $id, $lang, true ); } } elseif ( $type === 'slugs' ) { foreach ( $values as $slug ) { $slug_elements = explode( '/', $slug ); foreach ( $slug_elements as &$slug_element ) { $slug_element = $this->translate_term_slug( $slug_element, $taxonomy, $lang ); } $slug = implode( '/', $slug_elements ); $translated_values[] = $slug; } } return $translated_values; } /** * @param string $slug * @param string $taxonomy * @param string $lang * * @return null|string */ private function translate_term_slug( $slug, $taxonomy, $lang ) { $id = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT t.term_id FROM {$this->wpdb->terms} t JOIN {$this->wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.slug = %s LIMIT 1", $taxonomy, $slug ) ); $term_id = (int) $this->term_translations->term_id_in( $id, $lang, true ); if ( $term_id !== $id ) { $slug = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT slug FROM {$this->wpdb->terms} WHERE term_id = %d LIMIT 1", $term_id ) ); } return $slug; } /** * @param WP_Query $q * @param string $key * @param array $translated_values * @param string $glue * * @return WP_Query */ private function replace_query_vars_value( $q, $key, $translated_values, $glue ) { if ( ! empty( $translated_values ) && ! empty( $translated_values[0] ) ) { $translated_values = array_unique( $translated_values ); if ( is_scalar( $q->query_vars[ $key ] ) ) { $q->query_vars[ $key ] = implode( $glue, $translated_values ); } elseif ( is_array( $q->query_vars[ $key ] ) ) { $q->query_vars[ $key ] = $translated_values; } } return $q; } /** * @param WP_Query $q * * @return WP_Query */ private function adjust_taxonomy_query( $q ) { if ( isset( $q->query_vars['tax_query'], $q->tax_query->queries, $q->query['tax_query'] ) && is_array( $q->query_vars['tax_query'] ) && is_array( $q->tax_query->queries ) && is_array( $q->query['tax_query'] ) && empty( $q->query_vars['suppress_filters'] ) ) { $new_conditions = $this->adjust_tax_query_conditions( $q->query['tax_query'] ); $q->query['tax_query'] = $new_conditions; $q->tax_query->queries = $new_conditions; $q->query_vars['tax_query'] = $new_conditions; } return $q; } /** * Recursive method to allow conversion of nested conditions * * @param array $conditions * * @return array */ private function adjust_tax_query_conditions( $conditions ) { foreach ( $conditions as $key => $condition ) { if ( ! is_array( $condition ) ) { // e.g 'relation' => 'OR' continue; } elseif ( ! isset( $condition['terms'] ) ) { // Process recursively the nested condition $conditions[ $key ] = $this->adjust_tax_query_conditions( $condition ); } elseif ( is_array( $condition['terms'] ) ) { foreach ( $condition['terms'] as $value ) { $field = isset( $condition['field'] ) ? $condition['field'] : 'term_id'; $term = $this->sitepress->get_wp_api()->get_term_by( $field, $value, $condition['taxonomy'] ); if ( is_object( $term ) ) { if ( $field === 'id' && ! isset( $term->id ) ) { $translated_value = isset( $term->term_id ) ? $term->term_id : null; } else { $translated_value = isset( $term->{$field} ) ? $term->{$field} : null; } $index = array_search( $value, $condition['terms'] ); $condition['terms'][ $index ] = $translated_value; } } $conditions[ $key ] = $condition; } elseif ( is_scalar( $condition['terms'] ) ) { $field = isset( $condition['field'] ) ? $condition['field'] : 'id'; $term = $this->sitepress->get_wp_api()->get_term_by( $field, $condition['terms'], $condition['taxonomy'] ); if ( is_object( $term ) ) { $field = $field == 'id' ? 'term_id' : $field; $conditions[ $key ]['terms'] = isset( $term->{$field} ) ? $term->{$field} : null; } } } return $conditions; } /** * @param WP_Query $q * @param string $current_lang * * @return mixed */ private function maybe_redirect_to_translated_taxonomy( $q, $current_lang ) { if ( ! $q->is_main_query() ) { return $q; } foreach ( $this->get_query_taxonomy_term_slugs( $q ) as $slug => $taxonomy ) { $translated_slugs = $this->translate_term_values( array( $slug ), 'slugs', $taxonomy, $current_lang ); if ( $translated_slugs && (string) $slug !== $translated_slugs[0] ) { /** @var WP_Term|false */ $translated_term = get_term_by( 'slug', $translated_slugs[0], $taxonomy ); $new_url = $translated_term ? get_term_link( $translated_term, $taxonomy ) : null; if ( $new_url && ! is_wp_error( $new_url ) ) { /** @var WPML_WP_API */ global $wpml_wp_api; $wpml_wp_api->wp_safe_redirect( $new_url ); return null; } } } return $q; } private function get_query_taxonomy_term_slugs( WP_Query $q ) { $result = array(); if ( isset( $q->tax_query->queries ) && count( $q->tax_query->queries ) ) { foreach ( $q->tax_query->queries as $taxonomy_data ) { if ( isset( $taxonomy_data['terms'] ) && isset( $taxonomy_data['field'] ) && $taxonomy_data['field'] === 'slug' ) { foreach ( $taxonomy_data['terms'] as $slug ) { $result[ $slug ] = $taxonomy_data['taxonomy']; } } } } return $result; } /** * @param WP_Query $q * * @return WP_Query */ function parse_query( $q ) { if ( $this->sitepress->get_wp_api()->is_admin() && ! $this->sitepress->get_wp_api()->constant( 'DOING_AJAX' ) ) { return $q; } $q = apply_filters( 'wpml_pre_parse_query', $q ); list( $q, $redir_pid ) = $this->maybe_adjust_name_var( $q ); /** @var WP_Query $q */ if ( $q->is_main_query() && (bool) $redir_pid === true ) { if ( (bool) ( $redir_target = $this->is_redirected( $redir_pid, $q ) ) ) { $this->sitepress->get_wp_api()->wp_safe_redirect( $redir_target, 301 ); } } $post_type = 'post'; if ( ! empty( $q->query_vars['post_type'] ) ) { $post_type = $q->query_vars['post_type']; } $current_language = Obj::path( [ 'query_vars', self::LANG_VAR ], $q ) ?: $this->sitepress->get_current_language(); $q = $this->maybe_redirect_to_translated_taxonomy( $q, $current_language ); if ( ! $q ) { // it means that `maybe_redirect_to_translated_taxonomy` has made redirection, it just facilitates the test return $q; } if ( 'attachment' === $post_type || $current_language !== $this->sitepress->get_default_language() ) { $q = $this->adjust_default_taxonomies_query_vars( $q, $current_language ); if ( ! is_array( $post_type ) ) { $post_type = (array) $post_type; } if ( ! empty( $q->query_vars['page_id'] ) ) { $q->query_vars['page_id'] = $this->get_translated_post( $q->query_vars['page_id'], $current_language ); } $q = $this->adjust_query_ids( $q, 'include' ); $q = $this->adjust_query_ids( $q, 'exclude' ); if ( isset( $q->query_vars['p'] ) && ! empty( $q->query_vars['p'] ) ) { $q->query_vars['p'] = $this->get_translated_post( $q->query_vars['p'], $current_language ); } if ( $post_type ) { $first_post_type = reset( $post_type ); if ( $this->sitepress->is_translated_post_type( $first_post_type ) && ! empty( $q->query_vars['name'] ) ) { if ( is_post_type_hierarchical( $first_post_type ) ) { $requested_page = get_page_by_path( $q->query_vars['name'], OBJECT, $first_post_type ); if ( $requested_page && 'attachment' !== $requested_page->post_type ) { $q->query_vars['p'] = $this->post_translations->element_id_in( $requested_page->ID, $current_language, true ); unset( $q->query_vars['name'] ); // We need to set this to an empty string otherwise WP will derive the pagename from this. $q->query_vars[ $first_post_type ] = ''; } } else { $pid_prepared = $this->wpdb->prepare( " SELECT ID FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations t ON t.element_id = p.ID AND t.element_type='post_{$first_post_type}' WHERE post_name=%s AND post_type=%s AND t.language_code=%s LIMIT 1 ", array( $q->query_vars['name'], $first_post_type, $current_language ) ); $pid = $this->wpdb->get_var( $pid_prepared ); if ( ! empty( $pid ) ) { $q->query_vars['p'] = $this->post_translations->element_id_in( $pid, $current_language, true ); unset( $q->query_vars['name'] ); } } } $q = $this->adjust_q_var_pids( $q, $post_type, 'post__in' ); $q = $this->adjust_q_var_pids( $q, $post_type, 'post__not_in' ); $q = $this->maybe_adjust_parent( $q, $post_type, $current_language ); } // TODO: [WPML 3.3] Discuss this. Why WP assumes it's there if query vars are altered? Look at wp-includes/query.php line #2468 search: if ( $this->query_vars_changed ) { $q->query_vars['meta_query'] = isset( $q->query_vars['meta_query'] ) ? $q->query_vars['meta_query'] : array(); $q = $this->adjust_taxonomy_query( $q ); } $q = apply_filters( 'wpml_post_parse_query', $q ); return $q; } /** * Adjust the parent post in the query in case we're dealing with a translated * post type. * * @param WP_Query $q * @param string|string[] $post_type * @param string $current_language * * @return WP_Query mixed */ private function maybe_adjust_parent( $q, $post_type, $current_language ) { $post_type = ! is_scalar( $post_type ) && count( $post_type ) === 1 ? end( $post_type ) : $post_type; if ( ! empty( $q->query_vars['post_parent'] ) && $q->query_vars['post_type'] !== 'attachment' && $post_type && is_scalar( $post_type ) && $this->sitepress->is_translated_post_type( $post_type ) ) { $q->query_vars['post_parent'] = $this->post_translations->element_id_in( $q->query_vars['post_parent'], $current_language, true ); } return $q; } /** * Tries to transform certain queries from "by name" querying to "by ID" to overcome WordPress Core functionality * for resolving names not being filtered by language * * @param \WP_Query $q * * @return array<\WP_Query, bool> */ private function maybe_adjust_name_var( $q ) { $redirect = false; if ( ( (bool) ( $name_in_q = $q->get( 'name' ) ) === true || (bool) ( $name_in_q = $q->get( 'pagename' ) ) === true ) && (bool) $q->get( 'page_id' ) === false && (bool) $q->get( 'category_name' ) === false || ( (bool) ( $post_type = $q->get( 'post_type' ) ) === true && is_scalar( $post_type ) && (bool) ( $name_in_q = $q->get( $post_type ) ) === true ) ) { list( $name_found, $type, $altered ) = $this->query_filter->get_404_util()->guess_cpt_by_name( $name_in_q, $q ); if ( $altered === true ) { $name_before = $q->get( 'name' ); $q->set( 'name', $name_found ); } $type = $type ? $type : 'page'; $type = is_scalar( $type ) ? $type : ( count( $type ) === 1 ? end( $type ) : false ); /** * @var \WP_Query $q * @var int|false $redirect */ list( $q, $redirect ) = $type ? $this->query_filter->get_page_name_filter( $type )->filter_page_name( $q ) : array( $q, false ); if ( isset( $name_before ) ) { $q->set( 'name', $name_before ); } } return array( $q, $redirect ); } private function adjust_query_ids( $q, $index ) { if ( ! empty( $q->query_vars[ $index ] ) ) { $untranslated = is_array( $q->query_vars[ $index ] ) ? $q->query_vars[ $index ] : explode( ',', $q->query_vars[ $index ] ); $this->post_translations->prefetch_ids( $untranslated ); $ulanguage_code = $this->sitepress->get_current_language(); $translated = array(); foreach ( $untranslated as $element_id ) { $translated[] = $this->post_translations->element_id_in( $element_id, $ulanguage_code ); } $q->query_vars[ $index ] = is_array( $q->query_vars[ $index ] ) ? $translated : implode( ',', $translated ); } return $q; } private function adjust_q_var_pids( $q, $post_types, $index ) { if ( ! empty( $q->query_vars[ $index ] ) && (bool) $post_types !== false ) { $untranslated = $q->query_vars[ $index ]; $this->post_translations->prefetch_ids( $untranslated ); $current_lang = $this->sitepress->get_current_language(); $pid = array(); foreach ( $q->query_vars[ $index ] as $p ) { $pid[] = $this->post_translations->element_id_in( $p, $current_lang, true ); } $q->query_vars[ $index ] = $pid; } return $q; } /** * @param int $post_id * @param WP_Query $q * * @return false|string redirect target url if redirect is needed, false otherwise */ private function is_redirected( $post_id, $q ) { $request_uri = explode( '?', $_SERVER['REQUEST_URI'] ); $redirect = false; $permalink = $this->sitepress->get_wp_api()->get_permalink( $post_id ); if ( ! $this->is_permalink_part_of_request( $permalink, $request_uri[0] ) ) { if ( isset( $request_uri[1] ) ) { $args = array(); parse_str( $request_uri[1], $args ); if ( array_key_exists( 'lang', $args ) ) { $permalink = add_query_arg( array( 'lang' => $args['lang'] ), $permalink ); } } $redirect = $permalink; } return apply_filters( 'wpml_is_redirected', $redirect, $post_id, $q ); } public static function is_permalink_part_of_request( $permalink, $request_uri ) { $permalink_path = trailingslashit( urldecode( wpml_parse_url( $permalink, PHP_URL_PATH ) ) ); $request_uri = trailingslashit( urldecode( $request_uri ) ); return 0 === strcasecmp( substr( $request_uri, 0, strlen( $permalink_path ) ), $permalink_path ); } private function get_translated_post( $element_id, $current_language ) { $translated_id = $this->post_translations->element_id_in( $element_id, $current_language, true ); $type = $this->post_translations->get_type( $element_id ); if ( $this->sitepress->is_display_as_translated_post_type( $type ) ) { $post = get_post( $translated_id ); if ( 'publish' != $post->post_status ) { $translated_id = $element_id; } } return $translated_id; } } query-filtering/class-wpml-display-as-translated-attachments-query.php 0000755 00000002250 14720342453 0022352 0 ustar 00 <?php class WPML_Display_As_Translated_Attachments_Query { private $sitepress; private $post_translation; public function __construct( SitePress $sitepress, WPML_Post_Translation $post_translation ) { $this->sitepress = $sitepress; $this->post_translation = $post_translation; } public function add_hooks() { add_filter( 'wpml_post_parse_query', array( $this, 'adjust_post_parent' ) ); } /** * @param \WP_Query $q * * @return \WP_Query */ public function adjust_post_parent( $q ) { if ( ! empty( $q->query_vars['post_parent'] ) && isset( $q->query['post_type'] ) && 'attachment' === $q->query['post_type'] && $this->sitepress->is_display_as_translated_post_type( $q->query['post_type'] ) && $this->sitepress->get_current_language() !== $this->sitepress->get_default_language() ) { $q->query_vars['post_parent__in'] = [ $q->query_vars['post_parent'] ]; $originalElementId = $this->post_translation->get_original_element( $q->query_vars['post_parent'] ); if ( $originalElementId ) { $q->query_vars['post_parent__in'] [] = $originalElementId; } unset( $q->query_vars['post_parent'] ); } return $q; } } query-filtering/class-wpml-attachments-urls-with-identical-slugs.php 0000755 00000002042 14720342453 0022024 0 ustar 00 <?php /** * Class WPML_Attachments_Urls_With_Identical_Slugs * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4700 */ class WPML_Attachments_Urls_With_Identical_Slugs implements IWPML_Action { public function add_hooks() { add_filter( 'parse_query', array( $this, 'translate_attachment_id' ), PHP_INT_MAX ); } /** * Translate the attachment id in the $wp_query during parse_query * * @param WP_Query $wp_query * * @return WP_Query */ public function translate_attachment_id( $wp_query ) { if ( isset( $wp_query->query['pagename'] ) && false !== strpos( $wp_query->query['pagename'], '/' ) ) { if ( ! empty( $wp_query->queried_object_id ) ) { $post_type = get_post_field( 'post_type', $wp_query->queried_object_id ); if ( $post_type === 'attachment' ) { $wp_query->queried_object_id = apply_filters( 'wpml_object_id', $wp_query->queried_object_id, 'attachment', true ); $wp_query->queried_object = get_post( $wp_query->queried_object_id ); } } } return $wp_query; } } query-filtering/class-wpml-404-guess.php 0000755 00000011411 14720342453 0014123 0 ustar 00 <?php /** * Class WPML_404_Guess * * @package wpml-core * @subpackage post-translation * * @since 3.2.3 */ class WPML_404_Guess extends WPML_Slug_Resolution { /** @var WPML_Query_Filter $query_filter */ private $query_filter; /** * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_Query_Filter $query_filter */ public function __construct( &$wpdb, &$sitepress, &$query_filter ) { parent::__construct( $wpdb, $sitepress ); $this->query_filter = &$query_filter; } /** * Attempts to guess the correct URL based on query vars * * @since 3.2.3 * * @param string $name * @param WP_Query $query * * @return array<string|bool> containing most likely name, type and whether or not a match was found */ public function guess_cpt_by_name( $name, $query ) { $type = $query->get( 'post_type' ); $ret = array( $name, $type, false ); $types = (bool) $type === false ? $this->sitepress->get_wp_api()->get_post_types( array( 'public' => true ) ) : (array) $type; if ( (bool) $types === true ) { $date_snippet = $this->by_date_snippet( $query ); $page_first = (bool) $query->get( 'pagename' ); $cache = new WPML_WP_Cache( 'WPML_404_Guess' ); $cache_key = 'guess_cpt' . $name . wp_json_encode( $types ) . $page_first . $date_snippet; $found = false; $ret = $cache->get( $cache_key, $found ); if ( ! $found ) { $ret = $this->find_post_type( $name, $type, $types, $date_snippet, $page_first ); $cache->set( $cache_key, $ret ); } } return $ret; } /** * Query the database to find the post type * * @param string $name * @param string $type * @param array $types * @param string $date_snippet * @param bool $page_first * * @return array */ private function find_post_type( $name, $type, $types, $date_snippet, $page_first ) { $ret = array( $name, $type, false ); $where = $this->wpdb->prepare( 'post_name = %s ', $name ); $where .= " AND post_type IN ('" . implode( "', '", $types ) . "')"; $where .= $date_snippet; /** @var \stdClass $res */ $res = $this->wpdb->get_row( " SELECT post_type, post_name FROM {$this->wpdb->posts} p LEFT JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON wpml_translations.element_id = p.ID AND CONCAT('post_', p.post_type) = wpml_translations.element_type AND " . $this->query_filter->in_translated_types_snippet( false, 'p' ) . " WHERE $where AND ( post_status = 'publish' OR ( post_type = 'attachment' AND post_status = 'inherit' ) ) " . $this->order_by_type_and_language_snippet( (bool) $date_snippet, $page_first ) . ' LIMIT 1' ); if ( (bool) $res === true ) { $ret = array( $res->post_name, $res->post_type, true ); } return $ret; } /** * Retrieves year, month and day parameters from the query if they are set and builds the appropriate sql * snippet to filter for them. * * @param WP_Query $query * * @return string */ private function by_date_snippet( $query ) { $snippet = ''; foreach ( array( 'year' => 'YEAR', 'monthnum' => 'MONTH', 'day' => 'DAY', ) as $index => $time_unit ) { if ( (bool) ( $value = $query->get( $index ) ) === true ) { $snippet .= $this->wpdb->prepare( " AND {$time_unit}(post_date) = %d ", $value ); } } return $snippet; } /** * @param bool $has_date * @param bool $page_first * * @return string */ private function order_by_type_and_language_snippet( $has_date, $page_first ) { $lang_order = $this->get_ordered_langs(); $current_lang = array_shift( $lang_order ); $best_score = count( $lang_order ) + 2; $order_by = sprintf( "ORDER BY post_type = 'page' %s", $page_first ? 'DESC' : 'ASC' ); if ( $best_score > 2 ) { $order_by .= $this->wpdb->prepare( ', CASE wpml_translations.language_code WHEN %s THEN %d ', $current_lang, $best_score ); $score = $best_score - 2; foreach ( $lang_order as $lang_code ) { $order_by .= $this->wpdb->prepare( ' WHEN %s THEN %d ', $lang_code, $score ); $score -= 1; } $order_by .= ' ELSE 0 END DESC '; if ( $has_date ) { $order_by .= ", CASE p.post_type WHEN 'post' THEN 0 ELSE 1 END "; } $order_by .= ' , ' . $this->order_by_post_type_snippet(); } return $order_by; } /** * * @return string */ private function order_by_post_type_snippet() { $post_types = array( 'page' => 2, 'post' => 1, ); $order_by = ' CASE p.post_type '; foreach ( $post_types as $type => $score ) { $order_by .= $this->wpdb->prepare( ' WHEN %s THEN %d ', $type, $score ); } $order_by .= ' ELSE 0 END DESC '; return $order_by; } } query-filtering/class-wpml-archives-query.php 0000755 00000002741 14720342453 0015445 0 ustar 00 <?php use WPML\FP\Obj; class WPML_Archives_Query implements IWPML_Frontend_Action, IWPML_DIC_Action { /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Language_Where_Clause $language_where_clause */ private $language_where_clause; /** @var SitePress */ private $sitepress; public function __construct( wpdb $wpdb, WPML_Language_Where_Clause $language_where_clause, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->language_where_clause = $language_where_clause; $this->sitepress = $sitepress; } public function add_hooks() { add_filter( 'getarchives_join', array( $this, 'get_archives_join' ), 10, 2 ); add_filter( 'getarchives_where', array( $this, 'get_archives_where' ), 10, 2 ); } /** * @param string $join * @param array $args * * @return string */ public function get_archives_join( $join, $args ) { $postType = esc_sql( Obj::propOr( 'post', 'post_type', $args ) ); if ( $this->sitepress->is_translated_post_type( $postType ) ) { $join .= " JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON wpml_translations.element_id = {$this->wpdb->posts}.ID AND wpml_translations.element_type='post_" . $postType . "'"; } return $join; } /** * @param string $where_clause * @param array $args * * @return string */ public function get_archives_where( $where_clause, $args ) { return $where_clause . $this->language_where_clause->get( Obj::propOr( 'post', 'post_type', $args ) ); } } query-filtering/class-wpml-term-display-as-translated-adjust-count.php 0000755 00000006323 14720342453 0022266 0 ustar 00 <?php /** * Class WPML_Term_Display_As_Translated_Adjust_Count */ class WPML_Term_Display_As_Translated_Adjust_Count { /** @var SitePress $sitepress */ private $sitepress; /** @var wpdb $wpdb */ private $wpdb; /** @var array */ private $taxonomies_display_as_translated; /** * WPML_Term_Display_As_Translated_Adjust_Count constructor. * * @param SitePress $sitepress * @param wpdb $wpdb */ public function __construct( SitePress $sitepress, wpdb $wpdb ) { if ( ! isset( $GLOBALS['wp_version'] ) || version_compare( $GLOBALS['wp_version'], '6.0', '<' ) ) { // Only needed since WP 6.0. return; } if ( is_admin() && ! WPML_Ajax::is_frontend_ajax_request() ) { // No need to adjust on admin sites. return; } $this->sitepress = $sitepress; $this->wpdb = $wpdb; $this->taxonomies_display_as_translated = $sitepress->get_display_as_translated_taxonomies(); // The final hook needs to be on the generic 'get_term', but the logic // should only run when categories or tags are fetched. add_filter( 'get_term', [ $this, 'add_get_term_adjust_count' ], 10, 2 ); } public function add_get_term_adjust_count( $term, $taxonomy ) { if ( ! in_array( $taxonomy, $this->taxonomies_display_as_translated, true ) ) { // Display as translated is not enabled for this taxonomy. return $term; } // Adjust the count on the get_term filter. add_filter( 'get_term', [ $this, 'get_term_adjust_count' ] ); // This is the next hook triggered to remove the previous filter again. add_filter( 'get_terms', [ $this, 'remove_get_term_adjust_count' ] ); return $term; } public function remove_get_term_adjust_count( $terms ) { remove_filter( 'get_term', [ $this, 'get_term_adjust_count' ] ); return $terms; } public function get_term_adjust_count( $term ) { if ( ! is_object( $term ) ) { return $term; } $table_prefix = $this->wpdb->prefix; $originalTermCount = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT ( SELECT term_taxonomy.count FROM {$table_prefix}term_taxonomy term_taxonomy INNER JOIN {$table_prefix}icl_translations translations ON translations.element_id = term_taxonomy.term_taxonomy_id WHERE translations.trid = icl_t.trid AND translations.language_code = %s ) as `originalCount` FROM {$table_prefix}terms AS t INNER JOIN {$table_prefix}term_taxonomy AS tt ON t.term_id = tt.term_id LEFT JOIN {$table_prefix}icl_translations icl_t ON icl_t.element_id = tt.term_taxonomy_id WHERE t.term_id = %d AND icl_t.element_type = %s ", $this->sitepress->get_default_language(), $term->term_id, 'tax_' . $term->taxonomy ) ); if ( $originalTermCount > $term->count ) { $term->count = $originalTermCount; } return $term; } } query-filtering/class-wpml-display-as-translated-posts-query.php 0000755 00000002457 14720342453 0021220 0 ustar 00 <?php class WPML_Display_As_Translated_Posts_Query extends WPML_Display_As_Translated_Query { /** @var string $post_table */ private $post_table; /** * WPML_Display_As_Translated_Posts_Query constructor. * * @param wpdb $wpdb * @param string $post_table_alias */ public function __construct( wpdb $wpdb, $post_table_alias = null ) { parent::__construct( $wpdb ); $this->post_table = $post_table_alias ? $post_table_alias : $wpdb->posts; } /** * @param array $post_types * * @return string */ protected function get_content_types_query( $post_types ) { $post_types = wpml_prepare_in( $post_types ); return "{$this->post_table}.post_type IN ( {$post_types} )"; } /** * @param string $language * * @return string */ protected function get_query_for_translation_not_published( $language ) { return $this->wpdb->prepare( " ( SELECT COUNT(element_id) FROM {$this->wpdb->prefix}icl_translations t2 JOIN {$this->wpdb->posts} p ON p.id = t2.element_id WHERE t2.trid = {$this->icl_translation_table_alias}.trid AND t2.language_code = %s AND ( p.post_status = 'publish' OR p.post_status = 'private' OR ( p.post_type='attachment' AND p.post_status = 'inherit' ) ) ) = 0", $language ); } } query-filtering/class-wpml-term-query-filter.php 0000755 00000014052 14720342453 0016071 0 ustar 00 <?php use \WPML\FP\Relation; use \WPML\FP\Fns; use WPML\TaxonomyTermTranslation\Hooks as TermTranslationHooks; class WPML_Term_Query_Filter { /** @var WPML_Term_Translation $term_translation */ private $term_translation; /** @var WPML_Debug_BackTrace $debug_backtrace */ private $debug_backtrace; /** @var wpdb $wpdb */ private $wpdb; /** @var IWPML_Taxonomy_State $taxonomy_state */ private $taxonomy_state; /** @var string $current_language */ private $current_language; /** @var string $default_language */ private $default_language; /** @var bool $lock */ private $lock; /** * WPML_Term_query_Filter constructor. * * @param WPML_Term_Translation $term_translation * @param WPML_Debug_BackTrace $debug_backtrace * @param wpdb $wpdb * @param IWPML_Taxonomy_State $taxonomy_state */ public function __construct( WPML_Term_Translation $term_translation, WPML_Debug_BackTrace $debug_backtrace, wpdb $wpdb, IWPML_Taxonomy_State $taxonomy_state ) { $this->term_translation = $term_translation; $this->debug_backtrace = $debug_backtrace; $this->wpdb = $wpdb; $this->taxonomy_state = $taxonomy_state; } /** @param string $current_language */ /** @param string $default_language */ public function set_lang( $current_language, $default_language ) { $this->current_language = $current_language; $this->default_language = $default_language; } /** * @param array $args * @param array $taxonomies * * @return array */ public function get_terms_args_filter( $args, $taxonomies ) { if ( $this->lock ) { return $args; } if ( class_exists( 'WPML\TaxonomyTermTranslation\Hooks' ) && TermTranslationHooks::shouldSkip( $args ) ) { return $args; } if ( 0 === count( array_filter( (array) $taxonomies, array( $this->taxonomy_state, 'is_translated_taxonomy' ) ) ) ) { return $args; } $this->lock = true; if ( isset( $args['cache_domain'] ) ) { $args['cache_domain'] .= '_' . $this->current_language; } $isOrderByEqualTo = Relation::propEq( 'orderby', Fns::__, $args ); $params = array( 'include', 'exclude', 'exclude_tree' ); foreach ( $params as $param ) { $adjusted_ids = $this->adjust_taxonomies_terms_ids( $args[ $param ], $isOrderByEqualTo( $param ) ); if ( ! empty( $adjusted_ids ) ) { $args[ $param ] = $adjusted_ids; } } $params = array( 'child_of', 'parent' ); foreach ( $params as $param ) { if ( ! isset( $args[ $param ] ) ) { continue; } $adjusted_ids = $this->adjust_taxonomies_terms_ids( $args[ $param ], $isOrderByEqualTo( $param ) ); if ( ! empty( $adjusted_ids ) ) { $args[ $param ] = array_pop( $adjusted_ids ); } } if ( ! empty( $args['slug'] ) ) { $args = $this->adjust_taxonomies_terms_slugs( $args, $taxonomies ); } // special case for when term hierarchy is cached in wp_options if ( $this->debug_backtrace->is_function_in_call_stack( '_get_term_hierarchy' ) ) { $args['_icl_show_all_langs'] = true; } $this->lock = false; return $args; } /** * @param string|array $terms_ids * @param bool $orderByTermId * * @return array */ private function adjust_taxonomies_terms_ids( $terms_ids, $orderByTermId ) { $terms_ids = array_filter( array_unique( $this->explode_and_trim( $terms_ids ) ) ); if ( empty( $terms_ids ) ) { return $terms_ids; } $terms = $this->get_terms( $terms_ids, $orderByTermId ); $translated_ids = array(); foreach ( $terms as $term ) { if ( $this->taxonomy_state->is_translated_taxonomy( $term->taxonomy ) ) { $translated_id = $this->term_translation->term_id_in( $term->term_id, $this->current_language ); if ( ! $translated_id && ! is_admin() && $this->taxonomy_state->is_display_as_translated_taxonomy( $term->taxonomy ) ) { $translated_id = $this->term_translation->term_id_in( $term->term_id, $this->default_language ); } $translated_ids[] = $translated_id; } else { $translated_ids[] = $term->term_id; } } return array_filter( $translated_ids ); } /** * @param array $args * @param array $taxonomies * * @return array */ private function adjust_taxonomies_terms_slugs( $args, array $taxonomies ) { $terms_slugs = $args['slug']; if ( is_string( $terms_slugs ) ) { $terms_slugs = [ $terms_slugs ]; } $duplicateSlugTranslations = []; $translated_slugs = []; foreach ( $terms_slugs as $terms_slug ) { $term = $this->guess_term( $terms_slug, $taxonomies ); if ( $term ) { $translated_id = $this->term_translation->term_id_in( $term->term_id, $this->current_language ); $translated_term = get_term( $translated_id, $term->taxonomy ); if ( $translated_term instanceof WP_Term ) { if ( $terms_slug === $translated_term->slug ) { $duplicateSlugTranslations[] = $translated_id; } $terms_slug = $translated_term->slug; } } $translated_slugs[] = $terms_slug; } if ( is_admin() && count( $duplicateSlugTranslations ) === 1 ) { $args['include'] = $duplicateSlugTranslations; $args['slug'] = ''; } else { $args['slug'] = array_filter( $translated_slugs ); } return $args; } /** * @param array $ids * @param bool $orderByTermId * * @return stdClass[] */ private function get_terms( $ids, $orderByTermId ) { $safeIds = wpml_prepare_in( $ids, '%d' ); $sql = "SELECT taxonomy, term_id FROM {$this->wpdb->term_taxonomy} WHERE term_id IN ({$safeIds}) "; $sql .= $orderByTermId ? "ORDER BY FIELD(term_id, {$safeIds})" : ''; return $this->wpdb->get_results( $sql ); } /** * @param string $slug * @param array $taxonomies * * @return null|WP_Term */ private function guess_term( $slug, array $taxonomies ) { foreach ( $taxonomies as $taxonomy ) { $term = get_term_by( 'slug', $slug, $taxonomy ); if ( $term ) { return $term; } } return null; } /** * @param string|array $source * * @return array */ private function explode_and_trim( $source ) { if ( ! is_array( $source ) ) { $source = array_map( 'trim', explode( ',', $source ) ); } return $source; } } query-filtering/class-wpml-get-page-by-path.php 0000755 00000004337 14720342453 0015534 0 ustar 00 <?php class WPML_Get_Page_By_Path { /** @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4918 */ const BEFORE_REMOVE_PLACEHOLDER_ESCAPE_PRIORITY = -1; /** @var wpdb $wpdb */ private $wpdb; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Debug_BackTrace $debug_backtrace */ private $debug_backtrace; /** @var string $language */ private $language; /** @var string $post_type */ private $post_type; public function __construct( wpdb $wpdb, SitePress $sitepress, WPML_Debug_BackTrace $debug_backtrace ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->debug_backtrace = $debug_backtrace; } public function get( $page_name, $lang, $output = OBJECT, $post_type = 'page' ) { $this->post_type = $post_type; $this->language = $lang; $this->clear_cache( $page_name, $post_type ); add_filter( 'query', [ $this, 'get_page_by_path_filter' ], self::BEFORE_REMOVE_PLACEHOLDER_ESCAPE_PRIORITY ); $temp_lang_switch = new WPML_Temporary_Switch_Language( $this->sitepress, $lang ); $page = get_page_by_path( $page_name, $output, $post_type ); $temp_lang_switch->restore_lang(); remove_filter( 'query', [ $this, 'get_page_by_path_filter' ], self::BEFORE_REMOVE_PLACEHOLDER_ESCAPE_PRIORITY ); return $page; } public function get_page_by_path_filter( $query ) { if ( $this->sitepress->is_translated_post_type( $this->post_type ) && $this->debug_backtrace->is_function_in_call_stack( 'get_page_by_path' ) ) { $where = $this->wpdb->prepare( "ID IN ( SELECT element_id FROM {$this->wpdb->prefix}icl_translations WHERE language_code = %s AND element_type LIKE 'post_%%' ) AND ", $this->language ); $query = str_replace( 'WHERE ', 'WHERE ' . $where, $query ); } return $query; } /** * @param string $page_name * @param string $post_type * * @see get_page_by_path where the cache key is built */ private function clear_cache( $page_name, $post_type ) { $last_changed = wp_cache_get_last_changed( 'posts' ); $hash = md5( $page_name . serialize( $post_type ) ); $cache_key = "get_page_by_path:$hash:$last_changed"; wp_cache_delete( $cache_key, 'posts' ); wp_cache_delete( $cache_key, 'post-queries' ); } } query-filtering/class-wpml-display-as-translated-taxonomy-query.php 0000755 00000005144 14720342453 0021722 0 ustar 00 <?php class WPML_Display_As_Translated_Taxonomy_Query extends WPML_Display_As_Translated_Query { /** @var string $term_taxonomy_table */ private $term_taxonomy_table; /** * WPML_Display_As_Translated_Posts_Query constructor. * * @param wpdb $wpdb * @param string $term_taxonomy_table_alias */ public function __construct( wpdb $wpdb, $term_taxonomy_table_alias = null ) { parent::__construct( $wpdb, 'icl_t' ); $this->term_taxonomy_table = $term_taxonomy_table_alias ? $term_taxonomy_table_alias : $wpdb->term_taxonomy; } /** * If "display as translated" mode is enabled, we check whether a category has some assigned posts or * its equivalent in the default language. * * @param array $clauses * @param string $default_lang * * @return array */ public function update_count( $clauses, $default_lang ) { $clauses['fields'] = $this->update_count_in_fields( $clauses['fields'], $default_lang ); $clauses['where'] = $this->update_count_in_where( $clauses['where'], $default_lang ); return $clauses; } private function update_count_in_fields( $fields, $default_lang ) { $sql = " tt.*, GREATEST( tt.count, ( SELECT term_taxonomy.count FROM {$this->wpdb->term_taxonomy} term_taxonomy INNER JOIN {$this->wpdb->prefix}icl_translations translations ON translations.element_id = term_taxonomy.term_taxonomy_id WHERE translations.trid = icl_t.trid AND translations.language_code = %s ) ) as `count` "; /** @var string $sql */ $sql = $this->wpdb->prepare( $sql, $default_lang ); return str_replace( 'tt.*', $sql, $fields ); } private function update_count_in_where( $where, $default_lang ) { $sql = " ( tt.count > 0 OR ( SELECT term_taxonomy.count FROM {$this->wpdb->term_taxonomy} term_taxonomy INNER JOIN {$this->wpdb->prefix}icl_translations translations ON translations.element_id = term_taxonomy.term_taxonomy_id WHERE translations.trid = icl_t.trid AND translations.language_code = %s ) > 0 ) "; /** @var string @sql */ $sql = $this->wpdb->prepare( $sql, $default_lang ); return str_replace( 'tt.count > 0', $sql, $where ); } /** * @param array<string> $taxonomies * * @return string */ protected function get_content_types_query( $taxonomies ) { $taxonomies = wpml_prepare_in( $taxonomies ); return "{$this->term_taxonomy_table}.taxonomy IN ( {$taxonomies} )"; } /** * @param string $language * * @return string */ protected function get_query_for_translation_not_published( $language ) { return '0'; } } query-filtering/class-wpml-display-as-translated-attachments-query-factory.php 0000755 00000000437 14720342453 0024024 0 ustar 00 <?php class WPML_Display_As_Translated_Attachments_Query_Factory implements IWPML_Frontend_Action_Loader { public function create() { global $sitepress, $wpml_post_translations; return new WPML_Display_As_Translated_Attachments_Query( $sitepress, $wpml_post_translations ); } } query-filtering/class-wpml-display-as-translated-tax-query-factory.php 0000755 00000000416 14720342453 0022302 0 ustar 00 <?php class WPML_Display_As_Translated_Tax_Query_Factory implements IWPML_Frontend_Action_Loader { public function create() { global $sitepress, $wpml_term_translations; return new WPML_Display_As_Translated_Tax_Query( $sitepress, $wpml_term_translations ); } } query-filtering/class-wpml-language-where-clause.php 0000755 00000002503 14720342453 0016637 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 27/10/17 * Time: 4:28 PM */ class WPML_Language_Where_Clause { /** @var SitePress $sitepress */ private $sitepress; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Display_As_Translated_Posts_Query $display_as_translated_query */ private $display_as_translated_query; public function __construct( SitePress $sitepress, wpdb $wpdb, WPML_Display_As_Translated_Posts_Query $display_as_translated_query ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; $this->display_as_translated_query = $display_as_translated_query; } public function get( $post_type ) { if ( $this->sitepress->is_translated_post_type( $post_type ) ) { $current_language = $this->sitepress->get_current_language(); if ( $this->sitepress->is_display_as_translated_post_type( $post_type ) ) { $default_language = $this->sitepress->get_default_language(); $display_as_translated_snippet = $this->display_as_translated_query->get_language_snippet( $current_language, $default_language, array( $post_type ) ); } else { $display_as_translated_snippet = '0'; } return $this->wpdb->prepare( " AND (language_code = '%s' OR {$display_as_translated_snippet} )", $current_language ); } else { return ''; } } } query-filtering/class-wpml-term-adjust-id.php 0000755 00000010511 14720342453 0015321 0 ustar 00 <?php /** * Class WPML_Term_Adjust_Id */ class WPML_Term_Adjust_Id { /** @var WPML_Debug_BackTrace */ private $debug_backtrace; /** @var WPML_Term_Translation */ private $term_translation; /** @var WPML_Post_Translation */ private $post_translation; /** @var SitePress */ private $sitepress; /** * WPML_Term_Adjust_Id constructor. * * @param WPML_Debug_BackTrace $debug_backtrace * @param WPML_Term_Translation $term_translation * @param WPML_Post_Translation $post_translation * @param SitePress $sitepress */ public function __construct( WPML_Debug_BackTrace $debug_backtrace, WPML_Term_Translation $term_translation, WPML_Post_Translation $post_translation, SitePress $sitepress ) { $this->debug_backtrace = $debug_backtrace; $this->term_translation = $term_translation; $this->post_translation = $post_translation; $this->sitepress = $sitepress; } /** * @param WP_Term $term * @param boolean $adjust_id_url_filter_off * * @return WP_Term */ public function filter( WP_Term $term, $adjust_id_url_filter_off ) { if ( $adjust_id_url_filter_off || ! $this->sitepress->get_setting( 'auto_adjust_ids' ) || $this->is_ajax_add_term_translation() || $this->debug_backtrace->are_functions_in_call_stack( [ 'get_category_parents', 'get_permalink', 'wp_update_post', 'wp_update_term', ] ) ) { WPML_Non_Persistent_Cache::flush_group( __CLASS__ ); return $term; } $object_id = isset( $term->object_id ) ? $term->object_id : false; $key = md5( (int) $object_id . $term->term_id . $this->sitepress->get_current_language() . $term->count ); $found = false; $cached_term = WPML_Non_Persistent_Cache::get( $key, __CLASS__, $found ); if ( $found ) { // See description on next return. return is_object( $cached_term ) ? clone $cached_term : $cached_term; } $translated_id = $this->term_translation->element_id_in( $term->term_taxonomy_id, $this->sitepress->get_current_language() ); if ( $translated_id && (int) $translated_id !== (int) $term->term_taxonomy_id ) { /** @var \WP_Term|\stdClass $term Declared also as \stdClass because we are setting `object_id`, which is not a property of \WP_Term. */ $term = get_term_by( 'term_taxonomy_id', $translated_id, $term->taxonomy ); if ( $object_id ) { $translated_object_id = $this->post_translation->element_id_in( $object_id, $this->sitepress->get_current_language() ); if ( $translated_object_id ) { $term->object_id = $translated_object_id; } elseif ( $this->sitepress->is_display_as_translated_post_type( $this->post_translation->get_type( $object_id ) ) ) { $term->object_id = $this->post_translation->element_id_in( $object_id, $this->sitepress->get_default_language() ); } } } WPML_Non_Persistent_Cache::set( $key, $term, __CLASS__ ); // Clone term object otherwise WP uses the same term object // whenever the term id matches. That leads to always have on all // references the last queried object_id. // Example: // Term "a EN" with id 1 has a translation "a FR" with id 2. // Querie contains en and fr post, language is en. // EN post with id 10 has term 1. The term object will have term_id 1 // and object_id 10. FR post with id 12 has term 2. 2 will be adjusted // to 1 as en is the current language. Instead of having two term // objects now, WP will get the reference by id and updates the // the object id. So both will have the object_id of the last queried: // [ { term_id: 1, object_id: 12 }, { term_id: 1, object_id: 12 } ] // To prevent that and get correct objects: // [ { term_id: 1, object_id: 10 }, { term_id: 1, object_id: 12 } ] // It's required to create a clone of the $term. return is_object( $term ) ? clone $term : $term; } /** * @return bool */ private function is_ajax_add_term_translation() { /* phpcs:disable WordPress.Security.NonceVerification.Missing */ $taxonomy = isset( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : false; if ( $taxonomy ) { return isset( $_POST['action'] ) && 'add-tag' === $_POST['action'] && ! empty( $_POST[ 'icl_tax_' . $taxonomy . '_language' ] ); } /* phpcs:enable WordPress.Security.NonceVerification.Missing */ return false; } } query-filtering/class-wpml-term-clauses.php 0000755 00000010461 14720342453 0015100 0 ustar 00 <?php use WPML\TaxonomyTermTranslation\Hooks as TermTranslationHooks; /** * Class WPML_Term_Clauses */ class WPML_Term_Clauses { /** @var SitePress $sitepress */ private $sitepress; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Display_As_Translated_Taxonomy_Query $display_as_translated_query */ private $display_as_translated_query; /** @var WPML_Debug_BackTrace $debug_backtrace */ private $debug_backtrace; /** @var array */ private $cache = null; /** * WPML_Term_Clauses constructor. * * @param SitePress $sitepress * @param wpdb $wpdb * @param WPML_Display_As_Translated_Taxonomy_Query $display_as_translated_query * @param WPML_Debug_BackTrace $debug_backtrace */ public function __construct( SitePress $sitepress, wpdb $wpdb, WPML_Display_As_Translated_Taxonomy_Query $display_as_translated_query, WPML_Debug_BackTrace $debug_backtrace ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; $this->display_as_translated_query = $display_as_translated_query; $this->debug_backtrace = $debug_backtrace; } /** * @param array $clauses * @param array $taxonomies * @param array $args * * @return array */ public function filter( $clauses, $taxonomies, $args ) { // Special case for when term hierarchy is cached in wp_options. if ( ! $taxonomies || ( class_exists( 'WPML\TaxonomyTermTranslation\Hooks' ) && TermTranslationHooks::shouldSkip( $args ) ) || $this->debug_backtrace->are_functions_in_call_stack( [ '_get_term_hierarchy', [ 'WPML_Term_Translation_Utils', 'synchronize_terms' ], 'wp_get_object_terms', 'get_term_by', ] ) ) { return $clauses; } $icl_taxonomies = array(); foreach ( $taxonomies as $tax ) { if ( $this->sitepress->is_translated_taxonomy( $tax ) ) { $icl_taxonomies[] = $tax; } } if ( ! $icl_taxonomies ) { return $clauses; } $icl_taxonomies = "'tax_" . join( "','tax_", esc_sql( $icl_taxonomies ) ) . "'"; $where_lang = $this->get_where_lang(); $clauses['join'] .= " LEFT JOIN {$this->wpdb->prefix}icl_translations icl_t ON icl_t.element_id = tt.term_taxonomy_id AND icl_t.element_type IN ({$icl_taxonomies})"; $clauses = $this->maybe_apply_count_adjustment( $clauses ); $clauses['where'] .= " AND ( ( icl_t.element_type IN ({$icl_taxonomies}) {$where_lang} ) OR icl_t.element_type NOT IN ({$icl_taxonomies}) OR icl_t.element_type IS NULL ) "; return $clauses; } /** * @return string|void */ private function get_where_lang() { $lang = $this->sitepress->get_current_language(); if ( 'all' === $lang ) { return ''; } else { $display_as_translated_snippet = $this->get_display_as_translated_snippet( $lang, $this->sitepress->get_default_language() ); return $this->wpdb->prepare( " AND ( icl_t.language_code = %s OR {$display_as_translated_snippet} ) ", $lang ); } } /** * @param array $clauses * * @return array */ private function maybe_apply_count_adjustment( $clauses ) { if ( $this->should_apply_display_as_translated_adjustments() ) { return $this->display_as_translated_query->update_count( $clauses, $this->sitepress->get_default_language() ); } return $clauses; } /** * @param string $current_language * @param string $fallback_language * * @return string */ private function get_display_as_translated_snippet( $current_language, $fallback_language ) { if ( $this->should_apply_display_as_translated_adjustments() ) { return $this->display_as_translated_query->get_language_snippet( $current_language, $fallback_language, $this->get_display_as_translated_taxonomies() ); } return '0'; } /** * @return bool */ private function should_apply_display_as_translated_adjustments() { return $this->get_display_as_translated_taxonomies() && ( ! is_admin() || WPML_Ajax::is_frontend_ajax_request() ); } /** * @return array */ private function get_display_as_translated_taxonomies() { if ( $this->cache === null ) { $this->cache = $this->sitepress->get_display_as_translated_taxonomies(); } return $this->cache; } } query-filtering/wpml-query-filter.class.php 0000755 00000035357 14720342453 0015140 0 ustar 00 <?php /** * Class WPML_Query_Filter * * @package wpml-core * @subpackage post-translation */ class WPML_Query_Filter extends WPML_Full_Translation_API { /** @var WPML_Name_Query_Filter[] $page_name_filter */ private $name_filter = array(); /** * @param string $post_type * * @return WPML_Name_Query_Filter */ public function get_page_name_filter( $post_type = 'page' ) { if ( ! isset( $this->name_filter[ $post_type ] ) ) { $this->name_filter[ $post_type ] = $post_type === 'page' ? new WPML_Page_Name_Query_Filter( $this->sitepress, $this->post_translations, $this->wpdb ) : ( $this->sitepress->is_translated_post_type( $post_type ) ? new WPML_Name_Query_Filter_Translated( $post_type, $this->sitepress, $this->post_translations, $this->wpdb ) : new WPML_Name_Query_Filter_Untranslated( $post_type, $this->sitepress, $this->post_translations, $this->wpdb ) ); } return $this->name_filter[ $post_type ]; } /** * @return WPML_404_Guess */ public function get_404_util() { return new WPML_404_Guess( $this->wpdb, $this->sitepress, $this ); } /** * @param string $join * @param string $post_type * * @return string */ public function filter_single_type_join( $join, $post_type ) { if ( 'any' === $post_type ) { $join .= $this->any_post_type_join(); } elseif ( $this->sitepress->is_translated_post_type ( $post_type ) ) { $join .= $this->any_post_type_join( false ); } return $join; } /** * Filters comment queries so that only comments in the current language are displayed for translated post types * * @param string[] $clauses * @param WP_Comment_Query $obj * * @return string[] */ public function comments_clauses_filter( $clauses, $obj ) { if ( $this->is_comment_query_filtered ( $obj ) && ( $current_language = $this->sitepress->get_current_language () ) !== 'all' ) { $join_part = $this->get_comment_query_join ( $obj ); if ( strstr( $clauses['join'], "JOIN {$this->wpdb->posts}" ) !== false ) { $join_part = ' AND '; } $clauses[ 'join' ] .= " JOIN {$this->wpdb->prefix}icl_translations icltr2 ON icltr2.element_id = {$this->wpdb->comments}.comment_post_ID {$join_part} {$this->wpdb->posts}.ID = icltr2.element_id AND CONCAT('post_', {$this->wpdb->posts}.post_type) = icltr2.element_type LEFT JOIN {$this->wpdb->prefix}icl_translations icltr_comment ON icltr_comment.element_id = {$this->wpdb->comments}.comment_ID AND icltr_comment.element_type = 'comment' "; $clauses[ 'where' ] .= " " . $this->wpdb->prepare ( " AND icltr2.language_code = %s AND (icltr_comment.language_code IS NULL OR icltr_comment.language_code = icltr2.language_code ) ", $current_language ); } return $clauses; } /** * @param string $join * @param WP_Query $query * * @return string */ public function posts_join_filter( $join, $query ) { global $pagenow; if ( ! $this->is_join_filter_active( $query, $pagenow ) ) { return $join; } $post_type = $this->determine_post_type( 'posts_join' ); $post_type = $post_type ? $post_type : ( $query->is_tax() ? $this->get_tax_query_posttype( $query ) : 'post' ); $post_type = $query->is_posts_page ? 'post' : $post_type; if ( is_array( $post_type ) && $this->has_translated_type( $post_type ) === true ) { $join .= $this->any_post_type_join(); } elseif ( $post_type ) { if ( is_string( $post_type ) ) { $join = $this->filter_single_type_join( $join, $post_type ); } } else { $taxonomy_post_types = $this->tax_post_types_from_query( $query ); $join = $this->tax_types_join( $join, $taxonomy_post_types ); } return $join; } /** * @param string $where * @param string | String[] $post_type * * @return string */ public function filter_single_type_where( $where, $post_type ) { if ( $this->posttypes_not_translated( $post_type ) === false ) { $where .= $this->specific_lang_where( $this->sitepress->get_current_language(), $this->sitepress->get_default_language() ); } return $where; } /** * @param string $where * @param WP_Query $query * * @return string */ public function posts_where_filter( $where, $query ) { if ( $query === null || $this->where_filter_active( $query ) === false ) { return $where; } $requested_id = isset( $_REQUEST['attachment_id'] ) && $_REQUEST['attachment_id'] ? $_REQUEST['attachment_id'] : false; $requested_id = isset( $_REQUEST['post_id'] ) && $_REQUEST['post_id'] ? $_REQUEST['post_id'] : $requested_id; $requested_id = (int) $requested_id; $default_language = $this->sitepress->get_default_language(); $post_language = $this->post_translations->get_element_lang_code( $requested_id ); $current_language = $requested_id && $post_language ? $post_language : $this->sitepress->get_current_language(); $condition = $current_language === 'all' ? $this->all_langs_where() : $this->specific_lang_where( $current_language, $default_language ); $where .= $condition; return $where; } /** * @param bool|false $not * @param bool|false|string $posts_alias * * @return string */ public function in_translated_types_snippet( $not = false, $posts_alias = false ) { $not = $not ? " NOT " : ""; $posts_alias = $posts_alias ? $posts_alias : $this->wpdb->posts; $post_types = $this->sitepress->get_translatable_documents( false ); if ( $post_types ) { return "{$posts_alias}.post_type {$not} IN (" . wpml_prepare_in( array_keys( $post_types ) ) . " ) "; } else { return ''; } } /** * @param bool|true $left if true the query will be filtered by a left join, allowing untranslated post types in it * simultaneous with translated ones * * @return string */ private function any_post_type_join( $left = true ) { $left = $left ? " LEFT " : ""; return $left . " JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON {$this->wpdb->posts}.ID = wpml_translations.element_id AND wpml_translations.element_type = CONCAT('post_', {$this->wpdb->posts}.post_type) "; } private function has_translated_type($core_types){ $res = false; foreach ( $core_types as $ptype ) { if ( $this->sitepress->is_translated_post_type ( $ptype ) ) { $res = true; break; } } return $res; } /** * @param WP_Query $query * @param String $pagenow * * @return bool */ private function is_join_filter_active( $query, $pagenow ) { if ( isset( $query->query['suppress_wpml_where_and_join_filter'] ) && $query->query['suppress_wpml_where_and_join_filter'] ) { return false; } $is_attachment_and_cant_be_translated = $query->is_attachment() ? $this->is_media_and_cant_be_translated( 'attachment' ) : false; return $pagenow !== 'media-upload.php' && ! $is_attachment_and_cant_be_translated && ! $this->is_queried_object_root( $query ); } /** * Checks whether the currently queried for object is the root page. * * @param WP_Query $query * * @return bool */ private function is_queried_object_root( $query ) { $url_settings = $this->sitepress->get_setting( 'urls' ); $root_id = ! empty( $url_settings['root_page'] ) ? $url_settings['root_page'] : - 1; return isset( $query->queried_object ) && isset( $query->queried_object->ID ) && $query->queried_object->ID == $root_id; } /** * @param string $query_type * * @return string|false */ private function determine_post_type( $query_type ) { $debug_backtrace = $this->sitepress->get_backtrace( 0, true, false ); //Limit to a maximum level? $post_type = false; foreach ( $debug_backtrace as $o ) { if ( $o['function'] == 'apply_filters_ref_array' && $o['args'][0] === $query_type ) { $query_vars = $o['args'][1][1]->query_vars; $post_type = esc_sql( $query_vars['post_type'] ); if ( ! $post_type ) { if ( (bool) $query_vars['pagename'] ) { $post_type = 'page'; } elseif ( $query_vars['attachment'] ) { $post_type = 'attachment'; } elseif ( isset( $query_vars['p'] ) ) { $post_type = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT post_type from {$this->wpdb->posts} where ID=%d", $query_vars['p'] ) ); } } break; } } return $post_type; } /** * @param WP_Query $query * @return String[] */ private function tax_post_types_from_query($query){ if ( $query->is_tax () && $query->is_main_query () ) { $taxonomy_post_types = $this->get_tax_query_posttype($query); } else { $taxonomy_post_types = array_keys ( $this->sitepress->get_translatable_documents ( false ) ); } return $taxonomy_post_types; } private function tax_types_join( $join, $tax_post_types ) { if ( !empty( $tax_post_types ) ) { foreach ( $tax_post_types as $k => $v ) { $tax_post_types[ $k ] = 'post_' . $v; } $join .= $this->any_post_type_join() . " AND wpml_translations.element_type IN (" . wpml_prepare_in ( $tax_post_types ) . ") "; } return $join; } /** * @param WP_Query $query * * @return string[] */ private function get_tax_query_posttype( $query ) { return WPML_WP_Taxonomy::get_linked_post_types( $query->get( 'taxonomy' ) ); } /** * @param string|string[] $post_types * * @return bool true if non of the input post types are translatable */ private function posttypes_not_translated( $post_types ) { $post_types = is_array( $post_types ) ? $post_types : array( $post_types ); $none_translated = true; foreach ( $post_types as $ptype ) { if ( $this->sitepress->is_translated_post_type( $ptype ) ) { $none_translated = false; break; } } return $none_translated; } private function all_langs_where() { return ' AND wpml_translations.language_code IN (' . wpml_prepare_in( array_keys( $this->sitepress->get_active_languages() ) ) . ') '; } private function specific_lang_where( $current_language, $fallback_language ) { return $this->wpdb->prepare ( " AND ( ( ( wpml_translations.language_code = %s OR " . $this->display_as_translated_snippet( $current_language, $fallback_language ) . " ) AND " . $this->in_translated_types_snippet () . " ) OR " . $this->in_translated_types_snippet ( true ) . " )", $current_language ); } private function display_as_translated_snippet( $current_language, $fallback_language ) { $content_types = null; $skip_content_check = true; /** * Filter wpml_should_force_display_as_translated_snippet. * * Force the "display as translated" mode for all post types. Implemented for * Toolset compatibility. */ if ( ! apply_filters( 'wpml_should_force_display_as_translated_snippet', false ) ) { $post_types = $this->sitepress->get_display_as_translated_documents(); if ( ! $post_types || ! apply_filters( 'wpml_should_use_display_as_translated_snippet', ! is_admin(), $post_types ) ) { return '0'; } $content_types = array_keys( $post_types ); $skip_content_check = false; } $display_as_translated_query = new WPML_Display_As_Translated_Posts_Query( $this->wpdb ); return $display_as_translated_query->get_language_snippet( $current_language, $fallback_language, $content_types, $skip_content_check ); } /** * @param WP_Query $query * * @return bool */ private function where_filter_active( $query ) { global $pagenow; if ( ! $this->is_join_filter_active( $query, $pagenow ) ) { return false; } $active = $this->is_queried_object_root( $query ) === false; if ( $active === true ) { $post_type = $this->determine_post_type( 'posts_where' ); $post_type = empty( $post_type ) && $query->is_tax() ? $this->get_tax_query_posttype( $query ) : $post_type; $post_type = $query->is_posts_page ? 'post' : $post_type; $post_type = $post_type ? $post_type : ( $query->is_attachment() ? 'attachment' : 'post' ); $active = $pagenow !== 'media-upload.php' && $post_type && ( $post_type === 'any' || $this->posttypes_not_translated( $post_type ) === false ) && ! $this->is_media_and_cant_be_translated( $post_type ); } return $active; } private function is_media_and_cant_be_translated( $post_type ) { $is_attachment_and_cant_be_translated = ( $post_type === 'attachment' && ! $this->sitepress->is_translated_post_type( 'attachment' ) ); return $is_attachment_and_cant_be_translated; } /** * @param \WP_Comment_Query $comment_query * * @return int|null */ private function get_post_id_from_comment_query( WP_Comment_Query $comment_query ) { if ( isset( $comment_query->query_vars['post_id'] ) ) { return $comment_query->query_vars['post_id']; } elseif ( isset( $comment_query->query_vars['post_ID'] ) ) { return $comment_query->query_vars['post_ID']; } return null; } /** * Checks if the comment query applies to posts that are of a translated type. * * @param WP_Comment_Query $comment_query * * @return bool */ private function is_comment_query_filtered( $comment_query ) { $filtered = true; $post_id = $this->get_post_id_from_comment_query( $comment_query ); if ( $post_id ) { $post = get_post( $post_id ); if ( (bool) $post === true && ! $this->sitepress->is_translated_post_type( $post->post_type ) ) { $filtered = false; } } /** * Override when a WP_Comment_Query should be filtered by language. * * @param bool $filtered * @param int $post_id * @param WP_Comment_Query $comment_query * * @return bool */ return apply_filters( 'wpml_is_comment_query_filtered', $filtered, $post_id, $comment_query ); } /** * Adds a join with the posts table to the query only if necessary because the comment query is not filtered * by post variables * * @param WP_Comment_Query $comment_query * @return string */ private function get_comment_query_join( $comment_query ){ $posts_params = array( 'post_author', 'post_name', 'post_parent', 'post_status', 'post_type', 'post_author__not_in', 'post_author__in' ); $query_vars = isset( $comment_query->query_vars ) ? array_filter ( $comment_query->query_vars ) : array(); $plucked = wp_array_slice_assoc ( $query_vars, $posts_params ); $post_fields = array_filter ( $plucked ); $posts_query = !empty( $post_fields ); $join_part = $posts_query ? " AND " : "JOIN {$this->wpdb->posts} ON "; return $join_part; } /** * @param int $requested_id * * @return bool|mixed|null|string */ private function get_current_language( $requested_id ) { $current_language = null; if ( $requested_id ) { $current_language = $this->post_translations->get_element_lang_code( $requested_id ); } if ( ! $current_language ) { $current_language = $this->sitepress->get_current_language(); } if ( ! $current_language ) { $current_language = $this->sitepress->get_default_language(); } return $current_language; } } query-filtering/class-wpml-display-as-translated-tax-query.php 0000755 00000012014 14720342453 0020632 0 ustar 00 <?php use WPML\Collect\Support\Collection; class WPML_Display_As_Translated_Tax_Query implements IWPML_Action { // Regex to find the term query. // eg. term_taxonomy_id IN (8) // We then add the fallback term to the query // eg. term_taxonomy_id IN (8,9) const TERM_REGEX = '/term_taxonomy_id\s+(IN|in)\s*\(([^\)]+)\)/'; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Term_Translation $term_translation */ private $term_translation; public function __construct( SitePress $sitepress, WPML_Term_Translation $term_translation ) { $this->sitepress = $sitepress; $this->term_translation = $term_translation; } public function add_hooks() { add_filter( 'posts_where', array( $this, 'posts_where_filter' ), 10, 2 ); } /** * @param string $where * @param WP_Query $q * * @return string */ public function posts_where_filter( $where, WP_Query $q ) { if ( $this->is_not_the_default_language() && $this->is_taxonomy_archive( $q ) ) { $post_types = $this->get_linked_post_types( $q ); if ( $this->is_display_as_translated_mode( $post_types ) ) { $terms = $this->find_terms( $where ); $fallback_terms = $this->get_fallback_terms( $terms ); $where = $this->add_fallback_terms_to_where_clause( $where, $fallback_terms, $q ); } } return $where; } /** * @return bool */ private function is_not_the_default_language() { return $this->sitepress->get_default_language() !== $this->sitepress->get_current_language(); } /** * @param WP_Query $q * * @return bool */ private function is_taxonomy_archive( WP_Query $q ) { return $q->is_archive() && ( $q->is_category() || $q->is_tax() || $q->is_tag() ); } /** * @param WP_Query $q * * @return array */ private function get_linked_post_types( WP_Query $q ) { $post_types = array(); foreach ( $q->tax_query->queries as $tax_query ) { if ( isset( $tax_query['taxonomy'] ) ) { $post_types = array_unique( array_merge( $post_types, WPML_WP_Taxonomy::get_linked_post_types( $tax_query['taxonomy'] ) ) ); } } return $post_types; } /** * @param array $post_types * * @return bool */ private function is_display_as_translated_mode( $post_types ) { foreach ( $post_types as $post_type ) { if ( $this->sitepress->is_display_as_translated_post_type( $post_type ) ) { return true; } } return false; } /** * @param string $where * * @return array */ private function find_terms( $where ) { $terms = array(); if ( preg_match_all( self::TERM_REGEX, $where, $matches ) ) { foreach ( $matches[2] as $terms_string ) { $terms_parts = explode( ',', $terms_string ); $terms = array_unique( array_merge( $terms, $terms_parts ) ); } } return $terms; } /** * @param array $terms * * @return array */ private function get_fallback_terms( $terms ) { $default_language = $this->sitepress->get_default_language(); $fallback_terms = array(); foreach ( $terms as $term ) { $translations = $this->term_translation->get_element_translations( (int) $term ); if ( isset( $translations[ $default_language ] ) && ! in_array( $translations[ $default_language ], $fallback_terms ) ) { $fallback_terms[ $term ] = $translations[ $default_language ]; } } return $fallback_terms; } /** * @param string $where * @param array $fallback_terms * @param WP_Query $q * * @return string */ private function add_fallback_terms_to_where_clause( $where, $fallback_terms, WP_Query $q ) { if ( preg_match_all( self::TERM_REGEX, $where, $matches ) ) { foreach ( $matches[2] as $index => $terms_string ) { $new_terms_string = $this->add_fallback_terms( $terms_string, $fallback_terms, $q ); $original_block = $matches[0][ $index ]; $new_block = str_replace( '(' . $terms_string . ')', '(' . $new_terms_string . ')', $original_block ); $where = str_replace( $original_block, $new_block, $where ); } } return $where; } /** * @param string $terms_string * @param array $fallback_terms * @param WP_Query $q * * @return string */ private function add_fallback_terms( $terms_string, $fallback_terms, WP_Query $q ) { $mergeFallbackTerms = function ( $term ) use ( $fallback_terms ) { return isset( $fallback_terms[ $term ] ) ? [ $term, $fallback_terms[ $term ] ] : $term; }; $queriedObject = $q->get_queried_object(); $taxonomy = isset( $queriedObject->taxonomy ) ? $queriedObject->taxonomy : null; if ( $taxonomy && $this->include_term_children( $q ) ) { $mergeChildren = function ( $term ) use ( $taxonomy ) { return [ $term, get_term_children( $term, $taxonomy ) ]; }; } else { $mergeChildren = \WPML\FP\Fns::identity(); } return wpml_collect( explode( ',', $terms_string ) ) ->map( $mergeFallbackTerms ) ->flatten() ->map( $mergeChildren ) ->flatten() ->unique() ->implode( ',' ); } private function include_term_children( WP_Query $q ) { return (bool) \WPML\FP\Obj::path( [ 'tax_query', 'queries', 0, 'include_children' ], $q ); } } query-filtering/class-wpml-display-as-translated-query.php 0000755 00000004602 14720342453 0020044 0 ustar 00 <?php abstract class WPML_Display_As_Translated_Query { /** @var wpdb $wpdb */ protected $wpdb; /** @var string $icl_translation_table_alias */ protected $icl_translation_table_alias; /** * WPML_Display_As_Translated_Query constructor. * * @param wpdb $wpdb */ public function __construct( wpdb $wpdb, $icl_translation_table_alias = 'wpml_translations' ) { $this->wpdb = $wpdb; $this->icl_translation_table_alias = $icl_translation_table_alias; } /** * @return string */ public function get_icl_translations_table_alias() { return is_string( $this->icl_translation_table_alias ) && ! empty( $this->icl_translation_table_alias ) ? $this->icl_translation_table_alias : 'wpml_translations'; } /** * @param string $current_language * @param string $fallback_language * @param array $content_types * @param bool $skip_content_type_check Ignore $content_types if true. * * @return string */ public function get_language_snippet( $current_language, $fallback_language, $content_types, $skip_content_type_check = false ) { if ( ! $fallback_language || ( ! $skip_content_type_check && ! $content_types ) ) { return '0'; } $content_types_query = $skip_content_type_check ? '' : 'AND ' . $this->get_content_types_query( $content_types ); $sub_query_no_translation = $this->get_query_for_no_translation( $current_language ); $sub_query_translation_not_published = $this->get_query_for_translation_not_published( $current_language ); return $this->wpdb->prepare( "( {$this->icl_translation_table_alias}.language_code = %s {$content_types_query} AND ( ( {$sub_query_no_translation} ) OR ( {$sub_query_translation_not_published} ) ) )", $fallback_language ); } /** * @param string $language * * @return string */ private function get_query_for_no_translation( $language ) { return $this->wpdb->prepare( " ( SELECT COUNT(element_id) FROM {$this->wpdb->prefix}icl_translations WHERE trid = {$this->icl_translation_table_alias}.trid AND language_code = %s ) = 0 ", $language ); } /** * @param array $content_types * * @return string */ abstract protected function get_content_types_query( $content_types ); /** * @param string $language * * @return string */ abstract protected function get_query_for_translation_not_published( $language ); } translation-dashboard/EncodedFieldsValidation/Validator.php 0000755 00000014514 14720342453 0020204 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard\EncodedFieldsValidation; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; use WPML\LIB\WP\Post; use WPML\TM\TranslationDashboard\SentContentMessages; use function WPML\FP\invoke; use function WPML\FP\spreadArgs; class Validator { /** @var \WPML_Encoding_Validation */ private $encoding_validation; /** @var \WPML_Element_Translation_Package */ private $package_helper; /** @var SentContentMessages */ private $sentContentMessages; /** @var FieldTitle */ private $fieldTitle; /** @var \WPML_PB_Factory */ private $pbFactory; public function __construct( \WPML_Encoding_Validation $encoding_validation, \WPML_Element_Translation_Package $package_helper, SentContentMessages $sentContentMessages, FieldTitle $fieldTitle, \WPML_PB_Factory $pbFactory ) { $this->encoding_validation = $encoding_validation; $this->package_helper = $package_helper; $this->sentContentMessages = $sentContentMessages; $this->fieldTitle = $fieldTitle; $this->pbFactory = $pbFactory; } /** * $data may contain two keys: 'post' and 'package'. Each of them has the same shape: * [ * idOfElement1 => [ * checked: 1, * type: 'post', * ], * idOfElement2 => [ * type: 'post', * ], * ] and so on. * * If element has "checked" field, it means it has been selected for translation. * Therefore, if we want to filter it out from translation, we have to remove that field. * * The "validateTMDashboardInput" performs similar check for both "post" and "package" lists, * checks if their elements contains encoded fields and removes them from the list. * * @param array $data * * @return array */ public function validateTMDashboardInput( $data ) { $postInvalidElements = []; if ( isset( $data['post'] ) ) { $postInvalidElements = $this->findPostsWithEncodedFields( $this->getCheckedIds( 'post', $data ) ); $data = $this->excludeInvalidElements( 'post', $data, Lst::pluck( 'elementId', $postInvalidElements ) ); } $packageInvalidElements = []; if ( isset( $data['package'] ) ) { $packageInvalidElements = $this->findPackagesWithEncodedFields( $this->getCheckedIds( 'package', $data ) ); $data = $this->excludeInvalidElements( 'package', $data, Lst::pluck( 'elementId', $packageInvalidElements ) ); } $invalidElements = array_merge( $postInvalidElements, $packageInvalidElements ); if ( count( $invalidElements ) ) { // Display the error message only if there are invalid elements. $this->sentContentMessages->postsWithEncodedFieldsHasBeenSkipped( $invalidElements ); } return $data; } /** * Get list of ids of selected posts/packages * * @param 'post'|'package' $type * @param array $data * * @return [] */ private function getCheckedIds( $type, $data ) { return \wpml_collect( Obj::propOr( [], $type, $data ) ) ->filter( Obj::prop( 'checked' ) ) ->keys() ->toArray(); } /** * It removes "checked" property from the elements that have "encoded" fields. It means they will not be sent to translation. * * @param 'post'|'package' $type * @param array $data * @param int[] $ids * * @return array */ private function excludeInvalidElements( $type, $data, $invalidElementIds ) { return (array) Obj::over( Obj::lensProp( $type ), function ( $elements ) use ( $invalidElementIds ) { return \wpml_collect( $elements ) ->map( function ( $element, $elementId ) use ( $invalidElementIds ) { if ( Lst::includes( $elementId, $invalidElementIds ) ) { return Obj::removeProp( 'checked', $element ); } return $element; } ) ->toArray(); }, $data ); } /** * @param int[] $postIds * * @return ErrorEntry[] */ private function findPostsWithEncodedFields( $postIds ) { $appendPackage = function ( \WP_Post $post ) { $package = $this->package_helper->create_translation_package( $post->ID, true ); return [ $post, $package ]; }; $isFieldEncoded = function ( $field ) { $decodedFieldData = base64_decode( $field['data'] ); return array_key_exists( 'format', $field ) && 'base64' === $field['format'] && $this->encoding_validation->is_base64_with_100_chars_or_more( $decodedFieldData ); /** * @todo : we should handle not to block the whole job from being translated but instead we should exclude the problematic field from the job and send it to be translated without that field. * @todo check Youtrack ticket * @see : https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1694 */ }; $getInvalidFieldData = function ( $field, $slug ) { return [ 'title' => $this->fieldTitle->get( $slug ), 'content' => base64_decode( $field['data'] ), ]; }; $tryToGetError = function ( \WP_Post $post, $package ) use ( $isFieldEncoded, $getInvalidFieldData ) { $invalidFields = \wpml_collect( $package['contents'] ) ->filter( $isFieldEncoded ) ->map( $getInvalidFieldData ) ->values() ->toArray(); if ( $invalidFields ) { return new ErrorEntry( $post->ID, $package['title'], $invalidFields ); } return null; }; return \wpml_collect( $postIds ) ->map( Post::get() ) ->filter() ->map( $appendPackage ) ->map( spreadArgs( $tryToGetError ) ) ->filter() ->toArray(); } /** * @param int[] $packageIds * * @return ErrorEntry[] */ private function findPackagesWithEncodedFields( $packageIds ) { $getInvalidFieldData = function ( $field, $slug ) { return [ 'title' => $this->fieldTitle->get( $slug ), 'content' => $field, ]; }; /** * @param \WPML_Package $package * * @return ErrorEntry|null */ $tryToGetError = function ( $package ) use ( $getInvalidFieldData ) { $invalidFields = \wpml_collect( Obj::propOr( [], 'string_data', $package ) ) ->filter( [ $this->encoding_validation, 'is_base64_with_100_chars_or_more' ] ) ->map( $getInvalidFieldData ) ->values() ->toArray(); if ( $invalidFields ) { return new ErrorEntry( $package->ID, $package->title, $invalidFields ); } return null; }; return \wpml_collect( $packageIds ) ->map( function ( $packageId ) { return $this->pbFactory->get_wpml_package( $packageId ); } ) ->filter( Obj::prop( 'ID' ) ) ->map( $tryToGetError ) ->filter() ->toArray(); } } translation-dashboard/EncodedFieldsValidation/FieldTitle.php 0000755 00000000461 14720342453 0020300 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard\EncodedFieldsValidation; class FieldTitle { /** * @param string $slug * * @return string */ public function get( $slug ) { $string_slug = new \WPML_TM_Page_Builders_Field_Wrapper( $slug ); return (string) $string_slug->get_string_title(); } } translation-dashboard/EncodedFieldsValidation/ErrorEntry.php 0000755 00000001114 14720342453 0020362 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard\EncodedFieldsValidation; /** * @template field of array{title:string, content:string} */ class ErrorEntry { /** @var int ID of post or package */ public $elementId; /** @var string */ public $elementTitle; /** @var field[] */ public $fields; /** * @param int $elementId * @param string $elementTitle * @param field[] $fields */ public function __construct( $elementId, $elementTitle, $fields ) { $this->elementId = (int) $elementId; $this->elementTitle = $elementTitle; $this->fields = $fields; } } translation-dashboard/Endpoints/DisplayNeedSyncMessage.php 0000755 00000000732 14720342453 0020057 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard\Endpoints; use WPML\Collect\Support\Collection; use WPML\FP\Either; /** * It calls `new_duplicated_terms_filter` function which displays the admin notice informing about term taxonomies which have to be synced. */ class DisplayNeedSyncMessage { public function run( Collection $data ) { $postIds = $data->get( 'postIds', [] ); do_action( 'wpml_new_duplicated_terms', $postIds ); return Either::of( $postIds ); } } translation-dashboard/Endpoints/Duplicate.php 0000755 00000001264 14720342453 0015427 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard\Endpoints; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use function WPML\FP\spreadArgs; /** * It duplicates posts into specified languages. */ class Duplicate { public function run( Collection $data ) { global $sitepress; $postIds = $data->get( 'postIds' ); $languages = $data->get( 'languages' ); $pairs = Lst::xprod( $postIds, $languages ); $result = Fns::map( spreadArgs( function ( $postId, $languageCode ) use ( $sitepress ) { return [ $postId, $languageCode, $sitepress->make_duplicate( $postId, $languageCode ) ]; } ), $pairs ); return Either::of( $result ); } } translation-dashboard/SentContentMessages.php 0000755 00000007110 14720342453 0015502 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard; use WPML\TM\TranslationDashboard\EncodedFieldsValidation\ErrorEntry; use WPML\UIPage; class SentContentMessages { /** * @var null|array{message: string, description: string, type: string} */ private static $confirmation = null; /** @var ErrorEntry[]|null */ private static $encodedFieldErrorEntries = null; public function duplicate() { self::$confirmation = [ 'message' => '', 'description' => __( 'You successfully duplicated your content.', 'sitepress-multilingual-cms' ), 'type' => 'success', ]; } public function duplicateAndAutomatic() { self::$confirmation = [ 'message' => __( 'You successfully duplicated your content, and WPML is handling your translations for you.', 'sitepress-multilingual-cms' ), 'description' => __( 'Your translations will be ready soon. You can see the status of your automatic translations below or in the status bar at the top of WordPress admin.', 'sitepress-multilingual-cms' ), 'type' => 'success', 'automatic' => true, ]; } public function duplicateAndMyself() { self::$confirmation = [ 'message' => __( 'You successfully duplicated your content. What’s next for your translations?', 'sitepress-multilingual-cms' ), 'description' => sprintf( __( 'Go to the <a href="%s">Translations Queue</a> to translate it.', 'sitepress-multilingual-cms' ), UIPage::getTranslationQueue() ), 'type' => 'info', ]; } public function duplicateAndBasket() { self::$confirmation = [ 'message' => __( 'You successfully duplicated your content. What’s next for your translations?', 'sitepress-multilingual-cms' ), 'description' => sprintf( __( 'Go to the <a href="%s">Translation Basket</a> to decide who should translate your content', 'sitepress-multilingual-cms' ), UIPage::getTMBasket() ), 'type' => 'info', ]; } public function automatic() { self::$confirmation = [ 'message' => __( 'WPML is translating your content', 'sitepress-multilingual-cms' ), 'description' => __( 'Your translations will be ready soon. You can see the status of your automatic translations below or in the status bar at the top of WordPress admin.', 'sitepress-multilingual-cms' ), 'type' => 'success', 'automatic' => true, ]; } public function myself() { self::$confirmation = [ 'message' => __( 'You’ve queued up your content for translation. What’s next?', 'sitepress-multilingual-cms' ), 'description' => sprintf( __( 'Go to the <a href="%s">Translations Queue</a> to translate it.', 'sitepress-multilingual-cms' ), UIPage::getTranslationQueue() ), 'type' => 'info', ]; } public function basket() { self::$confirmation = [ 'message' => __( 'You added your translations to the basket. What’s next?', 'sitepress-multilingual-cms' ), 'description' => sprintf( __( 'Go to the <a href="%s">Translation Basket</a> to decide who should translate your content', 'sitepress-multilingual-cms' ), UIPage::getTMBasket() ), 'type' => 'info', ]; } /** * @param ErrorEntry[] $invalidElements */ public function postsWithEncodedFieldsHasBeenSkipped( array $invalidElements ) { self::$encodedFieldErrorEntries = $invalidElements; } /** * @return array{confirmMessage: null|array{message: string, description: string, type: string}, encodedFieldErrorEntries: null|ErrorEntry[]} */ public function get() { return [ 'confirmation' => self::$confirmation, 'encodedFieldErrorEntries' => self::$encodedFieldErrorEntries, ]; } } translation-dashboard/class-wpml-tm-parent-filter-ajax-factory.php 0000755 00000000362 14720342453 0021410 0 ustar 00 <?php class WPML_TM_Parent_Filter_Ajax_Factory implements IWPML_AJAX_Action_Loader { public function create() { global $sitepress, $wp_post_types; return new WPML_TM_Parent_Filter_Ajax( $sitepress, array_keys( $wp_post_types ) ); } } translation-dashboard/class-wpml-tm-parent-filter-ajax.php 0000755 00000003302 14720342453 0017740 0 ustar 00 <?php class WPML_TM_Parent_Filter_Ajax implements IWPML_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var array $wp_post_types */ private $wp_post_types; public function __construct( SitePress $sitepress, array $wp_post_types ) { $this->sitepress = $sitepress; $this->wp_post_types = $wp_post_types; } public function add_hooks() { add_action( 'wp_ajax_icl_tm_parent_filter', array( $this, 'get_parents_dropdown' ) ); } public function get_parents_dropdown() { $current_language = $this->sitepress->get_current_language(); $request_post_type = filter_var( $_POST['type'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $request_post_lang = filter_var( $_POST['from_lang'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $request_post_parent_id = filter_var( $_POST['parent_id'], FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $this->sitepress->switch_lang( $request_post_lang ); if ( in_array( $request_post_type, $this->wp_post_types ) ) { $html = wp_dropdown_pages( array( 'echo' => 0, 'name' => 'filter[parent_id]', 'selected' => $request_post_parent_id ?: 0, ) ); } else { $html = wp_dropdown_categories( array( 'echo' => 0, 'orderby' => 'name', 'name' => 'filter[parent_id]', 'selected' => $request_post_parent_id ?: 0, 'taxonomy' => $request_post_type, 'hide_if_empty' => true, ) ); } $this->sitepress->switch_lang( $current_language ); if ( ! $html ) { $html = esc_html__( 'None found', 'wpml-translation-management' ); } wp_send_json_success( array( 'html' => $html ) ); } } translation-dashboard/FiltersStorage.php 0000755 00000001071 14720342453 0014503 0 ustar 00 <?php namespace WPML\TM\TranslationDashboard; use WPML\API\Sanitize; use WPML\Element\API\Languages; use WPML\FP\Obj; class FiltersStorage { /** * @return array */ public static function get() { $result = []; $dashboard_filter = Sanitize::stringProp( 'wp-translation_dashboard_filter', $_COOKIE ); if ( $dashboard_filter ) { parse_str( $dashboard_filter, $result ); } return $result; } /** * @return string */ public static function getFromLanguage() { return Obj::propOr( Languages::getCurrentCode(), 'from_lang', self::get() ); } } post-translation/class-wpml-pre-option-page.php 0000755 00000006003 14720342453 0015672 0 ustar 00 <?php class WPML_Pre_Option_Page extends WPML_WPDB_And_SP_User { const CACHE_GROUP = 'wpml_pre_option_page'; private $switched; private $lang; public function __construct( &$wpdb, &$sitepress, $switched, $lang ) { parent::__construct( $wpdb, $sitepress ); $this->switched = $switched; $this->lang = $lang; } public function get( $type, $from_language = null ) { $cache_key = $type; $cache_found = false; $cache = new WPML_WP_Cache( self::CACHE_GROUP ); $results = $cache->get( $cache_key, $cache_found ); if ( ( ( ! $cache_found || ! isset ( $results[ $type ] ) ) && ! $this->switched ) || ( $this->switched && $this->sitepress->get_setting( 'setup_complete' ) ) ) { $results = []; $results[ $type ] = []; // Fetch for all languages and cache them. $values = $this->wpdb->get_results( $this->wpdb->prepare( " SELECT element_id, language_code FROM {$this->wpdb->prefix}icl_translations WHERE trid = (SELECT trid FROM {$this->wpdb->prefix}icl_translations WHERE element_type = 'post_page' AND element_id = (SELECT option_value FROM {$this->wpdb->options} WHERE option_name=%s LIMIT 1)) ", $type ) ); if ( is_array( $values ) && count( $values ) ) { foreach ( $values as $lang_result ) { $results [ $type ] [ $lang_result->language_code ] = $lang_result->element_id; } } $cache->set( $cache_key, $results ); } $target_language = $from_language ? $from_language : $this->lang; return isset( $results[ $type ][ $target_language ] ) ? $results[ $type ][ $target_language ] : false; } public static function clear_cache() { $cache = new WPML_WP_Cache( self::CACHE_GROUP ); $cache->flush_group_cache(); } function fix_trashed_front_or_posts_page_settings( $post_id ) { if ( 'page' !== get_post_type( $post_id ) ) { return; } $post_id = (int) $post_id; $page_on_front_current = (int) $this->get( 'page_on_front' ); $page_for_posts_current = (int) $this->get( 'page_for_posts' ); $page_on_front_default = (int) $this->get( 'page_on_front', $this->sitepress->get_default_language() ); $page_for_posts_default = (int) $this->get( 'page_for_posts', $this->sitepress->get_default_language() ); if ( $page_on_front_current === $post_id && $page_on_front_current !== $page_on_front_default ) { remove_filter( 'pre_option_page_on_front', array( $this->sitepress, 'pre_option_page_on_front' ) ); update_option( 'page_on_front', $page_on_front_default ); add_filter( 'pre_option_page_on_front', array( $this->sitepress, 'pre_option_page_on_front' ) ); } if ( $page_for_posts_current === $post_id && $page_for_posts_current !== $page_for_posts_default ) { remove_filter( 'pre_option_page_for_posts', array( $this->sitepress, 'pre_option_page_for_posts' ) ); update_option( 'page_for_posts', $page_for_posts_default ); add_filter( 'pre_option_page_for_posts', array( $this->sitepress, 'pre_option_page_for_posts' ) ); } } } post-translation/class-wpml-post-edit-terms-hooks-factory.php 0000755 00000000746 14720342453 0020522 0 ustar 00 <?php class WPML_Post_Edit_Terms_Hooks_Factory implements IWPML_Backend_Action_Loader { public function create() { global $sitepress, $wpdb; if ( $this->is_saving_post_data_with_terms() ) { return new WPML_Post_Edit_Terms_Hooks( $sitepress, $wpdb ); } return null; } private function is_saving_post_data_with_terms() { return isset( $_POST['action'] ) && in_array( $_POST['action'], array( 'editpost', 'inline-save' ) ) && ! empty( $_POST['tax_input'] ); } } post-translation/endpoints/UntranslatedCount.php 0000755 00000005106 14720342453 0016267 0 ustar 00 <?php namespace WPML\Posts; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Str; use WPML\LIB\WP\PostType; class UntranslatedCount { public function run( Collection $data, \wpdb $wpdb ) { $postTypes = $data->get( 'postTypes', PostTypes::getAutomaticTranslatable() ); $postIn = wpml_prepare_in( Fns::map( Str::concat( 'post_' ), $postTypes ) ); $statuses = wpml_prepare_in( [ ICL_TM_NOT_TRANSLATED, ICL_TM_ATE_CANCELLED ] ); $query = " SELECT translations.post_type, COUNT(translations.ID) FROM ( SELECT RIGHT(element_type, LENGTH(element_type) - 5) as post_type, posts.ID FROM {$wpdb->prefix}icl_translations INNER JOIN {$wpdb->prefix}posts posts ON element_id = ID LEFT JOIN {$wpdb->postmeta} postmeta ON postmeta.post_id = posts.ID AND postmeta.meta_key = %s WHERE element_type IN ({$postIn}) AND post_status = 'publish' AND source_language_code IS NULL AND language_code = %s AND ( SELECT COUNT(trid) FROM {$wpdb->prefix}icl_translations icl_translations_inner INNER JOIN {$wpdb->prefix}icl_translation_status icl_translations_status on icl_translations_inner.translation_id = icl_translations_status.translation_id WHERE icl_translations_inner.trid = {$wpdb->prefix}icl_translations.trid AND icl_translations_status.status NOT IN ({$statuses}) AND icl_translations_status.needs_update != 1 ) < %d AND ( postmeta.meta_value IS NULL OR postmeta.meta_value = 'no' ) ) as translations GROUP BY translations.post_type; "; $untranslatedPosts = $wpdb->get_results( $wpdb->prepare( $query, \WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, Languages::getDefaultCode(), Lst::length( Languages::getSecondaries() ) ), ARRAY_N ); // $setPluralPostName :: [ 'post' => '1' ] -> [ 'Posts' => 1 ] $setPluralPostName = function ( $postType ) { return [ PostType::getPluralName( $postType[0] )->getOrElse( $postType[0] ) => (int) $postType[1] ]; }; // $setCountToZero :: 'post' -> [ 'post' => 0 ] $setCountToZero = Lst::makePair( Fns::__, 0 ); return wpml_collect( $postTypes ) ->map( $setCountToZero ) ->merge( $untranslatedPosts ) ->mapWithKeys( $setPluralPostName ) ->toArray(); } } post-translation/endpoints/CountPerPostType.php 0000755 00000002456 14720342453 0016066 0 ustar 00 <?php namespace WPML\Posts; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\LIB\WP\PostType; class CountPerPostType { public function run( Collection $data, \wpdb $wpdb ) { $postTypes = $data->get( 'postTypes', PostTypes::getAutomaticTranslatable() ); $postIn = wpml_prepare_in( $postTypes ); $query = " SELECT posts.post_type, COUNT(posts.ID) FROM {$wpdb->posts} posts INNER JOIN {$wpdb->prefix}icl_translations translations ON translations.element_id = posts.ID AND translations.element_type = CONCAT('post_', posts.post_type) WHERE posts.post_type IN ({$postIn}) AND posts.post_status = %s AND translations.source_language_code IS NULL GROUP BY posts.post_type "; $postCountPerType = $wpdb->get_results( $wpdb->prepare( $query, 'publish' ), ARRAY_N ); // $setPluralPostName :: [ 'post' => '1' ] -> [ 'Posts' => 1 ] $setPluralPostName = function ( $postType ) { return [ PostType::getPluralName( $postType[0] )->getOrElse( $postType[0] ) => (int) $postType[1] ]; }; // $setCountToZero :: 'post' -> [ 'post' => 0 ] $setCountToZero = Lst::makePair( Fns::__, 0 ); return wpml_collect( $postTypes ) ->map( $setCountToZero ) ->merge( $postCountPerType ) ->mapWithKeys( $setPluralPostName ) ->toArray(); } } post-translation/endpoints/DeleteTranslatedContentOfLanguages.php 0000755 00000001225 14720342453 0021503 0 ustar 00 <?php namespace WPML\Posts; use WPML\Collect\Support\Collection; use WPML\DatabaseQueries\TranslatedPosts; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use function WPML\FP\partialRight; class DeleteTranslatedContentOfLanguages { public function run( Collection $data ) { $deleteTranslatedContent = Fns::unary( partialRight( 'wp_delete_post', true ) ); return Either::of( $data->get( 'language_code' ) ) ->filter( Lst::length() ) ->map( [ TranslatedPosts::class, 'getIdsForLangs' ] ) ->map( Fns::map( $deleteTranslatedContent ) ) ->coalesce( Fns::identity(), Fns::identity() ); } } post-translation/class-wpml-post-status.php 0000755 00000014033 14720342453 0015174 0 ustar 00 <?php class WPML_Post_Status extends WPML_WPDB_User { private $needs_update = array(); private $status = array(); private $preload_done = false; private $wp_api; public function __construct( &$wpdb, $wp_api ) { parent::__construct( $wpdb ); $this->wp_api = $wp_api; } public function needs_update( $post_id ) { global $wpml_post_translations, $wpml_cache_factory; if ( !isset( $this->needs_update[ $post_id ] ) ) { $this->maybe_preload(); $trid = $wpml_post_translations->get_element_trid ( $post_id ); $cache = $wpml_cache_factory->get( 'WPML_Post_Status::needs_update' ); $found = false; $results = $cache->get( $trid, $found ); if ( ! $found ) { $results = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT ts.needs_update, it.language_code FROM {$this->wpdb->prefix}icl_translation_status ts JOIN {$this->wpdb->prefix}icl_translations it ON it.translation_id = ts.translation_id WHERE it.trid = %d", $trid ) ); $cache->set( $trid, $results ); } $language = $wpml_post_translations->get_element_lang_code ( $post_id ); $needs_update = false; foreach( $results as $result ) { if ( $result->language_code == $language ) { $needs_update = $result->needs_update; break; } } $this->needs_update [ $post_id ] = $needs_update; } return $this->needs_update [ $post_id ]; } private function maybe_preload() { global $wpml_post_translations, $wpml_cache_factory; if ( ! $this->preload_done ) { $trids = $wpml_post_translations->get_trids(); $trids = implode( ',', $trids ); if ( $trids ) { $cache = $wpml_cache_factory->get( 'WPML_Post_Status::needs_update' ); $results = $this->wpdb->get_results( "SELECT ts.needs_update, it.language_code, it.trid FROM {$this->wpdb->prefix}icl_translation_status ts JOIN {$this->wpdb->prefix}icl_translations it ON it.translation_id = ts.translation_id WHERE it.trid IN ( {$trids} )" ); $groups = array(); foreach ( $results as $result ) { if ( ! isset( $groups[ $result->trid ] ) ) { $groups[ $result->trid ] = array(); } $groups[ $result->trid ][] = $result; } foreach ( $groups as $trid => $group ) { $cache->set( $trid, $group ); } } $this->preload_done = true; } } public function reload() { $this->needs_update = array(); $this->status = array(); $this->preload_done = false; } public function set_update_status( $post_id, $update ) { global $wpml_post_translations; $update = (bool) $update; $translation_id = $this->wpdb->get_var ( $this->wpdb->prepare ( "SELECT ts.translation_id FROM {$this->wpdb->prefix}icl_translations it JOIN {$this->wpdb->prefix}icl_translation_status ts ON it.translation_id = ts.translation_id WHERE it.trid = %d AND it.language_code = %s", $wpml_post_translations->get_element_trid ( $post_id ), $wpml_post_translations->get_element_lang_code ( $post_id ) ) ); if ( $translation_id ) { $res = $this->wpdb->update ( $this->wpdb->prefix . 'icl_translation_status', array( 'needs_update' => $update ), array( 'translation_id' => $translation_id ) ); } $this->needs_update[ $post_id ] = (bool) $update; do_action( 'wpml_translation_status_update', array( 'post_id' => $post_id, 'type' => 'needs_update', 'value' => $update ) ); return isset( $res ); } /** * @param int $post_id * @param int $status * * @return bool */ public function set_status( $post_id, $status ) { global $wpml_post_translations; if ( ! $post_id ) { throw new InvalidArgumentException( 'Tried to set status' . $status . ' for falsy post_id ' . serialize( $post_id ) ); } /** @var \stdClass $translation_id */ $translation_id = $this->wpdb->get_row ( $this->wpdb->prepare ( "SELECT it.translation_id AS transid, ts.translation_id AS status_id FROM {$this->wpdb->prefix}icl_translations it LEFT JOIN {$this->wpdb->prefix}icl_translation_status ts ON it.translation_id = ts.translation_id WHERE it.trid = %d AND it.language_code = %s LIMIT 1", $wpml_post_translations->get_element_trid ( $post_id ), $wpml_post_translations->get_element_lang_code ( $post_id ) ) ); if ( ! $translation_id ) { // No translations for given post. return false; } if ( $translation_id->status_id && $translation_id->transid ) { $res = $this->wpdb->update ( $this->wpdb->prefix . 'icl_translation_status', array( 'status' => $status ), array( 'translation_id' => $translation_id->transid ) ); $this->status[ $post_id ] = $status; } else { $res = $this->wpdb->insert ( $this->wpdb->prefix . 'icl_translation_status', array( 'status' => $status, 'translation_id' => $translation_id->transid ) ); } do_action( 'wpml_translation_status_update', array( 'post_id' => $post_id, 'type' => 'status', 'value' => $status ) ); return $res; } public function get_status( $post_id, $trid = false, $lang_code = false ) { global $wpml_post_translations; $trid = $trid !== false ? $trid : $wpml_post_translations->get_element_trid ( $post_id ); $lang_code = $lang_code !== false ? $lang_code : $wpml_post_translations->get_element_lang_code ( $post_id ); $post_id = $post_id ? $post_id : $wpml_post_translations->get_element_id ( $lang_code, $trid ); if ( !$post_id ) { $status = ICL_TM_NOT_TRANSLATED; $post_id = $lang_code . $trid; } else { $status = $this->is_duplicate( $post_id ) ? ICL_TM_DUPLICATE : ( $this->needs_update ( $post_id ) ? ICL_TM_NEEDS_UPDATE : ICL_TM_COMPLETE ); } $status = apply_filters ( 'wpml_translation_status', $status, $trid, $lang_code, true ); $this->status[ $post_id ] = $status; return $status; } public function is_duplicate( $post_id ) { return (bool) $this->wp_api->get_post_meta ( $post_id, '_icl_lang_duplicate_of', true ); } } post-translation/class-wpml-post-edit-terms-hooks.php 0000755 00000003405 14720342453 0017050 0 ustar 00 <?php class WPML_Post_Edit_Terms_Hooks implements IWPML_Action { const AFTER_POST_DATA_SANITIZED_ACTION = 'init'; /** @var IWPML_Current_Language $language */ private $language; /** @var wpdb $wpdb */ private $wpdb; public function __construct( IWPML_Current_Language $current_language, wpdb $wpdb ) { $this->language = $current_language; $this->wpdb = $wpdb; } public function add_hooks() { add_action( self::AFTER_POST_DATA_SANITIZED_ACTION, array( $this, 'set_tags_input_with_ids' ) ); } public function set_tags_input_with_ids() { $tag_names = $this->get_tags_from_tax_input(); if ( $tag_names ) { $sql = "SELECT t.name, t.term_id FROM {$this->wpdb->terms} AS t LEFT JOIN {$this->wpdb->term_taxonomy} AS tt ON tt.term_id = t.term_id LEFT JOIN {$this->wpdb->prefix}icl_translations AS tr ON tr.element_id = tt.term_taxonomy_id AND tr.element_type = 'tax_post_tag' WHERE tr.language_code = %s AND t.name IN(" . wpml_prepare_in( $tag_names ) . ")"; $tags = $this->wpdb->get_results( $this->wpdb->prepare( $sql, $this->language->get_current_language() ) ); foreach ( $tags as $tag ) { $_POST['tags_input'][] = (int) $tag->term_id; } } } /** * @return array|null */ public function get_tags_from_tax_input() { if ( ! empty( $_POST['tax_input']['post_tag'] ) ) { $tags = $_POST['tax_input']['post_tag']; if ( ! is_array( $tags ) ) { /** * This code is following the logic from `edit_post()` in core * where the terms name are converted into IDs. * * @see edit_post */ $delimiter = _x( ',', 'tag delimiter' ); $tags = explode( $delimiter, trim( $tags, " \n\t\r\0\x0B," ) ); } if ( $tags ) { return array_map( 'trim', $tags ); } } return null; } } post-translation/SpecialPage/Hooks.php 0000755 00000000576 14720342453 0014055 0 ustar 00 <?php namespace WPML\PostTranslation\SpecialPage; class Hooks implements \IWPML_Backend_Action { public function add_hooks() { add_action( 'current_screen', [ $this, 'deleteCacheOnSettingPage' ] ); } public function deleteCacheOnSettingPage( \WP_Screen $currentScreen ) { if ( 'options-reading' === $currentScreen->id ) { \WPML_Pre_Option_Page::clear_cache(); } } } ISitePress.php 0000755 00000000065 14720342453 0007317 0 ustar 00 <?php namespace WPML\Core; interface ISitePress {} post-types/class-wpml-post-types.php 0000755 00000001514 14720342453 0013623 0 ustar 00 <?php class WPML_Post_Types extends WPML_SP_User { public function get_translatable() { return $this->sitepress->get_translatable_documents( true ); } public function get_readonly() { $wp_post_types = $this->sitepress->get_wp_api()->get_wp_post_types_global(); $types = array(); $tm_settings = $this->sitepress->get_setting( 'translation-management', array() ); if ( array_key_exists( 'custom-types_readonly_config', $tm_settings ) && is_array( $tm_settings['custom-types_readonly_config'] ) ) { foreach ( array_keys( $tm_settings['custom-types_readonly_config'] ) as $cp ) { if ( isset( $wp_post_types[ $cp ] ) ) { $types[ $cp ] = $wp_post_types[ $cp ]; } } } return $types; } public function get_translatable_and_readonly() { return $this->get_translatable() + $this->get_readonly(); } } post-types/class-wpml-translation-modes.php 0000755 00000002713 14720342453 0015141 0 ustar 00 <?php use WPML\FP\Lst; /** * Created by PhpStorm. * User: bruce * Date: 4/10/17 * Time: 10:15 AM */ class WPML_Translation_Modes { public function is_translatable_mode( $mode ) { return Lst::includes( (int) $mode, [ WPML_CONTENT_TYPE_TRANSLATE, WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED ] ); } public function get_options_for_post_type( $post_type_label ) { return [ WPML_CONTENT_TYPE_DONT_TRANSLATE => sprintf( __( "Do not make '%s' translatable", 'sitepress' ), $post_type_label ), WPML_CONTENT_TYPE_TRANSLATE => sprintf( __( "Make '%s' translatable", 'sitepress' ), $post_type_label ), WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED => sprintf( __( "Make '%s' appear as translated", 'sitepress' ), $post_type_label ), ]; } public function get_options() { $formatHeading = function ( $a, $b ) { return $a . "<br/><span class='explanation-text'>" . $b . '</span>'; }; return [ WPML_CONTENT_TYPE_TRANSLATE => $formatHeading( esc_html__( 'Translatable', 'sitepress' ), esc_html__( 'only show translated items', 'sitepress' ) ), WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED => $formatHeading( esc_html__( 'Translatable', 'sitepress' ), esc_html__( 'use translation if available or fallback to default language', 'sitepress' ) ), WPML_CONTENT_TYPE_DONT_TRANSLATE => $formatHeading( esc_html__( 'Not translatable', 'sitepress' ), ' ' ) ]; } } troubleshooting/class-wpml-tm-troubleshooting-reset-pro-trans-config-ui.php 0000755 00000005073 14720342453 0023366 0 ustar 00 <?php class WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI { const TROUBLESHOOTING_RESET_PRO_TRANS_TEMPLATE = 'reset-pro-trans-config.twig'; /** * Template service. * * @var IWPML_Template_Service */ private $template_service; /** * WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI constructor. * * @param IWPML_Template_Service $template_service WPML_Twig_Template twig service. */ public function __construct( IWPML_Template_Service $template_service ) { $this->template_service = $template_service; } /** * Returns of template service render result. * * @return string */ public function show() { return $this->template_service->show( $this->get_model(), self::TROUBLESHOOTING_RESET_PRO_TRANS_TEMPLATE ); } /** * Returns model array for Troubleshooting Reset Pro Trans. * * @return array */ private function get_model() { $translation_service_name = TranslationProxy::get_current_service_name(); if ( ! $translation_service_name ) { $translation_service_name = 'PRO'; $alert_2 = __( 'Only select this option if you have no pending jobs or you are sure of what you are doing.', 'wpml-translation-management' ); } else { if ( ! TranslationProxy::has_preferred_translation_service() ) { /* translators: Reset professional translation state ("%1$s" is the service name) */ $alert_2 = sprintf( __( 'If you have sent content to %1$s, you should cancel the projects in %1$s system. Any work that completes after you do this reset cannot be received by your site.', 'wpml-translation-management' ), $translation_service_name ); } else { $alert_2 = __( 'Any work that completes after you do this reset cannot be received by your site.', 'wpml-translation-management' ); } } $model = array( 'strings' => array( 'title' => __( 'Reset professional translation state', 'wpml-translation-management' ), 'alert1' => __( 'Use this feature when you want to reset your translation process. All your existing translations will remain unchanged. Any translation work that is currently in progress will be stopped.', 'wpml-translation-management' ), 'alert2' => $alert_2, /* translators: Reset professional translation state ("%1$s" is the service name) */ 'checkBoxLabel' => sprintf( __( 'I am about to stop any ongoing work done by %1$s.', 'wpml-translation-management' ), $translation_service_name ), 'button' => __( 'Reset professional translation state', 'wpml-translation-management' ), ), 'placeHolder' => 'icl_reset_pro', ); return $model; } } troubleshooting/class-wpml-tm-troubleshooting-reset-pro-trans-config-ui-factory.php 0000755 00000001252 14720342453 0025026 0 ustar 00 <?php class WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI_Factory { /** * Sets template base directory. */ private function get_template_base_dir() { return array( WPML_TM_PATH . '/templates/troubleshooting', ); } /** * Creates WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI instance * * @return WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI */ public function create() { $template_paths = $this->get_template_base_dir(); $template_loader = new WPML_Twig_Template_Loader( $template_paths ); $template_service = $template_loader->get_template(); return new WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI( $template_service ); } } troubleshooting/ResetPreferredTranslationService.php 0000755 00000003766 14720342453 0017210 0 ustar 00 <?php namespace WPML\TM\Troubleshooting; use WPML\API\Sanitize; class ResetPreferredTranslationService implements \IWPML_Backend_Action { const ACTION_ID = 'wpml-tm-reset-preferred-translation-service'; public function add_hooks() { add_action( 'after_setup_complete_troubleshooting_functions', [ $this, 'displayButton' ], 11 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScripts' ] ); add_action( 'wp_ajax_' . self::ACTION_ID, [ $this, 'resetAndFetchPreferredTS' ] ); } public function displayButton() { $resetTitle = sprintf( __( 'Reset preferred translation service', 'wpml-translation-manager' ) ); $resetMessage = sprintf( __( 'Reset and fetch local preferred translation service to use Preferred Translation Service configured on WPML.org account.', 'wpml-translation-manager' ) ); $resetButton = sprintf( __( 'Reset & Fetch', 'wpml-translation-manager' ) ); $html = '<div class="icl_cyan_box" id="wpml_tm_reset_preferred_translation_service_btn">' . wp_nonce_field( self::ACTION_ID, 'wpml_tm_reset_preferred_translation_service_nonce', true, false ) . // <-- This seams to be never used. '<h3>' . $resetTitle . '</h3> <p>' . $resetMessage . '</p> <a class="button-primary" href="#">' . $resetButton . '</a><span class="spinner"></span> </div>'; echo $html; } public function resetAndFetchPreferredTS() { $action = Sanitize::stringProp( 'action', $_POST ); $nonce = Sanitize::stringProp( 'nonce', $_POST ); if ( $nonce && $action && wp_verify_nonce( $nonce, $action ) ) { OTGS_Installer()->settings['repositories']['wpml']['ts_info']['preferred'] = null; OTGS_Installer()->refresh_subscriptions_data(); wp_send_json_success(); } else { wp_send_json_error(); } } public function enqueueScripts( $page ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $page ) { wp_enqueue_script( self::ACTION_ID, WPML_TM_URL . '/res/js/reset-preferred-ts.js', [ 'jquery' ], ICL_SITEPRESS_VERSION ); } } } troubleshooting/class-wpml-tm-troubleshooting-reset-pro-trans-config.php 0000755 00000017774 14720342453 0022766 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_TM_Troubleshooting_Reset_Pro_Trans_Config extends WPML_TM_AJAX_Factory_Obsolete { const SCRIPT_HANDLE = 'wpml_reset_pro_trans_config'; /** * Wpdb Object to perform DB queries. * * @var wpdb $wpdb */ private $wpdb; /** * SitePress object - superclass for all WPML classes using the global wpdb object. * * @var SitePress */ private $sitepress; /** * Translation Proxy service. * * @var WPML_Translation_Proxy_API $translation_proxy */ private $translation_proxy; /** * WPML_TM_Troubleshooting_Clear_TS constructor. * * @param SitePress $sitepress SitePress object. * @param WPML_Translation_Proxy_API $translation_proxy Translation Proxy service. * @param WPML_WP_API $wpml_wp_api WPML WordPress API wrapper. * @param wpdb $wpdb Wpdb Object to perform DB queries. */ public function __construct( &$sitepress, &$translation_proxy, &$wpml_wp_api, &$wpdb ) { parent::__construct( $wpml_wp_api ); $this->sitepress = &$sitepress; $this->translation_proxy = &$translation_proxy; $this->wpdb = &$wpdb; add_action( 'init', array( $this, 'load_action' ) ); $this->add_ajax_action( 'wp_ajax_wpml_reset_pro_trans_config', array( $this, 'reset_pro_translation_configuration_action' ) ); $this->init(); } /** * Loading actions. */ public function load_action() { $page = Sanitize::stringProp( 'page', $_GET ); $should_proceed = SitePress_Setup::setup_complete() && ! $this->wpml_wp_api->is_heartbeat() && ! $this->wpml_wp_api->is_ajax() && ! $this->wpml_wp_api->is_cron_job() && $page && strpos( $page, 'sitepress-multilingual-cms/menu/troubleshooting.php' ) === 0; if ( $should_proceed ) { $this->add_hooks(); } } /** * Adding WP hooks. */ private function add_hooks() { add_action( 'after_setup_complete_troubleshooting_functions', array( $this, 'render_ui' ) ); } /** * Registering WP scripts. */ public function register_resources() { wp_register_script( self::SCRIPT_HANDLE, WPML_TM_URL . '/res/js/reset-pro-trans-config.js', array( 'jquery', 'jquery-ui-dialog' ), false, true ); } /** * Enqueue WordPress resources. * * @param string $hook_suffix Hook suffix. */ public function enqueue_resources( $hook_suffix ) { if ( $this->wpml_wp_api->is_troubleshooting_page() ) { $this->register_resources(); $translation_service_name = $this->translation_proxy->get_current_service_name(); $strings = array( 'placeHolder' => 'icl_reset_pro', 'reset' => wp_create_nonce( 'reset_pro_translation_configuration' ), /* translators: Reset professional translation state confirmation ("%1$s" is the service name) */ 'confirmation' => sprintf( __( 'Are you sure you want to reset the %1$s translation process?', 'wpml-translation-management' ), $translation_service_name ), 'action' => self::SCRIPT_HANDLE, 'nonce' => wp_create_nonce( self::SCRIPT_HANDLE ), ); wp_localize_script( self::SCRIPT_HANDLE, self::SCRIPT_HANDLE . '_strings', $strings ); wp_enqueue_script( self::SCRIPT_HANDLE ); } } /** * Rendering user interface. */ public function render_ui() { $clear_ts_factory = new WPML_TM_Troubleshooting_Reset_Pro_Trans_Config_UI_Factory(); $clear_ts = $clear_ts_factory->create(); // phpcs:disable WordPress.XSS.EscapeOutput.OutputNotEscaped echo $clear_ts->show(); // phpcs:enable WordPress.XSS.EscapeOutput.OutputNotEscaped } /** * Resetting professional translation configuration action. * * @return array|null */ public function reset_pro_translation_configuration_action() { $action = Sanitize::stringProp( 'action', $_POST ); $nonce = Sanitize::stringProp( 'nonce', $_POST ); $valid_nonce = $nonce && $action && wp_verify_nonce( $nonce, $action ); if ( $valid_nonce ) { return $this->wpml_wp_api->wp_send_json_success( $this->reset_pro_translation_configuration() ); } else { return $this->wpml_wp_api->wp_send_json_error( __( "You can't do that!", 'wpml-translation-management' ) ); } } /** * Implementation of core class functionality - resetting professional translation configuration. * * @return string */ public function reset_pro_translation_configuration() { $translation_service_name = $this->translation_proxy->get_current_service_name(); $this->sitepress->set_setting( 'content_translation_languages_setup', false ); $this->sitepress->set_setting( 'content_translation_setup_complete', false ); $this->sitepress->set_setting( 'content_translation_setup_wizard_step', false ); $this->sitepress->set_setting( 'translator_choice', false ); $this->sitepress->set_setting( 'icl_lang_status', false ); $this->sitepress->set_setting( 'icl_balance', false ); $this->sitepress->set_setting( 'icl_support_ticket_id', false ); $this->sitepress->set_setting( 'icl_current_session', false ); $this->sitepress->set_setting( 'last_get_translator_status_call', false ); $this->sitepress->set_setting( 'last_icl_reminder_fetch', false ); $this->sitepress->set_setting( 'icl_account_email', false ); $this->sitepress->set_setting( 'translators_management_info', false ); $this->sitepress->set_setting( 'site_id', false ); $this->sitepress->set_setting( 'access_key', false ); $this->sitepress->set_setting( 'ts_site_id', false ); $this->sitepress->set_setting( 'ts_access_key', false ); if ( class_exists( 'TranslationProxy_Basket' ) ) { // Cleaning the basket. TranslationProxy_Basket::delete_all_items_from_basket(); } global $wpdb; $sql_for_remote_rids = $wpdb->prepare( "FROM {$wpdb->prefix}icl_translation_status WHERE translation_service != 'local' AND translation_service != 0 AND status IN ( %d, %d )", ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ); // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared // Delete all translation service jobs with status "waiting for translator" or "in progress". $wpdb->query( "DELETE FROM {$wpdb->prefix}icl_translate_job WHERE rid IN (SELECT rid {$sql_for_remote_rids})" ); // Delete all translation statuses with status "waiting for translator" or "in progress". $wpdb->query( "DELETE {$sql_for_remote_rids}" ); // phpcs:enable WordPress.WP.PreparedSQL.NotPrepared // Cleaning up Translation Proxy settings. $this->sitepress->set_setting( 'icl_html_status', false ); $this->sitepress->set_setting( 'language_pairs', false ); if ( ! $this->translation_proxy->has_preferred_translation_service() ) { $this->sitepress->set_setting( 'translation_service', false ); $this->sitepress->set_setting( 'icl_translation_projects', false ); } $this->sitepress->save_settings(); $this->wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}icl_core_status" ); $this->wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}icl_content_status" ); $this->wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}icl_string_status" ); $this->wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}icl_node" ); $this->wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}icl_reminders" ); if ( $this->translation_proxy->has_preferred_translation_service() && $translation_service_name ) { /* translators: Confirmation that translation process was reset ("%1$s" is the service name) */ $confirm_message = sprintf( __( 'The translation process with %1$s was reset.', 'wpml-translation-management' ), $translation_service_name ); } elseif ( $translation_service_name ) { /* translators: Confirmation that site has been disconnected from translation service ("%1$s" is the service name) */ $confirm_message = sprintf( __( 'Your site was successfully disconnected from %1$s. Go to the translators tab to connect a new %1$s account or use a different translation service.', 'wpml-translation-management' ), $translation_service_name ); } else { $confirm_message = __( 'PRO translation has been reset.', 'wpml-translation-management' ); } return $confirm_message; } } troubleshooting/SynchronizeSourceIdOfATEJobs/TriggerSynchronization.php 0000755 00000004363 14720342453 0022654 0 ustar 00 <?php namespace WPML\TM\Troubleshooting\SynchronizeSourceIdOfATEJobs; use WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs\Command; use WPML\Upgrade\CommandsStatus; class TriggerSynchronization implements \IWPML_Backend_Action, \IWPML_DIC_Action { const ACTION_ID = 'wpml-tm-ate-source-id-migration'; /** @var CommandsStatus */ private $commandStatus; /** * @param CommandsStatus $commandStatus */ public function __construct( CommandsStatus $commandStatus ) { $this->commandStatus = $commandStatus; } public function add_hooks() { add_action( 'wpml_troubleshooting_after_fix_element_type_collation', [ $this, 'displayButton' ] ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueueScripts' ) ); add_action( 'wp_ajax_' . self::ACTION_ID, array( $this, 'clearExecutedStateToForceUpgrade' ) ); } public function displayButton() { ?> <p> <input id="wpml_tm_ate_source_id_migration_btn" type="button" class="button-secondary" value="<?php esc_attr_e( 'Synchronize local job ids with ATE jobs', 'wpml-translation-manager' ); ?>" data-action="<?php echo self::ACTION_ID; ?>" data-nonce="<?php echo wp_create_nonce( self::ACTION_ID ); ?>" /> <br/> <small style="margin-left:10px;"> <?php esc_attr_e( 'Synchronize local job ids with their ATE counterparts. You will have to refresh a few times any admin page to accomplish the process.', 'wpml-translation-manager' ) ?> </small> </p> <?php } public function enqueueScripts( $hook ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $hook ) { wp_enqueue_script( self::ACTION_ID, WPML_TM_URL . '/res/js/ate-jobs-migration.js', [ 'jquery' ], ICL_SITEPRESS_VERSION ); wp_localize_script( self::ACTION_ID, 'ate_jobs_migration_data', [ 'nonce' => wp_create_nonce( self::ACTION_ID ), ] ); } } public function clearExecutedStateToForceUpgrade() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, self::ACTION_ID ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $this->commandStatus->markAsExecuted( Command::class, false ); wp_send_json_success(); } } privacy/class-wpml-privacy-content-factory.php 0000755 00000000344 14720342453 0015614 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Privacy_Content_Factory implements IWPML_Backend_Action_Loader { /** * @return IWPML_Action */ public function create() { return new WPML_Core_Privacy_Content(); } } privacy/class-wpml-core-privacy-content.php 0000755 00000001151 14720342453 0015072 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Core_Privacy_Content extends WPML_Privacy_Content { /** * @return string */ protected function get_plugin_name() { return 'WPML'; } /** * @return string|array */ protected function get_privacy_policy() { return array( __( 'WPML uses cookies to identify the visitor’s current language, the last visited language and the language of users who have logged in.', 'sitepress' ), __( 'While you use the plugin, WPML will share data regarding the site through Installer. No data from the user itself will be shared.', 'sitepress' ), ); } } privacy/class-wpml-tm-privacy-content-factory.php 0000755 00000000455 14720342453 0016235 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Privacy_Content_Factory implements IWPML_Backend_Action_Loader { /** * @return IWPML_Action */ public function create() { if ( class_exists( 'WPML_Privacy_Content' ) ) { return new WPML_TM_Privacy_Content(); } return null; } } privacy/class-wpml-tm-privacy-content.php 0000755 00000001063 14720342453 0014564 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Privacy_Content extends WPML_Privacy_Content { /** * @return string */ protected function get_plugin_name() { return 'WPML Translation Management'; } /** * @return string|array */ protected function get_privacy_policy() { return __( 'WPML Translation Management will send the email address and name of each manager and assigned translator as well as the content itself to Advanced Translation Editor and to the translation services which are used.', 'wpml-translation-management' ); } } privacy/class-wpml-privacy-content.php 0000755 00000001610 14720342453 0014144 0 ustar 00 <?php /** * @author OnTheGo Systems */ abstract class WPML_Privacy_Content implements IWPML_Action { public function add_hooks() { add_action( 'admin_init', array( $this, 'privacy_policy' ) ); } public function privacy_policy() { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $policy_text_content = $this->get_privacy_policy(); if ( $policy_text_content ) { if ( is_array( $policy_text_content ) ) { $policy_text_content = '<p>' . implode( '</p><p>', $policy_text_content ) . '</p>'; } wp_add_privacy_policy_content( $this->get_plugin_name(), $policy_text_content ); } } /** * @return string */ abstract protected function get_plugin_name(); /** * @return string|array a single or an array of strings (plain text or HTML). Array items will be wrapped by a paragraph tag. */ abstract protected function get_privacy_policy(); } class-wpml-tm-page.php 0000755 00000003756 14720342453 0010711 0 ustar 00 <?php use \WPML\FP\Obj; class WPML_TM_Page { public static function is_tm_dashboard() { $is_tm_page = self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_MANAGEMENT ); if ( ! $is_tm_page ) { return false; } return ! isset( $_GET['sm'] ) || ( isset( $_GET['sm'] ) && $_GET['sm'] === 'dashboard' ); } public static function is_tm_translators() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_MANAGEMENT ) && isset( $_GET['sm'] ) && $_GET['sm'] === 'translators'; } public static function is_settings() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_SETTINGS ) && ( ! isset( $_GET['sm'] ) || $_GET['sm'] === 'mcsetup' ); } public static function is_translation_queue() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_QUEUE ); } public static function is_translation_editor_page() { return self::is_translation_queue() && isset( $_GET['job_id'] ); } public static function is_job_list() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_MANAGEMENT ) && isset( $_GET['sm'] ) && $_GET['sm'] === 'jobs'; } public static function is_dashboard() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_MANAGEMENT ) && ( ! isset( $_GET['sm'] ) || $_GET['sm'] === 'dashboard' ); } public static function is_notifications_page() { return self::is_tm_page( WPML_Translation_Management::PAGE_SLUG_MANAGEMENT ) && isset( $_GET['sm'] ) && $_GET['sm'] === 'notifications'; } public static function get_translators_url( $params = array() ) { $url = admin_url( 'admin.php?page=' . static::get_tm_folder() . '/menu/main.php' ); $params['sm'] = 'translators'; return add_query_arg( $params, $url ); } private static function is_tm_page( $page = null ) { return is_admin() && Obj::propOr( false, 'page', $_GET ) === static::get_tm_folder() . $page; } private static function get_tm_folder() { return defined( 'WPML_TM_FOLDER' ) ? WPML_TM_FOLDER : ''; } } class-wpml-config-update.php 0000755 00000022626 14720342453 0012101 0 ustar 00 <?php /** * Fetch the wpml config files for known plugins and themes * * @package wpml-core */ class WPML_Config_Update { /** @var bool */ private $has_errors; private $log; /** @var SitePress $sitepress */ protected $sitepress; /** * @var WP_Http $http */ private $http; /** * @var WPML_Active_Plugin_Provider */ private $active_plugin_provider; /** * WPML_Config_Update constructor. * * @param SitePress $sitepress * @param WP_Http $http * @param WPML_Log|null $log */ public function __construct( $sitepress, $http, WPML_Log $log = null ) { $this->sitepress = $sitepress; $this->http = $http; $this->log = $log; } /** * @param WPML_Active_Plugin_Provider $active_plugin_provider */ public function set_active_plugin_provider( WPML_Active_Plugin_Provider $active_plugin_provider ) { $this->active_plugin_provider = $active_plugin_provider; } /** * @return WPML_Active_Plugin_Provider */ public function get_active_plugin_provider() { if ( null === $this->active_plugin_provider ) { if ( ! class_exists( 'WPML_Active_Plugin_Provider' ) ) { require_once WPML_PLUGIN_PATH . '/classes/class-wpml-active-plugin-provider.php'; } $this->active_plugin_provider = new WPML_Active_Plugin_Provider(); } return $this->active_plugin_provider; } public function run() { if ( ! $this->is_config_update_disabled() ) { $this->has_errors = false; $request_args = array( 'timeout' => 45 ); $index_response = $this->http->get( ICL_REMOTE_WPML_CONFIG_FILES_INDEX . 'wpml-config/config-index.json', $request_args ); if ( ! $this->is_a_valid_remote_response( $index_response ) ) { $this->log_response( $index_response, 'index', 'wpml-config/config-index.json' ); } else { $arr = json_decode( $index_response['body'] ); $plugins = isset( $arr->plugins ) ? $arr->plugins : array(); $themes = isset( $arr->themes ) ? $arr->themes : array(); if ( $plugins || $themes ) { update_option( 'wpml_config_index', $arr, false ); update_option( 'wpml_config_index_updated', time(), false ); $config_files_original = get_option( 'wpml_config_files_arr', null ); $config_files = maybe_unserialize( $config_files_original ); $config_files_for_themes = array(); $deleted_configs_for_themes = array(); $config_files_for_plugins = array(); $deleted_configs_for_plugins = array(); if ( $config_files ) { if ( isset( $config_files->themes ) && $config_files->themes ) { $config_files_for_themes = $config_files->themes; $deleted_configs_for_themes = $config_files->themes; } if ( isset( $config_files->plugins ) && $config_files->plugins ) { $config_files_for_plugins = $config_files->plugins; $deleted_configs_for_plugins = $config_files->plugins; } } $current_theme_name = $this->sitepress->get_wp_api() ->get_theme_name(); $current_theme_parent = ''; if ( method_exists( $this->sitepress->get_wp_api(), 'get_theme_parent_name' ) ) { $current_theme_parent = $this->sitepress->get_wp_api() ->get_theme_parent_name(); } $active_theme_names = array( $current_theme_name ); if ( $current_theme_parent ) { $active_theme_names[] = $current_theme_parent; } foreach ( $themes as $theme ) { if ( in_array( $theme->name, $active_theme_names, true ) ) { unset( $deleted_configs_for_themes[ $theme->name ] ); if ( ! isset( $config_files_for_themes[ $theme->name ] ) || md5( $config_files_for_themes[ $theme->name ] ) !== $theme->hash ) { $theme_response = $this->http->get( ICL_REMOTE_WPML_CONFIG_FILES_INDEX . $theme->path, $request_args ); if ( ! $this->is_a_valid_remote_response( $theme_response ) ) { $this->log_response( $theme_response, 'index', $theme->name ); } else { $config_files_for_themes[ $theme->name ] = $theme_response['body']; } } } } foreach ( $deleted_configs_for_themes as $key => $deleted_config ) { unset( $config_files_for_themes[ $key ] ); } $active_plugins_names = $this->get_active_plugin_provider() ->get_active_plugin_names(); foreach ( $plugins as $plugin ) { if ( in_array( $plugin->name, $active_plugins_names, true ) ) { unset( $deleted_configs_for_plugins[ $plugin->name ] ); if ( ! isset( $config_files_for_plugins[ $plugin->name ] ) || md5( $config_files_for_plugins[ $plugin->name ] ) !== $plugin->hash ) { $plugin_response = $this->http->get( ICL_REMOTE_WPML_CONFIG_FILES_INDEX . $plugin->path, $request_args ); if ( ! $this->is_a_valid_remote_response( $plugin_response ) ) { $this->log_response( $plugin_response, 'index', $plugin->name ); } else { $config_files_for_plugins[ $plugin->name ] = $plugin_response['body']; } } } } foreach ( $deleted_configs_for_plugins as $key => $deleted_config ) { unset( $config_files_for_plugins[ $key ] ); } if ( ! $config_files ) { $config_files = new stdClass(); } $config_files->themes = $config_files_for_themes; $config_files->plugins = $config_files_for_plugins; update_option( 'wpml_config_files_arr', $config_files, false ); } } $wpml_config_files_arr = maybe_unserialize( get_option( 'wpml_config_files_arr', null ) ); if ( ! $wpml_config_files_arr ) { $this->log_response( 'Missing data', 'get_option', 'wpml_config_files_arr' ); } if ( ! $this->has_errors && $this->log ) { $this->log->clear(); } } return ! $this->has_errors; } private function is_valid_wpml_config_files_arr( $wpml_config_files_arr ) { $is_valid = true; $is_valid &= is_object( $wpml_config_files_arr ); $at_least_plugins_or_themes = false; $at_least_plugins_or_themes |= isset( $wpml_config_files_arr->themes ) && is_array( $wpml_config_files_arr->themes ) && $wpml_config_files_arr->themes; $at_least_plugins_or_themes |= isset( $wpml_config_files_arr->plugins ) && is_array( $wpml_config_files_arr->plugins ) && $wpml_config_files_arr->plugins; return $is_valid && $at_least_plugins_or_themes; } /** * @param array|WP_Error $response * * @return bool */ private function is_a_valid_remote_response( $response ) { return $response && ! is_wp_error( $response ) && ! $this->is_http_error( $response ); } private function is_http_error( $response ) { return $response && is_array( $response ) && ( ( array_key_exists( 'response', $response ) && array_key_exists( 'code', $response['response'] ) && 200 !== (int) $response['response']['code'] ) || ! array_key_exists( 'body', $response ) || '' === trim( $response['body'] ) ); } /** * @param string|array|WP_Error $response * @param string $request_type * @param ?string $component * @param array|stdClass|null $extra_data */ private function log_response( $response, $request_type = 'unknown', $component = null, $extra_data = null ) { if ( ! $this->log ) { return; } $message_type = 'message'; if ( ! defined( 'JSON_PRETTY_PRINT' ) ) { // Fallback -> Introduced in PHP 5.4.0 define( 'JSON_PRETTY_PRINT', 128 ); } $response_data = null; if ( is_scalar( $response ) ) { $message_type = 'app_error'; $response_data = $response; } elseif ( is_wp_error( $response ) ) { $message_type = 'wp_error'; $response_data = array( 'code' => $response->get_error_code(), 'message' => $response->get_error_message(), ); } elseif ( $this->is_http_error( $response ) ) { $message_type = 'http_error'; if ( array_key_exists( 'response', $response ) ) { if ( array_key_exists( 'code', $response['response'] ) ) { $response_data['code'] = $response['response']['code']; } if ( array_key_exists( 'message', $response['response'] ) ) { $response_data['message'] = $response['response']['message']; } } $response_data['body'] = 'Missing!'; if ( array_key_exists( 'body', $response ) ) { $response_data['body'] = 'Empty!'; if ( $response['body'] ) { $body_encode = wp_json_encode( simplexml_load_string( $response['body'] ) ); if ( $body_encode ) { $response_data['body'] = json_decode( $body_encode, true ); } } } } elseif ( is_array( $response ) ) { $response_data = $response; } else { $response_data = array( wp_json_encode( $response, JSON_PRETTY_PRINT ) ); } $serialized_extra_data = null; if ( $extra_data ) { $serialized_extra_data = $extra_data; if ( is_object( $serialized_extra_data ) ) { $serialized_extra_data = get_object_vars( $serialized_extra_data ); } if ( ! is_array( $serialized_extra_data ) ) { $serialized_extra_data = array( wp_json_encode( $serialized_extra_data, JSON_PRETTY_PRINT ) ); } } $entry = array( 'request' => $request_type, 'type' => $message_type, 'component' => $component, 'response' => $response_data, 'extra' => $serialized_extra_data, ); $this->log->insert( microtime( true ), $entry ); $this->has_errors = true; } private function is_config_update_disabled() { if ( $this->sitepress->get_wp_api() ->constant( 'ICL_REMOTE_WPML_CONFIG_DISABLED' ) ) { delete_option( 'wpml_config_index' ); delete_option( 'wpml_config_index_updated' ); delete_option( 'wpml_config_files_arr' ); return true; } return false; } } translation-basket/wpml-tm-translation-basket-dialog-hooks.php 0000755 00000002206 14720342453 0020647 0 ustar 00 <?php class WPML_TM_Translation_Basket_Dialog_Hooks implements IWPML_Action { const PRIORITY_GREATER_THAN_MEDIA_DIALOG = 5; /** @var WPML_TM_Translation_Basket_Dialog_View $dialog_view */ private $dialog_view; private $wp_api; public function __construct( WPML_TM_Translation_Basket_Dialog_View $dialog_view, WPML_WP_API $wp_api ) { $this->dialog_view = $dialog_view; $this->wp_api = $wp_api; } public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), self::PRIORITY_GREATER_THAN_MEDIA_DIALOG ); add_action( 'wpml_translation_basket_page_after', array( $this, 'display_dialog_markup' ) ); } public function enqueue_scripts() { $handler = 'wpml-tm-translation-basket-dialog'; wp_register_script( $handler, WPML_TM_URL . '/res/js/translation-basket/dialog.js', array( 'jquery-ui-dialog' ) ); wp_localize_script( $handler, 'wpmlTMBasket', array( 'dialogs' => array(), 'redirection' => $this->wp_api->get_tm_url(), ) ); wp_enqueue_script( $handler ); } public function display_dialog_markup() { echo $this->dialog_view->render(); } } translation-basket/wpml-tm-translation-basket-dialog-view.php 0000755 00000001734 14720342453 0020503 0 ustar 00 <?php class WPML_TM_Translation_Basket_Dialog_View { const TEMPLATE_FILE = 'dialog.twig'; /** @var IWPML_Template_Service $template_service */ private $template_service; /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( IWPML_Template_Service $template_service, WPML_WP_API $wp_api ) { $this->template_service = $template_service; $this->wp_api = $wp_api; } /** * @return string */ public function render() { $model = array( 'strings' => self::get_strings(), 'redirect_url' => $this->wp_api->get_tm_url(), ); return $this->template_service->show( $model, self::TEMPLATE_FILE ); } public static function get_strings() { return array( 'title' => __( 'Sending for translation', 'wpml-translation-management' ), 'sent_to_translation' => __( 'Items sent for translation!', 'wpml-translation-management' ), 'button_done' => __( 'Done', 'wpml-translation-management' ), ); } } translation-basket/class-wpml-tm-translation-basket-hooks-factory.php 0000755 00000001234 14720342453 0022162 0 ustar 00 <?php class WPML_TM_Translation_Basket_Hooks_Factory implements IWPML_Backend_Action_Loader { public function create() { global $sitepress; $hooks = array(); if ( $sitepress->get_wp_api()->is_tm_page( 'basket' ) ) { $template_service_loader = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/translation-basket' ) ); $template_service = $template_service_loader->get_template(); $dialog_view = new WPML_TM_Translation_Basket_Dialog_View( $template_service, $sitepress->get_wp_api() ); $hooks['dialog'] = new WPML_TM_Translation_Basket_Dialog_Hooks( $dialog_view, $sitepress->get_wp_api() ); } return $hooks; } } class-wpml-xmlrpc.php 0000755 00000010636 14720342453 0010657 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XMLRPC extends WPML_SP_User { private $xmlrpc_call_methods_for_save_post; /** * WPML_XMLRPC constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { parent::__construct( $sitepress ); $this->xmlrpc_call_methods_for_save_post = array( 'wp.newPost', 'wp.editPost', 'wp.newPage', 'wp.editPage' ); } public function init_hooks() { add_action( 'xmlrpc_call_success_mw_newPost', array( $this, 'meta_weblog_xmlrpc_post_update_action' ), 10, 2 ); add_action( 'xmlrpc_call_success_mw_editPost', array( $this, 'meta_weblog_xmlrpc_post_update_action' ), 10, 2 ); add_action( 'xmlrpc_call', array( $this, 'xmlrpc_call' ) ); add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) ); } function get_languages( $args ) { list( $blog_id, $username, $password ) = $args; if ( ! $this->sitepress->get_wp_api()->get_wp_xmlrpc_server()->login( $username, $password ) ) { return $this->sitepress->get_wp_api()->get_wp_xmlrpc_server()->error; } if ( ! defined( 'WP_ADMIN' ) ) { define( 'WP_ADMIN', true ); // hack - allow to force display language } return $this->sitepress->get_active_languages( true ); } public function get_post_trid( $args ) { list( $blog_id, $username, $password, $element_id ) = $args; if ( ! $this->sitepress->get_wp_api()->get_wp_xmlrpc_server()->login( $username, $password ) ) { return $this->sitepress->get_wp_api()->get_wp_xmlrpc_server()->error; } $post_element = new WPML_Post_Element( $element_id, $this->sitepress ); return $post_element->get_trid(); } /** * @param int $post_ID * @param array $args * * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ public function meta_weblog_xmlrpc_post_update_action( $post_ID, $args ) { $custom_fields = array(); if ( array_key_exists( 'custom_fields', $args[3] ) ) { foreach ( $args[3]['custom_fields'] as $cf ) { $custom_fields[ $cf['key'] ] = $cf['value']; } } $post_language_code = $this->sitepress->get_default_language(); $trid = false; if ( array_key_exists( '_wpml_language', $custom_fields ) ) { $post_language_code = $custom_fields['_wpml_language']; } if ( array_key_exists( '_wpml_trid', $custom_fields ) ) { $trid = $custom_fields['_wpml_trid']; } $post_type = 'post'; if ( array_key_exists( 'post_type', $args[3] ) ) { $post_type = $args[3]['post_type']; } $this->set_post_language( $post_ID, $post_type, $post_language_code, $trid ); } public function save_post_action( $pidd, $post ) { $post_language_code = get_post_meta( $pidd, '_wpml_language', true ); $post_language_code = $post_language_code ? $post_language_code : $this->sitepress->get_default_language(); $trid = get_post_meta( $pidd, '_wpml_trid', true ); $trid = $trid ? $trid : false; $this->set_post_language( $pidd, $post->post_type, $post_language_code, $trid ); } /** * @param int $post_ID * @param string $post_type * @param string $post_language_code * @param int|bool $trid * * @throws \InvalidArgumentException * @throws \UnexpectedValueException */ private function set_post_language( $post_ID, $post_type, $post_language_code, $trid = false ) { if ( $post_language_code && $this->sitepress->is_translated_post_type( $post_type ) ) { $wpml_translations = new WPML_Translations( $this->sitepress ); $post_element = new WPML_Post_Element( $post_ID, $this->sitepress ); if ( $post_language_code ) { $wpml_translations->set_language_code( $post_element, $post_language_code ); } if ( $trid ) { $wpml_translations->set_trid( $post_element, $trid ); } } } public function xmlrpc_call( $action ) { if ( in_array( $action, $this->xmlrpc_call_methods_for_save_post, true ) ) { add_action( 'save_post', array( $this, 'save_post_action' ), 10, 2 ); } } public function xmlrpc_methods( $methods ) { /** * Parameters: * - int blog_id * - string username * - string password * - int post_id * Returns: * - struct * - int trid */ $methods['wpml.get_post_trid'] = array( $this, 'get_post_trid' ); /** * Parameters: * - int blog_id * - string username * - string password * Returns: * - struct * - array active languages */ $methods['wpml.get_languages'] = array( $this, 'get_languages' ); return apply_filters( 'wpml_xmlrpc_methods', $methods ); } } wp-cli/factories/ClearCacheFactory.php 0000755 00000000534 14720342453 0013742 0 ustar 00 <?php namespace WPML\CLI\Core\Commands; use function WPML\Container\make; class ClearCacheFactory implements IWPML_Core { /** * @return ClearCache * @throws \WPML\Auryn\InjectionException If it's not possible to create the instance (see \WPML\Auryn\Injector::make). */ public function create() { return make( ClearCache::class ); } } wp-cli/factories/IWPML_Command_Factory.php 0000755 00000000210 14720342453 0014444 0 ustar 00 <?php namespace WPML\CLI\Core\Commands; interface IWPML_Command_Factory { /** * @return ICommand */ public function create(); } wp-cli/BootStrap.php 0000755 00000002261 14720342453 0010375 0 ustar 00 <?php namespace WPML\CLI\Core; use WPML\CLI\Core\Commands\ClearCacheFactory; use WPML\CLI\Core\Commands\ICommand; class BootStrap { const MAIN_COMMAND = 'wpml'; /** * @throws \Exception The exception thrown by \WP_CLI::add_command. */ public function init() { $commands_factory = [ ClearCacheFactory::class, ]; foreach ( $commands_factory as $command_factory ) { $command_factory_obj = new $command_factory(); $command = $command_factory_obj->create(); $this->add_command( $this->getFullCommand( $command ), $command ); } } /** * @param string $command_text The subcommand. * @param callable $command Command implementation as a class, function or closure. * * @throws \Exception The exception thrown by \WP_CLI::add_command. */ private function add_command( $command_text, $command ) { \WP_CLI::add_command( $command_text, $command ); } /** * @param ICommand $command Command implementation as a class, function or closure. * * @return string The sub command prefixed by the top-level command (all trimmed). */ private function getFullCommand( $command ) { return trim( self::MAIN_COMMAND . ' ' . $command->get_command() ); } } wp-cli/IWPML_Core.php 0000755 00000000137 14720342453 0010320 0 ustar 00 <?php namespace WPML\CLI\Core\Commands; interface IWPML_Core extends IWPML_Command_Factory { } wp-cli/commands/ClearCache.php 0000755 00000001215 14720342453 0012231 0 ustar 00 <?php namespace WPML\CLI\Core\Commands; class ClearCache implements ICommand { /** * @var \WPML_Cache_Directory */ private $cache_directory; public function __construct( \WPML_Cache_Directory $cache_directory ) { $this->cache_directory = $cache_directory; } /** * Clear the WPML cache * * ## EXAMPLE * * wp wpml clear-cache * * @when wpml_loaded * * {@inheritDoc} */ public function __invoke( $args, $assoc_args ) { icl_cache_clear(); $this->cache_directory->remove(); \WP_CLI::success( 'WPML cache cleared' ); } /** * @return string */ public function get_command() { return 'clear-cache'; } } wp-cli/commands/ICommand.php 0000755 00000000441 14720342453 0011746 0 ustar 00 <?php namespace WPML\CLI\Core\Commands; interface ICommand { /** * @param string[] $args * @param array<string,string> $assoc_args * * @return mixed */ public function __invoke( $args, $assoc_args ); /** * @return string */ public function get_command(); } taxonomy/interface-iwpml-taxomony-state.php 0000755 00000000462 14720342453 0015224 0 ustar 00 <?php interface IWPML_Taxonomy_State { public function is_translated_taxonomy( $tax ); public function is_display_as_translated_taxonomy( $tax ); public function get_display_as_translated_taxonomies(); public function get_translatable_taxonomies( $include_not_synced = false, $deprecated = 'post' ); } icl-to-ate-migration/Data.php 0000755 00000002700 14720342453 0012061 0 ustar 00 <?php namespace WPML\ICLToATEMigration; use WPML\FP\Obj; use WPML\LIB\WP\Option; class Data { const OPTION_KEY = 'wpml_icl_to_ate_migration'; const MEMORY_MIGRATED = 'memory_migrated'; const ICL_DEACTIVATED = 'icl-deactivated'; const ICL_CREDENTIALS = 'icl-credentials'; /** * @param bool $flag * * @return void */ public static function setMemoryMigrated( $flag = true ) { self::save( self::MEMORY_MIGRATED, $flag ); } /** * @return bool */ public static function isMemoryMigrated() { return self::get( self::MEMORY_MIGRATED ); } /** * @param bool $flag * * @return void */ public static function setICLDeactivated( $flag = true ) { self::save( self::ICL_DEACTIVATED, $flag ); } /** * @return bool */ public static function isICLDeactivated() { return self::get( self::ICL_DEACTIVATED ); } /** * @param array $credentials * * @return void */ public static function saveICLCredentials( array $credentials ) { self::save( self::ICL_CREDENTIALS, $credentials ); } /** * @return array */ public static function getICLCredentials() { return self::get( self::ICL_CREDENTIALS ); } private static function save( $name, $value ) { $options = Option::getOr( self::OPTION_KEY, [] ); $options[ $name ] = $value; Option::update( self::OPTION_KEY, $options ); } private static function get( $name ) { return Obj::propOr( false, $name, Option::getOr( self::OPTION_KEY, [] ) ); } } icl-to-ate-migration/endpoints/AuthenticateICL.php 0000755 00000005075 14720342453 0016171 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Json; use WPML\FP\Logic; use WPML\ICLToATEMigration\ICLStatus; use WPML\TM\API\TranslationServices; use WPML\TM\TranslationProxy\Services\AuthorizationFactory; class AuthenticateICL implements IHandler { /** @var TranslationServices */ private $translationServices; /** @var ICLStatus */ private $iclStatus; /** * @param TranslationServices $translationServices */ public function __construct( TranslationServices $translationServices ) { $this->translationServices = $translationServices; $this->iclStatus = new ICLStatus( $translationServices ); } public function run( Collection $data ) { return Either::of( $data->get( 'token' ) ) ->chain( Logic::ifElse( Fns::identity(), Either::of(), Fns::always( Either::left( __( 'Token is not defined', 'sitepress-multilingual-cms' ) ) ) ) ) ->chain( Logic::ifElse( [ $this->iclStatus, 'isActivatedAndAuthorized' ], Fns::always( Either::left( __( 'ICanLocalize.com is already active and authorized', 'sitepress-multilingual-cms' ) ) ), Either::of() ) ) ->map( Logic::ifElse( $this->isAnotherServiceCurrentlyActive(), Fns::tap( [ $this->translationServices, 'deselect' ] ), Fns::identity() ) ) ->chain( Logic::ifElse( [ $this->iclStatus, 'isActivated' ], Either::of(), function ( $token ) { return $this->translationServices->selectBySUID( ICLStatus::SUID )->map( Fns::always( $token ) ); } ) ) ->chain( [ $this->translationServices, 'authorize' ] ) ->bimap( $this->errorResponse(), $this->successResponse() ); } private function successResponse() { return Fns::always( __( 'Service activated.', 'sitepress-multilingual-cms' ) ); } private function errorResponse() { return function ( $errorDetails = '' ) { return [ 'message' => __( 'The authentication didn\'t work. Please make sure you entered your details correctly and try again.', 'sitepress-multilingual-cms' ), 'details' => $errorDetails ]; }; } private function isAnotherServiceCurrentlyActive() { return function () { return $this->translationServices->isAnyActive() && ! $this->iclStatus->isActivated(); }; } } icl-to-ate-migration/endpoints/DeactivateICL.php 0000755 00000001166 14720342453 0015621 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\ICLToATEMigration\Data; class DeactivateICL implements IHandler { public function run( Collection $data ) { if ( \TranslationProxy::is_current_service_active_and_authenticated() ) { \TranslationProxy::deselect_active_service(); global $sitepress_settings; $translationServiceData = current( $sitepress_settings['icl_translation_projects'] ); Data::setICLDeactivated( true ); Data::saveICLCredentials( $translationServiceData ); } return Either::of( true ); } } icl-to-ate-migration/endpoints/Translators/GetFromICL.php 0000755 00000001564 14720342453 0017431 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints\Translators; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML_TM_ATE_API; class GetFromICL implements IHandler { /** * @var WPML_TM_ATE_API */ private $apiClient; public function __construct( WPML_TM_ATE_API $apiClient ) { $this->apiClient = $apiClient; } public function run( Collection $data ) { global $sitepress_settings; $translationServiceData = current( $sitepress_settings['icl_translation_projects'] ); $result = $this->apiClient->import_icl_translators( $translationServiceData['ts_id'], $translationServiceData['ts_access_key'] ); if ( Fns::isLeft( $result ) ) { return Either::left( __( 'Error happened! Please try again.', 'sitepress-multilingual-cms' ) ); } return GetFromICLResponseMapper::map( $result->get()->records ); } } icl-to-ate-migration/endpoints/Translators/Save.php 0000755 00000005451 14720342453 0016433 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints\Translators; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Left; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Logic; use WPML\Element\API\Languages; use WPML\FP\Right; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\partialRight; use WPML\TranslationRoles\SaveUser; use WPML_Language_Pair_Records; use function WPML\FP\pipe; class Save extends SaveUser { const ERROR_MESSAGE_TRANSLATORS = 'There was an error when saving the following translators:'; const SUCCESS_MESSAGE_TRANSLATORS = 'The translators were saved successfully.'; /** * @inheritDoc */ public function run( Collection $data ) { return Either::of( $data ) ->map( Fns::map( $this->createTranslator() ) ) ->chain( $this->syncAteIfRequired() ) ->chain( $this->handleErrors() ); } /** * Creates a translator and returns either an error or the translator email. * * @return callable(Collection):Either */ private function createTranslator() { return function( $translator ) { $handleError = function($error) use ($translator) { return [ 'translator' => $translator, 'error' => $error ]; }; $translator = \wpml_collect( $translator ); return self::getUser( \wpml_collect( $translator ) ) ->map( Fns::tap( invoke( 'add_cap' )->with( \WPML\LIB\WP\User::CAP_TRANSLATE ) ) ) ->map( Obj::prop( 'ID' ) ) ->map( Fns::tap( partialRight( [ make( WPML_Language_Pair_Records::class ), 'store_active' ], $translator->get( 'languagePairs' ) ) ) ) ->bimap( $handleError, Fns::always( $translator->get( 'user' )['email'] ) ); }; } /** * Synchronize ATE translators if one of the results was added. * * @return callable(Either[]):Either[] */ private function syncAteIfRequired() { return function( $translatorResults ) { foreach( $translatorResults as $translatorResult ) { if ( $translatorResult instanceof Right ) { do_action( 'wpml_update_translator' ); break; } } return $translatorResults; }; } /** * If any error happened, it prepares a message and the translators that failed. * * @return callable(Either[]):Either */ private function handleErrors() { return function( $translatorResults ) { $getErrorMsg = pipe( invoke( 'coalesce' )->with( Fns::identity(), Fns::identity() ), invoke( 'get' ) ); $getErrorDetails = pipe( Fns::filter( Fns::isLeft() ), Fns::map( $getErrorMsg ) ); $errorDetails = $getErrorDetails( $translatorResults ); if ( count( $errorDetails ) ) { return Either::left( [ 'message' => __( self::ERROR_MESSAGE_TRANSLATORS, 'sitepress' ), 'details' => $errorDetails ] ); } return Either::right( __( self::SUCCESS_MESSAGE_TRANSLATORS, 'sitepress' ) ); }; } } icl-to-ate-migration/endpoints/Translators/GetFromICLResponseMapper.php 0000755 00000003567 14720342453 0022322 0 ustar 00 <?php // This is sample of data that mapper returns formatted data same as it. // return Either::of( [ // 'translators' => [ // [ // 'user' => [ // 'id' => 1, // 'first' => 'Translator', // 'last' => 'First', // 'email' => 'translator3@ytest.com', // 'userName' => 'translator', // 'wpRole' => 'author', // ], // 'languagePairs' => [ // 'en' => [ 'ar', 'bs'], // ], // ], // ] // ] ); namespace WPML\ICLToATEMigration\Endpoints\Translators; use WPML\FP\Either; use WPML\FP\Fns; class GetFromICLResponseMapper { /** * Returns formatted data of translators. * * @param array $records * * @return callable|\WPML\FP\Right */ public static function map( $records ) { return Either::of( [ 'translators' => Fns::map( [ self::class, 'constructUserData' ], $records ) ] ); } /** * Formats translator data * * @param object $record * * @return array */ public static function constructUserData( $record ) { $user = get_user_by( 'email', $record->email ); return [ 'user' => [ 'id' => $record->icl_id, 'first' => $record->first_name, 'last' => $record->last_name, 'email' => $record->email, 'userName' => $user ? $user->data->user_login : strtolower( $record->first_name . '_' . $record->last_name ), 'wpRole' => $user ? current( $user->roles ) : 'subscriber' ], 'languagePairs' => self::constructUserLanguagePairs( $record->lang_pairs ) ]; } /** * Formats translator language pairs data * * @param array $langPairs * * @return array */ public static function constructUserLanguagePairs( $langPairs ) { $constructedLangPairs = []; foreach ( $langPairs as $langPair ) { $constructedLangPairs[ $langPair->from ][] = $langPair->to; } return $constructedLangPairs; } } icl-to-ate-migration/endpoints/TranslationMemory/CheckMigrationStatus.php 0000755 00000001437 14720342453 0023003 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints\TranslationMemory; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\ICLToATEMigration\Data; use WPML_TM_ATE_API; class CheckMigrationStatus implements IHandler { /** * @var WPML_TM_ATE_API */ private $apiClient; /** * @param WPML_TM_ATE_API $apiClient */ public function __construct( WPML_TM_ATE_API $apiClient ) { $this->apiClient = $apiClient; } public function run( Collection $data ) { $status = 'done'; if ( 'done' === $status ) { Data::setMemoryMigrated( true ); } return Either::of( [ 'started_at' => null, 'finished_at' => null, 'status' => $status, 'last_imported_icl_id' => 15, 'imported_count' => 4 ] ); } } icl-to-ate-migration/endpoints/TranslationMemory/StartMigration.php 0000755 00000002016 14720342453 0021651 0 ustar 00 <?php namespace WPML\ICLToATEMigration\Endpoints\TranslationMemory; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML_TM_ATE_API; class StartMigration implements IHandler { /** * @var WPML_TM_ATE_API */ private $apiClient; /** * @param WPML_TM_ATE_API $apiClient */ public function __construct( WPML_TM_ATE_API $apiClient ) { $this->apiClient = $apiClient; } public function run( Collection $data ) { // call ATE endpoint to trigger translation memory migration // This should be the final version I comment out because the api is not working as expected /*$responde = $this->apiClient->start_translation_memory_migration(); return Either::of( $responde );*/ // I have mocked the return of the APi for now, will be removed for the final version return Either::of( [ 'started_at' => null, 'finished_at' => null, 'status' => 'in_progress', 'last_imported_icl_id' => 15, 'imported_count' => 4 ] ); } } icl-to-ate-migration/ICLStatus.php 0000755 00000001547 14720342453 0013033 0 ustar 00 <?php namespace WPML\ICLToATEMigration; use WPML\FP\Relation; use WPML\TM\API\TranslationServices; class ICLStatus { const ICL_NAME = 'ICanLocalize'; const ICL_ID = 67; const SUID = 'dd17d48516ca4bce0b83043583fabd2e'; /** @var TranslationServices */ private $translationServices; /** * @param TranslationServices $translationServices */ public function __construct( TranslationServices $translationServices ) { $this->translationServices = $translationServices; } /** * @return bool */ public function isActivated() { return $this->translationServices->getCurrentService() && Relation::propEq( 'name', self::ICL_NAME, $this->translationServices->getCurrentService() ); } /** * @return bool */ public function isActivatedAndAuthorized() { return self::isActivated() && $this->translationServices->isAuthorized(); } } icl-to-ate-migration/Loader.php 0000755 00000005357 14720342453 0012431 0 ustar 00 <?php namespace WPML\ICLToATEMigration; use WPML\Element\API\Languages; use WPML\FP\Obj; use WPML\ICLToATEMigration\Endpoints\AuthenticateICL; use WPML\ICLToATEMigration\Endpoints\DeactivateICL; use WPML\ICLToATEMigration\Endpoints\TranslationMemory\CheckMigrationStatus; use WPML\ICLToATEMigration\Endpoints\TranslationMemory\StartMigration; use WPML\ICLToATEMigration\Endpoints\Translators\GetFromICL; use WPML\ICLToATEMigration\Endpoints\Translators\Save; use WPML\LIB\WP\Hooks; use WPML\Core\WP\App\Resources; use WPML\UIPage; use function WPML\Container\make; class Loader implements \IWPML_Backend_Action { const ICL_NAME = 'ICanLocalize'; public function add_hooks() { if ( self::shouldShowMigration() ) { Hooks::onAction( 'wp_loaded' ) ->then( [ self::class, 'getData' ] ) ->then( Resources::enqueueApp( 'icl-to-ate-migration' ) ); } } /** * @return bool */ public static function shouldShowMigration() { // TODO: Remove wpml_is_ajax condition once wpmltm-4351 is done. // phpcs:disable // This feature is disabled by default now. See wpmldev-857. if ( ! defined( 'WPML_ICL_ATE_MIGRATION_ENABLED' ) || ! WPML_ICL_ATE_MIGRATION_ENABLED ) { return false; } return ! wpml_is_ajax() && UIPage::isTroubleshooting( $_GET ) && ( isset( $_GET['icl-to-ate'] ) || make(ICLStatus::class)->isActivatedAndAuthorized() || Data::isICLDeactivated() ); // phpcs:enable } public static function renderContainerIfNeeded() { if ( self::shouldShowMigration() ) { return '<div id="wpml-icl-to-ate-migration"></div>'; } return ''; } public static function getData() { $originalLanguageCode = Languages::getDefaultCode(); $userLanguageCode = Languages::getUserLanguageCode()->getOrElse( $originalLanguageCode ); $languages = Languages::withFlags( Languages::getAll( $userLanguageCode ) ); return [ 'name' => 'wpmlIclToAteMigration', 'data' => [ 'endpoints' => [ 'GetTranslatorsFromICL' => GetFromICL::class, 'StartImportTranslationMemory' => StartMigration::class, 'CheckImportTranslationMemoryProgress' => CheckMigrationStatus::class, 'SaveTranslators' => Save::class, 'AuthenticateICL' => AuthenticateICL::class, 'DeactivateICL' => DeactivateICL::class, ], 'languages' => [ 'list' => $languages ? Obj::values( $languages ) : [], 'secondaries' => Languages::getSecondaryCodes(), 'original' => $originalLanguageCode, ], 'isICLActive' => make( ICLStatus::class )->isActivatedAndAuthorized(), 'migrationsDone' => [ 'memory' => Data::isMemoryMigrated(), ], 'ICLDeactivated' => Data::isICLDeactivated(), ], ]; } } REST/Status.php 0000755 00000004542 14720342453 0007331 0 ustar 00 <?php namespace WPML\Core\REST; class Status { const PING_KEY = 'wp-rest-enabled-ping'; const CACHE_EXPIRATION_IN_SEC = 3600; const ENABLED = 'enabled'; const DISABLED = 'disabled'; const TIMEOUT = 'timeout'; /** @var \WP_Http */ private $wp_http; /** * @param \WP_Http $wp_http */ public function __construct( \WP_Http $wp_http ) { $this->wp_http = $wp_http; } public function isEnabled() { // Check this condition first to avoid infinite loop in testing PING request made below. if ( wpml_is_rest_request() ) { return true; } $filters = [ 'json_enabled', 'json_jsonp_enabled', 'rest_jsonp_enabled', 'rest_enabled' ]; foreach ( $filters as $filter ) { if ( ! apply_filters( $filter, true ) ) { return false; } } return $this->is_rest_accessible(); } /** * @return bool */ private function is_rest_accessible() { $value = $this->cacheInTransient( function () { return $this->pingRestEndpoint(); } ); return self::DISABLED !== $value; } /** * @param callable $callback * * @return mixed */ private function cacheInTransient( callable $callback ) { $value = get_transient( self::PING_KEY ); if ( ! $value ) { $value = $callback(); set_transient( self::PING_KEY, $value, self::CACHE_EXPIRATION_IN_SEC ); } return $value; } /** * @return string */ private function pingRestEndpoint() { $url = get_rest_url(); $response = $this->wp_http->get( $url, [ 'timeout' => 5, 'headers' => [ 'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ), ], 'cookies' => $this->getCookiesWithoutSessionId(), ] ); if ( is_wp_error( $response ) ) { return $this->isTimeout( $response ) ? self::TIMEOUT : self::DISABLED; } return isset( $response['response']['code'] ) && $response['response']['code'] === 200 ? self::ENABLED : self::DISABLED; } /** * The PHP session ID causes the request to be blocked if some theme/plugin * calls `session_start` (this always leads to hit the timeout). * * @return array */ private function getCookiesWithoutSessionId() { return array_diff_key( $_COOKIE, [ 'PHPSESSID' => '' ] ); } /** * @param \WP_Error $response * * @return bool */ private function isTimeout( \WP_Error $response ) { return strpos( $response->get_error_message(), 'cURL error 28: Operation timed out after' ) === 0; } } REST/RewriteRules.php 0000755 00000003706 14720342453 0010503 0 ustar 00 <?php namespace WPML\Core\REST; class RewriteRules implements \IWPML_REST_Action, \IWPML_DIC_Action { /** @var \SitePress */ private $sitepress; /** * @param \SitePress $sitepress */ public function __construct( \SitePress $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { add_action( 'init', [ $this, 'addOptionRewriteRulesHook' ] ); } public function addOptionRewriteRulesHook() { if ( $this->isLangInDirectory() && $this->isUseDirectoryForDefaultLanguage() && $this->isInstalledInSubdirectory() ) { add_filter( 'option_rewrite_rules', [ $this, 'updateRules' ] ); } } public function updateRules( $rewriteRules ) { if ( ! is_array( $rewriteRules ) ) { return $rewriteRules; } $subdirectory = $this->getSubdirectory(); $mapKeys = function ( $value, $key ) use ( $subdirectory ) { if ( $key === '^wp-json/?$' ) { $key = "^($subdirectory/)?wp-json/?$"; } elseif ( $key === '^wp-json/(.*)?' ) { $key = "^($subdirectory/)?wp-json/(.*)?"; $value = str_replace( 'matches[1]', 'matches[2]', $value ); } return [ $key => $value ]; }; return \wpml_collect( $rewriteRules )->mapWithKeys( $mapKeys )->toArray(); } /** * @return bool */ private function isLangInDirectory() { return (int) $this->sitepress->get_setting( 'language_negotiation_type' ) === WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY; } /** * @return bool */ private function isUseDirectoryForDefaultLanguage() { $urlSettings = $this->sitepress->get_setting( 'urls' ); return isset( $urlSettings['directory_for_default_language'] ) && $urlSettings['directory_for_default_language']; } /** * @return bool */ private function isInstalledInSubdirectory() { return ! empty( $this->getSubdirectory() ); } /** * @return string */ private function getSubdirectory() { $url = get_option( 'home' ); $home_path = trim( (string) parse_url( $url, PHP_URL_PATH ), '/' ); return $home_path; } } class-wpml-translation-job-factory.php 0000755 00000035302 14720342453 0014122 0 ustar 00 <?php use \WPML\FP\Obj; use WPML\TM\API\Jobs; use WPML\TM\Menu\TranslationQueue\PostTypeFilters; /** * Class WPML_Translation_Job_Factory * * Use `wpml_tm_load_job_factory` to get an instance of this class */ class WPML_Translation_Job_Factory extends WPML_Abstract_Job_Collection { /** @var WPML_TM_Records $tm_records */ private $tm_records; /** * @param WPML_TM_Records $tm_records */ public function __construct( &$tm_records ) { $wpdb = $tm_records->wpdb(); parent::__construct( $wpdb ); $this->tm_records = &$tm_records; } /** * @return WPML_TM_Records */ public function &tm_records() { return $this->tm_records; } public function init_hooks() { add_filter( 'wpml_translation_jobs', array( $this, 'get_translation_jobs_filter', ), 10, 2 ); add_filter( 'wpml_get_translation_job', array( $this, 'get_translation_job_filter', ), 10, 3 ); } /** * Creates a local translation job for a given post and target language and returns the job_id of the created job. * * @param int $post_id * @param string $target_language_code * @param int|null $translator_id * @param int|null $sendFrom * * @return int|null */ public function create_local_post_job( $post_id, $target_language_code, $translator_id = null, $sendFrom = Jobs::SENT_MANUALLY ) { return $this->create_local_job( $post_id, $target_language_code, $translator_id, null, $sendFrom ); } /** * @param int $element_id * @param string $target_language_code * @param int|null $translator_id * @param string|null $element_type * @param int|null $sendFrom * @param string|null $sourceLanguageCode * * @return int|null */ public function create_local_job( $element_id, $target_language_code, $translator_id, $element_type = null, $sendFrom = Jobs::SENT_MANUALLY, $sourceLanguageCode = null ) { /** * @var TranslationManagement $iclTranslationManagement */ global $iclTranslationManagement; $trid = null; $element_type_prefix = 'post'; if ( $element_type ) { $element_type_prefix = preg_replace( '#^([^_]+)(.*)$#', '$1', $element_type ); } $translation_record = $this->tm_records() ->icl_translations_by_element_id_and_type_prefix( $element_id, $element_type_prefix ); if ( $translation_record ) { $trid = $translation_record->trid(); $sourceLang = $sourceLanguageCode ?: $translation_record->language_code(); $batch = new WPML_TM_Translation_Batch( array( new WPML_TM_Translation_Batch_Element( $element_id, $element_type_prefix, $sourceLang, array( $target_language_code => 1 ) ), ), TranslationProxy_Batch::get_generic_batch_name(), array( $target_language_code => $translator_id ?: 0 ) ); $iclTranslationManagement->send_jobs( $batch, $element_type_prefix, $sendFrom ); } return $this->job_id_by_trid_and_lang( $trid, $target_language_code ); } public function get_translation_jobs_filter( $jobs, $args ) { return array_merge( $jobs, $this->get_translation_jobs( $args ) ); } public function get_translation_job_filter( $job_id, $include_non_translatable_elements = false, $revisions = 0 ) { return $this->get_translation_job( $job_id, $include_non_translatable_elements, $revisions ); } /** * @param int $job_id * @param bool $include_non_translatable_elements * @param int $revisions * @param bool $as_job_instance returns WPML_Element_Translation_Job instead of plain object if true * * @return bool|stdClass|WPML_Element_Translation_Job */ public function get_translation_job( $job_id, $include_non_translatable_elements = false, $revisions = 0, $as_job_instance = false ) { $job_data = false; $job = $this->retrieve_job_data( $job_id ); if ( (bool) $job !== false ) { $job_data = $this->complete_job_data( $job, $include_non_translatable_elements, $revisions ); } if ( $as_job_instance === true ) { $job_arr = $this->plain_objects_to_job_instances( array( $job ) ); $job_data = end( $job_arr ); } return $job_data; } /** * @param int $job_id * * @return bool|stdClass */ public function get_translation_job_as_stdclass( $job_id ) { return $this->get_translation_job( $job_id ); } /** * @param int $job_id * * @return bool|WPML_Element_Translation_Job */ public function get_translation_job_as_active_record( $job_id ) { return $this->get_translation_job($job_id, false, 0, true); } /** * @param int $translation_id * * @return bool|stdClass|WPML_Element_Translation_Job */ public function job_by_translation_id( $translation_id ) { $row = $this->tm_records->icl_translations_by_translation_id( $translation_id ); return $row ? $this->get_translation_job( $this->job_id_by_trid_and_lang( $row->trid(), $row->language_code() ), false, 0, true ) : 0; } public function string_job_by_translation_id( $string_translation_id ) { return new WPML_String_Translation_Job( $string_translation_id ); } public function job_id_by_trid_and_lang( $trid, $target_language_code ) { global /** @var TranslationManagement $iclTranslationManagement */ $iclTranslationManagement; return $iclTranslationManagement->get_translation_job_id( $trid, $target_language_code ); } public function get_translation_jobs( array $args = array(), $only_ids = false, $as_job_instances = false, $default_sort_jobs = false ) { $include_unassigned = isset( $args['include_unassigned'] ) ? $args['include_unassigned'] : false; $order_by = $this->build_order_by_clause( $args, $include_unassigned, $default_sort_jobs ); $where = $this->build_where_clause( $args ); $jobs_sql = $this->get_job_sql( $where, $order_by, $only_ids ); $jobs = $this->wpdb->get_results( $jobs_sql ); if ( is_array( $jobs ) && $only_ids === false ) { $jobs = $this->add_data_to_post_jobs( $jobs ); } if ( $as_job_instances === true ) { $jobs = $this->plain_objects_to_job_instances( $jobs ); } return $jobs; } /** * @param array $args * @param bool $include_unassigned * * @return string */ private function build_order_by_clause( array $args, $include_unassigned, $default_sort_jobs ) { $order_by = isset( $args['order_by'] ) ? $args['order_by'] : array(); $order = isset( $args['order'] ) ? $args['order'] : false; if ( $order_by && $order ) { $order = 'desc' === $order ? 'DESC' : 'ASC'; switch ( $order_by ) { case 'deadline': $clause_items[] = "j.deadline_date $order"; break; case 'job_id': $clause_items[] = "j.job_id $order"; break; } } else { $clause_items = is_scalar( $order_by ) ? array( $order_by ) : $order_by; } if ( $default_sort_jobs ) { $clause_items[] = " IF ((s.status = 10 AND (s.review_status = 'EDITING' OR s.review_status = 'NEEDS_REVIEW')) OR s.status = 1, 1, IF (s.status = 2, 2, IF (s.needs_update = 1, 3, 4)) ) ASC "; } if ( $include_unassigned ) { $clause_items[] = 'j.translator_id DESC'; } $clause_items[] = 'j.job_id DESC'; return implode( ', ', $clause_items ); } private function add_data_to_post_jobs( array $jobs ) { /** * @var $iclTranslationManagement TranslationManagement */ global $iclTranslationManagement, $sitepress; foreach ( $jobs as $job_index => $job ) { $post_id = $job->original_doc_id; $doc = $iclTranslationManagement->get_post( $post_id, $job->element_type_prefix ); if ( $doc ) { $element_language_details = $sitepress->get_element_language_details( $post_id, $job->original_post_type ); $language_from_code = $element_language_details->language_code; $edit_url = get_edit_post_link( $doc->ID ); if ( $iclTranslationManagement->is_external_type( $job->element_type_prefix ) ) { $post_title = $job->title ? $job->title : $this->get_external_job_post_title( $job->job_id, $post_id ); $edit_url = apply_filters( 'wpml_external_item_url', '', $post_id ); $edit_url = apply_filters( 'wpml_document_edit_item_url', $edit_url, $doc->kind_slug, $doc->ID ); } else { $post_title = $job->title ? $job->title : $doc->post_title; $edit_url = apply_filters( 'wpml_document_edit_item_url', $edit_url, $job->original_post_type, $doc->ID ); } $jobs[ $job_index ]->original_doc_id = $doc->ID; $jobs[ $job_index ]->language_code_source = $language_from_code; } else { $post_title = __( 'The original has been deleted!', 'wpml-translation-management' ); $edit_url = ''; $jobs[ $job_index ]->original_doc_id = 0; $jobs[ $job_index ]->language_code_source = null; } $jobs[ $job_index ]->post_title = $post_title; $jobs[ $job_index ]->edit_link = $edit_url; } return $jobs; } private function retrieve_job_data( $job_ids ) { global $wpdb; $job_ids = is_scalar( $job_ids ) ? array( $job_ids ) : $job_ids; if ( (bool) $job_ids === false ) { return array(); } list( $prefix_select, $prefix_posts_join ) = $this->left_join_post(); $job_id_in = wpml_prepare_in( $job_ids, '%d' ); $limit = count( $job_ids ); $data_query = 'SELECT ' . $this->get_job_select() . ", {$prefix_select} FROM " . $this->get_table_join( count( $job_ids ) === 1 ) . " {$prefix_posts_join} WHERE j.job_id IN ({$job_id_in}) AND iclt.field_type = 'original_id' LIMIT %d"; $data_prepare = $wpdb->prepare( $data_query, $limit ); $data = $wpdb->get_results( $data_prepare ); if ( false === (bool) $data ) { return array(); } if ( 1 === $limit ) { return $data[0]; } return $data; } private function get_job_sql( $where, $order_by, $only_ids = false ) { global $wpdb; list( $prefix_select, $prefix_posts_join ) = $this->left_join_post(); $cols = "j.job_id, s.batch_id, {$prefix_select}" . ( $only_ids === false ? ',' . $this->get_job_select() : '' ); return "SELECT SQL_CALC_FOUND_ROWS {$cols} FROM " . $this->get_table_join() . " {$prefix_posts_join} LEFT JOIN {$wpdb->users} u ON s.translator_id = u.ID WHERE ( {$where} ) AND iclt.field_type = 'original_id' ORDER BY {$order_by} "; } /** * @param int $job_id * @param array $data */ public function update_job_data( $job_id, array $data ) { global $wpdb; $wpdb->update( $wpdb->prefix . 'icl_translate_job', $data, array( 'job_id' => $job_id ) ); } /** * @param int $job_id */ public function delete_job_data( $job_id ) { global $wpdb; $wpdb->delete( $wpdb->prefix . 'icl_translate_job', array( 'job_id' => $job_id ) ); } private function get_job_select( $icl_translate_alias = 'iclt', $icl_translations_translated_alias = 't', $icl_translations_original_alias = 'ito', $icl_translation_status_alias = 's', $icl_translate_job_alias = 'j' ) { return "{$icl_translate_job_alias}.rid, {$icl_translate_job_alias}.translator_id, {$icl_translations_translated_alias}.translation_id, {$icl_translation_status_alias}.batch_id, {$icl_translate_job_alias}.translated, {$icl_translate_job_alias}.manager_id, {$icl_translation_status_alias}.status, {$icl_translation_status_alias}.review_status, {$icl_translation_status_alias}.needs_update, {$icl_translation_status_alias}.translation_service, {$icl_translation_status_alias}.uuid, {$icl_translation_status_alias}.ate_comm_retry_count, {$icl_translations_translated_alias}.trid, {$icl_translations_translated_alias}.language_code, {$icl_translations_translated_alias}.source_language_code, {$icl_translate_alias}.field_data AS original_doc_id, {$icl_translate_alias}.job_id, {$icl_translations_original_alias}.element_type AS original_post_type, {$icl_translate_job_alias}.title, {$icl_translate_job_alias}.deadline_date, {$icl_translate_job_alias}.completed_date, {$icl_translate_job_alias}.editor, {$icl_translate_job_alias}.editor_job_id, {$icl_translate_job_alias}.automatic"; } private function add_job_elements( $job, $include_non_translatable_elements ) { global $wpdb, $sitepress; $jelq = ! $include_non_translatable_elements ? ' AND field_translate = 1' : ''; $query = "SELECT * FROM {$wpdb->prefix}icl_translate WHERE job_id = %d {$jelq} ORDER BY tid ASC"; $elements = $wpdb->get_results( $wpdb->prepare( $query, $job->job_id ) ); // allow adding custom elements $job->elements = apply_filters( 'icl_job_elements', $elements, $job->original_doc_id, $job->job_id ); return $job; } /** * @param $job_id * @param $post_id * * @return string */ private function get_external_job_post_title( $job_id, $post_id ) { global $wpdb; $query = "SELECT n.field_data AS name, t.field_data AS title FROM {$wpdb->prefix}icl_translate AS n JOIN {$wpdb->prefix}icl_translate AS t ON n.job_id = t.job_id WHERE n.job_id = %d AND n.field_type = 'name' AND t.field_type = 'title' LIMIT 1"; $title_and_name = $wpdb->get_row( $wpdb->prepare( $query, $job_id ) ); $post_title = ''; if ( $title_and_name !== null ) { if ( $title_and_name->name ) { $title = $title_and_name->name; } else { $title = $title_and_name->title; } $post_title = base64_decode( $title ); } $post_title = apply_filters( 'wpml_tm_external_translation_job_title', $post_title, $post_id ); return $post_title; } private function complete_job_data( $job, $include_non_translatable_elements, $revisions ) { global $sitepress, $wpdb; $_ld = $sitepress->get_language_details( $job->source_language_code ); $job->from_language = isset( $_ld['display_name'] ) ? $_ld['display_name'] : ''; $_ld = $sitepress->get_language_details( $job->language_code ); $job->to_language = isset( $_ld['display_name'] ) ? $_ld['display_name'] : ''; $job = $this->add_job_elements( $job, $include_non_translatable_elements ); // Do we have a previous version? if ( $revisions > 0 ) { $query = "SELECT MAX(job_id) FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d AND job_id < %d"; $prev_version_job_id = $wpdb->get_var( $wpdb->prepare( $query, $job->rid, $job->job_id ) ); if ( $prev_version_job_id ) { $job->prev_version = $this->get_translation_job( $prev_version_job_id, false, $revisions - 1 ); } } return $job; } } ATE/Sync/Arguments.php 0000755 00000000562 14720342453 0010561 0 ustar 00 <?php namespace WPML\TM\ATE\Sync; class Arguments { /** @var string|null $lockKey */ public $lockKey; /** @var string|null $ateToken */ public $ateToken; /** @var int|null $page */ public $page; /** @var int|null $numberOfPages */ public $numberOfPages; /** @var boolean $includeManualAndLongstandingJobs */ public $includeManualAndLongstandingJobs; } ATE/Sync/Result.php 0000755 00000000667 14720342453 0010100 0 ustar 00 <?php namespace WPML\TM\ATE\Sync; class Result { /** @var string|false|null $lockKey */ public $lockKey; /** @var string|null $ateToken */ public $ateToken; /** @var int|null $nextPage */ public $nextPage; /** @var int|null $numberOfPages */ public $numberOfPages; /** @var int $downloadQueueSize */ public $downloadQueueSize = 0; /** @var array[wpmlJobId, wpmlStatus, ateStatus, wpmlJobStatus] */ public $jobs = []; } ATE/Sync/Process.php 0000755 00000010263 14720342453 0010231 0 ustar 00 <?php namespace WPML\TM\ATE\Sync; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\TM\API\Job\Map; use WPML\TM\API\Jobs; use WPML\TM\ATE\Download\Job; use WPML\TM\ATE\Log\EventsTypes; use WPML_TM_ATE_API; use WPML_TM_ATE_Job_Repository; use WPML\TM\ATE\Log\Storage; use WPML\TM\ATE\Log\Entry; use function WPML\FP\pipe; class Process { const LOCK_RELEASE_TIMEOUT = 1 * MINUTE_IN_SECONDS; /** @var WPML_TM_ATE_API $api */ private $api; /** @var WPML_TM_ATE_Job_Repository $ateRepository */ private $ateRepository; public function __construct( WPML_TM_ATE_API $api, WPML_TM_ATE_Job_Repository $ateRepository ) { $this->api = $api; $this->ateRepository = $ateRepository; } /** * @param Arguments $args * * @return Result */ public function run( Arguments $args ) { $result = new Result(); if ( $args->page ) { $result = $this->runSyncOnPages( $result, $args ); } else { $includeManualAndLongstandingJobs = (bool) Obj::propOr( true , 'includeManualAndLongstandingJobs', $args); $result = $this->runSyncInit( $result, $includeManualAndLongstandingJobs ); } return $result; } /** * This will run the sync on extra pages. * * @param Result $result * @param Arguments $args * * @return Result */ private function runSyncOnPages( Result $result, Arguments $args ) { $apiPage = $args->page - 1; // ATE API pagination starts at 0. $data = $this->api->sync_page( $args->ateToken, $apiPage ); $jobs = Obj::propOr( [], 'items', $data ); $result->jobs = $this->handleJobs( $jobs ); if ( !$result->jobs ){ Storage::add( Entry::createForType( EventsTypes::JOBS_SYNC, [ 'numberOfPages' => $args->numberOfPages, 'page' => $args->page, 'downloadQueueSize' => $result->downloadQueueSize, 'nextPage' => $result->nextPage, ] ) ); } if ( $args->numberOfPages > $args->page ) { $result->nextPage = $args->page + 1; $result->numberOfPages = $args->numberOfPages; $result->ateToken = $args->ateToken; } return $result; } /** * This will run the first sync iteration. * We send all the job IDs we want to sync. * * @param Result $result * @param boolean $includeManualAndLongstandingJobs * * @return Result */ private function runSyncInit( Result $result, $includeManualAndLongstandingJobs = true ) { $ateJobIds = $this->ateRepository->get_jobs_to_sync( $includeManualAndLongstandingJobs, true ); if ( $ateJobIds ) { $this->ateRepository->increment_ate_sync_count( $ateJobIds ); $data = $this->api->sync_all( $ateJobIds ); $jobs = Obj::propOr( [], 'items', $data ); $result->jobs = $this->handleJobs( $jobs ); if ( isset( $data->next->pagination_token, $data->next->pages_number ) ) { $result->ateToken = $data->next->pagination_token; $result->numberOfPages = $data->next->pages_number; $result->nextPage = 1; // We start pagination at 1 to avoid carrying a falsy value. } } return $result; } /** * @param boolean $includeManualAndLongstandingJobs * * @return array */ private function getAteJobIdsToSync( $includeManualAndLongstandingJobs = true ) { return $this->ateRepository ->get_jobs_to_sync( $includeManualAndLongstandingJobs ) ->map_to_property( 'editor_job_id' ); } /** * @param array $items * * @return Job[] $items */ private function handleJobs( array $items ) { $setStatus = function ( $status ) { return pipe( Fns::tap( Jobs::setStatus( Fns::__, $status ) ), Obj::assoc('status', $status) ); }; $updateStatus = Logic::cond( [ [ Relation::propEq( 'status', \WPML_TM_ATE_API::CANCELLED_STATUS ), $setStatus( ICL_TM_NOT_TRANSLATED ), ], [ Relation::propEq( 'status', \WPML_TM_ATE_API::SHOULD_HIDE_STATUS ), $setStatus( ICL_TM_ATE_CANCELLED ), ], [ Fns::always( true ), Fns::identity(), ] ] ); return wpml_collect( $items ) ->map( [ Job::class, 'fromAteResponse' ] ) ->map( Obj::over( Obj::lensProp( 'jobId' ), Map::fromRid() ) ) // wpmlJobId returned by ATE endpoint represents RID column in wp_icl_translation_status ->map( $updateStatus ) ->toArray(); } } ATE/SyncLock.php 0000755 00000001016 14720342453 0007420 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\Utilities\KeyedLock; use function WPML\Container\make; class SyncLock { /** @var KeyedLock */ private $keyedLock; public function __construct() { $this->keyedLock = make( KeyedLock::class, [ ':name' => 'ate_sync' ] ); } /** * @param null~string $key * * @return false|string */ public function create( $key = null ) { return $this->keyedLock->create( $key, 30 ); } /** * @return bool */ public function release() { return $this->keyedLock->release(); } } ATE/StatusBar.php 0000755 00000002660 14720342453 0007611 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\BackgroundTask\BackgroundTask; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\User; use function WPML\FP\spreadArgs; use WPML\Setup\Option; class StatusBar { /** * @param bool $hasAutomaticJobsInProgress * @param int $needsReviewCount * @param bool $hasBackgroundTasksInProgress * * @return void */ public static function add_hooks( $hasAutomaticJobsInProgress = false, $needsReviewCount = 0, $hasBackgroundTasksInProgress = false ) { if ( User::canManageTranslations() && ( Option::shouldTranslateEverything() || $hasAutomaticJobsInProgress || $needsReviewCount > 0 || $hasBackgroundTasksInProgress ) ) { Hooks::onAction( 'admin_bar_menu', 999 ) ->then( spreadArgs( [ self::class, 'add' ] ) ); } } public static function add( \WP_Admin_Bar $adminBar ) { $adminBar->add_node( [ 'parent' => false, 'id' => 'ate-status-bar', 'title' => '<i id="wpml-status-bar-icon" class="otgs-ico otgs-ico-wpml"></i>' . '<span id="wp-admin-bar-ate-status-bar-badge"></span>', 'href' => false, ] ); $adminBar->add_node( [ 'parent' => 'ate-status-bar', 'id' => 'ate-status-bar-content', 'meta' => [ 'html' => '<div id="wpml-ate-status-bar-content"></div>' ], 'href' => false, ] ); } } ATE/LanguageMapping/InvalidateCacheEndpoint.php 0000755 00000000526 14720342453 0015444 0 ustar 00 <?php namespace WPML\TM\ATE\LanguageMapping; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\TM\API\ATE\CachedLanguageMappings; class InvalidateCacheEndpoint implements IHandler { public function run( Collection $data ) { CachedLanguageMappings::clearCache(); return Either::of( true ); } } ATE/StatusIcons.php 0000755 00000003330 14720342453 0010153 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\TM\API\Jobs; use function WPML\FP\spreadArgs; class StatusIcons implements \IWPML_Backend_Action { /** @var bool */ private $alreadyFound = false; public function add_hooks() { if ( (int) Obj::prop( 'ate_job_id', $_GET ) && self::hasTranslatedStatusInAte() ) { Hooks::onFilter( 'wpml_css_class_to_translation', PHP_INT_MAX, 5 ) ->then( spreadArgs( [ $this, 'setSpinningIconOnPageList' ] ) ); Hooks::onFilter( 'wpml_tm_translation_queue_job_icon', 10, 2 ) ->then( spreadArgs( [ $this, 'setSpinningIconInTranslationQueue' ] ) ); } } private static function hasTranslatedStatusInAte() { return Lst::includes( (int) Obj::prop( 'ate_status', $_GET ), [ \WPML_TM_ATE_API::TRANSLATED, \WPML_TM_ATE_API::DELIVERING ] ); } public function setSpinningIconOnPageList( $default, $postId, $languageCode, $trid, $status ) { if ( ICL_TM_COMPLETE === $status ) { return $default; } if ( $this->alreadyFound ) { return $default; } else { return $this->getIcon( $default, Jobs::getTridJob( $trid, $languageCode ) ); } } public function setSpinningIconInTranslationQueue( $default, $job ) { return $this->getIcon( $default, $job ); } public function getIcon( $default, $job ) { if ( $job && (int) Obj::prop( 'editor_job_id', $job ) === (int) Obj::prop( 'ate_job_id', $_GET ) && Relation::propEq( 'editor', \WPML_TM_Editors::ATE, $job ) && Lst::includes( (int) Obj::prop( 'status', $job ), [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ] ) ) { $this->alreadyFound = true; return 'otgs-ico-refresh-spin'; } else { return $default; } } } ATE/REST/class-wpml-tm-ate-required-rest-base.php 0000755 00000001271 14720342453 0015423 0 ustar 00 <?php /** * @author OnTheGo Systems */ abstract class WPML_TM_ATE_Required_Rest_Base extends WPML_REST_Base { const REST_NAMESPACE = 'wpml/tm/v1'; /** * WPML_TM_ATE_Required_Rest_Base constructor. */ public function __construct() { parent::__construct( self::REST_NAMESPACE ); } /** * @param WP_REST_Request $request * * @return bool */ public function validate_permission( WP_REST_Request $request ) { return WPML_TM_ATE_Status::is_enabled() && parent::validate_permission( $request ); } /** * @param string $endpoint * * @return string */ static function get_url( $endpoint ) { return get_rest_url( null, '/' . self::REST_NAMESPACE . $endpoint ); } } ATE/REST/class-wpml-tm-rest-ate-api.php 0000755 00000002755 14720342453 0013454 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_REST_ATE_API extends WPML_TM_ATE_Required_Rest_Base { const CAPABILITY_CREATE = 'manage_translations'; const CAPABILITY_READ = 'translate'; private $api; /** * WPML_TM_REST_AMS_Clients constructor. * * @param WPML_TM_ATE_API $api */ public function __construct( WPML_TM_ATE_API $api ) { parent::__construct(); $this->api = $api; } function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( '/ate/jobs', array( 'methods' => 'POST', 'callback' => array( $this, 'create_jobs' ), ) ); parent::register_route( '/ate/jobs/(?P<ateJobId>\d+)', array( 'methods' => 'GET', 'callback' => array( $this, 'get_job' ), ) ); } /** * @param WP_REST_Request $request * * @return array|WP_Error * @throws \InvalidArgumentException */ public function create_jobs( WP_REST_Request $request ) { return $this->api->create_jobs( $request->get_params() ); } /** * @param WP_REST_Request $request * * @return array|WP_Error * @throws \InvalidArgumentException */ public function get_job( WP_REST_Request $request ) { $ate_job_id = $request->get_param( 'ateJobId' ); return $this->api->get_job( $ate_job_id ); } function get_allowed_capabilities( WP_REST_Request $request ) { if ( 'GET' === $request->get_method() ) { return array( self::CAPABILITY_CREATE, self::CAPABILITY_READ ); } return self::CAPABILITY_CREATE; } } ATE/REST/Sync.php 0000755 00000005362 14720342453 0007374 0 ustar 00 <?php namespace WPML\TM\ATE\REST; use WP_REST_Request; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\TM\API\Jobs; use WPML\TM\ATE\Sync\Arguments; use WPML\TM\ATE\Sync\Process; use WPML\TM\ATE\Sync\Result; use WPML\TM\ATE\SyncLock; use WPML\TM\REST\Base; use WPML_TM_ATE_AMS_Endpoints; use WPML_TM_ATE_Job; use function WPML\Container\make; class Sync extends Base { /** * @return array */ public function get_routes() { return [ [ 'route' => WPML_TM_ATE_AMS_Endpoints::SYNC_JOBS, 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'sync' ], 'args' => [ 'lockKey' => self::getStringType(), 'ateToken' => self::getStringType(), 'page' => self::getIntType(), 'numberOfPages' => self::getIntType(), ], ], ], ]; } /** * @param WP_REST_Request $request * * @return array */ public function get_allowed_capabilities( WP_REST_Request $request ) { return [ 'manage_options', 'manage_translations', 'translate', ]; } /** * @param WP_REST_Request $request * * @return array * @throws \Auryn\InjectionException */ public function sync( WP_REST_Request $request ) { $args = new Arguments(); $args->ateToken = $request->get_param( 'ateToken' ); $args->page = $request->get_param( 'nextPage' ); $args->numberOfPages = $request->get_param( 'numberOfPages' ); $args->includeManualAndLongstandingJobs = $request->get_param( 'includeManualAndLongstandingJobs' ); $lock = make( SyncLock::class ); $lockKey = $lock->create( $request->get_param( 'lockKey' ) ); if ( $lockKey ) { $result = make( Process::class )->run( $args ); $result->lockKey = $lockKey; $this->fallback_to_unstuck_completed_jobs( $result->jobs ); } else { $result = new Result(); } return (array) $result; } /** * The job was already completed, but for some reason it got into a * different status afterwards. WPML confirms a complete translation * to ATE and then ATE set the job status to "Delivered". So, it's safe * at this point to set the job status to "Completed". * * See wpmldev-2801. * * @param array $jobs */ private function fallback_to_unstuck_completed_jobs( &$jobs ) { if ( ! is_array( $jobs ) ) { return; } foreach ( $jobs as $job ) { if ( ! is_object( $job ) || ! property_exists( $job, 'jobId' ) || ! property_exists( $job, 'ateStatus' ) || ! property_exists( $job, 'status' ) ) { continue; } if ( WPML_TM_ATE_Job::ATE_JOB_DELIVERED === $job->ateStatus ) { $job->status = ICL_TM_COMPLETE; Jobs::setStatus( $job->jobId, ICL_TM_COMPLETE ); } } } } ATE/REST/class-wpml-tm-rest-ate-api-factory.php 0000755 00000000373 14720342453 0015113 0 ustar 00 <?php class WPML_TM_REST_ATE_API_Factory extends WPML_REST_Factory_Loader { /** * @return \WPML_TM_REST_ATE_API * @throws \Auryn\InjectionException */ public function create() { return \WPML\Container\make( '\WPML_TM_REST_ATE_API' ); } } ATE/REST/class-wpml-tm-rest-ate-jobs-factory.php 0000755 00000000503 14720342453 0015272 0 ustar 00 <?php class WPML_TM_REST_ATE_Jobs_Factory extends WPML_REST_Factory_Loader { public function create() { $ate_jobs_records = wpml_tm_get_ate_job_records(); $ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records ); return new WPML_TM_REST_ATE_Jobs( $ate_jobs, wpml_tm_get_ate_jobs_repository() ); } } ATE/REST/class-wpml-tm-rest-ams-clients.php 0000755 00000010050 14720342453 0014336 0 ustar 00 <?php /** * @author OnTheGo Systems */ use WPML\FP\Fns; class WPML_TM_REST_AMS_Clients extends WPML_REST_Base { /** @var WPML_TM_AMS_API */ private $api; private $ams_user_records; /** @var WPML_TM_AMS_Translator_Activation_Records $translator_activation_records */ private $translator_activation_records; /** * @var WPML_TM_MCS_ATE_Strings */ private $strings; public function __construct( WPML_TM_AMS_API $api, WPML_TM_AMS_Users $ams_user_records, WPML_TM_AMS_Translator_Activation_Records $translator_activation_records, WPML_TM_MCS_ATE_Strings $strings ) { parent::__construct( 'wpml/tm/v1' ); $this->api = $api; $this->ams_user_records = $ams_user_records; $this->translator_activation_records = $translator_activation_records; $this->strings = $strings; } function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( '/ams/register_manager', array( 'methods' => 'POST', 'callback' => array( $this, 'register_manager' ), ) ); parent::register_route( '/ams/synchronize/translators', array( 'methods' => 'GET', 'callback' => array( $this, 'synchronize_translators' ), ) ); parent::register_route( '/ams/synchronize/managers', array( 'methods' => 'GET', 'callback' => array( $this, 'synchronize_managers' ), ) ); parent::register_route( '/ams/status', array( 'methods' => 'GET', 'callback' => array( $this, 'get_status' ), ) ); parent::register_route( '/ams/console', array( 'methods' => 'GET', 'callback' => array( $this, 'get_console' ), ) ); parent::register_route( '/ams/engines', array( 'methods' => 'GET', 'callback' => array( $this, 'get_translation_engines' ), ) ); parent::register_route( '/ams/engines', array( 'methods' => 'POST', 'callback' => array( $this, 'update_translation_engines' ), ) ); } /** * @return array|WP_Error * @throws \InvalidArgumentException */ public function register_manager() { $current_user = wp_get_current_user(); $translators = $this->ams_user_records->get_translators(); $managers = $this->ams_user_records->get_managers(); $handleError = function ( $error ) { return [ 'enabled' => false, 'error' => $error ]; }; return $this->api->register_manager( $current_user, $translators, $managers ) ->coalesce( $handleError, Fns::always( [ 'enabled' => true ] ) ) ->get(); } /** * @return array|WP_Error * @throws \InvalidArgumentException */ public function synchronize_translators() { $translators = $this->ams_user_records->get_translators(); $result = $this->api->synchronize_translators( $translators ); if ( is_wp_error( $result ) ) { return $result; } $this->translator_activation_records->update( $result['translators'] ); return array( 'result' => $result ); } /** * @return array|WP_Error * @throws \InvalidArgumentException */ public function synchronize_managers() { $managers = $this->ams_user_records->get_managers(); $result = $this->api->synchronize_managers( $managers ); if ( is_wp_error( $result ) ) { return $result; } return array( 'result' => $result ); } /** * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function get_status() { return $this->api->get_status(); } public function get_console() { return $this->strings->get_auto_login(); } function get_allowed_capabilities( WP_REST_Request $request ) { return array( 'manage_translations', 'manage_options' ); } public function get_translation_engines() { return $this->api->get_translation_engines(); } public function update_translation_engines( WP_REST_Request $request ) { $params = $request->get_json_params(); $result = $this->api->update_translation_engines( $params ); if ( ! is_wp_error( $result ) ) { do_action( 'wpml_tm_ate_translation_engines_updated' ); } return $result; } } ATE/REST/class-wpml-tm-rest-ams-clients-factory.php 0000755 00000000407 14720342453 0016010 0 ustar 00 <?php class WPML_TM_REST_AMS_Clients_Factory extends WPML_REST_Factory_Loader { /** * @return \WPML_TM_REST_AMS_Clients * @throws \Auryn\InjectionException */ public function create() { return \WPML\Container\make( '\WPML_TM_REST_AMS_Clients' ); } } ATE/REST/class-wpml-tm-rest-xliff-factory.php 0000755 00000000220 14720342453 0014672 0 ustar 00 <?php class WPML_TM_REST_XLIFF_Factory extends WPML_REST_Factory_Loader { public function create() { return new WPML_TM_REST_XLIFF(); } } ATE/REST/class-wpml-tm-rest-xliff.php 0000755 00000002310 14720342453 0013227 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_REST_XLIFF extends WPML_TM_ATE_Required_Rest_Base { const CAPABILITY = 'translate'; function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( '/xliff/fetch/(?P<jobId>\d+)', array( 'methods' => 'GET', 'callback' => array( $this, 'fetch_xliff' ), ) ); } /** * @param WP_REST_Request $request * * @return array * @throws \InvalidArgumentException */ public function fetch_xliff( WP_REST_Request $request ) { $result = null; $wpml_translation_job_factory = wpml_tm_load_job_factory(); $iclTranslationManagement = wpml_load_core_tm(); $job_id = $request->get_param( 'jobId' ); $writer = new WPML_TM_Xliff_Writer( $wpml_translation_job_factory ); $xliff = base64_encode( $writer->generate_job_xliff( $job_id ) ); $job = $iclTranslationManagement->get_translation_job( (int) $job_id, false, false, 1 ); $result = array( 'content' => $xliff, 'sourceLang' => $job->source_language_code, 'targetLang' => $job->language_code, ); return $result; } function get_allowed_capabilities( WP_REST_Request $request ) { return self::CAPABILITY; } } ATE/REST/class-wpml-tm-rest-ate-jobs.php 0000755 00000003273 14720342453 0013634 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_REST_ATE_Jobs extends WPML_TM_ATE_Required_Rest_Base { const CAPABILITY = 'manage_translations'; private $ate_jobs; /** @var WPML_TM_ATE_Job_Repository */ private $job_repository; /** * WPML_TM_REST_ATE_Jobs constructor. * * @param WPML_TM_ATE_Jobs $ate_jobs * @param WPML_TM_ATE_Job_Repository $job_repository */ public function __construct( WPML_TM_ATE_Jobs $ate_jobs, WPML_TM_ATE_Job_Repository $job_repository ) { parent::__construct(); $this->ate_jobs = $ate_jobs; $this->job_repository = $job_repository; } function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( WPML_TM_ATE_AMS_Endpoints::STORE_JOB, array( 'methods' => 'POST', 'callback' => array( $this, 'store_ate_job' ), 'args' => array( 'wpml_job_id' => array( 'required' => true, 'type' => 'string', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ), ), 'ate_job_data' => array( 'required' => true, 'type' => 'array', ), ), ) ); } /** * @param WP_REST_Request $request * * @return bool * @throws \InvalidArgumentException */ public function store_ate_job( WP_REST_Request $request ) { $wpml_job_id = $request->get_param( 'wpml_job_id' ); $ate_job_data = $request->get_param( 'ate_job_data' ); $this->ate_jobs->store( $wpml_job_id, $ate_job_data ); return true; } function get_allowed_capabilities( WP_REST_Request $request ) { return self::CAPABILITY; } } ATE/REST/FixJob.php 0000755 00000006602 14720342453 0007637 0 ustar 00 <?php namespace WPML\TM\ATE\REST; use WPML\TM\ATE\ReturnedJobs; use WP_REST_Request; use WPML\Rest\Adaptor; use WPML\TM\REST\Base; use WPML_TM_ATE_AMS_Endpoints; use \WPML_TM_ATE_API; use \WPML_TM_ATE_Jobs; use \WPML_TM_Jobs_Repository; use WPML\FP\Obj; use WPML\TM\ATE\API\RequestException; use WPML\TM\ATE\Log\Entry; use WPML\TM\ATE\Log\EventsTypes; class FixJob extends Base { /** * @var WPML_TM_ATE_Jobs */ private $ateJobs; /** * @var WPML_TM_ATE_API */ private $ateApi; /** * @var WPML_TM_Jobs_Repository */ private $jobsRepository; const PARAM_ATE_JOB_ID = 'ateJobId'; const PARAM_WPML_JOB_ID = 'jobId'; public function __construct( Adaptor $adaptor, WPML_TM_ATE_API $ateApi, WPML_TM_ATE_Jobs $ateJobs ) { parent::__construct( $adaptor ); $this->ateApi = $ateApi; $this->ateJobs = $ateJobs; $this->jobsRepository = wpml_tm_get_jobs_repository(); } /** * @return array */ public function get_routes() { return [ [ 'route' => WPML_TM_ATE_AMS_Endpoints::FIX_JOB, 'args' => [ 'methods' => 'GET', 'callback' => [ $this, 'fix_job' ], ], ], ]; } /** * @param WP_REST_Request $request * * @return array */ public function get_allowed_capabilities( WP_REST_Request $request ) { return [ 'manage_options', 'manage_translations', 'translate', ]; } /** * @param WP_REST_Request $request * * @return bool[] */ public function fix_job( WP_REST_Request $request ) { try { $ateJobId = $request->get_param( self::PARAM_ATE_JOB_ID ); $wpmlJobId = $request->get_param( self::PARAM_WPML_JOB_ID ); $processedJobResult = $this->process( $ateJobId, $wpmlJobId ); if ( $processedJobResult ) { return [ 'completed' => true, 'error' => false ]; } } catch ( \Exception $e ) { $this->logException( $e, [ 'ateJobId' => $ateJobId, 'wpmlJobId' => $wpmlJobId ] ); return [ 'completed' => false, 'error' => true ]; } return [ 'completed' => false, 'error' => false ]; } /** * Processes the job status. * * @param $ateJobId * @param $wpmlJobId * * @return bool * @throws RequestException */ public function process( $ateJobId, $wpmlJobId ) { $ateJob = $this->ateApi->get_job( $ateJobId )->$ateJobId; $xliffUrl = Obj::prop('translated_xliff', $ateJob); if ( $xliffUrl ) { $xliffContent = $this->ateApi->get_remote_xliff_content( $xliffUrl, [ 'jobId' => $wpmlJobId, 'ateJobId' => $ateJobId ] ); $receivedWpmlJobId = $this->ateJobs->apply( $xliffContent ); if ( $receivedWpmlJobId && intval( $receivedWpmlJobId ) !== intval( $wpmlJobId ) ) { $error_message = sprintf( 'The received wpmlJobId (%s) does not match (%s).', $receivedWpmlJobId, $wpmlJobId ); throw new \Exception( $error_message ); } if ( $receivedWpmlJobId ) { return true; } } return false; } /** * @param \Exception $e * @param array|null $job */ private function logException( \Exception $e, $job = null ) { $entry = new Entry(); $entry->description = $e->getMessage(); if ( $job ) { $entry->ateJobId = Obj::prop('ateJobId', $job); $entry->wpmlJobId = Obj::prop('wpmlJobId', $job); $entry->extraData = [ 'downloadUrl' => Obj::prop('url', $job) ]; } if ( $e instanceof RequestException ) { $entry->eventType = EventsTypes::SERVER_XLIFF; } else { $entry->eventType = EventsTypes::JOB_DOWNLOAD; } wpml_tm_ate_ams_log( $entry ); } } ATE/REST/PublicReceive.php 0000755 00000005262 14720342453 0011200 0 ustar 00 <?php namespace WPML\TM\ATE\REST; use WPML\FP\Relation; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\TM\API\ATE; use WPML\TM\API\Jobs; use WPML\TM\ATE\Review\ReviewStatus; use WPML\TM\ATE\SyncLock; use function WPML\Container\make; use function WPML\FP\curryN; use function WPML\FP\pipe; /** * @author OnTheGo Systems */ class PublicReceive extends \WPML_TM_ATE_Required_Rest_Base { const CODE_LOCKED = 423; const CODE_UNPROCESSABLE_ENTITY = 422; const CODE_OK = 200; const ENDPOINT_JOBS_RECEIVE = '/ate/jobs/receive/'; function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( self::ENDPOINT_JOBS_RECEIVE . '(?P<wpmlJobId>\d+)', array( 'methods' => 'GET', 'callback' => array( $this, 'receive_ate_job' ), 'args' => array( 'wpmlJobId' => array( 'required' => true, 'type' => 'int', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ), ), ), 'permission_callback' => '__return_true', ) ); } public function get_allowed_capabilities( \WP_REST_Request $request ) { return []; } /** * @param \WP_REST_Request $request * * @return true|\WP_Error */ public function receive_ate_job( \WP_REST_Request $request ) { $wpmlJobId = $request->get_param( 'wpmlJobId' ); $lock = make( SyncLock::class ); $lockKey = $lock->create( 'publicReceive' ); if ( ! $lockKey ) { return new \WP_Error( self::CODE_LOCKED ); } $skipEditReviewJobs = Logic::complement( Relation::propEq( 'review_status', ReviewStatus::EDITING ) ); $ateAPI = make( ATE::class ); $getXLIFF = pipe( Obj::prop( 'job_id' ), Fns::safe( [ $ateAPI, 'checkJobStatus' ] ), Fns::map( Obj::prop( 'translated_xliff' ) ) ); $applyTranslations = Fns::converge( Fns::liftA3( curryN( 3, [ $ateAPI, 'applyTranslation' ] ) ), [ Fns::safe( Obj::prop( 'job_id' ) ), Fns::safe( Obj::prop( 'original_doc_id' ) ), $getXLIFF ] ); $result = Maybe::of( $wpmlJobId ) ->map( Jobs::get() ) ->filter( $skipEditReviewJobs ) ->chain( $applyTranslations ) ->map( Fns::always( new \WP_REST_Response( null, self::CODE_OK ) ) ) ->getOrElse( new \WP_Error( self::CODE_UNPROCESSABLE_ENTITY ) ); $lock->release(); return $result; } /** * @param int $wpml_job_id * * @return string */ public static function get_receive_ate_job_url( $wpml_job_id ) { return self::get_url( self::ENDPOINT_JOBS_RECEIVE . $wpml_job_id ); } } ATE/REST/Download.php 0000755 00000002351 14720342453 0010222 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\TM\ATE\REST; use WP_REST_Request; use WPML\Collect\Support\Collection; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\TM\API\Jobs; use WPML\TM\ATE\Download\Process; use WPML\TM\ATE\Review\PreviewLink; use WPML\TM\ATE\Review\ReviewStatus; use WPML\TM\ATE\Review\StatusIcons; use WPML\TM\ATE\SyncLock; use WPML\TM\REST\Base; use WPML_TM_ATE_AMS_Endpoints; use function WPML\Container\make; use function WPML\FP\pipe; class Download extends Base { /** * @return array */ public function get_routes() { return [ [ 'route' => WPML_TM_ATE_AMS_Endpoints::DOWNLOAD_JOBS, 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'download' ], ], ], ]; } /** * @param WP_REST_Request $request * * @return array */ public function get_allowed_capabilities( WP_REST_Request $request ) { return [ 'manage_options', 'manage_translations', 'translate', ]; } public function download( WP_REST_Request $request ) { $lock = make( SyncLock::class ); if ( ! $lock->create( $request->get_param( 'lockKey' ) ) ) { return []; } return make( Process::class )->run( $request->get_param( 'jobs' ) )->all(); } } ATE/REST/Retry.php 0000755 00000001663 14720342453 0007565 0 ustar 00 <?php namespace WPML\TM\ATE\REST; use WP_REST_Request; use WPML\TM\ATE\Retry\Arguments; use WPML\TM\ATE\Retry\Process; use WPML\TM\REST\Base; use WPML_TM_ATE_AMS_Endpoints; use function WPML\Container\make; class Retry extends Base { /** * @return array */ public function get_routes() { return [ [ 'route' => WPML_TM_ATE_AMS_Endpoints::RETRY_JOBS, 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'retry' ], ], ], ]; } /** * @param WP_REST_Request $request * * @return array */ public function get_allowed_capabilities( WP_REST_Request $request ) { return [ 'manage_options', 'manage_translations', 'translate', ]; } /** * @param WP_REST_Request $request * * @return array * @throws \Auryn\InjectionException */ public function retry( WP_REST_Request $request ) { return (array) make( Process::class )->run( $request->get_param( 'jobsToProcess' ) ); } } ATE/Download/Job.php 0000755 00000001741 14720342453 0010161 0 ustar 00 <?php namespace WPML\TM\ATE\Download; class Job { /** @var int $ateJobId */ public $ateJobId; /** @var string $url */ public $url; /** @var int */ public $ateStatus; /** * This property is not part of the database data, * but it can be added when the job is downloaded * to provide more information to the UI. * * @var int $jobId */ public $jobId; /** @var int */ public $status = ICL_TM_IN_PROGRESS; /** * @param \stdClass $item * * @return Job */ public static function fromAteResponse( \stdClass $item ) { $job = new self(); $job->ateJobId = $item->ate_id; $job->url = $item->download_link; $job->ateStatus = (int) $item->status; $job->jobId = (int) $item->id; return $job; } /** * @param \stdClass $row * * @return Job */ public static function fromDb( \stdClass $row ) { $job = new self(); $job->ateJobId = $row->editor_job_id; $job->url = $row->download_url; return $job; } } ATE/Download/Process.php 0000755 00000006765 14720342453 0011100 0 ustar 00 <?php namespace WPML\TM\ATE\Download; use Exception; use Error; use WPML\Collect\Support\Collection; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\TM\ATE\API\RequestException; use WPML\TM\ATE\Jobs; use WPML\TM\ATE\Log\Entry; use WPML\TM\ATE\Log\EventsTypes; use WPML\TM\ATE\Review\ReviewStatus; use WPML_TM_ATE_API; use function WPML\FP\pipe; class Process { /** @var Consumer $consumer */ private $consumer; /** @var WPML_TM_ATE_API $ateApi */ private $ateApi; public function __construct( Consumer $consumer, WPML_TM_ATE_API $ateApi ) { $this->consumer = $consumer; $this->ateApi = $ateApi; } /** * @param array $jobs * * @return Collection */ public function run( $jobs ) { $appendNeedsReviewAndAutomaticValues = function ( $job ) { $data = \WPML\TM\API\Jobs::get( Obj::prop('jobId', $job) ); $job = Obj::assoc( 'needsReview', ReviewStatus::doesJobNeedReview( $data ), $job ); $job = Obj::assoc( 'automatic', (bool) Obj::prop( 'automatic', $data ), $job ); return $job; }; $downloadJob = function( $job ) { $processedJob = null; try { $processedJob = $this->consumer->process( $job ); if ( ! $processedJob ) { global $iclTranslationManagement; $message = 'The translation job could not be applied.'; if ( $iclTranslationManagement->messages_by_type( 'error' ) ) { $stringifyError = pipe( Lst::pluck( 'text' ), Lst::join( ' ' ) ); $message .= ' ' . $stringifyError( $iclTranslationManagement->messages_by_type( 'error ') ); } throw new Exception( $message ); } } catch ( Exception $e ) { $this->logException( $e, $processedJob ?: $job ); } catch ( Error $e ) { $this->logError( $e, $processedJob ?: $job ); } return $processedJob; }; $jobs = \wpml_collect( $jobs )->map( $downloadJob )->filter()->values()->map( $appendNeedsReviewAndAutomaticValues ); $this->acknowledgeAte( $jobs ); do_action( 'wpml_tm_ate_jobs_downloaded', $jobs ); return $jobs; } private function acknowledgeAte( Collection $processedJobs ) { if ( $processedJobs->count() ) { $this->ateApi->confirm_received_job( $processedJobs->pluck( 'ateJobId' )->toArray() ); } } /** * @param Exception $e * @param Job|null $job */ private function logException( Exception $e, $job = null ) { $entry = new Entry(); $entry->description = $e->getMessage(); $avoidDuplication = false; if ( $job ) { $entry->ateJobId = Obj::prop('ateJobId', $job); $entry->wpmlJobId = Obj::prop('jobId', $job); $entry->extraData = [ 'downloadUrl' => Obj::prop('url', $job) ]; } if ( $e instanceof RequestException ) { $entry->eventType = EventsTypes::SERVER_XLIFF; if ( $e->getData() ) { $entry->extraData += is_array( $e->getData() ) ? $e->getData() : [ $e->getData() ]; } $avoidDuplication = $e->shouldAvoidLogDuplication(); } else { $entry->eventType = EventsTypes::JOB_DOWNLOAD; } wpml_tm_ate_ams_log( $entry, $avoidDuplication ); } /** * @param Error $e * @param Job|null $job */ private function logError( Error $e, $job = null ) { $entry = new Entry(); $entry->description = sprintf( '%s %s:%s', $e->getMessage(), $e->getFile(), $e->getLine() ); if ( $job ) { $entry->ateJobId = Obj::prop( 'ateJobId', $job ); $entry->wpmlJobId = Obj::prop( 'jobId', $job ); $entry->extraData = [ 'downloadUrl' => Obj::prop( 'url', $job ) ]; } $entry->eventType = EventsTypes::JOB_DOWNLOAD; wpml_tm_ate_ams_log( $entry, true ); } } ATE/Download/Consumer.php 0000755 00000001550 14720342453 0011240 0 ustar 00 <?php namespace WPML\TM\ATE\Download; use Exception; use WPML\FP\Obj; use WPML\TM\ATE\ReturnedJobs; use WPML_TM_ATE_API; use WPML_TM_ATE_Jobs; class Consumer { /** @var WPML_TM_ATE_API $ateApi */ private $ateApi; /** @var WPML_TM_ATE_Jobs $ateJobs */ private $ateJobs; public function __construct( WPML_TM_ATE_API $ateApi, WPML_TM_ATE_Jobs $ateJobs ) { $this->ateApi = $ateApi; $this->ateJobs = $ateJobs; } /** * @param $job * * @return array|\stdClass|false * @throws Exception */ public function process( $job ) { $xliffContent = $this->ateApi->get_remote_xliff_content( Obj::prop( 'url', $job ), $job ); $wpmlJobId = $this->ateJobs->apply( $xliffContent ); if ( $wpmlJobId ) { $job = Obj::assoc( 'jobId', $wpmlJobId, $job ); $job = Obj::assoc( 'status', ICL_TM_COMPLETE, $job ); return $job; } return false; } } ATE/class-wpml-tm-ams-users.php 0000755 00000001102 14720342453 0012304 0 ustar 00 <?php use WPML\User\UsersByCapsRepository; use WPML\LIB\WP\User; class WPML_TM_AMS_Users { /** @var UsersByCapsRepository */ private $userByCapsRepository; public function __construct( UsersByCapsRepository $userByCapsRepository ) { $this->userByCapsRepository = $userByCapsRepository; } public function get_translators() { return $this->userByCapsRepository->get( [ User::CAP_TRANSLATE, User::CAP_ADMINISTRATOR ] ); } public function get_managers() { return $this->userByCapsRepository->get( [ User::CAP_MANAGE_TRANSLATIONS, User::CAP_ADMINISTRATOR ] ); } } ATE/class-wpml-tm-ate-ams-endpoints.php 0000755 00000031675 14720342453 0013737 0 ustar 00 <?php /** * @author OnTheGo Systems * * AMS: https://git.onthegosystems.com/ate/ams/wikis/home * ATE: https://git.onthegosystems.com/ate/ams/wikis/home (https://bitbucket.org/emartini_crossover/ate/wiki/browse/API/V1/jobs) */ class WPML_TM_ATE_AMS_Endpoints { const AMS_BASE_URL = 'https://ams.wpml.org'; const ATE_BASE_URL = 'https://ate.wpml.org'; const ATE_JOB_STATUS_CREATED = 0; const ATE_JOB_STATUS_TRANSLATING = 1; const ATE_JOB_STATUS_TRANSLATED = 6; const ATE_JOB_STATUS_DELIVERING = 7; const ATE_JOB_STATUS_DELIVERED = 8; const ATE_JOB_STATUS_EDITED = 15; /** * AMS */ const ENDPOINTS_AUTO_LOGIN = '/panel/autologin'; const ENDPOINTS_CLIENTS = '/api/wpml/clients'; const ENDPOINTS_CONFIRM = '/api/wpml/jobs/confirm'; const ENDPOINTS_EDITOR = '/api/wpml/jobs/{job_id}/open?translator={translator_email}&return_url={return_url}'; const ENDPOINTS_SUBSCRIPTION = '/api/wpml/websites/translators/{translator_email}/enable'; const ENDPOINTS_SUBSCRIPTION_STATUS = '/api/wpml/websites/{WEBSITE_UUID}/translators/{translator_email}'; const ENDPOINTS_WEBSITES = '/api/wpml/websites'; const ENDPOINTS_CREDITS = '/api/wpml/credits'; const ENDPOINTS_RESUME_ALL = '/api/wpml/jobs/resume/all'; const ENDPOINTS_SEND_SITEKEY = '/api/wpml/websites/assign_key'; const ENDPOINTS_TRANSLATION_ENGINES = '/api/wpml/engines'; /** * AMS CLONED SITES */ const ENDPOINTS_SITE_COPY = '/api/wpml/websites/copy'; const ENDPOINTS_SITE_MOVE = '/api/wpml/websites/move'; const ENDPOINTS_SITE_CONFIRM = '/api/wpml/websites/confirm'; const ENDPOINTS_COPY_ATTACHED = '/api/wpml/websites/copy_attached'; /** * ATE */ const ENDPOINTS_JOB = '/api/wpml/job'; const ENDPOINTS_JOBS = '/api/wpml/jobs'; const ENDPOINT_JOBS_BY_WPML_JOB_IDS = '/api/wpml/jobs/wpml'; const ENDPOINT_JOBS_STATUSES = '/api/wpml/jobs/statuses'; const ENDPOINTS_MANAGERS = '/api/wpml/websites/translation_managers'; const ENDPOINTS_SITE = '/api/wpml/websites/create_unique'; const ENDPOINTS_STATUS = '/api/wpml/access_keys/{SHARED_KEY}/status'; const ENDPOINTS_TRANSLATORS = '/api/wpml/websites/translators'; const ENDPOINT_SOURCE_ID_MIGRATION = '/api/wpml/migration'; const ENDPOINTS_SYNC_ALL = '/api/wpml/sync/all'; const ENDPOINTS_SYNC_PAGE = '/api/wpml/sync/page'; const ENDPOINTS_RETRANSLATE = '/api/wpml/retranslations/sync'; const ENDPOINTS_CLONE_JOB = '/api/wpml/jobs/%s/clone'; const ENDPOINTS_CANCEL_JOBS = '/api/wpml/jobs/cancel'; const ENDPOINTS_HIDE_JOBS = '/api/wpml/jobs/canceled_on_wpml'; const ENDPOINTS_LANGUAGES = '/api/wpml/languages'; const ENDPOINTS_LANGUAGES_MAPPING = '/api/wpml/languages/mappings'; const ENDPOINTS_LANGUAGES_MAPPING_DELETE = '/api/wpml/languages/delete_mapping'; const ENDPOINTS_LANGUAGES_CHECK_PAIRS = '/api/wpml/languages/check_pairs'; const ENDPOINTS_LANGUAGES_SHOW = '/api/wpml/languages/%s'; const SERVICE_AMS = 'ams'; const SERVICE_ATE = 'ate'; const STORE_JOB = '/ate/jobs/store'; const SYNC_JOBS = '/ate/jobs/sync'; const DOWNLOAD_JOBS = '/ate/jobs/download'; const RETRY_JOBS = '/ate/jobs/retry'; const FIX_JOB = '/ate/jobs/(?P<ateJobId>\d+)/fix'; /** * ICL to ATE migration */ const ENDPOINTS_IMPORT_TRANSLATORS_FROM_ICL = '/api/wpml/icl/translators/import'; const ENDPOINTS_START_MIGRATION_IMPORT_FROM_ICL = '/api/wpml/icl/translations/import/start'; const ENDPOINTS_CHECK_STATUS_MIGRATION_IMPORT_FROM_ICL = '/api/wpml/icl/translations/import/status'; /** * @return string * @throws \InvalidArgumentException */ public function get_ams_auto_login() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_AUTO_LOGIN ); } /** * @param string $service * @param string $endpoint * @param array|null $query_string * * @return string * @throws \InvalidArgumentException */ public function get_endpoint_url( $service, $endpoint, array $query_string = null ) { $url = $this->get_base_url( $service ) . $endpoint; if ( $query_string ) { $url_parts = wp_parse_url( $url ); $query = array(); if ( $url_parts && array_key_exists( 'query', $url_parts ) ) { parse_str( $url_parts['query'], $query ); } foreach ( $query_string as $key => $value ) { if ( $value ) { if ( is_scalar( $value ) ) { $query[ $key ] = $value; } else { $query[ $key ] = implode( ',', $value ); } } } $url_parts['query'] = http_build_query( $query ); $url = http_build_url( $url_parts ); } return $url; } /** * @param $service * * @return string * @throws \InvalidArgumentException */ public function get_base_url( $service ) { switch ( $service ) { case self::SERVICE_AMS: return $this->get_AMS_base_url(); case self::SERVICE_ATE: return $this->get_ATE_base_url(); default: throw new InvalidArgumentException( $service . ' is not a valid argument' ); } } private function get_AMS_base_url() { return $this->get_service_base_url( self::SERVICE_AMS ); } private function get_ATE_base_url() { return $this->get_service_base_url( self::SERVICE_ATE ); } private function get_service_base_url( $service ) { $constant_name = strtoupper( $service ) . '_BASE_URL'; $url = constant( __CLASS__ . '::' . $constant_name ); if ( defined( $constant_name ) ) { $url = constant( $constant_name ); } if ( getenv( $constant_name ) ) { $url = getenv( $constant_name ); } return $url; } public function get_AMS_host() { return $this->get_service_host( self::SERVICE_AMS ); } public function get_ATE_host() { return $this->get_service_host( self::SERVICE_ATE ); } private function get_service_host( $service ) { $base_url = $this->get_service_base_url( $service ); $url_parts = wp_parse_url( $base_url ); return $url_parts['host']; } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_register_client() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SITE ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_status() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_STATUS ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_synchronize_managers() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_MANAGERS ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_synchronize_translators() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_TRANSLATORS ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_site_copy() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SITE_COPY ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_copy_attached() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_COPY_ATTACHED ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_site_move() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SITE_MOVE ); } /** * @return string * @throws \InvalidArgumentException */ public function get_ams_site_confirm() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SITE_CONFIRM ); } /** * @return string * @throws \InvalidArgumentException */ public function get_enable_subscription() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SUBSCRIPTION ); } /** * @return string * @throws \InvalidArgumentException */ public function get_subscription_status() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SUBSCRIPTION_STATUS ); } /** * @param int|string|array $job_params * * @return string * @throws \InvalidArgumentException */ public function get_ate_confirm_job( $job_params = null ) { $job_id_part = $this->parse_job_params( $job_params ); return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_CONFIRM . $job_id_part ); } public function get_translation_engines() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_TRANSLATION_ENGINES ); } /** * @param null|int|string|array $job_params * * @return string */ private function parse_job_params( $job_params ) { $job_id_part = ''; if ( $job_params ) { if ( is_array( $job_params ) ) { $job_ids = implode( ',', $job_params ); } else { $job_ids = $job_params; } $job_id_part = '/' . $job_ids; } return $job_id_part; } /** * @return string * @throws \InvalidArgumentException */ public function get_ate_editor() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_EDITOR ); } /** * @param null|int|string|array $job_params * @param null|array $statuses * * @return string * @throws \InvalidArgumentException */ public function get_ate_jobs( $job_params = null, array $statuses = null ) { $job_id_part = $this->parse_job_params( $job_params ); return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_JOBS . $job_id_part, array( 'status' => $statuses ) ); } public function getAteCancelJobs() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_CANCEL_JOBS ); } public function getAteHideJobs() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_HIDE_JOBS ); } public function getLanguages() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_LANGUAGES ); } public function getLanguagesMapping() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_LANGUAGES_MAPPING ); } public function getDeleteLanguagesMapping() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_LANGUAGES_MAPPING_DELETE ); } public function getLanguagesCheckPairs() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_LANGUAGES_CHECK_PAIRS ); } public function getShowLanguage() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_LANGUAGES_SHOW ); } public function startTranlsationMemoryIclMigration(){ return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_START_MIGRATION_IMPORT_FROM_ICL ); } public function checkStatusTranlsationMemoryIclMigration(){ return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_CHECK_STATUS_MIGRATION_IMPORT_FROM_ICL ); } public function importIclTranslators(){ return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_IMPORT_TRANSLATORS_FROM_ICL); } /** * @return string * @throws \InvalidArgumentException */ public function get_ate_job_status( ) { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINT_JOBS_STATUSES ); } /** * @param int() $job_ids * * @return string */ public function get_ate_jobs_by_wpml_job_ids( $job_ids ) { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINT_JOBS_BY_WPML_JOB_IDS, array( 'site_identifier' => wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ), 'wpml_job_ids' => $job_ids, ) ); } /** * @return string */ public function get_websites() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_WEBSITES ); } /** * @return string */ public function get_source_id_migration() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINT_SOURCE_ID_MIGRATION ); } /** * @throws \InvalidArgumentException * @return string */ public function get_retranslate(): string { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_RETRANSLATE ); } /** * @return string */ public function get_sync_all() { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_SYNC_ALL ); } /** * @param string $paginationToken * @param int $page * * @return string */ public function get_sync_page( $paginationToken, $page ) { return $this->get_endpoint_url( self::SERVICE_ATE, self::ENDPOINTS_SYNC_PAGE, [ 'pagination_token' => $paginationToken, 'page' => $page, ] ); } /** * @param int $job_id * * @return string */ public function get_clone_job( $job_id ) { return $this->get_endpoint_url( self::SERVICE_ATE, sprintf( self::ENDPOINTS_CLONE_JOB, $job_id ) ); } /** * @return string * @throws \InvalidArgumentException */ public function get_credits() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_CREDITS ); } /** * @return string * @throws \InvalidArgumentException */ public function get_resume_all() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_RESUME_ALL ); } public function get_send_sitekey() { return $this->get_endpoint_url( self::SERVICE_AMS, self::ENDPOINTS_SEND_SITEKEY ); } } ATE/ReturnedJobs.php 0000755 00000002423 14720342453 0010304 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\FP\Obj; use function WPML\Container\make; /** * @package WPML\TM\ATE */ class ReturnedJobs { /** @var callable(int): int It maps ate_job_id to job_id value inside wp_icl_translate_job table */ private $ateIdToWpmlId; /** * For jobs that are completed in ATE, but belong to a Translation that is currently marked as "Duplicate". * In such cases, we want to get rid of the Duplicate status, otherwise it will not be processed during ATE sync. * * @see \WPML\TM\ATE\Loader::getData * @see \WPML_Meta_Boxes_Post_Edit_HTML::post_edit_languages_duplicate_of How it's handled for CTE. * * @param int $ateJobId * @param callable $ateIdToWpmlId */ public static function removeJobTranslationDuplicateStatus( $ateJobId, callable $ateIdToWpmlId ) { $wpmlJobId = $ateIdToWpmlId( $ateJobId ); if ( $wpmlJobId ) { /** @var \WPML_TM_Records $tm_records */ $tm_records = make( \WPML_TM_Records::class ); $jobTranslation = $tm_records->icl_translate_job_by_job_id( $wpmlJobId ); $translationStatus = $tm_records->icl_translation_status_by_rid( $jobTranslation->rid() ); if ( ICL_TM_DUPLICATE === $translationStatus->status() ) { $translationStatus->update( [ 'status' => ICL_TM_IN_PROGRESS ] ); } } } } ATE/class-wpml-tm-ate.php 0000755 00000005356 14720342453 0011155 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE { const SITE_ID_SCOPE = 'ate'; private $translation_method_ate_enabled; /** * @var WPML_TM_ATE_API */ private $tm_ate_api; /** * @var WPML_TM_ATE_Jobs */ private $tm_ate_jobs; public function is_translation_method_ate_enabled() { if ( null === $this->translation_method_ate_enabled ) { $tm_settings = wpml_get_setting_filter( null, 'translation-management' ); $doc_translation_method = null; if ( $tm_settings && array_key_exists( 'doc_translation_method', $tm_settings ) ) { $doc_translation_method = $tm_settings['doc_translation_method']; } $this->translation_method_ate_enabled = $doc_translation_method === ICL_TM_TMETHOD_ATE; } return $this->translation_method_ate_enabled; } /** * @param int $trid * @param string $language * * @return bool */ public function is_translation_ready_for_post( $trid, $language ) { $translation_status_id = $this->get_translation_status_id_for_post( $trid, $language ); return $translation_status_id && ! in_array( $translation_status_id, array( WPML_TM_ATE_Job::ATE_JOB_CREATED, WPML_TM_ATE_Job::ATE_JOB_IN_PROGRESS ), true ); } /** * @param int $trid * @param string $language * * @return int|bool */ public function get_translation_status_id_for_post( $trid, $language ) { $status_id = false; $ate_job = $this->get_job_data_for_post( $trid, $language ); if ( $ate_job && ! is_wp_error( $ate_job ) ) { $status_id = $ate_job->status_id; } return $status_id; } /** * @param int $trid * @param string $language * * @return array|WP_Error */ public function get_job_data_for_post( $trid, $language ) { $tm_ate_api = $this->get_tm_ate_api(); $tm_ate_jobs = $this->get_tm_ate_jobs(); $core_tm = wpml_load_core_tm(); $job_id = $core_tm->get_translation_job_id( $trid, $language ); $editor = $core_tm->get_translation_job_editor( $trid, $language ); if ( \WPML_TM_Editors::ATE !== strtolower( $editor ) ) { return null; } $ate_job_id = $tm_ate_jobs->get_ate_job_id( $job_id ); $ate_job = $tm_ate_api->get_job( $ate_job_id ); return isset( $ate_job->$ate_job_id ) ? $ate_job->$ate_job_id : $ate_job; } /** * @return WPML_TM_ATE_API */ private function get_tm_ate_api() { if ( null === $this->tm_ate_api ) { $ams_ate_factories = wpml_tm_ams_ate_factories(); $this->tm_ate_api = $ams_ate_factories->get_ate_api(); } return $this->tm_ate_api; } /** * @return WPML_TM_ATE_Jobs */ private function get_tm_ate_jobs() { if ( null === $this->tm_ate_jobs ) { $ate_jobs_records = wpml_tm_get_ate_job_records(); $this->tm_ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records ); } return $this->tm_ate_jobs; } } ATE/sitekey/Sync.php 0000755 00000002044 14720342453 0010266 0 ustar 00 <?php namespace WPML\TM\ATE\Sitekey; use WPML\Core\BackgroundTask\Service\BackgroundTaskService; use WPML\LIB\WP\Hooks; use WPML\WP\OptionManager; use function WPML\Container\make; use function WPML\FP\spreadArgs; class Sync implements \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var BackgroundTaskService */ private $backgroundTaskService; /** * @param BackgroundTaskService $backgroundTaskService */ public function __construct( BackgroundTaskService $backgroundTaskService ) { $this->backgroundTaskService = $backgroundTaskService; } public function add_hooks() { if ( ! OptionManager::getOr( false, 'TM-has-run', self::class ) && \WPML_TM_ATE_Status::is_enabled_and_activated() ) { $this->backgroundTaskService->addOnce( make( Endpoint::class ), wpml_collect( [] ) ); } $clearHasRun = function ( $repo ) { if ( $repo === 'wpml' ) { OptionManager::update( 'TM-has-run', self::class, false ); } }; Hooks::onAction( 'otgs_installer_site_key_update' ) ->then( spreadArgs( $clearHasRun ) ); } } ATE/sitekey/Endpoint.php 0000755 00000002110 14720342453 0011124 0 ustar 00 <?php namespace WPML\TM\ATE\Sitekey; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\Core\BackgroundTask\Model\BackgroundTask; use WPML\FP\Either; use WPML\BackgroundTask\AbstractTaskEndpoint; use WPML\Core\BackgroundTask\Model\TaskEndpointInterface; use WPML\Utilities\Lock; use WPML\WP\OptionManager; use function WPML\Container\make; class Endpoint extends AbstractTaskEndpoint implements IHandler, TaskEndpointInterface { const LOCK_TIME = 30; const MAX_RETRIES = 0; public function isDisplayed() { return false; } public function runBackgroundTask( BackgroundTask $task ) { if( function_exists( 'OTGS_Installer' ) ) { $sitekey = OTGS_Installer()->get_site_key( 'wpml' ); if ( $sitekey && make( \WPML_TM_AMS_API::class )->send_sitekey( $sitekey ) ) { OptionManager::update( 'TM-has-run', Sync::class, true ); } } $task->finish(); return $task; } public function getTotalRecords( Collection $data ) { return 1; } public function getDescription( Collection $data ) { return __('Initializing AMS credentials.', 'sitepress'); } } ATE/class-wpml-tm-ams-translator-activation-records.php 0000755 00000002031 14720342453 0017134 0 ustar 00 <?php class WPML_TM_AMS_Translator_Activation_Records { const USER_META = 'ate_activated'; /** @var WPML_WP_User_Factory $user_factory */ private $user_factory; public function __construct( WPML_WP_User_Factory $user_factory ) { $this->user_factory = $user_factory; } public function is_activated( $user_email ) { return $this->is_user_activated( $this->user_factory->create_by_email( $user_email ) ); } public function is_current_user_activated() { return $this->is_user_activated( $this->user_factory->create_current() ); } public function is_user_activated( WPML_User $user ) { return (bool) $user->get_option( self::USER_META ); } public function set_activated( $user_email, $state ) { $user = $this->user_factory->create_by_email( $user_email ); if ( $user->ID ) { return $user->update_option( self::USER_META, $state ); } } public function update( array $translators ) { foreach ( $translators as $translator ) { $this->set_activated( $translator['email'], $translator['subscription'] ); } } } ATE/Jobs.php 0000755 00000011313 14720342453 0006571 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\Element\API\Languages; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\TM\ATE\Review\ReviewStatus; class Jobs { const LONGSTANDING_AT_ATE_SYNC_COUNT = 100; /** * @param bool $includeLongstanding A long-standing job is an automatic ATE job which we already tried to sync LONGSTANDING_AT_ATE_SYNC_COUNT or more times. * @return int */ public function getCountOfAutomaticInProgress( $includeLongstanding = true ) { global $wpdb; $sql = " SELECT COUNT(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs INNER JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = jobs.rid INNER JOIN {$wpdb->prefix}icl_translations translations ON translations.translation_id = translation_status.translation_id WHERE jobs.job_id IN ( SELECT MAX(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs GROUP BY jobs.rid ) AND jobs.automatic = 1 AND jobs.editor = %s AND translation_status.status = %d AND translations.source_language_code = %s "; if ( ! $includeLongstanding ) { $sql .= " AND jobs.ate_sync_count < %d"; return (int) $wpdb->get_var( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS, Languages::getDefaultCode(), self::LONGSTANDING_AT_ATE_SYNC_COUNT ) ); } return (int) $wpdb->get_var( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS, Languages::getDefaultCode() ) ); } /** * @return int */ public function getCountOfInProgress() { global $wpdb; $sql = " SELECT COUNT(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs INNER JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = jobs.rid WHERE jobs.job_id IN ( SELECT MAX(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs GROUP BY jobs.rid ) AND jobs.editor = %s AND translation_status.status = %d "; return (int) $wpdb->get_var( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS ) ); } /** * @return int */ public function getCountOfNeedsReview() { global $wpdb; $sql = " SELECT COUNT(translation_status.translation_id) FROM {$wpdb->prefix}icl_translation_status translation_status WHERE translation_status.review_status = %s OR translation_status.review_status = %s "; return (int) $wpdb->get_var( $wpdb->prepare( $sql, ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ) ); } /** * It checks whether we have ANY jobs in the DB. It doesn't matter what kind of jobs they are. It can be a job from ATE, CTE or even the Translation Proxy. * * @return bool * @todo This method should not be here as the current class relates solely to ATE jobs, while this method asks for ANY jobs. */ public function hasAny() { global $wpdb; $noOfRowsToFetch = 1; $sql = $wpdb->prepare( "SELECT EXISTS(SELECT %d FROM {$wpdb->prefix}icl_translate_job)", $noOfRowsToFetch ); return boolval( $wpdb->get_var( $sql ) ); } /** * @return bool True if there is at least one job to sync. */ public function hasAnyToSync() { global $wpdb; $sql = " SELECT jobs.job_id FROM {$wpdb->prefix}icl_translate_job jobs INNER JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = jobs.rid WHERE jobs.job_id IN ( SELECT MAX(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs GROUP BY jobs.rid ) AND jobs.editor = %s AND translation_status.status = %d LIMIT 1 "; return (bool) $wpdb->get_var( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS ) ); } /** * This is optimized query for getting the ate job ids to sync. * * @param bool $includeManualAndLongstandingJobs * @return int[] */ public function getATEJobIdsToSync( $includeManualAndLongstandingJobs = true ) { global $wpdb; $sql = " SELECT jobs.editor_job_id FROM {$wpdb->prefix}icl_translate_job jobs INNER JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = jobs.rid WHERE jobs.job_id IN ( SELECT MAX(jobs.job_id) FROM {$wpdb->prefix}icl_translate_job jobs GROUP BY jobs.rid ) AND jobs.editor = %s AND ( translation_status.status = %d OR translation_status.status = %d ) "; if ( ! $includeManualAndLongstandingJobs ) { $sql .= " AND jobs.ate_sync_count < %d AND jobs.automatic = 1"; return $wpdb->get_col( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR, self::LONGSTANDING_AT_ATE_SYNC_COUNT ) ); } return $wpdb->get_col( $wpdb->prepare( $sql, \WPML_TM_Editors::ATE, ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR ) ); } } ATE/API/ClonedSites/SecondaryDomains.php 0000755 00000005025 14720342453 0013766 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\LIB\WP\Option; use WPML\TM\ATE\API\FingerprintGenerator; /** * One physical site can have multiple domains. * In such situation, we have to take a note about it in order to use a proper domain while communicating with AMS/ATE. * * It is a different case than when a user decides to copy or move a site to another domain. * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-2026 */ class SecondaryDomains { const OPTION = 'wpml_tm_ate_secondary_domains'; const ORIGINAL_SITE_URL = 'wpml_tm_ate_original_site_url'; /** * @param string $domain * @param string $originalSiteUrl * * @return string[] */ public function add( $domain, $originalSiteUrl ) { $domains = $this->get(); if ( ! in_array( $domain, $domains, true ) ) { $domains[] = $domain; } Option::update( self::OPTION, $domains ); Option::update( self::ORIGINAL_SITE_URL, $originalSiteUrl ); return $domains; } /** * The purpose of the method is to fall back to the original site URL * in the case when the current site URL is a secondary domain of the same site, * * 1. If the current site URL is the same as the original site URL, * then we can use the current site URL. * 2. If the current site URL is different from the original site URL and is registered as a secondary domain, * then we can use the current site URL. * 3. If the current site URL is different from the original site URL and is not registered as a secondary domain, * then we return the current site url which eventually will cause ATE error with code 421. * * @return string */ public function maybeFallBackToTheOriginalURL( $currentSiteUrl ) { $originalSiteUrl = Option::get( self::ORIGINAL_SITE_URL ); if ( $currentSiteUrl === $originalSiteUrl ) { return $currentSiteUrl; } if ( $this->isRegistered( $currentSiteUrl ) ) { return $originalSiteUrl; // the fallback to the original site URL } return $currentSiteUrl; } /** * @return array{originalSiteUrl: string, aliasDomains: string[]}|null */ public function getInfo() { $domains = $this->get(); if ( ! $domains ) { return null; } return [ 'originalSiteUrl' => Option::get( self::ORIGINAL_SITE_URL ), 'aliasDomains' => $domains, ]; } /** * @return string[] */ private function get() { return Option::getOr( self::OPTION, [] ); } /** * @param string $domain * * @return bool */ private function isRegistered( $domain ) { return in_array( $domain, $this->get(), true ); } } ATE/API/ClonedSites/Report.php 0000755 00000005047 14720342453 0012003 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\FP\Fns; class Report { /** * @var \WPML_TM_AMS_API */ private $apiClient; /** * @var Lock */ private $lock; /** * @var \WPML_TM_ATE_Job_Repository */ private $ateJobsRepository; /** * Update jobs synchronisation * * @var \WPML_TP_Sync_Update_Job */ private $updateJobs; /** * @var \WPML_Translation_Job_Factory */ private $translationJobFactory; /** * @param \WPML_TM_AMS_API $apiClient * @param Lock $lock * @param \WPML_TM_ATE_Job_Repository $ateJobsRepository * @param \WPML_Translation_Job_Factory $translationJobFactory */ public function __construct( \WPML_TM_AMS_API $apiClient, Lock $lock, \WPML_TM_ATE_Job_Repository $ateJobsRepository, \WPML_TP_Sync_Update_Job $updateJobs, \WPML_Translation_Job_Factory $translationJobFactory ) { $this->apiClient = $apiClient; $this->lock = $lock; $this->ateJobsRepository = $ateJobsRepository; $this->updateJobs = $updateJobs; $this->translationJobFactory = $translationJobFactory; } /** * @return true|\WP_Error */ public function move() { $reportResult = $this->apiClient->reportMovedSite(); $result = $this->apiClient->processMoveReport( $reportResult ); if ( $result ) { $this->lock->unlock(); do_action( 'wpml_tm_ate_synchronize_translators' ); } return $result; } /** * @return bool */ public function copy() { return $this->copyWithStrategy( 'reportCopiedSite' ); } /** * @param string $migrationCode * * @return bool */ public function copyWithCredit( $migrationCode ) { return $this->copyWithStrategy( 'reportCopiedSiteWithCreditTransfer', [ $migrationCode ] ); } /** * @param string $copyStrategy * @param mixed[] $arguments * * @return bool */ private function copyWithStrategy( $copyStrategy, $arguments = [] ) { $reportResult = call_user_func_array( [ $this->apiClient, $copyStrategy ], $arguments ); $result = $this->apiClient->processCopyReportConfirmation( $reportResult ); if ( $result ) { $jobsInProgress = $this->ateJobsRepository->get_jobs_to_sync(); /** @var \WPML_TM_Post_Job_Entity $jobInProgress */ foreach ( $jobsInProgress as $jobInProgress ) { $jobInProgress->set_status( ICL_TM_NOT_TRANSLATED ); $this->updateJobs->update_state( $jobInProgress ); $this->translationJobFactory->delete_job_data( $jobInProgress->get_translate_job_id() ); } $this->lock->unlock(); do_action( 'wpml_tm_ate_synchronize_translators' ); } return $result; } } ATE/API/ClonedSites/Endpoints/GetCredits.php 0000755 00000002416 14720342453 0014525 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Lst; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\WordPress; use WPML\TM\API\ATE\Account; use WPML\TM\ATE\ClonedSites\Endpoints\GetCredits\AMSAPIFactory; use WPML\TM\ATE\ClonedSites\FingerprintGeneratorForOriginalSite; use WPML\TM\ATE\ClonedSites\Lock; use function WPML\Container\make; class GetCredits implements IHandler { /** @var AMSAPIFactory */ private $amsAPIFactory; public function __construct( AMSAPIFactory $amsAPIFactory ) { $this->amsAPIFactory = $amsAPIFactory; } public function run( Collection $data ) { $getCredit = function () { try { $credits = $this->amsAPIFactory->create()->getCredits(); return is_array( $credits ) ? Either::right( $credits ) : Either::left( __( 'Communication error', 'sitepress' ) ); } catch ( \Exception $e ) { return Either::left( __( 'Communication error', 'sitepress' ) ); } }; $addCreditEndpointToWhitelist = function ( $otherEndpoints ) { return Lst::append( \WPML_TM_ATE_AMS_Endpoints::ENDPOINTS_CREDITS, $otherEndpoints ); }; return Hooks::callWithFilter( $getCredit, 'wpml_ate_locked_endpoints_whitelist', $addCreditEndpointToWhitelist ); } } ATE/API/ClonedSites/Endpoints/GetCredits/AMSAPIFactory.php 0000755 00000001272 14720342453 0017026 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites\Endpoints\GetCredits; use WPML\TM\ATE\ClonedSites\FingerprintGeneratorForOriginalSite; class AMSAPIFactory { /** * It creates an instance of \WPML_TM_AMS_API which uses a special Fingerprint generator. * It let us make a call against the AMS API with the fingerprint of the original site. * * @return \WPML_TM_AMS_API */ public function create() { $lock = new \WPML\TM\ATE\ClonedSites\Lock(); return new \WPML_TM_AMS_API( new \WP_Http(), new \WPML_TM_ATE_Authentication(), new \WPML_TM_ATE_AMS_Endpoints(), new \WPML\TM\ATE\ClonedSites\ApiCommunication( $lock ), new FingerprintGeneratorForOriginalSite( $lock ) ); } } ATE/API/ClonedSites/Endpoints/Move.php 0000755 00000000763 14720342453 0013401 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites\Endpoints; use WPML\FP\Either; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\LIB\WP\WordPress; use WPML\TM\ATE\ClonedSites\Report; use function WPML\Container\make; class Move implements IHandler { public function run( Collection $data ) { /** @var Report $report */ $report = make( Report::class ); $result = $report->move(); return is_wp_error( $result ) ? Either::left( 'Failed to report' ) : Either::of( true ); } } ATE/API/ClonedSites/Endpoints/Copy.php 0000755 00000000712 14720342453 0013377 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\TM\ATE\ClonedSites\Report; use function WPML\Container\make; class Copy implements IHandler { public function run( Collection $data ) { /** @var Report $report */ $report = make( Report::class ); $result = $report->copy(); return $result ? Either::of( true ) : Either::left( 'Failed to report' ); } } ATE/API/ClonedSites/Endpoints/CopyWithCredits.php 0000755 00000001075 14720342453 0015554 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\LIB\WP\WordPress; use WPML\TM\ATE\ClonedSites\Report; use function WPML\Container\make; class CopyWithCredits implements IHandler { public function run( Collection $data ) { $migrationCode = $data->get( 'migrationCode' ); /** @var Report $report */ $report = make( Report::class ); $result = $report->copyWithCredit( $migrationCode ); return $result ? Either::of( true ) : Either::left( 'Failed to report' ); } } ATE/API/ClonedSites/FingerprintGeneratorForOriginalSite.php 0000755 00000001157 14720342453 0017645 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\TM\ATE\API\FingerprintGenerator; use WPML\FP\Obj; /** * We need this class in order to be able to make an API calls against the original site ( the site that was cloned to the current url ). */ class FingerprintGeneratorForOriginalSite extends FingerprintGenerator { /** * @var Lock */ private $lock; public function __construct( Lock $lock ) { $this->lock = $lock; } protected function getSiteUrl() { if ( Lock::isLocked() ) { return Obj::prop( 'urlCurrentlyRegisteredInAMS', $this->lock->getLockData() ); } return parent::getSiteUrl(); } } ATE/API/ClonedSites/Lock.php 0000755 00000003747 14720342453 0011425 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\FP\Obj; class Lock { const CLONED_SITE_OPTION = 'otgs_wpml_tm_ate_cloned_site_lock'; public function lock( $lockData ) { if ( $this->isLockDataPresent( $lockData ) ) { update_option( self::CLONED_SITE_OPTION, [ 'stored_fingerprint' => $lockData['stored_fingerprint'], 'received_fingerprint' => $lockData['received_fingerprint'], 'fingerprint_confirmed' => $lockData['fingerprint_confirmed'], 'identical_url_before_movement' => isset( $lockData['identical_url_before_movement'] ) ? $lockData['identical_url_before_movement'] : false, ], 'no' ); } } /** * @return array{urlCurrentlyRegisteredInAMS: string, urlUsedToMakeRequest: string, siteMoved: bool} */ public function getLockData() { $option = get_option( self::CLONED_SITE_OPTION, [] ); $urlUsedToMakeRequest = Obj::propOr( '', 'wp_url', is_string( $option['received_fingerprint'] ) ? json_decode( $option['received_fingerprint'] ) : $option['received_fingerprint'] ); $urlCurrentlyRegisteredInAMS = Obj::pathOr( '', [ 'stored_fingerprint', 'wp_url' ], $option ); return [ 'urlCurrentlyRegisteredInAMS' => $urlCurrentlyRegisteredInAMS, 'urlUsedToMakeRequest' => $urlUsedToMakeRequest, 'identicalUrlBeforeMovement' => Obj::propOr( false, 'identical_url_before_movement', $option ), ]; } /** * @return string */ public function getUrlRegisteredInAMS() { $lockData = $this->getLockData(); return $lockData['urlCurrentlyRegisteredInAMS']; } private function isLockDataPresent( $lockData ) { return isset( $lockData['stored_fingerprint'] ) && isset( $lockData['received_fingerprint'] ) && isset( $lockData['fingerprint_confirmed'] ); } public function unlock() { delete_option( self::CLONED_SITE_OPTION ); } public static function isLocked() { return (bool) get_option( self::CLONED_SITE_OPTION, false ) && \WPML_TM_ATE_Status::is_enabled(); } } ATE/API/ClonedSites/ApiCommunication.php 0000755 00000004225 14720342453 0013764 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Str; use WPML\UIPage; class ApiCommunication { const SITE_CLONED_ERROR = 426; const SITE_MOVED_OR_COPIED_MESSAGE = "WPML has detected a change in your site's URL. To continue translating your site, go to your <a href='%s'>WordPress Dashboard</a> and tell WPML if your site has been <a href='%s'>moved or copied</a>."; const SITE_MOVED_OR_COPIED_DOCS_URL = 'https://wpml.org/documentation/translating-your-contents/advanced-translation-editor/using-advanced-translation-editor-when-you-move-or-use-a-copy-of-your-site/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm'; /** * @var Lock */ private $lock; /** * @param Lock $lock */ public function __construct( Lock $lock ) { $this->lock = $lock; } public function handleClonedSiteError( $response ) { if ( self::SITE_CLONED_ERROR === $response['response']['code'] ) { $parsedResponse = json_decode( $response['body'], true ); if ( isset( $parsedResponse['errors'] ) ) { $this->handleClonedDetection( $parsedResponse['errors'] ); } $errorMessage = sprintf( __( self::SITE_MOVED_OR_COPIED_MESSAGE, 'sitepress-multilingual-cms' ), UIPage::getTMDashboard(), self::SITE_MOVED_OR_COPIED_DOCS_URL ); return new \WP_Error( self::SITE_CLONED_ERROR, $errorMessage ); } return $response; } /** * @param string $endpointUrl * * @return \WP_Error|null */ public function checkCloneSiteLock( $endpointUrl = '' ) { $isOnWhiteList = function ( $endpointUrl ) { $endpointsWhitelist = \apply_filters( 'wpml_ate_locked_endpoints_whitelist', [] ); return (bool) Lst::find( Str::includes( Fns::__, $endpointUrl ), $endpointsWhitelist ); }; if ( Lock::isLocked() && ! $isOnWhiteList( $endpointUrl ) ) { $errorMessage = sprintf( __( self::SITE_MOVED_OR_COPIED_MESSAGE, 'sitepress' ), UIPage::getTMDashboard(), self::SITE_MOVED_OR_COPIED_DOCS_URL ); return new \WP_Error( self::SITE_CLONED_ERROR, $errorMessage ); } return null; } private function handleClonedDetection( $error_data ) { $error = array_pop( $error_data ); $this->lock->lock( $error ); } } ATE/API/ClonedSites/Loader.php 0000755 00000007325 14720342453 0011737 0 ustar 00 <?php namespace WPML\TM\ATE\ClonedSites; use WPML\Core\WP\App\Resources; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\User; use WPML\TM\ATE\ClonedSites\Endpoints\GetCredits as ClonedSitesGetCredits; use WPML\TM\ATE\ClonedSites\Endpoints\Copy; use WPML\TM\ATE\ClonedSites\Endpoints\Move; use WPML\TM\ATE\ClonedSites\Endpoints\CopyWithCredits; use WPML\TM\ATE\ClonedSites\Lock; use WPML\TM\Templates\Notices\AteLocked; use WPML\LIB\WP\Hooks; use WPML\UIPage; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\spreadArgs; class Loader implements \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var Lock */ private $lock; public function __construct( Lock $lock ) { $this->lock = $lock; } public function add_hooks() { $displayPlaceholder = function () { if ( $this->shouldRender() ) { echo '<div id="wpml-tm-ate-lock-notice"></div>'; } }; Hooks::onAction( 'admin_notices' ) ->then( $displayPlaceholder ); Hooks::onFilter( 'wpml_tm_dashboard_notices' ) ->then( spreadArgs( Lst::append( $displayPlaceholder ) ) ); Hooks::onAction( 'admin_enqueue_scripts' ) ->then( function () { if ( $this->shouldRender() ) { $fn = Resources::enqueueApp( 'ate/clonedSites' ); $fn( $this->getData() ); } } ); } public function getData() { $lockData = $this->lock->getLockData(); $urlCurrentlyRegisteredInAMS = Obj::prop( 'urlCurrentlyRegisteredInAMS', $lockData ); $urlUsedToMakeRequest = Obj::prop( 'urlUsedToMakeRequest', $lockData ); return [ 'name' => 'ate_cloned_sites', 'data' => [ 'hasRightToHandle' => User::isAdministrator(), 'endpoints' => [ 'move' => Move::class, 'copy' => Copy::class, 'copyWithCredits' => CopyWithCredits::class, 'clonedSitesGetCredits' => ClonedSitesGetCredits::class, ], 'urls' => [ 'toolsOnOldSite' => $urlCurrentlyRegisteredInAMS . '/wp-admin/' . UIPage::getTMATE() . '&widget_action=open_sites&force_code=1', ], 'settings' => [ 'allowedModes' => apply_filters( 'wpml_ate_locked_allow_site_move_copy', [ 'move' => true, 'copy' => true ] ), 'urlCurrentlyRegisteredInAMS' => $urlCurrentlyRegisteredInAMS, 'urlUsedToMakeRequest' => Obj::prop( 'urlUsedToMakeRequest', $lockData ), 'isOriginalSiteMovedToAnotherURL' => $lockData['identicalUrlBeforeMovement'], ], ], ]; } private function shouldRender() { return Lock::isLocked() && $this->shouldDisplayOnCurrentPage(); } private function shouldDisplayOnCurrentPage() { return $this->shouldDisplayOnScreen( [ 'dashboard' ] ) || $this->shouldDisplayOnPage( $this->getPages() ); } /** * @param array $screens * * @return bool */ private function shouldDisplayOnScreen( array $screens ) { $currentScreen = \get_current_screen(); return $currentScreen instanceof \WP_Screen && in_array( $currentScreen->id, $screens ); } /** * @param array $pages * * @return bool */ private function shouldDisplayOnPage( array $pages ) { return isset( $_GET['page'] ) && in_array( $_GET['page'], $pages ); } /** * @return array|string[] */ private function getPages() { $pages = [ WPML_PLUGIN_FOLDER . '/menu/languages.php', WPML_PLUGIN_FOLDER . '/menu/theme-localization.php', WPML_PLUGIN_FOLDER . '/menu/settings.php', WPML_PLUGIN_FOLDER . '/menu/support.php', WPML_TM_FOLDER . '/menu/settings', WPML_TM_FOLDER . '/menu/main.php', WPML_TM_FOLDER . '/menu/translations-queue.php', WPML_TM_FOLDER . '/menu/string-translation.php', WPML_TM_FOLDER . '/menu/settings.php', ]; return $pages; } } ATE/API/RequestException.php 0000755 00000002632 14720342453 0011620 0 ustar 00 <?php namespace WPML\TM\ATE\API; /** * Exception for HTTP requests */ class RequestException extends \Exception { /** * Type of exception * * @var string */ protected $type; /** * Data associated with the exception * * @var mixed */ protected $data; /** * Whether to avoid logging the exception. * * @var bool */ protected $avoidLogDuplication; /** * Create a new exception * * @param string $message Exception message. * @param string|int $type Exception type. * @param mixed $data Associated data. * @param int $code Exception numerical code, if applicable. */ public function __construct( $message, $type, $data = null, $code = 0, $avoidLogDuplication = false ) { // Use type as code if code is not set. // This is for backward compatibility. $code = 0 === $code ? (int) $type : $code; $this->avoidLogDuplication = $avoidLogDuplication; parent::__construct( $message, $code ); $this->type = (string) $type; $this->data = $data; } /** * Like {@see getCode()}, but a string code. * * @return string */ public function getType() { return $this->type; } /** * Gives any relevant data * * @return mixed */ public function getData() { return $this->data; } /** * Whether to avoid logging the exception. * * @return bool */ public function shouldAvoidLogDuplication() { return $this->avoidLogDuplication; } } ATE/API/class-wpml-tm-ate-api.php 0000755 00000046304 14720342453 0012333 0 ustar 00 <?php use WPML\TM\ATE\API\FingerprintGenerator; use WPML\TM\ATE\Log\Entry; use WPML\TM\ATE\Log\EventsTypes; use WPML\TM\ATE\ClonedSites\ApiCommunication as ClonedSitesHandler; use WPML\FP\Obj; use WPML\FP\Fns; use WPML\FP\Either; use WPML\FP\Lst; use WPML\FP\Logic; use WPML\FP\Str; use WPML\Element\API\Entity\LanguageMapping; use WPML\LIB\WP\WordPress; use WPML\TM\Editor\ATEDetailedErrorMessage; use function WPML\FP\invoke; use function WPML\FP\pipe; use WPML\Element\API\Languages; use WPML\FP\Relation; use WPML\FP\Maybe; use WPML\TM\ATE\API\RequestException; /** * @author OnTheGo Systems */ class WPML_TM_ATE_API { const TRANSLATED = 6; const DELIVERING = 7; const NOT_ENOUGH_CREDIT_STATUS = 31; const CANCELLED_STATUS = 20; const SHOULD_HIDE_STATUS = 42; private $wp_http; private $auth; private $endpoints; /** * @var ClonedSitesHandler */ private $clonedSitesHandler; /** * @var FingerprintGenerator */ private $fingerprintGenerator; /** * WPML_TM_ATE_API constructor. * * @param WP_Http $wp_http * @param WPML_TM_ATE_Authentication $auth * @param WPML_TM_ATE_AMS_Endpoints $endpoints * @param ClonedSitesHandler $clonedSitesHandler * @param FingerprintGenerator $fingerprintGenerator */ public function __construct( WP_Http $wp_http, WPML_TM_ATE_Authentication $auth, WPML_TM_ATE_AMS_Endpoints $endpoints, ClonedSitesHandler $clonedSitesHandler, FingerprintGenerator $fingerprintGenerator ) { $this->wp_http = $wp_http; $this->auth = $auth; $this->endpoints = $endpoints; $this->clonedSitesHandler = $clonedSitesHandler; $this->fingerprintGenerator = $fingerprintGenerator; } /** * On success, it returns the map: wpmlJobId => ateJobId inside the 'jobs' key. * * @param array $params * @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/jobs/create * * @return array{ * code: int, * status: string, * message: string, * jobs: array{ * int: int * } * } | WP_Error * * @throws \InvalidArgumentException */ public function create_jobs( array $params ) { return $this->requestWithLog( $this->endpoints->get_ate_jobs(), [ 'method' => 'POST', 'body' => $params, ] ); } /** * @param int|string|array $ate_job_id * * @return array|WP_Error * @throws \InvalidArgumentException */ public function confirm_received_job( $ate_job_id ) { return $this->requestWithLog( $this->endpoints->get_ate_confirm_job( $ate_job_id ) ); } /** * @param array|int $jobIds * @param bool $onlyFailed * * @return array|mixed|object|string|\WP_Error|null */ public function cancelJobs( $jobIds, $onlyFailed = false ) { return $this->requestWithLog( $this->endpoints->getAteCancelJobs(), [ 'method' => 'POST', 'body' => [ 'id' => (array) $jobIds, 'only_failed' => $onlyFailed ] ] ); } /** * @param array|int $jobIds * @param bool $force * * @return array|mixed|object|string|\WP_Error|null */ public function hideJobs( $jobIds, $force = false ) { return $this->requestWithLog( $this->endpoints->getAteHideJobs(), [ 'method' => 'POST', 'body' => [ 'id' => (array) $jobIds, 'force' => $force ] ] ); } /** * @param int $job_id * @param string $return_url * * @return string|WP_Error * @throws \InvalidArgumentException */ public function get_editor_url( $job_id, $return_url ) { $lock = $this->clonedSitesHandler->checkCloneSiteLock(); if ( $lock ) { return new WP_Error( 'communication_error', 'ATE communication is locked, please update configuration' ); } $translator_email = filter_var( wp_get_current_user()->user_email, FILTER_SANITIZE_URL ); $return_url = filter_var( $return_url, FILTER_SANITIZE_URL ); $url = $this->endpoints->get_ate_editor(); $url = str_replace( [ '{job_id}', '{translator_email}', '{return_url}', ], [ $job_id, $translator_email ? urlencode( $translator_email ) : '', $return_url ? urlencode( $return_url ) : '', ], $url ); return $this->auth->get_signed_url_with_parameters( 'GET', $url, null ); } /** * @param int $ate_job_id * @param WPML_Element_Translation_Job $job_object * @param int|null $sentFrom * * @return array */ public function clone_job( $ate_job_id, WPML_Element_Translation_Job $job_object, $sentFrom = null ) { $url = $this->endpoints->get_clone_job( $ate_job_id ); $params = [ 'id' => $ate_job_id, 'notify_url' => \WPML\TM\ATE\REST\PublicReceive::get_receive_ate_job_url( $job_object->get_id() ), 'site_identifier' => wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ), 'source_id' => wpml_tm_get_records() ->icl_translate_job_by_job_id( $job_object->get_id() ) ->rid(), 'permalink' => $job_object->get_url( true ), 'ate_ams_console_url' => wpml_tm_get_ams_ate_console_url(), ]; if ( $sentFrom ) { $params['job_type'] = $sentFrom; } $result = $this->requestWithLog( $url, [ 'method' => 'POST', 'body' => $params ] ); if ( ! is_object( $result ) || ! property_exists( $result, 'job_id' ) ) { return false; } return [ 'id' => $result->job_id, 'ate_status' => Obj::propOr( WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_CREATED, 'status', $result ), ]; } /** * @param int $ate_job_id * * @return array|WP_Error * @throws \InvalidArgumentException */ public function get_job( $ate_job_id ) { if ( ! $ate_job_id ) { return null; } return $this->requestWithLog( $this->endpoints->get_ate_jobs( $ate_job_id ) ); } /** * If `$job_ids` is not an empty array, * the `$statuses` parameter will be ignored in ATE's endpoint. * * @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/jobs/status * * @param null|array $job_ids * @param null|array $statuses * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function get_jobs( $job_ids, $statuses = null ) { return $this->requestWithLog( $this->endpoints->get_ate_jobs( $job_ids, $statuses ) ); } public function get_job_status_with_priority( $job_id ) { return $this->requestWithLog( $this->endpoints->get_ate_job_status(), [ 'method' => 'POST', 'body' => [ 'id' => $job_id, 'preview' => true], ] ); } /** * @param $wpml_job_ids * * @return array|mixed|object|WP_Error|null */ public function get_jobs_by_wpml_ids( $wpml_job_ids ) { return $this->requestWithLog( $this->endpoints->get_ate_jobs_by_wpml_job_ids( $wpml_job_ids ) ); } /** * @param array $pairs * @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/migration/migrate * @return bool */ public function migrate_source_id( array $pairs ) { $lock = $this->clonedSitesHandler->checkCloneSiteLock(); if ( $lock ) { return false; } $verb = 'POST'; $url = $this->auth->get_signed_url_with_parameters( $verb, $this->endpoints->get_source_id_migration(), $pairs ); if ( is_wp_error( $url ) ) { return $url; } $body = wp_json_encode( $pairs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); $result = $this->wp_http->request( $url, array( 'timeout' => 60, 'method' => $verb, 'headers' => $this->json_headers(), 'body' => $body ?: '', ) ); if ( ! is_wp_error( $result ) ) { $result = $this->clonedSitesHandler->handleClonedSiteError( $result ); } return $this->get_response_errors( $result ) === null; } /** * @param LanguageMapping[] $languagesToMap * * @return Either */ public function create_language_mapping( array $languagesToMap ) { $result = $this->requestWithLog( $this->endpoints->getLanguages(), [ 'method' => 'POST', 'body' => [ 'mappings' => Fns::map( invoke( 'toATEFormat' ), Obj::values( $languagesToMap ) ) ] ] ); // it has an error when there is at least one record which has falsy "result" => "created" field $hasError = Lst::find( Logic::complement( Obj::path( [ 'result', 'created' ] ) ) ); $logError = Fns::tap( function ( $data ) { $entry = new Entry(); $entry->eventType = EventsTypes::SERVER_ATE; $entry->description = __( 'Saving of Language mapping to ATE failed', 'wpml-translation-management' ); $entry->extraData = $data; wpml_tm_ate_ams_log( $entry ); } ); return WordPress::handleError( $result ) ->map( Obj::prop( 'mappings' ) ) ->chain( Logic::ifElse( $hasError, pipe( $logError, Either::left() ), Either::right() ) ); } /** * @param $mappingIds * * @return false|array */ public function remove_language_mapping( $mappingIds ) { $result = $this->requestWithLog( $this->endpoints->getDeleteLanguagesMapping(), [ 'method' => 'POST', 'body' => [ 'mappings' => $mappingIds ] ] ); return is_wp_error( $result ) ? false : $result; } /** * @param string[] $languageCodes * @param null|string $sourceLanguage * * @return Maybe */ public function get_languages_supported_by_automatic_translations( $languageCodes, $sourceLanguage = null ) { $sourceLanguage = $sourceLanguage ?: Languages::getDefaultCode(); $result = $this->requestWithLog( $this->endpoints->getLanguagesCheckPairs(), [ 'method' => 'POST', 'body' => [ [ 'source_language' => $sourceLanguage, 'target_languages' => $languageCodes, ] ] ] ); return Maybe::of( $result ) ->reject( 'is_wp_error' ) ->map( Obj::prop( 'results' ) ) ->map( Lst::find( Relation::propEq( 'source_language', $sourceLanguage ) ) ) ->map( Obj::prop( 'target_languages' ) ); } /** * It returns language details from ATE including the info about translation engine supporting this language. * * If $inTheWebsiteContext is true, then we are taking into consideration user's translation engine settings. * It means that generally language may be supported e.g. by google, but when he turns off this engine, it will be reflected in the response. * * @param string $languageCode * @param bool $inTheWebsiteContext * * @return Maybe */ public function get_language_details( $languageCode, $inTheWebsiteContext = true ) { $result = $this->requestWithLog( sprintf( $this->endpoints->getShowLanguage(), $languageCode ), [ 'method' => 'GET' ] ); return Maybe::of( $result ) ->reject( 'is_wp_error' ) ->map( Obj::prop( $inTheWebsiteContext ? 'website_language' : 'language' ) ); } /** * @return array */ public function get_available_languages() { $result = $this->requestWithLog( $this->endpoints->getLanguages(), [ 'method' => 'GET' ] ); return is_wp_error( $result ) ? [] : $result; } /** * @return Maybe */ public function get_language_mapping() { $result = $this->requestWithLog( $this->endpoints->getLanguagesMapping(), [ 'method' => 'GET' ] ); return Maybe::of( $result )->reject( 'is_wp_error' ); } public function start_translation_memory_migration() { $result = $this->requestWithLog( $this->endpoints->startTranlsationMemoryIclMigration(), [ 'method' => 'POST', 'body' => [ 'site_identifier' => $this->get_website_id( site_url() ), 'ts_id' => 10, // random numbers for now, we should check what needs to be done for the final version. 'ts_access_key' => 20, ], ] ); return WordPress::handleError( $result ); } public function check_translation_memory_migration() { $result = $this->requestWithLog( $this->endpoints->checkStatusTranlsationMemoryIclMigration(), [ 'method' => 'GET', 'body' => [ 'site_identifier' => $this->get_website_id( site_url() ), 'ts_id' => 10, // random numbers for now, we should check what needs to be done for the final version. 'ts_access_key' => 20, ], ] ); return WordPress::handleError( $result ); } /** * @see https://ate.pages.onthegosystems.com/ate-docs/ATE/API/V1/icl/translators/import * * @param $iclToken * @param $iclServiceId * * @return callable|Either */ public function import_icl_translators( $tsId, $tsAccessKey ) { $params = [ 'site_identifier' => $this->auth->get_site_id(), 'ts_id' => $tsId, 'ts_access_key' => $tsAccessKey ]; $result = $this->requestWithLog( $this->endpoints->importIclTranslators(), [ 'method' => 'POST', 'body' => $params ] ); return WordPress::handleError( $result ); } private function get_response( $result ) { $errors = $this->get_response_errors( $result ); if ( is_wp_error( $errors ) ) { return $errors; } return $this->get_response_body( $result ); } private function get_response_body( $result ) { if ( is_array( $result ) && array_key_exists( 'body', $result ) ) { $body = json_decode( $result['body'] ); if ( isset( $body->authenticated ) && ! (bool) $body->authenticated ) { return new WP_Error( 'ate_auth_failed', isset( $body->message ) ? $body->message : '' ); } return $body; } return $result; } private function get_response_errors( $response ) { if ( is_wp_error( $response ) ) { return $response; } $response_errors = null; $response = (array) $response; if ( array_key_exists( 'body', $response ) && $response['response']['code'] >= 400 ) { $errors = array(); $response_body = json_decode( $response['body'], true ); if ( is_array( $response_body ) && array_key_exists( 'errors', $response_body ) ) { $errors = $response_body['errors']; } $response_errors = new WP_Error( $response['response']['code'], $response['response']['message'], $errors ); } return $response_errors; } /** * @return array */ private function json_headers() { return [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', FingerprintGenerator::SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(), ]; } /** * @param array $args * * @return string */ private function encode_body_args( array $args ) { return wp_json_encode( $args, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); } /** * @param string $xliff_url * @param array|\stdClass|false|null $job * * @return string * @throws RequestException The request to ATE failed. */ public function get_remote_xliff_content( $xliff_url, $job = null ) { $avoidLogDuplication = false; try { /** @var \WP_Error|array $response */ $response = $this->wp_http->get($xliff_url, array( 'timeout' => min(30, ini_get('max_execution_time') ?: 10) )); } catch ( \Error $e ) { $response = new \WP_Error( 'ate_request_failed', 'Started attempt to download xliff file. The process did not finish.', [ 'errorMessage' => $e->getMessage(), 'debugTrace' => $e->getTraceAsString() ] ); $avoidLogDuplication = true; } if ( is_wp_error( $response ) ) { throw new RequestException( $response->get_error_message(), $response->get_error_code(), $response->get_error_data(), 0, $avoidLogDuplication ); } return $response['body']; } public function override_site_id( $site_id ) { $this->auth->override_site_id( $site_id ); } public function get_website_id( $site_url ) { $lock = $this->clonedSitesHandler->checkCloneSiteLock(); if ( $lock ) { return null; } $signed_url = $this->auth->get_signed_url_with_parameters( 'GET', $this->endpoints->get_websites() ); if ( is_wp_error( $signed_url ) ) { return null; } $requestArguments = [ 'headers' => $this->json_headers() ]; $response = $this->wp_http->request( $signed_url, $requestArguments ); if ( ! is_wp_error( $response ) ) { $response = $this->clonedSitesHandler->handleClonedSiteError( $response ); } $sites = $this->get_response( $response ); foreach ( $sites as $site ) { if ( $site->url === $site_url ) { return $site->uuid; } } return null; } /** * @param int $page * * @return \WPML\FP\Left|\WPML\FP\Right */ public function get_jobs_to_retranslation( int $page = 1 ) { try { $result = $this->requestWithLog( $this->endpoints->get_retranslate(), [ 'method' => 'GET', 'body' => [ 'page_number' => $page ], ] ); } catch ( \Exception $e ) { $result = new \WP_Error( $e->getCode(), $e->getMessage() ); } return WordPress::handleError( $result ); } /** * @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/sync/all * * @param array $ateJobIds * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function sync_all( array $ateJobIds ) { return $this->requestWithLog( $this->endpoints->get_sync_all(), [ 'method' => 'POST', 'body' => [ 'ids' => $ateJobIds ], ] ); } /** * @see https://bitbucket.org/emartini_crossover/ate/wiki/API/V1/sync/page * * @param string $token * @param int $page * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function sync_page( $token, $page ) { return $this->requestWithLog( $this->endpoints->get_sync_page( $token, $page ) ); } /** * @param string $url * @param array $requestArgs * * @return array|mixed|object|string|WP_Error|null */ private function request( $url, array $requestArgs = [] ) { $lock = $this->clonedSitesHandler->checkCloneSiteLock( $url ); if ( $lock ) { return $lock; } $requestArgs = array_merge( [ 'timeout' => 60, 'method' => 'GET', 'headers' => $this->json_headers(), ], $requestArgs ); $bodyArgs = isset( $requestArgs['body'] ) && is_array( $requestArgs['body'] ) ? $requestArgs['body'] : null; $signedUrl = $this->auth->get_signed_url_with_parameters( $requestArgs['method'], $url, $bodyArgs ); if ( is_wp_error( $signedUrl ) ) { return $signedUrl; } // For GET requests there's no point sending parameters in the body. // Actually, this will trigger an error in WP_HTTP Curl class when // trying to build the params into a string. if ( $bodyArgs && $requestArgs['method'] !== 'GET' ) { $requestArgs['body'] = $this->encode_body_args( $bodyArgs ); } $result = $this->wp_http->request( $signedUrl, $requestArgs ); if ( ! is_wp_error( $result ) ) { $result = $this->clonedSitesHandler->handleClonedSiteError( $result ); } return $this->get_response( $result ); } /** * @param string $url * @param array $requestArgs * * @return array|int|float|object|string|WP_Error|null */ private function requestWithLog( $url, array $requestArgs = [] ) { $response = $this->request( $url, $requestArgs ); if ( is_wp_error( $response ) ) { $entry = new Entry(); $entry->eventType = EventsTypes::SERVER_ATE; $entry->description = $response->get_error_message(); $errorCode = $response->get_error_code(); $entry->extraData = [ 'url' => $url, 'requestArgs' => $requestArgs, ]; if ( $errorCode ) { $entry->extraData['status'] = $errorCode; } if ( $response->get_error_data( $errorCode ) ) { $entry->extraData['details'] = $response->get_error_data( $errorCode ); } wpml_tm_ate_ams_log( $entry ); ATEDetailedErrorMessage::saveDetailedError( $response ); } return $response; } } ATE/API/CachedATEAPI.php 0000755 00000003550 14720342453 0010344 0 ustar 00 <?php namespace WPML\TM\ATE\API; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\LIB\WP\Transient; use WPML\FP\Obj; use WPML\TM\ATE\API\CacheStorage\Storage; use function WPML\FP\curryN; class CachedATEAPI { const CACHE_OPTION = 'wpml-tm-ate-api-cache'; /** @var \WPML_TM_ATE_API */ private $ateAPI; /** @var Storage */ private $storage; private $cachedFns = [ 'get_languages_supported_by_automatic_translations', 'get_language_details', 'get_language_mapping' ]; /** * @param \WPML_TM_ATE_API $ateAPI */ public function __construct( \WPML_TM_ATE_API $ateAPI, Storage $storage ) { $this->ateAPI = $ateAPI; $this->storage = $storage; } public function __call( $name, $args ) { return Lst::includes( $name, $this->cachedFns ) ? $this->callWithCache( $name, $args ) : call_user_func_array( [ $this->ateAPI, $name ], $args ); } private function callWithCache( $fnName, $args ) { $data = $this->storage->get( self::CACHE_OPTION, [] ); $key = $this->getKey( $args ); if ( ! array_key_exists( $fnName, $data ) || ! array_key_exists( $key, $data[ $fnName ] ) ) { return call_user_func_array( [ $this->ateAPI, $fnName ], $args )->map( $this->cacheValue( $fnName, $args ) ); } return Maybe::of( $data[ $fnName ][ $key ] ); } public function cacheValue( $fnName = null, $args = null, $result = null ) { $fn = curryN( 3, function ( $fnName, $args, $result ) { $data = $this->storage->get( self::CACHE_OPTION, [] ); $data[ $fnName ][ $this->getKey( $args ) ] = $result; $this->storage->save( self::CACHE_OPTION, $data ); return $result; } ); return call_user_func_array( $fn, func_get_args() ); } /** * @param mixed $args * * @return string */ private function getKey( $args ) { // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize return \serialize( $args ); } } ATE/API/FingerprintGenerator.php 0000755 00000002763 14720342453 0012454 0 ustar 00 <?php namespace WPML\TM\ATE\API; use WPML\TM\ATE\ClonedSites\SecondaryDomains; class FingerprintGenerator { const SITE_FINGERPRINT_HEADER = 'SITE-FINGERPRINT'; const NEW_SITE_FINGERPRINT_HEADER = 'NEW-SITE-FINGERPRINT'; /** @var SecondaryDomains */ private $secondaryDomains; /** * @param SecondaryDomains $secondaryDomains */ public function __construct( SecondaryDomains $secondaryDomains ) { $this->secondaryDomains = $secondaryDomains; } public function getSiteFingerprint() { $siteFingerprint = [ 'wp_url' => $this->getSiteUrl(), ]; return json_encode( $siteFingerprint ); } protected function getSiteUrl() { $siteUrl = defined( 'ATE_CLONED_SITE_URL' ) ? ATE_CLONED_SITE_URL : $this->secondaryDomains->maybeFallBackToTheOriginalURL( site_url() ); return $this->getDefaultSiteUrl( $siteUrl ); } private function getDefaultSiteUrl( $siteUrl ) { global $sitepress; $filteredSiteUrl = false; if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN === (int) $sitepress->get_setting( 'language_negotiation_type' ) ) { /* @var WPML_URL_Converter $wpml_url_converter */ global $wpml_url_converter; $site_url_default_lang = $wpml_url_converter->get_default_site_url(); $filteredSiteUrl = filter_var( $site_url_default_lang, FILTER_SANITIZE_URL ); } $defaultSiteUrl = $filteredSiteUrl ? $filteredSiteUrl : $siteUrl; $defaultSiteUrl = defined( 'ATE_CLONED_DEFAULT_SITE_URL' ) ? ATE_CLONED_DEFAULT_SITE_URL : $defaultSiteUrl; return $defaultSiteUrl; } } ATE/API/CacheStorage/StaticVariable.php 0000755 00000001361 14720342453 0013534 0 ustar 00 <?php namespace WPML\TM\ATE\API\CacheStorage; use WPML\FP\Obj; class StaticVariable implements Storage { /** @var array */ private static $cache = []; /** @var self */ private static $instance; public static function getInstance() { if ( ! self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * @param string $key * @param mixed $default * * @return mixed */ public function get( $key, $default = null ) { return Obj::propOr( $default, $key, self::$cache ); } /** * @param string $key * @param mixed $value */ public function save( $key, $value ) { self::$cache[ $key ] = $value; } /** * @param string $key */ public function delete( $key ) { self::$cache = []; } } ATE/API/CacheStorage/Transient.php 0000755 00000001060 14720342453 0012602 0 ustar 00 <?php namespace WPML\TM\ATE\API\CacheStorage; use WPML\LIB\WP\Transient as WPTransient; class Transient implements Storage { /** * @param string $key * @param mixed $default * * @return mixed */ public function get( $key, $default = null ) { return WPTransient::getOr( $key, $default ); } /** * @param string $key * @param mixed $value */ public function save( $key, $value ) { WPTransient::set( $key, $value, 3600 * 24 ); } /** * @param string $key */ public function delete( $key ) { WPTransient::delete( $key ); } } ATE/API/CacheStorage/Storage.php 0000755 00000000551 14720342453 0012243 0 ustar 00 <?php namespace WPML\TM\ATE\API\CacheStorage; interface Storage { /** * @param string $key * @param mixed $default * * @return mixed */ public function get( $key, $default = null ); /** * @param string $key * @param mixed $value */ public function save( $key, $value ); /** * @param string $key */ public function delete( $key ); } ATE/API/class-wpml-tm-ams-api.php 0000755 00000060240 14720342453 0012335 0 ustar 00 <?php use WPML\TM\ATE\API\FingerprintGenerator; use WPML\TM\ATE\Log\Entry; use WPML\TM\ATE\Log\Storage; use WPML\TM\ATE\Log\EventsTypes; use WPML\TM\ATE\ClonedSites\ApiCommunication as ClonedSitesHandler; use WPML\FP\Json; use WPML\FP\Relation; use WPML\FP\Either; use WPML\TM\ATE\API\ErrorMessages; use WPML\FP\Fns; use function WPML\FP\pipe; use WPML\FP\Logic; use function WPML\FP\invoke; /** * @author OnTheGo Systems */ class WPML_TM_AMS_API { const HTTP_ERROR_CODE_400 = 400; private $auth; /** @var WPML_TM_ATE_AMS_Endpoints */ private $endpoints; private $wp_http; /** * @var ClonedSitesHandler */ private $clonedSitesHandler; /** * @var FingerprintGenerator */ private $fingerprintGenerator; /** * WPML_TM_ATE_API constructor. * * @param WP_Http $wp_http * @param WPML_TM_ATE_Authentication $auth * @param WPML_TM_ATE_AMS_Endpoints $endpoints * @param ClonedSitesHandler $clonedSitesHandler * @param FingerprintGenerator $fingerprintGenerator */ public function __construct( WP_Http $wp_http, WPML_TM_ATE_Authentication $auth, WPML_TM_ATE_AMS_Endpoints $endpoints, ClonedSitesHandler $clonedSitesHandler, FingerprintGenerator $fingerprintGenerator ) { $this->wp_http = $wp_http; $this->auth = $auth; $this->endpoints = $endpoints; $this->clonedSitesHandler = $clonedSitesHandler; $this->fingerprintGenerator = $fingerprintGenerator; } /** * @param string $translator_email * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function enable_subscription( $translator_email ) { $result = null; $verb = 'PUT'; $url = $this->endpoints->get_enable_subscription(); $url = str_replace( '{translator_email}', base64_encode( $translator_email ), $url ); $response = $this->signed_request( $verb, $url ); if ( $this->response_has_body( $response ) ) { $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = json_decode( $response['body'], true ); } } return $result; } /** * @param string $translator_email * * @return bool|WP_Error */ public function is_subscription_activated( $translator_email ) { $result = null; $url = $this->endpoints->get_subscription_status(); $url = str_replace( '{translator_email}', base64_encode( $translator_email ), $url ); $url = str_replace( '{WEBSITE_UUID}', $this->auth->get_site_id(), $url ); $response = $this->signed_request( 'GET', $url ); if ( $this->response_has_body( $response ) ) { $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = json_decode( $response['body'], true ); $result = $result['subscription']; } } return $result; } /** * @return array|mixed|null|object|WP_Error * * @throws \InvalidArgumentException Exception. */ public function get_status() { $result = null; $registration_data = $this->get_registration_data(); $shared = array_key_exists( 'shared', $registration_data ) ? $registration_data['shared'] : null; if ( $shared ) { $url = $this->endpoints->get_ams_status(); $url = str_replace( '{SHARED_KEY}', $shared, $url ); $response = $this->request( 'GET', $url ); if ( $this->response_has_body( $response ) ) { $response_body = json_decode( $response['body'], true ); $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $registration_data = $this->get_registration_data(); if ( isset( $response_body['activated'] ) && (bool) $response_body['activated'] ) { $registration_data['status'] = WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE; $this->set_registration_data( $registration_data ); } $result = $response_body; } } } return $result; } /** * @return WP_Error|null */ public function get_translation_engines() { $result = null; $url = $this->endpoints->get_translation_engines(); $response = $this->signed_request( 'GET', $url ); if ( $this->response_has_body( $response ) ) { $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = json_decode( $response['body'], true ); } } return $result; } /** * @param $engine_settings * * @return bool|WP_Error */ public function update_translation_engines( $engine_settings ) { $result = false; $url = $this->endpoints->get_translation_engines(); $response = $this->signed_request( 'POST', $url, [ 'list' => $engine_settings ] ); if ( $this->response_has_body( $response ) ) { $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = json_decode( $response['body'], true ); return \WPML\FP\Obj::propOr( false, 'success', $result ); } } return $result; } /** * Used to register a manager and, at the same time, create a website in AMS. * This is called only when registering the site with AMS. * To register new managers or translators `\WPML_TM_ATE_AMS_Endpoints::get_ams_synchronize_managers` * and `\WPML_TM_ATE_AMS_Endpoints::get_ams_synchronize_translators` will be used. * * @param WP_User $manager The WP_User instance of the manager. * @param WP_User[] $translators An array of WP_User instances representing the current translators. * @param WP_User[] $managers An array of WP_User instances representing the current managers. * * @return \WPML\FP\Either */ public function register_manager( WP_User $manager, array $translators, array $managers ) { $uuid = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE, false ); $makeRequest = $this->makeRegistrationRequest( $manager, $translators, $managers ); $logErrorResponse = $this->logErrorResponse(); $getErrors = Fns::memorize( function ( $response ) { return $this->get_errors( $response, false ); } ); $handleErrorResponse = $this->handleErrorResponse( $logErrorResponse, $getErrors ); $handleGeneralError = $handleErrorResponse( Fns::identity(), pipe( [ ErrorMessages::class, 'invalidResponse' ], Either::left() ) ); $handle409Error = $this->handle409Error( $handleErrorResponse, $makeRequest ); return Either::of( $uuid ) ->chain( $makeRequest ) ->chain( $handle409Error ) ->chain( $handleGeneralError ) ->chain( $this->handleInvalidBodyError() ) ->map( $this->saveRegistrationData( $manager ) ); } private function makeRegistrationRequest( $manager, $translators, $managers ) { $buildParams = function ( $uuid ) use ( $manager, $translators, $managers ) { $manager_data = $this->get_user_data( $manager, true ); $translators_data = $this->get_users_data( $translators ); $managers_data = $this->get_users_data( $managers, true ); $sitekey = function_exists( 'OTGS_Installer' ) ? OTGS_Installer()->get_site_key( 'wpml' ) : null; $params = $manager_data; $params['website_url'] = get_site_url(); $params['website_uuid'] = $uuid; $params['translators'] = $translators_data; $params['translation_managers'] = $managers_data; if ( $sitekey ) { $params['site_key'] = $sitekey; } return $params; }; $handleUnavailableATEError = function ( $response, $uuid ) { if ( is_wp_error( $response ) ) { $this->log_api_error( ErrorMessages::serverUnavailableHeader(), [ 'responseError' => $response->get_error_message(), 'website_uuid' => $uuid ] ); $msg = $this->ping_healthy_wpml_endpoint() ? ErrorMessages::serverUnavailable( $uuid ) : ErrorMessages::offline( $uuid ); return Either::left( $msg ); } return Either::of( [ $response, $uuid ] ); }; return function ( $uuid ) use ( $buildParams, $handleUnavailableATEError ) { $response = $this->request( 'POST', $this->endpoints->get_ams_register_client(), $buildParams( $uuid ) ); return $handleUnavailableATEError( $response, $uuid ); }; } private function logErrorResponse() { return function ( $error, $uuid ) { $this->log_api_error( ErrorMessages::respondedWithError(), [ 'responseError' => $error->get_error_code() === 409 ? ErrorMessages::uuidAlreadyExists() : $error->get_error_message(), 'website_uuid' => $uuid ] ); }; } private function handleErrorResponse($logErrorResponse, $getErrors) { return \WPML\FP\curryN( 3, function ( $shouldHandleError, $errorHandler, $data ) use ( $logErrorResponse, $getErrors ) { list( $response, $uuid ) = $data; $error = $getErrors( $response ); if ( $shouldHandleError( $error ) ) { $logErrorResponse( $error, $uuid ); return $errorHandler( $uuid ); } return Either::of( $data ); } ); } private function handle409Error($handleErrorResponse, $makeRequest) { $is409Error = Logic::both( Fns::identity(), pipe( invoke( 'get_error_code' ), Relation::equals( 409 ) ) ); return $handleErrorResponse($is409Error, function ( $uuid ) use ( $makeRequest ) { $uuid = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE, true ); return $makeRequest( $uuid ); } ); } private function handleInvalidBodyError( ) { return function ( $data ) { list( $response, $uuid ) = $data; if ( ! $this->response_has_keys( $response ) ) { $this->log_api_error( ErrorMessages::respondedWithError(), [ 'responseError' => ErrorMessages::bodyWithoutRequiredFields(), 'response' => json_encode( $response ), 'website_uuid' => $uuid ] ); return Either::left( ErrorMessages::invalidResponse( $uuid ) ); } return Either::of( $data ); }; } private function saveRegistrationData($manager) { return function ( $data ) use ( $manager ) { list( $response) = $data; $registration_data = $this->get_registration_data(); $response_body = json_decode( $response['body'], true ); $registration_data['user_id'] = $manager->ID; $registration_data['secret'] = $response_body['secret_key']; $registration_data['shared'] = $response_body['shared_key']; $registration_data['status'] = WPML_TM_ATE_Authentication::AMS_STATUS_ENABLED; return $this->set_registration_data( $registration_data ); }; } /** * Gets the data required by AMS to register a user. * * @param WP_User $wp_user The user from which data should be extracted. * @param bool $with_name_details True if name details should be included. * * @return array */ private function get_user_data( WP_User $wp_user, $with_name_details = false ) { $data = array(); $data['email'] = $wp_user->user_email; if ( $with_name_details ) { $data['display_name'] = $wp_user->display_name; $data['first_name'] = $wp_user->first_name; $data['last_name'] = $wp_user->last_name; } else { $data['name'] = $wp_user->display_name; } return $data; } private function prepareClonedSiteArguments( $method ) { $headers = [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', FingerprintGenerator::NEW_SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(), ]; return [ 'method' => $method, 'headers' => $headers, ]; } /** * @return array|WP_Error */ public function reportCopiedSite() { return $this->processReport( $this->endpoints->get_ams_site_copy(), 'POST' ); } /** * @return array|WP_Error */ public function reportMovedSite() { return $this->processReport( $this->endpoints->get_ams_site_move(), 'PUT' ); } /** * @param string $migrationCode * * @return array|WP_Error */ public function reportCopiedSiteWithCreditTransfer( $migrationCode ) { return $this->processReport( $this->endpoints->get_ams_copy_attached(), 'POST', [ 'migration_code' => $migrationCode ] ); } /** * @param array $response Response from reportMovedSite() * * @return bool|WP_Error */ public function processMoveReport( $response ) { if ( ! $this->response_has_body( $response ) ) { return new WP_Error( 'auth_error', 'Unable to report site moved.' ); } $response_body = json_decode( $response['body'], true ); if ( isset( $response_body['moved_successfully'] ) && (bool) $response_body['moved_successfully'] ) { return true; } return new WP_Error( 'auth_error', 'Unable to report site moved.' ); } /** * @param array $response_body body from reportMovedSite() response. * * @return bool */ private function storeAuthData( $response_body ) { $setRegistrationDataResult = $this->updateRegistrationData( $response_body ); $setUuidResult = $this->updateSiteUuId( $response_body ); return $setRegistrationDataResult && $setUuidResult; } /** * @param array $response_body body from reportMovedSite() response. * * @return bool */ private function updateRegistrationData( $response_body ) { $registration_data = $this->get_registration_data(); $registration_data['secret'] = $response_body['new_secret_key']; $registration_data['shared'] = $response_body['new_shared_key']; return $this->set_registration_data( $registration_data ); } /** * @param array $response_body body from reportMovedSite() response. * * @return bool */ private function updateSiteUuId( $response_body ) { $this->override_site_id( $response_body['new_website_uuid'] ); return update_option( WPML_Site_ID::SITE_ID_KEY . ':ate', $response_body['new_website_uuid'], false ); } private function sendSiteReportConfirmation() { $url = $this->endpoints->get_ams_site_confirm(); $method = 'POST'; $args = $this->prepareClonedSiteArguments( $method ); $url_parts = wp_parse_url( $url ); $registration_data = $this->get_registration_data(); $query['new_shared_key'] = $registration_data['shared']; $query['token'] = uuid_v5( wp_generate_uuid4(), $url ); $query['new_website_uuid'] = $this->auth->get_site_id(); $url_parts['query'] = http_build_query( $query ); $url = http_build_url( $url_parts ); $signed_url = $this->auth->signUrl( $method, $url ); $response = $this->wp_http->request( $signed_url, $args ); if ( $this->response_has_body( $response ) ) { $response_body = json_decode( $response['body'], true ); return (bool) $response_body['confirmed']; } return new WP_Error( 'auth_error', 'Unable confirm site copied.' ); } /** * @param string $url * @param string $method * * @return array|WP_Error */ private function processReport( $url, $method, $queryParams = [] ) { $args = $this->prepareClonedSiteArguments( $method ); $url_parts = wp_parse_url( $url ); $registration_data = $this->get_registration_data(); $query = $queryParams; $query['shared_key'] = $registration_data['shared']; $query['token'] = uuid_v5( wp_generate_uuid4(), $url ); $query['website_uuid'] = $this->auth->get_site_id(); $url_parts['query'] = http_build_query( $query ); $url = http_build_url( $url_parts ); $signed_url = $this->auth->signUrl( $method, $url ); return $this->wp_http->request( $signed_url, $args ); } /** * @param array $response Response from reportCopiedSite() * * @return bool */ public function processCopyReportConfirmation( $response ) { if ( ! is_array( $response ) || ! array_key_exists( 'response', $response ) || ! array_key_exists( 'code', $response[ 'response' ] ) || $response[ 'response' ][ 'code' ] !== 200 ) { return false; } if ( $this->response_has_body( $response ) ) { $response_body = json_decode( $response['body'], true ); return $this->storeAuthData( $response_body ) && (bool) $this->sendSiteReportConfirmation(); } return false; } /** * Converts an array of WP_User instances into an array of data nedded by AMS to identify users. * * @param WP_User[] $users An array of WP_User instances. * @param bool $with_name_details True if name details should be included. * * @return array */ private function get_users_data( array $users, $with_name_details = false ) { $user_data = array(); foreach ( $users as $user ) { $wp_user = get_user_by( 'id', $user->ID ); if ( $wp_user ) { $user_data[] = $this->get_user_data( $wp_user, $with_name_details ); } } return $user_data; } /** * Checks if a reponse has a body. * * @param array|\WP_Error $response The response of the remote request. * * @return bool */ private function response_has_body( $response ) { return ! is_wp_error( $response ) && array_key_exists( 'body', $response ); } private function get_errors( $response, $logError = true ) { $response_errors = null; if ( is_wp_error( $response ) ) { $response_errors = $response; } elseif ( array_key_exists( 'body', $response ) && $response['response']['code'] >= self::HTTP_ERROR_CODE_400 ) { $main_error = array(); $errors = array(); $error_message = $response['response']['message']; $response_body = json_decode( $response['body'], true ); if ( ! $response_body ) { $error_message = $response['body']; $main_error = array( $response['body'] ); } elseif ( array_key_exists( 'errors', $response_body ) ) { $errors = $response_body['errors']; $main_error = array_shift( $errors ); $error_message = $this->get_error_message( $main_error, $response['body'] ); } $response_errors = new WP_Error( $response['response']['code'], $error_message, $main_error ); foreach ( $errors as $error ) { $error_message = $this->get_error_message( $error, $response['body'] ); $error_status = isset( $error['status'] ) ? 'ams_error: ' . $error['status'] : ''; $response_errors->add( $error_status, $error_message, $error ); } } if ( $logError && $response_errors ) { $this->log_api_error( $response_errors->get_error_message(), $response_errors->get_error_data() ); } return $response_errors; } private function log_api_error( $message, $data ) { $entry = new Entry(); $entry->eventType = EventsTypes::SERVER_AMS; $entry->description = $message; $entry->extraData = [ 'errorData' => $data ]; wpml_tm_ate_ams_log( $entry ); } private function ping_healthy_wpml_endpoint() { $response = $this->request( 'GET', defined( 'WPML_TM_INTERNET_CHECK_URL' ) ? WPML_TM_INTERNET_CHECK_URL : 'https://health.wpml.org/', [] ); return ! is_wp_error( $response ) && (int) \WPML\FP\Obj::path( [ 'response', 'code' ], $response ) === 200; } /** * @param array $ams_error * @param string $default * * @return string */ private function get_error_message( $ams_error, $default ) { $title = isset( $ams_error['title'] ) ? $ams_error['title'] . ': ' : ''; $details = isset( $ams_error['detail'] ) ? $ams_error['detail'] : $default; return $title . $details; } private function response_has_keys( $response ) { $response_body = json_decode( $response['body'], true ); return array_key_exists( 'secret_key', $response_body ) && array_key_exists( 'shared_key', $response_body ); } /** * @return array */ public function get_registration_data() { return get_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, [] ); } /** * @param $registration_data * * @return bool */ private function set_registration_data( $registration_data ) { return update_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, $registration_data ); } /** * @param array $managers * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function synchronize_managers( array $managers ) { $result = null; $managers_data = $this->get_users_data( $managers, true ); if ( $managers_data ) { $url = $this->endpoints->get_ams_synchronize_managers(); $url = str_replace( '{WEBSITE_UUID}', wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ), $url ); $params = array( 'translation_managers' => $managers_data ); $response = $this->signed_request( 'PUT', $url, $params ); if ( $this->response_has_body( $response ) ) { $response_body = json_decode( $response['body'], true ); $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = $response_body; } } } return $result; } /** * @param array $translators * * @return array|mixed|null|object|WP_Error * @throws \InvalidArgumentException */ public function synchronize_translators( array $translators ) { $result = null; $translators_data = $this->get_users_data( $translators ); if ( $translators_data ) { $url = $this->endpoints->get_ams_synchronize_translators(); $params = array( 'translators' => $translators_data ); $response = $this->signed_request( 'PUT', $url, $params ); if ( $this->response_has_body( $response ) ) { $response_body = json_decode( $response['body'], true ); $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = $response_body; } } elseif ( is_wp_error( $response ) ) { $result = $response; } } return $result; } /** * @param string $method * @param string $url * @param array|null $params * * @return array|WP_Error */ private function request( $method, $url, array $params = null ) { $lock = $this->clonedSitesHandler->checkCloneSiteLock( $url ); if ( $lock ) { return $lock; } $method = strtoupper( $method ); $headers = [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', FingerprintGenerator::SITE_FINGERPRINT_HEADER => $this->fingerprintGenerator->getSiteFingerprint(), ]; $max_execution_time = ini_get( 'max_execution_time' ); $timeout = $max_execution_time ? (int) $max_execution_time / 2 : 1; $args = [ 'method' => $method, 'headers' => $headers, 'timeout' => (float) max( $timeout, 5 ), ]; if ( $params ) { $body = wp_json_encode( $params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); $args['body'] = $body ?: ''; } $response = $this->wp_http->request( $this->add_versions_to_url( $url ), $args ); if ( ! is_wp_error( $response ) ) { $response = $this->clonedSitesHandler->handleClonedSiteError( $response ); } return $response; } /** * @param string $verb * @param string $url * @param array|null $params * * @return array|WP_Error */ private function signed_request( $verb, $url, array $params = null ) { $verb = strtoupper( $verb ); $signed_url = $this->auth->get_signed_url_with_parameters( $verb, $url, $params ); if ( is_wp_error( $signed_url ) ) { return $signed_url; } return $this->request( $verb, $signed_url, $params ); } /** * @param $url * * @return string */ private function add_versions_to_url( $url ) { $url_parts = wp_parse_url( $url ); $url_parts = $url_parts ?: []; $query = array(); if ( array_key_exists( 'query', $url_parts ) ) { parse_str( $url_parts['query'], $query ); } $query['wpml_core_version'] = ICL_SITEPRESS_VERSION; $query['wpml_tm_version'] = WPML_TM_VERSION; $url_parts['query'] = http_build_query( $query ); $url = http_build_url( $url_parts ); return $url; } public function override_site_id( $site_id ) { $this->auth->override_site_id( $site_id ); } /** * @return array|WP_Error */ public function getCredits() { return $this->getSignedResult( 'GET', $this->endpoints->get_credits() ); } /** * @return array|WP_Error */ public function resumeAll() { return $this->getSignedResult( 'GET', $this->endpoints->get_resume_all() ); } public function send_sitekey( $sitekey ) { $siteId = wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ); $response = $this->getSignedResult( 'POST', $this->endpoints->get_send_sitekey(), [ 'site_key' => $sitekey, 'website_uuid' => $siteId, ] ); return Relation::propEq( 'updated_website', $siteId, $response ); } /** * @param string $verb * @param string $url * @param array|null $params * * @return array|WP_Error */ private function getSignedResult( $verb, $url, array $params = null ) { $result = null; $response = $this->signed_request( $verb, $url, $params ); if ( $this->response_has_body( $response ) ) { $result = $this->get_errors( $response ); if ( ! is_wp_error( $result ) ) { $result = Json::toArray( $response['body'] ); } } return $result; } } ATE/API/class-wpml-tm-ate-authentication.php 0000755 00000010243 14720342453 0014572 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Authentication { const AMS_DATA_KEY = 'WPML_TM_AMS'; const AMS_STATUS_NON_ACTIVE = 'non-active'; const AMS_STATUS_ENABLED = 'enabled'; const AMS_STATUS_ACTIVE = 'active'; /** @var string|null $site_id */ private $site_id = null; public function get_signed_url_with_parameters( $verb, $url, $params = null ) { if ( $this->has_keys() ) { $url = $this->add_required_arguments_to_url( $verb, $url, $params ); return $this->signUrl( $verb, $url, $params ); } return new WP_Error( 'auth_error', 'Unable to authenticate' ); } public function signUrl( $verb, $url, $params = null ) { $url_parts = wp_parse_url( $url ); $query = $this->get_url_query( $url ); $query['signature'] = $this->get_signature( $verb, $url, $params ); $url_parts['query'] = $this->build_query( $query ); return http_build_url( $url_parts ); } private function get_signature( $verb, $url, array $params = null ) { if ( $this->has_keys() ) { $verb = strtolower( $verb ); $url_parts = wp_parse_url( $url ); $query_to_sign = $this->get_url_query( $url ); if ( $params && 'get' !== $verb ) { $query_to_sign['body'] = md5( (string) wp_json_encode( $params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) ); } $url_parts_to_sign = $url_parts; $url_parts_to_sign['query'] = $this->build_query( $query_to_sign ); $url_to_sign = http_build_url( $url_parts_to_sign ); $string_to_sign = strtolower( $verb ) . $url_to_sign; $sha1 = hash_hmac( 'sha1', $string_to_sign, $this->get_secret(), true ); return base64_encode( $sha1 ); } return null; } public function has_keys() { return $this->get_secret() && $this->get_shared(); } private function get_secret() { return $this->get_ams_data_property( 'secret' ); } private function get_shared() { return $this->get_ams_data_property( 'shared' ); } private function get_ams_data_property( $field ) { $data = $this->get_ams_data(); if ( array_key_exists( $field, $data ) ) { return $data[ $field ]; } return null; } /** * @return array */ private function get_ams_data() { return get_option( self::AMS_DATA_KEY, [] ); } /** * @param string $verb * @param string $url * @param array|null $params * * @return string */ private function add_required_arguments_to_url( $verb, $url, array $params = null ) { $verb = strtolower( $verb ); $url_parts = wp_parse_url( $url ); $query = $this->get_url_query( $url ); if ( $params && 'get' === $verb ) { foreach ( $params as $key => $value ) { $query[ $key ] = $value; } } $query['wpml_core_version'] = ICL_SITEPRESS_VERSION; $query['wpml_tm_version'] = WPML_TM_VERSION; $query['shared_key'] = $this->get_shared(); $query['token'] = uuid_v5( wp_generate_uuid4(), $url ); $query['website_uuid'] = $this->get_site_id(); $query['ui_language_code'] = apply_filters( 'wpml_get_user_admin_language', wpml_get_default_language(), get_current_user_id() ); if( function_exists( 'OTGS_Installer' ) ) { $query['site_key'] = OTGS_Installer()->get_site_key( 'wpml' ); } $url_parts['query'] = http_build_query( $query ); return http_build_url( $url_parts ); } /** * @param string $url * * @return array */ private function get_url_query( $url ) { $url_parts = wp_parse_url( $url ); $query = array(); if ( is_array( $url_parts ) && array_key_exists( 'query', $url_parts ) ) { parse_str( $url_parts['query'], $query ); } return $query; } /** * @param $query * * @return mixed|string */ protected function build_query( $query ) { if ( PHP_VERSION_ID >= 50400 ) { $final_query = http_build_query( $query, '', '&', PHP_QUERY_RFC3986 ); } else { $final_query = str_replace( array( '+', '%7E' ), array( '%20', '~' ), http_build_query( $query ) ); } return $final_query; } /** * @param string|null $site_id */ public function override_site_id( $site_id ) { $this->site_id = $site_id; } public function get_site_id() { return $this->site_id ? $this->site_id : wpml_get_site_id( WPML_TM_ATE::SITE_ID_SCOPE ); } } ATE/API/ErrorMessages.php 0000755 00000004741 14720342453 0011075 0 ustar 00 <?php namespace WPML\TM\ATE\API; class ErrorMessages { public static function serverUnavailable( $uuid ) { return [ 'header' => self::serverUnavailableHeader(), 'description' => self::invalidResponseDescription( $uuid ), ]; } public static function offline( $uuid ) { $description = _x( 'WPML needs an Internet connection to translate your site’s content. It seems that your server is not allowing external connections, or your network is temporarily down.', 'part1', 'wpml-translation-management' ); $description .= _x( 'If this is the first time you’re seeing this message, please wait a minute and reload the page. If the problem persists, contact %1$s for help and mention that your website ID is %2$s.', 'part2', 'wpml-translation-management' ); return [ 'header' => __( 'Cannot Connect to the Internet', 'wpml-translation-management' ), 'description' => sprintf( $description, self::getSupportLink(), $uuid ), ]; } public static function invalidResponse( $uuid ) { return [ 'header' => __( 'WPML’s Advanced Translation Editor is not working', 'wpml-translation-management' ), 'description' => self::invalidResponseDescription( $uuid ), ]; } public static function respondedWithError() { return __( "WPML's Advanced Translation Editor responded with an error", 'wpml-translation-management' ); } public static function serverUnavailableHeader() { return __( 'WPML’s Advanced Translation Editor is not responding', 'wpml-translation-management' ); } public static function invalidResponseDescription( $uuid ) { $description = _x( 'WPML cannot connect to the translation editor. If this is the first time you’re seeing this message, please wait a minute and reload the page.', 'part1', 'wpml-translation-management' ); $description .= _x( 'If the problem persists, contact %1$s for help and mention that your website ID is %2$s.', 'part2', 'wpml-translation-management' ); return sprintf( $description, self::getSupportLink(), $uuid ); } public static function getSupportLink() { return '<a href="https://wpml.org/forums/forum/english-support/" target="_blank" rel="noreferrer">' . __( 'WPML support', 'wpml-translation-management' ) . '</a>'; } public static function bodyWithoutRequiredFields() { return __( 'The body does not contain the required fields', 'wpml-translation-management' ); } public static function uuidAlreadyExists() { return __( 'UUID already exists', 'wpml-translation-management' ); } } ATE/JobRecords.php 0000755 00000010500 14720342453 0007725 0 ustar 00 <?php namespace WPML\TM\ATE; use Exception; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Lst; use WPML_TM_ATE_API_Error; use WPML_TM_Editors; class JobRecords { const FIELD_ATE_JOB_ID = 'ate_job_id'; const FIELD_IS_EDITING = 'is_editing'; /** @var \wpdb $wpdb */ private $wpdb; /** @var Collection $jobs */ private $jobs; public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; $this->jobs = wpml_collect( [] ); } /** * This method will retrieve data from the ATE job ID. * Beware of the returned data shape which is not standard. * * @param int $ateJobId * * @return array|null */ public function get_data_from_ate_job_id( $ateJobId ) { $ateJobId = (int) $ateJobId; $this->warmCache( [], [ $ateJobId ] ); $job = $this->jobs->first( function( JobRecord $job ) use ( $ateJobId ) { return $job->ateJobId === $ateJobId; } ); if ( $job ) { /** @var JobRecord $job */ return [ 'wpml_job_id' => $job->wpmlJobId, 'ate_job_data' => [ 'ate_job_id' => $job->ateJobId, ], ]; } return null; } /** * @param int $wpmlJobId * @param array $ateJobData */ public function store( $wpmlJobId, array $ateJobData ) { $ateJobData['job_id'] = (int) $wpmlJobId; $this->warmCache( [ $wpmlJobId ] ); $job = $this->jobs->get( $wpmlJobId ); if ( ! $job ) { $job = new JobRecord(); $job->wpmlJobId = (int) $wpmlJobId; } if ( isset( $ateJobData[ self::FIELD_ATE_JOB_ID ] ) ) { $job->ateJobId = $ateJobData[ self::FIELD_ATE_JOB_ID ]; } $this->persist( $job ); } /** * @param JobRecord $job */ public function persist( JobRecord $job ) { $this->jobs->put( $job->wpmlJobId, $job ); $this->wpdb->update( $this->wpdb->prefix . 'icl_translate_job', [ 'editor_job_id' => $job->ateJobId ], [ 'job_id' => $job->wpmlJobId ], [ '%d' ], [ '%d' ] ); } /** * This method will load in-memory the required jobs. * * @param array $wpmlJobIds * @param array $ateJobIds */ public function warmCache( array $wpmlJobIds, array $ateJobIds = [] ) { $wpmlJobIds = wpml_collect( $wpmlJobIds )->reject( $this->isAlreadyLoaded( 'wpmlJobId' ) )->toArray(); $ateJobIds = wpml_collect( $ateJobIds )->reject( $this->isAlreadyLoaded( 'ateJobId' ) )->toArray(); $where = []; if ( $wpmlJobIds ) { $where[] = 'job_id IN(' . wpml_prepare_in( $wpmlJobIds, '%d' ) . ')'; } if ( $ateJobIds ) { $where[] = 'editor_job_id IN(' . wpml_prepare_in( $ateJobIds, '%d' ) . ')'; } if ( ! $where ) { return; } $whereHasJobIds = implode( ' OR ', $where ); $rows = $this->wpdb->get_results( " SELECT job_id, editor_job_id FROM {$this->wpdb->prefix}icl_translate_job WHERE editor = '" . WPML_TM_Editors::ATE . "' AND ({$whereHasJobIds}) " ); foreach ( $rows as $row ) { $job = new JobRecord( $row ); $this->jobs->put( $job->wpmlJobId, $job ); } } /** * @param $idPropertyName * * @return \Closure */ private function isAlreadyLoaded( $idPropertyName ) { $loadedIds = $this->jobs->pluck( $idPropertyName )->values()->toArray(); return Lst::includes( Fns::__, $loadedIds ); } /** * @param int $wpmlJobId * * @return int */ public function get_ate_job_id( $wpmlJobId ) { return $this->get( $wpmlJobId )->ateJobId; } /** * @param int $wpmlJobId * * @return bool */ public function is_editing_job( $wpmlJobId ) { return $this->get( $wpmlJobId )->isEditing(); } /** * @param $wpmlJobId * * @return JobRecord */ public function get( $wpmlJobId ) { if ( ! $this->jobs->has( $wpmlJobId ) ) { $this->warmCache( [ (int) $wpmlJobId ] ); } /** @var null|JobRecord $job */ $job = $this->jobs->get( $wpmlJobId ); if ( ! $job || ! $job->ateJobId ) { $this->restoreJobDataFromATE( $wpmlJobId ); $job = $this->jobs->get( $wpmlJobId, new JobRecord() ); } return $job; } /** * This method will try to recover the job data from ATE server, * and persist it in the local repository. * * @param int $wpmlJobId */ private function restoreJobDataFromATE( $wpmlJobId ) { $data = apply_filters( 'wpml_tm_ate_job_data_fallback', [], $wpmlJobId ); if ( $data ) { try { $this->store( $wpmlJobId, $data ); } catch ( Exception $e ) { $error_log = new WPML_TM_ATE_API_Error(); $error_log->log( $e->getMessage() ); } } } } ATE/class-wpml-tm-ate-jobs.php 0000755 00000010303 14720342453 0012074 0 ustar 00 <?php use WPML\FP\Cast; use WPML\FP\Maybe; use WPML\TM\ATE\JobRecords; use WPML\TM\ATE\API\RequestException; use function WPML\FP\pipe; use function WPML\FP\partialRight; use WPML\FP\Obj; use WPML\FP\Logic; use WPML\FP\Fns; use function \WPML\FP\invoke; /** * @author OnTheGo Systems */ class WPML_TM_ATE_Jobs { /** @var JobRecords $records */ private $records; /** * WPML_TM_ATE_Jobs constructor. * * @param JobRecords $records */ public function __construct( JobRecords $records ) { $this->records = $records; } /** * @param int $wpml_job_id * * @return int */ public function get_ate_job_id( $wpml_job_id ) { $wpml_job_id = (int) $wpml_job_id; return $this->records->get_ate_job_id( $wpml_job_id ); } /** * @param int $ate_job_id * * @return int|null */ public function get_wpml_job_id( $ate_job_id ) { return Maybe::fromNullable( $ate_job_id ) ->map( Cast::toInt() ) ->map( [ $this->records, 'get_data_from_ate_job_id' ] ) ->map( Obj::prop( 'wpml_job_id' ) ) ->map( Cast::toInt() ) ->getOrElse( null ); } /** * @param int $wpml_job_id * @param array $ate_job_data */ public function store( $wpml_job_id, $ate_job_data ) { $this->records->store( (int) $wpml_job_id, $ate_job_data ); } /** * @todo: Check possible duplicated code / We already have functionality to import XLIFF files from Translator's queue * * @param string $xliff * * @return bool|int * @throws RequestException The job could not be loaded. * @throws \Exception When the xliff cannot be applied to the job. */ public function apply( $xliff ) { $factory = wpml_tm_load_job_factory(); $xliff_factory = new WPML_TM_Xliff_Reader_Factory( $factory ); $xliff_reader = $xliff_factory->general_xliff_reader(); $job_data = $xliff_reader->get_data( $xliff ); if ( is_wp_error( $job_data ) ) { throw new RequestException( $job_data->get_error_message(), $job_data->get_error_code() ); } kses_remove_filters(); $job_data = $this->filterJobData( $job_data ); $wpml_job_id = $job_data['job_id']; try { $is_saved = wpml_tm_save_data( $job_data, false ); } catch ( Exception $e ) { throw new Exception( 'The XLIFF file could not be applied to the content of the job ID: ' . $wpml_job_id, $e->getCode() ); } kses_init(); return $is_saved ? $wpml_job_id : false; } private function filterJobData( $jobData ) { /** * It lets modify $job_data, which is especially usefull when we want to alter `data` of field. * * @param array $jobData { * * @type int $job_id * @type array fields { * @type string $data Translated content * @type int $finished * @type int $tid * @type string $field_type * @type string $format * } * @type int $complete * } * * @param callable $getJobTargetLanguage The callback which expects $jobId as parameter * * @since 2.10.0 */ $filteredJobData = apply_filters( 'wpml_tm_ate_job_data_from_xliff', $jobData, $this->getJobTargetLanguage() ); if ( array_key_exists( 'job_id', $filteredJobData ) && array_key_exists( 'fields', $filteredJobData ) ) { $jobData = $filteredJobData; } return $jobData; } /** * getJobTargetLanguage :: void → ( object → string|null ) * * @return callable */ private function getJobTargetLanguage() { // $getJobEntityById :: int -> \WPML_TM_Job_Entity|false $getJobEntityById = partialRight( [ wpml_tm_get_jobs_repository(), 'get_job' ], \WPML_TM_Job_Entity::POST_TYPE ); // $getTargetLangIfEntityExists :: \WPML_TM_Job_Entity|false -> string|null $getTargetLangIfEntityExists = Logic::ifElse( Fns::identity(), invoke( 'get_target_language' ), Fns::always( null ) ); return pipe( Obj::prop( 'rid' ), $getJobEntityById, $getTargetLangIfEntityExists ); } /** * @param int $wpml_job_id * * @return bool */ public function is_editing_job( $wpml_job_id ) { return $this->records->is_editing_job( $wpml_job_id ); } /** * @param array $wpml_job_ids */ public function warm_cache( array $wpml_job_ids ) { $this->records->warmCache( $wpml_job_ids ); } } ATE/Hooks/class-wpml-tm-ate-api-error.php 0000755 00000001211 14720342453 0014120 0 ustar 00 <?php class WPML_TM_ATE_API_Error { public function log( $message ) { $wpml_admin_notices = wpml_get_admin_notices(); $notice = new WPML_Notice( WPML_TM_ATE_Jobs_Actions::RESPONSE_ATE_ERROR_NOTICE_ID, sprintf( __( 'There was a problem communicating with ATE: %s ', 'wpml-translation-management' ), '(<i>' . $message . '</i>)' ), WPML_TM_ATE_Jobs_Actions::RESPONSE_ATE_ERROR_NOTICE_GROUP ); $notice->set_css_class_types( array( 'warning' ) ); $notice->add_capability_check( array( 'manage_options', 'wpml_manage_translation_management' ) ); $notice->set_flash(); $wpml_admin_notices->add_notice( $notice ); } } ATE/Hooks/class-wpml-tm-ate-jobs-store-actions-factory.php 0000755 00000000752 14720342453 0017423 0 ustar 00 <?php /** * @todo Perhaps this class is redundant * * @author OnTheGo Systems */ class WPML_TM_ATE_Jobs_Store_Actions_Factory implements IWPML_Backend_Action_Loader { /** * @return IWPML_Action|IWPML_Action[]|null */ public function create() { if ( WPML_TM_ATE_Status::is_enabled() ) { $ate_jobs_records = wpml_tm_get_ate_job_records(); $ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records ); return new WPML_TM_ATE_Jobs_Store_Actions( $ate_jobs ); } } } ATE/Hooks/LanguageMappingCache.php 0000755 00000001027 14720342453 0012743 0 ustar 00 <?php namespace WPML\TM\ATE\Hooks; use WPML\LIB\WP\Hooks; use WPML\TM\API\ATE\CachedLanguageMappings; class LanguageMappingCache implements \IWPML_Backend_Action, \IWPML_REST_Action { public function add_hooks() { $clearCache = function () { CachedLanguageMappings::clearCache(); }; Hooks::onAction( 'wpml_tm_ate_translation_engines_updated' )->then( $clearCache ); Hooks::onAction( 'icl_after_set_default_language' )->then( $clearCache ); Hooks::onAction( 'wpml_update_active_languages' )->then( $clearCache ); } } ATE/Hooks/class-wpml-tm-ams-synchronize-actions.php 0000755 00000006711 14720342453 0016252 0 ustar 00 <?php use WPML\LIB\WP\User; /** * @author OnTheGo Systems */ class WPML_TM_AMS_Synchronize_Actions implements IWPML_Action { const ENABLED_FOR_TRANSLATION_VIA_ATE = 'wpml_enabled_for_translation_via_ate'; /** * @var WPML_TM_AMS_API */ private $ams_api; /** * @var WPML_TM_AMS_Users */ private $ams_user_records; /** * @var WPML_WP_User_Factory $user_factory */ private $user_factory; /** * @var WPML_TM_AMS_Translator_Activation_Records */ private $translator_activation_records; /** @var int[] */ private $deletedManagerIds = []; /** @var int[] */ private $deletedTranslatorIds = []; public function __construct( WPML_TM_AMS_API $ams_api, WPML_TM_AMS_Users $ams_user_records, WPML_WP_User_Factory $user_factory, WPML_TM_AMS_Translator_Activation_Records $translator_activation_records ) { $this->ams_api = $ams_api; $this->ams_user_records = $ams_user_records; $this->user_factory = $user_factory; $this->translator_activation_records = $translator_activation_records; } public function add_hooks() { add_action( 'wpml_tm_ate_synchronize_translators', array( $this, 'synchronize_translators' ) ); add_action( 'wpml_update_translator', array( $this, 'synchronize_translators' ) ); add_action( 'wpml_tm_ate_synchronize_managers', array( $this, 'synchronize_managers' ) ); add_action( 'wpml_tm_ate_enable_subscription', array( $this, 'enable_subscription' ) ); add_action( 'delete_user', array( $this, 'prepare_user_deleted' ), 10, 1 ); add_action( 'deleted_user', array( $this, 'user_changed' ), 10, 1 ); add_action( 'profile_update', array( $this, 'user_changed' ), 10, 1 ); } /** * @throws \InvalidArgumentException */ public function synchronize_translators() { $result = $this->ams_api->synchronize_translators( $this->ams_user_records->get_translators() ); if ( ! is_wp_error( $result ) ) { $this->translator_activation_records->update( isset( $result['translators'] ) ? $result['translators'] : array() ); } } /** * @throws \InvalidArgumentException */ public function synchronize_managers() { $this->ams_api->synchronize_managers( $this->ams_user_records->get_managers() ); } public function enable_subscription( $user_id ) { $user = $this->user_factory->create( $user_id ); if ( ! $user->get_meta( self::ENABLED_FOR_TRANSLATION_VIA_ATE ) ) { $this->ams_api->enable_subscription( $user->user_email ); $user->update_meta( self::ENABLED_FOR_TRANSLATION_VIA_ATE, true ); } } /** * @param int $user_id */ public function prepare_user_deleted( $user_id ) { $user = User::get( $user_id ); if ( $user ) { if ( User::hasCap( User::CAP_ADMINISTRATOR ) || User::hasCap( User::CAP_MANAGE_TRANSLATIONS, $user ) ) { $this->deletedManagerIds[] = $user_id; } if ( User::hasCap( User::CAP_ADMINISTRATOR ) || User::hasCap( User::CAP_TRANSLATE, $user ) ) { $this->deletedTranslatorIds[] = $user_id; } } } /** * @param int $user_id */ public function user_changed( $user_id ) { $user = User::get( $user_id ); if ( $user ) { if ( in_array( $user_id, $this->deletedManagerIds ) || User::hasCap( User::CAP_ADMINISTRATOR ) || User::hasCap( User::CAP_MANAGE_TRANSLATIONS, $user ) ) { $this->synchronize_managers(); } if ( in_array( $user_id, $this->deletedTranslatorIds ) || User::hasCap( User::CAP_ADMINISTRATOR ) || User::hasCap( User::CAP_TRANSLATE, $user ) ) { $this->synchronize_translators(); } } } } ATE/Hooks/JobActions.php 0000755 00000007021 14720342453 0011013 0 ustar 00 <?php namespace WPML\TM\ATE\Hooks; use function WPML\Container\make; use WPML\Element\API\Languages; use WPML\FP\Fns; use function WPML\FP\invoke; use WPML\FP\Lst; use WPML\FP\Obj; use function WPML\FP\pipe; use WPML\FP\Relation; use WPML\Setup\Option; class JobActions implements \IWPML_Action { /** @var \WPML_TM_ATE_API $apiClient */ private $apiClient; public function __construct( \WPML_TM_ATE_API $apiClient ) { $this->apiClient = $apiClient; } public function add_hooks() { add_action( 'wpml_tm_job_cancelled', [ $this, 'cancelJobInATE' ] ); add_action( 'wpml_tm_jobs_cancelled', [ $this, 'cancelJobsInATE' ] ); add_action( 'wpml_set_translate_everything', [ $this, 'hideJobsAfterTranslationMethodChange' ] ); add_action( 'wpml_update_active_languages', [ $this, 'hideJobsAfterRemoveLanguage' ] ); } public function cancelJobInATE( \WPML_TM_Post_Job_Entity $job ) { if ( $job->is_ate_editor() ) { $this->apiClient->cancelJobs( $job->get_editor_job_id() ); } } /** * @param \WPML_TM_Post_Job_Entity[]|\WPML_TM_Post_Job_Entity $jobs * * @return void */ public function cancelJobsInATE( $jobs ) { /** * We need this check because if we pass only one job to the hook: * do_action( 'wpml_tm_jobs_cancelled', [ $job ] ) * then WordPress converts it to $job. */ if ( is_object( $jobs ) ) { $jobs = [ $jobs ]; } $getIds = pipe( Fns::filter( invoke( 'is_ate_editor' ) ), Fns::map( invoke( 'get_editor_job_id' ) ) ); $this->apiClient->cancelJobs( $getIds( $jobs ) ); } /** * @param array $oldLanguages * @return void */ public function hideJobsAfterRemoveLanguage( $oldLanguages ) { $removedLanguages = Lst::diff( array_keys( $oldLanguages ), array_keys( Languages::getActive() ) ); if ( $removedLanguages ) { $inProgressJobsSearchParams = self::getInProgressSearch() /** @phpstan-ignore-next-line */ ->set_target_language( array_values( $removedLanguages ) ); $this->hideJobs( $inProgressJobsSearchParams ); Fns::map( [ Option::class, 'removeLanguageFromCompleted' ], $removedLanguages ); } } public function hideJobsAfterTranslationMethodChange( $translateEverythingActive ) { if ( ! $translateEverythingActive ) { $this->hideJobs( self::getInProgressSearch() ); } } private static function getInProgressSearch() { return ( new \WPML_TM_Jobs_Search_Params() )->set_status( [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ] ); } private function hideJobs( \WPML_TM_Jobs_Search_Params $jobsSearchParams ) { $translationJobs = wpml_collect( wpml_tm_get_jobs_repository()->get( $jobsSearchParams ) ) ->filter( invoke( 'is_ate_editor' ) ) ->filter( invoke( 'is_automatic' ) ); $canceledInATE = $this->apiClient->hideJobs( $translationJobs->map( invoke( 'get_editor_job_id' ) )->values()->toArray() ); $isResponseValid = $canceledInATE && ! is_wp_error( $canceledInATE ); $jobsHiddenInATE = $isResponseValid ? Obj::propOr( [], 'jobs', $canceledInATE ) : []; $isHiddenInATE = function ( $job ) use ( $isResponseValid, $jobsHiddenInATE ) { return $isResponseValid && Lst::includes( $job->get_editor_job_id(), $jobsHiddenInATE ); }; $setStatus = Fns::tap( function ( \WPML_TM_Post_Job_Entity $job ) use ( $isHiddenInATE ) { $status = $isHiddenInATE( $job ) ? ICL_TM_ATE_CANCELLED : ICL_TM_NOT_TRANSLATED; $job->set_status( $status ); } ); $translationJobs->map( $setStatus ) ->map( Fns::tap( [ make( \WPML_TP_Sync_Update_Job::class ), 'update_state' ] ) ); } } ATE/Hooks/class-wpml-tm-ate-job-data-fallback-action-factory.php 0000755 00000000417 14720342453 0020365 0 ustar 00 <?php class WPML_TM_ATE_Job_Data_Fallback_Factory implements IWPML_Backend_Action_Loader, IWPML_REST_Action_Loader { /** * @return WPML_TM_ATE_Job_Data_Fallback */ public function create() { return \WPML\Container\make( '\WPML_TM_ATE_Job_Data_Fallback' ); } } ATE/Hooks/class-wpml-tm-ate-jobs-actions.php 0000755 00000042150 14720342453 0014622 0 ustar 00 <?php use WPML\API\Sanitize; use WPML\FP\Fns; use WPML\FP\Json; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; use WPML\FP\Relation; use WPML\TM\API\Jobs; use WPML\FP\Wrapper; use WPML\Settings\PostType\Automatic; use WPML\Setup\Option; use WPML\TM\ATE\JobRecords; use WPML\TM\ATE\Log\Storage; use WPML\TM\ATE\Log\Entry; use function WPML\FP\partialRight; use function WPML\FP\pipe; use WPML\TM\API\ATE\LanguageMappings; use WPML\Element\API\Languages; /** * @author OnTheGo Systems */ class WPML_TM_ATE_Jobs_Actions implements IWPML_Action { const RESPONSE_ATE_NOT_ACTIVE_ERROR = 403; const RESPONSE_ATE_DUPLICATED_SOURCE_ID = 417; const RESPONSE_ATE_UNEXPECTED_ERROR = 500; const RESPONSE_ATE_ERROR_NOTICE_ID = 'ate-update-error'; const RESPONSE_ATE_ERROR_NOTICE_GROUP = 'default'; const CREATE_ATE_JOB_CHUNK_WORDS_LIMIT = 2000; /** * @var WPML_TM_ATE_API */ private $ate_api; /** * @var WPML_TM_ATE_Jobs */ private $ate_jobs; /** * @var WPML_TM_AMS_Translator_Activation_Records */ private $translator_activation_records; /** @var bool */ private $is_second_attempt_to_get_jobs_data = false; /** * @var SitePress */ private $sitepress; /** * @var WPML_Current_Screen */ private $current_screen; /** * WPML_TM_ATE_Jobs_Actions constructor. * * @param \WPML_TM_ATE_API $ate_api * @param \WPML_TM_ATE_Jobs $ate_jobs * @param \SitePress $sitepress * @param \WPML_Current_Screen $current_screen * @param \WPML_TM_AMS_Translator_Activation_Records $translator_activation_records */ public function __construct( WPML_TM_ATE_API $ate_api, WPML_TM_ATE_Jobs $ate_jobs, SitePress $sitepress, WPML_Current_Screen $current_screen, WPML_TM_AMS_Translator_Activation_Records $translator_activation_records ) { $this->ate_api = $ate_api; $this->ate_jobs = $ate_jobs; $this->sitepress = $sitepress; $this->current_screen = $current_screen; $this->translator_activation_records = $translator_activation_records; } public function add_hooks() { add_action( 'wpml_added_translation_job', [ $this, 'added_translation_job' ], 10, 2 ); add_action( 'wpml_added_translation_jobs', [ $this, 'added_translation_jobs' ], 10, 3 ); add_action( 'admin_notices', [ $this, 'handle_messages' ] ); add_filter( 'wpml_tm_ate_jobs_data', [ $this, 'get_ate_jobs_data_filter' ], 10, 2 ); add_filter( 'wpml_tm_ate_jobs_editor_url', [ $this, 'get_editor_url' ], 10, 3 ); } public function handle_messages() { if ( $this->current_screen->id_ends_with( WPML_TM_FOLDER . '/menu/translations-queue' ) ) { if ( array_key_exists( 'message', $_GET ) ) { if ( array_key_exists( 'ate_job_id', $_GET ) ) { $ate_job_id = filter_var( $_GET['ate_job_id'], FILTER_SANITIZE_NUMBER_INT ); $this->resign_job_on_error( $ate_job_id ); } $message = Sanitize::stringProp( 'message', $_GET ); ?> <div class="error notice-error notice otgs-notice"> <p><?php echo $message; ?></p> </div> <?php } } } /** * @param int $job_id * @param string $translation_service * * @throws \InvalidArgumentException * @throws \RuntimeException */ public function added_translation_job( $job_id, $translation_service ) { $this->added_translation_jobs( array( $translation_service => array( $job_id ) ) ); } /** * @param array $jobs * @param int|null $sentFrom * @param \WPML_TM_Translation_Batch $batch * * @return void * @throws \InvalidArgumentException * @throws \RuntimeException */ public function added_translation_jobs( array $jobs, $sentFrom = null, \WPML_TM_Translation_Batch $batch = null ) { $oldEditor = wpml_tm_load_old_jobs_editor(); $job_ids = Fns::reject( [ $oldEditor, 'shouldStickToWPMLEditor' ], Obj::propOr( [], 'local', $jobs ) ); if ( ! $job_ids ) { return; } $translationModeSetInDashboard = $batch ? $batch->getTranslationMode() : null; if ( $translationModeSetInDashboard === 'auto' ) { $applyTranslationMemory = ! $batch || $batch->getHowToHandleExisting() === \WPML_TM_Translation_Batch::HANDLE_EXISTING_LEAVE; } else { // We don't want to clear Translation Memory for manual jobs. // Most likely, such job will be almost immediately completed in ATE, but it is expected by users. $applyTranslationMemory = true; } $jobs = Fns::map( function ( $jobId ) use ( $applyTranslationMemory ) { return wpml_tm_create_ATE_job_creation_model( $jobId, $applyTranslationMemory ); }, $job_ids ); $responses = Fns::map( Fns::unary( partialRight( [ $this, 'create_jobs' ], $sentFrom ) ), $this->getChunkedJobs( $jobs, $translationModeSetInDashboard ) ); $created_jobs = $this->getResponsesJobs( $responses, $jobs ); if ( $created_jobs ) { $created_jobs = $this->map_response_jobs( $created_jobs ); $this->ate_jobs->warm_cache( array_keys( $created_jobs ) ); foreach ( $created_jobs as $wpml_job_id => $ate_job_id ) { $this->ate_jobs->store( $wpml_job_id, [ JobRecords::FIELD_ATE_JOB_ID => $ate_job_id ] ); $oldEditor->set( $wpml_job_id, WPML_TM_Editors::ATE ); $translationJob = wpml_tm_load_job_factory()->get_translation_job( $wpml_job_id, false, 0, true ); if ( $translationJob ) { $jobType = $this->getJobType( $translationJob, $translationModeSetInDashboard ); Jobs::setAutomaticStatus( $wpml_job_id, $jobType === 'auto' ); } if ( $sentFrom === Jobs::SENT_RETRY ) { Jobs::setStatus( $wpml_job_id, ICL_TM_WAITING_FOR_TRANSLATOR ); } } $message = __( '%1$s jobs added to the Advanced Translation Editor.', 'wpml-translation-management' ); $this->add_message( 'updated', sprintf( $message, count( $created_jobs ) ), 'wpml_tm_ate_create_job' ); do_action( 'wpml_tm_ate_jobs_created', $created_jobs ); } else { if ( Lst::includes( $sentFrom, [ Jobs::SENT_AUTOMATICALLY, Jobs::SENT_RETRY ] ) ) { if ( $sentFrom === Jobs::SENT_RETRY ) { $updateJob = function ($jobId) { Jobs::incrementRetryCount($jobId); $this->logRetryError( $jobId ); }; } else { $updateJob = function ( $jobId ) use ( $oldEditor, $translationModeSetInDashboard ) { $this->logError( $jobId ); $translationJob = wpml_tm_load_job_factory()->get_translation_job( $jobId, false, 0, true ); if ( $translationJob ) { $jobType = $this->getJobType( $translationJob, $translationModeSetInDashboard ); if ( $jobType === 'auto' ) { Jobs::setStatus( $jobId, ICL_TM_ATE_NEEDS_RETRY ); $oldEditor->set( $jobId, WPML_TM_Editors::ATE ); wpml_tm_load_job_factory()->update_job_data( $jobId, [ 'automatic' => 1 ] ); } } }; } wpml_collect( $job_ids )->map( $updateJob ); } $this->add_message( 'error', __( 'Jobs could not be created in Advanced Translation Editor. Please try again or contact the WPML support for help.', 'wpml-translation-management' ), 'wpml_tm_ate_create_job' ); } } private function map_response_jobs( $responseJobs ) { $result = []; foreach ( $responseJobs as $rid => $ate_job_id ) { $jobId = \WPML\TM\API\Job\Map::fromRid( $rid ); if ( $jobId ) { $result[ $jobId ] = $ate_job_id; } } return $result; } /** * @param string $type * @param string $message * @param string|null $id */ private function add_message( $type, $message, $id = null ) { do_action( 'wpml_tm_basket_add_message', $type, $message, $id ); } /** * @param array $jobsData * @param int|null $sentFrom * * @return mixed * @throws \InvalidArgumentException */ public function create_jobs( array $jobsData, $sentFrom ) { $setJobType = Logic::ifElse( Fns::always( $sentFrom ), Obj::assoc( 'job_type', $sentFrom ), Fns::identity() ); list( $existing, $new ) = Lst::partition( pipe( Obj::propOr( null, 'existing_ate_id' ), Logic::isNotNull() ), $jobsData['jobs'] ); $isAuto = Relation::propEq( 'type', 'auto', $jobsData ); return Wrapper::of( [ 'jobs' => $new, 'existing_jobs' => Lst::pluck( 'existing_ate_id', $existing ) ] ) ->map( Obj::assoc( 'auto_translate', $isAuto ) ) ->map( Obj::assoc( 'preview', $isAuto && Option::shouldBeReviewed() ) ) ->map( $setJobType ) ->map( 'wp_json_encode' ) ->map( Json::toArray() ) ->map( [ $this->ate_api, 'create_jobs' ] ) ->get(); } /** * After implementation of wpmltm-3211 and wpmltm-3391, we should not find missing ATE IDs anymore. * Some code below seems dead but we'll keep it for now in case we are missing a specific context. * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3211 * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-3391 */ private function get_ate_jobs_data( array $translation_jobs ) { $ate_jobs_data = array(); $skip_getting_data = false; $ate_jobs_to_create = array(); $this->ate_jobs->warm_cache( wpml_collect( $translation_jobs )->pluck( 'job_id' )->toArray() ); foreach ( $translation_jobs as $translation_job ) { if ( $this->is_ate_translation_job( $translation_job ) ) { $ate_job_id = $this->get_ate_job_id( $translation_job->job_id ); // Start of possibly dead code. if ( ! $ate_job_id ) { $ate_jobs_to_create[] = $translation_job->job_id; $skip_getting_data = true; } // End of possibly dead code. if ( ! $skip_getting_data ) { $ate_jobs_data[ $translation_job->job_id ] = [ 'ate_job_id' => $ate_job_id ]; } } } // Start of possibly dead code. if ( ! $this->is_second_attempt_to_get_jobs_data && $ate_jobs_to_create ) { $this->added_translation_jobs( array( 'local' => $ate_jobs_to_create ) ); $ate_jobs_data = $this->get_ate_jobs_data( $translation_jobs ); $this->is_second_attempt_to_get_jobs_data = true; } // End of possibly dead code. return $ate_jobs_data; } /** * @param string $default_url * @param int $job_id * @param null|string $return_url * * @return string * @throws \InvalidArgumentException */ public function get_editor_url( $default_url, $job_id, $return_url = null ) { $isUserActivated = $this->translator_activation_records->is_current_user_activated(); if ( $isUserActivated || is_admin() ) { $ate_job_id = $this->ate_jobs->get_ate_job_id( $job_id ); if ( $ate_job_id ) { if ( ! $return_url ) { $return_url = add_query_arg( array( 'page' => WPML_TM_FOLDER . '/menu/translations-queue.php', 'ate-return-job' => $job_id, ), admin_url( '/admin.php' ) ); } $ate_job_url = $this->ate_api->get_editor_url( $ate_job_id, $return_url ); if ( $ate_job_url && ! is_wp_error( $ate_job_url ) ) { return $ate_job_url; } } } return $default_url; } /** * @param $ignore * @param array $translation_jobs * * @return array */ public function get_ate_jobs_data_filter( $ignore, array $translation_jobs ) { return $this->get_ate_jobs_data( $translation_jobs ); } private function get_ate_job_id( $job_id ) { return $this->ate_jobs->get_ate_job_id( $job_id ); } /** * @param mixed $response * * @throws \RuntimeException */ protected function check_response_error( $response ) { if ( is_wp_error( $response ) ) { $code = 0; $message = $response->get_error_message(); if ( $response->error_data && is_array( $response->error_data ) ) { foreach ( $response->error_data as $http_code => $error_data ) { $code = (int) Obj::pathOr(0, [0, 'status'], $error_data ); $message = ''; switch ( $code ) { case self::RESPONSE_ATE_NOT_ACTIVE_ERROR: $wp_admin_url = admin_url( 'admin.php' ); $mcsetup_page = add_query_arg( array( 'page' => WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS, 'sm' => 'mcsetup', ), $wp_admin_url ); $mcsetup_page .= '#ml-content-setup-sec-1'; $resend_link = '<a href="' . $mcsetup_page . '">' . esc_html__( 'Resend that email', 'wpml-translation-management' ) . '</a>'; $message .= '<p>' . esc_html__( 'WPML cannot send these documents to translation because the Advanced Translation Editor is not fully set-up yet.', 'wpml-translation-management' ) . '</p><p>' . esc_html__( 'Please open the confirmation email that you received and click on the link inside it to confirm your email.', 'wpml-translation-management' ) . '</p><p>' . $resend_link . '</p>'; break; case self::RESPONSE_ATE_DUPLICATED_SOURCE_ID: case self::RESPONSE_ATE_UNEXPECTED_ERROR: default: $message = '<p>' . __( 'Advanced Translation Editor error:', 'wpml-translation-management' ) . '</p><p>' . $error_data[0]['message'] . '</p>'; } $message = '<p>' . $message . '</p>'; } } /** @var WP_Error $response */ throw new RuntimeException( $message, $code ); } } /** * @param $ate_job_id */ private function resign_job_on_error( $ate_job_id ) { $job_id = $this->ate_jobs->get_wpml_job_id( $ate_job_id ); if ( $job_id ) { wpml_load_core_tm()->resign_translator( $job_id ); } } /** * @param $translation_job * * @return bool */ private function is_ate_translation_job( $translation_job ) { return 'local' === $translation_job->translation_service && WPML_TM_Editors::ATE === $translation_job->editor; } /** * @param array $responses * @param \WPML_TM_ATE_Models_Job_Create[] $sentJobs * * @return array */ private function getResponsesJobs( $responses, $sentJobs ) { $jobs = []; foreach ( $responses as $response ) { try { $this->check_response_error( $response ); if ( $response && isset( $response->jobs ) ) { $jobs = $jobs + (array) $response->jobs; } } catch ( RuntimeException $ex ) { do_action( 'wpml_tm_basket_add_message', 'error', $ex->getMessage() ); } } $existingJobs = wpml_collect( $sentJobs ) ->filter( Obj::prop( 'existing_ate_id' ) ) ->map( Obj::pick( [ 'source_id', 'existing_ate_id' ] ) ) ->keyBy( 'source_id' ) ->map( Obj::prop( 'existing_ate_id' ) ) ->toArray(); return $jobs + $existingJobs; } /** * @param \WPML_TM_ATE_Models_Job_Create[] $jobs * @param "auto"|"manual"|null $translationModeSetInDashboard * * @return array */ private function getChunkedJobs( $jobs, $translationModeSetInDashboard ) { $chunkedJobs = []; $currentChunk = -1; $currentWordCount = 0; $chunkType = 'auto'; $newChunk = function( $chunkType ) use ( &$chunkedJobs, &$currentChunk, &$currentWordCount ) { $currentChunk ++; $currentWordCount = 0; $chunkedJobs[ $currentChunk ] = [ 'type' => $chunkType, 'jobs' => [] ]; }; $newChunk( $chunkType ); foreach ( $jobs as $job ) { /** @var WPML_Element_Translation_Job $translationJob */ $translationJob = wpml_tm_load_job_factory()->get_translation_job( $job->id, false, 0, true ); if ( $translationJob ) { if ( ! Obj::prop( 'existing_ate_id', $job ) ) { $currentWordCount += $translationJob->estimate_word_count(); } $jobType = $this->getJobType( $translationJob, $translationModeSetInDashboard ); if ( $jobType !== $chunkType ) { $chunkType = $jobType; $newChunk( $chunkType ); } if ( $currentWordCount > self::CREATE_ATE_JOB_CHUNK_WORDS_LIMIT && count( $chunkedJobs[ $currentChunk ] ) > 0 ) { $newChunk( $chunkType ); } } $chunkedJobs[ $currentChunk ]['jobs'] [] = $job; } $hasJobs = pipe( Obj::prop( 'jobs' ), Lst::length() ); return Fns::filter( $hasJobs, $chunkedJobs ); } /** * @param int $jobId */ private function logRetryError( $jobId ) { $job = Jobs::get( $jobId ); if ( isset( $job->ate_comm_retry_count ) && $job->ate_comm_retry_count ) { Storage::add( Entry::retryJob( $jobId, [ 'retry_count' => $job->ate_comm_retry_count ] ) ); } } /** * @param int $jobId */ private function logError( $jobId ) { $job = Jobs::get( $jobId ); if ( $job ) { Storage::add( Entry::retryJob( $jobId, [ 'retry_count' => 0, 'comment' => 'Sending job to ate failed, queued to be sent again.', ] ) ); } } /** * @param $translationJob * @param "auto"|"manual"|null $translationModeSetInDashboard * * @return "manual"|"auto" */ private function getJobType( $translationJob, $translationModeSetInDashboard ) { $document = $translationJob->get_original_document(); if ( ! $document ) { return 'manual'; } elseif ( $translationModeSetInDashboard ) { return $translationModeSetInDashboard; } else { return $translationJob->get_source_language_code() === Languages::getDefaultCode() && Jobs::isEligibleForAutomaticTranslations( $translationJob->get_id() ) ? 'auto' : 'manual'; } } } ATE/Hooks/class-wpml-tm-old-editor-factory.php 0000755 00000000260 14720342453 0015163 0 ustar 00 <?php class WPML_TM_Old_Editor_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { public function create() { return new WPML_TM_Old_Editor(); } } ATE/Hooks/ReturnedJobActionsFactory.php 0000755 00000001036 14720342453 0014054 0 ustar 00 <?php namespace WPML\TM\ATE\Hooks; use WPML\TM\ATE\ReturnedJobs; use function WPML\Container\make; use function WPML\FP\partialRight; class ReturnedJobActionsFactory implements \IWPML_Backend_Action_Loader, \IWPML_REST_Action_Loader { public function create() { $ateJobs = make( \WPML_TM_ATE_Jobs::class ); $removeTranslationDuplicateStatus = partialRight( [ ReturnedJobs::class, 'removeJobTranslationDuplicateStatus' ], [ $ateJobs, 'get_wpml_job_id' ] ); return new ReturnedJobActions( $removeTranslationDuplicateStatus ); } } ATE/Hooks/class-wpml-tm-ate-post-edit-actions-factory.php 0000755 00000000705 14720342453 0017242 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Post_Edit_Actions_Factory implements IWPML_Backend_Action_Loader { /** * @return IWPML_Action|IWPML_Action[]|null */ public function create() { $tm_ate = new WPML_TM_ATE(); $endpoints = WPML\Container\make( 'WPML_TM_ATE_AMS_Endpoints' ); if ( $tm_ate->is_translation_method_ate_enabled() ) { return new WPML_TM_ATE_Post_Edit_Actions( $endpoints ); } return null; } } ATE/Hooks/class-wpml-tm-ams-synchronize-users-on-access-denied.php 0000755 00000004246 14720342453 0021053 0 ustar 00 <?php class WPML_TM_AMS_Synchronize_Users_On_Access_Denied { const ERROR_MESSAGE = 'Authentication error, please contact your translation manager to check your subscription'; /** @var WPML_TM_AMS_Synchronize_Actions */ private $ams_synchronize_actions; /** @var WPML_TM_ATE_Jobs */ private $ate_jobs; public function add_hooks() { if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) { add_action( 'init', array( $this, 'catch_access_error' ) ); } } public function catch_access_error() { if ( ! $this->ate_redirected_due_to_lack_of_access() ) { return; } $this->get_ams_synchronize_actions()->synchronize_translators(); if ( ! isset( $_GET['ate_job_id'] ) ) { return; } $wpml_job_id = $this->get_ate_jobs()->get_wpml_job_id( $_GET['ate_job_id'] ); if ( ! $wpml_job_id ) { return; } $url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php&job_id=' . $wpml_job_id ); wp_safe_redirect( $url, 302, 'WPML' ); } /** * @return bool */ private function ate_redirected_due_to_lack_of_access() { return isset( $_GET['message'] ) && false !== strpos( $_GET['message'], self::ERROR_MESSAGE ); } /** * @return IWPML_Action|IWPML_Action[]|WPML_TM_AMS_Synchronize_Actions */ private function get_ams_synchronize_actions() { if ( ! $this->ams_synchronize_actions ) { $factory = new WPML_TM_AMS_Synchronize_Actions_Factory(); $this->ams_synchronize_actions = $factory->create(); } return $this->ams_synchronize_actions; } /** * @return WPML_TM_ATE_Jobs */ private function get_ate_jobs() { if ( ! $this->ate_jobs ) { $ate_jobs_records = wpml_tm_get_ate_job_records(); $this->ate_jobs = new WPML_TM_ATE_Jobs( $ate_jobs_records ); } return $this->ate_jobs; } /** * @param WPML_TM_AMS_Synchronize_Actions $ams_synchronize_actions */ public function set_ams_synchronize_actions( WPML_TM_AMS_Synchronize_Actions $ams_synchronize_actions ) { $this->ams_synchronize_actions = $ams_synchronize_actions; } /** * @param WPML_TM_ATE_Jobs $ate_jobs */ public function set_ate_jobs( WPML_TM_ATE_Jobs $ate_jobs ) { $this->ate_jobs = $ate_jobs; } } ATE/Hooks/class-wpml-tm-ate-job-data-fallback-action.php 0000755 00000001573 14720342453 0016724 0 ustar 00 <?php use WPML\TM\ATE\JobRecords; class WPML_TM_ATE_Job_Data_Fallback implements IWPML_Action { /** @var WPML_TM_ATE_API */ private $ate_api; /** * @param WPML_TM_ATE_API $ate_api */ public function __construct( WPML_TM_ATE_API $ate_api ) { $this->ate_api = $ate_api; } public function add_hooks() { add_filter( 'wpml_tm_ate_job_data_fallback', array( $this, 'get_data_from_api' ), 10, 2 ); } /** * @param array $data * @param int $wpml_job_id * * @return array */ public function get_data_from_api( array $data, $wpml_job_id ) { $response = $this->ate_api->get_jobs_by_wpml_ids( array( $wpml_job_id ) ); if ( ! $response || is_wp_error( $response ) ) { return $data; } if ( ! isset( $response->{$wpml_job_id}->ate_job_id ) ) { return $data; } return array( JobRecords::FIELD_ATE_JOB_ID => $response->{$wpml_job_id}->ate_job_id ); } } ATE/Hooks/class-wpml-tm-old-editor.php 0000755 00000003641 14720342453 0013524 0 ustar 00 <?php class WPML_TM_Old_Editor implements IWPML_Action { const ACTION = 'icl_ajx_custom_call'; const CUSTOM_AJAX_CALL = 'icl_doc_translation_method'; const NOTICE_ID = 'wpml-translation-management-old-editor'; const NOTICE_GROUP = 'wpml-translation-management'; public function add_hooks() { add_action( self::ACTION, array( $this, 'handle_custom_ajax_call' ), 10, 2 ); } public function handle_custom_ajax_call( $call, $data ) { if ( self::CUSTOM_AJAX_CALL === $call ) { if ( ! isset( $data[ WPML_TM_Old_Jobs_Editor::OPTION_NAME ] ) ) { return; } $old_editor = $data[ WPML_TM_Old_Jobs_Editor::OPTION_NAME ]; if ( ! in_array( $old_editor, array( WPML_TM_Editors::WPML, WPML_TM_Editors::ATE ), true ) ) { return; } update_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME, $old_editor ); if ( WPML_TM_Editors::WPML === $old_editor && $this->is_ate_enabled_and_manager_wizard_completed() ) { $text = __( 'You activated the Advanced Translation Editor for this site, but you are updating an old translation. WPML opened the Standard Translation Editor, so you can update this translation. When you translate new content, you\'ll get the Advanced Translation Editor with all its features. To change your settings, go to WPML Settings.', 'sitepress' ); $notice = new WPML_Notice( self::NOTICE_ID, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( 'notice-info' ); $notice->set_dismissible( true ); $notice->add_display_callback( 'WPML_TM_Page::is_translation_editor_page' ); wpml_get_admin_notices()->add_notice( $notice, true ); } else { wpml_get_admin_notices()->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID ); } } } /** * @return bool */ private function is_ate_enabled_and_manager_wizard_completed() { return WPML_TM_ATE_Status::is_enabled_and_activated() && (bool) get_option( WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER, false ); } } ATE/Hooks/class-wpml-tm-ate-translator-login-factory.php 0000755 00000001163 14720342453 0017172 0 ustar 00 <?php /** * \WPML_TM_ATE_Translator_Login factory. * * @author OnTheGo Systems * * NOTE: This uses the Frontend loader because is_admin() returns false during wp_login */ class WPML_TM_ATE_Translator_Login_Factory implements IWPML_Frontend_Action_Loader { /** * It returns an instance of WPML_TM_ATE_Translator_Login is ATE is enabled and active. * * @return \WPML_TM_ATE_Translator_Logine|\IWPML_Frontend_Action_Loader|null */ public function create() { if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) { return WPML\Container\make( WPML_TM_ATE_Translator_Login::class ); } return null; } } ATE/Hooks/class-wpml-tm-ate-jobs-actions-factory.php 0000755 00000003227 14720342453 0016271 0 ustar 00 <?php use function WPML\Container\make; use WPML\TM\ATE\ReturnedJobs; /** * Factory class for \WPML_TM_ATE_Jobs_Actions. * * @package wpml\tm * * @author OnTheGo Systems */ class WPML_TM_ATE_Jobs_Actions_Factory implements IWPML_Backend_Action_Loader, \IWPML_REST_Action_Loader { /** * The instance of \WPML_Current_Screen. * * @var WPML_Current_Screen */ private $current_screen; /** * It returns an instance of \WPML_TM_ATE_Jobs_Actions or null if ATE is not enabled and active. * * @return \WPML_TM_ATE_Jobs_Actions|null * @throws \Auryn\InjectionException */ public function create() { $ams_ate_factories = wpml_tm_ams_ate_factories(); if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) { $sitepress = $this->get_sitepress(); $current_screen = $this->get_current_screen(); $ate_api = $ams_ate_factories->get_ate_api(); $records = wpml_tm_get_ate_job_records(); $ate_jobs = new WPML_TM_ATE_Jobs( $records ); $translator_activation_records = new WPML_TM_AMS_Translator_Activation_Records( new WPML_WP_User_Factory() ); return new WPML_TM_ATE_Jobs_Actions( $ate_api, $ate_jobs, $sitepress, $current_screen, $translator_activation_records ); } return null; } /** * The global instance of \Sitepress. * * @return SitePress */ private function get_sitepress() { global $sitepress; return $sitepress; } /** * It gets the instance of \WPML_Current_Screen. * * @return \WPML_Current_Screen */ private function get_current_screen() { if ( ! $this->current_screen ) { $this->current_screen = new WPML_Current_Screen(); } return $this->current_screen; } } ATE/Hooks/class-wpml-tm-ate-translator-login.php 0000755 00000002174 14720342453 0015530 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Translator_Login implements IWPML_Action { /** @var WPML_TM_AMS_Translator_Activation_Records */ private $translator_activation_records; /** @var WPML_Translator_Records */ private $translator_records; /** @var WPML_TM_AMS_API */ private $ams_api; public function __construct( WPML_TM_AMS_Translator_Activation_Records $translator_activation_records, WPML_Translator_Records $translator_records, WPML_TM_AMS_API $ams_api ) { $this->translator_activation_records = $translator_activation_records; $this->translator_records = $translator_records; $this->ams_api = $ams_api; } public function add_hooks() { add_action( 'wp_login', array( $this, 'wp_login' ), 10, 2 ); } public function wp_login( $user_login, $user ) { if ( $this->translator_records->does_user_have_capability( $user->ID ) ) { $result = $this->ams_api->is_subscription_activated( $user->user_email ); if ( ! is_wp_error( $result ) ) { $this->translator_activation_records->set_activated( $user->user_email, $result ); } } } } ATE/Hooks/class-wpml-tm-ate-translator-message-classic-editor-factory.php 0000755 00000003021 14720342453 0022404 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_TM_ATE_Translator_Message_Classic_Editor_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { /** * @return \WPML_TM_ATE_Translator_Message_Classic_Editor|\IWPML_Action|null */ public function create() { global $wpdb; if ( $this->is_ajax_or_translation_queue() && $this->is_ate_enabled_and_manager_wizard_completed() && ! $this->is_editing_old_translation_and_te_is_used_for_old_translation() ) { $email_twig_factory = wpml_tm_get_email_twig_template_factory(); return new WPML_TM_ATE_Translator_Message_Classic_Editor( new WPML_Translation_Manager_Records( $wpdb, wpml_tm_get_wp_user_query_factory(), wp_roles() ), wpml_tm_get_wp_user_factory(), new WPML_TM_ATE_Request_Activation_Email( new WPML_TM_Email_Notification_View( $email_twig_factory->create() ) ) ); } return null; } /** * @return bool */ private function is_editing_old_translation_and_te_is_used_for_old_translation() { return Sanitize::stringProp( 'job_id', $_GET ) && get_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME ) === WPML_TM_Editors::WPML; } /** * @return bool */ private function is_ate_enabled_and_manager_wizard_completed() { return WPML_TM_ATE_Status::is_enabled_and_activated() && (bool) get_option( WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER, false ); } /** * @return bool */ private function is_ajax_or_translation_queue() { return wpml_is_ajax() || WPML_TM_Page::is_translation_queue(); } } ATE/Hooks/class-wpml-tm-ate-post-edit-actions.php 0000755 00000001161 14720342453 0015572 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Post_Edit_Actions implements IWPML_Action { private $endpoints; /** * WPML_TM_ATE_Jobs_Actions constructor. * * @param WPML_TM_ATE_AMS_Endpoints $endpoints */ public function __construct( WPML_TM_ATE_AMS_Endpoints $endpoints ) { $this->endpoints = $endpoints; } public function add_hooks() { add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) ); } public function allowed_redirect_hosts( $hosts ) { $hosts[] = $this->endpoints->get_AMS_host(); $hosts[] = $this->endpoints->get_ATE_host(); return $hosts; } } ATE/Hooks/JobActionsFactory.php 0000755 00000000505 14720342453 0012343 0 ustar 00 <?php namespace WPML\TM\ATE\Hooks; use function WPML\Container\make; class JobActionsFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { public function create() { return \WPML_TM_ATE_Status::is_enabled_and_activated() ? new JobActions( make( \WPML_TM_ATE_API::class ) ) : null; } } ATE/Hooks/ReturnedJobActions.php 0000755 00000002234 14720342453 0012525 0 ustar 00 <?php namespace WPML\TM\ATE\Hooks; use WPML\FP\Obj; /** * It performs the action at the moment when a user comes back from ATE to WordPress site. * Currently, It utilizes the _GET parameters like: * - ate_original_id * - complete * * We have the access to additional params like: * - complete_no_changes * - ate_status * * At this moment, we have only one action which changes the status of a job from "duplicated" to "in progress" . * @see WPML\TM\ATE\ReturnedJobs::removeJobTranslationDuplicateStatus */ class ReturnedJobActions implements \IWPML_Action { /** @var callable(int): void */ private $removeTranslationDuplicateStatus; /** * @param callable $removeTranslationDuplicateStatus */ public function __construct( callable $removeTranslationDuplicateStatus ) { $this->removeTranslationDuplicateStatus = $removeTranslationDuplicateStatus; } public function add_hooks() { add_action( 'init', [ $this, 'callActions' ] ); } public function callActions() { if ( isset( $_GET['ate_original_id'] ) && Obj::prop( 'complete', $_GET ) ) { call_user_func( $this->removeTranslationDuplicateStatus, (int) $_GET['ate_original_id'] ); } } } ATE/Hooks/class-wpml-tm-ate-translator-message-classic-editor.php 0000755 00000011003 14720342453 0020736 0 ustar 00 <?php use WPML\DocPage; class WPML_TM_ATE_Translator_Message_Classic_Editor implements IWPML_Action { const ACTION = 'wpml_ate_translator_classic_editor'; const USER_OPTION = 'wpml_ate_translator_classic_editor_minimized'; /** @var WPML_Translation_Manager_Records */ private $translation_manager_records; /** @var WPML_WP_User_Factory */ private $user_factory; /** @var WPML_TM_ATE_Request_Activation_Email */ private $activation_email; public function __construct( WPML_Translation_Manager_Records $translation_manager_records, WPML_WP_User_Factory $user_factory, WPML_TM_ATE_Request_Activation_Email $activation_email ) { $this->translation_manager_records = $translation_manager_records; $this->user_factory = $user_factory; $this->activation_email = $activation_email; } public function add_hooks() { add_action( 'wpml_tm_editor_messages', array( $this, 'classic_editor_message' ) ); add_action( 'wp_ajax_' . self::ACTION, array( $this, 'handle_ajax' ) ); } public function classic_editor_message() { $main_message = esc_html__( "This site can use WPML's Advanced Translation Editor, but you did not receive permission to use it. You are still translating with WPML's classic translation editor. Please ask your site's Translation Manager to enable the Advanced Translation Editor for you.", 'wpml-translation-management' ); $learn_more = esc_html__( "Learn more about WPML's Advanced Translation Editor", 'wpml-translation-management' ); $short_message = esc_html__( 'Advanced Translation Editor is disabled.', 'wpml-translation-management' ); $more = esc_html__( 'More', 'wpml-translation-management' ); $request_activation = esc_html__( 'Request activation from', 'wpml-translation-management' ); $link = DocPage::aboutATE(); $show_minimized = (bool) $this->user_factory->create_current()->get_option( self::USER_OPTION ); ?> <div class="notice notice-info otgs-notice js-classic-editor-notice" data-nonce="<?php echo wp_create_nonce( self::ACTION ); ?>" data-action="<?php echo self::ACTION; ?>" <?php if ( $show_minimized ) { ?> style="display: none" <?php } ?> > <p><?php echo $main_message; ?></p> <p><a href="<?php echo esc_attr( $link ); ?>" class="wpml-external-link" target="_blank"><?php echo $learn_more; ?></a></p> <p> <a class="button js-request-activation"><?php echo $request_activation; ?></a> <?php $this->output_translation_manager_list(); ?> </p> <p class="js-email-sent" style="display: none"></p> <a class="js-minimize otgs-notice-toggle"> <?php esc_html_e( 'Minimize', 'wpml-translation-management' ); ?> </a> </div> <div class="notice notice-info otgs-notice js-classic-editor-notice-minimized" <?php if ( ! $show_minimized ) { ?> style="display: none" <?php } ?> > <p><?php echo $short_message; ?> <a class="js-maximize"><?php echo $more; ?></a></p> </div> <?php } private function output_translation_manager_list() { $translation_managers = $this->translation_manager_records->get_users_with_capability(); ?> <select class="js-translation-managers"> <?php foreach ( $translation_managers as $translation_manager ) { $display_name = $translation_manager->user_login . ' (' . $translation_manager->user_email . ')'; ?> <option value="<?php echo $translation_manager->ID; ?> "><?php echo $display_name; ?></option> <?php } ?> </select> <?php } public function handle_ajax() { if ( wp_verify_nonce( $_POST['nonce'], self::ACTION ) ) { $current_user = $this->user_factory->create_current(); switch ( $_POST['command'] ) { case 'minimize': $current_user->update_option( self::USER_OPTION, true ); wp_send_json_success( array( 'message' => '' ) ); case 'maximize': $current_user->update_option( self::USER_OPTION, false ); wp_send_json_success( array( 'message' => '' ) ); case 'requestActivation': $manager = $this->user_factory->create( (int) $_POST['manager'] ); if ( $this->activation_email->send_email( $manager, $current_user ) ) { $message = sprintf( esc_html__( 'An email has been sent to %s', 'wpml-translation-management' ), $manager->user_login ); } else { $message = sprintf( esc_html__( 'Sorry, the email could not be sent to %s for an unknown reason.', 'wpml-translation-management' ), $manager->user_login ); } wp_send_json_success( array( 'message' => $message ) ); } } } } ATE/Hooks/class-wpml-tm-ams-synchronize-actions-factory.php 0000755 00000002204 14720342453 0017710 0 ustar 00 <?php use WPML\TM\ATE\UsersByCapsRepository; use function WPML\Container\make; /** * @author OnTheGo Systems */ class WPML_TM_AMS_Synchronize_Actions_Factory implements IWPML_Backend_Action_Loader { /** * @return WPML_TM_AMS_Synchronize_Actions|null */ public function create() { if ( WPML_TM_ATE_Status::is_enabled_and_activated() ) { $ams_api = make( WPML_TM_AMS_API::class ); global $wpdb; $user_query_factory = new WPML_WP_User_Query_Factory(); $wp_roles = wp_roles(); $translator_records = new WPML_Translator_Records( $wpdb, $user_query_factory, $wp_roles ); $manager_records = new WPML_Translation_Manager_Records( $wpdb, $user_query_factory, $wp_roles ); $user_records = make( \WPML_TM_AMS_Users::class ); $user_factory = new WPML_WP_User_Factory(); $translator_activation_records = new WPML_TM_AMS_Translator_Activation_Records( new WPML_WP_User_Factory() ); return new WPML_TM_AMS_Synchronize_Actions( $ams_api, $user_records, $user_factory, $translator_activation_records ); } return null; } } ATE/Hooks/class-wpml-tm-ams-synchronize-users-on-access-denied-factory.php 0000755 00000000316 14720342453 0022512 0 ustar 00 <?php class WPML_TM_AMS_Synchronize_Users_On_Access_Denied_Factory implements IWPML_Backend_Action_Loader { public function create() { return new WPML_TM_AMS_Synchronize_Users_On_Access_Denied(); } } ATE/Hooks/class-wpml-tm-ate-jobs-store-actions.php 0000755 00000001776 14720342453 0015765 0 ustar 00 <?php /** * @todo The hook 'wpml_tm_ate_jobs_store' seems to be never used so this class and its factory may be obsolete * * @author OnTheGo Systems */ class WPML_TM_ATE_Jobs_Store_Actions implements IWPML_Action { /** * @var WPML_TM_ATE_Jobs */ private $ate_jobs; /** * WPML_TM_ATE_Jobs_Actions constructor. * * @param WPML_TM_ATE_Jobs $ate_jobs */ public function __construct( WPML_TM_ATE_Jobs $ate_jobs ) { $this->ate_jobs = $ate_jobs; } public function add_hooks() { add_action( 'wpml_tm_ate_jobs_store', array( $this, 'store_action' ), 10, 2 ); } /** * @param int $wpml_job_id * @param array $ate_job_data * * @return array|null */ public function store( $wpml_job_id, $ate_job_data ) { return $this->ate_jobs->store( $wpml_job_id, $ate_job_data ); } /** * @param int $wpml_job_id * @param array $ate_job_data * * @return void */ public function store_action( $wpml_job_id, $ate_job_data ) { $this->ate_jobs->store( $wpml_job_id, $ate_job_data ); } } ATE/Hooks/class-wpml-tm-ate-required-actions-base.php 0000755 00000001066 14720342453 0016416 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Required_Actions_Base { private $ate_enabled; protected function is_ate_enabled() { if ( null === $this->ate_enabled ) { $tm_settings = wpml_get_setting_filter( null, 'translation-management' ); $doc_translation_method = null; if ( array_key_exists( 'doc_translation_method', $tm_settings ) ) { $doc_translation_method = $tm_settings['doc_translation_method']; } $this->ate_enabled = $doc_translation_method === ICL_TM_TMETHOD_ATE; } return $this->ate_enabled; } } ATE/NoCreditPopup.php 0000755 00000002752 14720342453 0010436 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\LIB\WP\User; use function WPML\Container\make; class NoCreditPopup { /** * @return string */ public function getUrl() { $baseUrl = make( \WPML_TM_ATE_AMS_Endpoints::class )->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ); return $baseUrl . '/mini_app/main.js'; } /** * @return array */ public function getData() { $registration_data = make( \WPML_TM_AMS_API::class )->get_registration_data(); $data = [ 'host' => make( \WPML_TM_ATE_AMS_Endpoints::class )->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ), 'wpml_host' => get_site_url(), 'return_url' => \WPML\TM\API\Jobs::getCurrentUrl(), 'secret_key' => Obj::prop( 'secret', $registration_data ), 'shared_key' => Obj::prop( 'shared', $registration_data ), 'website_uuid' => make( \WPML_TM_ATE_Authentication::class )->get_site_id(), 'ui_language' => make( \SitePress::class )->get_user_admin_language( User::getCurrentId() ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'container' => '#wpml-ate-console-container', 'languages' => $this->getLanguagesData(), ]; return $data; } public function getLanguagesData() { $languageFields = [ 'code', 'english_name', 'native_name', 'default_locale', 'encode_url', 'tag', 'flag_url', 'display_name' ]; return Fns::map( Obj::pick( $languageFields ), Languages::withFlags( Languages::getActive() ) ); } } ATE/auto-translate/endpoints/SyncLock.php 0000755 00000001301 14720342453 0014363 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\Utilities\KeyedLock; use function WPML\Container\make; class SyncLock implements IHandler { public function run( Collection $data ) { $lock = make( \WPML\TM\ATE\SyncLock::class ); $action = $data->get( 'action', 'acquire' ); if ( $action === 'release' ) { $lockKey = $lock->create( $data->get( 'lockKey' ) ); if ( $lockKey ) { $lock->release(); } return Either::of( [ 'action' => 'release', 'result' => (bool) $lockKey ] ); } else { return Either::of( [ 'action' => 'acquire', 'result' => $lock->create( null ) ] ); } } } ATE/auto-translate/endpoints/GetJobsCount.php 0000755 00000001304 14720342453 0015207 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\TM\ATE\AutoTranslate\Repository\JobsCountInterface; use WPML\TM\ATE\Jobs; /** * The endpoint is used in the sync process to determine: * - how many any ATE jobs left to sync * - how many automatic ATE jobs left to sync * - how many jobs needs review */ class GetJobsCount implements IHandler { /** @var JobsCountInterface $jobsCount */ private $jobsCount; public function __construct( JobsCountInterface $jobsCount ) { $this->jobsCount = $jobsCount; } public function run( Collection $data ) { return Either::of( $this->jobsCount->get() ); } } ATE/auto-translate/endpoints/GetJobsInfo.php 0000755 00000003416 14720342453 0015020 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\FP\Either; use WPML\Collect\Support\Collection; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\TM\API\Jobs; use WPML\TM\ATE\Review\PreviewLink; use WPML\TM\ATE\Review\ReviewStatus; use WPML\TM\ATE\Review\StatusIcons; use function WPML\FP\pipe; class GetJobsInfo implements \WPML\Ajax\IHandler { /** * @param Collection<jobIds: int[], returnUrl: string> $data * * @return Either<{jobId: int, automatic:'1'|'0', status: int, ateJobId: int}[]> */ public function run( Collection $data ) { $jobIds = $data->get( 'jobIds', [] ); $returnUrl = $data->get( 'returnUrl', '' ); $getLink = Logic::ifElse( ReviewStatus::doesJobNeedReview(), Fns::converge( PreviewLink::getWithSpecifiedReturnUrl( $returnUrl ), [ Obj::prop( 'translatedPostId' ), Obj::prop( 'jobId' ) ] ), pipe( Obj::prop( 'jobId' ), Jobs::getEditUrl( $returnUrl ) ) ); $getLabel = Logic::ifElse( ReviewStatus::doesJobNeedReview(), StatusIcons::getReviewTitle( 'language_code' ), StatusIcons::getEditTitle( 'language_code' ) ); return Either::of( \wpml_collect( $jobIds ) ->map( Jobs::get() ) ->map( Obj::addProp( 'translatedPostId', Jobs::getTranslatedPostId() ) ) ->map( Obj::renameProp( 'job_id', 'jobId' ) ) ->map( Obj::renameProp( 'editor_job_id', 'ateJobId' ) ) ->map( Obj::addProp( 'viewLink', $getLink ) ) ->map( Obj::addProp( 'label', $getLabel ) ) ->map( Obj::pick( [ 'jobId', 'viewLink', 'automatic', 'status', 'label', 'review_status', 'ateJobId' ] ) ) ->map( Obj::evolve( [ 'jobId' => Cast::toInt(), 'automatic' => Cast::toInt(), 'status' => Cast::toInt(), 'ateJobId' => Cast::toInt(), ] ) ) ); } } ATE/auto-translate/endpoints/GetCredits.php 0000755 00000000603 14720342453 0014677 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\LIB\WP\Option; use WPML\TM\API\ATE\Account; use WPML\WP\OptionManager; use function WPML\Container\make; class GetCredits implements IHandler { public function run( Collection $data ) { return Account::getCredits(); } } ATE/auto-translate/endpoints/ResumeAll.php 0000755 00000000506 14720342453 0014535 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use function WPML\Container\make; class ResumeAll implements IHandler { public function run( Collection $data ) { return Either::of( make( \WPML_TM_AMS_API::class )->resumeAll() ); } } ATE/auto-translate/endpoints/GetATEJobsToSync.php 0000755 00000000671 14720342453 0015676 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use function WPML\Container\make; class GetATEJobsToSync implements IHandler { public function run( Collection $data ) { /** @var \WPML_TM_ATE_Job_Repository $jobsRepo */ $jobsRepo = make( \WPML_TM_ATE_Job_Repository::class ); return Either::of( $jobsRepo->get_jobs_to_sync( true, true ) ); } } ATE/auto-translate/endpoints/GetNumberOfPosts.php 0000755 00000000656 14720342453 0016060 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; class GetNumberOfPosts { public function run( Collection $data, \wpdb $wpdb ) { $postIn = wpml_prepare_in( $data->get( 'postTypes', PostTypes::getAutomaticTranslatable() ) ); return $wpdb->get_var( "SELECT COUNT(id) FROM {$wpdb->posts} WHERE post_type IN ({$postIn}) AND post_status='publish'" ); } } ATE/auto-translate/endpoints/CancelJobs.php 0000755 00000002260 14720342453 0014646 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use function WPML\Container\make; use function WPML\FP\invoke; class CancelJobs implements IHandler { public function run( Collection $data ) { if ( $data->get( 'getTotal', false ) ) { return Either::of( wpml_tm_get_jobs_repository()->get_count( $this->getSearchParams() ) ); } $batchSize = $data->get( 'batchSize', 1000 ); $params = $this->getSearchParams()->set_limit( $batchSize ); $toCancel = wpml_collect( wpml_tm_get_jobs_repository()->get( $params ) ); $toCancel->map( Fns::tap( invoke( 'set_status' )->with( ICL_TM_NOT_TRANSLATED ) ) ) ->map( Fns::tap( [ make( \WPML_TP_Sync_Update_Job::class ), 'update_state' ] ) ); return Either::of( $toCancel->count() ); } /** * @return \WPML_TM_Jobs_Search_Params */ private function getSearchParams() { $searchParams = new \WPML_TM_Jobs_Search_Params(); $searchParams->set_status( [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ] ); $searchParams->set_custom_where_conditions( [ 'translate_job.automatic = 1' ] ); return $searchParams; } } ATE/auto-translate/endpoints/AutoTranslate.php 0000755 00000002171 14720342453 0015432 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Left; use WPML\FP\Obj; use WPML\FP\Right; use WPML\TM\API\Jobs; class AutoTranslate implements IHandler { public function run( Collection $data ) { global $wpml_translation_job_factory; $trid = $data->get( 'trid' ); $language_code = $data->get( 'language' ); if ( $trid && $language_code ) { $post_id = \SitePress::get_original_element_id_by_trid( $trid ); if ( ! $post_id ) { return Either::left( 'Post cannot be found by trid' ); } $job_id = $wpml_translation_job_factory->create_local_post_job( $post_id, $language_code ); $job = Jobs::get( (int) $job_id ); if ( ! $job ) { return Either::left( 'Job could not be created' ); } if ( Obj::prop( 'automatic', $job ) ) { return Right::of( [ 'jobId' => $job_id, 'automatic' => 1 ] ); } else { return Right::of( [ 'jobId' => $job_id, 'automatic' => 0, 'editUrl' => Jobs::getEditUrl( $data->get( 'currentUrl' ), $job_id ) ] ); } } return Left::of( 'invalid data' ); } } ATE/auto-translate/endpoints/ActivateLanguage.php 0000755 00000004422 14720342453 0016051 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\Setup\Option; use WPML\TM\API\ATE\LanguageMappings; class ActivateLanguage { public function run( Collection $data ) { $postTypes = PostTypes::getAutomaticTranslatable(); $updatePostTypes = function ( $newLanguages, $mergingFn ) use ( $postTypes ) { if ( $newLanguages && $postTypes ) { $completed = Option::getTranslateEverythingCompleted(); foreach ( $postTypes as $postType ) { $existingLanguages = Obj::propOr( [], $postType, $completed ); Option::markPostTypeAsCompleted( $postType, $mergingFn( $existingLanguages, $newLanguages ) ); } } }; $translateExistingContent = $data->get( 'translate-existing-content', false ); $newLanguages = $data->get( 'languages' ); if ( $translateExistingContent ) { $doesSupportAutomaticTranslations = function ( $code ) { $languageDetails = [ $code => [ 'code' => $code ] ]; // we need to build an input acceptable by LanguageMappings::withCanBeTranslatedAutomatically $languageDetails = LanguageMappings::withCanBeTranslatedAutomatically( $languageDetails ); return Obj::pathOr( false, [ $code, 'can_be_translated_automatically' ], $languageDetails ); }; list( $newLanguagesWhichCanBeAutoTranslated, $newLanguagesWhichCannotBeAutoTranslated ) = Lst::partition( $doesSupportAutomaticTranslations, $newLanguages ); // those languages which can be auto-translated should be removed from the completed list $updatePostTypes( $newLanguagesWhichCanBeAutoTranslated, Lst::diff() ); // those languages which cannot be auto-translated should be added to the completed list // to avoid accidental triggering Translate Everything for them when a user changes mapping or translation engines, // a∂nd they will become eligible for auto-translation. $updatePostTypes( $newLanguagesWhichCannotBeAutoTranslated, Lst::concat() ); } else { /** * If a user has chosen not to translate existing content, we should mark all languages as completed regardless of whether they can be auto-translated or not. */ $updatePostTypes( $newLanguages, Lst::concat() ); } return Either::of( 'ok' ); } } ATE/auto-translate/endpoints/EnableATE.php 0000755 00000003277 14720342453 0014374 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Ajax\IHandler; use WPML\API\Settings; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\User; use WPML\Setup\Option; use WPML\TM\API\ATE\LanguageMappings; use function WPML\Container\make; use function WPML\FP\pipe; class EnableATE implements IHandler { public function run( Collection $data ) { Settings::assoc( 'translation-management', 'doc_translation_method', ICL_TM_TMETHOD_ATE ); $cache = wpml_get_cache( \WPML_Translation_Roles_Records::CACHE_GROUP ); $cache->flush_group_cache(); /** @var \WPML_TM_AMS_API $ateApi */ $ateApi = make( \WPML_TM_AMS_API::class ); $status = $ateApi->get_status(); if ( Obj::propOr( false, 'activated', $status ) ) { $result = Either::right( true ); } else { /** @var \WPML_TM_AMS_Users $amsUsers */ $amsUsers = make( \WPML_TM_AMS_Users::class ); /** @var \WPML_TM_AMS_API $amsApi */ $amsApi = make( \WPML_TM_AMS_API::class ); $saveLanguageMapping = Fns::tap( pipe( [ Option::class, 'getLanguageMappings' ], Logic::ifElse( Logic::isEmpty(), Fns::always( true ), [ LanguageMappings::class, 'saveMapping'] ) ) ); $result = $amsApi->register_manager( User::getCurrent(), $amsUsers->get_translators(), $amsUsers->get_managers() )->map( $saveLanguageMapping ); $ateApi->get_status(); // Required to get the active status and store it. } return $result->map( Fns::tap( [ make( \WPML_TM_AMS_Synchronize_Actions::class ), 'synchronize_translators' ] ) ) ->bimap( pipe( Lst::make(), Lst::keyWith( 'error' ), Lst::nth(0) ), Fns::identity() ); } } ATE/auto-translate/endpoints/Languages.php 0000755 00000001556 14720342453 0014560 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Element\API\Languages as APILanguages; use WPML\TM\API\ATE\CachedLanguageMappings; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Right; use function WPML\FP\pipe; class Languages implements IHandler { public function run( Collection $data ) { $isAteActive = \WPML_TM_ATE_Status::is_enabled_and_activated(); if ( ! $isAteActive ) { return Right::of( [] ); } $defaultLanguage = APILanguages::getDefaultCode(); $getLanguages = pipe( APILanguages::class . '::getActive', CachedLanguageMappings::withCanBeTranslatedAutomatically(), Fns::map( Obj::addProp( 'is_default', Relation::propEq( 'code', $defaultLanguage ) ) ), Obj::values() ); return Right::of( $getLanguages() ); } } ATE/auto-translate/endpoints/SetForPostType.php 0000755 00000001607 14720342453 0015561 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\Settings\PostType\Automatic; use WPML\Setup\Option; use WPML\Element\API\Languages; class SetForPostType { public function run( Collection $data ) { $postTypes = $data->get( 'postTypes' ); $automatic = (bool) $data->get( 'automatic' ); $onlyNew = (bool) $data->get( 'onlyNew' ); foreach ( $postTypes as $type ) { Automatic::set( $type, $automatic ); if ( $automatic && $onlyNew ) { // Only future content should be translated. Option::markPostTypeAsCompleted( $type, Languages::getSecondaryCodes() ); continue; } // Not automatic or existing data should also be translated. // => Remove the flag that the post type was already translated. Option::removePostTypeFromCompleted( $type ); } return Either::of( true ); } } ATE/auto-translate/endpoints/CheckLanguageSupport.php 0000755 00000000725 14720342453 0016725 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Endpoint; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\TM\API\ATE\LanguageMappings; class CheckLanguageSupport { public function run( Collection $data ) { return Either::of( $data->get( 'languages', [] ) ) ->map( Fns::map( Obj::objOf( 'code' ) ) ) ->map( LanguageMappings::withCanBeTranslatedAutomatically() ); } } ATE/auto-translate/repository/JobsCount.php 0000755 00000001442 14720342453 0014766 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Repository; use WPML\TM\ATE\Jobs; class JobsCount implements JobsCountInterface { /** @var Jobs $jobs */ private $jobs; public function __construct( Jobs $jobs ) { $this->jobs = $jobs; } /** * @return array{ * allCount: int, * allAutomaticCount: int, * automaticWithoutLongstandingCount: int, * needsReviewCount: int * } */ public function get(): array { return [ 'allCount' => $this->jobs->getCountOfInProgress(), 'allAutomaticCount' => $this->jobs->getCountOfAutomaticInProgress( true ), 'automaticWithoutLongstandingCount' => $this->jobs->getCountOfAutomaticInProgress( false ), 'needsReviewCount' => $this->jobs->getCountOfNeedsReview(), ]; } } ATE/auto-translate/repository/CachedJobsCount.php 0000755 00000002170 14720342453 0016055 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Repository; use WPML\LIB\WP\Transient; class CachedJobsCount implements JobsCountInterface { const CACHE_KEY = 'wpml-ate-jobs-count'; /** @var JobsCountInterface $jobsCount */ private $jobsCount; public function __construct( JobsCountInterface $jobsCount ) { $this->jobsCount = $jobsCount; } /** * @return array{ * allCount: int, * allAutomaticCount: int, * automaticWithoutLongstandingCount: int, * needsReviewCount: int * } */ public function get(): array { $data = Transient::get( self::CACHE_KEY ); if ( $data && $this->validateCachedData( $data ) ) { return $data; } $data = $this->jobsCount->get(); Transient::set(self::CACHE_KEY, $data, 60*2 ); return $data; } private function validateCachedData( $data ): bool { if ( !is_array( $data ) ) { return false; } $requiredKeys = [ 'allCount', 'allAutomaticCount', 'automaticWithoutLongstandingCount', 'needsReviewCount' ]; foreach ( $requiredKeys as $key ) { if ( !array_key_exists( $key, $data ) || !is_int( $data[ $key ] ) ) { return false; } } return true; } } ATE/auto-translate/repository/JobsCountInterface.php 0000755 00000000431 14720342453 0016604 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Repository; interface JobsCountInterface { /** * @return array{ * allCount: int, * allAutomaticCount: int, * automaticWithoutLongstandingCount: int, * needsReviewCount: int * } */ public function get(): array; } ATE/auto-translate/hooks/JobsCountCacheInvalidateAction.php 0000755 00000000745 14720342453 0017762 0 ustar 00 <?php namespace WPML\TM\ATE\AutoTranslate\Hooks; use WPML\LIB\WP\Transient; use WPML\TM\ATE\AutoTranslate\Repository\CachedJobsCount; class JobsCountCacheInvalidateAction implements \IWPML_Backend_Action, \IWPML_REST_Action { public function add_hooks() { $clearCache = function() { Transient::delete( CachedJobsCount::CACHE_KEY ); }; add_action( 'wpml_tm_ate_jobs_created', $clearCache, 10, 0 ); add_action( 'wpml_tm_ate_jobs_downloaded', $clearCache, 10, 0 ); } } ATE/class-wpml-tm-ate-status.php 0000755 00000001632 14720342453 0012467 0 ustar 00 <?php use WPML\Setup\Option; /** * @author OnTheGo Systems */ class WPML_TM_ATE_Status { public static function is_enabled() { $tm_settings = wpml_get_setting_filter( null, 'translation-management' ); $doc_translation_method = null; if ( is_array( $tm_settings ) && array_key_exists( 'doc_translation_method', $tm_settings ) ) { $doc_translation_method = $tm_settings['doc_translation_method']; } return $doc_translation_method === ICL_TM_TMETHOD_ATE; } public static function is_active() { if ( Option::isTMAllowed() ) { $ams_data = get_option( WPML_TM_ATE_Authentication::AMS_DATA_KEY, array() ); if ( $ams_data && array_key_exists( 'status', $ams_data ) ) { return $ams_data['status'] === WPML_TM_ATE_Authentication::AMS_STATUS_ACTIVE; } } return false; } public static function is_enabled_and_activated() { return self::is_enabled() && self::is_active(); } } ATE/proxies/Widget.php 0000755 00000002362 14720342453 0010614 0 ustar 00 <?php namespace WPML\ATE\Proxies; use WPML\API\Sanitize; use WPML\LIB\WP\User; class Widget implements \IWPML_Frontend_Action, \IWPML_DIC_Action { const QUERY_VAR_ATE_WIDGET_SCRIPT = 'wpml-app'; const SCRIPT_NAME = 'ate-widget'; public function add_hooks() { // The widget is called using a script tag with src /?wpml-app=ate-widget, which invokes a frontend call. // There were several issues with 3rd party plugins which block the previous solution using 'template_include'. // Better using 'template_redirect'. This also prevents loading any further unnecessary frontend stuff. add_action( 'template_redirect', function() { $script = $this->get_script(); if ( $script ) { include $script; die(); } }, -PHP_INT_MAX // Make sure to be the first. Some plugins using this hook also to prevent usual rendering. ); } /** * @return string|void */ public function get_script() { if ( ! User::canManageTranslations() ) { return false; } $app = Sanitize::stringProp( self::QUERY_VAR_ATE_WIDGET_SCRIPT, $_GET ); if ( self::SCRIPT_NAME !== $app ) { return false; } $script = WPML_TM_PATH . '/res/js/' . $app . '.php'; return file_exists( $script ) ? $script : false; } } ATE/TranslateEverything.php 0000755 00000012544 14720342453 0011705 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Left; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Right; use WPML\FP\Str; use WPML\FP\Wrapper; use WPML\LIB\WP\Post; use WPML\Media\Option as MediaOption; use WPML\Records\Translations; use WPML\Setup\Option; use WPML\TM\API\ATE\CachedLanguageMappings; use WPML\TM\API\ATE\LanguageMappings; use WPML\TM\ATE\TranslateEverything\UntranslatedPosts; use WPML\TM\AutomaticTranslation\Actions\Actions; use WPML\Utilities\KeyedLock; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\pipe; class TranslateEverything { /** * @var UntranslatedPosts */ private $untranslatedPosts; const LOCK_RELEASE_TIMEOUT = 2 * MINUTE_IN_SECONDS; const QUEUE_SIZE = 15; public function __construct( UntranslatedPosts $untranslatedPosts ) { $this->untranslatedPosts = $untranslatedPosts; } public function run( Collection $data, Actions $actions ) { if ( ! MediaOption::isSetupFinished() ) { return Left::of( [ 'key' => 'media-setup-not-finished' ] ); } $lock = make( KeyedLock::class, [ ':name' => self::class ] ); $key = $lock->create( $data->get( 'key' ), self::LOCK_RELEASE_TIMEOUT ); if ( $key ) { $createdJobs = []; if ( Option::shouldTranslateEverything() ) { $createdJobs = $this->translateEverything( $actions ); } if ( self::isEverythingProcessed() || ! Option::shouldTranslateEverything() ) { $lock->release(); $key = false; } return Right::of( [ 'key' => $key, 'createdJobs' => $createdJobs ] ); } else { return Left::of( [ 'key' => 'in-use', ] ); } } /** * @param Actions $actions */ private function translateEverything( Actions $actions ) { list( $postType, $languagesToProcess ) = self::getPostTypeToProcess(); if ( ! $postType || ! $languagesToProcess ) { return []; } $elements = $this->untranslatedPosts->get( $languagesToProcess, $postType, self::QUEUE_SIZE + 1 ); if ( count( $elements ) <= self::QUEUE_SIZE ) { /** * We mark $postType as completed in all secondary languages, not only in eligible for automatic translations. * This is important due to the problem: * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1456/Changing-translation-engines-configuration-may-trigger-Translate-Everything-process * * When we activate a new secondary language and it does not support automatic translations, we mark it as completed by default. * It is done to prevent unexpected triggering Translate Everything process for that language, * when it suddently becomes eligible, for example because adjustment of translation engines. */ Option::markPostTypeAsCompleted( $postType, Languages::getSecondaryCodes() ); } return count( $elements ) ? $actions->createNewTranslationJobs( Languages::getDefaultCode(), Lst::slice( 0, self::QUEUE_SIZE, $elements ) ) : []; } /** * @return array Eg. ['post', ['fr', 'de', 'es']] */ private static function getPostTypeToProcess() { $postTypes = self::getPostTypesToTranslate( PostTypes::getAutomaticTranslatable(), LanguageMappings::geCodesEligibleForAutomaticTranslations() ); return wpml_collect( $postTypes ) ->prioritize( Relation::propEq(0, 'post') ) ->prioritize( Relation::propEq(0, 'page') ) ->first(); } /** * @param array $postTypes * @param array $targetLanguages * * @return array Eg. [['post', ['fr', 'de', 'es']], ['page', ['fr', 'de', 'es']]] */ public static function getPostTypesToTranslate( array $postTypes, array $targetLanguages ) { $completed = Option::getTranslateEverythingCompleted(); $getLanguageCodesNotCompletedForPostType = pipe( Obj::propOr( [], Fns::__, $completed ), Lst::diff( $targetLanguages ) ); $getPostTypesToTranslate = pipe( Fns::map( function ( $postType ) use ( $getLanguageCodesNotCompletedForPostType ) { return [ $postType, $getLanguageCodesNotCompletedForPostType( $postType ) ]; } ), Fns::filter( pipe( Obj::prop( 1 ), Lst::length() ) ) ); return $getPostTypesToTranslate( $postTypes ); } /** * @param string $postType * @param array $targetLanguages * * @return string[] Eg. ['fr', 'de', 'es'] */ public static function getLanguagesToTranslate( $postType, array $targetLanguages ) { $completed = Option::getTranslateEverythingCompleted(); return Lst::diff( $targetLanguages, Obj::propOr( [], $postType, $completed ) ); } /** * Checks if Translate Everything is processed for a given Post Type and Language. * * @param string|bool $postType * @param string $language * * @return bool */ public static function isEverythingProcessedForPostTypeAndLanguage( $postType, $language ) { $completed = Option::getTranslateEverythingCompleted(); return isset( $completed[ $postType ] ) && in_array( $language, $completed[ $postType ] ); } /** * @param bool $cached * * @return bool */ public static function isEverythingProcessed( $cached = false ) { $postTypes = PostTypes::getAutomaticTranslatable(); $getTargetLanguages = [ $cached ? CachedLanguageMappings::class : LanguageMappings::class, 'geCodesEligibleForAutomaticTranslations']; return count( self::getPostTypesToTranslate( $postTypes, $getTargetLanguages() ) ) === 0; } } ATE/Review/UpdateTranslation.php 0000755 00000005465 14720342453 0012611 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\TM\API\ATE; use WPML\TM\API\Jobs; use function WPML\Container\make; use function WPML\FP\partial; use function WPML\FP\pipe; class UpdateTranslation implements IHandler { public function run( Collection $data ) { $jobId = $data->get( 'jobId' ); $postId = $data->get( 'postId' ); $completedInATE = $data->get( 'completedInATE' ); $clickedBackInATE = $data->get( 'clickedBackInATE' ); if ( $completedInATE === 'COMPLETED_WITHOUT_CHANGED' || $clickedBackInATE ) { return $this->completeWithoutChanges( $jobId ); } $ateAPI = make( ATE::class ); $applyTranslation = pipe( partial( [ $ateAPI, 'applyTranslation' ], $jobId, $postId ), Logic::ifElse( Fns::identity(), Fns::tap( function () use ( $jobId ) { Jobs::setReviewStatus( $jobId, ReviewStatus::NEEDS_REVIEW ); } ), Fns::identity() ) ); $hasStatus = function ( $statuses ) { return pipe( Obj::prop( 'status_id' ), Lst::includes( Fns::__, $statuses ) ); }; $shouldApplyXLIFF = $hasStatus( [ \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERING, \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_TRANSLATED, \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_EDITED, ] ); $applyXLIFF = Logic::ifElse( pipe( Obj::prop( 'translated_xliff' ), $applyTranslation ), Fns::always( Either::of( 'applied' ) ), Fns::always( Either::left( 'error' ) ) ); $isDelivered = $hasStatus( [ \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_DELIVERED ] ); $isTranslating = $hasStatus( [ \WPML_TM_ATE_AMS_Endpoints::ATE_JOB_STATUS_TRANSLATING ] ); $userClickedCompleteInATE = Fns::always( $completedInATE === 'COMPLETED' ); $otherwise = Fns::always( true ); $handleATEResult = Logic::cond( [ [ $shouldApplyXLIFF, $applyXLIFF ], [ $isDelivered, Fns::always( Either::of( 'applied-without-changes' ) ) ], [ $userClickedCompleteInATE, Fns::always( Either::of( 'underway' ) ) ], [ $isTranslating, Fns::always( Either::of( 'in-progress' ) ) ], [ Logic::isEmpty(), Fns::always( Either::left( 'error' ) ) ], [ $otherwise, Fns::always( Either::of( 'in-progress' ) ) ] ] ); return Either::of( $jobId ) ->map( [ $ateAPI, 'checkJobStatus' ] ) ->chain( $handleATEResult ); } private function completeWithoutChanges( $jobId ) { $applyWithoutChanges = pipe( Fns::tap( Jobs::setStatus( Fns::__, ICL_TM_COMPLETE ) ), Fns::tap( Jobs::setReviewStatus( Fns::__, ReviewStatus::NEEDS_REVIEW ) ) ); return Either::of( $jobId ) ->map( $applyWithoutChanges ) ->map( Fns::always( 'applied-without-changes' ) ); } } ATE/Review/PackageJob.php 0000755 00000006127 14720342453 0011132 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\Setup\Option; use WPML\TM\API\Jobs; use function WPML\FP\spreadArgs; class PackageJob implements \IWPML_Backend_Action, \IWPML_REST_Action { const ELEMENT_TYPE_PREFIX = 'package'; /** * @return void */ public function add_hooks() { if ( \WPML_TM_ATE_Status::is_enabled_and_activated() && Option::shouldBeReviewed() ) { self::addReviewStatusHook(); self::addSavePackageHook(); self::addTranslationCompleteHook(); } } /** * @return void */ private static function addReviewStatusHook() { Hooks::onAction( 'wpml_added_translation_jobs' ) ->then( spreadArgs( function( $jobsIds ) { /** @var callable(object):void $setNeedsReview */ $setNeedsReview = function( $job ) { if ( self::isAutomatic( $job ) ) { Jobs::setReviewStatus( (int) Obj::prop( 'job_id', $job ), ReviewStatus::NEEDS_REVIEW ); } }; wpml_collect( $jobsIds ) ->flatten() ->map( Jobs::get() ) ->filter( Fns::unary( [ self::class, 'isPackageJob' ] ) ) ->map( $setNeedsReview ); } ) ); } /** * @return void */ private static function addSavePackageHook() { /** @var callable(null|string):callable $isReviewStatus */ $isReviewStatus = Relation::propEq( 'review_status' ); /** @var callable(object):bool $mightNeedReview */ $mightNeedReview = Logic::anyPass( [ $isReviewStatus( ReviewStatus::ACCEPTED ), // a previous job was completed. $isReviewStatus( ReviewStatus::NEEDS_REVIEW ), // a previous job needs update. $isReviewStatus( null ), // new job. ] ); /** @var callable(bool, string, object):bool $shouldSaveStringPackageTranslation */ $shouldSaveStringPackageTranslation = function( $shouldSave, $typePrefix, $job ) use ( $mightNeedReview ) { if ( self::ELEMENT_TYPE_PREFIX === $typePrefix && self::isAutomatic( $job ) && $mightNeedReview( $job ) && Option::HOLD_FOR_REVIEW === Option::getReviewMode() ) { return false; } return $shouldSave; }; Hooks::onFilter( 'wpml_should_save_external', 10, 3 ) ->then( spreadArgs( $shouldSaveStringPackageTranslation ) ); } /** * @return void */ private static function addTranslationCompleteHook() { $setPackageTranslationStatus = function( $new_post_id, $fields, $job ) { if ( ! self::isPackageJob( $job ) ) { return; } if ( Relation::propEq( 'review_status', ReviewStatus::EDITING, $job ) ) { Jobs::setReviewStatus( (int) $job->job_id, ReviewStatus::ACCEPTED ); } }; Hooks::onAction( 'wpml_pro_translation_completed', 10, 3 ) ->then( spreadArgs( $setPackageTranslationStatus ) ); } /** * @param object $job * * @return bool */ public static function isPackageJob( $job ) { return Relation::propEq( 'element_type_prefix', self::ELEMENT_TYPE_PREFIX, $job ); } /** * @param object $job * * @return bool */ private static function isAutomatic( $job ) { return (bool) Obj::prop( 'automatic', $job ); } } ATE/Review/StatusIcons.php 0000755 00000010347 14720342453 0011422 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Element\API\Languages; use WPML\Element\API\PostTranslations; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\Setup\Option; use WPML\TM\API\Jobs; use function WPML\FP\partial; use function WPML\FP\pipe; class StatusIcons implements \IWPML_Backend_Action { public function add_hooks() { if ( ! Option::isTMAllowed() ) { // Blog License. No access to Review mechanic. return; } $ifNeedsReview = function ( $fn ) { $doesNeedReview = function( $job ) { // Treat null as ACCEPTED. $review_status = isset( $job['review_status'] ) && $job['review_status'] ? $job['review_status'] : ReviewStatus::ACCEPTED; return ReviewStatus::needsReview( $review_status ); }; return Logic::ifElse( $doesNeedReview, $fn, Obj::prop( 'default' ) ); }; Hooks::onFilter( 'wpml_css_class_to_translation', PHP_INT_MAX , 6 ) ->then( Hooks::getArgs( [ 0 => 'default', 2 => 'languageCode', 3 => 'trid', 4 => 'status', 5 => 'review_status' ] ) ) ->then( $ifNeedsReview( Fns::always( 'otgs-ico-needs-review' ) ) ); Hooks::onFilter( 'wpml_text_to_translation', PHP_INT_MAX, 7 ) ->then( Hooks::getArgs( [ 0 => 'default', 2 => 'languageCode', 3 => 'trid', 5 => 'status', 6 => 'review_status' ] ) ) ->then( $ifNeedsReview ( self::getReviewTitle( 'languageCode' ) ) ); Hooks::onFilter( 'wpml_link_to_translation', PHP_INT_MAX, 7 ) ->then( Hooks::getArgs( [ 0 => 'default', 1 => 'postId', 2 => 'langCode', 3 => 'trid', 5 => 'status', 6 => 'review_status' ] ) ) ->then( $this->setLink() ); } public static function getReviewTitle( $langProp ) { return pipe( self::getLanguageName( $langProp ), Fns::unary( partial( 'sprintf', __( 'Review %s language', 'wpml-translation-management' ) ) ) ); } public static function getEditTitle( $langProp ) { return pipe( self::getLanguageName( $langProp ), Fns::unary( partial( 'sprintf', __( 'Edit %s translation', 'wpml-translation-management' ) ) ) ); } private static function getLanguageName( $langProp ) { return Fns::memorize( pipe( Obj::prop( $langProp ), Languages::getLanguageDetails(), Obj::prop( 'display_name' ) ) ); } private function setLink() { return function ( $data ) { if ( array_key_exists( 'review_status', $data ) ) { // Review status already provided by the filter. $review_status = $data['review_status'] ?: ReviewStatus::ACCEPTED; if ( ! ReviewStatus::needsReview( $review_status ) ) { // Does not need review. return $data['default']; } } $isInProgress = pipe( Obj::prop( 'status' ), Lst::includes( Fns::__, [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS, ICL_TM_ATE_NEEDS_RETRY ] ) ); $isInProgressOrCompleted = Logic::anyPass( [ $isInProgress, Relation::propEq( 'status', ICL_TM_COMPLETE ) ] ); $getTranslations = Fns::memorize( PostTranslations::get() ); $getTranslation = Fns::converge( Obj::prop(), [ Obj::prop( 'langCode' ), pipe( Obj::prop( 'postId' ), $getTranslations ) ] ); $getJob = Fns::converge( Jobs::getPostJob(), [ Obj::prop( 'postId' ), Fns::always( 'post' ), Obj::prop( 'langCode' ) ] ); $doesNeedsReview = pipe( Obj::prop( 'job' ), ReviewStatus::doesJobNeedReview() ); $getPreviewLink = Fns::converge( PreviewLink::get(), [ Obj::path( [ 'translation', 'element_id' ] ), Obj::path( [ 'job', 'job_id' ] ) ] ); $disableInProgressIconOfAutomaticJob = Logic::ifElse( Logic::both( $isInProgress, Obj::path( [ 'job', 'automatic' ] ) ), Fns::always( 0 ), // no link at all Obj::prop( 'default' ) ); return Maybe::of( $data ) ->filter( $isInProgressOrCompleted ) ->map( Obj::addProp( 'translation', $getTranslation ) ) ->filter( Obj::prop( 'translation' ) ) ->reject( Obj::path( [ 'translation', 'original' ] ) ) ->map( Obj::addProp( 'job', $getJob ) ) ->filter( Obj::prop( 'job' ) ) ->map( Logic::ifElse( $doesNeedsReview, $getPreviewLink, $disableInProgressIconOfAutomaticJob ) ) ->getOrElse( Obj::prop( 'default', $data ) ); }; } } ATE/Review/Cancel.php 0000755 00000003465 14720342453 0010333 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Post; use WPML\TM\API\Job\Map; use WPML\TM\API\Jobs; use function WPML\FP\pipe; class Cancel implements IHandler { public function run( Collection $data ) { $jobIds = $data->get( 'jobsIds' ); $deleteDrafts = $data->get( 'deleteDrafts' ); $reviewJobs = wpml_collect( $jobIds ) ->map( Jobs::get() ) ->filter( ReviewStatus::doesJobNeedReview() ); if ( $reviewJobs->count() ) { $reviewJobs->map( Obj::prop( 'job_id' ) ) ->map( Jobs::clearReviewStatus() ); if ( $deleteDrafts ) { $this->deleteDrafts( $reviewJobs ); } return $reviewJobs->pluck( 'job_id' )->map( Cast::toInt() ); } return []; } private function deleteDrafts( Collection $reviewJobs ) { $doCancelJobsAction = function ( Collection $jobIds ) { $getJobEntity = function ( $jobId ) { return wpml_tm_get_jobs_repository()->get_job( Map::fromJobId( $jobId ), \WPML_TM_Job_Entity::POST_TYPE ); }; $jobEntities = $jobIds->map( $getJobEntity )->toArray(); do_action( 'wpml_tm_jobs_cancelled', $jobEntities ); }; $getTranslatedId = Fns::memorize( pipe( Jobs::get(), Jobs::getTranslatedPostId() ) ); $isDraft = pipe( $getTranslatedId, Post::getStatus(), Relation::equals( 'draft' ) ); $deleteTranslatedPost = pipe( $getTranslatedId, Post::delete() ); $reviewJobs = $reviewJobs->map( Obj::prop( 'job_id' ) ) ->map( Jobs::setNotTranslatedStatus() ) ->map( Jobs::clearTranslated() ); $doCancelJobsAction( $reviewJobs ); $reviewJobs->filter( $isDraft ) ->map( Fns::tap( $deleteTranslatedPost ) ) ->map( Jobs::delete() ); } } ATE/Review/NonPublicCPTPreview.php 0000755 00000001446 14720342453 0012745 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; /** * This will allow displaying private CPT reviews on the frontend. */ class NonPublicCPTPreview { const POST_TYPE = 'wpmlReviewPostType'; /** * @param array $args * * @return array */ public static function addArgs( array $args ) { return Obj::assoc( self::POST_TYPE, \get_post_type( $args['preview_id'] ), $args ); } /** * @return callable */ public static function allowReviewPostTypeQueryVar() { return Lst::append( self::POST_TYPE ); } /** * @return callable */ public static function enforceReviewPostTypeIfSet() { return Logic::ifElse( Obj::prop( self::POST_TYPE ), Obj::renameProp( self::POST_TYPE, 'post_type' ), Fns::identity() ) ; } } ATE/Review/NextTranslationLink.php 0000755 00000010637 14720342453 0013120 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Element\API\Post; use WPML\Element\API\PostTranslations; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\TM\API\Translators; use WPML_Translations_Queue; use function WPML\FP\invoke; use function WPML\FP\partial; use function WPML\FP\pipe; class NextTranslationLink { public static function get( $currentJob, $filterTargetLanguages ) { global $sitepress; $doNotFilterPreviewLang = Fns::always( false ); $addPreviewLangFilter = function () use ( $doNotFilterPreviewLang ) { add_filter( 'wpml_should_filter_preview_lang', $doNotFilterPreviewLang ); }; $removePreviewLangFilter = function () use ( $doNotFilterPreviewLang ) { remove_filter( 'wpml_should_filter_preview_lang', $doNotFilterPreviewLang ); }; $getTranslationPostId = Fns::memorize( self::getTranslationPostId() ); $switchToPostLang = function ( $job ) use ( $sitepress, $getTranslationPostId ) { Maybe::of( $job ) ->chain( $getTranslationPostId ) ->map( Post::getLang() ) ->map( [ $sitepress, 'switch_lang' ] ); }; $restoreLang = function () use ( $sitepress ) { $sitepress->switch_lang( null ); }; $getLink = Fns::converge( Fns::liftA2( PreviewLink::getWithLanguagesParam( $filterTargetLanguages ) ), [ $getTranslationPostId, Maybe::safe( invoke( 'get_translate_job_id' ) ) ] ); return Maybe::of( $currentJob ) ->map( self::getNextJob( $filterTargetLanguages ) ) ->map( Fns::tap( $switchToPostLang ) ) ->map( Fns::tap( $addPreviewLangFilter ) ) ->chain( $getLink ) ->map( Fns::tap( $removePreviewLangFilter ) ) ->map( Fns::tap( $restoreLang ) ) ->getOrElse( null ); } private static function getTranslationPostId() { return function ( $nextJob ) { return Maybe::of( $nextJob ) ->map( pipe( invoke( 'get_original_element_id' ), PostTranslations::get() ) ) ->map( Obj::prop( $nextJob->get_target_language() ) ) ->map( Obj::prop( 'element_id' ) ); }; } /** * @return \Closure :: \stdClass -> \WPML_TM_Post_Job_Entity */ private static function getNextJob( $filterTargetLanguages ) { return function ( $currentJob ) use ( $filterTargetLanguages ) { $getJob = function ( $sourceLanguage, $targetLanguages ) use ( $currentJob ) { $excludeCurrentJob = pipe( invoke( 'get_translate_job_id' ), Relation::equals( (int) $currentJob->job_id ), Logic::not() ); $postJobsEntitiesOnly = function ( $nextJob ) { return $nextJob instanceof \WPML_TM_Post_Job_Entity; }; $samePostTypes = function ( $nextJob ) use ( $currentJob ) { $currentJobPostType = \get_post_type( $currentJob->original_doc_id ); $nextJobPostType = \get_post_type( $nextJob->get_original_element_id() ); return $currentJobPostType === $nextJobPostType; }; $nextJob = \wpml_collect(wpml_tm_get_jobs_repository() ->get( self::buildSearchParams( $sourceLanguage, $targetLanguages ) ) ) ->filter( $postJobsEntitiesOnly ) ->filter( $samePostTypes ) ->first( $excludeCurrentJob ); if ( ! $nextJob ) { $nextJob = \wpml_collect( wpml_tm_get_jobs_repository() ->get( self::buildSearchParams( $sourceLanguage, $targetLanguages ) ) ) ->filter( $postJobsEntitiesOnly ) ->first( $excludeCurrentJob ); } return $nextJob; }; $languagePairs = \wpml_collect( Obj::propOr( [], 'language_pairs', Translators::getCurrent() ) ); $filterTargetLanguages = function ( $targetLanguages, $sourceLanguage ) use ( $filterTargetLanguages ) { return [ 'source' => $sourceLanguage, 'targets' => is_array( $filterTargetLanguages ) ? $filterTargetLanguages : $targetLanguages, ]; }; $filterJobByPairOfLanguages = function ( $job, $pairOfLanguages ) use ( $getJob ) { return $job ?: $getJob( Obj::prop( 'source', $pairOfLanguages ), Obj::prop( 'targets', $pairOfLanguages ) ); }; return $languagePairs ->map($filterTargetLanguages) ->reduce($filterJobByPairOfLanguages); }; } /** * @param string $sourceLang * @param string[] $targetLanguages * * @return \WPML_TM_Jobs_Search_Params */ private static function buildSearchParams( $sourceLang, array $targetLanguages ) { return ( new \WPML_TM_Jobs_Search_Params() ) ->set_custom_where_conditions( [ 'translations.element_type NOT LIKE "package_%"' ] ) ->set_needs_review() ->set_source_language( $sourceLang ) ->set_target_language( $targetLanguages ); } } ATE/Review/PreviewLink.php 0000755 00000010462 14720342453 0011400 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\API\Sanitize; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Str; use WPML\TM\API\Jobs; use function WPML\FP\curryN; /** * Class PreviewLink * * @phpstan-type curried "__CURRIED_PLACEHOLDER__" * * @package WPML\TM\ATE\Review * * @method static callable|string get( ...$translationPostId, ...$jobId ) : Curried:: int->int->string * @method static callable|string getWithLanguagesParam( ...$languages, ...$translationPostId, ...$jobId ) : Curried:: int->int->string * @method static callable|string getByJob( ...$job ) : Curried:: \stdClass->string */ class PreviewLink { use Macroable; public static function init() { self::macro( 'getWithLanguagesParam', curryN( 3, function ( $languages, $translationPostId, $jobId ) { $returnUrl = Sanitize::string( Obj::propOr( Obj::prop( 'REQUEST_URI', $_SERVER ), 'returnUrl', $_GET ) ); $url = self::getWithSpecifiedReturnUrl( (string) $returnUrl, $translationPostId, $jobId ); if ( $languages ) { $url = \add_query_arg(['targetLanguages' => urlencode( join( ',', $languages ) ),], $url); } return $url; } ) ); self::macro( 'get', curryN( 2, function ( $translationPostId, $jobId ) { $returnUrl = Sanitize::string( Obj::propOr( Obj::prop( 'REQUEST_URI', $_SERVER ), 'returnUrl', $_GET ) ); return self::getWithSpecifiedReturnUrl( (string) $returnUrl, $translationPostId, $jobId ); } ) ); self::macro( 'getByJob', curryN( 1, Fns::converge( self::get(), [ Jobs::getTranslatedPostId(), Obj::prop( 'job_id' ), ] ) ) ); } /** * @param string $returnUrl * @param string|int $translationPostId * @param string|int $jobId * * @return string * * @phpstan-template V1 of string|curried * @phpstan-template V2 of string|int|curried * @phpstan-template V3 of object|int|curried * @phpstan-template P1 of string * @phpstan-template P2 of string|int * @phpstan-template P3 of string|int * @phpstan-template R of string * * @phpstan-param ?V1 $returnUrl * @phpstan-param ?V2 $translationPostId * @phpstan-param ?V3 $jobId * * @phpstan-return ($a is P1 * ? ($b is P2 * ? ($c is P3 * ? R * : callable(P3=):R) * : ($c is P3 * ? callable(P2=):R * : callable(P2=,P3=):R) * ) * : ($b is P2 * ? ($c is P3 * ? callable(P1=):R * : callable(P1=,P3=):R) * : ($c is P3 * ? callable(P1=,P2=):R * : callable(P1=,P2=,P3=):R) * ) * ) */ public static function getWithSpecifiedReturnUrl( $returnUrl = null, $translationPostId = null, $jobId = null ) { $callback = function ( $returnUrl, $translationPostId, $jobId ) { $returnUrl = (string) $returnUrl; $translationPostId = (int) $translationPostId; $jobId = (int) $jobId; /** * Returns TRUE if post_type of post is among public post type and FALSE otherwise. * * @param $postId * * @return bool */ $isPublicPostType = function ( $postId ) { $publicPostTypes = get_post_types( [ 'public' => true ] ); $postType = get_post_type( $postId ); return in_array( $postType, $publicPostTypes, true ); }; $args = [ 'preview_id' => $translationPostId, 'preview_nonce' => \wp_create_nonce( self::getNonceName( $translationPostId ) ), 'preview' => true, 'jobId' => $jobId > 0 ? $jobId : '', 'returnUrl' => rawurlencode( $returnUrl ), ]; /** * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-4273 * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1366/Translate-Everything-Incorrect-template-when-reviewing-a-translated-page */ if ( !$isPublicPostType( $translationPostId ) ) { // Add 'p' URL parameter only if post type isn't public $args['p'] = $translationPostId; } return \add_query_arg( NonPublicCPTPreview::addArgs( $args ), \get_permalink( $translationPostId ) ); }; return call_user_func_array( curryN( 3, $callback ), func_get_args() ); } /** * @template A as string|int|curried * * @param A $translationPostId * * @return (A is curried ? callable : string) */ public static function getNonceName( $translationPostId = null ) { return Str::concat( 'post_preview_', $translationPostId ); } } PreviewLink::init(); ATE/Review/ReviewCompletedNotice.php 0000755 00000001077 14720342453 0013403 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\FP\Relation; class ReviewCompletedNotice implements \IWPML_Backend_Action { public function add_hooks() { if ( Relation::propEq( 'reviewCompleted', 'inWPML', $_GET ) ) { $text = esc_html__( "You've completed reviewing your selection. WPML will let you know when there's new content to review.", 'wpml-translation-management' ); wpml_get_admin_notices()->add_notice( \WPML_Notice::make( 'reviewCompleted', $text ) ->set_css_class_types( 'notice-info' ) ->set_flash() ); } } } ATE/Review/ReviewTranslation.php 0000755 00000025276 14720342453 0012632 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\API\Sanitize; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Relation; use WPML\FP\Str; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Post; use WPML\Element\API\Post as WPMLPost; use WPML\TM\API\Jobs; use WPML\TM\API\Translators; use WPML\Core\WP\App\Resources; use WPML\FP\Obj; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class ReviewTranslation implements \IWPML_Frontend_Action, \IWPML_Backend_Action { public function add_hooks() { if ( self::hasValidNonce() ) { Hooks::onFilter( 'query_vars' ) ->then( spreadArgs( NonPublicCPTPreview::allowReviewPostTypeQueryVar() ) ); Hooks::onFilter( 'request' ) ->then( spreadArgs( NonPublicCPTPreview::enforceReviewPostTypeIfSet() ) ); Hooks::onFilter( 'the_preview' ) ->then( Hooks::getArgs( [ 0 => 'post' ] ) ) ->then( $this->handleTranslationReview() ); if ( $this->isCurrentPageReview() ) { add_filter( 'init', function () { // This hook is only needed for the WP Autosaved revision preview. // For Translation Review it can cause problems overwritting the post by an autosaved draft. remove_filter( 'the_preview', '_set_preview' ); return true; }, PHP_INT_MAX ); } } Hooks::onFilter( 'user_has_cap', 10, 3 ) ->then( spreadArgs( function ( $userCaps, $requiredCaps, $args ) { /** @var array $userCaps */ /** @var array $requiredCaps */ /** @var array $args */ if ( Relation::propEq( 0, 'edit_post', $args ) ) { $translator = Translators::getCurrent(); if ( $translator->ID ) { $postId = $args[2]; $job = Jobs::getPostJob( $postId, Post::getType( $postId ), WPMLPost::getLang( $postId ) ); if ( ReviewStatus::doesJobNeedReview( $job ) && self::canEditLanguage( $translator, $job ) ) { return Lst::concat( $userCaps, Lst::zipObj( $requiredCaps, Lst::repeat( true, count( $requiredCaps ) ) ) ); } } return $userCaps; } return $userCaps; } ) ); Hooks::onFilter( 'wpml_tm_allowed_translators_for_job', 10, 2 ) ->then( spreadArgs( function ( $allowedTranslators, \WPML_Element_Translation_Job $job ) { $job = $job->to_array(); $translator = Translators::getCurrent(); if ( ReviewStatus::doesJobNeedReview( $job ) && self::canEditLanguage( $translator, $job ) ) { return array_merge( $allowedTranslators, [ $translator->ID ] ); } return $allowedTranslators; } ) ); if ( $this->isCurrentPageReviewPostTypeTemplate() ) { Hooks::onFilter( 'pre_render_block', 10, 2 )->then( spreadArgs( [ $this, 'onPreRenderBlock' ] ) ); } } private static function canEditLanguage( $translator, $job ) { if ( ! $job ) { return false; } return Lst::includes( Obj::prop('language_code', $job), Obj::pathOr( [], [ 'language_pairs', Obj::prop('source_language_code', $job) ], $translator ) ); } /** * This will ensure to block the standard preview * for non-public CPTs. * * @return bool */ private static function hasValidNonce() { $get = Obj::prop( Fns::__, $_GET ); return (bool) \wp_verify_nonce( $get( 'preview_nonce' ), PreviewLink::getNonceName( (int) $get( 'preview_id' ) ) ); } /** * @param int $jobId * * @return callable */ public function handleTranslationReview() { return function ( $data ) { $post = Obj::prop( 'post', $data ); $jobId = filter_input( INPUT_GET, 'jobId', FILTER_SANITIZE_NUMBER_INT ); $filterTargetLanguages = Sanitize::stringProp('targetLanguages', $_GET) ? Str::split( ',', Sanitize::stringProp('targetLanguages', $_GET) ) : null; if ( $jobId ) { /** * This hooks is fired as soon as a translation review is about to be displayed. * * @since 4.5.0 * * @param int $jobId The job Id. * @param object|\WP_Post $post The job's related object to be reviewed. */ do_action( 'wpml_tm_handle_translation_review', $jobId, $post ); Hooks::onFilter( 'wp_redirect' ) ->then( [ __CLASS__, 'failGracefullyOnPreviewRedirection' ] ); Hooks::onAction( 'template_redirect', PHP_INT_MAX ) ->then( function () { Hooks::onAction( 'wp_footer' ) ->then( [ __CLASS__, 'printReviewToolbarAnchor' ] ); } ); show_admin_bar( false ); $enqueue = Resources::enqueueApp( 'translationReview' ); $enqueue( $this->getData( $jobId, $post, $filterTargetLanguages ) ); } return $post; }; } public static function printReviewToolbarAnchor() { echo ' <script type="text/javascript" > var ajaxurl = "' . \admin_url( 'admin-ajax.php', 'relative' ) . '" </script> <div id="wpml_translation_review"></div> '; } /** * @return null This will stop the redirection. */ public static function failGracefullyOnPreviewRedirection() { do_action( 'wp_head' ); self::printReviewToolbarAnchor(); echo ' <div class="wpml-review__modal-mask wpml-review__modal-mask-transparent"> <div class="wpml-review__modal-box wpml-review__modal-box-transparent wpml-review__modal-preview-not-available"> <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 0C10.8 0 0 10.8 0 24C0 37.2 10.8 48 24 48C37.2 48 48 37.2 48 24C48 10.8 37.2 0 24 0ZM24 43.5C13.2 43.5 4.5 34.8 4.5 24C4.5 13.2 13.2 4.5 24 4.5C34.8 4.5 43.5 13.2 43.5 24C43.5 34.8 34.8 43.5 24 43.5Z" fill="url(#paint0_linear)"/><path d="M24 10.2C22.5 10.2 21 11.4 21 13.2C21 15 22.2 16.2 24 16.2C25.8 16.2 27 15 27 13.2C27 11.4 25.5 10.2 24 10.2ZM24 20.4C22.8 20.4 21.9 21.3 21.9 22.5V35.7C21.9 36.9 22.8 37.8 24 37.8C25.2 37.8 26.1 36.9 26.1 35.7V22.5C26.1 21.3 25.2 20.4 24 20.4Z" fill="url(#paint1_linear)"/><defs><linearGradient id="paint0_linear" x1="38.6667" y1="6.66666" x2="8" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="#27AD95"/><stop offset="1" stop-color="#2782AD"/></linearGradient><linearGradient id="paint1_linear" x1="38.6667" y1="6.66666" x2="8" y2="48" gradientUnits="userSpaceOnUse"><stop stop-color="#27AD95"/><stop offset="1" stop-color="#2782AD"/></linearGradient></defs></svg> <h2>'. esc_html__( 'Preview is not available', 'wpml-translation-management' ) .'</h2> <p>'. sprintf(esc_html__( 'Click %sEdit Translation%s in the toolbar above to review your translation in the editor.', 'wpml-translation-management' ), '<strong>', '</strong>') .'</p> </div> </div> '; return null; } public function getData( $jobId, $post, $filterTargetLanguages ) { $job = Jobs::get( $jobId ); return [ 'name' => 'reviewTranslation', 'data' => [ 'jobEditUrl' => $this->getEditUrl( $jobId ), 'nextJobUrl' => NextTranslationLink::get( $job, $filterTargetLanguages ), 'jobId' => (int) $jobId, 'postId' => $post->ID, 'isPublished' => Relation::propEq( 'post_status', 'publish', $post ) ? 1 : 0, 'needsReview' => ReviewStatus::doesJobNeedReview( $job ), 'completedInATE' => $this->isCompletedInATE( $_GET ), 'isReturningFromATE' => (bool) Obj::prop( 'editFromReviewPage', $_GET ), 'clickedBackInATE' => (bool) Obj::prop( 'back', $_GET ), 'needsUpdate' => is_object( $job ) ? Relation::propEq( 'review_status', ReviewStatus::EDITING, $job ) : false, 'previousTranslation' => Sanitize::stringProp( 'previousTranslation', $_GET ), 'backUrl' => Obj::prop( 'returnUrl', $_GET ), 'endpoints' => [ 'accept' => AcceptTranslation::class, 'update' => UpdateTranslation::class ], ] ]; } /** * Returns completed status based on key 'complete_no_changes' in $params. * Returns NOT_COMPLETED if 'complete_no_changes' is not set. * * @param array $params * * @return string */ public function isCompletedInATE( $params ) { $completedInATE = pipe( Obj::prop( 'complete_no_changes' ), 'strval', Logic::cond( [ [ Relation::equals( '1' ), Fns::always( 'COMPLETED_WITHOUT_CHANGED' ) ], [ Relation::equals( '0' ), Fns::always( 'COMPLETED' ) ], [ Fns::always( true ), Fns::always( 'NOT_COMPLETED' ) ], ] ) ); return $completedInATE( $params ); } /** * @param int $jobId * * @return string */ private function getEditUrl( $jobId ) { return \add_query_arg( [ 'preview' => 1 ], Jobs::getEditUrl( $this->getReturnParamInEditUrl(), $jobId ) ); } /** * @return string */ private function getReturnParamInEditUrl() { /** * Those are GET params which ATE may send when we return to the review page. * We don't want to include them in subsequent edit link. */ $ateParams = [ 'back', 'complete_no_changes', 'ate_status', 'complete', 'in_progress', 'ate_original_id', 'ate_status', 'editFromReviewPage', ]; $returnParam = Jobs::getCurrentUrl(); $returnParam = \remove_query_arg( $ateParams, $returnParam ); /** * We need to add the `editFromReviewPage` param to the return URL to be able to detect that we are returning from ATE to the review page. * It is used to repeat the sync on "in-progress" status. */ $returnParam = \add_query_arg( [ 'editFromReviewPage' => 1 ], $returnParam ); return $returnParam; } /** * @return boolean */ public function isCurrentPageReviewPostTypeTemplate() { $queryVars = []; if ( isset( $_SERVER['QUERY_STRING'] ) ) { parse_str( $_SERVER['QUERY_STRING'], $queryVars ); } return Obj::has( 'wpmlReviewPostType', $queryVars ) && 'wp_template' === $queryVars['wpmlReviewPostType']; } /** * @return boolean */ public function isCurrentPageReview() { $queryVars = []; if ( isset( $_SERVER['QUERY_STRING'] ) ) { parse_str( $_SERVER['QUERY_STRING'], $queryVars ); } $jobId = filter_input( INPUT_GET, 'jobId', FILTER_SANITIZE_NUMBER_INT ); return !! $jobId || Obj::has( 'wpmlReviewPostType', $queryVars ); } /** * This filter is called from WP core /wp-includes/blocks.php right before block is rendered. * If anything other than null is returned from this filter that value is used as final block rendered value without calling actual block render function. * * @param string|null $preRenderedContent Pre-rendered context for the block. * @param array $blockParams Block params being rendered. * * @return string|null $context */ public function onPreRenderBlock( $preRenderedContent, $blockParams ) { // Fixes error 'postId is not defined' in WP core when context vars are removed for posts with 'wp_template' type. if ( is_array( $blockParams ) && 'core/comments' === $blockParams['blockName'] && $this->isCurrentPageReviewPostTypeTemplate() ) { return ''; } } } ATE/Review/AcceptTranslation.php 0000755 00000001634 14720342453 0012560 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\LIB\WP\Post; use WPML\TM\API\Jobs; use function WPML\FP\partial; use function WPML\FP\pipe; class AcceptTranslation implements IHandler { public function run( Collection $data ) { $postId = $data->get( 'postId' ); $jobId = $data->get( 'jobId' ); $canEdit = partial( 'current_user_can', 'edit_post' ); $completeJob = Fns::tap( pipe( Fns::always( $jobId ), Fns::tap( Jobs::setStatus( Fns::__, ICL_TM_COMPLETE ) ), Fns::tap( Jobs::setReviewStatus( Fns::__, ReviewStatus::ACCEPTED ) ) ) ); return Either::of( $postId ) ->filter( $canEdit ) ->map( Post::setStatusWithoutFilters( Fns::__, 'publish' ) ) ->map( $completeJob ) ->bimap( Fns::always( $jobId ), Fns::always( $jobId ) ); } } ATE/Review/ReviewStatus.php 0000755 00000002010 14720342453 0011574 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use function WPML\FP\curryN; use function WPML\FP\pipe; /** * Class ReviewStatus * @package WPML\TM\ATE\Review * * @method static callable|bool doesJobNeedReview( ...$job ) - Curried :: \stdClass->bool */ class ReviewStatus { use Macroable; const NEEDS_REVIEW = 'NEEDS_REVIEW'; const EDITING = 'EDITING'; const ACCEPTED = 'ACCEPTED'; public static function init() { self::macro( 'doesJobNeedReview', curryN( 1, Logic::ifElse( Fns::identity(), pipe( Obj::prop( 'review_status' ), self::needsReview() ), Fns::always(false) ) )); } /** * @template A as string|curried * @param A $reviewStatus * * @return (A is curried ? callable : bool) */ public static function needsReview( $reviewStatus = null ) { return Lst::includes( $reviewStatus ?: Fns::__, [ self::NEEDS_REVIEW, self::EDITING, ] ); } } ReviewStatus::init(); ATE/Review/ApproveTranslations.php 0000755 00000001722 14720342453 0013156 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\TM\API\Jobs; use function WPML\Container\make; class ApproveTranslations implements IHandler { public function run( Collection $data ) { $jobIds = $data->get( 'jobsIds' ); return wpml_collect( $jobIds ) ->map( Jobs::get() ) ->filter( ReviewStatus::doesJobNeedReview() ) ->map( Obj::addProp( 'translated_id', Jobs::getTranslatedPostId() ) ) ->map( Obj::props( [ 'job_id', 'translated_id' ] ) ) ->map( Lst::zipObj( [ 'jobId', 'postId' ] ) ) ->map( 'wpml_collect' ) ->map( [ make( AcceptTranslation::class ), 'run' ] ) ->map( function ( Either $result ) { $isJobApproved = Fns::isRight( $result ); $jobId = $result->coalesce( Fns::identity(), Fns::identity() )->getOrElse( 0 ); return [ 'jobId' => $jobId, 'status' => $isJobApproved ]; } ); } } ATE/Review/ApplyJob.php 0000755 00000005566 14720342453 0010672 0 ustar 00 <?php namespace WPML\TM\ATE\Review; use WPML\Element\API\Post as WPMLPost; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Post; use WPML\Setup\Option; use WPML\TM\API\Jobs; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class ApplyJob implements \IWPML_Backend_Action, \IWPML_REST_Action, \IWPML_AJAX_Action { /** @var string[] */ private static $excluded_from_review = [ 'st-batch', 'package' ]; public function add_hooks() { if ( \WPML_TM_ATE_Status::is_enabled_and_activated() && Option::shouldBeReviewed() ) { self::addJobStatusHook(); self::addTranslationCompleteHook(); self::addTranslationPreSaveHook(); } } private static function addJobStatusHook() { $applyReviewStatus = function ( $status, $job ) { if ( self::shouldBeReviewed( $job ) && $status === ICL_TM_COMPLETE ) { Jobs::setReviewStatus( (int) $job->job_id, ReviewStatus::NEEDS_REVIEW ); } return $status; }; Hooks::onFilter( 'wpml_tm_applied_job_status', 10, 2 ) ->then( spreadArgs( $applyReviewStatus ) ); } private static function addTranslationCompleteHook() { $isHoldToReviewMode = Fns::always( Option::getReviewMode() === 'before-publish' ); $shouldTranslationBeReviewed = function ( $translatedPostId ) { $job = Jobs::getPostJob( $translatedPostId, Post::getType( $translatedPostId ), WPMLPost::getLang( $translatedPostId ) ); return $job && self::shouldBeReviewed( $job ); }; $isPostNewlyCreated = Fns::converge( Relation::equals(), [ Obj::prop( 'post_date' ), Obj::prop( 'post_modified' ) ] ); /** @var callable $isNotNull */ $isNotNull = Logic::isNotNull(); $setPostStatus = pipe( Maybe::of(), Fns::filter( $isHoldToReviewMode ), Fns::filter( $shouldTranslationBeReviewed ), Fns::map( Post::get() ), Fns::filter( $isNotNull ), Fns::filter( $isPostNewlyCreated ), Fns::map( Obj::prop( 'ID' ) ), Fns::map( Post::setStatus( Fns::__, 'draft' ) ) ); Hooks::onAction( 'wpml_pro_translation_completed' ) ->then( spreadArgs( $setPostStatus ) ); } private static function addTranslationPreSaveHook() { $keepDraftPostsDraftIfNeedsReview = function ( $postArr, $job ) { if ( self::shouldBeReviewed( $job ) && isset( $postArr['ID'] ) && get_post_status( $postArr['ID'] ) === 'draft' ) { $postArr['post_status'] = 'draft'; } return $postArr; }; Hooks::onFilter( 'wpml_pre_save_pro_translation', 10, 2 ) ->then( spreadArgs( $keepDraftPostsDraftIfNeedsReview ) ); } /** * @param $job * * @return bool */ private static function shouldBeReviewed( $job ) { return ! Lst::includes( $job->element_type_prefix, self::$excluded_from_review ) && $job->automatic && (int) $job->original_doc_id !== (int) get_option( 'page_for_posts' ); } } ATE/class-wpml-tm-ate-job.php 0000755 00000000345 14720342453 0011716 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Job { const ATE_JOB_CREATED = 0; const ATE_JOB_IN_PROGRESS = 1; const ATE_JOB_TRANSLATED = 6; const ATE_JOB_DELIVERING = 7; const ATE_JOB_DELIVERED = 8; } ATE/Retranslation/JobsCollector/ATEResponse.php 0000755 00000002205 14720342453 0015415 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation\JobsCollector; class ATEResponse { /** * Job ids in ATE that have to be re-translated. * * @var int[] */ private $jobIds; /** * @var int */ private $currentPage; /** * @var int */ private $totalPages; /** * @var bool */ private $retranslationFinished; /** * @param bool $retranslationFinished * @param int[] $jobIds * @param int $currentPage * @param int $totalPages */ public function __construct( bool $retranslationFinished, array $jobIds, int $currentPage, int $totalPages ) { $this->retranslationFinished = $retranslationFinished; $this->jobIds = $jobIds; $this->currentPage = $currentPage; $this->totalPages = $totalPages; } /** * @return int[] */ public function getJobIds() { return $this->jobIds; } /** * @return int */ public function getCurrentPage() { return $this->currentPage; } /** * @return int */ public function getTotalPages() { return $this->totalPages; } /** * @return bool */ public function isRetranslationFinished() { return $this->retranslationFinished; } } ATE/Retranslation/SinglePageBatchHandler.php 0000755 00000003702 14720342453 0015002 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation; class SinglePageBatchHandler { const NOT_FINISHED_IN_ATE = 'retranslations-no-finished-in-ate'; const FINISHED_IN_WPML = 'retranslations-finished-in-wpml'; const GO_TO_NEXT_PAGE = 'retranslate-next-page'; /** * @var JobsCollector */ private $jobsCollector; /** * @var RetranslationPreparer */ private $retranslationPreparer; public function __construct( JobsCollector $jobsCollector, RetranslationPreparer $retranslationPreparer ) { $this->jobsCollector = $jobsCollector; $this->retranslationPreparer = $retranslationPreparer; } /** * It tries to handle the Jobs and returns result array with following keys : * * "state" Defines what's the state of the response that we received from ATE, it can be * * NOT_FINISHED_IN_ATE : When ATE didn't finish retranslations yet. * * FINISHED_IN_WPML : When retranslations finished on both ATE and WPML side (no more page to retranslate). * * GO_TO_NEXT_PAGE : When ATE finished retranslations and WPML is trying to finish them page by page. * * "nextPage" When combined with the `state` it defines the value of the next page that should be returned in the AJAX response, it can be * * 0 : means no more pages to retranslate or ATE didn't finish retranslations yet. * * $nextPage : the number of next page that WPML needs to handle * * @param int $pageNumber * * @return array * */ public function handle( int $pageNumber = 1 ): array { $jobsBatch = $this->jobsCollector->get( $pageNumber ); if ( ! $jobsBatch->isRetranslationFinished() ) { return [ 'state' => self::NOT_FINISHED_IN_ATE, 'nextPage' => 0 ]; } if ( $jobsBatch->getJobIds() ) { $this->retranslationPreparer->delegate( $jobsBatch->getJobIds() ); } return $pageNumber >= $jobsBatch->getTotalPages() ? [ 'state' => self::FINISHED_IN_WPML, 'nextPage' => 0 ] : [ 'state' => self::GO_TO_NEXT_PAGE, 'nextPage' => $pageNumber + 1 ]; } } ATE/Retranslation/Endpoint.php 0000755 00000003134 14720342453 0012303 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\TM\ATE\SyncLock; class Endpoint implements IHandler { /** @var SinglePageBatchHandler */ private $singlePageBatchHandler; /** @var Scheduler */ private $scheduler; /** @var SyncLock */ private $syncLock; public function __construct( SinglePageBatchHandler $singlePageBatchHandler, Scheduler $scheduler, SyncLock $syncLock ) { $this->singlePageBatchHandler = $singlePageBatchHandler; $this->scheduler = $scheduler; $this->syncLock = $syncLock; } /** * @param Collection $data * * @return Right<{lockKey: bool|string, nextPage: int}>|Left<{lockKey: bool|string, nextPage: int}> it returns next page number or 0 if there are no more pages */ public function run( Collection $data ) { $lockKey = $this->syncLock->create( $data->get( 'lockKey' ) ); if ( ! $lockKey ) { return Either::left( [ 'lockKey' => false, 'nextPage' => 0 ] ); } $page = $data->get( 'page', 1 ); $singlePageBatchHandlerResult = $this->singlePageBatchHandler->handle( $page ); switch ( $singlePageBatchHandlerResult['state'] ) { case SinglePageBatchHandler::NOT_FINISHED_IN_ATE: $this->scheduler->scheduleNextRun(); $this->syncLock->release(); $lockKey = false; break; case SinglePageBatchHandler::FINISHED_IN_WPML: $this->scheduler->disable(); $this->syncLock->release(); $lockKey = false; break; } return Either::of( [ 'lockKey' => $lockKey, 'nextPage' => $singlePageBatchHandlerResult['nextPage'] ] ); } } ATE/Retranslation/Scheduler.php 0000755 00000001257 14720342453 0012445 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation; /** * The class is responsible for determining if the re-translation should be run and when. * It is used inside WPML\TM\ATE\Loader::add_hooks() to schedule the re-translation. */ class Scheduler { const LAST_CALL_OPTION = 'wpml_ate_retranslation_last_call'; const INTERVAL = 60 * 2; // 2 minutes public function shouldRun(): bool { $lastCall = get_option( self::LAST_CALL_OPTION ); return $lastCall ? ( time() - $lastCall ) > self::INTERVAL : $lastCall; } public function scheduleNextRun() { update_option( self::LAST_CALL_OPTION, time() ); } public function disable() { delete_option( self::LAST_CALL_OPTION ); } } ATE/Retranslation/JobsCollector.php 0000755 00000001461 14720342453 0013270 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation; use WPML\FP\Obj; use WPML\TM\ATE\Retranslation\JobsCollector\ATEResponse; /** * The class calls ATE endpoint to get list of the jobs that have to be re-translated. */ class JobsCollector { /** @var \WPML_TM_ATE_API */ private $ateAPI; public function __construct( \WPML_TM_ATE_API $ateAPI ) { $this->ateAPI = $ateAPI; } public function get( int $page = 1 ): ATEResponse { $result = $this->ateAPI->get_jobs_to_retranslation( $page ); return $result->map( function ( $result ) { return new ATEResponse( Obj::propOr( false, 'retranslation_finished', $result), Obj::propOr( [], 'job_ids', $result ), Obj::propOr( 0, 'page', $result ), Obj::propOr( 0, 'pages', $result ) ); } )->getOrElse( new ATEResponse( false, [], 0, 0 ) ); } } ATE/Retranslation/RetranslationPreparer.php 0000755 00000004545 14720342453 0015060 0 ustar 00 <?php namespace WPML\TM\ATE\Retranslation; use WPML\Collect\Support\Collection; use WPML\FP\Obj; class RetranslationPreparer { /** @var \wpdb */ private $wpdb; /** @var \WPML_TM_ATE_API $ateApi */ private $ateApi; /** * @param \wpdb $wpdb * @param \WPML_TM_ATE_API $ateApi */ public function __construct( \wpdb $wpdb, \WPML_TM_ATE_API $ateApi ) { $this->wpdb = $wpdb; $this->ateApi = $ateApi; } /** * It changes status of corresponding WPML jobs into "waiting to translator" ( means in-progress ), * which will trigger the ATE Sync flow for them. * * @param int[] $ateJobIds * * @return array{int, int} */ public function delegate( array $ateJobIds ): array { $rowset = \wpml_collect( $this->wpdb->get_results( $this->buildSelectQuery( $ateJobIds ) ) ); list( $jobsWhichShouldBeReFetched, $outdatedJobs ) = $rowset->partition( Obj::prop( 'is_the_most_recent_job' ) ); if ( count( $jobsWhichShouldBeReFetched ) ) { $this->turnJobsIntoInProgress( $jobsWhichShouldBeReFetched ); } if ( count( $outdatedJobs ) ) { $this->ateApi->confirm_received_job( $outdatedJobs->pluck( 'editor_job_id' )->toArray() ); } return [ count( $jobsWhichShouldBeReFetched ), count( $outdatedJobs ) ]; } private function buildSelectQuery( array $ateJobIds ): string { $isTheMostRecentJob = " SELECT IF ( MAX(icl_translate_job2.job_id) = icl_translate_job1.job_id , 1, 0 ) FROM {$this->wpdb->prefix}icl_translate_job icl_translate_job2 WHERE icl_translate_job1.rid = icl_translate_job2.rid "; $select = " SELECT tranlation_status.rid, icl_translate_job1.job_id, icl_translate_job1.editor_job_id, ({$isTheMostRecentJob}) as is_the_most_recent_job FROM {$this->wpdb->prefix}icl_translation_status tranlation_status INNER JOIN {$this->wpdb->prefix}icl_translate_job icl_translate_job1 ON icl_translate_job1.rid = tranlation_status.rid WHERE icl_translate_job1.editor_job_id IN (" . implode( ',', $ateJobIds ) . ") "; return $select; } private function turnJobsIntoInProgress( Collection $jobsWhichShouldBeReFetched ) { $query = " UPDATE {$this->wpdb->prefix}icl_translation_status SET `status` = %d WHERE rid IN (" . implode( ',', $jobsWhichShouldBeReFetched->pluck( 'rid' )->toArray() ) . ") "; $query = $this->wpdb->prepare( $query, ICL_TM_WAITING_FOR_TRANSLATOR ); $this->wpdb->query( $query ); } } ATE/class-wpml-tm-ams-ate-factories.php 0000755 00000001524 14720342453 0013701 0 ustar 00 <?php /** * Used for helping building other factories. * * @see Usage. * * @author OnTheGo Systems */ class WPML_TM_AMS_ATE_Factories { /** * It returns an cached instance of \WPML_TM_ATE_API. * * @return \WPML_TM_ATE_API */ public function get_ate_api() { return WPML\Container\make( WPML_TM_ATE_API::class ); } /** * It returns an cached instance of \WPML_TM_ATE_API. * * @return \WPML_TM_AMS_API */ public function get_ams_api() { return WPML\Container\make( WPML_TM_AMS_API::class ); } /** * If ATE is active, it returns true. * * @return bool */ public function is_ate_active() { if ( ! WPML_TM_ATE_Status::is_active() ) { try { $this->get_ams_api()->get_status(); return WPML_TM_ATE_Status::is_active(); } catch ( Exception $ex ) { return false; } } return true; } } ATE/Log/Hooks.php 0000755 00000001403 14720342453 0007477 0 ustar 00 <?php namespace WPML\TM\ATE\Log; class Hooks implements \IWPML_Backend_Action, \IWPML_DIC_Action { const SUBMENU_HANDLE = 'wpml-tm-ate-log'; /** @var ViewFactory $viewFactory */ private $viewFactory; public function __construct( ViewFactory $viewFactory ) { $this->viewFactory = $viewFactory; } public function add_hooks() { add_action( 'admin_menu', [ $this, 'addLogSubmenuPage' ] ); } public function addLogSubmenuPage() { add_submenu_page( WPML_PLUGIN_FOLDER . '/menu/support.php', __( 'Advanced Translation Editor Error Logs', 'wpml-translation-management' ), 'ATE logs', 'manage_options', self::SUBMENU_HANDLE, [ $this, 'renderPage' ] ); } public function renderPage() { $this->viewFactory->create()->renderPage(); } } ATE/Log/Entry.php 0000755 00000003415 14720342453 0007522 0 ustar 00 <?php namespace WPML\TM\ATE\Log; class Entry { /** * @var int $timestamp The log's creation timestamp. */ public $timestamp = 0; /** * @see EventsTypes * * @var int $eventType The event code that triggered the log. */ public $eventType = 0; /** * @var string $description The details of the log (e.g. exception message). */ public $description = ''; /** * @var int $wpmlJobId [Optional] The WPML Job ID (when applies). */ public $wpmlJobId = 0; /** * @var int $ateJobId [Optional] The ATE Job ID (when applies). */ public $ateJobId = 0; /** * @var array $extraData [Optional] Complementary serialized data (e.g. API request/response data). */ public $extraData = []; /** * @param array $item * * @return Entry */ public function __construct( array $item = null ) { if ( $item ) { $this->timestamp = (int) $item['timestamp']; $this->eventType = (int) ( isset( $item['eventType'] ) ? $item['eventType'] : $item['event'] ); $this->description = $item['description']; $this->wpmlJobId = (int) $item['wpmlJobId']; $this->ateJobId = (int) $item['ateJobId']; $this->extraData = (array) $item['extraData']; } } public static function createForType($eventType, $extraData) { $entry = new self(); $entry->eventType = $eventType; $entry->extraData = $extraData; return $entry; } public static function retryJob( $wpmlJobId, $extraData ) { $entry = self::createForType(EventsTypes::JOB_RETRY, $extraData); $entry->wpmlJobId = $wpmlJobId; return $entry; } /** * @return string */ public function getFormattedDate() { return date_i18n( 'Y/m/d g:i:s A', $this->timestamp ); } /** * @return string */ public function getExtraDataToString() { return json_encode( $this->extraData ); } } ATE/Log/ViewFactory.php 0000755 00000000305 14720342453 0010656 0 ustar 00 <?php namespace WPML\TM\ATE\Log; use function WPML\Container\make; class ViewFactory { public function create() { $logs = make( Storage::class )->getAll(); return new View( $logs ); } } ATE/Log/Storage.php 0000755 00000003764 14720342453 0010034 0 ustar 00 <?php namespace WPML\TM\ATE\Log; use WPML\Collect\Support\Collection; use WPML\WP\OptionManager; class Storage { const OPTION_GROUP = 'TM\ATE\Log'; const OPTION_NAME = 'logs'; const MAX_ENTRIES = 50; public static function add( Entry $entry, $avoidDuplication = false ) { $entry->timestamp = $entry->timestamp ?: time(); $entries = self::getAll(); if ( $avoidDuplication ) { $entries = $entries->reject( function( $iteratedEntry ) use ( $entry ) { return ( $iteratedEntry->wpmlJobId === $entry->wpmlJobId && $entry->ateJobId === $iteratedEntry->ateJobId && $entry->description === $iteratedEntry->description && $entry->eventType === $iteratedEntry->eventType ); } ); } $entries->prepend( $entry ); $newOptionValue = $entries->forPage( 1, self::MAX_ENTRIES ) ->map( function( Entry $entry ) { return (array) $entry; } ) ->toArray(); OptionManager::updateWithoutAutoLoad( self::OPTION_NAME, self::OPTION_GROUP, $newOptionValue ); } /** * @param Entry $entry */ public static function remove( Entry $entry ) { $entries = self::getAll(); $entries = $entries->reject( function( $iteratedEntry ) use ( $entry ) { return $iteratedEntry->timestamp === $entry->timestamp && $entry->ateJobId === $iteratedEntry->ateJobId; } ); $newOptionValue = $entries->forPage( 1, self::MAX_ENTRIES ) ->map( function( Entry $entry ) { return (array) $entry; } ) ->toArray(); OptionManager::updateWithoutAutoLoad( self::OPTION_NAME, self::OPTION_GROUP, $newOptionValue ); } /** * @return Collection Collection of Entry objects. */ public static function getAll() { return wpml_collect( OptionManager::getOr( [], self::OPTION_NAME, self::OPTION_GROUP ) ) ->map( function( array $item ) { return new Entry( $item ); } ); } public function getCount(): int { return count( OptionManager::getOr( [], self::OPTION_NAME, self::OPTION_GROUP ) ); } } ATE/Log/View.php 0000755 00000004645 14720342453 0007341 0 ustar 00 <?php namespace WPML\TM\ATE\Log; use WPML\Collect\Support\Collection; class View { /** @var Collection $logs */ private $logs; public function __construct( Collection $logs ) { $this->logs = $logs; } public function renderPage() { ?> <div class="wrap"> <h1><?php esc_html_e( 'Advanced Translation Editor Error Logs', 'wpml-translation-management' ); ?></h1> <br> <table class="wp-list-table widefat fixed striped posts"> <thead><?php $this->renderTableHeader(); ?></thead> <tbody id="the-list"> <?php if ( $this->logs->isEmpty() ) { $this->renderEmptyTable(); } else { $this->logs->each( [ $this, 'renderTableRow' ] ); } ?> </tbody> <tfoot><?php $this->renderTableHeader(); ?></tfoot> </table> </div> <?php } private function renderTableHeader() { ?> <tr> <th class="date"> <span><?php esc_html_e( 'Date', 'wpml-translation-management' ); ?></span> </th> <th class="event"> <span><?php esc_html_e( 'Event', 'wpml-translation-management' ); ?></span> </th> <th class="description"> <span><?php esc_html_e( 'Description', 'wpml-translation-management' ); ?></span> </th> <th class="wpml-job-id"> <span><?php esc_html_e( 'WPML Job ID', 'wpml-translation-management' ); ?></span> </th> <th class="ate-job-id"> <span><?php esc_html_e( 'ATE Job ID', 'wpml-translation-management' ); ?></span> </th> <th class="extra-data"> <span><?php esc_html_e( 'Extra data', 'wpml-translation-management' ); ?></span> </th> </tr> <?php } public function renderTableRow( Entry $entry ) { ?> <tr> <td class="date"> <?php echo esc_html( $entry->getFormattedDate() ); ?> </td> <td class="event"> <?php echo esc_html( EventsTypes::getLabel( $entry->eventType ) ); ?> </td> <td class="description"> <?php echo esc_html( $entry->description ); ?> </td> <td class="wpml-job-id"> <?php echo esc_html( (string) $entry->wpmlJobId ); ?> </td> <td class="ate-job-id"> <?php echo esc_html( (string) $entry->ateJobId ); ?> </td> <td class="extra-data"> <?php echo esc_html( $entry->getExtraDataToString() ); ?> </td> </tr> <?php } private function renderEmptyTable() { ?> <tr> <td colspan="6" class="title column-title has-row-actions column-primary"> <?php esc_html_e( 'No entries', 'wpml-translation-management' ); ?> </td> </tr> <?php } } ATE/Log/EventsTypes.php 0000755 00000001556 14720342453 0010716 0 ustar 00 <?php namespace WPML\TM\ATE\Log; class EventsTypes { /** Communication errors */ const SERVER_ATE = 1; const SERVER_AMS = 2; const SERVER_XLIFF = 3; /** Internal errors */ const JOB_DOWNLOAD = 10; /** Retry */ const JOB_RETRY = 20; const SITE_REGISTRATION_RETRY = 21; /** Sync */ const JOBS_SYNC = 30; public static function getLabel( $eventType ) { return wpml_collect( [ EventsTypes::SERVER_ATE => 'ATE Server Communication', EventsTypes::SERVER_AMS => 'AMS Server Communication', EventsTypes::SERVER_XLIFF => 'XLIFF Server Communication', EventsTypes::JOB_DOWNLOAD => 'Job Download', EventsTypes::JOB_RETRY => 'Job resent to ATE', EventsTypes::SITE_REGISTRATION_RETRY => 'Site registration request resent to ATE', EventsTypes::JOBS_SYNC => 'Jobs sync request sent to ATE failed', ] )->get( $eventType, '' ); } } ATE/TranslateEverything/TranslatableData/DataPreSetup.php 0000755 00000006615 14720342453 0017456 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\TranslatableData; use \wpdb; class DataPreSetup { const POSTS_CHUNK_SIZE = 100; const TERMS_CHUNK_SIZE = 10000; const KEY_POST_TYPES = 'post_types'; const KEY_TAXONOMIES = 'taxonomies'; /** @var wpdb $db */ private $db; /** @var Calculate $calculate */ private $calculate; public function listTranslatableData() { // Top items will be fetched first. return [ self::KEY_TAXONOMIES => [ 'category', 'post_tag', 'product_cat', 'product_tag', ], self::KEY_POST_TYPES => [ 'post', 'page', 'product', ], ]; } /** * @param wpdb $db * @param Calculate $calculate * * @return void */ public function __construct( wpdb $db, Calculate $calculate ) { $this->db = $db; $this->calculate = $calculate; } public function fetch( Stack $stack ) { switch ( $stack->type() ) { case self::KEY_POST_TYPES: return $this->posts( $stack ); case self::KEY_TAXONOMIES: return $this->terms( $stack ); } throw new \InvalidArgumentException( 'The stack type "' . $stack->type() . '" is not supported.' ); } /** * Calculates the words inside the posts of the given $type. * This fetches a maximum of self::CHUNK_SIZE posts. To * fetch all posts, multiple requests must be made by using the * $offset parameter. * * The found data is applied to the given $stack. * * @param Stack $stack The stack to apply the data to. * * @return Stack */ private function posts( Stack $stack ) { $posts = $this->db->get_results( $this->db->prepare( "SELECT ID, post_content, post_title, post_excerpt FROM {$this->db->posts} WHERE `post_status` = 'publish' AND `post_type` = '%s' ORDER BY ID LIMIT %d, %d", $stack->name(), $stack->count(), self::POSTS_CHUNK_SIZE ) ); return $this->fillStack( $stack, $posts, function( $post ) { return $post->post_title . $post->post_content . $post->post_excerpt; }, self::POSTS_CHUNK_SIZE ); } /** * Calculates the words of all terms in the given $taxonomy. * * The found data is applied to the given stack. * * @param Stack $stack The stack to apply the data to. * * @return Stack */ private function terms( Stack $stack ) { $terms = $this->db->get_results( $this->db->prepare( "SELECT name FROM {$this->db->terms} as t LEFT JOIN {$this->db->term_taxonomy} as tt ON t.term_id = tt.term_id WHERE tt.taxonomy = '%s' AND tt.count > 0 LIMIT %d, %d", $stack->name(), $stack->count(), self::TERMS_CHUNK_SIZE ) ); return $this->fillStack( $stack, $terms, function( $term ) { return $term->name; }, self::TERMS_CHUNK_SIZE ); } /** * @param Stack $stack * @param mixed $dataSet * @param callable $dataExtract * @param int $chunkSize * * @return Stack */ private function fillStack( Stack $stack, $dataSet, $dataExtract, $chunkSize ) { if ( ! is_array( $dataSet ) || count( $dataSet ) === 0 ) { // No data to add. Mark stack as completed. $stack->completed(); return $stack; } if ( count( $dataSet ) < $chunkSize ) { // No more data to fetch. $stack->completed(); } $stack->addCount( count( $dataSet ) ); foreach ( $dataSet as $data ) { $stack->addWords( $this->calculate->words( $dataExtract( $data ) ) ); } return $stack; } } ATE/TranslateEverything/TranslatableData/Stack.php 0000755 00000003625 14720342453 0016160 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\TranslatableData; class Stack { /** @var string $type */ private $type; /** @var string $name */ private $name; /** @var int $count */ private $count; /** @var int|float $words */ private $words = 0; /** @var bool $completed */ private $completed = false; /** @var array $labels */ private $labels; /** * @param string $type * @param string $name * @param int $count * @param int|float $words * @param array<singular: string, plural: string> $labels * * @return void * * @throws \InvalidArgumentException When $type or $name is empty. */ public function __construct( $type, $name, $count = 0, $words = 0, $labels = [] ) { if ( empty( $type ) || empty( $name ) ) { throw new \InvalidArgumentException( 'Stack "type" and "name" should not be empty.' ); } $this->type = $type; $this->name = $name; $this->count = $count; $this->words = $words; $this->labels = $labels; } /** @return string */ public function type() { return $this->type; } /** @return string */ public function name() { return $this->name; } /** @return int */ public function count() { return $this->count; } /** * @param int|float $words * * @return self */ public function addWords( $words ) { $this->words += $words; return $this; } /** * @param int $count * * @return self */ public function addCount( $count ) { $this->count += $count; return $this; } public function completed() { $this->completed = true; } public function toArray() { return [ 'type' => $this->type, 'name' => $this->name, 'labels' => $this->labels, 'count' => $this->count, 'words' => $this->words, 'completed' => $this->completed, ]; } } ATE/TranslateEverything/TranslatableData/Calculate.php 0000755 00000001257 14720342453 0017007 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\TranslatableData; class Calculate { const AVERAGE_CHARS_PER_WORD = 5; /** * @param string $content * * @return int */ public function chars( $content ) { $content = strlen( preg_replace( [ '/[^@\s]*@[^@\s]*\.[^@\s]*/', // Emails. '/[0-9\t\n\r\s]+/', // Spaces. ], '', wp_strip_all_tags( strip_shortcodes( htmlspecialchars_decode( $content ) ) ) ) ); return ! $content ? 0 : $content; } /** * @param string $content * * @return int|float */ public function words( $content ) { return $this->chars( $content ) / self::AVERAGE_CHARS_PER_WORD; } } ATE/TranslateEverything/TranslatableData/View.php 0000755 00000004074 14720342453 0016024 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\TranslatableData; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; use WPML\FP\Left; class View implements IHandler { const ACTION_LIST_TRANSLATABLES = 'list-translatables'; const ACTION_FETCH_DATA = 'fetch-data'; /** @var DataPreSetup $data_pre_setup */ private $data; /** * @param DataPreSetup $data * * @return void */ public function __construct( DataPreSetup $data ) { $this->data = $data; } public function run( Collection $data ) { $action = $data->get( 'action' ); switch ( $action ) { case self::ACTION_LIST_TRANSLATABLES: return Right::of( $this->data->listTranslatableData() ); case self::ACTION_FETCH_DATA: return $this->fetchData( $data ); } return $this->unexpectedError(); } private function fetchData( Collection $for ) { $fetchDataFor = $for->get( 'field' ); if ( ! $fetchDataFor ) { $this->unexpectedError(); } $fetchDataFor = array_merge( [ 'count' => 0, 'words' => 0, ], $fetchDataFor ); try { $labels = 0 === (int) $fetchDataFor['count'] ? $this->fetchLabelFor( $fetchDataFor['type'], $fetchDataFor['name'] ) : $fetchDataFor['labels']; $stack = new Stack( $fetchDataFor['type'], $fetchDataFor['name'], $fetchDataFor['count'], $fetchDataFor['words'], $labels ); $stack = $this->data->fetch( $stack ); return Right::of( $stack->toArray() ); } catch ( \Exception $e ) { $this->unexpectedError(); } } private function fetchLabelFor( $type, $name ) { if ( DataPreSetup::KEY_POST_TYPES !== $type ) { return []; } $postTypeObject = get_post_type_object( $name ); if ( ! is_object( $postTypeObject ) || ! property_exists( $postTypeObject, 'labels' ) ) { return []; } return [ 'singular' => $postTypeObject->labels->singular_name, 'plural' => $postTypeObject->labels->name, ]; } private function unexpectedError() { return Left::of( __( 'Server error. Please refresh and try again.', 'sitepress' ) ); } } ATE/TranslateEverything/Pause/UserAuthorisation.php 0000755 00000001175 14720342453 0016450 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\Pause; use WPML\LIB\WP\User; class UserAuthorisation { public function isAllowedToPauseAutomaticTranslation() { return $this->isTranslationManager(); } public function isAllowedToResumeAutomaticTranslation() { return $this->isTranslationManager(); } private function isTranslationManager() { if ( ! User::canManageTranslations() // Check also for manage_options as on WPML Setup the admin // has not the above capability. && ! User::canManageOptions() ) { // User is neither Translation Manager nor Administrator. return false; } return true; } } ATE/TranslateEverything/Pause/PauseAndResume.php 0000755 00000001522 14720342453 0015635 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\Pause; use WPML\Setup\Option; use WPML\TranslationMode\Endpoint\SetTranslateEverything; class PauseAndResume { /** @var SetTranslateEverything $set_translate_everything */ private $set_translate_everything; public function __construct( SetTranslateEverything $set_translate_everything ) { $this->set_translate_everything = $set_translate_everything; } public function pause() { Option::setIsPausedTranslateEverything( true ); $this->set_translate_everything->run( // Set mark that there is nothing further to translate now. wpml_collect( [ 'onlyNew' => true ] ) ); } public function resume( $translateExisting ) { Option::setIsPausedTranslateEverything( false ); $this->set_translate_everything->run( wpml_collect( [ 'onlyNew' => ! $translateExisting ] ) ); } } ATE/TranslateEverything/Pause/View.php 0000755 00000003342 14720342453 0013670 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything\Pause; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; use WPML\FP\Left; class View implements IHandler { const ACTION_PAUSE = 'pause'; const ACTION_RESUME = 'resume'; /** @var PauseAndResume $translate_everything */ private $translate_everything; /** @var UserAuthorisation $user */ private $user; public function __construct( PauseAndResume $translate_everything, UserAuthorisation $user ) { $this->translate_everything = $translate_everything; $this->user = $user; } public function run( Collection $data ) { $action = $data->get( 'action' ); switch ( $action ) { case self::ACTION_PAUSE: return $this->pauseAutomaticTranslation(); case self::ACTION_RESUME: $translateExisting = $data->get( 'translateExisting' ); return $this->resumeAutomaticTranslation( $translateExisting ); } return $this->unexpectedError(); } private function pauseAutomaticTranslation() { if ( ! $this->user->isAllowedToPauseAutomaticTranslation() ) { return Left::of( __( "You're not allowed to pause automatic translation.", 'sitepress' ) ); } $this->translate_everything->pause(); return Right::of( true ); } private function resumeAutomaticTranslation( $translateExisting ) { if ( ! $this->user->isAllowedToResumeAutomaticTranslation() ) { return Left::of( __( "You're not allowed to start automatic translation.", 'sitepress' ) ); } $this->translate_everything->resume( $translateExisting ); return Right::of( true ); } private function unexpectedError() { return Left::of( __( 'Server error. Please refresh and try again.', 'sitepress' ) ); } } ATE/TranslateEverything/UntranslatedPosts.php 0000755 00000004457 14720342453 0015406 0 ustar 00 <?php namespace WPML\TM\ATE\TranslateEverything; use WPML\Element\API\Languages; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; class UntranslatedPosts { /** * @param string[] $secondaryLanguages * @param string $postType e.g. 'post', 'page' * @param $queueSize * * @return array{0: int, 1: int} [ [element_id1, language_code1], [element_id1, language_code2], [element_id2, language_code3], ... ] */ public function get( array $secondaryLanguages, $postType, $queueSize ) { global $wpdb; if ( empty( $secondaryLanguages ) ) { // Without secondaryLanguages there won't be any posts, and // the following query will throw an error. return []; } $languagesPart = Lst::join( ' UNION ALL ', Fns::map( Str::replace( '__', Fns::__, "SELECT '__' AS code" ), $secondaryLanguages ) ); $acceptableStatuses = ICL_TM_NOT_TRANSLATED . ', ' . ICL_TM_ATE_CANCELLED; $sql = " SELECT original_element.element_id, languages.code FROM {$wpdb->prefix}icl_translations original_element INNER JOIN ( $languagesPart ) as languages LEFT JOIN {$wpdb->prefix}icl_translations translations ON translations.trid = original_element.trid AND translations.language_code = languages.code LEFT JOIN {$wpdb->prefix}icl_translation_status translation_status ON translation_status.translation_id = translations.translation_id INNER JOIN {$wpdb->posts} posts ON posts.ID = original_element.element_id LEFT JOIN {$wpdb->postmeta} postmeta ON postmeta.post_id = posts.ID AND postmeta.meta_key = %s WHERE original_element.element_type = %s AND original_element.source_language_code IS NULL AND original_element.language_code = %s AND ( translation_status.status IS NULL OR translation_status.status IN ({$acceptableStatuses}) OR translation_status.needs_update = 1) AND posts.post_status IN ( 'publish', 'inherit' ) AND ( postmeta.meta_value IS NULL OR postmeta.meta_value = 'no' ) ORDER BY original_element.element_id, languages.code LIMIT %d "; $result = $wpdb->get_results( $wpdb->prepare( $sql, \WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, 'post_' . $postType, Languages::getDefaultCode(), $queueSize ), ARRAY_N ); return Fns::map( Obj::evolve( [ 0 => Cast::toInt() ] ), $result ); } } ATE/JobRecord.php 0000755 00000001417 14720342453 0007551 0 ustar 00 <?php namespace WPML\TM\ATE; use stdClass; class JobRecord { /** @var int $wpmlJobId */ public $wpmlJobId; /** @var int $ateJobId */ public $ateJobId; /** * @todo: Remove this property. * * @var int $editTimestamp */ public $editTimestamp = 0; public function __construct( stdClass $dbRow = null ) { if ( $dbRow ) { $this->wpmlJobId = (int) $dbRow->job_id; $this->ateJobId = (int) $dbRow->editor_job_id; } } /** * @todo: Remove the "$editTimestamp" and "is_editing", not handled on WPML side anymore. * * The job is considered as being edited if * the timestamp is not greater than 1 day. * * @return bool */ public function isEditing() { $elapsedTime = time() - $this->editTimestamp; return $elapsedTime < DAY_IN_SECONDS; } } ATE/Loader.php 0000755 00000022241 14720342453 0007104 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\Element\API\Languages; use WPML\TM\API\ATE\CachedLanguageMappings; use WPML\API\Settings; use WPML\Core\BackgroundTask\Model\BackgroundTask; use WPML\Core\BackgroundTask\Repository\BackgroundTaskRepository; use WPML\DocPage; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\User; use WPML\Posts\UntranslatedCount; use WPML\Setup\Option; use WPML\TM\ATE\AutoTranslate\Endpoint\AutoTranslate; use WPML\TM\ATE\AutoTranslate\Endpoint\CancelJobs; use WPML\TM\ATE\AutoTranslate\Endpoint\CountJobsInProgress; use WPML\TM\ATE\AutoTranslate\Endpoint\EnableATE; use WPML\TM\ATE\AutoTranslate\Endpoint\GetATEJobsToSync; use WPML\TM\ATE\AutoTranslate\Endpoint\GetCredits; use WPML\TM\ATE\AutoTranslate\Endpoint\GetJobsCount; use WPML\TM\ATE\AutoTranslate\Endpoint\GetJobsInfo; use WPML\TM\ATE\AutoTranslate\Endpoint\GetStatus; use WPML\TM\ATE\AutoTranslate\Endpoint\RefreshJobsStatus; use WPML\TM\ATE\AutoTranslate\Endpoint\SyncLock; use WPML\TM\ATE\AutoTranslate\Endpoint\Languages as EndpointLanguages; use WPML\TM\ATE\Download\Queue; use WPML\TM\ATE\LanguageMapping\InvalidateCacheEndpoint; use WPML\TM\ATE\Retranslation\Endpoint as RetranslationEndpoint; use WPML\TM\ATE\Sync\Trigger; use WPML\TM\ATE\TranslateEverything\Pause\View as PauseTranslateEverything; use WPML\Core\WP\App\Resources; use WPML\UIPage; use WPML\TM\ATE\Retranslation\Scheduler; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\pipe; class Loader implements \IWPML_Backend_Action, \IWPML_DIC_Action { const JOB_ID_PLACEHOLDER = '###'; /** @var BackgroundTaskRepository */ private $backgroundTaskRepository; /** * @param BackgroundTaskRepository $backgroundTaskRepository */ public function __construct( BackgroundTaskRepository $backgroundTaskRepository ) { $this->backgroundTaskRepository = $backgroundTaskRepository; } public function add_hooks() { if ( wpml_is_ajax() ) { // Prevent loading this for ajax calls. // All tasks of this class are not relevant for ajax requests. Currently it's loaded by the root plugin.php // which do not separate between ajax and non-ajax calls and loads this whenever is_admin() is true. // Problem: ALL ajax calls return true for is_admin() - also on the frontend and for non logged-in users. // TODO: Remove once wpmltm-4351 is done. return; } if ( UIPage::isTMJobs( $_GET ) ) { return; } $displayBackgroundTasks = $this->backgroundTaskRepository->getCountRunnableTasks() > 0; $maybeLoadStatusBarAndATEConsole = Fns::tap( function ( $data ) use ( $displayBackgroundTasks ) { if ( \WPML_TM_ATE_Status::is_enabled_and_activated() || Settings::pathOr( false, [ 'translation-management', 'doc_translation_method' ] ) === ICL_TM_TMETHOD_ATE || $displayBackgroundTasks ) { StatusBar::add_hooks( $data['data']['automaticJobsInProgressTotal'] > 0, $data['data']['needsReviewCount'], $displayBackgroundTasks ); Hooks::onAction( 'in_admin_header' ) ->then( [ self::class, 'showAteConsoleContainer' ] ); } } ); Hooks::onAction( 'wp_loaded' ) ->then( [ self::class, 'getData' ] ) ->then( $maybeLoadStatusBarAndATEConsole ) ->then( Resources::enqueueApp( 'ate-jobs-sync' ) ) ->then( Fns::always( make( \WPML_TM_Scripts_Factory::class ) ) ) ->then( invoke( 'localize_script' )->with( 'wpml-ate-jobs-sync-ui' ) ); Hooks::onFilter( 'wpml_tm_get_wpml_auto_translate_container' ) ->then( [ self::class, 'getWpmlAutoTranslateContainer' ] ); } public static function getData() { $ateTab = admin_url( UIPage::getTMATE() ); $isAteActive = \WPML_TM_ATE_Status::is_enabled_and_activated(); $defaultLanguage = Languages::getDefaultCode(); $getLanguages = pipe( Languages::class . '::getActive', CachedLanguageMappings::withCanBeTranslatedAutomatically(), CachedLanguageMappings::withMapping(), Fns::map( Obj::over( Obj::lensProp( 'mapping' ), Obj::prop( 'targetCode' ) ) ), Fns::map( Obj::addProp( 'is_default', Relation::propEq( 'code', $defaultLanguage ) ) ), Obj::values() ); /** @var Jobs $jobs */ $jobs = make( Jobs::class ); /** @var Scheduler */ $scheduler = make( Scheduler::class ); $data = [ 'name' => 'ate_jobs_sync', 'data' => [ 'endpoints' => self::getEndpoints(), 'urls' => self::getUrls( $ateTab ), 'jobIdPlaceHolder' => self::JOB_ID_PLACEHOLDER, 'languages' => $isAteActive ? $getLanguages() : [], 'isTranslationManager' => User::canManageTranslations(), 'jobsToSyncCount' => $jobs->getCountOfInProgress(), 'automaticJobsInProgressTotal' => $jobs->getCountOfAutomaticInProgress(), 'needsReviewCount' => $jobs->getCountOfNeedsReview(), 'shouldTranslateEverything' => ! Option::isPausedTranslateEverything() && Option::shouldTranslateEverything() && ! TranslateEverything::isEverythingProcessed( true ), 'isPausedTranslateEverything' => Option::isPausedTranslateEverything() ? 1 : 0, 'isAutomaticTranslations' => Option::shouldTranslateEverything(), 'notEnoughCreditPopup' => self::getNotEnoughCreditPopup(), 'ateConsole' => self::getAteData(), 'isAteActive' => $isAteActive, 'editorMode' => Settings::pathOr( false, [ 'translation-management', 'doc_translation_method' ] ), 'shouldCheckForRetranslation' => $scheduler->shouldRun(), 'ateCallbacks' => [], // Should be used to add any needed ATE callbacks in JS side, refer to 'src/js/ate/retranslation/index.js' for example 'settings' => [ 'numberOfParallelDownloads' => defined('WPML_ATE_MAX_PARALLEL_DOWNLOADS') ? WPML_ATE_MAX_PARALLEL_DOWNLOADS : 2, ], ], ]; if ( UIPage::isTMDashboard( $_GET ) ) { $data['data']['anyJobsExist'] = $jobs->hasAny(); // any jobs, even including CTE jobs } return $data; } /** * @return string */ public static function getNotEnoughCreditPopup() { $isTranslationManager = User::canManageTranslations(); $content = $isTranslationManager ? __( "There is an issue with automatic translation that needs your attention.", 'wpml-translation-management' ) : __( " There is an issue with automatic translation that needs attention from a translation manager.", 'wpml-translation-management' ); $fix = __( 'Fix it to continue translating automatically', 'wpml-translation-management' ); $primaryButton = $isTranslationManager ? '<button class="wpml-antd-button wpml-antd-button-primary" onclick="CREDITS_ACTION">' . $fix . '</button>' : ''; $translate = __( 'Translate content myself', 'wpml-translation-management' ); $secondaryButton = UIPage::isTMDashboard( $_GET ) || ! $isTranslationManager ? '' : '<button class="wpml-antd-button wpml-antd-button-secondary" onclick="window.location.href=\'TRANSLATE_LINK\'">' . $translate . '</button>'; return '<div class="wpml-not-enough-credit-popup">' . '<p>' . $content . '</p>' . $primaryButton . $secondaryButton . '</div>'; } public static function showAteConsoleContainer() { echo '<div id="wpml-ate-console-container"></div>'; } public static function getWpmlAutoTranslateContainer() { return '<div id="wpml-auto-translate" style="display:none"> <div class="content"></div> <div class="connect"></div> </div>'; } private static function getAteData() { if ( User::canManageTranslations() ) { /** @var NoCreditPopup $noCreditPopup */ $noCreditPopup = make( NoCreditPopup::class ); return $noCreditPopup->getData(); } return false; } private static function getEndpoints() { return [ 'auto-translate' => AutoTranslate::class, 'translate-everything' => TranslateEverything::class, 'getCredits' => GetCredits::class, 'enableATE' => EnableATE::class, 'getATEJobsToSync' => GetATEJobsToSync::class, 'syncLock' => SyncLock::class, 'pauseTranslateEverything' => PauseTranslateEverything::class, 'untranslatedCount' => UntranslatedCount::class, 'getJobsCount' => GetJobsCount::class, 'getJobsInfo' => GetJobsInfo::class, 'languages' => EndpointLanguages::class, 'assignToTranslation' => RetranslationEndpoint::class, 'invalidateLangMappingCache' => InvalidateCacheEndpoint::class, ]; } private static function getUrls( $ateTab ) { return [ 'editor' => \WPML_TM_Translation_Status_Display::get_link_for_existing_job( self::JOB_ID_PLACEHOLDER ), 'ateams' => $ateTab, 'automaticSettings' => \admin_url( UIPage::getSettings() ), 'translateAutomaticallyDoc' => DocPage::getTranslateAutomatically(), 'ateConsole' => make( NoCreditPopup::class )->getUrl(), 'translationQueue' => \add_query_arg( [ 'status' => ICL_TM_NEEDS_REVIEW ], \admin_url( UIPage::getTranslationQueue() ) ), 'currentUrl' => \WPML\TM\API\Jobs::getCurrentUrl(), 'editLanguages' => add_query_arg( [ 'trop' => 1 ], UIPage::getLanguages() ), ]; } } ATE/models/class-wpml-tm-ate-models-language.php 0000755 00000000430 14720342453 0015466 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Models_Language { public $code; public $name; /** * @param string $code * @param string $name */ public function __construct( $code = null, $name = null ) { $this->code = $code; $this->name = $name; } } ATE/models/class-wpml-tm-ate-job-repository.php 0000755 00000004162 14720342453 0015417 0 ustar 00 <?php use function WPML\FP\invoke; use WPML\TM\ATE\Jobs; class WPML_TM_ATE_Job_Repository { /** @var WPML_TM_Jobs_Repository */ private $job_repository; /** @var Jobs */ private $ateJobs; public function __construct( WPML_TM_Jobs_Repository $job_repository, Jobs $ateJobs ) { $this->job_repository = $job_repository; $this->ateJobs = $ateJobs; } /** * If $onlyIds is true, it will return an array of ids instead of a collection of jobs. It is more optimized query which is used in the sync process to avoid loading all the jobs. * * @param bool $includeManualAndLongstandingJobs * * @return WPML_TM_Jobs_Collection|int[] */ public function get_jobs_to_sync( $includeManualAndLongstandingJobs = true, $onlyIds = false ) { if ( $onlyIds ) { return $this->ateJobs->getATEJobIdsToSync( $includeManualAndLongstandingJobs ); } $searchParams = $this->getSearchParamsPrototype(); $searchParams->set_status( [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ] ); $searchParams->set_exclude_manual( ! $includeManualAndLongstandingJobs ); $searchParams->set_exclude_longstanding( ! $includeManualAndLongstandingJobs ); return $this->job_repository ->get( $searchParams ) ->filter( invoke( 'is_ate_job' ) ); } /** * @param array $ateJobIds * * @return bool */ public function increment_ate_sync_count( array $ateJobIds ) { return $this->job_repository->increment_ate_sync_count( $ateJobIds ); } /** * @return WPML_TM_Jobs_Collection */ public function get_jobs_to_retry() { $searchParams = $this->getSearchParamsPrototype(); $searchParams->set_status( [ ICL_TM_ATE_NEEDS_RETRY ] ); return $this->job_repository ->get( $searchParams ) ->filter( invoke( 'is_ate_job' ) ); } /** * @return WPML_TM_Jobs_Search_Params */ private function getSearchParamsPrototype() { $searchParams = new WPML_TM_Jobs_Search_Params(); $searchParams->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_LOCAL ); $searchParams->set_job_types( [ WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE, WPML_TM_Job_Entity::STRING_BATCH, ] ); return $searchParams; } } ATE/models/class-wpml-tm-ate-models-job-create.php 0000755 00000002465 14720342453 0015730 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Models_Job_Create { /** @var int */ public $id; /** @var int */ public $deadline; /** @var WPML_TM_ATE_Models_Job_File */ public $file; /** @var bool */ public $notify_enabled; /** @var string */ public $notify_url; /** @var int */ public $source_id; /** @var string */ public $permalink; /** @var string */ public $site_identifier; /** @var WPML_TM_ATE_Models_Language */ public $source_language; /** @var WPML_TM_ATE_Models_Language */ public $target_language; /** @var string */ public $ate_ams_console_url; /** @var int */ public $existing_ate_id; /** @var int */ public $wpml_chars_count; /** @var bool */ public $apply_memory; /** * WPML_TM_ATE_Models_Job_Create constructor. * * @param array $args * * @throws \Auryn\InjectionException */ public function __construct( array $args = array() ) { foreach ( $args as $key => $value ) { $this->$key = $value; } if ( ! $this->file ) { $this->file = new WPML_TM_ATE_Models_Job_File(); } if ( ! $this->source_language ) { $this->source_language = new WPML_TM_ATE_Models_Language(); } if ( ! $this->target_language ) { $this->target_language = new WPML_TM_ATE_Models_Language(); } $this->ate_ams_console_url = wpml_tm_get_ams_ate_console_url(); } } ATE/models/class-wpml-tm-ate-models-job-file.php 0000755 00000000522 14720342453 0015374 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ATE_Models_Job_File { public $content; public $name; public $type; /** * WPML_TM_ATE_Models_Job_File constructor. * * @param array $args */ public function __construct( array $args = array() ) { foreach ( $args as $key => $value ) { $this->$key = $value; } } } ATE/models/class-wpml-tm-job-created.php 0000755 00000000643 14720342453 0014040 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Job_Created { public $job_id; public $rid; public $translation_service; public $translator_id; public $translation_package; public $batch_options; public $data; /** * WPML_TM_Job_Created constructor. * * @param array $args */ public function __construct( array $args ) { foreach ( $args as $key => $value ) { $this->$key = $value; } } } ATE/Retry/Trigger.php 0000755 00000001033 14720342453 0010402 0 ustar 00 <?php namespace WPML\TM\ATE\Retry; use WPML\WP\OptionManager; class Trigger { const RETRY_TIMEOUT = 10 * MINUTE_IN_SECONDS; const OPTION_GROUP = 'WPML\TM\ATE\Retry'; const RETRY_LAST = 'last'; /** * @return bool */ public function isRetryRequired() { $retrySync = OptionManager::getOr( 0, self::RETRY_LAST, self::OPTION_GROUP ); return ( time() - self::RETRY_TIMEOUT ) > $retrySync; } public function setLastRetry( $time ) { OptionManager::updateWithoutAutoLoad( self::RETRY_LAST, self::OPTION_GROUP, $time ); } } ATE/Retry/Result.php 0000755 00000000422 14720342453 0010256 0 ustar 00 <?php namespace WPML\TM\ATE\Retry; use WPML\Collect\Support\Collection; class Result { /** @var Collection */ public $jobsToProcess; /** @var array[wpmlJobId] */ public $processed = []; public function __construct() { $this->jobsToProcess = wpml_collect(); } } ATE/Retry/Process.php 0000755 00000004163 14720342453 0010424 0 ustar 00 <?php namespace WPML\TM\ATE\Retry; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Relation; use WPML\TM\API\Jobs; use WPML_TM_ATE_Job_Repository; use function WPML\FP\pipe; class Process { const JOBS_PROCESSED_PER_REQUEST = 10; /** @var WPML_TM_ATE_Job_Repository $ateRepository */ private $ateRepository; /** @var Trigger $trigger */ private $trigger; public function __construct( WPML_TM_ATE_Job_Repository $ateRepository, Trigger $trigger ) { $this->ateRepository = $ateRepository; $this->trigger = $trigger; } /** * @param array $jobsToProcess * * @return Result */ public function run( $jobsToProcess ) { $result = new Result(); if ( $jobsToProcess ) { $result = $this->retry( $result, wpml_collect( $jobsToProcess ) ); } else { $result = $this->runRetryInit( $result ); } if ( $result->jobsToProcess->isEmpty() && $this->trigger->isRetryRequired() ) { $this->trigger->setLastRetry( time() ); } return $result; } /** * @param Result $result * * @return Result */ private function runRetryInit( Result $result ) { $wpmlJobIds = $this->getWpmlJobIdsToRetry(); if ( $this->trigger->isRetryRequired() && ! $wpmlJobIds->isEmpty() ) { $result = $this->retry( $result, $wpmlJobIds ); } return $result; } /** * @param Result $result * @param Collection $jobs * * @return Result */ private function retry( Result $result, Collection $jobs ) { $jobsChunks = $jobs->chunk( self::JOBS_PROCESSED_PER_REQUEST ); $result->processed = $this->handleJobs( $jobsChunks->shift() ); $result->jobsToProcess = $jobsChunks->flatten( 1 ); return $result; } /** * @return Collection */ private function getWpmlJobIdsToRetry() { return wpml_collect( $this->ateRepository->get_jobs_to_retry()->map_to_property( 'translate_job_id' ) ); } /** * @param Collection $items * * @return array $items [[wpmlJobId, status, ateJobId], ...] */ private function handleJobs( Collection $items ) { do_action( 'wpml_added_translation_jobs', [ 'local' => $items->toArray() ], Jobs::SENT_RETRY ); return $items->toArray(); } } reset/class-wpml-tm-reset-options-filter.php 0000755 00000001347 14720342453 0015207 0 ustar 00 <?php /** * WPML_TM_Reset_Options_Filter class file * * @package WPML\TM */ /** * Class WPML_TM_Reset_Options_Filter */ class WPML_TM_Reset_Options_Filter implements IWPML_Action { /** * Add hooks */ public function add_hooks() { add_filter( 'wpml_reset_options', array( $this, 'reset_options' ) ); } /** * Add options to reset. * * @param array $options Options. * * @return array */ public function reset_options( array $options ) { $options[] = WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER; $options[] = WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_ADMIN; $options[] = WPML_TM_Wizard_Options::CURRENT_STEP; $options[] = WPML_TM_Wizard_Options::WHO_WILL_TRANSLATE_MODE; return $options; } } reset/class-wpml-tm-reset-options-filter-factory.php 0000755 00000000614 14720342453 0016650 0 ustar 00 <?php /** * WPML_TM_Reset_Options_Filter_Factory class file. * * @package WPML\TM */ /** * Class WPML_TM_Reset_Options_Filter_Factory */ class WPML_TM_Reset_Options_Filter_Factory implements IWPML_Backend_Action_Loader { /** * Create reset options filter. * * @return WPML_TM_Reset_Options_Filter */ public function create() { return new WPML_TM_Reset_Options_Filter(); } } wpml-wp/iwpml-wp-element-type.php 0000755 00000000253 14720342453 0013051 0 ustar 00 <?php interface IWPML_WP_Element_Type { /** * @param string $element_name * * @return mixed */ public function get_wp_element_type_object( $element_name ); } wpml-wp/class-wpml-wp-roles.php 0000755 00000007261 14720342453 0012525 0 ustar 00 <?php class WPML_WP_Roles { const ROLES_ADMINISTRATOR = 'administrator'; const ROLES_EDITOR = 'editor'; const ROLES_CONTRIBUTOR = 'contributor'; const ROLES_SUBSCRIBER = 'subscriber'; const EDITOR_LEVEL = 'level_7'; const CONTRIBUTOR_LEVEL = 'level_1'; const SUBSCRIBER_LEVEL = 'level_0'; /** * Returns an array of roles which meet the capability level set in \WPML_WP_Roles::EDITOR_LEVEL. * * @return array */ public static function get_editor_roles() { return self::get_roles_for_level( self::EDITOR_LEVEL, self::ROLES_EDITOR ); } /** * Returns an array of roles which meet the capability level set in \WPML_WP_Roles::CONTRIBUTOR_LEVEL. * * @return array */ public static function get_contributor_roles() { return self::get_roles_for_level( self::CONTRIBUTOR_LEVEL, self::ROLES_CONTRIBUTOR ); } /** * Returns an array of roles wich meet the capability level set in \WPML_WP_Roles::SUBSCRIBER_LEVEL. * * @return array */ public static function get_subscriber_roles() { return self::get_roles_for_level( self::SUBSCRIBER_LEVEL, self::ROLES_SUBSCRIBER ); } /** * @return array */ public static function get_roles_up_to_user_level( WP_User $user ) { return self::get_roles_with_max_level( self::get_user_max_level( $user ), self::ROLES_SUBSCRIBER ); } /** * @param WP_User $user * * @return int */ public static function get_user_max_level( WP_User $user ) { return self::get_highest_level( $user->get_role_caps() ); } public static function get_highest_level( array $capabilities ) { $capabilitiesWithLevel = function ( $has, $cap ) { return $has && strpos( $cap, 'level_' ) === 0; }; $levelToNumber = function ( $cap ) { return (int) substr( $cap, strlen( 'level_' ) ); }; return \wpml_collect( $capabilities ) ->filter( $capabilitiesWithLevel ) ->keys() ->map( $levelToNumber ) ->sort() ->last(); } /** * It returns a filtered array of roles. * * @param string $level The capability level that the role must meet. * @param null|string $default The role ID to use as a default. * * @return array */ private static function get_roles_for_level( $level, $default = null ) { return \wpml_collect( get_editable_roles() ) ->filter( function ( $role ) use ( $level ) { return isset( $role['capabilities'][ $level ] ) && $role['capabilities'][ $level ]; } ) ->map( self::create_build_role_entity( $level, $default ) ) ->values() ->toArray(); } private static function get_roles_with_max_level( $level, $default = null ) { $isRoleLowerThanLevel = function ( $role ) use ( $level ) { return self::get_highest_level( $role['capabilities'] ) <= $level; }; return \wpml_collect( get_editable_roles() ) ->filter( $isRoleLowerThanLevel ) ->map( self::create_build_role_entity( $level, $default ) ) ->values() ->toArray(); } private static function create_build_role_entity( $level, $default = null ) { $is_default = self::create_is_default( $level, $default ); return function ( $role, $id ) use ( $is_default ) { return [ 'id' => $id, 'name' => $role['name'], 'default' => $is_default( $id ), ]; }; } private static function create_is_default( $level, $default = null ) { /** * Filters the role ID to use as a default. * * @param string $default The role ID to use as a default. * @param string $level The capability level required for this role (@see \WPML_WP_Roles::get_roles_for_level). * * @since 2.8.0 */ $default = apply_filters( 'wpml_role_for_level_default', $default, $level ); return function ( $id ) use ( $default ) { return $default && ( $default === $id ); }; } } wpml-wp/class-wpml-wp-api.php 0000755 00000070374 14720342453 0012157 0 ustar 00 <?php use WPML\API\Sanitize; use WPML\Core\Twig_Environment; use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Loader_String; use WPML\Core\Twig_LoaderInterface; use function WPML\Container\make; class WPML_WP_API extends WPML_PHP_Functions { /** * @param string $file * @param string $filename * * @return false | string */ public function get_file_mime_type( $file, $filename ) { $mime_type = false; if ( file_exists( $file ) ) { $file_info = wp_check_filetype_and_ext( $file, $filename ); $mime_type = $file_info['type']; } return $mime_type; } /** * Wrapper for \get_option * * @param string $option * @param bool|false $default * * @return mixed */ public function get_option( $option, $default = false ) { return get_option( $option, $default ); } public function is_url( $value ) { $regex = '((https?|ftp)\:\/\/)?'; // SCHEME $regex .= '([a-z0-9+!*(),;?&=$_.-]+(\:[a-z0-9+!*(),;?&=$_.-]+)?@)?'; // User and Pass $regex .= '([a-z0-9-.]*)\.([a-z]{2,3})'; // Host or IP $regex .= '(\:[0-9]{2,5})?'; // Port $regex .= '(\/([a-z0-9+$_-]\.?)+)*\/?'; // Path $regex .= '(\?[a-z+&$_.-][a-z0-9;:@&%=+\/$_.-]*)?'; // GET Query $regex .= '(#[a-z_.-][a-z0-9+$_.-]*)?'; // Anchor return preg_match( "/^$regex$/", $value ); } public function get_transient( $transient ) { return get_transient( $transient ); } public function set_transient( $transient, $value, $expiration = 0 ) { set_transient( $transient, $value, $expiration ); } /** * @param string $option * @param mixed $value * @param string|bool $autoload * * @return bool False if value was not updated and true if value was updated. */ public function update_option( $option, $value, $autoload = null ) { return update_option( $option, $value, $autoload ); } /** * @param string|int|WP_Post $ID Optional. Post ID or post object. Default empty. * * @return false|string */ public function get_post_status( $ID = '' ) { $ID = is_string( $ID ) ? (int) $ID : $ID; return get_post_status( $ID ); } /** * Wrapper for \get_term_link * * @param WP_Term|int|string $term * @param string $taxonomy * * @return string|WP_Error */ public function get_term_link( $term, $taxonomy = '' ) { return get_term_link( $term, $taxonomy ); } /** * Wrapper for \get_term_by * * @param string $field * @param string|int $value * @param string $taxonomy * @param string $output * @param string $filter * * @return bool|WP_Term */ public function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) { return get_term_by( $field, $value, $taxonomy, $output, $filter ); } /** * Wrapper for \add_submenu_page * * @param string $parent_slug * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param array|string $function * * @return false|string */ public function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) { /** @phpstan-ignore-next-line WP doc issue. */ return add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); } /** * @param string $page_title * @param string $menu_title * @param string $capability * @param string $menu_slug * @param array|string $function * @param string $icon_url * @param null $position * * @return string */ public function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) { /** @phpstan-ignore-next-line WP doc issue. */ return add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); } /** * Wrapper for \get_post_type_archive_link * * @param string $post_type * * @return string */ public function get_post_type_archive_link( $post_type ) { return get_post_type_archive_link( $post_type ); } /** * Wrapper for \get_edit_post_link * * @param int $id * @param string $context * * @return null|string|void */ public function get_edit_post_link( $id = 0, $context = 'display' ) { return get_edit_post_link( $id, $context ); } /** * Wrapper for get_the_title * * @param int|WP_Post $post * * @return string */ public function get_the_title( $post ) { return get_the_title( $post ); } /** * Wrapper for \get_day_link * * @param int $year * @param int $month * @param int $day * * @return string */ public function get_day_link( $year, $month, $day ) { return get_day_link( $year, $month, $day ); } /** * Wrapper for \get_month_link * * @param int $year * @param int $month * * @return string */ public function get_month_link( $year, $month ) { return get_month_link( $year, $month ); } /** * Wrapper for \get_year_link * * @param int $year * * @return string */ public function get_year_link( $year ) { return get_year_link( $year ); } /** * Wrapper for \get_author_posts_url * * @param int $author_id * @param string $author_nicename * * @return string */ public function get_author_posts_url( $author_id, $author_nicename = '' ) { return get_author_posts_url( $author_id, $author_nicename ); } /** * Wrapper for \current_user_can * * @param string $capability * * @return bool */ public function current_user_can( $capability ) { return WPML\LIB\WP\User::currentUserCan( $capability ); } /** * @param int $user_id * @param string $key * @param bool $single * * @return mixed */ public function get_user_meta( $user_id, $key = '', $single = false ) { return get_user_meta( $user_id, $key, $single ); } /** * Wrapper for \get_post_type * * @param null|int|WP_Post $post * * @return false|string */ public function get_post_type( $post = null ) { return get_post_type( $post ); } public function is_archive() { return is_archive(); } public function is_front_page() { return is_front_page(); } public function is_home() { return is_home(); } /** * @param int|string|array $page Optional. Page ID, title, slug, or array of such. Default empty. * * @return bool */ public function is_page( $page = '' ) { return is_page( $page ); } public function is_paged() { return is_paged(); } /** * @param string $post * * @return int|string|array $post Optional. Post ID, title, slug, or array of such. Default empty. */ public function is_single( $post = '' ) { return is_single( $post ); } /** * @param string|array $post_types * * @return bool */ public function is_singular( $post_types = '' ) { return is_singular( $post_types ); } /** * @param int|WP_User $user * @param string $capability * * @return bool */ public function user_can( $user, $capability ) { return WPML\LIB\WP\User::userCan( $user, $capability ); } /** * Wrapper for add_filter * * @param string $tag * @param callable $function_to_add * @param int $priority * @param int $accepted_args * * @return bool|mixed|true|void */ public function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { return add_filter( $tag, $function_to_add, $priority, $accepted_args ); } /** * Wrapper for remove_filter * * @param string $tag * @param callable $function_to_remove * @param int $priority * * @return bool */ public function remove_filter( $tag, $function_to_remove, $priority = 10 ) { return remove_filter( $tag, $function_to_remove, $priority ); } /** * Wrapper for current_filter */ public function current_filter() { return current_filter(); } /** * @param null|string $tab * @param null|string $hash * * @return string */ public function get_tm_url( $tab = null, $hash = null ) { $tm_url = menu_page_url( $this->constant( 'WPML_TM_FOLDER' ) . '/menu/main.php', false ); $query_vars = array(); if ( $tab ) { $query_vars['sm'] = $tab; } $tm_url = add_query_arg( $query_vars, $tm_url ); if ( $hash ) { if ( strpos( $hash, '#' ) !== 0 ) { $hash = '#' . $hash; } $tm_url .= $hash; } return $tm_url; } /** * Wrapper for \is_admin() * * @return bool */ public function is_admin() { return is_admin(); } public function is_jobs_tab() { return $this->is_tm_page( 'jobs' ); } /** * @param string|null $tab * @param string|null $page_type * * @return bool */ public function is_tm_page( $tab = null, $page_type = 'management' ) { if ( 'settings' === $page_type ) { $page_suffix = '/menu/settings'; $default_tab = 'mcsetup'; } else { $page_suffix = '/menu/main.php'; $default_tab = 'dashboard'; } $result = is_admin() && isset( $_GET['page'] ) && $_GET['page'] == $this->constant( 'WPML_TM_FOLDER' ) . $page_suffix; if ( $tab ) { if ( $tab == $default_tab && ! isset( $_GET['sm'] ) ) { $result = $result && true; } else { $result = $result && isset( $_GET['sm'] ) && $_GET['sm'] == $tab; } } return $result; } public function is_translation_queue_page() { return is_admin() && isset( $_GET['page'] ) && $this->constant( 'WPML_TM_FOLDER' ) . '/menu/translations-queue.php' == $_GET['page']; } public function is_string_translation_page() { return is_admin() && isset( $_GET['page'] ) && $this->constant( 'WPML_ST_FOLDER' ) . '/menu/string-translation.php' == $_GET['page']; } public function is_support_page() { return $this->is_core_page( 'support.php' ); } public function is_troubleshooting_page() { return $this->is_core_page( 'troubleshooting.php' ); } /** * @param string $page * * @return bool */ public function is_core_page( $page = '' ) { $result = is_admin() && isset( $_GET['page'] ) && stripos( $_GET['page'], $this->constant( 'ICL_PLUGIN_FOLDER' ) . '/menu/' . $page ) !== false; return $result; } public function is_back_end() { return is_admin() && ! $this->is_ajax() && ! $this->is_cron_job(); } public function is_front_end() { return ! is_admin() && ! $this->is_ajax() && ! $this->is_cron_job() && ! wpml_is_rest_request(); } public function is_ajax() { $result = defined( 'DOING_AJAX' ) && DOING_AJAX; if ( $this->function_exists( 'wpml_is_ajax' ) ) { /** @noinspection PhpUndefinedFunctionInspection */ $result = $result || wpml_is_ajax(); } return $result; } public function is_cron_job() { return defined( 'DOING_CRON' ) && DOING_CRON; } public function is_heartbeat() { $action = Sanitize::stringProp( 'action', $_POST ); return 'heartbeat' === $action; } public function is_post_edit_page() { global $pagenow; return 'post.php' === $pagenow && isset( $_GET['action'], $_GET['post'] ) && 'edit' === Sanitize::stringProp( 'action', $_GET ); } public function is_new_post_page() { global $pagenow; return 'post-new.php' === $pagenow; } public function is_term_edit_page() { global $pagenow; return 'term.php' === $pagenow || ( 'edit-tags.php' === $pagenow && isset( $_GET['action'] ) && 'edit' === filter_var( $_GET['action'] ) ); } public function is_customize_page() { global $pagenow; return 'customize.php' === $pagenow; } public function is_comments_post_page() { global $pagenow; return 'wp-comments-post.php' === $pagenow; } public function is_plugins_page() { global $pagenow; return 'plugins.php' === $pagenow; } public function is_themes_page() { global $pagenow; return 'themes.php' === $pagenow; } /** * Wrapper for \is_feed that returns false if called before the loop * * @param string $feeds * * @return bool */ public function is_feed( $feeds = '' ) { global $wp_query; return isset( $wp_query ) && is_feed( $feeds ); } /** * Wrapper for \wp_update_term_count * * @param int[] $terms given by their term_taxonomy_ids * @param string $taxonomy * @param bool|false $do_deferred * * @return bool */ public function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) { return wp_update_term_count( $terms, $taxonomy, $do_deferred ); } /** * Wrapper for \get_taxonomy * * @param string $taxonomy * * @return bool|object */ public function get_taxonomy( $taxonomy ) { return get_taxonomy( $taxonomy ); } /** * Wrapper for \wp_set_object_terms * * @param int $object_id The object to relate to. * @param array|int|string $terms A single term slug, single term id, or array of either term slugs or ids. * Will replace all existing related terms in this taxonomy. * @param string $taxonomy The context in which to relate the term to the object. * @param bool $append Optional. If false will delete difference of terms. Default false. * * @return array|WP_Error Affected Term IDs. */ public function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { return wp_set_object_terms( $object_id, $terms, $taxonomy, $append ); } /** * Wrapper for \get_post_types * * @param array $args * @param string $output * @param string $operator * * @return array */ public function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) { return get_post_types( $args, $output, $operator ); } public function wp_send_json( $response ) { wp_send_json( $response ); return $response; } public function wp_send_json_success( $data = null ) { wp_send_json_success( $data ); return $data; } public function wp_send_json_error( $data = null ) { wp_send_json_error( $data ); return $data; } /** * Wrapper for \get_current_user_id * * @return int */ public function get_current_user_id() { return get_current_user_id(); } /** * Wrapper for \get_post * * @param null|int|WP_Post $post * @param string $output * @param string $filter * * @return array|null|WP_Post */ public function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) { return get_post( $post, $output, $filter ); } /** * Wrapper for \get_post_meta * * @param int $post_id Post ID. * @param string $key Optional. The meta key to retrieve. By default, returns * data for all keys. Default empty. * @param bool $single Optional. Whether to return a single value. Default false. * * @return mixed Will be an array if $single is false. Will be value of meta data * field if $single is true. */ public function get_post_meta( $post_id, $key = '', $single = false ) { return get_post_meta( $post_id, $key, $single ); } /** * Wrapper for \update_post_meta * * @param int $post_id Post ID. * @param string $key * @param mixed $value * @param mixed $prev_value * * @return int|bool */ public function update_post_meta( $post_id, $key, $value, $prev_value = '' ) { return update_post_meta( $post_id, $key, $value, $prev_value ); } /** * Wrapper for add_post_meta * * @param int $post_id Post ID. * @param string $meta_key Metadata name. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $unique Optional. Whether the same key should not be added. * Default false. * @return int|false Meta ID on success, false on failure. */ public function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) { return add_post_meta( $post_id, $meta_key, $meta_value, $unique ); } /** * Wrapper for delete_post_meta * * @param int $post_id Post ID. * @param string $meta_key Metadata name. * @param mixed $meta_value Optional. Metadata value. Must be serializable if * non-scalar. Default empty. * @return bool True on success, false on failure. */ public function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) { return delete_post_meta( $post_id, $meta_key, $meta_value ); } /** * Wrapper for \get_term_meta * * @param int $term_id * @param string $key * @param bool $single * * @return mixed */ public function get_term_meta( $term_id, $key = '', $single = false ) { return get_term_meta( $term_id, $key, $single ); } /** * Wrapper for \get_permalink * * @param int $id * @param bool|false $leavename * * @return bool|string */ public function get_permalink( $id = 0, $leavename = false ) { return get_permalink( $id, $leavename ); } /** * Wrapper for \wp_mail * * @param string $to * @param string $subject * @param string $message * @param string|array $headers * @param array|array $attachments * * @return bool */ public function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) { return wp_mail( $to, $subject, $message, $headers, $attachments ); } /** * Wrapper for \get_post_custom * * @param int $post_id * * @return array */ public function get_post_custom( $post_id = 0 ) { return get_post_custom( $post_id ); } public function is_dashboard_tab() { return $this->is_tm_page( 'dashboard' ); } public function wp_safe_redirect( $redir_target, $status = 302 ) { if ( wp_safe_redirect( $redir_target, $status, 'WPML' ) ) { exit; } } /** * Wrapper for \load_textdomain * * @param string $domain * @param string $mofile * * @return bool */ public function load_textdomain( $domain, $mofile ) { return load_textdomain( $domain, $mofile ); } /** * Wrapper for \get_home_url * * @param null|int $blog_id * @param string $path * @param null|string $scheme * * @return string */ public function get_home_url( $blog_id = null, $path = '', $scheme = null ) { return get_home_url( $blog_id, $path, $scheme ); } /** * Wrapper for \get_site_url * * @param null|int $blog_id * @param string $path * @param null|string $scheme * * @return string */ public function get_site_url( $blog_id = null, $path = '', $scheme = null ) { return get_site_url( $blog_id, $path, $scheme ); } /** * Wrapper for \is_multisite * * @return bool */ public function is_multisite() { return is_multisite(); } /** * Wrapper for \is_main_site * * @param null|int $site_id * * @return bool */ public function is_main_site( $site_id = null ) { return is_main_site( $site_id ); } /** * Wrapper for \ms_is_switched * * @return bool */ public function ms_is_switched() { return ms_is_switched(); } /** * Wrapper for \get_current_blog_id * * @return int */ public function get_current_blog_id() { return get_current_blog_id(); } /** * Wrapper for wp_get_post_terms * * @param int $post_id * @param string $taxonomy * @param array $args * * @return array|WP_Error */ public function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) { return wp_get_post_terms( $post_id, $taxonomy, $args ); } /** * Wrapper for get_taxonomies * * @param array $args * @param string $output * @param string $operator * * @return array */ public function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) { return get_taxonomies( $args, $output, $operator ); } /** * Wrapper for \wp_get_theme * * @param string $stylesheet * @param string $theme_root * * @return WP_Theme */ public function wp_get_theme( $stylesheet = null, $theme_root = null ) { return wp_get_theme( $stylesheet, $theme_root ); } /** * Wrapper for \wp_get_theme->get('Name') * * @return string */ public function get_theme_name() { return wp_get_theme()->get( 'Name' ); } /** * Wrapper for \wp_get_theme->parent_theme * * @return string */ public function get_theme_parent_name() { return wp_get_theme()->parent_theme; } /** * Wrapper for \wp_get_theme->get('URI') * * @return string */ public function get_theme_URI() { return wp_get_theme()->get( 'URI' ); } /** * Wrapper for \wp_get_theme->get('Author') * * @return string */ public function get_theme_author() { return wp_get_theme()->get( 'Author' ); } /** * Wrapper for \wp_get_theme->get('AuthorURI') * * @return string */ public function get_theme_authorURI() { return wp_get_theme()->get( 'AuthorURI' ); } /** * Wrapper for \wp_get_theme->get('Template') * * @return string */ public function get_theme_template() { return wp_get_theme()->get( 'Template' ); } /** * Wrapper for \wp_get_theme->get('Version') * * @return string */ public function get_theme_version() { return wp_get_theme()->get( 'Version' ); } /** * Wrapper for \wp_get_theme->get('TextDomain') * * @return string */ public function get_theme_textdomain() { return wp_get_theme()->get( 'TextDomain' ); } /** * Wrapper for \wp_get_theme->get('DomainPath') * * @return string */ public function get_theme_domainpath() { return wp_get_theme()->get( 'DomainPath' ); } /** * Wrapper for \get_plugins() * * @return array */ public function get_plugins() { require_once ABSPATH . 'wp-admin/includes/plugin.php'; return get_plugins(); } /** * Wrapper for \get_post_custom_keys * * @param int $post_id * * @return array|void */ public function get_post_custom_keys( $post_id ) { return get_post_custom_keys( $post_id ); } /** * Wrapper for \get_bloginfo * * @param string $show (optional) * @param string $filter (optional) * * @return string */ public function get_bloginfo( $show = '', $filter = 'raw' ) { return get_bloginfo( $show, $filter ); } /** * Compare version in their "naked" form * * @see \WPML_WP_API::get_naked_version * @see \WPML_WP_API::version_compare * @see \version_compare * * @param string $version1 * @param string $version2 * @param null $operator * * @return mixed */ public function version_compare_naked( $version1, $version2, $operator = null ) { return $this->version_compare( $this->get_naked_version( $version1 ), $this->get_naked_version( $version2 ), $operator ); } /** * Returns only the first 3 numeric elements of a version (assuming to use MAJOR.MINOR.PATCH * * @param string $version * * @return string */ public function get_naked_version( $version ) { $elements = explode( '.', str_replace( '..', '.', preg_replace( '/([^0-9\.]+)/', '.$1.', str_replace( array( '-', '_', '+' ), '.', trim( $version ) ) ) ) ); $naked_elements = array( '0', '0', '0' ); $elements_count = 0; foreach ( $elements as $element ) { if ( $elements_count === 3 || ! is_numeric( $element ) ) { break; } $naked_elements[ $elements_count ] = $element; $elements_count ++; } return implode( $naked_elements ); } public function has_filter( $tag, $function_to_check = false ) { return has_filter( $tag, $function_to_check ); } public function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { return add_action( $tag, $function_to_add, $priority, $accepted_args ); } public function get_current_screen() { return get_current_screen(); } /** * Wrapper for \get_query_var * * @param string $var * @param mixed $default * * @return mixed */ public function get_query_var( $var, $default = '' ) { return get_query_var( $var, $default ); } /** * Wrapper for \get_queried_object */ public function get_queried_object() { return get_queried_object(); } public function get_raw_post_data() { $raw_post_data = @file_get_contents( 'php://input' ); if ( ! $raw_post_data && array_key_exists( 'HTTP_RAW_POST_DATA', $GLOBALS ) ) { $raw_post_data = $GLOBALS['HTTP_RAW_POST_DATA']; } return $raw_post_data; } public function wp_verify_nonce( $nonce, $action = -1 ) { return wp_verify_nonce( $nonce, $action ); } /** * @param string $action * * @return int The number of times action hook $tag is fired. */ public function did_action( $action ) { return did_action( $action ); } /** * @return string */ public function current_action() { return current_action(); } public function get_wp_post_types_global() { global $wp_post_types; return $wp_post_types; } /** * @return wp_xmlrpc_server */ public function get_wp_xmlrpc_server() { global $wp_xmlrpc_server; return $wp_xmlrpc_server; } /** * Wrapper for $wp_taxonomies global variable */ public function get_wp_taxonomies() { global $wp_taxonomies; return $wp_taxonomies; } /** * Wrapper for get_category_link function * * @param int $category_id * * @return string */ public function get_category_link( $category_id ) { return get_category_link( $category_id ); } /** * Wrapper for is_wp_error function * * @param mixed $thing * * @return bool */ public function is_wp_error( $thing ) { return is_wp_error( $thing ); } /** * @param int $limit * @param bool $provide_object * @param bool $ignore_args * * @return array */ public function get_backtrace( $limit = 0, $provide_object = false, $ignore_args = true ) { $options = false; if ( version_compare( $this->phpversion(), '5.3.6' ) < 0 ) { // Before 5.3.6, the only values recognized are TRUE or FALSE, // which are the same as setting or not setting the DEBUG_BACKTRACE_PROVIDE_OBJECT option respectively. $options = $provide_object; } else { // As of 5.3.6, 'options' parameter is a bitmask for the following options: if ( $provide_object ) { $options |= DEBUG_BACKTRACE_PROVIDE_OBJECT; } if ( $ignore_args ) { // phpcs:disable PHPCompatibility.Constants.NewConstants.debug_backtrace_ignore_argsFound -- It has a version check $options |= DEBUG_BACKTRACE_IGNORE_ARGS; // phpcs:enable PHPCompatibility.Constants.NewConstants.debug_backtrace_ignore_argsFound } } // phpcs:disable PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection -- It has a version check // phpcs:disable PHPCompatibility.FunctionUse.NewFunctionParameters.debug_backtrace_limitFound -- It has a version check if ( version_compare( $this->phpversion(), '5.4.0' ) >= 0 ) { $debug_backtrace = debug_backtrace( $options, $limit ); // add one item to include the current frame } elseif ( version_compare( $this->phpversion(), '5.2.4' ) >= 0 ) { // @link https://core.trac.wordpress.org/ticket/20953 $debug_backtrace = debug_backtrace(); } else { $debug_backtrace = debug_backtrace( $options ); } // phpcs:enable PHPCompatibility.FunctionUse.NewFunctionParameters.debug_backtrace_limitFound // phpcs:enable PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection // Remove the current frame if ( $debug_backtrace ) { array_shift( $debug_backtrace ); } return $debug_backtrace; } /** * @return WP_Filesystem_Direct */ public function get_wp_filesystem_direct() { global $wp_filesystem; require_once ABSPATH . 'wp-admin/includes/file.php'; /** * We need to make sure that `WP_Filesystem` has been called * at least once so that some constants are defined with * default values. */ if ( ! $wp_filesystem ) { WP_Filesystem(); } require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; return new WP_Filesystem_Direct( null ); } /** * @return WPML_Notices */ public function get_admin_notices() { global $wpml_admin_notices; if ( ! $wpml_admin_notices ) { $wpml_admin_notices = new WPML_Notices( new WPML_Notice_Render() ); $wpml_admin_notices->init_hooks(); } return $wpml_admin_notices; } /** * @param Twig_LoaderInterface $loader * @param array $environment_args * * @return Twig_Environment */ public function get_twig_environment( $loader, $environment_args ) { return new Twig_Environment( $loader, $environment_args ); } /** * @param array $template_paths * * @return Twig_Loader_Filesystem|\WPML\Core\Twig_LoaderInterface */ public function get_twig_loader_filesystem( $template_paths ) { return new Twig_Loader_Filesystem( $template_paths ); } /** * @return \WPML\Core\Twig_Loader_String|\WPML\Core\Twig_LoaderInterface */ public function get_twig_loader_string() { return new Twig_Loader_String(); } public function is_a_REST_request() { return defined( 'REST_REQUEST' ) && REST_REQUEST; } } wpml-wp/class-wpml-wp-taxonomy.php 0000755 00000001157 14720342453 0013255 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 30/10/17 * Time: 9:09 PM */ class WPML_WP_Taxonomy implements IWPML_WP_Element_Type { public static function get_linked_post_types( $taxonomy ) { global $wp_taxonomies; $post_types = array(); if ( isset( $wp_taxonomies[ $taxonomy ] ) && isset( $wp_taxonomies[ $taxonomy ]->object_type ) ) { $post_types = $wp_taxonomies[ $taxonomy ]->object_type; } return $post_types; } /** * @param string $taxonomy * * @return false|WP_Taxonomy */ public function get_wp_element_type_object( $taxonomy ) { return get_taxonomy( $taxonomy ); } } wpml-wp/wpml-wp-post-type.php 0000755 00000000373 14720342453 0012237 0 ustar 00 <?php class WPML_WP_Post_Type implements IWPML_WP_Element_Type { /** * @param string $post_type * * @return null|WP_Post_Type */ public function get_wp_element_type_object( $post_type ) { return get_post_type_object( $post_type ); } } wpml-wp/wpml-php-functions.php 0000755 00000005740 14720342453 0012447 0 ustar 00 <?php /** * Wrapper class for basic PHP functions */ class WPML_PHP_Functions { /** * Wrapper around PHP constant defined * * @param string $constant_name * * @return bool */ public function defined( $constant_name ) { return defined( $constant_name ); } /** * Wrapper around PHP constant lookup * * @param string $constant_name * * @return string|int */ public function constant( $constant_name ) { return $this->defined( $constant_name ) ? constant( $constant_name ) : null; } /** * @param string $function_name The function name, as a string. * * @return bool true if <i>function_name</i> exists and is a function, false otherwise. * This function will return false for constructs, such as <b>include_once</b> and <b>echo</b>. * @return bool */ public function function_exists( $function_name ) { return function_exists( $function_name ); } /** * @param string $class_name The class name. The name is matched in a case-insensitive manner. * @param bool $autoload [optional] Whether or not to call &link.autoload; by default. * * @return bool true if <i>class_name</i> is a defined class, false otherwise. * @return bool */ public function class_exists( $class_name, $autoload = true ) { return class_exists( $class_name, $autoload ); } /** * @param string $name The extension name * * @return bool true if the extension identified by <i>name</i> is loaded, false otherwise. */ public function extension_loaded( $name ) { return extension_loaded( $name ); } /** * @param string $string * * @return string */ public function mb_strtolower( $string ) { if ( function_exists( 'mb_strtolower' ) ) { return mb_strtolower( $string ); } return strtolower( $string ); } /** * Wrapper for \phpversion() * * * @param string $extension (optional) * * @return string */ public function phpversion( $extension = null ) { if ( defined( 'PHP_VERSION' ) ) { return PHP_VERSION; } else { return phpversion( $extension ); } } /** * Compares two "PHP-standardized" version number strings * * @see \WPML_WP_API::version_compare * * @param string $version1 * @param string $version2 * @param ?string $operator * * @return mixed */ public function version_compare( $version1, $version2, $operator = null ) { return version_compare( $version1 ?: '0.0.0', $version2 ?: '0.0.0', $operator ); } /** * @param array $array * @param int $sort_flags * * @return array */ public function array_unique( $array, $sort_flags = SORT_REGULAR ) { return wpml_array_unique( $array, $sort_flags ); } /** * @param string $message * @param int $message_type * @param string $destination * @param string $extra_headers * * @return bool */ public function error_log( $message, $message_type = null, $destination = null, $extra_headers = null ) { return error_log( $message, $message_type, $destination, $extra_headers ); } public function exit_php() { exit(); } } translation-priorities/class-wpml-tm-translation-priorities-register-action.php 0000755 00000003727 14720342453 0024342 0 ustar 00 <?php /** * Class WPML_TM_Translation_Priorities_Register_Action */ class WPML_TM_Translation_Priorities_Register_Action implements IWPML_Action { /** @var SitePress */ private $sitepress; const TRANSLATION_PRIORITY_TAXONOMY = 'translation_priority'; /** * WPML_TM_Translation_Priorities_Register_Action constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { add_action( 'init', array( $this, 'register_translation_priority_taxonomy' ), 5 ); } public function register_translation_priority_taxonomy() { if ( ! is_blog_installed() ) { return; } register_taxonomy( self::TRANSLATION_PRIORITY_TAXONOMY, apply_filters( 'wpml_taxonomy_objects_translation_priority', array_keys( $this->sitepress->get_translatable_documents() ) ), apply_filters( 'wpml_taxonomy_args_translation_priority', array( 'label' => __( 'Translation Priority', 'sitepress' ), 'labels' => array( 'name' => __( 'Translation Priorities', 'sitepress' ), 'singular_name' => __( 'Translation Priority', 'sitepress' ), 'all_items' => __( 'All Translation Priorities', 'sitepress' ), 'edit_item' => __( 'Edit Translation Priority', 'sitepress' ), 'update_item' => __( 'Update Translation Priority', 'sitepress' ), 'add_new_item' => __( 'Add new Translation Priority', 'sitepress' ), 'new_item_name' => __( 'New Translation Priority Name', 'sitepress' ), ), 'hierarchical' => false, 'show_ui' => true, 'show_in_menu' => false, 'show_in_rest' => false, 'show_tagcloud' => false, 'show_in_quick_edit' => true, 'show_admin_column' => false, 'query_var' => is_admin(), 'rewrite' => false, 'public' => false, 'meta_box_cb' => false, ) ) ); } } translation-priorities/class-wpml-tm-translation-priorities.php 0000755 00000006114 14720342453 0021236 0 ustar 00 <?php /** * Class WPML_Translation_Priorities */ class WPML_TM_Translation_Priorities { const DEFAULT_TRANSLATION_PRIORITY_VALUE_SLUG = 'optional'; const TAXONOMY = 'translation_priority'; public function get_values() { return get_terms( array( 'taxonomy' => self::TAXONOMY, 'hide_empty' => false, ) ); } /** * @return int */ public function get_default_value_id() { return (int) self::get_default_term()->term_id; } /** * @return WP_Term */ public static function get_default_term() { $term = get_term_by( 'slug', self::DEFAULT_TRANSLATION_PRIORITY_VALUE_SLUG, self::TAXONOMY ); if ( ! $term ) { $term = new WP_Term( (object) [ 'term_id' => 0 ] ); } return $term; } /** * @param int $term_taxonomy_id * @param string $original_name * @param string $target_language * * @return int|bool */ public static function insert_missing_translation( $term_taxonomy_id, $original_name, $target_language ) { global $sitepress; $trid = (int) $sitepress->get_element_trid( $term_taxonomy_id, 'tax_' . self::TAXONOMY ); $term_translations = $sitepress->get_element_translations( $trid, 'tax_' . self::TAXONOMY ); if ( ! isset( $term_translations[ $target_language ] ) ) { $sitepress->switch_locale( $target_language ); $name = __( $original_name, 'sitepress' ); $slug = WPML_Terms_Translations::term_unique_slug( sanitize_title( $name ), self::TAXONOMY, $target_language ); $translated_term = wp_insert_term( $name, self::TAXONOMY, array( 'slug' => $slug ) ); if ( $translated_term && ! is_wp_error( $translated_term ) ) { $sitepress->set_element_language_details( $translated_term['term_taxonomy_id'], 'tax_' . self::TAXONOMY, $trid, $target_language ); return $translated_term['term_taxonomy_id']; } } return false; } public static function insert_missing_default_terms() { global $sitepress; $terms = array( array( 'default' => __( 'Optional', 'sitepress' ), 'en_name' => 'Optional', ), array( 'default' => __( 'Required', 'sitepress' ), 'en_name' => 'Required', ), array( 'default' => __( 'Not needed', 'sitepress' ), 'en_name' => 'Not needed', ), ); $default_language = $sitepress->get_default_language(); $active_languages = $sitepress->get_active_languages(); $current_language = $sitepress->get_current_language(); unset( $active_languages[ $default_language ] ); foreach ( $terms as $term ) { $sitepress->switch_locale( $default_language ); $original_term = get_term_by( 'name', $term['default'], self::TAXONOMY, ARRAY_A ); if ( ! $original_term ) { $original_term = wp_insert_term( $term['default'], self::TAXONOMY ); $sitepress->set_element_language_details( $original_term['term_taxonomy_id'], 'tax_' . self::TAXONOMY, null, $default_language ); } foreach ( $active_languages as $language ) { self::insert_missing_translation( $original_term['term_taxonomy_id'], $term['en_name'], $language['code'] ); } } $sitepress->switch_locale( $current_language ); } } translation-priorities/class-wpml-tm-translation-priorities-factory.php 0000755 00000000467 14720342453 0022710 0 ustar 00 <?php /** * Class WPML_TM_Translation_Priorities_Factory */ class WPML_TM_Translation_Priorities_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { public function create() { global $sitepress; return new WPML_TM_Translation_Priorities_Register_Action( $sitepress ); } } comments/wpml-wp-comments.php 0000755 00000001261 14720342453 0012337 0 ustar 00 <?php class WPML_WP_Comments { const LANG_CODE_FIELD = 'wpml_language_code'; /** * @var SitePress */ private $sitepress; /** * WPML_WP_Comments constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { add_filter( 'comment_form_field_comment', array( $this, 'add_wpml_language_field' ) ); } /** * @return mixed */ public function add_wpml_language_field( $comment_field ) { $comment_field .= '<input name="' . self::LANG_CODE_FIELD . '" type="hidden" value="' . $this->sitepress->get_current_language() . '" />'; return $comment_field; } } translation-management/class-wpml-translation-management-filters-and-actions.php 0000755 00000003562 14720342453 0024336 0 ustar 00 <?php class WPML_Translation_Management_Filters_And_Actions { /** * @var SitePress $sitepress */ private $sitepress; /** * @var \AbsoluteLinks */ private $absolute_links; /** * @var \WPML_Absolute_To_Permalinks */ private $permalinks_converter; /** * @var \WPML_Translate_Link_Targets_In_Custom_Fields */ private $translate_links_in_custom_fields; /** * @var \WPML_Translate_Link_Targets_In_Custom_Fields_Hooks */ private $translate_links_in_custom_fields_hooks; /** * @var \WPML_Translate_Link_Targets */ private $translate_link_target; /** * @var \WPML_Translate_Link_Targets_Hooks */ private $translate_link_target_hooks; /** * @param TranslationManagement $tm_instance * @param \SitePress $sitepress */ public function __construct( $tm_instance, $sitepress ) { $this->sitepress = $sitepress; if ( ! is_admin() ) { $this->add_filters_for_translating_link_targets( $tm_instance ); } } private function add_filters_for_translating_link_targets( &$tm_instance ) { $this->absolute_links = new AbsoluteLinks(); $wp_api = $this->sitepress->get_wp_api(); $this->permalinks_converter = new WPML_Absolute_To_Permalinks( $this->sitepress ); $this->translate_links_in_custom_fields = new WPML_Translate_Link_Targets_In_Custom_Fields( $tm_instance, $wp_api, $this->absolute_links, $this->permalinks_converter ); $this->translate_links_in_custom_fields_hooks = new WPML_Translate_Link_Targets_In_Custom_Fields_Hooks( $this->translate_links_in_custom_fields, $wp_api ); $this->translate_link_target = new WPML_Translate_Link_Targets( $this->absolute_links, $this->permalinks_converter ); $this->translate_link_target_hooks = new WPML_Translate_Link_Targets_Hooks( $this->translate_link_target, $wp_api ); } } translation-management/class-wpml-translate-link-targets-hooks.php 0000755 00000000634 14720342453 0021537 0 ustar 00 <?php class WPML_Translate_Link_Targets_Hooks { /** * WPML_Translate_Link_Targets_Hooks constructor. * * @param WPML_Translate_Link_Targets $translate_link_targets * @param WPML_WP_API $wp_api */ public function __construct( $translate_link_targets, $wp_api ) { $wp_api->add_filter( 'wpml_translate_link_targets', array( $translate_link_targets, 'convert_text' ), 10, 1 ); } } icl/class-wpml-icl-client.php 0000755 00000004236 14720342453 0012143 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ICL_Client { private $error; /** @var WP_Http $http */ private $http; /** @var WPML_WP_API $wp_api */ private $wp_api; private $method = 'GET'; private $post_data; /** * WPML_ICL_Client constructor. * * @param WP_Http $http * @param WPML_WP_API $wp_api */ public function __construct( $http, $wp_api ) { $this->http = $http; $this->wp_api = $wp_api; } function request( $request_url ) { $results = false; $this->error = false; $request_url = $this->get_adjusted_request_url( $request_url ); $this->adjust_post_data(); if ( 'GET' === $this->method ) { $result = $this->http->get( $request_url ); } else { $result = $this->http->post( $request_url, array( 'body' => $this->post_data ) ); } if ( is_wp_error( $result ) ) { $this->error = $result->get_error_message(); } else { $results = icl_xml2array( $result['body'], 1 ); if ( array_key_exists( 'info', $results ) && '-1' === $results['info']['status']['attr']['err_code'] ) { $this->error = $results['info']['status']['value']; $results = false; } } return $results; } public function get_error() { return $this->error; } /** * @return array */ private function get_debug_data() { $debug_vars = array( 'debug_cms' => 'WordPress', 'debug_module' => 'WPML ' . $this->wp_api->constant( 'ICL_SITEPRESS_VERSION' ), 'debug_url' => $this->wp_api->get_bloginfo( 'url' ), ); return $debug_vars; } /** * @param string $request_url * * @return mixed|string */ private function get_adjusted_request_url( $request_url ) { $request_url = str_replace( ' ', '%20', $request_url ); if ( 'GET' === $this->method ) { $request_url .= '&' . http_build_query( $this->get_debug_data() ); } return $request_url; } private function adjust_post_data() { if ( 'GET' !== $this->method ) { $this->post_data = array_merge( $this->post_data, $this->get_debug_data() ); } } /** * @param string $method */ public function set_method( $method ) { $this->method = $method; } public function set_post_data( $post_data ) { $this->post_data = $post_data; } } translation-proxy/ui/wpml-tp-extra-field-display.php 0000755 00000004212 14720342453 0016662 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TP_Extra_Field_Display { private $fields_with_items = array( 'select', 'radio', 'checkbox' ); /** * WPML_TP_Extra_Field_Display constructor. */ public function __construct() { } public function render( $field ) { if ( $this->must_render( $field ) ) { $field_id = 'wpml-tp-extra-field-' . $field->name; $row = '<tr>'; $row .= '<th scope="row">'; $row .= '<label for="' . esc_attr( $field_id ) . '">' . esc_html( $field->label ) . '</label>'; $row .= '</th>'; $row .= '<td>'; switch ( $field->type ) { case 'select': $row .= '<select id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field->name ) . '">'; foreach ( $field->items as $id => $name ) { $row .= '<option value="' . esc_attr( $id ) . '">' . esc_html( $name ) . '</option>'; } $row .= '</select>'; break; case 'textarea': $row .= '<textarea id="' . esc_attr( $field_id ) . '" name="' . esc_attr( $field->name ) . '"></textarea>'; break; case 'radio': case 'checkbox': $row .= '<ol>'; foreach ( $field->items as $id => $name ) { $row .= '<li>'; $row .= '<input id="' . esc_attr( $field_id . '-' . $id ) . '" type="' . esc_attr( $field->type ) . '" name="' . esc_attr( $field->name ) . '" value="' . esc_attr( $id ) . '"> '; $row .= '<label for="' . esc_attr( $field_id . '-' . $id ) . '">' . esc_html( $name ) . '</label>'; $row .= '</li>'; } $row .= '</ol>'; break; case 'text': default: $type = null !== $field->type ? $field->type : 'text'; $row .= '<input id="' . esc_attr( $field_id ) . '" type="' . esc_attr( $type ) . '" name="' . esc_attr( $field->name ) . '">'; break; } $row .= '</td>'; $row .= '</tr>'; return $row; } return ''; } /** * @param $field * * @return bool */ private function must_render( $field ) { $must_render = isset( $field->type ) && $field->type; if ( $must_render && in_array( $field->type, $this->fields_with_items, true ) ) { $must_render = isset( $field->items ) && $field->items; } return $must_render; } } translation-proxy/class-wpml-tp-translator.php 0000755 00000000465 14720342453 0015700 0 ustar 00 <?php /** * Class WPML_TP_Translator */ class WPML_TP_Translator { /** * Return translator status array. * * @param bool $force * * @return array */ public function get_icl_translator_status( $force = false ) { return TranslationProxy_Translator::get_icl_translator_status( $force ); } } translation-proxy/class-wpml-tm-cms-id.php 0000755 00000012420 14720342453 0014652 0 ustar 00 <?php /** * Class WPML_TM_CMS_ID */ class WPML_TM_CMS_ID extends WPML_TM_Record_User { private $cms_id_parts_glue = '_'; private $cms_id_parts_fallback_glue = '|||'; /** @var WPML_Translation_Job_Factory $tm_job_factory */ private $job_factory; /** @var wpdb $wpdb */ private $wpdb; /** * WPML_TM_CMS_ID constructor. * * @param WPML_TM_Records $tm_records * @param WPML_Translation_Job_Factory $job_factory */ public function __construct( &$tm_records, &$job_factory ) { parent::__construct( $tm_records ); $this->job_factory = &$job_factory; $this->wpdb = $this->tm_records->wpdb(); } /** * @param int $post_id * @param string $post_type * @param string $source_language * @param string $target_language * * @return string */ public function build_cms_id( $post_id, $post_type, $source_language, $target_language ) { $cms_id_parts = array( $post_type, $post_id, $source_language, $target_language ); return implode( $this->cms_id_parts_glue, $cms_id_parts ); } /** * Returns the cms_id for a given job * * @param int $job_id * * @return false|string */ function cms_id_from_job_id( $job_id ) { $original_element_row = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT o.element_id, o.element_type, o.language_code as source_lang, i.language_code as target_lang FROM {$this->wpdb->prefix}icl_translations o JOIN {$this->wpdb->prefix}icl_translations i ON i.trid = o.trid AND i.source_language_code = o.language_code JOIN {$this->wpdb->prefix}icl_translation_status s ON s.translation_id = i.translation_id JOIN {$this->wpdb->prefix}icl_translate_job j ON j.rid = s.rid WHERE j.job_id = %d LIMIT 1", $job_id ) ); $type_parts = (bool) $original_element_row === true ? explode( '_', $original_element_row->element_type, 2 ) : false; return is_array( $type_parts ) && count( $type_parts ) === 2 ? $this->build_cms_id( $original_element_row->element_id, end( $type_parts ), $original_element_row->source_lang, $original_element_row->target_lang ) : false; } /** * @param string $cms_id * * @return array; */ public function parse_cms_id( $cms_id ) { if ( $this->is_standard_format( $cms_id ) ) { $parts = array_filter( explode( $this->cms_id_parts_glue, $cms_id ) ); while ( count( $parts ) > 4 ) { $parts_copy = $parts; $parts[0] = $parts_copy[0] . $this->cms_id_parts_glue . $parts_copy[1]; unset( $parts_copy[0] ); unset( $parts_copy[1] ); $parts = array_merge( array( $parts[0] ), array_values( array_filter( $parts_copy ) ) ); } } else { $parts = explode( $this->cms_id_parts_fallback_glue, $cms_id ); } return array_pad( array_slice( $parts, 0, 4 ), 4, false ); } /** * @param string $cms_id * @param bool|TranslationProxy_Service $translation_service * * @return int|null translation id for the given cms_id's target */ public function get_translation_id( $cms_id, $translation_service = false ) { list( $post_type, $element_id, , $target_lang ) = $this->parse_cms_id( $cms_id ); $translation = $this->wpdb->get_row( $this->wpdb->prepare( " SELECT t.translation_id, j.job_id, t.element_id FROM {$this->wpdb->prefix}icl_translations t JOIN {$this->wpdb->prefix}icl_translations o ON o.trid = t.trid AND o.element_type = t.element_type LEFT JOIN {$this->wpdb->prefix}icl_translation_status st ON st.translation_id = t.translation_id LEFT JOIN {$this->wpdb->prefix}icl_translate_job j ON j.rid = st.rid WHERE o.element_id=%d AND t.language_code=%s AND o.element_type LIKE %s LIMIT 1", $element_id, $target_lang, '%_' . $post_type ) ); $translation_id = $this->maybe_cleanup_broken_row( $translation, $translation_service ); if ( $translation_service && ! isset( $translation_id ) && $translation_service ) { $job_id = $this->job_factory->create_local_post_job( $element_id, $target_lang ); $job = $this->job_factory->get_translation_job( $job_id, false, false, true ); $translation_id = $job ? $job->get_translation_id() : 0; if ( $translation_id ) { $this->tm_records->icl_translation_status_by_translation_id( $translation_id )->update( array( 'status' => ICL_TM_IN_PROGRESS, 'translation_service' => $translation_service->id, ) ); } } return $translation_id; } private function maybe_cleanup_broken_row( $translation, $translation_service ) { if ( $translation && ( $translation_id = $translation->translation_id ) && ! $translation->element_id && $translation_service && ! $translation->job_id ) { $this->tm_records->icl_translations_by_translation_id( $translation_id )->delete(); $translation_id = null; } return isset( $translation_id ) ? $translation_id : null; } /** * @param $cms_id * * @return bool */ private function is_standard_format( $cms_id ) { return count( array_filter( explode( $this->cms_id_parts_fallback_glue, $cms_id ) ) ) < 3; } } translation-proxy/class-wpml-tp-string-job.php 0000755 00000007324 14720342453 0015566 0 ustar 00 <?php class WPML_TP_String_Job extends WPML_WPDB_User { /** @var WPML_Translation_Basket $basket */ private $basket; /** @var WPML_Translation_Job_Factory $job_factory */ private $job_factory; /** * WPML_TP_String_Job constructor. * * @param wpdb $wpdb * @param WPML_Translation_Basket $basket * @param WPML_Translation_Job_Factory $job_factory */ public function __construct( &$wpdb, &$basket, &$job_factory ) { parent::__construct( $wpdb ); $this->basket = &$basket; $this->job_factory = &$job_factory; } function send_strings_to_translation_service( $string_ids, $target_language, $translator_id ) { /** @var WPML_String_Translation $WPML_String_Translation */ global $WPML_String_Translation; if ( sizeof( $string_ids ) > 0 ) { $project = $this->basket->get_project(); $strings = array(); $word_count = 0; $source_language = $this->basket->get_source_language(); foreach ( $string_ids as $string_id ) { $string_data_query = "SELECT id, context, name, value FROM {$this->wpdb->prefix}icl_strings WHERE id=%d"; $string_data_prepare = $this->wpdb->prepare( $string_data_query, $string_id ); $string_data = $this->wpdb->get_row( $string_data_prepare ); $word_count += $WPML_String_Translation->estimate_word_count( $string_data->value, $source_language ); $strings[] = $string_data; } $xliff = new WPML_TM_Xliff_Writer( $this->job_factory ); try { $tp_job_id = $project->send_to_translation_batch_mode( $xliff->get_strings_xliff_file( $strings, $source_language, $target_language ), 'String Translations', '', '', $source_language, $target_language, $word_count ); } catch ( Exception $e ) { return [ 'errors' => [ $e->getMessage() ] ]; } if ( $tp_job_id ) { foreach ( $strings as $string_data ) { $translation_service = TranslationProxy_Service::get_translator_data_from_wpml( $translator_id ); $string_translation_id = icl_add_string_translation( $string_data->id, $target_language, null, ICL_TM_IN_PROGRESS, $translation_service['translator_id'], $translation_service['translation_service'], TranslationProxy_Batch::update_translation_batch( $this->basket->get_name() ) ); if ( $string_translation_id ) { $data = array( 'rid' => $tp_job_id, 'string_translation_id' => $string_translation_id, 'timestamp' => date( 'Y-m-d H:i:s' ), 'md5' => md5( $string_data->value ), ); $current_rid = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT rid FROM {$this->wpdb->prefix}icl_string_status WHERE string_translation_id=%d", $string_translation_id ) ); if ( $current_rid ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_string_status', $data, array( 'rid' => $current_rid ) ); } else { $this->wpdb->insert( $this->wpdb->prefix . 'icl_string_status', $data ); // insert rid } } } $data = array( 'rid' => $tp_job_id, 'module' => '', 'origin' => $source_language, 'target' => $target_language, 'status' => ICL_TM_IN_PROGRESS, ); if ( ! empty( $current_rid ) ) { $data['ts_status'] = null; $this->wpdb->update( $this->wpdb->prefix . 'icl_core_status', $data, array( 'rid' => $current_rid ) ); } else { $this->wpdb->insert( $this->wpdb->prefix . 'icl_core_status', $data ); } if ( $project->errors && count( $project->errors ) ) { $tp_job_id['errors'] = $project->errors; } return $tp_job_id; } } return 0; } } translation-proxy/class-wpml-tp-refresh-language-pairs.php 0000755 00000002420 14720342453 0020033 0 ustar 00 <?php class WPML_TP_Refresh_Language_Pairs { const AJAX_ACTION = 'wpml-tp-refresh-language-pairs'; /** * @var WPML_TP_Project_API */ private $tp_api; /** * WPML_TP_AJAX constructor. * * @param WPML_TP_Project_API $wpml_tp_api */ public function __construct( WPML_TP_Project_API $wpml_tp_api ) { $this->tp_api = $wpml_tp_api; } public function add_hooks() { add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'refresh_language_pairs' ) ); } public function refresh_language_pairs() { if ( $this->is_valid_request() ) { try { $this->tp_api->refresh_language_pairs(); wp_send_json_success( array( 'msg' => __( 'Language pairs refreshed', 'wpml-translation-management' ), ) ); } catch ( Exception $e ) { wp_send_json_error( array( 'msg' => __( 'Language pairs not refreshed, please try again', 'wpml-translation-management' ), ) ); } } else { wp_send_json_error( array( 'msg' => __( 'Invalid Request', 'wpml-translation-management' ), ) ); } } /** * @return bool */ private function is_valid_request() { return array_key_exists( 'nonce', $_POST ) && wp_verify_nonce( filter_var( $_POST['nonce'], FILTER_SANITIZE_FULL_SPECIAL_CHARS ), self::AJAX_ACTION ); } } translation-proxy/translations/apply/class-wpml-tp-apply-translation-strategy.php 0000755 00000000437 14720342453 0024675 0 ustar 00 <?php interface WPML_TP_Apply_Translation_Strategy { /** * @param WPML_TM_Job_Entity $job * @param WPML_TP_Translation_Collection $translations * * @return void */ public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations ); } translation-proxy/translations/apply/class-wpml-tp-apply-translation-post-strategy.php 0000755 00000003413 14720342453 0025655 0 ustar 00 <?php class WPML_TP_Apply_Translation_Post_Strategy implements WPML_TP_Apply_Translation_Strategy { /** @var WPML_TP_Jobs_API */ private $jobs_api; /** @var wpdb */ private $wpdb; /** * @param WPML_TP_Jobs_API $jobs_api */ public function __construct( WPML_TP_Jobs_API $jobs_api ) { $this->jobs_api = $jobs_api; global $wpdb; $this->wpdb = $wpdb; } /** * @param WPML_TM_Job_Entity $job * @param WPML_TP_Translation_Collection $translations * * @return void * @throws WPML_TP_API_Exception */ public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations ) { if ( ! $job instanceof WPML_TM_Post_Job_Entity ) { throw new InvalidArgumentException( 'A job must have post type' ); } kses_remove_filters(); wpml_tm_save_data( $this->build_data( $job, $translations ) ); kses_init(); $this->jobs_api->update_job_state( $job, 'delivered' ); } /** * @param WPML_TM_Job_Entity $job * @param WPML_TP_Translation_Collection $translations * * @return array */ private function build_data( WPML_TM_Post_Job_Entity $job, WPML_TP_Translation_Collection $translations ) { $data = array( 'job_id' => $job->get_translate_job_id(), 'fields' => array(), 'complete' => 1 ); /** @var WPML_TP_Translation $translation */ foreach ( $translations as $translation ) { foreach ( $job->get_elements() as $element ) { if ( $element->get_type() === $translation->get_field() ) { $data['fields'][ $element->get_type() ] = array( 'data' => $translation->get_target(), 'finished' => 1, 'tid' => $element->get_id(), 'field_type' => $element->get_type(), 'format' => $element->get_format() ); } } } return $data; } } translation-proxy/translations/apply/class-wpml-tp-apply-single-job.php 0000755 00000002002 14720342453 0022516 0 ustar 00 <?php class WPML_TP_Apply_Single_Job { /** @var WPML_TP_Translations_Repository */ private $translations_repository; /** @var WPML_TP_Apply_Translation_Strategies */ private $strategy_dispatcher; /** * @param WPML_TP_Translations_Repository $translations_repository * @param WPML_TP_Apply_Translation_Strategies $strategy_dispatcher */ public function __construct( WPML_TP_Translations_Repository $translations_repository, WPML_TP_Apply_Translation_Strategies $strategy_dispatcher ) { $this->translations_repository = $translations_repository; $this->strategy_dispatcher = $strategy_dispatcher; } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TM_Job_Entity * @throws WPML_TP_API_Exception */ public function apply( WPML_TM_Job_Entity $job ) { $translations = $this->translations_repository->get_job_translations_by_job_entity( $job ); $this->strategy_dispatcher->get( $job )->apply( $job, $translations ); $job->set_status( ICL_TM_COMPLETE ); return $job; } } translation-proxy/translations/apply/class-wpml-tp-apply-translation-strategies.php 0000755 00000002711 14720342453 0025202 0 ustar 00 <?php class WPML_TP_Apply_Translation_Strategies { /** @var WPML_TP_Apply_Translation_Post_Strategy */ private $post_strategy; /** @var WPML_TP_Apply_Translation_String_Strategy */ private $string_strategy; /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TP_Apply_Translation_Strategy */ public function get( WPML_TM_Job_Entity $job ) { switch ( $job->get_type() ) { case WPML_TM_Job_Entity::STRING_TYPE: return $this->get_string_strategy(); case WPML_TM_Job_Entity::POST_TYPE: case WPML_TM_Job_Entity::PACKAGE_TYPE: case WPML_TM_Job_Entity::STRING_BATCH: return $this->get_post_strategy(); default: throw new InvalidArgumentException( 'Job type: ' . $job->get_type() . ' is not supported' ); } } /** * @return WPML_TP_Apply_Translation_Post_Strategy */ private function get_post_strategy() { if ( ! $this->post_strategy ) { $this->post_strategy = new WPML_TP_Apply_Translation_Post_Strategy( wpml_tm_get_tp_jobs_api() ); } return $this->post_strategy; } /** * @return WPML_TP_Apply_Translation_String_Strategy */ private function get_string_strategy() { if ( ! $this->string_strategy ) { $this->string_strategy = new WPML_TP_Apply_Translation_String_Strategy( wpml_tm_get_tp_jobs_api(), $this->wpdb ); } return $this->string_strategy; } } translation-proxy/translations/apply/class-wpml-tp-apply-translation-string-strategy.php 0000755 00000003216 14720342453 0026177 0 ustar 00 <?php class WPML_TP_Apply_Translation_String_Strategy implements WPML_TP_Apply_Translation_Strategy { /** @var WPML_TP_Jobs_API */ private $jobs_api; /** @var wpdb */ private $wpdb; /** * @param WPML_TP_Jobs_API $jobs_api * @param wpdb $wpdb */ public function __construct( WPML_TP_Jobs_API $jobs_api, wpdb $wpdb ) { $this->jobs_api = $jobs_api; $this->wpdb = $wpdb; } /** * @param WPML_TM_Job_Entity $job * @param WPML_TP_Translation_Collection $translations * * @return void * @throws WPML_TP_API_Exception */ public function apply( WPML_TM_Job_Entity $job, WPML_TP_Translation_Collection $translations ) { if ( ! icl_translation_add_string_translation( $job->get_tp_id(), $this->map_translations_to_legacy_array( $translations ), $translations->get_target_language() ) ) { throw new WPML_TP_API_Exception( 'Could not apply string translation!' ); } $this->update_local_job_status( $job, ICL_TM_COMPLETE ); $this->jobs_api->update_job_state( $job, 'delivered' ); } /** * @param WPML_TM_Job_Entity $job * @param int $status */ private function update_local_job_status( WPML_TM_Job_Entity $job, $status ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_core_status', array( 'status' => $status ), array( 'id' => $job->get_id() ) ); } private function map_translations_to_legacy_array( WPML_TP_Translation_Collection $translations ) { $result = array(); /** @var WPML_TP_Translation $translation */ foreach ( $translations as $translation ) { $result[ $translation->get_field() ] = $translation->get_target(); } return $result; } } translation-proxy/translations/class-wpml-tp-translation.php 0000755 00000001314 14720342453 0020560 0 ustar 00 <?php class WPML_TP_Translation { /** @var string */ private $field; /** @var string */ private $source; /** @var string */ private $target; /** * @param string $field * @param string $source * @param string $target */ public function __construct( $field, $source, $target ) { $this->field = $field; $this->source = $source; $this->target = $target; } /** * @return string */ public function get_field() { return $this->field; } /** * @return string */ public function get_source() { return $this->source; } /** * @return string */ public function get_target() { return $this->target; } public function to_array() { return get_object_vars( $this ); } } translation-proxy/translations/class-wpml-tp-apply-translation.php 0000755 00000007000 14720342453 0021701 0 ustar 00 <?php class WPML_TP_Apply_Translations { /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** @var WPML_TP_Apply_Single_Job */ private $apply_single_job; /** @var WPML_TP_Sync_Jobs */ private $tp_sync; /** * @param WPML_TM_Jobs_Repository $jobs_repository * @param WPML_TP_Apply_Single_Job $apply_single_job * @param WPML_TP_Sync_Jobs $tp_sync */ public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Apply_Single_Job $apply_single_job, WPML_TP_Sync_Jobs $tp_sync ) { $this->jobs_repository = $jobs_repository; $this->apply_single_job = $apply_single_job; $this->tp_sync = $tp_sync; } /** * @param array $params * * @return WPML_TM_Jobs_Collection * @throws WPML_TP_API_Exception */ public function apply( array $params ) { $jobs = $this->get_jobs( $params ); if ( $this->has_in_progress_jobs( $jobs ) ) { $jobs = $this->sync_jobs( $jobs ); } $cancelled_jobs = $jobs->filter_by_status( ICL_TM_NOT_TRANSLATED ); $downloaded_jobs = new WPML_TM_Jobs_Collection( $jobs->filter_by_status( [ ICL_TM_TRANSLATION_READY_TO_DOWNLOAD, ICL_TM_COMPLETE ] ) ->map( array( $this->apply_single_job, 'apply' ) ) ); return $downloaded_jobs->append( $cancelled_jobs ); } /** * @param WPML_TM_Jobs_Collection $jobs * * @return bool */ private function has_in_progress_jobs( WPML_TM_Jobs_Collection $jobs ) { return count( $jobs->filter_by_status( ICL_TM_IN_PROGRESS ) ) > 0; } /** * @param array $params * * @return WPML_TM_Jobs_Collection */ private function get_jobs( array $params ) { if ( $params ) { if ( isset( $params['original_element_id'], $params['element_type'] ) ) { $jobs = $this->get_jobs_by_original_element( $params['original_element_id'], $params['element_type'] ); } else { $jobs = $this->get_jobs_by_ids( $params ); } } else { $jobs = $this->get_all_ready_jobs(); } return $jobs; } /** * @param int $original_element_id * @param string $element_type * * @return WPML_TM_Jobs_Collection */ private function get_jobs_by_original_element( $original_element_id, $element_type ) { $params = new WPML_TM_Jobs_Search_Params(); $params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE ); $params->set_original_element_id( $original_element_id ); $params->set_job_types( $element_type ); return $this->jobs_repository->get_collection( $params ); } /** * @param array $params * * @return WPML_TM_Jobs_Collection */ private function get_jobs_by_ids( array $params ) { $jobs = array(); foreach ( $params as $param ) { $jobs[] = $this->jobs_repository->get_job( $param['id'], $param['type'] ); } return new WPML_TM_Jobs_Collection( $jobs ); } /** * @return WPML_TM_Jobs_Collection */ private function get_all_ready_jobs() { return $this->jobs_repository->get_collection( new WPML_TM_Jobs_Search_Params( array( 'status' => array( ICL_TM_TRANSLATION_READY_TO_DOWNLOAD ), 'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE, ) ) ); } /** * @param WPML_TM_Jobs_Collection $jobs * * @return WPML_TM_Jobs_Collection * @throws WPML_TP_API_Exception */ private function sync_jobs( WPML_TM_Jobs_Collection $jobs ) { $synced_jobs = $this->tp_sync->sync(); /** @var WPML_TM_Job_Entity $job */ foreach ( $jobs as $job ) { foreach ( $synced_jobs as $synced_job ) { if ( $job->is_equal( $synced_job ) ) { $job->set_status( $synced_job->get_status() ); break; } } } return $jobs; } } translation-proxy/translations/class-wpml-tp-translation-collection.php 0000755 00000002316 14720342453 0022714 0 ustar 00 <?php use WPML\FP\Fns; use function WPML\FP\invoke; class WPML_TP_Translation_Collection implements IteratorAggregate { /** @var WPML_TP_Translation[] */ private $translations; /** @var string */ private $source_language; /** @var string */ private $target_language; /** * @param WPML_TP_Translation[] $translations * @param string $source_language * @param string $target_language */ public function __construct( array $translations, $source_language, $target_language ) { $this->translations = $translations; $this->source_language = $source_language; $this->target_language = $target_language; } /** * @return string */ public function get_source_language() { return $this->source_language; } /** * @return string */ public function get_target_language() { return $this->target_language; } public function getIterator(): Traversable { return new ArrayIterator( $this->translations ); } /** * @return array */ public function to_array() { return [ 'source_language' => $this->source_language, 'target_language' => $this->target_language, 'translations' => Fns::map( invoke( 'to_array' ), $this->translations ), ]; } } translation-proxy/translations/class-wpml-tp-translations-repository.php 0000755 00000004152 14720342453 0023163 0 ustar 00 <?php class WPML_TP_Translations_Repository { /** @var WPML_TP_XLIFF_API */ private $xliff_api; /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** * @param WPML_TP_XLIFF_API $xliff_api * @param WPML_TM_Jobs_Repository $jobs_repository */ public function __construct( WPML_TP_XLIFF_API $xliff_api, WPML_TM_Jobs_Repository $jobs_repository ) { $this->xliff_api = $xliff_api; $this->jobs_repository = $jobs_repository; } /** * @param int $job_id * @param int $job_type * @param bool $parse When true, it returns the parsed translation, otherwise, it returns the raw XLIFF. * * @return WPML_TP_Translation_Collection|string * @throws WPML_TP_API_Exception|InvalidArgumentException */ public function get_job_translations( $job_id, $job_type, $parse = true ) { $job = $this->jobs_repository->get_job( $job_id, $job_type ); if ( ! $job ) { throw new InvalidArgumentException( 'Cannot find job' ); } return $this->get_job_translations_by_job_entity( $job, $parse ); } /** * @param WPML_TM_Job_Entity $job * @param bool $parse When true, it returns the parsed translation, otherwise, it returns the raw XLIFF. * * @return WPML_TP_Translation_Collection|string * @throws WPML_TP_API_Exception */ public function get_job_translations_by_job_entity( WPML_TM_Job_Entity $job, $parse = true ) { $correct_states = array( ICL_TM_TRANSLATION_READY_TO_DOWNLOAD, ICL_TM_COMPLETE ); if ( ! in_array( $job->get_status(), $correct_states, true ) ) { throw new InvalidArgumentException( 'Job\'s translation is not ready.' ); } if ( ! $job->get_tp_id() ) { throw new InvalidArgumentException( 'This is only a local job.' ); } $translations = $this->xliff_api->get_remote_translations( $job->get_tp_id(), $parse ); if ( $parse ) { /** * It filters translations coming from the Translation Proxy. * * @param \WPML_TP_Translation_Collection|string $translations * @param \WPML_TM_Job_Entity $job */ $translations = apply_filters( 'wpml_tm_proxy_translations', $translations, $job ); } return $translations; } } translation-proxy/class-wpml-tp-http-request-filter.php 0000755 00000005776 14720342453 0017451 0 ustar 00 <?php class WPML_TP_HTTP_Request_Filter { /** * @return array filtered response */ public function build_request_context( array $request ) { if ( ! $this->contains_resource( $request ) ) { $request['headers'] = 'Content-type: application/json'; $request['body'] = wp_json_encode( $request['body'] ); } else { list( $headers, $body ) = $this->_prepare_multipart_request( $request['body'] ); $request['headers'] = $headers; $request['body'] = $body; } if ( $request['method'] === 'GET' ) { unset( $request['body'] ); } return $request; } /** * Checks if a request contains a file resource handle * * @param array $request_snippet * * @return bool */ private function contains_resource( array $request_snippet ) { foreach ( $request_snippet as $part ) { if ( is_resource( $part ) === true || ( is_array( $part ) && $this->contains_resource( $part ) ) ) { return true; } } return false; } private function _prepare_multipart_request( $params ) { $boundary = '----' . microtime(); $header = "Content-Type: multipart/form-data; boundary=$boundary"; $content = self::_add_multipart_contents( $boundary, $params ); $content .= "--$boundary--\r\n"; return array( $header, $content ); } private function _add_multipart_contents( $boundary, $params, $context = array() ) { $initial_context = $context; $content = ''; foreach ( $params as $key => $value ) { $context = $initial_context; $context[] = $key; if ( is_array( $value ) ) { $content .= self::_add_multipart_contents( $boundary, $value, $context ); } else { $pieces = array_slice( $context, 1 ); if ( $pieces ) { $name = "{$context[0]}[" . implode( '][', $pieces ) . ']'; } else { $name = "{$context[0]}"; } $content .= "--$boundary\r\n" . "Content-Disposition: form-data; name=\"$name\""; if ( is_resource( $value ) ) { $filename = self::get_file_name( $params, $key ); $value = stream_get_contents( $value ); $value = $value ?: ''; $content .= "; filename=\"$filename\"\r\n" . "Content-Type: application/octet-stream\r\n\r\n" . gzencode( $value ) . "\r\n"; } else { $content .= "\r\n\r\n$value\r\n"; } } } return $content; } private function get_file_name( $params, $default = 'file' ) { $title = isset( $params['title'] ) ? sanitize_title_with_dashes( strtolower( filter_var( $params['title'], FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH ) ) ) : ''; if ( str_replace( array( '-', '_' ), '', $title ) == '' ) { $title = $default; } $source_language = isset( $params['source_language'] ) ? $params['source_language'] : ''; $target_language = isset( $params['target_language'] ) ? $params['target_language'] : ''; $filename = implode( '-', array_filter( array( $title, $source_language, $target_language, ) ) ); return $filename . '.xliff.gz'; } } translation-proxy/class-wpml-tp-project-user.php 0000755 00000000443 14720342453 0016125 0 ustar 00 <?php abstract class WPML_TP_Project_User { /** @var TranslationProxy_Project $project */ protected $project; /** * WPML_TP_Project_User constructor. * * @param TranslationProxy_Project $project */ public function __construct( &$project ) { $this->project = &$project; } } translation-proxy/lock/wpml-tp-lock.php 0000755 00000001622 14720342453 0014260 0 ustar 00 <?php class WPML_TP_Lock { private $lockable_endpoints = array( '/jobs/{job_id}/xliff.json', ); /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_WP_API $wp_api ) { $this->wp_api = $wp_api; } /** * @param string $url * * @return bool */ public function is_locked( $url ) { return $this->get_locker_reason() && $this->is_lockable( $url ); } /** * @return string|false */ public function get_locker_reason() { if ( 'test' === $this->wp_api->constant( 'WPML_ENVIRONMENT' ) ) { return __( 'The constant WPML_ENVIRONMENT is set to "Test".', 'wpml-translation-management' ); } return false; } /** * @param string $url * * @return bool */ private function is_lockable( $url ) { $endpoint = preg_replace( '#^' . OTG_TRANSLATION_PROXY_URL . '#', '', $url, 1 ); return in_array( $endpoint, $this->lockable_endpoints, true ); } } translation-proxy/lock/wpml-tp-lock-notice.php 0000755 00000002047 14720342453 0015541 0 ustar 00 <?php class WPML_TP_Lock_Notice implements IWPML_Action { const NOTICE_GROUP = 'tp-lock'; const NOTICE_LOCKED = 'locked'; /** @var WPML_TP_Lock $tp_lock */ private $tp_lock; /** @var WPML_Notices $notices */ private $notices; public function __construct( WPML_TP_Lock $tp_lock, WPML_Notices $notices ) { $this->tp_lock = $tp_lock; $this->notices = $notices; } public function add_hooks() { add_action( 'admin_init', array( $this, 'handle_notice' ) ); } public function handle_notice() { $locker_reason = $this->tp_lock->get_locker_reason(); if ( (bool) $locker_reason ) { $text = '<p>' . __( 'Some communications with the translation proxy are locked.', 'wpml-translation-management' ) . '</p>'; $text .= '<p>' . $locker_reason . '</p>'; $notice = $this->notices->create_notice( self::NOTICE_LOCKED, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( 'notice-warning' ); $this->notices->add_notice( $notice ); } else { $this->notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_LOCKED ); } } } translation-proxy/lock/wpml-tp-lock-factory.php 0000755 00000000166 14720342453 0015727 0 ustar 00 <?php class WPML_TP_Lock_Factory { public function create() { return new WPML_TP_Lock( new WPML_WP_API() ); } } translation-proxy/lock/wpml-tp-lock-notice-factory.php 0000755 00000000436 14720342453 0017206 0 ustar 00 <?php class WPML_TP_Lock_Notice_Factory implements IWPML_Backend_Action_Loader { public function create() { $tp_lock_factory = new WPML_TP_Lock_Factory(); $notices = wpml_get_admin_notices(); return new WPML_TP_Lock_Notice( $tp_lock_factory->create(), $notices ); } } translation-proxy/sync-jobs/class-wpml-tp-sync-jobs-status.php 0000755 00000003642 14720342453 0020646 0 ustar 00 <?php class WPML_TM_Sync_Jobs_Status { /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** @var WPML_TP_Jobs_API */ private $tp_api; /** * WPML_TM_Sync_Jobs_Status constructor. * * @param WPML_TM_Jobs_Repository $jobs_repository * @param WPML_TP_Jobs_API $tp_api */ public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Jobs_API $tp_api ) { $this->jobs_repository = $jobs_repository; $this->tp_api = $tp_api; } /** * @return WPML_TM_Jobs_Collection * @throws WPML_TP_API_Exception */ public function sync() { return $this->update_tp_state_of_jobs( $this->jobs_repository->get_collection( new WPML_TM_Jobs_Search_Params( array( 'status' => array( ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ), 'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE, ) ) ) ); } /** * @param WPML_TM_Jobs_Collection $jobs * * @return WPML_TM_Jobs_Collection * @throws WPML_TP_API_Exception */ private function update_tp_state_of_jobs( WPML_TM_Jobs_Collection $jobs ) { $tp_ids = $this->extract_tp_id_from_jobs( $jobs ); if ( $tp_ids ) { $tp_statuses = $this->tp_api->get_jobs_statuses( $tp_ids ); foreach ( $tp_statuses as $job_status ) { $job = $jobs->get_by_tp_id( $job_status->get_tp_id() ); if ( $job ) { $job->set_status( WPML_TP_Job_States::map_tp_state_to_local( $job_status->get_status() ) ); $job->set_ts_status( $job_status->get_ts_status() ); if ( WPML_TP_Job_States::CANCELLED === $job_status->get_status() ) { do_action( 'wpml_tm_canceled_job_notification', $job ); } } } } return $jobs; } /** * @param WPML_TM_Jobs_Collection $jobs * * @return array */ private function extract_tp_id_from_jobs( WPML_TM_Jobs_Collection $jobs ) { $tp_ids = array(); foreach ( $jobs as $job ) { $tp_ids[] = $job->get_tp_id(); } return $tp_ids; } } translation-proxy/sync-jobs/class-wpml-tp-sync-jobs-revision.php 0000755 00000003125 14720342453 0021155 0 ustar 00 <?php class WPML_TM_Sync_Jobs_Revision { /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** @var WPML_TP_Jobs_API */ private $tp_api; /** * WPML_TM_Sync_Jobs_Revision constructor. * * @param WPML_TM_Jobs_Repository $jobs_repository * @param WPML_TP_Jobs_API $tp_api */ public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Jobs_API $tp_api ) { $this->jobs_repository = $jobs_repository; $this->tp_api = $tp_api; } /** * @return WPML_TM_Job_Entity[] * @throws WPML_TP_API_Exception */ public function sync() { $result = array(); $tp_statuses_of_revised_jobs = $this->tp_api->get_revised_jobs(); if ( $tp_statuses_of_revised_jobs ) { $revised_jobs = array(); foreach ( $tp_statuses_of_revised_jobs as $tp_job_status ) { $revised_jobs[ $tp_job_status->get_tp_id() ] = $tp_job_status->get_revision(); } $job_search = new WPML_TM_Jobs_Search_Params( array( 'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE, 'tp_id' => array_keys( $revised_jobs ), ) ); /** @var WPML_TM_Job_Entity $job */ foreach ( $this->jobs_repository->get( $job_search ) as $job ) { if ( isset( $revised_jobs[ $job->get_tp_id() ] ) && $job->get_revision() < $revised_jobs[ $job->get_tp_id() ] ) { $job->set_status( WPML_TP_Job_States::map_tp_state_to_local( WPML_TP_Job_States::TRANSLATION_READY ) ); $job->set_revision( $revised_jobs[ $job->get_tp_id() ] ); $result[] = $job; do_action( 'wpml_tm_revised_job_notification', $job->get_id() ); } } } return $result; } } translation-proxy/sync-jobs/class-wpml-tp-sync-ajax-handler.php 0000755 00000003351 14720342453 0020723 0 ustar 00 <?php class WPML_TP_Sync_Ajax_Handler { const AJAX_ACTION = 'wpml-tp-sync-job-states'; /** @var WPML_TP_Sync_Jobs */ private $tp_sync; /** @var WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up */ private $wpml_tm_last_picked_up; /** * WPML_TP_Sync_Jobs constructor. * * @param WPML_TP_Sync_Jobs $tp_sync * @param WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up */ public function __construct( WPML_TP_Sync_Jobs $tp_sync, WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up ) { $this->tp_sync = $tp_sync; $this->wpml_tm_last_picked_up = $wpml_tm_last_picked_up; } public function add_hooks() { add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'handle' ) ); } public function handle() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'sync-job-states' ) ) { wp_send_json_error( esc_html__( 'Invalid request!' ) ); } try { if ( isset( $_REQUEST['update_last_picked_up'] ) ) { $this->wpml_tm_last_picked_up->set(); } $jobs = $this->tp_sync->sync(); do_action( 'wpml_tm_empty_mail_queue' ); wp_send_json_success( $jobs->map( array( $this, 'map_job_to_result' ) ) ); return true; } catch ( Exception $e ) { wp_send_json_error( $e->getMessage(), 503 ); return false; } } /** * @param WPML_TM_Job_Entity $job * * @return array */ public function map_job_to_result( WPML_TM_Job_Entity $job ) { return array( 'id' => $job->get_id(), 'type' => $job->get_type(), 'status' => $job->get_status(), 'hasCompletedTranslation' => $job->has_completed_translation(), 'needsUpdate' => $job->does_need_update(), ); } } translation-proxy/sync-jobs/wpml-tp-sync-orphan-jobs.php 0000755 00000002126 14720342453 0017503 0 ustar 00 <?php class WPML_TP_Sync_Orphan_Jobs { /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** @var WPML_TP_Sync_Update_Job */ private $update_job; /** * @param WPML_TM_Jobs_Repository $jobs_repository * @param WPML_TP_Sync_Update_Job $update_job */ public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TP_Sync_Update_Job $update_job ) { $this->jobs_repository = $jobs_repository; $this->update_job = $update_job; } /** * @return WPML_TM_Jobs_Collection */ public function cancel_orphans() { $params = new WPML_TM_Jobs_Search_Params( array( 'scope' => WPML_TM_Jobs_Search_Params::SCOPE_REMOTE, 'tp_id' => 0, 'status' => array( ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ), ) ); return $this->jobs_repository->get( $params )->map( array( $this, 'cancel_job' ), true ); } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TM_Job_Entity */ public function cancel_job( WPML_TM_Job_Entity $job ) { $job->set_status( ICL_TM_NOT_TRANSLATED ); return $this->update_job->update_state( $job ); } } translation-proxy/sync-jobs/class-wpml-tp-sync-update-job.php 0000755 00000007024 14720342453 0020420 0 ustar 00 <?php use WPML\FP\Relation; use \WPML\LIB\WP\Post; class WPML_TP_Sync_Update_Job { private $strategies = array( WPML_TM_Job_Entity::POST_TYPE => 'update_post_job', WPML_TM_Job_Entity::STRING_TYPE => 'update_string_job', WPML_TM_Job_Entity::PACKAGE_TYPE => 'update_post_job', WPML_TM_Job_Entity::STRING_BATCH => 'update_post_job', ); /** @var wpdb */ private $wpdb; /** @var SitePress */ private $sitepress; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TM_Job_Entity */ public function update_state( WPML_TM_Job_Entity $job ) { if ( ! array_key_exists( $job->get_type(), $this->strategies ) ) { return $job; } $method = $this->strategies[ $job->get_type() ]; return call_user_func( array( $this, $method ), $job ); } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TM_Job_Entity */ private function update_string_job( WPML_TM_Job_Entity $job ) { if ( $job->get_tp_id() ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_core_status', array( 'status' => $job->get_status(), 'tp_revision' => $job->get_revision(), 'ts_status' => $this->get_ts_status_in_ts_format( $job ), ), array( 'rid' => $job->get_tp_id() ) ); } $data = array( 'status' => $job->get_status(), ); if ( ICL_TM_NOT_TRANSLATED === $job->get_status() ) { $data['translator_id'] = null; } $this->wpdb->update( $this->wpdb->prefix . 'icl_string_translations', $data, array( 'id' => $job->get_id() ) ); icl_update_string_status( $job->get_original_element_id() ); return $job; } /** * @param WPML_TM_Job_Entity $job * * @return WPML_TM_Job_Entity */ private function update_post_job( WPML_TM_Job_Entity $job ) { $job_id = $job->get_id(); if ( $job->get_status() === ICL_TM_NOT_TRANSLATED ) { $prev_status = $this->get_job_prev_status( $job_id ); if ( $prev_status && Relation::propEq( 'needs_update', '1', $prev_status ) ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_translation_status', $prev_status, [ 'rid' => $job_id ] ); $job->set_needs_update( true ); return $job; } } $this->wpdb->update( $this->wpdb->prefix . 'icl_translation_status', array( 'status' => $job->get_status(), 'tp_revision' => $job->get_revision(), 'ts_status' => $this->get_ts_status_in_ts_format( $job ), ), array( 'rid' => $job_id ) ); if ( ICL_TM_NOT_TRANSLATED === $job->get_status() && ( $post_type = Post::getType( $job->get_original_element_id() ) ) ) { $this->sitepress->delete_orphan_element( $job->get_original_element_id(), 'post_' . $post_type, $job->get_target_language() ); } return $job; } private function get_job_prev_status( $job_id ) { $previous_state = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT _prevstate FROM {$this->wpdb->prefix}icl_translation_status WHERE rid=%d LIMIT 1", $job_id ) ); return $previous_state ? unserialize( $previous_state ) : null; } /** * In the db, we store the exact json format that we get from TS. It includes an extra ts_status key * * @param WPML_TM_Job_Entity $job * * @return string */ private function get_ts_status_in_ts_format( WPML_TM_Job_Entity $job ) { $ts_status = $job->get_ts_status(); return $ts_status ? wp_json_encode( array( 'ts_status' => json_decode( (string) $ts_status ) ) ) : null; } } translation-proxy/sync-jobs/wpml-tp-sync-orphan-jobs-factory.php 0000755 00000000437 14720342453 0021153 0 ustar 00 <?php class WPML_TP_Sync_Orphan_Jobs_Factory { /** * @return WPML_TP_Sync_Orphan_Jobs */ public function create() { global $wpdb, $sitepress; return new WPML_TP_Sync_Orphan_Jobs( wpml_tm_get_jobs_repository(), new WPML_TP_Sync_Update_Job( $wpdb, $sitepress ) ); } } translation-proxy/sync-jobs/class-wpml-tp-sync-jobs.php 0000755 00000002174 14720342453 0017324 0 ustar 00 <?php class WPML_TP_Sync_Jobs { /** @var WPML_TM_Sync_Jobs_Status */ private $jobs_status_sync; /** @var WPML_TM_Sync_Jobs_Revision */ private $jobs_revision_sync; /** @var WPML_TP_Sync_Update_Job */ private $update_job; /** * WPML_TP_Sync_Jobs constructor. * * @param WPML_TM_Sync_Jobs_Status $jobs_status_sync * @param WPML_TM_Sync_Jobs_Revision $jobs_revision_sync * @param WPML_TP_Sync_Update_Job $update_job */ public function __construct( WPML_TM_Sync_Jobs_Status $jobs_status_sync, WPML_TM_Sync_Jobs_Revision $jobs_revision_sync, WPML_TP_Sync_Update_Job $update_job ) { $this->jobs_status_sync = $jobs_status_sync; $this->jobs_revision_sync = $jobs_revision_sync; $this->update_job = $update_job; } /** * @return WPML_TM_Jobs_Collection * @throws WPML_TP_API_Exception */ public function sync() { return new WPML_TM_Jobs_Collection( $this->jobs_status_sync ->sync() ->append( $this->jobs_revision_sync->sync() ) ->filter_by_status( array( ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR ), true ) ->map( array( $this->update_job, 'update_state' ) ) ); } } translation-proxy/sync-jobs/class-wpml-tp-sync-installer-wrapper.php 0000755 00000000270 14720342453 0022035 0 ustar 00 <?php class WPML_TM_Sync_Installer_Wrapper { /** * @return bool */ public function is_wpml_registered() { return (bool) WP_Installer::instance()->get_site_key( 'wpml' ); } } translation-proxy/services/AuthorizationFactory.php 0000755 00000000731 14720342453 0017015 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services; use WPML\TM\TranslationProxy\Services\Project\Manager; use function WPML\Container\make; class AuthorizationFactory { /** * @return Authorization * @throws \Auryn\InjectionException */ public function create() { $projectManager = make( Manager::class, [ ':projectApi' => wpml_tm_get_tp_project_api(), ] ); return make( Authorization::class, [ ':projectManager' => $projectManager ] ); } } translation-proxy/services/Authorization.php 0000755 00000003523 14720342453 0015467 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services; use WPML\TM\TranslationProxy\Services\Project\Manager; class Authorization { /** @var Storage */ private $storage; /** @var Manager */ private $projectManager; /** * @param Storage $storage * @param Manager $projectManager */ public function __construct( Storage $storage, Manager $projectManager ) { $this->storage = $storage; $this->projectManager = $projectManager; } /** * @param \stdClass $credentials * * @throws \RuntimeException * @throws \WPML_TP_API_Exception */ public function authorize( \stdClass $credentials ) { $service = $this->getCurrentService(); $service->custom_fields_data = $credentials; $project = $this->projectManager->create( $service ); $this->storage->setCurrentService( $service ); do_action( 'wpml_tm_translation_service_authorized', $service, $project ); } /** * @param \stdClass $credentials * * @throws \WPML_TP_API_Exception */ public function updateCredentials( \stdClass $credentials ) { $service = $this->getCurrentService(); $this->projectManager->updateCredentials( $service, $credentials ); $service->custom_fields_data = $credentials; $this->storage->setCurrentService( $service ); } /** * @throws \RuntimeException */ public function deauthorize() { $service = $this->getCurrentService(); $service->custom_fields_data = null; $this->storage->setCurrentService( $service ); do_action( 'wpml_tp_service_de_authorized', $service ); } /** * @return \stdClass * @throws \RuntimeException */ private function getCurrentService() { $service = $this->storage->getCurrentService(); if ( (bool) $service === false ) { throw new \RuntimeException( 'Tried to authenticate a service, but no service is active!' ); } return $service; } } translation-proxy/services/Storage.php 0000755 00000001406 14720342453 0014231 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services; class Storage { /** @var \SitePress $sitepress */ private $sitepress; /** * @param \SitePress $sitepress */ public function __construct( \SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * Gets the current translation service * * @return bool|\stdClass */ public function getCurrentService() { return $this->sitepress->get_setting( 'translation_service' ); } /** * Saves the input service as the current translation service setting. * * @param \stdClass $service */ public function setCurrentService( \stdClass $service ) { do_action( 'wpml_tm_before_set_translation_service', $service ); $this->sitepress->set_setting( 'translation_service', $service, true ); } } translation-proxy/services/Project/Manager.php 0000755 00000004612 14720342453 0015607 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services\Project; class Manager { /** @var \WPML_TP_Project_API */ private $projectApi; /** @var Storage */ private $projectStorage; /** @var SiteDetails */ private $siteDetails; /** * @param \WPML_TP_Project_API $projectApi * @param Storage $projectStorage * @param SiteDetails $siteDetails */ public function __construct( \WPML_TP_Project_API $projectApi, Storage $projectStorage, SiteDetails $siteDetails ) { $this->projectApi = $projectApi; $this->projectStorage = $projectStorage; $this->siteDetails = $siteDetails; } /** * @param \stdClass $service * * @return Project * @throws \WPML_TP_API_Exception */ public function create( \stdClass $service ) { $project = $this->projectStorage->getByService( $service ) ?: $this->fromTranslationProxy( $service ); $project->extraFields = $this->projectApi->get_extra_fields( $project ); $this->projectStorage->save( $service, $project ); do_action( 'wpml_tp_project_created', $service, $project, $this->projectStorage->getProjects()->toArray() ); return $project; } /** * @param \stdClass $service * @param \stdClass $credentials * * @return Project|null * @throws \WPML_TP_API_Exception */ public function updateCredentials( \stdClass $service, \stdClass $credentials ) { $project = $this->projectStorage->getByService( $service ); if ( ! $project ) { throw new \RuntimeException( 'Project does not exist' ); } $this->projectApi->update_project_credentials( $project, $credentials ); $project->extraFields = $this->projectApi->get_extra_fields( $project ); $this->projectStorage->save( $this->createServiceWithNewCredentials( $service, $credentials ), $project ); return $project; } /** * @param \stdClass $service * * @return Project * @throws \WPML_TP_API_Exception */ private function fromTranslationProxy( \stdClass $service ) { $response = $this->projectApi->create_project( $service, $this->siteDetails ); return Project::fromResponse( $response->project ); } /** * @param \stdClass $service * @param \stdClass $credentials * * @return \stdClass */ private function createServiceWithNewCredentials( \stdClass $service, \stdClass $credentials ) { $updatedService = clone $service; $updatedService->custom_fields_data = $credentials; return $updatedService; } } translation-proxy/services/Project/Storage.php 0000755 00000002430 14720342453 0015635 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services\Project; use WPML\Collect\Support\Collection; class Storage { /** @var \SitePress */ private $sitepress; /** * @param \SitePress $sitepress */ public function __construct( \SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param \stdClass $service * * @return Project|null */ public function getByService( \stdClass $service ) { return $this->getProjects()->get( \TranslationProxy_Project::generate_service_index( $service ) ); } /** * @param \stdClass $service * @param Project $project */ public function save( \stdClass $service, Project $project ) { $index = \TranslationProxy_Project::generate_service_index( $service ); $this->sitepress->set_setting( 'icl_translation_projects', $this->getProjects()->put( $index, $project )->map( function ( Project $project ) { return $project->toArray(); } )->toArray(), true ); } /** * @return Collection */ public function getProjects() { $projects = $this->sitepress->get_setting( 'icl_translation_projects', [] ); if ( ! is_array( $projects ) ) { $projects = []; } return \wpml_collect( $projects )->map( function ( array $rawProject ) { return Project::fromArray( $rawProject ); } ); } } translation-proxy/services/Project/Project.php 0000755 00000002317 14720342453 0015643 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services\Project; class Project { /** @var int */ public $id; /** @var string */ public $accessKey; /** @var string */ public $tsId; /** @var string */ public $tsAccessKey; /** @var \stdClass */ public $extraFields; /** * @return array */ public function toArray() { return [ 'id' => $this->id, 'access_key' => $this->accessKey, 'ts_id' => $this->tsId, 'ts_access_key' => $this->tsAccessKey, 'extra_fields' => $this->extraFields, ]; } /** * @param array $data * * @return Project */ public static function fromArray( array $data ) { $project = new Project(); $project->id = $data['id']; $project->accessKey = $data['access_key']; $project->tsId = $data['ts_id']; $project->tsAccessKey = $data['ts_access_key']; $project->extraFields = $data['extra_fields']; return $project; } public static function fromResponse( \stdClass $response ) { $project = new Project(); $project->id = $response->id; $project->accessKey = $response->accesskey; $project->tsId = $response->ts_id; $project->tsAccessKey = $response->ts_accesskey; return $project; } } translation-proxy/services/Project/SiteDetails.php 0000755 00000001622 14720342453 0016445 0 ustar 00 <?php namespace WPML\TM\TranslationProxy\Services\Project; class SiteDetails { /** @var \SitePress */ private $sitepress; /** * @param \SitePress $sitepress */ public function __construct( \SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @return string */ public function getDeliveryMethod() { return (int) $this->sitepress->get_setting( 'translation_pickup_method' ) === ICL_PRO_TRANSLATION_PICKUP_XMLRPC ? 'xmlrpc' : 'polling'; } /** * @return array */ public function getBlogInfo() { return [ 'url' => get_option( 'siteurl' ), 'name' => get_option( 'blogname' ), 'description' => get_option( 'blogdescription' ), ]; } /** * @return array */ public function getClientData() { $current_user = wp_get_current_user(); return [ 'email' => $current_user->user_email, 'name' => $current_user->display_name, ]; } } translation-proxy/class-wpml-translation-proxy-networking.php 0000755 00000011715 14720342453 0020770 0 ustar 00 <?php class WPML_Translation_Proxy_Networking { const API_VERSION = 1.1; /** @var WP_Http $http */ private $http; /** @var WPML_TP_Lock $tp_lock */ private $tp_lock; public function __construct( WP_Http $http, WPML_TP_Lock $tp_lock ) { $this->http = $http; $this->tp_lock = $tp_lock; } /** * @param string $url * @param array $params * @param string $method * @param bool|true $has_return_value * @param bool|true $json_response * @param bool|true $has_api_response * * @return array|mixed|stdClass|string * @throws WPMLTranslationProxyApiException */ public function send_request( $url, $params = array(), $method = 'GET', $has_return_value = true, $json_response = true, $has_api_response = true ) { if ( $this->tp_lock->is_locked( $url ) ) { throw new WPMLTranslationProxyApiException( 'Communication with translation proxy is not allowed.' ); } if ( ! $url ) { throw new WPMLTranslationProxyApiException( 'Empty target URL given!' ); } $response = null; $method = strtoupper( $method ); if ( $params ) { $url = TranslationProxy_Api::add_parameters_to_url( $url, $params ); if ( 'GET' === $method ) { $url .= '?' . wpml_http_build_query( $params ); } } if ( ! isset( $params['api_version'] ) || ! $params['api_version'] ) { $params['api_version'] = self::API_VERSION; } WPML_TranslationProxy_Com_Log::log_call( $url, $params ); $api_response = $this->call_remote_api( $url, $params, $method, $has_return_value ); if ( $has_return_value ) { if ( ! isset( $api_response['headers'] ) && ! isset( $api_response['headers']['content-type'] ) ) { throw new WPMLTranslationProxyApiException( 'Invalid HTTP response, no content type in header given!' ); } $content_type = $api_response['headers']['content-type']; $api_response = $api_response['body']; $api_response = strpos( $content_type, 'zip' ) !== false ? gzdecode( $api_response ) : $api_response; WPML_TranslationProxy_Com_Log::log_response( $json_response ? $api_response : 'XLIFF received' ); if ( $json_response ) { $response = json_decode( $api_response ); if ( $has_api_response ) { if ( ! $response || ! isset( $response->status->code ) || $response->status->code !== 0 ) { $exception_message = $this->get_exception_message( $url, $method, $params, $response ); if ( isset( $response->status->message ) ) { $exception_message = ''; if ( isset( $response->status->code ) ) { $exception_message = '(' . $response->status->code . ') '; } $exception_message .= $response->status->message; } throw new WPMLTranslationProxyApiException( $exception_message ); } $response = $response->response; } } else { $response = $api_response; } } return $response; } public function get_extra_fields_remote( $project ) { $params = array( 'accesskey' => $project->access_key, 'api_version' => self::API_VERSION, 'project_id' => $project->id, ); return TranslationProxy_Api::proxy_request( '/projects/{project_id}/extra_fields.json', $params ); } /** * @param string $url * @param array $params * @param string $method * @param bool $has_return_value * * @throws \WPMLTranslationProxyApiException * * @return array */ private function call_remote_api( $url, $params, $method, $has_return_value = true ) { $context = $this->filter_request_params( $params, $method ); $response = $this->http->request( $url, $context ); if ( ( $has_return_value && (bool) $response === false ) || is_wp_error( $response ) || ( isset( $response['response']['code'] ) && $response['response']['code'] > 400 ) ) { throw new WPMLTranslationProxyApiException( $this->get_exception_message( $url, $method, $context, $response ) ); } return $response; } private function get_exception_message( $url, $method, $context, $response ) { $sanitized_url = WPML_TranslationProxy_Com_Log::sanitize_url( $url ); $sanitized_context = WPML_TranslationProxy_Com_Log::sanitize_data( $context ); $sanitized_response = WPML_TranslationProxy_Com_Log::sanitize_data( $response ); return 'Cannot communicate with the remote service |' . ' url: ' . '`' . $sanitized_url . '`' . ' method: ' . '`' . $method . '`' . ' param: ' . '`' . wp_json_encode( $sanitized_context ) . '`' . ' response: ' . '`' . wp_json_encode( $sanitized_response ) . '`'; } /** * @param array $params request parameters * @param string $method HTTP request method * * @return array */ private function filter_request_params( $params, $method ) { $request_filter = new WPML_TP_HTTP_Request_Filter(); return $request_filter->build_request_context( array( 'method' => $method, 'body' => $params, 'sslverify' => true, 'timeout' => 60, ) ); } } translation-proxy/log/class-wpml-translationproxy-communication-log.php 0000755 00000007461 14720342453 0022734 0 ustar 00 <?php class WPML_TranslationProxy_Communication_Log { private $keys_to_block; private $sitepress; public function __construct( SitePress $sitepress ) { $this->keys_to_block = array( 'api_token', 'username', 'api_key', 'sitekey', 'accesskey', 'file', ); $this->sitepress = $sitepress; } public function log_call( $url, $params ) { $sanitized_params = $this->sanitize_data( $params ); $sanitized_url = $this->sanitize_url( $url ); $this->add_to_log( 'call - ' . $sanitized_url . ' - ' . json_encode( $sanitized_params ) ); } public function get_keys_to_block() { return $this->keys_to_block; } public function log_response( $response ) { $this->add_to_log( 'response - ' . $response ); } public function log_error( $message ) { $this->add_to_log( 'error - ' . $message ); } public function log_xml_rpc( $data ) { $this->add_to_log( 'xml-rpc - ' . json_encode( $data ) ); } public function get_log() { return get_option( 'wpml_tp_com_log', '' ); } public function clear_log() { $this->save_log( '' ); } public function is_logging_enabled() { return $this->sitepress->get_setting( 'tp-com-logging', true ); } /** * @param string|array|stdClass $params * * @return array|stdClass */ public function sanitize_data( $params ) { $sanitized_params = $params; if ( is_object( $sanitized_params ) ) { $sanitized_params = get_object_vars( $sanitized_params ); } if ( is_array( $sanitized_params ) ) { foreach ( $sanitized_params as $key => $value ) { $sanitized_params[ $key ] = $this->sanitize_data_item( $key, $sanitized_params[ $key ] ); } } return $sanitized_params; } /** * @param string $key * @param string|array|stdClass $item * * @return string|array|stdClass */ private function sanitize_data_item( $key, $item ) { if ( is_array( $item ) || is_object( $item ) ) { $item = $this->sanitize_data( $item ); } elseif ( in_array( $key, $this->get_keys_to_block(), true ) ) { $item = 'UNDISCLOSED'; } elseif ( $this->is_json( $item ) ) { $item = json_encode( $this->sanitize_data_item( $key, json_decode( $item ) ) ); } return $item; } /** * @param $url * * @return mixed */ public function sanitize_url( $url ) { $original_url_parsed = (string) wpml_parse_url( $url, PHP_URL_QUERY ); parse_str( $original_url_parsed, $original_query_vars ); $sanitized_query_vars = $this->sanitize_data( $original_query_vars ); return add_query_arg( $sanitized_query_vars, $url ); } public function set_logging_state( $state ) { $this->sitepress->set_setting( 'tp-com-logging', $state ); $this->sitepress->save_settings(); } public function add_com_log_link() { $url = esc_attr( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=com-log' ); ?> <?php printf( __( 'For retrieving debug information for communication between your%1$s site and the translation system, use the <a href="%2$s">communication log</a> page.', 'wpml-translation-management' ), '<br>', $url ); ?> </p> <?php } private function now() { return date( 'm/d/Y h:i:s a', time() ); } private function add_to_log( $string ) { if ( $this->is_logging_enabled() ) { $max_log_length = 10000; $string = $this->now() . ' - ' . $string; $log = $this->get_log(); $log .= $string; $log .= PHP_EOL; $log_length = strlen( $log ); if ( $log_length > $max_log_length ) { $log = substr( $log, $log_length - $max_log_length ); } $this->save_log( $log ); } } private function save_log( $log ) { update_option( 'wpml_tp_com_log', $log, false ); } /** * @param mixed $item * * @return bool */ private function is_json( $item ) { return is_string( $item ) && json_decode( $item ) && json_last_error() === JSON_ERROR_NONE; } } translation-proxy/api/class-wpml-tp-jobs-api.php 0000755 00000010021 14720342453 0015751 0 ustar 00 <?php use WPML\FP\Obj; class WPML_TP_Jobs_API extends WPML_TP_API { const CHUNK_SIZE = 100; /** * @param int[] $tp_job_ids * * @return WPML_TP_Job_Status[] * @throws WPML_TP_API_Exception */ public function get_jobs_statuses( array $tp_job_ids ) { $this->log( 'Get jobs status', $tp_job_ids ); $chunks = array(); while ( $tp_job_ids ) { $chunk_ids = array_splice( $tp_job_ids, 0, self::CHUNK_SIZE ); $chunks[] = $this->get_chunk_of_job_statuses( $chunk_ids ); } $result = call_user_func_array( 'array_merge', $chunks ); return array_map( array( $this, 'build_job_status' ), $result ); } private function get_chunk_of_job_statuses( $tp_job_ids ) { $request = new WPML_TP_API_Request( '/jobs.json' ); $request->set_params( array( 'filter' => array( 'job_ids' => array_filter( $tp_job_ids, 'is_int' ), 'archived' => 1, ), 'accesskey' => $this->project->get_access_key(), ) ); return $this->client->send_request( $request ); } /** * @param array $cms_ids * @param bool $archived * * @return array|mixed|stdClass|string * @throws WPML_TP_API_Exception */ public function get_jobs_per_cms_ids( array $cms_ids, $archived = false ) { $request = new WPML_TP_API_Request( '/jobs.json' ); $filter = array( 'filter' => array( 'cms_ids' => $cms_ids, ), 'accesskey' => $this->project->get_access_key(), ); if ( $archived ) { $filter['filter']['archived'] = (int) $archived; $request->set_params( $filter ); } return $this->client->send_request( $request ); } /** * @param WPML_TM_Job_Entity $job * @param string $state * @param string $post_url * * @throws WPML_TP_API_Exception */ public function update_job_state( WPML_TM_Job_Entity $job, $state = WPML_TP_Job_States::DELIVERED, $post_url = null ) { $params = array( 'job_id' => $job->get_tp_id(), 'project_id' => $this->project->get_id(), 'accesskey' => $this->project->get_access_key(), 'job' => array( 'state' => $state, ), ); if ( $post_url ) { $params['job']['url'] = $post_url; } $request = new WPML_TP_API_Request( '/jobs/{job_id}.json' ); $request->set_params( $params ); $request->set_method( 'PUT' ); $this->client->send_request( $request ); } private function build_job_status( stdClass $raw_data ) { return new WPML_TP_Job_Status( $raw_data->id, isset( $raw_data->batch->id ) ? $raw_data->batch->id : 0, $raw_data->job_state, isset( $raw_data->translation_revision ) ? $raw_data->translation_revision : 1, $raw_data->ts_status ? new WPML_TM_Job_TS_Status( $raw_data->ts_status->status, $raw_data->ts_status->links ) : null ); } /** * @return WPML_TP_Job_Status[] * @throws WPML_TP_API_Exception */ public function get_revised_jobs() { $this->log( 'Get revised jobs' ); $request = new WPML_TP_API_Request( '/jobs.json' ); $request->set_params( array( 'filter' => array( 'job_state' => WPML_TP_Job_States::TRANSLATION_READY, 'archived' => 0, 'revision_greater_than' => 1, ), 'accesskey' => $this->project->get_access_key(), ) ); $result = $this->client->send_request( $request ); return array_map( array( $this, 'build_job_status' ), $result ); } /** * @param int $tp_job_id * * @return string * @throws WPML_TP_API_Exception When response is incorrect. */ public function get_translated_xliff_download_url( $tp_job_id ) { $request = new WPML_TP_API_Request( '/jobs.json' ); $params = [ 'filter' => [ 'job_ids' => $tp_job_id, ], 'accesskey' => $this->project->get_access_key(), ]; $request->set_params( $params ); $result = $this->client->send_request( $request ); if ( ! $result || ! is_array( $result ) || 0 === count( $result ) || ! is_object( $result[0] ) ) { throw new WPML_TP_API_Exception( 'XLIFF download link could not be fetched for tp_job: ' . $tp_job_id, $request ); } return Obj::propOr( '', 'translated_xliff_download_url', $result[0] ); } } translation-proxy/api/class-wpml-translation-proxy-api-exception.php 0000755 00000000404 14720342453 0022110 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPMLTranslationProxyApiException extends Exception { public function __construct( $message, $code = 0 ) { WPML_TranslationProxy_Com_Log::log_error( $message ); parent::__construct( $message, $code ); } } translation-proxy/api/class-wpml-tm-log.php 0000755 00000001564 14720342453 0015037 0 ustar 00 <?php class WPML_TM_Log implements WPML_TP_API_Log_Interface { const LOG_WP_OPTION = '_wpml_tp_api_Logger'; const LOG_MAX_SIZE = 500; public function log( $action, $data = array() ) { $log_base_data = array( 'timestamp' => false, 'action' => false, ); $log_item = array_merge( $log_base_data, $data ); $log_item['timestamp'] = date( 'Y-m-d H:i:s' ); $log_item['action'] = $action; $log = $this->get_log_data(); $log = array_slice( $log, - ( self::LOG_MAX_SIZE - 1 ) ); $log[] = $log_item; $this->update_log( $log ); } private function update_log( $log ) { return update_option( self::LOG_WP_OPTION, $log, false ); } public function flush_log() { return update_option( self::LOG_WP_OPTION, [], false ); } public function get_log_data() { $log = get_option( self::LOG_WP_OPTION, [] ); return is_array( $log ) ? $log : []; } } translation-proxy/api/class-wpml-tp-job-states.php 0000755 00000002147 14720342453 0016332 0 ustar 00 <?php class WPML_TP_Job_States { const RECEIVED = 'received'; const WAITING_TRANSLATIONS = 'waiting_translation'; const TRANSLATION_READY = 'translation_ready'; const DELIVERED = 'delivered'; const CANCELLED = 'cancelled'; const ANY = 'any'; /** * @return array */ public static function get_possible_states() { return array( self::RECEIVED, self::WAITING_TRANSLATIONS, self::TRANSLATION_READY, self::DELIVERED, self::CANCELLED, self::ANY, ); } /** * @return string */ public static function get_default_state() { return self::WAITING_TRANSLATIONS; } /** * @return array */ public static function get_finished_states() { return array( self::TRANSLATION_READY, self::DELIVERED, self::CANCELLED, ); } public static function map_tp_state_to_local( $tp_state ) { switch ( $tp_state ) { case self::TRANSLATION_READY: case self::DELIVERED: return ICL_TM_TRANSLATION_READY_TO_DOWNLOAD; case self::CANCELLED: return ICL_TM_NOT_TRANSLATED; default: return ICL_TM_IN_PROGRESS; } } } translation-proxy/api/class-wpml-tp-api-client.php 0000755 00000007264 14720342453 0016311 0 ustar 00 <?php use WPML\FP\Str; class WPML_TP_API_Client { /** @var string */ private $proxy_url; /** @var WP_Http $http */ private $http; /** @var WPML_TP_Lock $tp_lock */ private $tp_lock; /** @var WPML_TP_HTTP_Request_Filter */ private $request_filter; public function __construct( $proxy_url, WP_Http $http, WPML_TP_Lock $tp_lock, WPML_TP_HTTP_Request_Filter $request_filter ) { $this->proxy_url = $proxy_url; $this->http = $http; $this->tp_lock = $tp_lock; $this->request_filter = $request_filter; } /** * @param WPML_TP_API_Request $request * @param bool $raw_json_response * * @return array|mixed|stdClass|string * @throws WPML_TP_API_Exception */ public function send_request( WPML_TP_API_Request $request, $raw_json_response = false ) { if ( $this->tp_lock->is_locked( $request->get_url() ) ) { throw new WPML_TP_API_Exception( 'Communication with translation proxy is not allowed.', $request ); } WPML_TranslationProxy_Com_Log::log_call( $request->get_url(), $request->get_params() ); $response = $this->call_remote_api( $request ); if ( ! $response || is_wp_error( $response ) || ( isset( $response['response'] ) && isset( $response['response']['code'] ) && $response['response']['code'] >= 400 ) ) { throw new WPML_TP_API_Exception( 'Communication error', $request, $response ); } if ( isset( $response['headers'] ) && isset( $response['headers']['content-type'] ) ) { $content_type = $response['headers']['content-type']; $response = $response['body']; if ( strpos( $content_type, 'zip' ) !== false ) { $response = gzdecode( $response ); } else { WPML_TranslationProxy_Com_Log::log_response( $response ); } $json_response = json_decode( $response ); if ( $json_response ) { if ( $raw_json_response ) { $response = $json_response; } else { $response = $this->handle_json_response( $request, $json_response ); } } } return $response; } /** * @param WPML_TP_API_Request $request * * @return null|WP_Error|string */ private function call_remote_api( WPML_TP_API_Request $request ) { $context = $this->filter_request_params( $request->get_params(), $request->get_method() ); $url = $request->get_url(); if ( ! Str::startsWith( 'http://', $url ) && ! Str::startsWith( 'https://', $url ) ) { $url = $this->proxy_url . $url; } return $this->http->request( $url, $context ); } /** * @param array $params request parameters * @param string $method HTTP request method * * @return array */ private function filter_request_params( $params, $method ) { return $this->request_filter->build_request_context( array( 'method' => $method, 'body' => $params, 'sslverify' => true, 'timeout' => 60, ) ); } /** * @param WPML_TP_API_Request $request * @param stdClass $response * * @return mixed * @throws WPML_TP_API_Exception */ private function handle_json_response( WPML_TP_API_Request $request, $response ) { if ( $request->has_api_response() ) { if ( ! isset( $response->status->code ) || $response->status->code !== 0 ) { throw new WPML_TP_API_Exception( $this->generate_error_message_from_status_field( $response ), $request, $response ); } $response = $response->response; } return $response; } private function generate_error_message_from_status_field( $response ) { $message = ''; if ( isset( $response->status->message ) ) { if ( isset( $response->status->code ) ) { $message = '(' . $response->status->code . ') '; } $message .= $response->status->message; } else { $message = 'Unknown error'; } return $message; } } translation-proxy/api/class-wpml-tp-job-status.php 0000755 00000002470 14720342453 0016351 0 ustar 00 <?php class WPML_TP_Job_Status { /** @var int */ private $tp_id; /** @var int */ private $batch_id; /** @var string */ private $status; /** @var int */ private $revision; /** @var WPML_TM_Job_TS_Status|null */ private $ts_status; /** * @param int $tp_id * @param int $batch_id * @param string $state * @param WPML_TM_Job_TS_Status|null $ts_status * @param int $revision */ public function __construct( $tp_id, $batch_id, $state, $revision = 1, $ts_status = null ) { $this->tp_id = (int) $tp_id; $this->batch_id = (int) $batch_id; if ( ! in_array( $state, WPML_TP_Job_States::get_possible_states(), true ) ) { $state = 'any'; } $this->status = $state; $this->revision = (int) $revision; $this->ts_status = $ts_status; } /** * @return int */ public function get_tp_id() { return $this->tp_id; } /** * @return int */ public function get_batch_id() { return $this->batch_id; } /** * @return string */ public function get_status() { return $this->status; } /** * @return int */ public function get_revision() { return $this->revision; } /** * @return WPML_TM_Job_TS_Status|null */ public function get_ts_status() { return $this->ts_status; } } translation-proxy/api/class-wpml-tp-api-request.php 0000755 00000006773 14720342453 0016527 0 ustar 00 <?php use WPML\FP\Obj; use WPML\FP\Str; class WPML_TP_API_Request { const API_VERSION = 1.1; /** @var string */ private $url; /** @var array */ private $params = array( 'api_version' => self::API_VERSION ); /** @var string */ private $method = 'GET'; /** @var bool */ private $has_api_response = true; /** * @param string $url */ public function __construct( $url ) { if ( empty( $url ) ) { throw new InvalidArgumentException( 'Url cannot be empty' ); } // If we get absolute url(like XLIFF download url from TP) it can already contain // get parameters in the query string, so we should parse them correctly in such case. if ( Str::startsWith( 'http://', $url ) || Str::startsWith( 'https://', $url ) ) { $urlParts = wp_parse_url( $url ); if ( Obj::has( 'query', $urlParts ) && is_string( $urlParts['query'] ) ) { $params = explode( '&', $urlParts['query'] ); $params = array_reduce( $params, function( $params, $param ) { $paramParts = explode( '=', $param ); $params[ $paramParts[0] ] = $paramParts[1]; return $params; }, [] ); $this->params = array_merge( $this->params, $params ); $this->url = explode( '?', $url )[0]; } else { $this->url = $url; } } else { // This is the default case. $this->url = $url; } } /** * @param array $params */ public function set_params( array $params ) { $this->params = array_merge( $this->params, $params ); } /** * @param string $method */ public function set_method( $method ) { if ( ! in_array( $method, array( 'GET', 'POST', 'PUT', 'DELETE', 'HEAD' ), true ) ) { throw new InvalidArgumentException( 'HTTP request method has invalid value' ); } $this->method = $method; } /** * @param bool $has_api_response */ public function set_has_api_response( $has_api_response ) { $this->has_api_response = (bool) $has_api_response; } /** * @return string */ public function get_url() { $url = $this->url; if ( $this->get_params() ) { list( $url, $params_used_in_path ) = $this->add_parameters_to_path( $url, $this->get_params() ); if ( 'GET' === $this->get_method() ) { $url = $this->add_query_parameters( $params_used_in_path, $url ); } } return $url; } /** * @return array */ public function get_params() { return $this->params; } /** * @return string */ public function get_method() { return $this->method; } /** * @return bool */ public function has_api_response() { return $this->has_api_response; } private function add_parameters_to_path( $url, array $params ) { $used_params = array(); if ( preg_match_all( '/\{.+?\}/', $url, $symbs ) ) { foreach ( $symbs[0] as $symb ) { $without_braces = preg_replace( '/\{|\}/', '', $symb ); if ( preg_match_all( '/\w+/', $without_braces, $indexes ) ) { foreach ( $indexes[0] as $index ) { if ( isset( $params[ $index ] ) ) { $used_params[] = $index; $value = $params[ $index ]; $url = preg_replace( preg_quote( "/$symb/" ), $value, $url ); } } } } } return array( $url, $used_params ); } /** * @param $params_used_in_path * @param $url * * @return string */ private function add_query_parameters( $params_used_in_path, $url ) { $url .= '?' . preg_replace( '/\%5B\d+\%5D/', '%5B%5D', wpml_http_build_query( array_diff_key( $this->get_params(), array_fill_keys( $params_used_in_path, 1 ) ) ) ); return $url; } } translation-proxy/api/class-wpml-tp-api-exception.php 0000755 00000001664 14720342453 0017027 0 ustar 00 <?php class WPML_TP_API_Exception extends Exception { public function __construct( $message, WPML_TP_API_Request $request = null, $response = null ) { if ( $request ) { $message .= ' ' . $this->get_exception_message( $request->get_url(), $request->get_method(), $request->get_params(), $response ); } parent::__construct( $message ); } private function get_exception_message( $url, $method, $params, $response ) { return 'Details: |' . ' url: ' . '`' . $url . '`' . ' method: ' . '`' . $method . '`' . ' param: ' . '`' . json_encode( $this->filter_params( $params ) ) . '`' . ' response: ' . '`' . json_encode( $response ) . '`'; } /** * @param array $params * * @return array mixed */ private function filter_params( $params ) { return wpml_collect( $params )->forget( 'accesskey' )->toArray(); } } translation-proxy/api/class-wpml-tp-services.php 0000755 00000000753 14720342453 0016103 0 ustar 00 <?php class WPML_TP_Services { public function get_current_project() { return TranslationProxy::get_current_project(); } public function get_current_service() { return TranslationProxy::get_current_service(); } /** * @param $service_id * @param bool $custom_fields * * @throws WPMLTranslationProxyApiException */ public function select_service( $service_id, $custom_fields = false ) { TranslationProxy::select_service( $service_id, $custom_fields ); } } translation-proxy/api/class-wpml-tp-project-api.php 0000755 00000005517 14720342453 0016500 0 ustar 00 <?php use WPML\TM\TranslationProxy\Services\Project\Project; use WPML\TM\TranslationProxy\Services\Project\SiteDetails; class WPML_TP_Project_API extends WPML_TP_API { const API_VERSION = 1.1; const PROJECTS_ENDPOINT = '/projects.json'; /** * @throws WPML_TP_API_Exception */ public function refresh_language_pairs() { $this->log( 'Refresh language pairs -> Request sent' ); $request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT ); $request->set_method( 'PUT' ); $request->set_params( [ 'project' => [ 'refresh_language_pairs' => 1 ], 'refresh_language_pairs' => 1, 'project_id' => $this->project->get_id(), 'accesskey' => $this->project->get_access_key(), ] ); $this->client->send_request( $request ); } /** * @param stdClass $service * @param SiteDetails $site_details * * @return stdClass * @throws WPML_TP_API_Exception */ public function create_project( \stdClass $service, SiteDetails $site_details ) { $project_data = array_merge( $site_details->getBlogInfo(), [ 'delivery_method' => $site_details->getDeliveryMethod(), 'sitekey' => WP_Installer_API::get_site_key( 'wpml' ), 'client_external_id' => WP_Installer_API::get_ts_client_id(), ] ); $params = [ 'api_version' => self::API_VERSION, 'service' => [ 'id' => $service->id ], 'project' => $project_data, 'custom_fields' => $service->custom_fields_data, 'client' => $site_details->getClientData(), 'linked_by' => \TranslationProxy::get_service_linked_by_suid( $service->suid ), ]; $request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT ); $request->set_method( 'POST' ); $request->set_params( $params ); return $this->client->send_request( $request ); } /** * @param Project $project * @param \stdClass $credentials * * @throws WPML_TP_API_Exception */ public function update_project_credentials( Project $project, \stdClass $credentials ) { $request = new WPML_TP_API_Request( self::PROJECTS_ENDPOINT ); $request->set_method( 'PUT' ); $request->set_params( [ 'api_version' => self::API_VERSION, 'accesskey' => $project->accessKey, 'project' => [ 'custom_fields' => (array) $credentials, ], ] ); $this->client->send_request( $request ); } /** * @param Project $project * * @return array|mixed|stdClass|string * @throws WPML_TP_API_Exception */ public function get_extra_fields( Project $project ) { $params = [ 'accesskey' => $project->accessKey, 'api_version' => self::API_VERSION, 'project_id' => $project->id, ]; $request = new WPML_TP_API_Request( '/projects/{project_id}/extra_fields.json' ); $request->set_method( 'GET' ); $request->set_params( $params ); return $this->client->send_request( $request ); } } translation-proxy/api/class-wpml-tp-xliff-api.php 0000755 00000003324 14720342453 0016134 0 ustar 00 <?php use WPML\FP\Obj; class WPML_TP_XLIFF_API extends WPML_TP_API { /** @var WPML_TP_Xliff_Parser */ private $xliff_parser; /** @var WPML_TP_Jobs_API */ private $jobs_api; public function __construct( WPML_TP_API_Client $client, WPML_TP_Project $project, WPML_TP_API_Log_Interface $logger, WPML_TP_Xliff_Parser $xliff_parser, WPML_TP_Jobs_API $jobs_api ) { parent::__construct( $client, $project, $logger ); $this->xliff_parser = $xliff_parser; $this->jobs_api = $jobs_api; } /** * @param int $tp_job_id * @param bool $parse * * @return WPML_TP_Translation_Collection|string * @throws WPML_TP_API_Exception */ public function get_remote_translations( $tp_job_id, $parse = true ) { try { $download_url = $this->jobs_api->get_translated_xliff_download_url( $tp_job_id ); } catch ( \WPML_TP_API_Exception $e ) { // Use the old fashioned way if the url cannot be retrieved via the Translation Proxy API $download_url = '/jobs/{job_id}/xliff.json'; } $request = new WPML_TP_API_Request( $download_url ); $request->set_params( array( 'job_id' => $tp_job_id, 'accesskey' => $this->project->get_access_key(), ) ); $result = $this->client->send_request( $request ); if ( empty( $result ) || false === strpos( $result, 'xliff' ) ) { throw new WPML_TP_API_Exception( 'XLIFF file could not be fetched for tp_job: ' . $tp_job_id, $request ); } $result = apply_filters( 'wpml_tm_data_from_pro_translation', $result ); if ( ! $parse ) { return $result; } $xliff = @simplexml_load_string( $result ); if ( ! $xliff ) { throw new WPML_TP_API_Exception( 'XLIFF file could not be parsed.' ); } return $this->xliff_parser->parse( $xliff ); } } translation-proxy/api/class-wpml-tp-xliff-parser.php 0000755 00000003606 14720342453 0016662 0 ustar 00 <?php class WPML_TP_Xliff_Parser { /** @var WPML_TM_Validate_HTML $validate_html */ private $validate_html; /** * WPML_TP_Xliff_Parser constructor. * * @param WPML_TM_Validate_HTML $validate_html */ public function __construct( WPML_TM_Validate_HTML $validate_html ) { $this->validate_html = $validate_html; } /** * @param SimpleXMLElement $xliff * * @return WPML_TP_Translation_Collection */ public function parse( SimpleXMLElement $xliff ) { $source_lang = (string) $xliff->file->attributes()->{'source-language'}; $target_lang = (string) $xliff->file->attributes()->{'target-language'}; $translations = array(); foreach ( $xliff->file->body->children() as $node ) { $source = $this->get_cdata_value( $node, 'source' ); $target = $this->get_cdata_value( $node, 'target' ); $sourceRestored = $this->validate_html->restore_html( $source ); $targetRestored = $this->validate_html->restore_html( $target ); $translations[] = new WPML_TP_Translation( (string) $node->attributes()->id, ( false !== $sourceRestored ) ? $sourceRestored : $source, ( false !== $targetRestored ) ? $targetRestored : $target ); } return new WPML_TP_Translation_Collection( $translations, $source_lang, $target_lang ); } /** * @param SimpleXMLElement $xliff_node * @param string $field * * @return string */ protected function get_cdata_value( SimpleXMLElement $xliff_node, $field ) { $value = ''; if ( isset( $xliff_node->$field->mrk ) ) { $value = (string) $xliff_node->$field->mrk; } elseif ( isset( $xliff_node->$field ) ) { $value = (string) $xliff_node->$field; } return self::restore_new_line( $value ); } /** * @param string $string * * @return string */ public static function restore_new_line( $string ) { return preg_replace( '/<br class="xliff-newline"\s*\/>/i', "\n", $string ); } } translation-proxy/api/class-wpml-tp-api-log-interface.php 0000755 00000000143 14720342453 0017537 0 ustar 00 <?php interface WPML_TP_API_Log_Interface { public function log( $action, $data = array() ); } translation-proxy/api/class-wpml-tp-batch-sync-api.php 0000755 00000002412 14720342453 0017054 0 ustar 00 <?php class WPML_TP_Batch_Sync_API extends WPML_TP_API { const INIT_SYNC = '/batches/sync.json'; const CHECK_STATUS = '/batches/sync/status.json'; /** * @param array $batch_ids * * @return int[] * @throws WPML_TP_API_Exception */ public function init_synchronization( array $batch_ids ) { $request = new WPML_TP_API_Request( self::INIT_SYNC ); $request->set_params( array( 'batch_id' => $batch_ids, 'accesskey' => $this->project->get_access_key(), ) ); return $this->handle_response( $request ); } /** * @return int[] * @throws WPML_TP_API_Exception */ public function check_progress() { $request = new WPML_TP_API_Request( self::CHECK_STATUS ); $request->set_params( array( 'accesskey' => $this->project->get_access_key() ) ); return $this->handle_response( $request ); } /** * @param WPML_TP_API_Request $request * * @return array * @throws WPML_TP_API_Exception */ private function handle_response( WPML_TP_API_Request $request ) { $result = $this->client->send_request( $request, true ); if ( empty( $result ) || ! isset( $result->queued_batches ) ) { throw new WPML_TP_API_Exception( 'Batch synchronization could not be initialized' ); } return array_map( 'intval', $result->queued_batches ); } } translation-proxy/api/class-wpml-tp-api.php 0000755 00000001066 14720342453 0015027 0 ustar 00 <?php abstract class WPML_TP_API { /** @var WPML_TP_API_Client */ protected $client; /** @var WPML_TP_Project */ protected $project; /** @var WPML_TP_API_Log_Interface */ protected $logger; public function __construct( WPML_TP_API_Client $client, WPML_TP_Project $project, WPML_TP_API_Log_Interface $logger = null ) { $this->client = $client; $this->project = $project; $this->logger = $logger; } protected function log( $action, array $params = array() ) { if ( null !== $this->logger ) { $this->logger->log( $action, $params ); } } } translation-proxy/class-wpml-translationproxy-com-log.php 0000755 00000003527 14720342453 0020063 0 ustar 00 <?php class WPML_TranslationProxy_Com_Log { private static $wrapped_class; /** * @return WPML_TranslationProxy_Communication_Log */ private static function get_wrapped_class_instance() { if ( null === self::$wrapped_class ) { global $sitepress; self::$wrapped_class = new WPML_TranslationProxy_Communication_Log( $sitepress ); } return self::$wrapped_class; } public static function log_call( $url, $params ) { self::get_wrapped_class_instance()->log_call( $url, $params ); } public static function get_keys_to_block() { return self::get_wrapped_class_instance()->get_keys_to_block(); } public static function log_response( $response ) { self::get_wrapped_class_instance()->log_response( $response ); } public static function log_error( $message ) { self::get_wrapped_class_instance()->log_error( $message ); } public static function log_xml_rpc( $data ) { self::get_wrapped_class_instance()->log_xml_rpc( $data ); } public static function get_log() { return self::get_wrapped_class_instance()->get_log(); } public static function clear_log() { self::get_wrapped_class_instance()->clear_log(); } public static function is_logging_enabled() { return self::get_wrapped_class_instance()->is_logging_enabled(); } /** * @param string|array|stdClass $params * * @return array|stdClass */ public static function sanitize_data( $params ) { return self::get_wrapped_class_instance()->sanitize_data( $params ); } /** * @param $url * * @return mixed */ public static function sanitize_url( $url ) { return self::get_wrapped_class_instance()->sanitize_url( $url ); } public static function set_logging_state( $state ) { self::get_wrapped_class_instance()->set_logging_state( $state ); } public static function add_com_log_link() { self::get_wrapped_class_instance()->add_com_log_link(); } } translation-proxy/models/wpml-tp-extra-field.php 0000755 00000000335 14720342453 0016067 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TP_Extra_Field { /** @var string */ public $type = 'text'; /** @var string */ public $label; /** @var string */ public $name; /** @var array */ public $items; } translation-proxy/class-wpml-update-pickup-method.php 0000755 00000001522 14720342453 0017112 0 ustar 00 <?php class WPML_Update_PickUp_Method { private $sitepress; public function __construct( $sitepress ) { $this->sitepress = $sitepress; } public function update_pickup_method( $data, $project = false ) { $method = isset( $data['icl_translation_pickup_method'] ) ? intval( $data['icl_translation_pickup_method'] ) : null; $iclsettings['translation_pickup_method'] = $method; $response = 'ok'; try { if ( $project ) { $project->set_delivery_method( ICL_PRO_TRANSLATION_PICKUP_XMLRPC == $method ? 'xmlrpc' : 'polling' ); $this->sitepress->save_settings( $iclsettings ); } elseif ( ICL_PRO_TRANSLATION_PICKUP_XMLRPC == $method ) { $response = 'no-ts'; } } catch ( RuntimeException $e ) { $response = 'cant-update'; } return $response; } } menu/class-wpml-tm-admin-sections.php 0000755 00000010006 14720342453 0013640 0 ustar 00 <?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 ); } } menu/translation-method/TranslationMethodSettings.php 0000755 00000011407 14720342453 0017227 0 ustar 00 <?php namespace WPML\TM\Menu\TranslationMethod; use WPML\API\PostTypes; use WPML\API\Settings; use WPML\DocPage; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\LIB\WP\User; use WPML\TranslationRoles\UI\Initializer as TranslationRolesInitializer; use function WPML\Container\make; 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\Core\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']); /** @var Jobs $jobs */ $jobs = make( Jobs::class ); return [ 'name' => 'wpml_translation_method', 'data' => [ 'mode' => self::getModeSettingsData(), 'languages' => TranslationRolesInitializer::getLanguagesData(), 'translateEverything' => Option::shouldTranslateEverything(), 'reviewMode' => Option::getReviewMode(), 'endpoints' => Lst::concat( [ 'setTranslateEverything' => SetTranslateEverything::class, 'untranslatedCount' => UntranslatedCount::class, ], TranslationRolesInitializer::getEndPoints() ), 'urls' => [ 'tmDashboard' => UIPage::getTMDashboard(), 'translateAutomaticallyDoc' => DocPage::getTranslateAutomatically(), 'translatorsTabLink' => UIPage::getTMTranslators(), ], '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' => $jobs->hasAnyToSync(), 'isTMAllowed' => \WPML\Setup\Option::isTMAllowed(), 'isClassicEditor' => Lst::includes( $editor, [ (string) ICL_TM_TMETHOD_EDITOR, (string) ICL_TM_TMETHOD_MANUAL ] ), 'translationRoles' => TranslationRolesInitializer::getTranslationData( null, false ), ], ]; } /** * @return array */ public static function getModeSettingsData() { $defaultServiceName = self::getDefaultTranslationServiceName(); Option::setDefaultTranslationMode( ! empty( $defaultServiceName ) ); $translationMethod = null; // User selected translation method. $userSelectedTranslationMethod = Option::shouldTranslateEverything( 'unknown' ); if ( true === $userSelectedTranslationMethod ) { // User selected Translate Everything. $translationMethod = 'automatic'; } elseif ( false === $userSelectedTranslationMethod ) { // User selected Translate Some. $translationMethod = 'manual'; } else { // No user selection. if ( ! empty( $defaultServiceName ) ) { // Pre-select "Translate Some" if a Translation Service is defined. $translationMethod = 'manual'; } } return [ 'whoModes' => Option::getTranslationMode(), 'defaultServiceName' => $defaultServiceName, 'method' => $translationMethod, 'reviewMode' => Option::getReviewMode(), 'isTMAllowed' => true, ]; } public static function render() { echo '<div id="translation-method-settings"></div>'; } /** * Get the actual service name, or empty string if there's no default service. * * @return string */ private static function getDefaultTranslationServiceName() { return Maybe::fromNullable( \TranslationProxy::get_tp_default_suid() ) ->map( [ \TranslationProxy_Service::class, 'get_service_by_suid'] ) ->map( Obj::prop('name') ) ->getOrElse(''); } } menu/iwpml-tm-admin-section-factory.php 0000755 00000000313 14720342453 0014170 0 ustar 00 <?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(); } menu/translation-basket/sitepress-table-basket.class.php 0000755 00000037237 14720342453 0017532 0 ustar 00 <?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' ), ICL_SITEPRESS_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 ), 'nonce' => wp_create_nonce( 'basket_extra_fields_refresh' ), ) ); wp_enqueue_style( 'wpml-tm-jquery-ui-datepicker', WPML_TM_URL . '/res/css/jquery-ui/datepicker.css', array( 'wpml-tooltip' ), ICL_SITEPRESS_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 ]; case 'type': return $this->get_post_type_label( $item[ $column_name ], $item ); case 'status': return $this->get_post_status_label( $item[ $column_name ] ); case 'words': return $item[ $column_name ]; 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; 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 } } menu/translation-basket/Utility.php 0000755 00000003764 14720342453 0013512 0 ustar 00 <?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 ( is_array( $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() ); } } menu/translation-basket/class-wpml-tm-translate-independently.php 0000755 00000006531 14720342453 0021375 0 ustar 00 <?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' ) ); } } menu/translation-basket/wpml-basket-tab-ajax.class.php 0000755 00000021010 14720342453 0017046 0 ustar 00 <?php class WPML_Basket_Tab_Ajax { /** @var TranslationProxy_Project|false $project */ private $project; /** @var WPML_Translation_Proxy_Basket_Networking $networking */ private $networking; /** @var WPML_Translation_Basket $basket */ private $basket; /** * @param TranslationProxy_Project|false $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 ), ); } } menu/translation-basket/Strings.php 0000755 00000017004 14720342453 0013470 0 ustar 00 <?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>'; } } menu/tp-polling/class-wpml-tm-polling-box.php 0000755 00000000426 14720342453 0015247 0 ustar 00 <?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>'; } } menu/tp-polling/wpml-tm-last-picked-up.php 0000755 00000002010 14720342453 0014523 0 ustar 00 <?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 ); } } menu/translation-notifications/class-wpml-tm-emails-settings-factory.php 0000755 00000001320 14720342453 0022704 0 ustar 00 <?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 ); } } menu/translation-notifications/class-wpml-tm-emails-settings.php 0000755 00000010155 14720342453 0021245 0 ustar 00 <?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 ); } } menu/mcsetup/class-wpml-tm-mcs-term-custom-field-settings-menu.php 0000755 00000002132 14720342453 0021324 0 ustar 00 <?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; } } menu/mcsetup/class-wpml-translate-link-targets-ui.php 0000755 00000004613 14720342453 0017006 0 ustar 00 <?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' ); $scanning_message = __( 'Scanning now, please wait...', 'sitepress' ); $error_message = __( 'Error! Reload the page and try again.', 'sitepress' ); if ( defined( 'WPML_ST_VERSION' ) ) { $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' ), 'string-message' => esc_attr__( 'Processing strings... %1$s of %2$s done.', 'wpml-translation-management' ), 'complete-message' => esc_attr( $complete_message ), 'scanning-message' => esc_attr( $scanning_message ), 'error-message' => esc_attr( $error_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; } } menu/mcsetup/class-wpml-tm-options-ajax.php 0000755 00000003412 14720342453 0015022 0 ustar 00 <?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 ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( array_key_exists( 'document_status_sync', $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $settings['translated_document_status_sync'] = filter_var( $_POST['document_status_sync'], 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; } } menu/mcsetup/class-wpml-tm-mcs-search-factory.php 0000755 00000000702 14720342453 0016077 0 ustar 00 <?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 ); } } menu/mcsetup/CfMetaBoxOption.php 0000755 00000000755 14720342453 0012720 0 ustar 00 <?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 ); } } menu/mcsetup/class-wpml-tm-mcs-pagination-render-factory.php 0000755 00000001732 14720342453 0020244 0 ustar 00 <?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 ); } } menu/mcsetup/class-wpml-tm-pickup-mode-ajax.php 0000755 00000003577 14720342453 0015560 0 ustar 00 <?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; } } menu/mcsetup/class-wpml-tm-mcs-pagination-render.php 0000755 00000007631 14720342453 0016603 0 ustar 00 <?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; } } menu/mcsetup/class-wpml-tm-mcs-post-custom-field-settings-menu.php 0000755 00000002166 14720342453 0021351 0 ustar 00 <?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; } } menu/mcsetup/class-wpml-tm-mcs-pagination-ajax.php 0000755 00000004075 14720342453 0016246 0 ustar 00 <?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' ) ) { $page = intval( $_POST['paged'] ); $args = array( 'items_per_page' => intval( $_POST['items_per_page'] ), 'page' => $page, 'highest_page_loaded' => isset( $_POST['highest_page_loaded'] ) ? intval( $_POST['highest_page_loaded'] ) : $page, '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' ), ) ); } } menu/mcsetup/class-wpml-tm-mcs-ate.php 0000755 00000006374 14720342453 0013751 0 ustar 00 <?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(), ); } } menu/mcsetup/class-wpml-tm-mcs-search-render.php 0000755 00000002125 14720342453 0015710 0 ustar 00 <?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 ); } } menu/mcsetup/class-wpml-tm-mcs-custom-field-settings-menu.php 0000755 00000025231 14720342453 0020364 0 ustar 00 <?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 bool $has_more If there are more fields to load. */ private $has_more; /** @var bool If all fields should be loaded. */ private $load_all_fields = false; /** @var int $highest_page_loaded */ private $highest_page_loaded; /** @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, 'highest_page_loaded' => 1, ), $args ); $this->load_all_fields = (int) $args['page'] < 0; $this->highest_page_loaded = (int) $args['highest_page_loaded']; // Fetch one more item than the wanted items per page. $this->custom_fields_keys = $this->get_query()->get( array_merge( $args, [ 'items_per_page' => $args['items_per_page'] + 1 ] ) ); // Check if we have more items to load. $this->has_more = $this->load_all_fields ? false : count( $this->custom_fields_keys ) > $args['items_per_page']; if ( $this->has_more ) { // Remove the extra loaded item as it should not be displayed. array_pop( $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->get_html_disabled(); ?> <div class="wpml-flex-table-row"> <div class="wpml-flex-table-cell name"> <?php $override = false; /** * This filter hook give the ability to override the * default custom field lock rendering. * * @since 4.6.0 * * @param bool $override * @param WPML_Custom_Field_Setting $setting */ if ( ! apply_filters( 'wpml_custom_field_settings_override_lock_render', $override, $setting ) ) { $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 ); if ( $this->load_all_fields ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $pagination->create( count( $this->custom_fields_keys ), $current_page )->render(); return; } $current_fields_count = count( $this->custom_fields_keys ); $total_items_so_far = 1 === $current_page && $current_fields_count < $items_per_page ? $current_fields_count : $items_per_page * $this->highest_page_loaded + ( $this->has_more ? 1 : 0 ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $pagination->create( $total_items_so_far, $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; } } menu/mcsetup/class-wpml-tm-mcs-pagination-ajax-factory.php 0000755 00000000541 14720342453 0017705 0 ustar 00 <?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() ); } } menu/mcsetup/class-wpml-tm-mcs-custom-field-settings-menu-factory.php 0000755 00000003152 14720342453 0022027 0 ustar 00 <?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; } } menu/mcsetup/class-wpml-tm-mcs-ate-strings.php 0000755 00000010627 14720342453 0015434 0 ustar 00 <?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' ); } } menu/mcsetup/class-wpml-tm-mcs-section-ui.php 0000755 00000002021 14720342453 0015240 0 ustar 00 <?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(); } menu/translation-roles/class-wpml-tm-translation-roles-section.php 0000755 00000004055 14720342453 0021534 0 ustar 00 <?php use WPML\TranslationRoles\UI\Initializer; use WPML\TM\Menu\TranslationServices\Section; use WPML\TM\Menu\TranslationMethod\TranslationMethodSettings; use WPML\FP\Relation; use WPML\LIB\WP\User; 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 [ User::CAP_MANAGE_TRANSLATIONS, User::CAP_ADMINISTRATOR ]; } /** * 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(); } } menu/translation-roles/class-wpml-tm-translation-roles-section-factory.php 0000755 00000000542 14720342453 0023176 0 ustar 00 <?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() ); } } menu/translation-roles/RoleValidator.php 0000755 00000001511 14720342453 0014455 0 ustar 00 <?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; } } menu/iwpml-tm-admin-section.php 0000755 00000002025 14720342453 0012525 0 ustar 00 <?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(); } menu/translation-editor/class-wpml-translation-editor-header.php 0000755 00000001371 14720342453 0021204 0 ustar 00 <?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; } } menu/translation-editor/class-wpml-translation-editor.php 0000755 00000012353 14720342453 0017760 0 ustar 00 <?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() . '&nonce=' . wp_create_nonce( 'save_translation' ) ), '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>'; } } menu/translation-editor/class-wpml-tm-field-type-sanitizer.php 0000755 00000001303 14720342453 0020615 0 ustar 00 <?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; } } } menu/translation-editor/class-wpml-editor-ui-job.php 0000755 00000004723 14720342453 0016611 0 ustar 00 <?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 ) { } } menu/translation-editor/fields/model/class-wpml-editor-ui-field-image.php 0000755 00000002411 14720342453 0022540 0 ustar 00 <?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; } } menu/translation-editor/fields/model/class-wpml-editor-ui-field-section.php 0000755 00000001014 14720342453 0023120 0 ustar 00 <?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; } } menu/translation-editor/fields/model/class-wpml-editor-ui-field-group.php 0000755 00000000677 14720342453 0022626 0 ustar 00 <?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; } } menu/translation-editor/fields/model/class-wpml-editor-ui-wysiwyg-field.php 0000755 00000000727 14720342453 0023210 0 ustar 00 <?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 ); } } menu/translation-editor/fields/model/class-wpml-editor-ui-fields.php 0000755 00000001162 14720342453 0021645 0 ustar 00 <?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; } } menu/translation-editor/fields/model/class-wpml-editor-ui-single-line-field.php 0000755 00000000732 14720342453 0023670 0 ustar 00 <?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 ); } } menu/translation-editor/fields/model/class-wpml-editor-ui-field.php 0000755 00000002242 14720342453 0021462 0 ustar 00 <?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; } } menu/translation-editor/fields/model/class-wpml-editor-ui-textarea-field.php 0000755 00000000727 14720342453 0023303 0 ustar 00 <?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 ); } } menu/translation-editor/class-wpml-custom-field-editor-settings.php 0000755 00000002027 14720342453 0021650 0 ustar 00 <?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_' ) ); } } menu/translation-editor/class-wpml-tm-editor-job-save.php 0000755 00000000370 14720342453 0017542 0 ustar 00 <?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(); } } menu/translation-editor/class-wpml-tm-editor-save-ajax-action.php 0000755 00000001203 14720342453 0021162 0 ustar 00 <?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 ); } } } menu/translation-editor/class-wpml-translation-editor-ui.php 0000755 00000033027 14720342453 0020374 0 ustar 00 <?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; } } menu/translation-editor/class-wpml-translation-editor-languages.php 0000755 00000001607 14720342453 0021724 0 ustar 00 <?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; } } menu/dashboard/class-wpml-tm-wp-query.php 0000755 00000000417 14720342453 0014450 0 ustar 00 <?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; } } menu/dashboard/class-wpml-tm-dashboard-document-row.php 0000755 00000035772 14720342453 0017243 0 ustar 00 <?php use WPML\FP\Obj; 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)', 'sitepress' ); } 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', 'sitepress' ) ); $post_view_link = $post_link_factory->view_link_anchor( $current_document->ID, __( 'View', 'sitepress' ) ); } $jobs = ( new PostJobsRepository() )->getJobsGroupedByLang( $current_document->ID, $element_type ); $post_edit_link = apply_filters( 'wpml_document_edit_item_link', $post_edit_link, __( 'Edit', 'sitepress' ), $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', 'sitepress' ), $current_document, $element_type, $this->get_type() ); if ( $post_view_link && ! in_array( $this->get_type(), [ 'wp_template_part', 'wp_template', 'wp_navigation' ] ) ) { $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 ); $tooltip_link = 'https://wpml.org/documentation/translating-your-contents/using-different-translation-editors-for-different-pages/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm'; /* translators: %1$s: opening <a> tag, %2$s: closing </a> tag. */ $tooltip_content = sprintf( __( 'This page is set to be %1$stranslated manually%2$s using the WordPress Editor', 'sitepress' ), '<a href="' . $tooltip_link . '">', '</a>' ); $name = $check_field_name . '[' . $current_document->ID . '][checked]'; $value = $current_document->ID; $originalElement = \WPML\Element\API\Translations::getOriginal( $current_document->ID, $current_document->translation_element_type ); $documentOriginalLangCode = Obj::propOr( false, 'language_code', $originalElement ); $isOriginalInPrimaryLang = (int) ( $documentOriginalLangCode === \WPML\Element\API\Languages::getDefaultCode() ); ?> <?php if ( Obj::propOr( false, 'is_blocked_by_filter', $current_document ) ) { ?> <div class="js-otgs-popover-tooltip" data-tippy-content="<?php echo esc_attr( $tooltip_content ); ?>"> <input type="checkbox" disabled="disabled" data-wpml-is-original-in-primary-lang="<?php echo $isOriginalInPrimaryLang ?>" data-wpml-original-lang-code="<?php echo $documentOriginalLangCode ?>" value="<?php echo esc_attr( $value ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ echo $checked; ?> /> </div> <?php } else { ?> <input type="checkbox" data-wpml-is-original-in-primary-lang="<?php echo $isOriginalInPrimaryLang ?>" data-wpml-original-lang-code="<?php echo $documentOriginalLangCode ?>" value="<?php echo esc_attr( $value ); ?>" name="<?php echo esc_attr( $name ); ?>" <?php /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ echo $checked; ?> /> <?php } ?> <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', 'sitepress' ); $this->note_icon_class = 'otgs-ico-note-edit-o'; } else { $this->note_text = __( 'Add note for the translators', 'sitepress' ); $this->note_icon_class = 'otgs-ico-note-add-o'; } } ?> <label for="post_note_<?php echo $current_document->ID; ?>"> <?php _e( 'Note for the translators', 'sitepress' ); ?> </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', 'sitepress' ); ?>" /> <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', 'sitepress' ); ?>"/> </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']; $isLocal = $job['isLocal']; $shouldBeSynced = Jobs::shouldBeATESynced( $job ); } else { $status = $this->get_status_in_lang( $code ); $job_entity_id = 0; $job_id = 0; $automatic = false; $shouldBeSynced = false; $isLocal = true; } if ( $needsReview ) { $translation_status_text = esc_attr( __( 'Needs review', 'sitepress' ) ); } else { switch ( $status ) { case ICL_TM_NOT_TRANSLATED: $translation_status_text = esc_attr( __( 'Not translated', 'sitepress' ) ); break; case ICL_TM_WAITING_FOR_TRANSLATOR: $translation_status_text = $automatic ? esc_attr( __( 'Waiting for automatic translation', 'sitepress' ) ) : esc_attr( __( 'Waiting for translator', 'sitepress' ) ); break; case ICL_TM_IN_BASKET: $translation_status_text = esc_attr( __( 'In basket', 'sitepress' ) ); break; case ICL_TM_IN_PROGRESS: if ( $automatic ) { $translation_status_text = esc_attr( __( 'Waiting for automatic translation', 'sitepress' ) ); } elseif ( $isLocal ) { $translation_status_text = esc_attr( __( 'Waiting for translator', 'sitepress' ) ); } else { $translation_status_text = esc_attr( __( 'Waiting for translation service', 'sitepress' ) ); } $has_jobs_in_progress = true; break; case ICL_TM_TRANSLATION_READY_TO_DOWNLOAD: $translation_status_text = esc_attr( __( 'Translation ready to download', 'sitepress' ) ); $has_jobs_in_progress = true; break; case ICL_TM_DUPLICATE: $translation_status_text = esc_attr( __( 'Duplicate of default language', 'sitepress' ) ); break; case ICL_TM_COMPLETE: $translation_status_text = esc_attr( __( 'Translation completed', 'sitepress' ) ); break; case ICL_TM_NEEDS_UPDATE: $translation_status_text = esc_attr( __( 'Needs update', 'sitepress' ) ); break; case ICL_TM_ATE_NEEDS_RETRY: $translation_status_text = esc_attr( __( 'In progress', 'sitepress' ) ) . ' - ' . esc_attr( __( 'needs retry', 'sitepress' ) ); 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( (string) json_encode( array_values( $jobs ) ) ); ?>" data-icons=" <?php echo htmlspecialchars( (string) 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', 'sitepress' ); ?>" </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 ); } } menu/dashboard/PostJobsRepository.php 0000755 00000004624 14720342453 0014030 0 ustar 00 <?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(); } } menu/dashboard/class-wpml-tm-dashboard-display-filter.php 0000755 00000032367 14720342453 0017545 0 ustar 00 <?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', 'nonce' => wp_create_nonce( '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 /** @phpstan-ignore-next-line */ 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"/> <input type="hidden" name="nonce" value="<?php echo esc_html( wp_create_nonce( '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 ); } } menu/dashboard/class-wpml-tm-dashboard-pagination.php 0000755 00000005236 14720342453 0016741 0 ustar 00 <?php /** * Class WPML_TM_Dashboard_Pagination */ class WPML_TM_Dashboard_Pagination { private $post_limit_number = 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->post_limit_number = ( is_int( $value ) && $value > 0 ) ? $value : $this->post_limit_number; } /** * Resets value of posts limit variable. * * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-616 */ public function resetPostsLimitValue() { $this->post_limit_number = 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 ( 0 === $this->post_limit_number ) ? '' : 'LIMIT ' . $this->post_limit_number; } /** * @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' => (int) $total_pages, 'current' => (int) $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 } } } menu/jobs-list/class-wpml-tm-jobs-list-services.php 0000755 00000002061 14720342453 0016362 0 ustar 00 <?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 ), ); } } menu/jobs-list/class-wpml-tm-jobs-list-script-data.php 0000755 00000016642 14720342453 0016764 0 ustar 00 <?php use WPML\FP\Obj; use WPML\FP\Fns; use WPML\FP\Relation; use WPML\TM\ATE\AutoTranslate\Endpoint\GetJobsCount; 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; use function WPML\FP\System\sanitizeString; use function WPML\Container\make; class WPML_TM_Jobs_List_Script_Data { const TM_JOBS_PAGE = 'tm-jobs'; const TRANSLATION_QUEUE_PAGE = 'translation-queue'; private $exportAllToXLIFFLimit; /** @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( new WPML_TM_Rest_Jobs_Translation_Service() ); } if ( ! $translated_by_filters ) { $translated_by_filters = new WPML_TM_Jobs_List_Translated_By_Filters( $services, $translators ); } if ( ! defined( 'WPML_EXPORT_ALL_TO_XLIFF_LIMIT' ) ) { define( 'WPML_EXPORT_ALL_TO_XLIFF_LIMIT', 1700 ); } $this->exportAllToXLIFFLimit = WPML_EXPORT_ALL_TO_XLIFF_LIMIT; $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(); /** @var Jobs $jobs */ $jobs = make( Jobs::class ); $data = [ 'isATEEnabled' => $isATEEnabled, 'hasAnyJobsToSync' => $isATEEnabled ? $jobs->hasAnyToSync() : false, '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, 'getJobsCount' => GetJobsCount::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, 'xliffExportAllLimit' => $this->exportAllToXLIFFLimit, ]; $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 = []; $getProp = sanitizeString(); if ( Obj::propOr( false, 'element_type', $_GET ) ) { $filters['element_type'] = $getProp( Obj::prop( 'element_type', $_GET ) ); } if ( Obj::propOr( false, 'targetLanguages', $_GET ) ) { $filters['targetLanguages'] = explode( ',', urldecode( $getProp( Obj::prop( 'targetLanguages', $_GET ) ) ) ); } if ( Obj::propOr( false, 'status', $_GET ) ) { $filters['status'] = [ $getProp( Obj::prop( 'status', $_GET ) ) ]; } if ( Obj::propOr( false, 'only_automatic', $_GET ) ) { $filters['translated_by'] = 'automatic'; } 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; } } menu/jobs-list/class-wpml-tm-jobs-list-translated_by_filters.php 0000755 00000002273 14720342453 0021127 0 ustar 00 <?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, ); } } menu/jobs-list/class-wpml-tm-jobs-list-translators.php 0000755 00000003125 14720342453 0017115 0 ustar 00 <?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 ) { /** @var callable $isValidLanguage */ $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(); } } menu/translation-services/AuthenticationAjax.php 0000755 00000006741 14720342453 0016202 0 ustar 00 <?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 void */ public function authenticate_service() { $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 void */ public function update_credentials() { $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 void */ public function invalidate_service() { $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 void */ private function handle_action( callable $action, callable $request_validation, $success_message, $failure_message ) { if ( $request_validation() ) { try { $action(); $this->send_success_response( $success_message ); } catch ( \Exception $e ) { return $this->send_error_message( $failure_message ); } } else { $this->send_error_message( __( 'Invalid Request', 'wpml-translation-management' ) ); } } /** * @param string $msg * * @return void */ private function send_success_response( $msg ) { wp_send_json_success( [ 'errors' => 0, 'message' => $msg, 'reload' => 1, ] ); } /** * @param string $msg * * @return bool */ private function send_error_message( $msg ) { wp_send_json_error( [ 'errors' => 1, 'message' => $msg, 'reload' => 0, ] ); } /** * @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(); } } menu/translation-services/NoSiteKeyTemplate.php 0000755 00000001204 14720342453 0015752 0 ustar 00 <?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' ), ], ]; } } menu/translation-services/SectionFactory.php 0000755 00000004053 14720342453 0015345 0 ustar 00 <?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(); } } menu/translation-services/ActiveServiceRepository.php 0000755 00000001036 14720342453 0017243 0 ustar 00 <?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 ); } } menu/translation-services/troubleshooting/RefreshServicesFactory.php 0000755 00000002124 14720342453 0022267 0 ustar 00 <?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'] ); } } menu/translation-services/troubleshooting/RefreshServices.php 0000755 00000005217 14720342453 0020745 0 ustar 00 <?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(), ICL_SITEPRESS_VERSION ); } } menu/translation-services/Resources.php 0000755 00000002370 14720342453 0014363 0 ustar 00 <?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(), ICL_SITEPRESS_VERSION ); wp_enqueue_style( 'wpml-tm-translation-services', WPML_TM_URL . '/dist/css/translationServices/styles.css', [], ICL_SITEPRESS_VERSION ); } public function enqueue_scripts() { wp_enqueue_script( 'wpml-tm-ts-admin-section', WPML_TM_URL . '/res/js/translation-services.js', array(), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'wpml-tm-translation-services', WPML_TM_URL . '/dist/js/translationServices/app.js', array(), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'wpml-tp-api', WPML_TM_URL . '/res/js/wpml-tp-api.js', array( 'jquery', 'wp-util' ), ICL_SITEPRESS_VERSION ); } private function is_active() { return isset( $_GET['sm'] ) && 'translators' === $_GET['sm']; } } menu/translation-services/ActiveServiceTemplate.php 0000755 00000010602 14720342453 0016636 0 ustar 00 <?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; } } menu/translation-services/ServicesRetriever.php 0000755 00000007771 14720342453 0016076 0 ustar 00 <?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() ); } } menu/translation-services/ActivationAjax.php 0000755 00000006510 14720342453 0015316 0 ustar 00 <?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 ); } } menu/translation-services/ActiveServiceTemplateFactory.php 0000755 00000001461 14720342453 0020171 0 ustar 00 <?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(); } } menu/translation-services/ServiceMapper.php 0000755 00000002612 14720342453 0015155 0 ustar 00 <?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(), ]; } } menu/translation-services/endpoints/Select.php 0000755 00000001764 14720342453 0015641 0 ustar 00 <?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 ); } } menu/translation-services/endpoints/Activate.php 0000755 00000001641 14720342453 0016154 0 ustar 00 <?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 ); } } menu/translation-services/endpoints/Deactivate.php 0000755 00000000660 14720342453 0016465 0 ustar 00 <?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' ); } } menu/translation-services/ActivationAjaxFactory.php 0000755 00000000464 14720342453 0016650 0 ustar 00 <?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() ); } } menu/translation-services/Section.php 0000755 00000004447 14720342453 0014024 0 ustar 00 <?php namespace WPML\TM\Menu\TranslationServices; use WPML\LIB\WP\User; 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 [ User::CAP_MANAGE_TRANSLATIONS, User::CAP_ADMINISTRATOR ]; } /** * 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 ) {} } menu/translation-services/MainLayoutTemplate.php 0000755 00000006272 14720342453 0016174 0 ustar 00 <?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 ], ], ]; } } menu/translation-services/AuthenticationAjaxFactory.php 0000755 00000000427 14720342453 0017525 0 ustar 00 <?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 ); } } menu/translation-queue/class-wpml-translations-queue.php 0000755 00000007140 14720342453 0017635 0 ustar 00 <?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 ( in_array( Obj::prop( 'editor', $response ), [ \WPML_TM_Editors::ATE, \WPML_TM_Editors::WP ] ) ) { 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; } } menu/translation-queue/PostTypeFilters.php 0000755 00000005355 14720342453 0015040 0 ustar 00 <?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; } } menu/translation-queue/CloneJobs.php 0000755 00000004741 14720342453 0013574 0 ustar 00 <?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( (string) 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; } } menu/wpml-tm-admin-menus-factory.php 0000755 00000000322 14720342453 0013502 0 ustar 00 <?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(); } } } menu/wpml-tm-admin-menus-hooks.php 0000755 00000002152 14720342453 0013161 0 ustar 00 <?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; } } menu/class-wpml-tm-scripts-factory.php 0000755 00000024674 14720342453 0014077 0 ustar 00 <?php use WPML\Element\API\Languages; use WPML\FP\Obj; use WPML\TM\API\ATE\CachedLanguageMappings; use WPML\TM\API\Basket; use WPML\TM\TranslationDashboard\FiltersStorage; use WPML\TM\TranslationDashboard\SentContentMessages; use WPML\Core\WP\App\Resources; use WPML\UIPage; use function WPML\Container\make; /** * @author OnTheGo Systems */ class WPML_TM_Scripts_Factory { private $ate; private $auth; private $endpoints; 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( '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(), ICL_SITEPRESS_VERSION ); } if ( WPML_TM_Page::is_notifications_page() ) { wp_enqueue_style( 'wpml-tm-translation-notifications', WPML_TM_URL . '/res/css/translation-notifications.css', array(), ICL_SITEPRESS_VERSION ); } } private function load_pick_up_box_scripts() { wp_enqueue_style( 'otgs-notices' ); global $iclTranslationManagement; $currentLanguageCode = FiltersStorage::getFromLanguage(); /** @var \WPML_Translation_Management $tmManager */ $tmManager = wpml_translation_management(); $currentTranslationService = TranslationProxy::get_current_service(); $isCurrentServiceAuthenticated = TranslationProxy_Service::is_authenticated( $currentTranslationService ); $getTargetLanguages = \WPML\FP\pipe( Languages::class . '::getActive', Obj::removeProp( $currentLanguageCode ), Languages::withFlags(), CachedLanguageMappings::withCanBeTranslatedAutomatically(), Obj::values() ); $data = [ 'name' => 'WPML_TM_DASHBOARD', 'data' => [ 'endpoints' => [ 'duplicate' => \WPML\TM\TranslationDashboard\Endpoints\Duplicate::class, 'displayNewMessage' => \WPML\TM\TranslationDashboard\Endpoints\DisplayNeedSyncMessage::class, 'setTranslateEverything' => \WPML\TranslationMode\Endpoint\SetTranslateEverything::class, 'getCredits' => \WPML\TM\ATE\AutoTranslate\Endpoint\GetCredits::class, ], 'strings' => [ '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' => [ '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 ), ], 'sendingToTranslation' => [ 'targetLanguages' => $getTargetLanguages(), 'iclnonce' => wp_create_nonce( 'pro-translation-icl' ), 'translationReviewMode' => \WPML\Setup\Option::getReviewMode( null ), 'settings' => [ 'defaultLanguageDisplayName' => Languages::getDefault()['display_name'], 'isInDefaultLanguage' => Languages::getDefaultCode() === $currentLanguageCode, 'shouldUseBasket' => Basket::shouldUse( $currentLanguageCode ), 'isATEActive' => WPML_TM_ATE_Status::is_enabled_and_activated(), 'hasAnyLocalTranslators' => wpml_tm_load_blog_translators()->has_translators(), 'hasAnyTranslationServices' => $currentTranslationService && $isCurrentServiceAuthenticated, 'doesTranslationServiceRequireAuthentication' => $currentTranslationService && ! $isCurrentServiceAuthenticated, 'doesServiceRequireTranslators' => $currentTranslationService && $tmManager->service_requires_translators(), 'currentTranslationServiceName' => $currentTranslationService ? Obj::prop( 'name', $currentTranslationService ) : null, ], 'urls' => [ 'translatorsTab' => UIPage::getTMTranslators() . '#js-wpml-active-service-wrapper', ], ], 'sentContentMessages' => make( SentContentMessages::class )->get(), ], ]; $enqueueApp = Resources::enqueueApp( 'translationDashboard' ); $enqueueApp( $data ); } 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' ), 'syncJobStatesNonce' => wp_create_nonce( 'sync-job-states' ), '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 ) { $filtered_current_user = clone $current_user; $filtered_current_user_data = new \stdClass(); $blacklistedProps = [ 'user_pass' ]; foreach ( $current_user->data as $prop => $value ) { if ( in_array( $prop, $blacklistedProps ) ) { continue; } $filtered_current_user_data->$prop = $value; } $filtered_current_user->data = $filtered_current_user_data; $data['currentUser'] = $filtered_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(); } } menu/ams-ate-console/class-wpml-tm-ams-ate-console-section.php 0000755 00000016530 14720342453 0020353 0 ustar 00 <?php use WPML\API\Sanitize; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\ATE\Proxies\Widget; use WPML\TM\ATE\NoCreditPopup; use WPML\LIB\WP\User; use function WPML\Container\make; /** * 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 [ User::CAP_MANAGE_TRANSLATIONS, User::CAP_ADMINISTRATOR, User::CAP_MANAGE_OPTIONS ]; } /** * Returns the caption to display in the section. * * @return string */ public function get_caption() { return __( 'Tools', '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, [], ICL_SITEPRESS_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(); /** @var NoCreditPopup $noCreditPopup */ $noCreditPopup = make( NoCreditPopup::class ); $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 ) ), 'dependencies' => [ 'sitepress-multilingual-cms' => [ 'version' => ICL_SITEPRESS_VERSION, ], ], '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' => $noCreditPopup->getLanguagesData(), ]; 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'; } } menu/ams-ate-console/class-wpml-tm-ams-ate-console-section-factory.php 0000755 00000000620 14720342453 0022011 0 ustar 00 <?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; } } translations/class-wpml-translation-element-factory.php 0000755 00000002611 14720342453 0017517 0 ustar 00 <?php class WPML_Translation_Element_Factory { const ELEMENT_TYPE_POST = 'Post'; const ELEMENT_TYPE_TERM = 'Term'; const ELEMENT_TYPE_MENU = 'Menu'; /** @var SitePress */ private $sitepress; /** @var WPML_WP_Cache */ private $wpml_cache; /** * @param SitePress $sitepress * @param WPML_WP_Cache $wpml_cache */ public function __construct( SitePress $sitepress, WPML_WP_Cache $wpml_cache = null ) { $this->sitepress = $sitepress; $this->wpml_cache = $wpml_cache; } /** * @param int $id * @param string $type any of `WPML_Translation_Element_Factory::ELEMENT_TYPE_POST`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_TERM`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_MENU`. * * @return WPML_Translation_Element * @throws InvalidArgumentException InvalidArgumentException. */ public function create( $id, $type ) { $fn = 'create_' . $type; if ( method_exists( $this, $fn ) ) { return $this->$fn( $id ); } throw new InvalidArgumentException( 'Element type: ' . $type . ' does not exist.' ); } public function create_post( $id ) { return new WPML_Post_Element( $id, $this->sitepress, $this->wpml_cache ); } public function create_term( $id ) { return new WPML_Term_Element( $id, $this->sitepress, '', $this->wpml_cache ); } public function create_menu( $id ) { return new WPML_Menu_Element( $id, $this->sitepress, $this->wpml_cache ); } } translations/class-wpml-term-element.php 0000755 00000004663 14720342453 0014474 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Term_Element extends WPML_Translation_Element { /** @var string Taxonomy name */ protected $taxonomy; /** * WPML_Term_Element constructor. * * @param int $id term_id of Term Element. * @param SitePress $sitepress * @param string $taxonomy * @param WPML_WP_Cache $wpml_cache */ public function __construct( $id, SitePress $sitepress, $taxonomy = '', WPML_WP_Cache $wpml_cache = null ) { $this->taxonomy = $taxonomy; parent::__construct( $id, $sitepress, $wpml_cache ); } /** * @return array|null|WP_Error|WP_Term */ public function get_wp_object() { $has_filter = remove_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1 ); $term = get_term( $this->id, $this->taxonomy ); if ( ! $term || is_wp_error( $term ) ) { $term = get_term_by( 'term_taxonomy_id', $this->id, $this->taxonomy ); $term = $term ?: null; } if ( $has_filter ) { add_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1, 1 ); } return $term; } /** * @param WP_Term $term * * @return string */ public function get_type( $term = null ) { if ( ! $this->taxonomy && $term instanceof WP_Term ) { $this->taxonomy = $term->taxonomy; } return $this->taxonomy; } public function get_wpml_element_type() { $element_type = ''; if ( ! is_wp_error( $this->get_wp_element_type() ) ) { $element_type = 'tax_' . $this->get_wp_element_type(); } return $element_type; } public function get_element_id() { $element_id = null; $term = $this->get_wp_object(); if ( $term && ! is_wp_error( $term ) ) { $element_id = $term->term_taxonomy_id; } return $element_id; } /** * @param null|stdClass $element_data null, or a standard object containing at least the `translation_id`, `language_code`, `element_id`, `source_language_code`, `element_type`, and `original` properties. * * @return WPML_Term_Element * @throws \InvalidArgumentException Exception. */ public function get_new_instance( $element_data ) { return new WPML_Term_Element( $element_data->element_id, $this->sitepress, $this->taxonomy, $this->wpml_cache ); } public function is_translatable() { return $this->sitepress->is_translated_taxonomy( $this->get_wp_element_type() ); } public function is_display_as_translated() { return $this->sitepress->is_display_as_translated_taxonomy( $this->get_wp_element_type() ); } } translations/interface-wpml-duplicable-element.php 0000755 00000000121 14720342453 0016445 0 ustar 00 <?php /** * @author OnTheGo Systems */ interface WPML_Duplicable_Element { } translations/class-wpml-menu-element.php 0000755 00000001424 14720342453 0014461 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Menu_Element extends WPML_Term_Element { /** * WPML_Menu_Element constructor. * * @param int $id * @param SitePress $sitepress * @param WPML_WP_Cache $wpml_cache */ public function __construct( $id, SitePress $sitepress, WPML_WP_Cache $wpml_cache = null ) { $this->taxonomy = 'nav_menu'; parent::__construct( $id, $sitepress, $this->taxonomy, $wpml_cache ); } /** * @param stdClass $element_data standard object containing at least the `term_id` property. * * @return WPML_Menu_Element * @throws \InvalidArgumentException Exception. */ public function get_new_instance( $element_data ) { return new WPML_Menu_Element( $element_data->term_id, $this->sitepress, $this->wpml_cache ); } } translations/class-wpml-translation-element.php 0000755 00000013065 14720342453 0016057 0 ustar 00 <?php /** * Use this class as parent class for translatable elements in WPML, * to have a common approach for retrieving and setting translation information. * * @author OnTheGo Systems */ abstract class WPML_Translation_Element extends WPML_SP_User { /** @var int */ protected $id; /** @var stdClass */ private $languages_details; /** @var array */ private $element_translations; /** @var WPML_WP_Cache */ protected $wpml_cache; /** * WPML_Translation_Element constructor. * * @param int $id * @param SitePress $sitepress * @param WPML_WP_Cache $wpml_cache */ public function __construct( $id, SitePress $sitepress, WPML_WP_Cache $wpml_cache = null ) { if ( ! is_numeric( $id ) || $id <= 0 ) { throw new InvalidArgumentException( 'Argument ID must be numeric and greater than 0.' ); } $this->id = (int) $id; $this->wpml_cache = $wpml_cache ? $wpml_cache : new WPML_WP_Cache( WPML_ELEMENT_TRANSLATIONS_CACHE_GROUP ); parent::__construct( $sitepress ); } public function get_id() { return $this->id; } /** * @return string|null */ public function get_source_language_code() { $source_language_code = null; if ( $this->get_language_details() ) { $source_language_code = $this->get_language_details()->source_language_code; } return $source_language_code; } /** * @return stdClass * @throws \UnexpectedValueException */ protected function get_language_details() { $this->init_language_details(); return $this->languages_details; } abstract function get_element_id(); abstract function get_wpml_element_type(); /** * @return array */ private function get_element_translations() { return $this->sitepress->get_element_translations( $this->get_trid(), $this->get_wpml_element_type() ); } /** * @param string $language_code * * @return WPML_Translation_Element|null * @throws \InvalidArgumentException */ public function get_translation( $language_code ) { if ( ! $language_code ) { throw new InvalidArgumentException( 'Argument $language_code must be a non empty string.' ); } $this->maybe_init_translations(); $translation = null; if ( $this->element_translations && array_key_exists( $language_code, $this->element_translations ) ) { $translation = $this->element_translations[ $language_code ]; } return $translation; } /** * @return WPML_Translation_Element[] */ public function get_translations() { return $this->maybe_init_translations(); } /** * @return WPML_Translation_Element[] */ public function maybe_init_translations() { if ( ! $this->element_translations ) { $this->element_translations = array(); $translations = $this->get_element_translations(); foreach ( $translations as $language_code => $element_data ) { if ( ! isset( $element_data->element_id ) ) { continue; } try { $this->element_translations[ $language_code ] = $this->get_new_instance( $element_data ); } catch ( Exception $e ) { } } } return $this->element_translations; } /** * @return false|int */ public function get_trid() { $trid = false; if ( $this->get_language_details() ) { $trid = $this->get_language_details()->trid; } return $trid; } /** * @return string|WP_Error */ function get_wp_element_type() { $element = $this->get_wp_object(); if ( is_wp_error( $element ) ) { return $element; } if ( false === (bool) $element ) { return new WP_Error( 1, 'Element does not exists.' ); } return $this->get_type( $element ); } /** * @return mixed|WP_Error */ abstract function get_wp_object(); /** * @param mixed $element * * @return string */ abstract function get_type( $element = null ); /** * @param null|object $element_data null, or a standard object containing at least the `translation_id`, `language_code`, `element_id`, `source_language_code`, `element_type`, and `original` properties. * * @return WPML_Translation_Element */ abstract function get_new_instance( $element_data ); /** * @return null|WPML_Translation_Element */ public function get_source_element() { $this->maybe_init_translations(); $source_element = null; $source_language_code = $this->get_source_language_code(); if ( $this->element_translations && $source_language_code && array_key_exists( $source_language_code, $this->element_translations ) ) { $source_element = $this->element_translations[ $source_language_code ]; } return $source_element; } /** * Determines whether the current element language is the root source element. * * @return bool */ public function is_root_source() { return null !== $this->get_source_language_code() && $this->get_source_language_code() === $this->get_language_code(); } /** * @return string|null */ public function get_language_code() { $language_code = null; if ( $this->get_language_details() ) { $language_code = $this->get_language_details()->language_code; } return $language_code; } protected function init_language_details() { if ( ! $this->languages_details ) { $this->languages_details = $this->sitepress->get_element_language_details( $this->get_element_id(), $this->get_wpml_element_type() ); } } public function flush_cache() { $this->languages_details = null; $this->element_translations = null; $this->wpml_cache->flush_group_cache(); } /** @return bool */ public function is_in_default_language() { return $this->get_language_code() === $this->sitepress->get_default_language(); } abstract function is_translatable(); abstract function is_display_as_translated(); } translations/class-wpml-element-type-translation.php 0000755 00000002565 14720342453 0017041 0 ustar 00 <?php /** NOTE: * Use the $wpml_post_translations or $wpml_term_translations globals for posts and taxonomy * They are more efficient */ class WPML_Element_Type_Translation { /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Cache_Factory $cache_factory */ private $cache_factory; /** @var string $element_type */ private $element_type; public function __construct( wpdb $wpdb, WPML_Cache_Factory $cache_factory, $element_type ) { $this->wpdb = $wpdb; $this->cache_factory = $cache_factory; $this->element_type = $element_type; } function get_element_lang_code( $element_id ) { $cache_key_array = array( $element_id, $this->element_type ); $cache_key = md5( serialize( $cache_key_array ) ); $cache_group = 'WPML_Element_Type_Translation::get_language_for_element'; $cache_found = false; $cache = $this->cache_factory->get( $cache_group ); $result = $cache->get( $cache_key, $cache_found ); if ( ! $cache_found ) { $language_for_element_prepared = $this->wpdb->prepare( "SELECT language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_id=%d AND element_type=%s LIMIT 1", array( $element_id, $this->element_type ) ); $result = $this->wpdb->get_var( $language_for_element_prepared ); if ( $result ) { $cache->set( $cache_key, $result ); } } return $result; } } translations/class-wpml-translations.php 0000755 00000025702 14720342453 0014614 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Translations extends WPML_SP_User { /** @var bool */ public $skip_empty = false; /** @var bool */ public $all_statuses = false; /** @var bool */ public $skip_cache = false; /** @var bool */ public $skip_recursions = false; private $duplicated_by = array(); private $mark_as_duplicate_meta_key = '_icl_lang_duplicate_of'; private $wpml_cache; /** * WPML_Translations constructor. * * @param SitePress $sitepress * @param WPML_WP_Cache $wpml_cache */ public function __construct( SitePress $sitepress, WPML_WP_Cache $wpml_cache = null ) { parent::__construct( $sitepress ); $this->wpml_cache = $wpml_cache ? $wpml_cache : new WPML_WP_Cache( WPML_ELEMENT_TRANSLATIONS_CACHE_GROUP ); } /** * @param int $trid * @param string $wpml_element_type * @param bool $skipPrivilegeChecking * * @return array<string,\stdClass> */ public function get_translations( $trid, $wpml_element_type, $skipPrivilegeChecking = false ) { $cache_key_args = array_filter( array( $trid, $wpml_element_type, $this->skip_empty, $this->all_statuses, $this->skip_recursions ) ); $cache_key = md5( (string) wp_json_encode( $cache_key_args ) ); $cache_found = false; $temp_elements = $this->wpml_cache->get( $cache_key, $cache_found ); if ( ! $this->skip_cache && $cache_found ) { return $temp_elements; } $translations = array(); $sql_parts = array( 'select' => array(), 'join' => array(), 'where' => array(), 'group_by' => array(), ); if ( $trid ) { if ( $this->wpml_element_type_is_post( $wpml_element_type ) ) { $sql_parts = $this->get_sql_parts_for_post( $wpml_element_type, $sql_parts, $skipPrivilegeChecking ); } elseif ( $this->wpml_element_type_is_taxonomy( $wpml_element_type ) ) { $sql_parts = $this->get_sql_parts_for_taxonomy( $sql_parts ); } $sql_parts['where'][] = $this->sitepress->get_wpdb()->prepare( ' AND wpml_translations.trid=%d ', $trid ); $select = implode( ' ', $sql_parts['select'] ); $join = implode( ' ', $sql_parts['join'] ); $where = implode( ' ', $sql_parts['where'] ); $group_by = implode( ' ', $sql_parts['group_by'] ); $query = " SELECT wpml_translations.translation_id, wpml_translations.language_code, wpml_translations.element_id, wpml_translations.source_language_code, wpml_translations.element_type, NULLIF(wpml_translations.source_language_code, '') IS NULL AS original {$select} FROM {$this->sitepress->get_wpdb()->prefix}icl_translations wpml_translations {$join} WHERE 1 {$where} {$group_by} "; $results = $this->sitepress->get_wpdb()->get_results( $query ); foreach ( $results as $translation ) { if ( $this->must_ignore_translation( $translation ) ) { continue; } $translations[ $translation->language_code ] = $translation; } } if ( $translations ) { $this->wpml_cache->set( $cache_key, $translations ); } return $translations; } public function link_elements( WPML_Translation_Element $source_translation_element, WPML_Translation_Element $target_translation_element, $target_language = null ) { if ( null !== $target_language ) { $this->set_language_code( $target_translation_element, $target_language ); } $this->set_source_element( $target_translation_element, $source_translation_element ); } public function set_source_element( WPML_Translation_Element $element, WPML_Translation_Element $source_element ) { $this->elements_type_matches( $element, $source_element ); $this->sitepress->set_element_language_details( $element->get_element_id(), $element->get_wpml_element_type(), $source_element->get_trid(), $element->get_language_code(), $source_element->get_language_code() ); $element->flush_cache(); } private function elements_type_matches( $element1, $element2 ) { if ( get_class( $element1 ) !== get_class( $element2 ) ) { throw new UnexpectedValueException( '$source_element is not an instance of ' . get_class( $element1 ) . ': instance of ' . get_class( $element2 ) . ' received instead.' ); } } /** * @param WPML_Translation_Element $element * @param string $language_code */ public function set_language_code( WPML_Translation_Element $element, $language_code ) { $element_id = $element->get_element_id(); $wpml_element_type = $element->get_wpml_element_type(); $trid = $element->get_trid(); $this->sitepress->set_element_language_details( $element_id, $wpml_element_type, $trid, $language_code ); $element->flush_cache(); } /** * @param WPML_Translation_Element $element * @param int $trid * * @throws \UnexpectedValueException */ public function set_trid( WPML_Translation_Element $element, $trid ) { if ( ! $element->get_language_code() ) { throw new UnexpectedValueException( 'Element has no language information.' ); } $this->sitepress->set_element_language_details( $element->get_element_id(), $element->get_wpml_element_type(), $trid, $element->get_language_code() ); $element->flush_cache(); } /** * @param \WPML_Translation_Element $duplicate * @param \WPML_Translation_Element $original * * @throws \UnexpectedValueException */ public function make_duplicate_of( WPML_Translation_Element $duplicate, WPML_Translation_Element $original ) { $this->validate_duplicable_element( $duplicate ); $this->validate_duplicable_element( $original, 'source' ); $this->set_source_element( $duplicate, $original ); update_post_meta( $duplicate->get_id(), $this->mark_as_duplicate_meta_key, $original->get_id() ); $duplicate->flush_cache(); $this->duplicated_by[ $duplicate->get_id() ] = array(); } /** * @param \WPML_Translation_Element $element * * @return WPML_Post_Element * @throws \InvalidArgumentException */ public function is_a_duplicate_of( WPML_Translation_Element $element ) { $this->validate_duplicable_element( $element ); $duplicate_of = get_post_meta( $element->get_id(), $this->mark_as_duplicate_meta_key, true ); if ( $duplicate_of ) { return new WPML_Post_Element( $duplicate_of, $this->sitepress ); } return null; } /** * @param \WPML_Translation_Element $element * * @return array * @throws \UnexpectedValueException * @throws \InvalidArgumentException */ public function is_duplicated_by( WPML_Translation_Element $element ) { $this->validate_duplicable_element( $element ); $this->init_cache_for_element( $element ); if ( ! $this->duplicated_by[ $element->get_id() ] ) { $this->duplicated_by[ $element->get_id() ] = array(); $args = array( 'post_type' => $element->get_wp_element_type(), 'meta_query' => array( array( 'key' => $this->mark_as_duplicate_meta_key, 'value' => $element->get_id(), 'compare' => '=', ), ), ); $query = new WP_Query( $args ); $results = $query->get_posts(); foreach ( $results as $post ) { $this->duplicated_by[ $element->get_id() ][] = new WPML_Post_Element( $post->ID, $this->sitepress ); } } return $this->duplicated_by[ $element->get_id() ]; } /** * @param \WPML_Translation_Element $element * @param string $argument_name * * @throws \UnexpectedValueException */ private function validate_duplicable_element( WPML_Translation_Element $element, $argument_name = 'element' ) { if ( ! ( $element instanceof WPML_Duplicable_Element ) ) { throw new UnexpectedValueException( sprintf( 'Argument %s does not implement `WPML_Duplicable_Element`.', $argument_name ) ); } } /** * @param \WPML_Translation_Element $element */ private function init_cache_for_element( WPML_Translation_Element $element ) { if ( ! array_key_exists( $element->get_id(), $this->duplicated_by ) ) { $this->duplicated_by[ $element->get_id() ] = array(); } } /** * @param string $element_type * @param array<string,array<string>> $sql_parts * @param bool $skipPrivilegeChecking * * @return array<string,array<string>> */ private function get_sql_parts_for_post( $element_type, $sql_parts, $skipPrivilegeChecking = false ) { $sql_parts['select'][] = ', p.post_title, p.post_status'; $sql_parts['join'][] = " LEFT JOIN {$this->sitepress->get_wpdb()->posts} p ON wpml_translations.element_id=p.ID"; if ( ! $this->all_statuses && 'post_attachment' !== $element_type && ! is_admin() ) { $public_statuses_where = $this->get_public_statuses(); // the current user may not be the admin but may have read private post/page caps! if ( current_user_can( 'read_private_pages' ) || current_user_can( 'read_private_posts' ) || $skipPrivilegeChecking ) { $sql_parts['where'][] = ' AND (p.post_status IN (' . $public_statuses_where . ", 'draft', 'private', 'pending' ))"; } else { $sql_parts['where'][] = ' AND ('; $sql_parts['where'][] = 'p.post_status IN (' . $public_statuses_where . ') '; if ( $uid = $this->sitepress->get_current_user()->ID ) { $sql_parts['where'][] = $this->sitepress->get_wpdb()->prepare( " OR (post_status in ('draft', 'private', 'pending') AND post_author = %d)", $uid ); } $sql_parts['where'][] = ') '; } } return $sql_parts; } /** * @return string */ private function get_public_statuses() { return wpml_prepare_in( get_post_stati( [ 'public' => true ] ) ); } /** * @param array<string,array<string>> $sql_parts * * @return array<string,array<string>> */ private function get_sql_parts_for_taxonomy( $sql_parts ) { $sql_parts['select'][] = ', tm.name, tm.term_id, COUNT(tr.object_id) AS instances'; $sql_parts['join'][] = " LEFT JOIN {$this->sitepress->get_wpdb()->term_taxonomy} tt ON wpml_translations.element_id=tt.term_taxonomy_id LEFT JOIN {$this->sitepress->get_wpdb()->terms} tm ON tt.term_id = tm.term_id LEFT JOIN {$this->sitepress->get_wpdb()->term_relationships} tr ON tr.term_taxonomy_id=tt.term_taxonomy_id "; $sql_parts['group_by'][] = 'GROUP BY tm.term_id'; return $sql_parts; } /** * @param stdClass $translation * * @return bool */ private function must_ignore_translation( stdClass $translation ) { return $this->skip_empty && ( ! $translation->element_id || $this->must_ignore_translation_for_taxonomy( $translation ) ); } /** * @param stdClass $translation * * @return bool */ private function must_ignore_translation_for_taxonomy( stdClass $translation ) { return $this->wpml_element_type_is_taxonomy( $translation->element_type ) && $translation->instances === 0 && ( ! $this->skip_recursions && ! _icl_tax_has_objects_recursive( $translation->element_id ) ); } /** * @param string $wpml_element_type * * @return int */ private function wpml_element_type_is_taxonomy( $wpml_element_type ) { return preg_match( '#^tax_(.+)$#', $wpml_element_type ); } /** * @param string $wpml_element_type * * @return bool */ private function wpml_element_type_is_post( $wpml_element_type ) { return 0 === strpos( $wpml_element_type, 'post_' ); } } translations/class-wpml-post-element.php 0000755 00000002546 14720342453 0014510 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Post_Element extends WPML_Translation_Element implements WPML_Duplicable_Element { /** * @return WP_Post */ function get_wp_object() { return get_post( $this->id ); } /** * @param WP_Post $post * * @return string */ function get_type( $post = null ) { if ( $post ) { return $post->post_type; } return $this->get_wp_object()->post_type; } public function get_wpml_element_type() { $element_type = ''; if ( ! is_wp_error( $this->get_wp_element_type() ) ) { $element_type = 'post_' . $this->get_wp_element_type(); } return $element_type; } function get_element_id() { return $this->id; } /** * @param null|stdClass $element_data null, or a standard object containing at least the `translation_id`, `language_code`, `element_id`, `source_language_code`, `element_type`, and `original` properties. * * @return WPML_Post_Element * @throws \InvalidArgumentException */ function get_new_instance( $element_data ) { return new WPML_Post_Element( $element_data->element_id, $this->sitepress, $this->wpml_cache ); } function is_translatable() { return $this->sitepress->is_translated_post_type( $this->get_wp_element_type() ); } function is_display_as_translated() { return $this->sitepress->is_display_as_translated_post_type( $this->get_wp_element_type() ); } } admin-bar/Hooks.php 0000755 00000003151 14720342453 0010201 0 ustar 00 <?php namespace WPML\TM\AdminBar; class Hooks implements \IWPML_Frontend_Action, \IWPML_DIC_Action { /** @var \WPML_Post_Translation */ private $postTranslations; public function __construct( \WPML_Post_Translation $postTranslations ) { $this->postTranslations = $postTranslations; } public function add_hooks() { if ( is_user_logged_in() ) { add_action( 'admin_bar_menu', [ $this, 'addTranslateMenuItem' ], 80 ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueueScripts' ] ); } } public function addTranslateMenuItem( \WP_Admin_Bar $wpAdminMenu ) { global $wp_the_query; $queriedObject = $wp_the_query->get_queried_object(); if ( ! empty( $queriedObject ) && ! empty( $queriedObject->post_type ) ) { wpml_tm_load_status_display_filter(); $trid = $this->postTranslations->get_element_trid( $queriedObject->ID ); if ( $trid ) { $originalID = $this->postTranslations->get_original_post_ID( $trid ); $lang = $this->postTranslations->get_element_lang_code( $queriedObject->ID ); $translateLink = apply_filters( 'wpml_link_to_translation', '', $originalID, $lang, $trid ); if ( $translateLink ) { $img = '<img class="ab-icon" src="' . ICL_PLUGIN_URL . '/res/img/icon16.png">'; $wpAdminMenu->add_menu( [ 'id' => 'translate', 'title' => $img . __( 'Edit Translation', 'wpml-translation-management' ), 'href' => admin_url() . $translateLink, ] ); } } } } public function enqueueScripts() { wp_enqueue_style( 'wpml-tm-admin-bar', WPML_TM_URL . '/res/css/admin-bar-style.css', array(), ICL_SITEPRESS_VERSION ); } } custom-field-translation/class-wpml-translate-link-targets-in-custom-fields-hooks.php 0000755 00000001004 14720342453 0025166 0 ustar 00 <?php class WPML_Translate_Link_Targets_In_Custom_Fields_Hooks { /** * WPML_Translate_Link_Targets_In_Custom_Fields_Hook constructor. * * @param WPML_Translate_Link_Targets_In_Custom_Fields $translate_links * @param WPML_WP_API $wp_api */ public function __construct( $translate_links, &$wp_api ) { if ( $translate_links->has_meta_keys() ) { $wp_api->add_filter( 'get_post_metadata', array( $translate_links, 'maybe_translate_link_targets' ), 10, 4 ); } } } custom-field-translation/class-wpml-sync-custom-fields.php 0000755 00000006020 14720342453 0020021 0 ustar 00 <?php class WPML_Sync_Custom_Fields { /** @var WPML_Translation_Element_Factory $element_factory */ private $element_factory; /** @var array $fields_to_sync */ private $fields_to_sync; /** * WPML_Sync_Custom_Fields constructor. * * @param WPML_Translation_Element_Factory $element_factory * @param array $fields_to_sync */ public function __construct( WPML_Translation_Element_Factory $element_factory, array $fields_to_sync ) { $this->element_factory = $element_factory; $this->fields_to_sync = $fields_to_sync; } /** * @param int $post_id_from * @param string $meta_key */ public function sync_to_translations( $post_id_from, $meta_key ) { if ( in_array( $meta_key, $this->fields_to_sync, true ) ) { $post_element = $this->element_factory->create( $post_id_from, 'post' ); $translations = $post_element->get_translations(); foreach ( $translations as $translation ) { $translation_id = $translation->get_element_id(); if ( $translation_id !== $post_id_from ) { $this->sync_custom_field( $post_id_from, $translation_id, $meta_key ); } } } } /** * @param int $post_id_from */ public function sync_all_custom_fields( $post_id_from ) { foreach ( $this->fields_to_sync as $meta_key ) { $this->sync_to_translations( $post_id_from, $meta_key ); } } /** * @param int $post_id_from * @param int $post_id_to * @param string $meta_key */ public function sync_custom_field( $post_id_from, $post_id_to, $meta_key ) { $custom_fields_from = get_post_meta( $post_id_from ); $custom_fields_to = get_post_meta( $post_id_to ); $values_from = isset( $custom_fields_from[ $meta_key ] ) ? $custom_fields_from[ $meta_key ] : []; $values_to = isset( $custom_fields_to[ $meta_key ] ) ? $custom_fields_to[ $meta_key ] : []; $removed = array_diff( $values_to, $values_from ); foreach ( $removed as $v ) { delete_post_meta( $post_id_to, $meta_key, maybe_unserialize( $v ) ); } $added = array_diff( $values_from, $values_to ); $args = [ 'values_from' => $values_from, 'values_to' => $values_to, 'removed' => $removed, 'added' => $added, ]; foreach ( $added as $v ) { $copied_value = maybe_unserialize( $v ); /** * Filters the $copied_value of $meta_key which will be copied from $post_id_from to $post_id_to * * @param mixed $copied_value The unserialized and slashed value. * @param int $post_id_from The ID of the source post. * @param int $post_id_to The ID of the destination post. * @param string $meta_key The key of the post meta being copied. * @param array $args The internal parameters. * * @since 4.3.0 */ $copied_value = apply_filters( 'wpml_sync_custom_field_copied_value', $copied_value, $post_id_from, $post_id_to, $meta_key, $args ); $copied_value = wp_slash( $copied_value ); add_post_meta( $post_id_to, $meta_key, $copied_value ); } do_action( 'wpml_after_copy_custom_field', $post_id_from, $post_id_to, $meta_key ); } } custom-field-translation/class-wpml-translate-link-targets-in-custom-fields.php 0000755 00000006356 14720342453 0024064 0 ustar 00 <?php class WPML_Translate_Link_Targets_In_Custom_Fields extends WPML_Translate_Link_Targets { /* @var TranslationManagement $tm_instance */ private $tm_instance; /* @var WPML_WP_API $wp_api */ private $wp_api; /* @var array $meta_keys */ private $meta_keys; /** * WPML_Translate_Link_Targets_In_Custom_Fields constructor. * * @param TranslationManagement $tm_instance * @param WPML_WP_API $wp_api * @param AbsoluteLinks $absolute_links * @param WPML_Absolute_To_Permalinks $permalinks_converter */ public function __construct( &$tm_instance, &$wp_api, $absolute_links, $permalinks_converter ) { parent::__construct( $absolute_links, $permalinks_converter ); $this->tm_instance = &$tm_instance; $this->wp_api = &$wp_api; $this->tm_instance->load_settings_if_required(); if ( isset( $this->tm_instance->settings['custom_fields_translate_link_target'] ) && ! empty( $this->tm_instance->settings['custom_fields_translate_link_target'] ) ) { $this->meta_keys = $this->tm_instance->settings['custom_fields_translate_link_target']; } } public function has_meta_keys() { return (bool) $this->meta_keys; } /** * maybe_translate_link_targets * * @param string|array $metadata - Always null for post metadata. * @param int $object_id - Post ID for post metadata * @param string $meta_key - metadata key. * @param bool $single - Indicates if processing only a single $metadata value or array of values. * * @return string|array Original or Modified $metadata. */ public function maybe_translate_link_targets( $metadata, $object_id, $meta_key, $single ) { if ( array_key_exists( $meta_key, $this->meta_keys ) ) { $custom_field_setting = new WPML_Post_Custom_Field_Setting( $this->tm_instance, $meta_key ); if ( $custom_field_setting->is_translate_link_target() ) { $sub_fields = $custom_field_setting->get_translate_link_target_sub_fields(); $this->wp_api->remove_filter( 'get_post_metadata', array( $this, 'maybe_translate_link_targets' ), 10 ); $metadata_raw = maybe_unserialize( $this->wp_api->get_post_meta( $object_id, $meta_key, $single ) ); $this->wp_api->add_filter( 'get_post_metadata', array( $this, 'maybe_translate_link_targets' ), 10, 4 ); if ( $metadata_raw ) { if ( $single ) { $metadata_raw = array( $metadata_raw ); } foreach ( $metadata_raw as $index => $metadata ) { if ( ! empty( $sub_fields ) ) { $metadata = $this->convert_sub_fields( $sub_fields, $metadata ); } else { $metadata = $this->convert_text( $metadata ); } $metadata_raw[ $index ] = $metadata; } if ( $single && ! is_array( $metadata_raw[0] ) ) { $metadata_raw = $metadata_raw[0]; } } $metadata = $metadata_raw; } } return $metadata; } private function convert_sub_fields( $sub_fields, $metadata ) { foreach ( $sub_fields as $sub_field ) { if ( isset( $sub_field['value'], $sub_field['attr']['translate_link_target'] ) && $sub_field['attr']['translate_link_target'] ) { $key = trim( $sub_field['value'] ); if ( isset( $metadata[ $key ] ) ) { $metadata[ $key ] = $this->convert_text( $metadata[ $key ] ); } } } return $metadata; } } custom-field-translation/class-wpml-custom-fields-post-meta-info.php 0000755 00000003766 14720342453 0021725 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_Fields_Post_Meta_Info implements IWPML_Action { const RESOURCES_HANDLE = 'wpml-cf-info'; const AJAX_ACTION = 'wpml-cf-info-get'; private $translatable_element_factory; /** * WPML_Custom_Fields_Post_Meta_Info constructor. * * @param WPML_Translation_Element_Factory $translatable_element_factory */ public function __construct( WPML_Translation_Element_Factory $translatable_element_factory ) { $this->translatable_element_factory = $translatable_element_factory; } public function add_hooks() { add_action( 'wp_ajax_wpml_cf_get_info', array( $this, 'get_info_ajax' ) ); add_filter( 'wpml_custom_field_original_data', array( $this, 'get_info_filter' ), 10, 3 ); } public function get_info_ajax() { $result = null; if ( check_ajax_referer( self::AJAX_ACTION, 'nonceGet', false ) ) { $meta_id = filter_var( $_GET['meta_id'], FILTER_SANITIZE_NUMBER_INT ); if ( $meta_id ) { /** @var \stdClass $custom_field */ $custom_field = get_post_meta_by_id( (int) $meta_id ); if ( $custom_field ) { $post_id = $custom_field->post_id; $meta_key = $custom_field->meta_key; $result = $this->get_info( $post_id, $meta_key ); if ( $result ) { wp_send_json_success( $result ); return; } } else { $result = 'Custom field not found'; } } else { $result = 'Missing meta_id'; } } wp_send_json_error( $result ); } public function get_info_filter( $ignore, $post_id, $meta_key ) { return $this->get_info( $post_id, $meta_key ); } private function get_info( $post_id, $meta_key ) { $post_element = $this->translatable_element_factory->create( $post_id, 'post' ); $original_element = $post_element->get_source_element(); if ( $original_element && $original_element->get_id() !== $post_element->get_id() ) { return array( 'meta_key' => $meta_key, 'value' => get_post_meta( $original_element->get_id(), $meta_key, true ) ); } return null; } } custom-field-translation/class-wpml-custom-fields-post-meta-info-factory.php 0000755 00000000571 14720342453 0023361 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_Fields_Post_Meta_Info_Factory implements IWPML_AJAX_Action_Loader, IWPML_Backend_Action_Loader { public function create() { global $sitepress; $translatable_element_factory = new WPML_Translation_Element_Factory( $sitepress ); return new WPML_Custom_Fields_Post_Meta_Info( $translatable_element_factory ); } } custom-field-translation/class-wpml-copy-once-custom-field.php 0000755 00000004314 14720342453 0020562 0 ustar 00 <?php class WPML_Copy_Once_Custom_Field implements IWPML_Backend_Action, IWPML_Frontend_Action, IWPML_DIC_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Post_Translation $wpml_post_translation */ private $wpml_post_translation; /** * WPML_Copy_Once_Custom_Field constructor. * * @param SitePress $sitepress * @param WPML_Post_Translation $wpml_post_translation */ public function __construct( SitePress $sitepress, WPML_Post_Translation $wpml_post_translation ) { $this->sitepress = $sitepress; $this->wpml_post_translation = $wpml_post_translation; } public function add_hooks() { add_action( 'wpml_after_save_post', array( $this, 'copy' ), 10, 1 ); add_action( 'wpml_pro_translation_completed', array( $this, 'copy' ), 10, 1 ); } /** * @param int $post_id */ public function copy( $post_id ) { $custom_fields_to_copy = $this->sitepress->get_custom_fields_translation_settings( WPML_COPY_ONCE_CUSTOM_FIELD ); if ( empty( $custom_fields_to_copy ) ) { return; } $source_element_id = $this->wpml_post_translation->get_original_element( $post_id ); $custom_fields = get_post_meta( $post_id ); foreach ( $custom_fields_to_copy as $meta_key ) { $values = isset( $custom_fields[ $meta_key ] ) && ! empty( $custom_fields[ $meta_key ] ) ? [ $custom_fields[ $meta_key ] ] : []; /** * Custom fields values for given post obtained directly from database * * @since 4.1 * * @param array<mixed> $values Custom fields values as they are in the database * @param array<int|string> $args { * @type int $post_id ID of post associated with custom field * @type string $meta_key custom fields meta key * @type int $custom_fields_translation field translation option * * } */ $values = apply_filters( 'wpml_custom_field_values', $values, [ 'post_id' => $post_id, 'meta_key' => $meta_key, 'custom_fields_translation' => WPML_COPY_ONCE_CUSTOM_FIELD, ] ); if ( empty( $values ) && $source_element_id ) { $this->sitepress->sync_custom_field( $source_element_id, $post_id, $meta_key ); } } } } browser-language-redirect/wpml-browser-language-redirect-dialog.php 0000755 00000003235 14720342453 0021607 0 ustar 00 <?php namespace WPML\BrowserLanguageRedirect; use WPML\LIB\WP\App\Resources; use WPML\LIB\WP\Nonce; class Dialog implements \IWPML_Backend_Action { const ACCEPTED = 'accepted'; const USER_META = 'wpml-browser-redirect-dialog'; const ACCEPT_ACTION = 'accept_wpml_browser_language_redirect_message'; const NONCE_KEY = 'wpml-browser-language-redirect-message'; public function add_hooks() { add_action( 'admin_notices', [ $this, 'print_dialog_container' ] ); add_action( 'admin_head', [ $this, 'enqueue_res' ] ); add_action( 'wp_ajax_' . self::ACCEPT_ACTION, [ $this, 'accept' ] ); } public function enqueue_res() { if ( $this->should_print_dialog() ) { Resources::enqueue( 'browser-language-redirect-dialog', ICL_PLUGIN_URL, WPML_PLUGIN_PATH, ICL_SITEPRESS_VERSION, null, [ 'name' => 'wpmlBrowserLanguageRedirectDialog', 'data' => [ 'endpoint' => self::ACCEPT_ACTION, ], ] ); } } public function print_dialog_container() { if ( $this->should_print_dialog() ) { ?> <div id="browser-language-redirect-dialog"></div> <?php } } public function accept() { if ( Nonce::verify( self::NONCE_KEY, wpml_collect( $_POST ) ) ) { update_user_meta( get_current_user_id(), self::USER_META, self::ACCEPTED ); } } private function should_print_dialog() { return ! $this->is_accepted() && $this->is_languages_page(); } private function is_accepted() { return get_user_meta( get_current_user_id(), self::USER_META, true ) === self::ACCEPTED; } private function is_languages_page() { return isset( $_GET['page'] ) && WPML_PLUGIN_FOLDER . '/menu/languages.php' === $_GET['page']; } } translate_link_targets/class-wpml-translate-link-targets-in-strings.php 0000755 00000003216 14720342453 0022603 0 ustar 00 <?php /** * Class WPML_Translate_Link_Targets_In_Strings * * @package wpml-tm */ class WPML_Translate_Link_Targets_In_Strings extends WPML_Translate_Link_Targets_In_Content { private $option_name = 'wpml_strings_need_links_fixed'; /* var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_Translate_Link_Target_Global_State $translate_link_target_global_state, &$wpdb, $wp_api, $pro_translation ) { parent::__construct( $translate_link_target_global_state, $wpdb, $pro_translation ); $this->wp_api = $wp_api; } protected function get_contents_with_links_needing_fix( $start = 0, $count = 0 ) { $strings_to_fix = $this->wp_api->get_option( $this->option_name, array() ); sort( $strings_to_fix, SORT_NUMERIC ); $strings_to_fix_part = array(); $include_all = $count == 0 ? true : false; foreach ( $strings_to_fix as $string_id ) { if ( $string_id >= $start ) { $strings_to_fix_part[] = $string_id; } if ( ! $include_all ) { $count--; if ( $count == 0 ) { break; } } } $this->content_to_fix = array(); if ( sizeof( $strings_to_fix_part ) ) { $strings_to_fix_part = wpml_prepare_in( $strings_to_fix_part, '%d' ); $this->content_to_fix = $this->wpdb->get_results( "SELECT id as element_id, language as language_code FROM {$this->wpdb->prefix}icl_string_translations WHERE id in ( {$strings_to_fix_part} )" ); } } protected function get_content_type() { return 'string'; } public function get_number_to_be_fixed( $start_id = 0, $limit = 0 ) { $this->get_contents_with_links_needing_fix( $start_id ); return sizeof( $this->content_to_fix ); } } translate_link_targets/Hooks.php 0000755 00000001135 14720342453 0013112 0 ustar 00 <?php namespace WPML\TranslateLinkTargets; use WPML\LIB\WP\Hooks as WPHooks; use WPML\LIB\WP\Post; use function WPML\FP\spreadArgs; class Hooks implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { public function create() { return [ self::class, 'add_hooks' ]; } public static function add_hooks() { WPHooks::onAction( 'wpml_pro_translation_completed', 10, 1 ) ->then( spreadArgs( [ self::class, 'clearStatus' ] ) ); } public static function clearStatus( $postId ) { \WPML_Links_Fixed_Status_For_Posts::clear( $postId, 'post_' . Post::getType( $postId ) ); } } translate_link_targets/class-wpml-ajax-update-link-targets-in-content.php 0000755 00000003057 14720342453 0022775 0 ustar 00 <?php abstract class WPML_Ajax_Update_Link_Targets_In_Content extends WPML_WPDB_User implements IWPML_AJAX_Action_Run { /** @var WPML_Translate_Link_Targets_In_Content $translate_link_targets */ private $translate_link_targets; private $post_data; /** @var WPML_Translate_Link_Target_Global_State $translate_link_target_global_state */ protected $translate_link_target_global_state; public function __construct( WPML_Translate_Link_Target_Global_State $translate_link_target_global_state, &$wpdb, $post_data ) { parent::__construct( $wpdb ); $this->translate_link_target_global_state = $translate_link_target_global_state; $this->translate_link_targets = $this->create_translate_link_target(); $this->post_data = $post_data; } public function run() { if ( wp_verify_nonce( $this->post_data['nonce'], 'WPML_Ajax_Update_Link_Targets' ) ) { $this->translate_link_target_global_state->clear_rescan_required(); $last_processed = $this->translate_link_targets->fix( $this->post_data['last_processed'], $this->post_data['number_to_process'] ); return new WPML_Ajax_Response( true, array( 'last_processed' => (int) $last_processed, 'number_left' => $last_processed ? $this->translate_link_targets->get_number_to_be_fixed( $last_processed + 1 ) : 0, 'links_fixed' => $this->translate_link_targets->get_number_of_links_that_were_fixed(), ) ); } else { return new WPML_Ajax_Response( false, 'wrong nonce' ); } } abstract protected function create_translate_link_target(); } translate_link_targets/class-wpml-links-fixed-status-factory.php 0000755 00000001320 14720342453 0021306 0 ustar 00 <?php /** * Class WPML_Links_Fixed_Status_Factory * * @package wpml-translation-management */ class WPML_Links_Fixed_Status_Factory extends WPML_WPDB_User { private $wp_api; public function __construct( &$wpdb, $wp_api ) { parent::__construct( $wpdb ); $this->wp_api = $wp_api; } public function create( $element_id, $element_type ) { $links_fixed_status = null; if ( strpos( $element_type, 'post' ) === 0 ) { $links_fixed_status = new WPML_Links_Fixed_Status_For_Posts( $this->wpdb, $element_id, $element_type ); } elseif ( $element_type == 'string' ) { $links_fixed_status = new WPML_Links_Fixed_Status_For_Strings( $this->wp_api, $element_id ); } return $links_fixed_status; } } translate_link_targets/class-wpml-translate-link-targets-in-posts-global.php 0000755 00000000153 14720342453 0023515 0 ustar 00 <?php class WPML_Translate_Link_Targets_In_Posts_Global extends WPML_Translate_Link_Targets_In_Posts { } translate_link_targets/class-wpml-ajax-update-link-targets-in-strings.php 0000755 00000001261 14720342453 0023007 0 ustar 00 <?php class WPML_Ajax_Update_Link_Targets_In_Strings extends WPML_Ajax_Update_Link_Targets_In_Content { private $wp_api; private $pro_translation; public function __construct( WPML_Translate_Link_Target_Global_State $translate_link_target_global_state, &$wpdb, $wp_api, $pro_translation, $post_data ) { $this->wp_api = $wp_api; $this->pro_translation = $pro_translation; parent::__construct( $translate_link_target_global_state, $wpdb, $post_data ); } protected function create_translate_link_target() { return new WPML_Translate_Link_Targets_In_Strings_Global( $this->translate_link_target_global_state, $this->wpdb, $this->wp_api, $this->pro_translation ); } } translate_link_targets/class-wpml-ajax-scan-link-targets.php 0000755 00000002405 14720342453 0020357 0 ustar 00 <?php class WPML_Ajax_Scan_Link_Targets extends WPML_WPDB_User implements IWPML_AJAX_Action_Run { /** @var WPML_Translate_Link_Targets_In_Posts_Global $post_links */ private $post_links; /** @var WPML_Translate_Link_Targets_In_Strings_Global|null $post_links */ private $string_links; /** @var array $post_data */ private $post_data; /** * WPML_Ajax_Scan_Link_Targets constructor. * * @param WPML_Translate_Link_Targets_In_Posts_Global $post_links * @param ?WPML_Translate_Link_Targets_In_Strings_Global $string_links * @param array $post_data */ public function __construct( WPML_Translate_Link_Targets_In_Posts_Global $post_links, $string_links, $post_data ) { $this->post_links = $post_links; $this->string_links = $string_links; $this->post_data = $post_data; } public function run() { if ( ! wp_verify_nonce( $this->post_data['nonce'], 'WPML_Ajax_Update_Link_Targets' ) ) { return new WPML_Ajax_Response( false, 'wrong nonce' ); } return new WPML_Ajax_Response( true, [ 'post_count' => $this->post_links->get_number_to_be_fixed(), 'string_count' => $this->string_links ? $this->string_links->get_number_to_be_fixed() : 0, ] ); } } translate_link_targets/class-wpml-links-fixed-status-for-posts.php 0000755 00000002537 14720342453 0021606 0 ustar 00 <?php /** * Class WPML_Links_Fixed_Status_For_Posts * * @package wpml-tm */ class WPML_Links_Fixed_Status_For_Posts extends WPML_Links_Fixed_Status { /* @var int $translation_id */ private $translation_id; private $wpdb; public function __construct( $wpdb, $element_id, $element_type ) { $this->wpdb = $wpdb; $this->translation_id = $wpdb->get_var( $wpdb->prepare( "SELECT translation_id FROM {$wpdb->prefix}icl_translations WHERE element_id=%d AND element_type=%s", $element_id, $element_type ) ); } public function set( $status ) { $status = $status ? 1 : 0; $q = "UPDATE {$this->wpdb->prefix}icl_translation_status SET links_fixed=%d WHERE translation_id=%d"; $q_prepared = $this->wpdb->prepare( $q, array( $status, $this->translation_id ) ); $this->wpdb->query($q_prepared); } public function are_links_fixed() { $state = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT links_fixed FROM {$this->wpdb->prefix}icl_translation_status WHERE translation_id=%d", $this->translation_id ) ); return (bool) $state; } public static function clear( $element_id, $element_type ) { global $wpdb; $status = new WPML_Links_Fixed_Status_For_Posts( $wpdb, $element_id, $element_type ); $status->set( false ); } } translate_link_targets/class-wpml-translate-link-targets-in-strings-global.php 0000755 00000001515 14720342453 0024041 0 ustar 00 <?php class WPML_Translate_Link_Targets_In_Strings_Global extends WPML_Translate_Link_Targets_In_Strings { protected function get_contents_with_links_needing_fix( $start_id = 0, $count = 0 ) { $limit = ''; if ( $count > 0 ) { $limit = ' LIMIT ' . $count; } $this->content_to_fix = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT id as element_id, language as language_code FROM {$this->wpdb->prefix}icl_string_translations WHERE id >= %d AND status = %d ORDER BY id " . $limit, $start_id, ICL_TM_COMPLETE) ); } public function get_number_to_be_fixed( $start_id = 0, $limit = 0 ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(id) FROM {$this->wpdb->prefix}icl_string_translations WHERE id >= %d AND status = %d ORDER BY id ", $start_id, ICL_TM_COMPLETE) ); } } translate_link_targets/class-wpml-ajax-update-link-targets-in-posts.php 0000755 00000001141 14720342453 0022463 0 ustar 00 <?php class WPML_Ajax_Update_Link_Targets_In_Posts extends WPML_Ajax_Update_Link_Targets_In_Content { private $pro_translation; public function __construct( WPML_Translate_Link_Target_Global_State $translate_link_target_global_state, &$wpdb, $pro_translation, $post_data ) { $this->pro_translation = $pro_translation; parent::__construct( $translate_link_target_global_state, $wpdb, $post_data ); } protected function create_translate_link_target() { return new WPML_Translate_Link_Targets_In_Posts_Global( $this->translate_link_target_global_state, $this->wpdb, $this->pro_translation ); } } translate_link_targets/class-wpml-links-fixed-status-for-strings.php 0000755 00000003407 14720342453 0022124 0 ustar 00 <?php /** * Class WPML_Links_Fixed_Status_For_Posts * * @package wpml-tm */ class WPML_Links_Fixed_Status_For_Strings extends WPML_Links_Fixed_Status { private $wp_api; private $string_id; private $option_name = 'wpml_strings_need_links_fixed'; public function __construct( &$wp_api, $string_id ) { $this->wp_api = &$wp_api; $this->string_id = $string_id; } public function set( $status ) { if ( $status ) { $this->remove_string_from_strings_that_need_fixing(); } else { $this->add_string_to_strings_that_need_fixing(); } } public function are_links_fixed() { $strings_that_need_links_fixed = $this->load_strings_that_need_fixing(); return array_search( $this->string_id, $strings_that_need_links_fixed ) === false; } private function remove_string_from_strings_that_need_fixing() { $strings_that_need_links_fixed = $this->load_strings_that_need_fixing(); if ( ( $key = array_search( $this->string_id, $strings_that_need_links_fixed ) ) !== false ) { unset( $strings_that_need_links_fixed[ $key ] ); } $this->save_strings_that_need_fixing( $strings_that_need_links_fixed ); } private function add_string_to_strings_that_need_fixing() { $strings_that_need_links_fixed = $this->load_strings_that_need_fixing(); if ( ( array_search( $this->string_id, $strings_that_need_links_fixed ) ) === false ) { $strings_that_need_links_fixed[] = $this->string_id; } $this->save_strings_that_need_fixing( $strings_that_need_links_fixed ); } private function load_strings_that_need_fixing() { return $this->wp_api->get_option( $this->option_name, array() ); } private function save_strings_that_need_fixing( $strings_that_need_links_fixed ) { $this->wp_api->update_option( $this->option_name, $strings_that_need_links_fixed ); } } translate_link_targets/class-wpml-translate-link-target-global-state.php 0000755 00000002075 14720342453 0022703 0 ustar 00 <?php class WPML_Translate_Link_Target_Global_State extends WPML_SP_User { private $rescan_required; const OPTION_NAME = 'WPML_Translate_Link_Target_Global_State'; const SHOULD_FIX_CONTENT_STATE = 'WPML_Translate_Link_Target_Global_State::should_fix_content'; public function __construct( SitePress &$sitepress ) { parent::__construct( $sitepress ); $this->rescan_required = $sitepress->get_setting( self::OPTION_NAME, false ); } public function should_fix_content() { return $this->sitepress->get_current_request_data( self::SHOULD_FIX_CONTENT_STATE, true ); } public function is_rescan_required() { return $this->rescan_required; } public function set_rescan_required() { $this->rescan_required = true; $this->sitepress->set_setting( self::OPTION_NAME, $this->rescan_required, true ); $this->sitepress->set_current_request_data( self::SHOULD_FIX_CONTENT_STATE, false ); } public function clear_rescan_required() { $this->rescan_required = false; $this->sitepress->set_setting( self::OPTION_NAME, $this->rescan_required, true ); } } translate_link_targets/class-wpml-translate-link-targets-in-posts.php 0000755 00000003106 14720342453 0022260 0 ustar 00 <?php /** * Class WPML_Translate_Link_Targets_In_Posts * * @package wpml-tm * * Disable phpcs warnings for prepare. Everything is escaped properly. * phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared */ class WPML_Translate_Link_Targets_In_Posts extends WPML_Translate_Link_Targets_In_Content { protected function get_contents_with_links_needing_fix( $start_id = 0, $count = 0 ) { $this->content_to_fix = $this->wpdb->get_results( $this->get_sql( $start_id, $count, false ) ); } protected function get_content_type() { return 'post'; } public function get_number_to_be_fixed( $start_id = 0, $limit = 0 ) { if ( $limit > 0 ) { $data = $this->wpdb->get_results( $this->get_sql( $start_id, $limit, false ) ); return is_array( $data ) ? count( $data ) : 0; } return $this->wpdb->get_var( $this->get_sql( $start_id, 0, true ) ); } protected function get_sql( $start_id, $count, $return_count_only ) { $limit = ''; if ( $count > 0 ) { $limit = ' LIMIT ' . $count; } if ( $return_count_only ) { $sql = 'SELECT COUNT(t.element_id)'; } else { $sql = 'SELECT t.element_id, t.language_code'; } $sql = $this->wpdb->prepare( $sql . " FROM {$this->wpdb->prefix}icl_translations AS t INNER JOIN {$this->wpdb->prefix}icl_translation_status AS ts ON t.translation_id = ts.translation_id WHERE ts.links_fixed = 0 AND t.element_id IS NOT NULL AND t.element_id >= %d AND t.element_type LIKE 'post_%%' ORDER BY t.element_id ASC" . $limit, $start_id ); return $sql; } } translate_link_targets/class-wpml-links-fixed-status.php 0000755 00000000340 14720342453 0017642 0 ustar 00 <?php /** * Class WPML_Links_Fixed_Status * * @package wpml-translation-management */ abstract class WPML_Links_Fixed_Status { abstract public function set( $status ); abstract public function are_links_fixed(); } translate_link_targets/class-wpml-translate-link-targets-in-content.php 0000755 00000004505 14720342453 0022566 0 ustar 00 <?php /** * Class WPML_Translate_Link_Targets_In_Content * * @package wpml-tm */ abstract class WPML_Translate_Link_Targets_In_Content extends WPML_WPDB_User { protected $scanning_in_progress = false; protected $content_to_fix; protected $number_of_links_fixed; /* var WPML_Pro_Translation $pro_translation */ protected $pro_translation; /** @var WPML_Translate_Link_Target_Global_State $translate_link_target_global_state */ private $translate_link_target_global_state; const MAX_TO_FIX_FOR_NEW_CONTENT = 10; public function __construct( WPML_Translate_Link_Target_Global_State $translate_link_target_global_state, &$wpdb, $pro_translation ) { parent::__construct( $wpdb ); $this->pro_translation = $pro_translation; $this->translate_link_target_global_state = $translate_link_target_global_state; } public function new_content() { if ( $this->translate_link_target_global_state->should_fix_content() ) { if ( ! $this->do_new_content() ) { $this->translate_link_target_global_state->set_rescan_required(); } } } private function do_new_content() { if ( $this->pro_translation && ! $this->scanning_in_progress ) { $number_needing_to_be_fixed = $this->get_number_to_be_fixed( 0, self::MAX_TO_FIX_FOR_NEW_CONTENT + 1 ); $this->fix( 0, self::MAX_TO_FIX_FOR_NEW_CONTENT ); return $number_needing_to_be_fixed <= self::MAX_TO_FIX_FOR_NEW_CONTENT; } else { return true; } } public function get_number_of_links_that_were_fixed() { return $this->number_of_links_fixed; } public function fix( $start = 0, $count = 0 ) { $this->scanning_in_progress = true; $this->get_contents_with_links_needing_fix( $start, $count ); $last_content_processed = 0; $this->number_of_links_fixed = 0; foreach ( $this->content_to_fix as $content ) { $this->number_of_links_fixed += $this->pro_translation->fix_links_to_translated_content( $content->element_id, $content->language_code, $this->get_content_type() ); $last_content_processed = $content->element_id; } $this->scanning_in_progress = false; return $last_content_processed; } abstract protected function get_contents_with_links_needing_fix( $start = 0, $count = 0 ); abstract protected function get_content_type(); abstract public function get_number_to_be_fixed( $start_id = 0, $limit = 0 ); } class-wpml-translation-proxy-api.php 0000755 00000000416 14720342453 0013631 0 ustar 00 <?php class WPML_Translation_Proxy_API { public function get_current_service_name() { return TranslationProxy::get_current_service_name(); } public function has_preferred_translation_service() { return TranslationProxy::has_preferred_translation_service(); } } Installer/class-wpml-installer-domain-url.php 0000755 00000000655 14720342453 0015351 0 ustar 00 <?php class WPML_Installer_Domain_URL { private $site_url_in_default_lang; public function __construct( $site_url_in_default_lang ) { $this->site_url_in_default_lang = $site_url_in_default_lang; } public function add_hooks() { add_filter( 'otgs_installer_site_url', array( $this, 'get_site_url_in_default_lang' ) ); } public function get_site_url_in_default_lang() { return $this->site_url_in_default_lang; } } Installer/class-wpml-installer-gateway.php 0000755 00000001556 14720342453 0014744 0 ustar 00 <?php class WPML_Installer_Gateway { private static $the_instance; private function __construct() {} private function __clone() {} public static function get_instance() { if ( ! self::$the_instance ) { self::$the_instance = new WPML_Installer_Gateway(); } return self::$the_instance; } public static function set_instance( $instance ) { self::$the_instance = $instance; } public function class_exists() { return class_exists( 'WP_Installer_API' ); } public function get_site_key( $repository_id = 'wpml' ) { return WP_Installer_API::get_site_key( $repository_id ); } public function get_ts_client_id( $repository_id = 'wpml' ) { return WP_Installer_API::get_ts_client_id( $repository_id ); } public function get_registering_user_id( $repository_id = 'wpml' ) { return WP_Installer_API::get_registering_user_id( $repository_id ); } } Installer/class-wpml-installer-domain-url-factory.php 0000755 00000001071 14720342453 0017007 0 ustar 00 <?php class WPML_Installer_Domain_URL_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { public function create() { global $sitepress; if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN === (int) $sitepress->get_setting( 'language_negotiation_type' ) ) { /* @var WPML_URL_Converter $wpml_url_converter */ global $wpml_url_converter; $site_url_default_lang = $wpml_url_converter->get_default_site_url(); if ( $site_url_default_lang ) { return new WPML_Installer_Domain_URL( $site_url_default_lang ); } } return null; } } Installer/DisableRegisterNow.php 0000755 00000000573 14720342453 0012762 0 ustar 00 <?php namespace WPML\Installer; use WPML\LIB\WP\Hooks; use function WPML\FP\spreadArgs; class DisableRegisterNow implements \IWPML_Backend_Action { public function add_hooks() { Hooks::onFilter( 'otgs_installer_display_subscription_notice' ) ->then( spreadArgs( function ( $notice ) { return $notice['repo'] === 'wpml' ? false : $notice; } ) ); } } Installer/AddSiteUrl.php 0000755 00000000744 14720342453 0011226 0 ustar 00 <?php namespace WPML\Installer; use WPML\FP\Obj; use WPML\LIB\WP\Hooks; use function WPML\FP\spreadArgs; class AddSiteUrl implements \IWPML_Backend_Action { public function add_hooks() { Hooks::onFilter( 'otgs_installer_add_site_url', 10, 2 ) ->then( spreadArgs( function ( $url, $repoID ) { return $repoID === 'wpml' ? ( $url . '&wpml_version=' . Obj::prop( 'Version', get_plugin_data( WPML_PLUGIN_PATH . '/' . WPML_PLUGIN_FILE ) ) ) : $url; } ) ); } } custom-xml-config/class-wpml-custom-xml-factory.php 0000755 00000000312 14720342453 0016470 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_XML_Factory { public function create_resources( WPML_WP_API $wpml_wp_api ) { return new WPML_Custom_XML_UI_Resources( $wpml_wp_api ); } } custom-xml-config/class-wpml-custom-xml-ui-resources.php 0000755 00000002137 14720342453 0017455 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_XML_UI_Resources { private $wpml_wp_api; /** * @var string */ private $wpml_core_url; function __construct( WPML_WP_API $wpml_wp_api) { $this->wpml_wp_api = $wpml_wp_api; $this->wpml_core_url = $this->wpml_wp_api->constant( 'ICL_PLUGIN_URL' ); } function admin_enqueue_scripts() { if ( $this->wpml_wp_api->is_tm_page( 'custom-xml-config', 'settings' ) ) { $core_version = $this->wpml_wp_api->constant( 'ICL_SITEPRESS_VERSION' ); $siteUrl = get_rest_url(); wp_register_script( 'wpml-custom-xml-config', $this->wpml_core_url . '/dist/js/xmlConfigEditor/app.js', [], $core_version ); wp_localize_script( 'wpml-custom-xml-config', 'wpmlCustomXML', [ 'restNonce' => wp_create_nonce( 'wp_rest' ), 'endpoint' => $siteUrl . 'wpml/v1/custom-xml-config', ] ); wp_register_style( 'wpml-custom-xml-config', $this->wpml_core_url . '/dist/css/xmlConfigEditor/styles.css', [], $core_version ); wp_enqueue_style( 'wpml-custom-xml-config' ); wp_enqueue_script( 'wpml-custom-xml-config' ); } } } custom-xml-config/class-wpml-custom-xml-ui-hooks.php 0000755 00000001650 14720342453 0016565 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_XML_UI_Hooks { /** @var WPML_Custom_XML_UI_Resources */ private $resources; public function __construct( WPML_Custom_XML_UI_Resources $resources ) { $this->resources = $resources; } public function init() { add_filter( 'wpml_tm_tab_items', array( $this, 'add_items' ) ); add_action( 'admin_enqueue_scripts', array( $this->resources, 'admin_enqueue_scripts' ) ); } public function add_items( $tab_items ) { $tab_items['custom-xml-config']['caption'] = __( 'Custom XML Configuration', 'wpml-translation-management' ); $tab_items['custom-xml-config']['callback'] = array( $this, 'build_content' ); $tab_items['custom-xml-config']['current_user_can'] = 'manage_options'; return $tab_items; } public function build_content() { echo '<div id="wpml-tm-custom-xml-content" class="wpml-tm-custom-xml js-wpml-tm-custom-xml"></div>'; } } options/class-wpml-wp-option.php 0000755 00000000546 14720342453 0013000 0 ustar 00 <?php /** * @author OnTheGo Systems */ abstract class WPML_WP_Option { abstract public function get_key(); abstract public function get_default(); public function get() { return get_option( $this->get_key(), $this->get_default() ); } public function set( $value, $autoload = true ) { update_option( $this->get_key(), $value, $autoload ); } } options/class-wpml-multilingual-options.php 0000755 00000012440 14720342453 0015237 0 ustar 00 <?php /** * Class WPML_Multilingual_Options */ class WPML_Multilingual_Options { private $array_helper; private $registered_options = array(); private $sitepress; private $utils; /** * WPML_Multilingual_Options constructor. * * @param SitePress $sitepress * @param WPML_Multilingual_Options_Array_Helper $array_helper * @param WPML_Multilingual_Options_Utils $utils */ public function __construct( SitePress $sitepress, WPML_Multilingual_Options_Array_Helper $array_helper, WPML_Multilingual_Options_Utils $utils ) { $this->sitepress = $sitepress; $this->array_helper = $array_helper; $this->utils = $utils; } /** * @param string $new_code New WPML default language code * @param string $previous_default Previous WPML default language code */ public function default_language_changed_action( $new_code, $previous_default ) { if ( $new_code !== $previous_default ) { foreach ( $this->registered_options as $option_name ) { $default_options = $this->utils->get_option_without_filtering( $option_name, null ); $translated_option = get_option( "{$option_name}_{$new_code}", null ); if ( $translated_option ) { $new_value = $this->merge( $default_options, $translated_option ); remove_filter( "pre_option_{$option_name}", array( $this, 'pre_option_filter' ), 10 ); remove_filter( "pre_update_option_{$option_name}", array( $this, 'pre_update_option_filter', ), 10 ); update_option( $option_name, $new_value ); delete_option( "{$option_name}_{$new_code}" ); $new_translated_option = $default_options; if ( is_array( $default_options ) && is_array( $new_value ) ) { $new_translated_option = $this->array_helper->array_diff_recursive( $default_options, $new_value ); } update_option( "{$option_name}_{$previous_default}", $new_translated_option, 'no' ); $this->multilingual_options_action( $option_name ); } $this->invalidate_cache( $option_name, $previous_default ); $this->invalidate_cache( $option_name, $new_code ); } } } /** * @param string $option_name */ public function multilingual_options_action( $option_name ) { if ( ! in_array( $option_name, $this->registered_options, true ) ) { $this->registered_options[] = $option_name; $current_language = $this->sitepress->get_current_language(); $default_language = $this->sitepress->get_default_language(); if ( $current_language !== $default_language ) { add_filter( "pre_option_{$option_name}", array( $this, 'pre_option_filter' ), 10, 2 ); add_filter( "pre_update_option_{$option_name}", array( $this, 'pre_update_option_filter' ), 10, 3 ); } } } public function init_hooks() { add_action( 'wpml_multilingual_options', array( $this, 'multilingual_options_action' ) ); add_action( 'icl_after_set_default_language', array( $this, 'default_language_changed_action' ), 10, 2 ); } /** * @param mixed $value * @param string $option_name * * @return mixed */ public function pre_option_filter( $value, $option_name ) { $current_language = $this->sitepress->get_current_language(); $cache_found = null; $options_filtered = wp_cache_get( "{$option_name}_{$current_language}_filtered", 'options', false, $cache_found ); if ( $cache_found ) { return $options_filtered; } $default_options = $this->utils->get_option_without_filtering( $option_name, null ); $translated_value = get_option( "{$option_name}_{$current_language}", $value ); $value = $this->merge( $default_options, $translated_value ); $this->update_cache( $option_name, $current_language, $value ); return $value; } /** * @param string $option_name * @param string $language * @param mixed $value * * @return bool */ private function update_cache( $option_name, $language, $value ) { return wp_cache_set( "{$option_name}_{$language}_filtered", $value, 'options' ); } /** * @param array<mixed>|mixed $new_value * @param array<mixed>|mixed $old_value * @param string $option_name * * @return array */ public function pre_update_option_filter( $new_value, $old_value, $option_name ) { $current_language = $this->sitepress->get_current_language(); $default_options = $this->utils->get_option_without_filtering( $option_name, null ); $translated_option = null; if ( is_array( $new_value ) && is_array( $default_options ) ) { $translated_option = $this->array_helper->array_diff_recursive( $new_value, $default_options ); } elseif ( $new_value !== $default_options ) { $translated_option = $new_value; } update_option( "{$option_name}_{$current_language}", $translated_option, 'no' ); $this->invalidate_cache( $option_name, $current_language ); return $default_options; } /** * @param string $option_name * @param string $language * * @return bool */ private function invalidate_cache( $option_name, $language ) { return wp_cache_delete( "{$option_name}_{$language}_filtered", 'options' ); } /** * @param array $target * @param array $source * * @return array */ private function merge( $target, $source ) { $value = $source; if ( is_array( $source ) && is_array( $target ) ) { $value = $this->array_helper->recursive_merge( $target, $source ); } return $value; } } options/class-wpml-multilingual-options-utils.php 0000755 00000001401 14720342453 0016370 0 ustar 00 <?php /** * Class WPML_Multilingual_Options_Utils */ class WPML_Multilingual_Options_Utils { /** @var wpdb */ private $wpdb; /** * WPML_Multilingual_Options_Utils constructor. * * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string $option_name * @param mixed $default * * @return mixed|null */ public function get_option_without_filtering( $option_name, $default = null ) { $value_query = "SELECT option_value FROM {$this->wpdb->options} WHERE option_name = %s LIMIT 1"; $value_sql = $this->wpdb->prepare( $value_query, $option_name ); $value = $this->wpdb->get_var( $value_sql ); return $value ? maybe_unserialize( $value ) : $default; } } options/class-wpml-multilingual-options-array-helper.php 0000755 00000002416 14720342453 0017632 0 ustar 00 <?php /** * Class WPML_Multilingual_Options_Array_Helper */ class WPML_Multilingual_Options_Array_Helper { /** * @param array $value1 * @param array $value2 * * @return array */ public function array_diff_recursive( array $value1, array $value2 ) { $diff = array(); foreach ( $value1 as $k => $v ) { if ( $this->in_array( $value2, $v, $k ) ) { $temp_diff = $this->array_diff_recursive( $v, $value2[ $k ] ); if ( $temp_diff ) { $diff[ $k ] = $temp_diff; } } elseif ( ! array_key_exists( $k, $value2 ) || $v !== $value2[ $k ] ) { $diff[ $k ] = $v; } } return $diff; } /** * @param array $target * @param array $source * * @return array */ public function recursive_merge( array $target, array $source ) { foreach ( $source as $k => $v ) { if ( $this->in_array( $target, $v, $k ) ) { $target[ $k ] = $this->recursive_merge( $target[ $k ], $v ); } else { $target[ $k ] = $v; } } return $target; } /** * @param array $haystack * @param mixed $needle * @param string $needle_key * * @return bool */ private function in_array( array $haystack, $needle, $needle_key ) { return is_array( $needle ) && array_key_exists( $needle_key, $haystack ) && is_array( $haystack[ $needle_key ] ); } } options/Reset.php 0000755 00000000340 14720342453 0010036 0 ustar 00 <?php namespace WPML\Options; use WPML\WP\OptionManager; class Reset implements \IWPML_Backend_Action { public function add_hooks() { add_filter( 'wpml_reset_options', [ new OptionManager(), 'reset_options' ] ); } } localization/class-wpml-download-localization.php 0000755 00000004407 14720342453 0016336 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Download_Localization { private $active_languages; private $default_language; private $not_founds = array(); private $errors = array(); /** * WPML_Localization constructor. * * @param array $active_languages * @param string $default_language */ public function __construct( array $active_languages, $default_language ) { $this->active_languages = $active_languages; $this->default_language = $default_language; } public function download_language_packs() { $results = array(); if ( $this->active_languages ) { if ( ! function_exists( 'request_filesystem_credentials' ) ) { /** WordPress Administration File API */ require_once ABSPATH . 'wp-admin/includes/file.php'; } $translation_install_file = get_home_path() . 'wp-admin/includes/translation-install.php'; if ( ! file_exists( $translation_install_file ) ) { return array(); } if ( ! function_exists( 'wp_can_install_language_pack' ) ) { /** WordPress Translation Install API */ require_once $translation_install_file; } if ( ! function_exists( 'submit_button' ) ) { /** WordPress Administration File API */ require_once ABSPATH . 'wp-admin/includes/template.php'; } if ( ! wp_can_install_language_pack() ) { $this->errors[] = 'wp_can_install_language_pack'; } else { foreach ( $this->active_languages as $active_language ) { $result = $this->download_language_pack( $active_language ); if ( $result ) { $results[] = $result; } } } } return $results; } public function get_not_founds() { return $this->not_founds; } public function get_errors() { return $this->errors; } private function download_language_pack( $language ) { $result = null; if ( 'en_US' !== $language['default_locale'] ) { if ( $language['default_locale'] ) { $result = wp_download_language_pack( $language['default_locale'] ); } if ( ! $result && $language['tag'] ) { $result = wp_download_language_pack( $language['tag'] ); } if ( ! $result && $language['code'] ) { $result = wp_download_language_pack( $language['code'] ); } if ( ! $result ) { $result = null; $this->not_founds[] = $language; } } return $result; } } translation-roles/endpoints/GetTranslatorRecords.php 0000755 00000000771 14720342453 0017067 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\Ajax\IHandler; use WPML\FP\Either; use WPML\FP\Fns; use WPML\LIB\WP\User; use function WPML\Container\make; class GetTranslatorRecords implements IHandler { /** * @inheritDoc */ public function run( Collection $data ) { $translators = make( \WPML_Translator_Records::class )->get_users_with_capability(); return Either::of( [ 'translators' => Fns::map( User::withAvatar(), $translators ) ] ); } } translation-roles/endpoints/SaveTranslator.php 0000755 00000002225 14720342453 0015720 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\partial; use function WPML\FP\partialRight; use function WPML\FP\pipe; class SaveTranslator extends SaveUser { /** * @inheritDoc */ public function run( Collection $data ) { $pairs = wpml_collect( $data->get( 'pairs' ) ) ->filter( pipe( Obj::prop( 'to' ), Lst::length() ) ) ->mapWithKeys( function ( $pair ) { return [ $pair['from'] => $pair['to'] ]; } ) ->toArray(); // $setRole :: WP_User -> WP_User $setRole = Fns::tap( invoke( 'add_cap' )->with( \WPML\LIB\WP\User::CAP_TRANSLATE ) ); // $storePairs :: int -> int $storePairs = Fns::tap( partialRight( [ make( \WPML_Language_Pair_Records::class ), 'store' ], $pairs ) ); return self::getUser( $data ) ->map( $setRole ) ->map( Obj::prop( 'ID' ) ) ->map( $storePairs ) ->map( function( $user ) { do_action( 'wpml_update_translator' ); return $user; } ) ->map( Fns::always( true ) ); } } translation-roles/endpoints/Remove.php 0000755 00000002460 14720342453 0014206 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\Ajax\IHandler; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\User; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\pipe; abstract class Remove implements IHandler { /** * @inheritDoc */ public function run( Collection $data ) { // $removeRole :: WP_user -> WP_user $removeRole = Fns::tap( invoke( 'remove_cap' )->with( static::getCap() ) ); // $removeOnlyI :: WP_user -> WP_user $removeOnlyI = Fns::tap( pipe( Obj::prop( 'ID' ), User::deleteMeta( Fns::__, \WPML_TM_Wizard_Options::ONLY_I_USER_META ) ) ); // $doActions :: WP_user -> WP_user $doActions = Fns::tap( function ( $user ) { do_action( 'wpml_tm_remove_translation_role', $user, static::getCap() ); } ); return Either::fromNullable( $data->get( 'ID' ) ) ->map( User::get() ) ->filter( invoke( 'exists' ) ) ->map( $removeRole ) ->map( $removeOnlyI ) ->map( $doActions ) ->bimap( Fns::always( $this->msgUserNotFound() ), Fns::always( true ) ); } abstract protected static function getCap(); protected function msgUserNotFound() { return __( 'User not found', 'sitepress' ); } } translation-roles/endpoints/GetManagerRecords.php 0000755 00000001063 14720342453 0016303 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\Ajax\IHandler; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\User; use function WPML\Container\make; use function WPML\FP\pipe; class GetManagerRecords implements IHandler { /** * @inheritDoc */ public function run( Collection $data ) { $managers = make( \WPML_Translation_Manager_Records::class )->get_users_with_capability(); return Either::of( [ 'managers' => Fns::map( User::withAvatar(), $managers ) ] ); } } translation-roles/endpoints/RemoveTranslator.php 0000755 00000002707 14720342453 0016264 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\LIB\WP\User; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\pipe; class RemoveTranslator extends Remove { /** * @inheritDoc */ public function run( Collection $data ) { // $removeLanguagePairs :: WP_user -> WP_user $removeLanguagePairs = Fns::tap( pipe( Obj::prop( 'ID' ), [ make( \WPML_Language_Pair_Records::class ), 'remove_all' ] ) ); // $resignFromUnfinishedJobs :: WP_user -> WP_user $resignFromUnfinishedJobs = Fns::tap( [ make( \TranslationManagement::class ), 'resign_translator_from_unfinished_jobs' ] ); $runParentRemove = function() use ( $data ) { return parent::run( $data ); }; return Either::fromNullable( $data->get( 'ID' ) ) ->map( User::get() ) ->filter( invoke( 'exists' ) ) ->map( $removeLanguagePairs ) ->map( $resignFromUnfinishedJobs ) ->bichain( pipe( Fns::always( $this->msgUserNotFound() ), Either::left() ), $runParentRemove ) ->map( function( $result ) { do_action( 'wpml_udpate_translator' ); do_action( 'wpml_tm_ate_synchronize_translators' ); return $result; } ); } protected static function getCap() { return User::CAP_TRANSLATE; } } translation-roles/endpoints/FindAvailableByRole.php 0000755 00000001601 14720342453 0016543 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\API\Sanitize; use WPML\Collect\Support\Collection; use WPML\Ajax\IHandler; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use function WPML\FP\invoke; class FindAvailableByRole implements IHandler { const USER_SEARCH_LIMIT = 10; /** * @inheritDoc */ public function run( Collection $data ) { $search = Sanitize::string( $data->get( 'search' ) ); $records = [ 'translator' => \WPML_Translator_Records::class, 'manager' => \WPML_Translation_Manager_Records::class, ]; return Either::of( $data->get( 'role' ) ) ->filter( Lst::includes( Fns::__, Obj::keys( $records ) ) ) ->map( Obj::prop( Fns::__, $records ) ) ->map( Fns::make() ) ->map( invoke( 'search_for_users_without_capability' )->with( $search, self::USER_SEARCH_LIMIT ) ); } } translation-roles/endpoints/SaveManager.php 0000755 00000005356 14720342453 0015151 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\User; use function WPML\Container\make; use function WPML\FP\invoke; use function WPML\FP\partial; class SaveManager extends SaveUser { const TRANSLATION_MANAGER_INSTRUCTIONS_TEMPLATE = 'notification/translation-manager-instructions.twig'; /** * @inheritDoc */ public function run( Collection $data ) { // $setRole :: WP_User -> WP_User $setRole = Fns::tap( invoke( 'add_cap' )->with( User::CAP_MANAGE_TRANSLATIONS ) ); return self::getUser( $data ) ->map( $setRole ) ->map( [ self::class, 'sendInstructions' ] ) ->map( function( $user ) { do_action( 'wpml_update_translator' ); do_action( 'wpml_tm_ate_synchronize_managers' ); return true; } ); } public static function sendInstructions( \WP_User $manager ) { $siteName = get_option( 'blogname' ); $translationSetupUrl = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php' ); $adminUser = User::getCurrent(); $model = [ 'setup_url' => esc_url( $translationSetupUrl ), 'username' => $manager->display_name, 'intro_message_1' => sprintf( __( 'You are the Translation Manager for %s. This role lets you manage everything related to translation for this site.', 'sitepress' ), $siteName ), 'intro_message_2' => __( 'Before you can start sending content to translation, you need to complete a short setup.', 'sitepress' ), 'setup' => __( 'Set-up the translation', 'sitepress' ), 'reminder' => sprintf( __( '* Remember, your login name for %1$s is %2$s. If you need help with your password, use the password reset in the login page.', 'sitepress' ), $siteName, $manager->user_login ), 'at_your_service' => __( 'At your service', 'sitepress' ), 'admin_name' => $adminUser->display_name, 'admin_for_site' => sprintf( __( 'Administrator for %s', 'sitepress' ), $siteName ), ]; $to = $manager->display_name . ' <' . $manager->user_email . '>'; $message = make( \WPML_TM_Email_Notification_View::class )->render_model( $model, self::TRANSLATION_MANAGER_INSTRUCTIONS_TEMPLATE ); $subject = sprintf( __( 'You are now the Translation Manager for %s - action needed', 'sitepress' ), $siteName ); $headers = array( 'MIME-Version: 1.0', 'Content-type: text/html; charset=UTF-8', 'Reply-To: ' . $adminUser->display_name . ' <' . $adminUser->user_email . '>', ); $forceDisplayName = Fns::always( $adminUser->display_name ); $sendMail = partial( 'wp_mail', $to, $subject, $message, $headers ); Hooks::callWithFilter( $sendMail, 'wp_mail_from_name', $forceDisplayName ); return true; } } translation-roles/endpoints/RemoveManager.php 0000755 00000000667 14720342453 0015510 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\LIB\WP\User; class RemoveManager extends Remove { /** * @inheritDoc */ public function run( Collection $data ) { $result = parent::run( $data ); do_action( 'wpml_update_translator' ); do_action( 'wpml_tm_ate_synchronize_managers' ); return $result; } protected static function getCap() { return User::CAP_MANAGE_TRANSLATIONS; } } translation-roles/endpoints/SaveUser.php 0000755 00000003570 14720342453 0014511 0 ustar 00 <?php namespace WPML\TranslationRoles; use WPML\Collect\Support\Collection; use WPML\Ajax\IHandler; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Left; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Right; use WPML\LIB\WP\User; use WPML\TM\Menu\TranslationRoles\RoleValidator; use WPML\LIB\WP\WordPress; use function WPML\FP\invoke; use function WPML\FP\partial; abstract class SaveUser implements IHandler { /** * @param Collection $data * * @return Left|Right */ protected static function getUser( Collection $data ) { $createNew = partial( [ self::class, 'createNewWpUser' ], $data ); return Either::fromNullable( Obj::path( [ 'user', 'ID' ], $data ) ) ->map( User::get() ) ->bichain( $createNew, Either::of() ); } /** * @param Collection $data * * @return Left|Right */ public static function createNewWpUser( Collection $data ) { $get = Obj::prop( Fns::__, $data->get( 'user' ) ); $firstName = $get( 'first' ); $lastName = $get( 'last' ); $email = filter_var( $get( 'email' ), FILTER_SANITIZE_EMAIL ); $userName = $get( 'userName' ); if ( ! RoleValidator::isValid( $get( 'wpRole' ) ) ) { return Either::left( __( 'The role was not found.', 'sitepress' ) ); } $role = RoleValidator::getTheHighestPossibleIfNotValid( $get( 'wpRole' ) ); if ( $email && $userName && $role ) { $userId = User::insert( [ 'first_name' => $firstName, 'last_name' => $lastName, 'user_email' => $email, 'user_login' => $userName, 'role' => $role, 'user_pass' => wp_generate_password(), ] ); return WordPress::handleError( $userId ) ->bimap( invoke( 'get_error_messages' ), User::notifyNew() ) ->bimap( Lst::join( ', ' ), User::get() ); } else { return Either::left( __( 'The user could not be created', 'sitepress' ) ); } } } translation-roles/UI/Initializer.php 0000755 00000010236 14720342453 0013546 0 ustar 00 <?php namespace WPML\TranslationRoles\UI; use WPML\Core\WP\App\Resources; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Wrapper; use WPML\LIB\WP\Option as WPOption; use WPML\LIB\WP\User; use WPML\Setup\Endpoint\TranslationServices; use WPML\TM\Menu\TranslationServices\Endpoints\Activate; use WPML\TM\Menu\TranslationServices\Endpoints\Deactivate; use WPML\Setup\Option; use WPML\TranslationRoles\FindAvailableByRole; use WPML\TranslationRoles\GetManagerRecords; use WPML\TranslationRoles\GetTranslatorRecords; use WPML\TranslationRoles\RemoveManager; use WPML\TranslationRoles\RemoveTranslator; use WPML\TranslationRoles\SaveManager; use WPML\TranslationRoles\SaveTranslator; use function WPML\Container\make; use function WPML\FP\pipe; class Initializer { public static function loadJS() { if ( \WPML_TM_Admin_Sections::is_translation_roles_section() ) { Wrapper::of( self::getData() )->map( Resources::enqueueApp( 'translation-roles-ui' ) ); } } public static function getData() { return [ 'name' => 'wpml_translation_roles_ui', 'data' => [ 'endpoints' => self::getEndPoints(), 'languages' => self::getLanguagesData(), /** @phpstan-ignore-next-line */ 'translation' => self::getTranslationData( User::withEditLink() ), ] ]; } public static function getEndPoints() { return [ 'findAvailableByRole' => FindAvailableByRole::class, 'saveTranslator' => SaveTranslator::class, 'removeTranslator' => RemoveTranslator::class, 'getTranslatorRecords' => GetTranslatorRecords::class, 'getManagerRecords' => GetManagerRecords::class, 'saveManager' => SaveManager::class, 'removeManager' => RemoveManager::class, 'getTranslationServices' => TranslationServices::class, 'activateService' => Activate::class, 'deactivateService' => Deactivate::class, ]; } public static function getTranslationData( callable $userExtra = null, $preload = true ) { $currentUser = User::getCurrent(); $service = Option::isTMAllowed() ? \TranslationProxy::get_current_service() : null; return [ 'canManageOptions' => $currentUser->has_cap( 'manage_options' ), 'adminUserName' => $currentUser->display_name, 'translators' => $preload ? Fns::map( User::withAvatar(), make( \WPML_Translator_Records::class )->get_users_with_capability() ) : null, 'managers' => $preload ? self::getManagers( $userExtra ) : null, 'wpRoles' => \WPML_WP_Roles::get_roles_up_to_user_level( $currentUser ), 'managerRoles' => self::getTranslationManagerRoles( $currentUser ), 'service' => ! is_wp_error( $service ) ? $service : null, ]; } public static function getLanguagesData() { $originalLang = Obj::prop( 'code', Languages::getDefault() ); $userLang = Languages::getUserLanguageCode()->getOrElse( $originalLang ); $secondaryCodes = pipe( Obj::without( $originalLang ), Obj::keys() ); return [ 'list' => Obj::values( Languages::withFlags( Languages::getAll( $userLang ) ) ), 'secondaries' => $secondaryCodes( Languages::getActive() ), 'original' => $originalLang, ]; } /** * @param \WP_User|null $currentUser * * @return array */ private static function getTranslationManagerRoles( $currentUser ) { $editorRoles = wpml_collect( \WPML_WP_Roles::get_editor_roles() ) ->pluck( 'id' ) ->reject( Relation::equals( 'administrator' ) ) ->all(); $filterManagerRoles = pipe( Obj::prop( 'id' ), Lst::includes( Fns::__, $editorRoles ) ); return Fns::filter( $filterManagerRoles, \WPML_WP_Roles::get_roles_up_to_user_level( $currentUser ) ); } /** * @param callable $userExtra * * @return array * @throws \WPML\Auryn\InjectionException */ private static function getManagers( callable $userExtra = null ) { $isAdministrator = pipe( Obj::prop( 'roles' ), Lst::includes( 'administrator' ) ); return wpml_collect( make( \WPML_Translation_Manager_Records::class )->get_users_with_capability() ) ->reject( $isAdministrator ) ->map( pipe( User::withAvatar(), $userExtra ?: Fns::identity() ) ) ->values() ->all(); } } utilities/wpml-uuid.php 0000755 00000004567 14720342453 0011236 0 ustar 00 <?php class WPML_UUID { /** * @param string $object_id * @param string $object_type * @param int $timestamp * * @return string */ public function get( $object_id, $object_type, $timestamp = null ) { $timestamp = $timestamp ? $timestamp : time(); $name = $object_id . ':' . $object_type . ':' . $timestamp; return $this->get_uuid_v5( $name, wpml_get_site_id() ); } /** * RFC 4122 compliant UUIDs. * * The RFC 4122 specification defines a Uniform Resource Name namespace for * UUIDs (Universally Unique Identifier), also known as GUIDs (Globally * Unique Identifier). A UUID is 128 bits long, and requires no central * registration process. * * @package UUID * @license https://www.gnu.org/licenses/gpl-2.0.txt GPLv2 * @author bjornjohansen * @see https://bjornjohansen.no/uuid-as-wordpress-guid * * RFC 4122 compliant UUID version 5. * * @param string $name The name to generate the UUID from. * @param string $ns_uuid Namespace UUID. Default is for the NS when name string is a URL. * * @return string The UUID string. */ public function get_uuid_v5( $name, $ns_uuid = '6ba7b811-9dad-11d1-80b4-00c04fd430c8' ) { // Compute the hash of the name space ID concatenated with the name. $hash = sha1( $ns_uuid . $name ); // Intialize the octets with the 16 first octets of the hash, and adjust specific bits later. $octets = str_split( substr( $hash, 0, 16 ), 1 ); /* * Set version to 0101 (UUID version 5). * * Set the four most significant bits (bits 12 through 15) of the * time_hi_and_version field to the appropriate 4-bit version number * from Section 4.1.3. * * That is 0101 for version 5. * time_hi_and_version is octets 6–7 */ $octets[6] = chr( ord( $octets[6] ) & 0x0f | 0x50 ); /* * Set the UUID variant to the one defined by RFC 4122, according to RFC 4122 section 4.1.1. * * Set the two most significant bits (bits 6 and 7) of the * clock_seq_hi_and_reserved to zero and one, respectively. * * clock_seq_hi_and_reserved is octet 8 */ $octets[8] = chr( ord( $octets[8] ) & 0x3f | 0x80 ); // Hex encode the octets for string representation. $octets = array_map( 'bin2hex', $octets ); // Return the octets in the format specified by the ABNF in RFC 4122 section 3. return vsprintf( '%s%s-%s-%s-%s-%s%s%s', str_split( implode( '', $octets ), 4 ) ); } } utilities/class-wpml-flags.php 0000755 00000011727 14720342453 0012463 0 ustar 00 <?php use WPML\FP\Obj; use WPML\TM\Settings\Flags\Options; /** * Class WPML_Flags * * @package wpml-core */ class WPML_Flags { /** @var icl_cache */ private $cache; /** @var wpdb $wpdb */ private $wpdb; /** @var WP_Filesystem_Direct */ private $filesystem; /** * @param wpdb $wpdb * @param icl_cache $cache * @param WP_Filesystem_Direct $filesystem */ public function __construct( $wpdb, icl_cache $cache, WP_Filesystem_Direct $filesystem ) { $this->wpdb = $wpdb; $this->cache = $cache; $this->filesystem = $filesystem; } /** * @param string $lang_code * * @return \stdClass|null */ public function get_flag( $lang_code ) { $flag = $this->cache->get( $lang_code ); if ( ! $flag ) { $flag = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT flag, from_template FROM {$this->wpdb->prefix}icl_flags WHERE lang_code=%s", $lang_code ) ); $this->cache->set( $lang_code, $flag ); } return $flag; } /** * @param string $lang_code * * @return string */ public function get_flag_url( $lang_code ) { $flag = $this->get_flag( $lang_code ); if ( ! $flag ) { return ''; } $path = ''; if ( $flag->from_template ) { $wp_upload_dir = wp_upload_dir(); $base_path = $wp_upload_dir['basedir'] . '/'; $base_url = $wp_upload_dir['baseurl']; $path = 'flags/'; } else { $base_path = self::get_wpml_flags_directory(); $base_url = self::get_wpml_flags_url(); } $path .= $flag->flag; if ( $this->flag_file_exists( $base_path . $path ) ) { return $this->append_path_to_url( $base_url, $path ); } return ''; } /** * @param string $lang_code * @param int[] $size An array describing [ $width, $height ]. It defaults to [18, 12]. * @param string $fallback_text * @param string[] $css_classes Array of CSS class strings. * * @return string */ public function get_flag_image( $lang_code, $size = [], $fallback_text = '', $css_classes = [] ) { $url = $this->get_flag_url( $lang_code ); if ( ! $url ) { return $fallback_text; } $class_attribute = is_array( $css_classes ) && ! empty( $css_classes ) ? ' class="' . implode( ' ', $css_classes ) . '"' : ''; return '<img' . $class_attribute . ' width="' . Obj::propOr( 18, 0, $size ) . '" height="' . Obj::propOr( 12, 1, $size ) . '" src="' . esc_url( $url ) . '" alt="' . esc_attr( sprintf( __( 'Flag for %s', 'sitepress' ), $lang_code ) ) . '" />'; } public function clear() { $this->cache->clear(); } /** * @param array $allowed_file_types * * @return string[] */ public function get_wpml_flags( $allowed_file_types = null ) { if ( null === $allowed_file_types ) { $allowed_file_types = array( 'gif', 'jpeg', 'png', 'svg' ); } $files = $this->filesystem->dirlist( $this->get_wpml_flags_directory(), false ); if ( ! $files ) { return []; } $files = array_keys( $files ); $result = $this->filter_flag_files( $allowed_file_types, $files ); sort( $result ); return $result; } /** * @return string */ final public function get_wpml_flags_directory() { return WPML_PLUGIN_PATH . '/res/flags/'; } /** * @return string */ final public static function get_wpml_flags_url() { return ICL_PLUGIN_URL . '/res/flags/'; } /** * @return string */ final public static function get_wpml_flags_by_locales_url() { return ICL_PLUGIN_URL . '/res/flags_by_locales.json'; } /** * @return string */ final public static function get_wpml_flag_image_ext() { return Options::getFormat(); } /** * @param string $path * * @return bool */ private function flag_file_exists( $path ) { return $this->filesystem->exists( $path ); } /** * @param array $allowed_file_types * @param array $files * * @return array */ private function filter_flag_files( $allowed_file_types, $files ) { $result = array(); foreach ( $files as $file ) { $path = $this->get_wpml_flags_directory() . $file; if ( $this->flag_file_exists( $path ) ) { $ext = pathinfo( $path, PATHINFO_EXTENSION ); if ( in_array( $ext, $allowed_file_types, true ) ) { $result[] = $file; } } } return $result; } /** * @param string $base_url * @param string $path * * @return string */ private function append_path_to_url( $base_url, $path ) { $base_url_parts = wp_parse_url( $base_url ); $base_url_path_components = array(); if ( $base_url_parts && array_key_exists( 'path', $base_url_parts ) ) { $base_url_path_components = explode( '/', untrailingslashit( $base_url_parts['path'] ) ); } $sub_dir_path_components = explode( '/', trim( $path, '/' ) ); foreach ( $sub_dir_path_components as $sub_dir_path_part ) { $base_url_path_components[] = $sub_dir_path_part; } $base_url_parts['path'] = implode( '/', $base_url_path_components ); return http_build_url( $base_url_parts ); } } utilities/AutoAdjustIds.php 0000755 00000007572 14720342453 0012035 0 ustar 00 <?php namespace WPML\Utils; use SitePress; use WPML_WP_API; class AutoAdjustIds { const WITH = true; const WITHOUT = false; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_WP_API $wp */ private $wp; /** * @param SitePress $sitepress * @param WPML_WP_API $wp */ public function __construct( SitePress $sitepress, WPML_WP_API $wp = null ) { $this->sitepress = $sitepress; $this->wp = $wp ?: $sitepress->get_wp_api(); } /** * Enables adjusting ids to retrieve translated post instead of original, runs * the given $function and afterwards restore the original behaviour again. * * @param callable $function */ public function runWith( callable $function ) { return $this->runWithOrWithout( self::WITH, $function ); } /** * Disables adjusting ids to retrieve translated post instead of original, runs * the given $function and afterwards restore the original behaviour again. * * @param callable $function * * @return mixed */ public function runWithout( callable $function ) { return $this->runWithOrWithout( self::WITHOUT, $function ); } private function runWithOrWithout( $withOrWithout, callable $function ) { // Enable / Disable adjusting of ids. $adjust_id_original_state = $this->adjustSettingAutoAdjustId( $withOrWithout ); $get_term_original_state = $this->adjustGetTermFilter( $withOrWithout ); $get_page_original_state = $this->adjustGetPagesFilter( $withOrWithout ); // Run given $function. $result = $function(); // Restore previous behaviour. $this->adjustSettingAutoAdjustId( $adjust_id_original_state ); $this->adjustGetTermFilter( $get_term_original_state ); $this->adjustGetPagesFilter( $get_page_original_state ); return $result; } /** * Adjusts setting 'auto_adjust_ids' to enable or disable. * It will only be switched if the setting differs from the current state * of the setting. * * @param bool $enable * * @return bool The state of the setting before adjusting it. */ private function adjustSettingAutoAdjustId( $enable = true ) { $is_setting_enabled = $this->sitepress->get_setting( 'auto_adjust_ids', false ); if ( $enable !== $is_setting_enabled ) { $this->sitepress->set_setting( 'auto_adjust_ids', $enable ); } return $is_setting_enabled; } /** * Add or remove to filter 'get_term' SitePress::get_term_adjust_id(). * It will only be added/removed if the current state differs from * expected. * * @param bool $add_filter * * @return bool The state of callback being added before adjusting it. */ private function adjustGetTermFilter( $add_filter = true ) { $is_filter_added = $this->wp->has_filter( 'get_term', [ $this->sitepress, 'get_term_adjust_id' ] ); if ( $add_filter !== $is_filter_added ) { // State differs. Add/Remove filter callback. $add_filter ? $this->wp->add_filter( 'get_term', [ $this->sitepress, 'get_term_adjust_id' ], 1 ) : $this->wp->remove_filter( 'get_term', [ $this->sitepress, 'get_term_adjust_id' ], 1 ); } return $is_filter_added; } /** * Add or remove to filter 'get_pages' SitePress::get_pages_adjust_ids(). * It will only be added/removed if the current state differs from * expected. * * @param bool $add_filter * * @return bool The state of callback being added before adjusting it. */ private function adjustGetPagesFilter( $add_filter = true ) { $is_filter_added = $this->wp->has_filter( 'get_pages', [ $this->sitepress, 'get_pages_adjust_ids' ] ); if ( $add_filter !== $is_filter_added ) { // State differs. Add/Remove filter callback. $add_filter ? $this->wp->add_filter( 'get_pages', [ $this->sitepress, 'get_pages_adjust_ids' ], 1, 2 ) : $this->wp->remove_filter( 'get_pages', [ $this->sitepress, 'get_pages_adjust_ids' ], 1 ); } return $is_filter_added; } } utilities/class-wpml-slash-management.php 0000755 00000006637 14720342453 0014617 0 ustar 00 <?php class WPML_Slash_Management { public function match_trailing_slash_to_reference( $url, $reference_url ) { if ( trailingslashit( $reference_url ) === $reference_url && ! $this->has_lang_param( $url ) ) { return trailingslashit( $url ); } else { return untrailingslashit( $url ); } } /** * @param string $url * * @return bool */ private function has_lang_param( $url ) { return strpos( $url, '?lang=' ) !== false || strpos( $url, '&lang=' ) !== false; } /** * @param string $url * @param string $method Deprecated. * * @return mixed|string */ public function maybe_user_trailingslashit( $url, $method = '' ) { $url_parts = wpml_parse_url( $url ); if ( ! $url_parts || ! is_array( $url_parts ) ) { return $url; } $url_parts = $this->parse_missing_host_from_path( $url_parts ); if ( $this->is_root_url_with_trailingslash( $url_parts ) || $this->is_root_url_without_trailingslash_and_without_query_args( $url_parts ) ) { return $url; } $path = isset( $url_parts['path'] ) ? $url_parts['path'] : ''; if ( ! $path && isset( $url_parts['query'] ) ) { $url_parts['path'] = '/'; } elseif ( $this->is_file_path( $path ) ) { $url_parts['path'] = untrailingslashit( $path ); } elseif ( $method ) { $url_parts['path'] = 'untrailingslashit' === $method ? untrailingslashit( $path ) : trailingslashit( $path ); } else { $url_parts['path'] = $this->user_trailingslashit( $path ); } return http_build_url( $url_parts ); } /** * Follows the logic of WordPress core user_trailingslashit(). * Can be called on plugins_loaded event, when $wp_rewrite is not set yet. * * @param string $path * * @return string */ private function user_trailingslashit( $path ) { global $wp_rewrite; if ( $wp_rewrite ) { return user_trailingslashit( $path ); } $permalink_structure = get_option( 'permalink_structure' ); $use_trailing_slashes = ( '/' === substr( $permalink_structure, - 1, 1 ) ); if ( $use_trailing_slashes ) { $path = trailingslashit( $path ); } else { $path = untrailingslashit( $path ); } return apply_filters( 'user_trailingslashit', $path, '' ); } /** * @param array $url_parts * * @return bool */ private function is_root_url_without_trailingslash_and_without_query_args( array $url_parts ) { return ! isset( $url_parts['path'] ) && ! isset( $url_parts['query'] ); } /** * @param array $url_parts * * @return bool */ private function is_root_url_with_trailingslash( array $url_parts ) { return isset( $url_parts['path'] ) && '/' === $url_parts['path']; } /** * @see Test_WPML_Lang_Domains_Converter::check_domains_and_subdir * * @param array $url_parts * * @return array */ public function parse_missing_host_from_path( array $url_parts ) { if ( ! isset( $url_parts['host'] ) && isset( $url_parts['path'] ) ) { $domain_and_subdir = explode( '/', $url_parts['path'] ); $domain = $domain_and_subdir[0]; $url_parts['host'] = $domain; array_shift( $domain_and_subdir ); if ( $domain_and_subdir ) { $url_parts['path'] = preg_replace( '/^(' . $url_parts['host'] . ')/', '', $url_parts['path'] ); } else { unset( $url_parts['path'] ); } } return $url_parts; } /** * @param string $path * * @return bool */ private function is_file_path( $path ) { $pathinfo = pathinfo( $path ); return isset( $pathinfo['extension'] ) && $pathinfo['extension']; } } utilities/class-wpml-encoding-validation.php 0000755 00000002350 14720342453 0015275 0 ustar 00 <?php use WPML\FP\Str; class WPML_Encoding_Validation { const MINIMUM_STRING_LENGTH = 100; /** * Checks if data passed is base64 encoded string and if the length of it is more than or equal to $minimumValidStringLength. * Here we check for the length because we had cases were featured image names are passed in a false positive base64 encoding format., * and this made the whole job to be blocked from sending to translation, while if a real field is encoded the length of it should be way more than how the image name will be. * * @param string $string * * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-553 * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1793 */ public function is_base64_with_100_chars_or_more( $string ) { if ( (bool) preg_match( '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $string ) === false ) { return false; } $decoded = base64_decode( $string, true ); if ( $decoded === false ) { return false; } $encoding = mb_detect_encoding( $decoded ); if ( ! in_array( $encoding, [ 'UTF-8', 'ASCII' ], true ) ) { return false; } return $decoded !== false && base64_encode( $decoded ) === $string && Str::len( $string ) >= self::MINIMUM_STRING_LENGTH; } } utilities/Resources.php 0000755 00000000541 14720342453 0011251 0 ustar 00 <?php namespace WPML\ST\WP\App; use function WPML\FP\partial; class Resources { // enqueueApp :: string $app -> ( string $localizeData ) public static function enqueueApp( $app ) { return partial( [ '\WPML\LIB\WP\App\Resources', 'enqueue' ], $app, WPML_ST_URL, WPML_ST_PATH, WPML_ST_VERSION, 'wpml-string-translation' ); } } utilities/class-wpml-translate-link-targets.php 0000755 00000002521 14720342453 0015756 0 ustar 00 <?php class WPML_Translate_Link_Targets { /* @var AbsoluteLinks $absolute_links */ private $absolute_links; /* @var WPML_Absolute_To_Permalinks $permalinks_converter */ private $permalinks_converter; /** * WPML_Translate_Link_Targets constructor. * * @param AbsoluteLinks $absolute_links * @param WPML_Absolute_To_Permalinks $permalinks_converter */ public function __construct( AbsoluteLinks $absolute_links, WPML_Absolute_To_Permalinks $permalinks_converter ) { $this->absolute_links = $absolute_links; $this->permalinks_converter = $permalinks_converter; } /** * convert_text * * @param string $text * * @return string */ public function convert_text( $text ) { if ( is_string( $text ) ) { $text = $this->absolute_links->convert_text( $text ); $text = $this->permalinks_converter->convert_text( $text ); } return $text; } public function is_internal_url( $url ) { $absolute_url = $this->absolute_links->convert_url( $url ); return $url != $absolute_url || $this->absolute_links->is_home( $url ); } /** * @param string $url * * @return string */ public function convert_url( $url ) { $link = '<a href="' . $url . '">removeit</a>'; $link = $this->convert_text( $link ); return str_replace( array( '<a href="', '">removeit</a>' ), array( '', '' ), $link ); } } utilities/class-wpml-transient.php 0000755 00000002306 14720342453 0013367 0 ustar 00 <?php /** * Class WPML_Transient * * Due to some conflicts between cached environments (e.g. using W3TC) and the normal * WP Transients API, we've added this class which should behaves almost like the normal * transients API. Except for the fact that it is stored as normal options, so WP won't * recognize/treat it as a transient. */ class WPML_Transient { const WPML_TRANSIENT_PREFIX = '_wpml_transient_'; /** * @param string $name * @param string $value * @param string $expiration */ public function set( $name, $value, $expiration = '' ) { $data = array( 'value' => $value, 'expiration' => $expiration ? time() + (int) $expiration : '', ); update_option( self::WPML_TRANSIENT_PREFIX . $name, $data ); } /** * @param string $name * * @return string */ public function get( $name ) { $data = get_option( self::WPML_TRANSIENT_PREFIX . $name ); if ( $data ) { if ( (int) $data['expiration'] < time() ) { delete_option( self::WPML_TRANSIENT_PREFIX . $name ); return ''; } return $data['value']; } return ''; } /** * @param string $name */ public function delete( $name ) { delete_option( self::WPML_TRANSIENT_PREFIX . $name ); } } utilities/class-wpml-temporary-switch-language.php 0000755 00000001112 14720342453 0016454 0 ustar 00 <?php class WPML_Temporary_Switch_Language extends WPML_SP_User { private $old_lang = false; /** * @param SitePress $sitepress * @param string $target_lang */ public function __construct( &$sitepress, $target_lang ) { parent::__construct( $sitepress ); $this->old_lang = $sitepress->get_current_language(); $sitepress->switch_lang( $target_lang ); } public function __destruct() { $this->restore_lang(); } public function restore_lang () { if ( $this->old_lang ) { $this->sitepress->switch_lang( $this->old_lang ); $this->old_lang = false; } } } utilities/class-wpml-global-ajax.php 0000755 00000013573 14720342453 0013551 0 ustar 00 <?php class WPML_Global_AJAX extends WPML_SP_User { /** * WPML_Global_AJAX constructor. * * @param SitePress $sitepress */ public function __construct( &$sitepress ) { parent::__construct( $sitepress ); add_action( 'wp_ajax_save_language_negotiation_type', array( $this, 'save_language_negotiation_type_action' ) ); } public function save_language_negotiation_type_action() { $errors = array(); $response = false; $nonce = filter_input( INPUT_POST, 'nonce' ); $action = filter_input( INPUT_POST, 'action' ); $is_valid_nonce = wp_verify_nonce( $nonce, $action ); if ( $is_valid_nonce ) { $icl_language_negotiation_type = filter_input( INPUT_POST, 'icl_language_negotiation_type', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $language_domains = filter_input( INPUT_POST, 'language_domains', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY | FILTER_NULL_ON_FAILURE ); $use_directory = filter_input( INPUT_POST, 'use_directory', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $show_on_root = filter_input( INPUT_POST, 'show_on_root', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $root_html_file_path = filter_input( INPUT_POST, 'root_html_file_path', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $hide_language_switchers = filter_input( INPUT_POST, 'hide_language_switchers', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $icl_xdomain_data = filter_input( INPUT_POST, 'xdomain', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $sso_enabled = filter_input( INPUT_POST, 'sso_enabled', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); if ( $icl_language_negotiation_type ) { $this->sitepress->set_setting( 'language_negotiation_type', $icl_language_negotiation_type ); $response = true; if ( ! empty( $language_domains ) ) { $this->sitepress->set_setting( 'language_domains', $language_domains ); } if ( 1 === (int) $icl_language_negotiation_type ) { $urls = $this->sitepress->get_setting( 'urls' ); $urls['directory_for_default_language'] = $use_directory ? true : 0; if ( $use_directory ) { $urls['show_on_root'] = $use_directory ? $show_on_root : ''; if ( 'html_file' === $show_on_root ) { $root_page_url = $root_html_file_path ? $root_html_file_path : ''; $response = $this->validateRootPageUrl( $root_page_url, $errors ); if ( $response ) { $urls['root_html_file_path'] = $root_page_url; } } else { $urls['hide_language_switchers'] = $hide_language_switchers ? $hide_language_switchers : 0; } } $this->sitepress->set_setting( 'urls', $urls ); } $this->sitepress->set_setting( 'xdomain_data', $icl_xdomain_data ); $this->sitepress->set_setting( 'language_per_domain_sso_enabled', $sso_enabled ); $this->sitepress->save_settings(); } if ( $response ) { $permalinks_settings_url = get_admin_url( null, 'options-permalink.php' ); $save_permalinks_link = '<a href="' . $permalinks_settings_url . '">' . _x( 're-save the site permalinks', 'You may need to {re-save the site permalinks} - 2/2', 'sitepress' ) . '</a>'; $save_permalinks_message = sprintf( _x( 'You may need to %s.', 'You may need to {re-save the site permalinks} - 1/2', 'sitepress' ), $save_permalinks_link ); wp_send_json_success( $save_permalinks_message ); } else { if ( ! $errors ) { $errors[] = __( 'Error', 'sitepress' ); } wp_send_json_error( $errors ); } } } /** * @param string $url * @param array $errors * * @return bool */ private function validateRootPageUrl( $url, array &$errors ) { $wp_http = new WP_Http(); if ( '' === trim( $url ) ) { $errors[] = __( 'The URL of the HTML file is required', 'sitepress' ); return false; } if ( 0 !== strpos( $url, 'http' ) ) { $url = get_site_url( null, $url ); } if ( $this->is_external( $url ) ) { $errors[] = __( 'You are trying to use an external URL: this is not allowed.', 'sitepress' ); return false; } try { $response = $wp_http->get( $url ); if ( is_wp_error( $response ) ) { $errors[] = $response->get_error_code() . ' - ' . $response->get_error_message( $response->get_error_code() ); return false; } if ( 200 !== (int) $response['response']['code'] ) { $errors[] = __( 'An attempt to open the URL specified as a root page failed with the following error:', 'sitepress' ); $errors[] = $response['response']['code'] . ': ' . $response['response']['message']; return false; } } catch ( Exception $ex ) { $errors[] = $ex->getMessage(); return false; } return true; } /** * @param string $url * * @return bool */ function is_external( $url ) { $site_url = get_site_url(); $site_components = wp_parse_url( $site_url ); $site_host = strtolower( $site_components['host'] ); $url_components = wp_parse_url( $url ); $url_host = strtolower( $url_components['host'] ); if ( empty( $url_host ) || 0 === strcasecmp( $url_host, $site_host ) ) { return false; } $site_host = $this->remove_www_prefix( $site_host ); $subdomain_position = strrpos( $url_host, '.' . $site_host ); $subdomain_length = strlen( $url_host ) - strlen( '.' . $site_host ); return $subdomain_position !== $subdomain_length; // check if the url host is a subdomain } /** * @param string $site_host * * @return string */ function remove_www_prefix( $site_host ) { $site_host_levels = explode( '.', $site_host ); if ( 2 > count( $site_host_levels ) && 'www' === $site_host_levels[0] ) { $site_host_levels = array_slice( $site_host_levels, - ( count( $site_host_levels ) - 1 ) ); $site_host = implode( '.', $site_host_levels ); } return $site_host; } } utilities/class-wpml-ajax.php 0000755 00000001223 14720342453 0012300 0 ustar 00 <?php class WPML_Ajax { /** * @return bool */ public static function is_frontend_ajax_request() { return wpml_is_ajax() && isset( $_SERVER['HTTP_REFERER'] ) && false === strpos( $_SERVER['HTTP_REFERER'], admin_url() ); } /** * @param string $url * * @return bool */ public static function is_admin_ajax_request_called_from_frontend( $url ) { if ( false === strpos( $url, 'admin-ajax.php' ) ) { return false; } // is not frontend if ( isset( $_SERVER['HTTP_REFERER'] ) && ( strpos( $_SERVER['HTTP_REFERER'], 'wp-admin' ) || strpos( $_SERVER['HTTP_REFERER'], 'admin-ajax' ) ) ) { return false; } return true; } } utilities/lock/KeyedLock.php 0000755 00000002324 14720342453 0012102 0 ustar 00 <?php namespace WPML\Utilities; class KeyedLock extends Lock { /** @var string $keyName */ private $keyName; /** * Lock constructor. * * @param \wpdb $wpdb * @param string $name */ public function __construct( \wpdb $wpdb, $name ) { $this->keyName = 'wpml.' . $name . '.lock.key'; parent::__construct( $wpdb, $name ); } /** * @param string $key * @param int $release_timeout * * @return string|false The key or false if could not acquire the lock */ public function create( $key = null, $release_timeout = null ) { $acquired = parent::create( $release_timeout ); if ( $acquired ) { if ( ! $key ) { $key = wp_generate_uuid4(); } update_option( $this->keyName, $key ); return $key; } elseif ( $key === get_option( $this->keyName ) ) { $this->extendTimeout(); return $key; } return false; } public function release() { delete_option( $this->keyName ); // When running concurrent calls to delete_option, the cache might not be updated properly. // And WP will skip its own cache invalidation. wp_cache_delete( $this->keyName, 'options' ); return parent::release(); } private function extendTimeout() { update_option( $this->name, time() ); } } utilities/lock/class-wpml-null-lock.php 0000755 00000000277 14720342453 0014215 0 ustar 00 <?php namespace WPML\Utilities; class NullLock implements ILock { public function create( $release_timeout = null ) { return true; } public function release() { return true; } } utilities/class-wpml-deactivate-old-media.php 0000755 00000001052 14720342453 0015317 0 ustar 00 <?php class WPML_Deactivate_Old_Media { private $php_functions; public function __construct( WPML_PHP_Functions $php_functions ) { $this->php_functions = $php_functions; } public function add_hooks() { add_action( 'admin_init', array( $this, 'deactivate_media' ) ); } public function deactivate_media() { if ( $this->php_functions->defined( 'WPML_MEDIA_VERSION' ) && $this->php_functions->constant( 'WPML_MEDIA_VERSION' ) < '2.3' ) { deactivate_plugins( $this->php_functions->constant( 'WPML_MEDIA_PATH' ) . '/plugin.php' ); } } } utilities/class-wpml-wp-cache.php 0000755 00000005752 14720342453 0013057 0 ustar 00 <?php class WPML_WP_Cache { /** @var string Key name under which array of all group keys is stored */ const KEYS = 'WPML_WP_Cache__group_keys'; /** @var string Group name */ private $group; /** * WPML_WP_Cache constructor. * * @param string $group Optional. Where the cache contents are grouped. Default empty. */ public function __construct( $group = '' ) { $this->group = $group; } /** * Retrieves the cache contents from the cache by key and group. * * @param int|string $key The key under which the cache contents are stored. * @param bool $found Optional. Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. Default null. * * @return bool|mixed False on failure to retrieve contents or the cache * contents on success */ public function get( $key, &$found = null ) { $value = wp_cache_get( $key, $this->group, false, $found ); if ( is_array( $value ) && array_key_exists( 'data', $value ) ) { // We know that we have set something in the cache. $found = true; return $value['data']; } else { $found = false; return $value; } } /** * Saves the data to the cache. * * @param int|string $key The cache key to use for retrieval later. * @param mixed $data The contents to store in the cache. * @param int $expire Optional. When to expire the cache contents, in seconds. * Default 0 (no expiration). * * @return bool False on failure, true on success */ public function set( $key, $data, $expire = 0 ) { $keys = $this->get_keys(); if ( ! in_array( $key, $keys, true ) ) { $keys[] = $key; wp_cache_set( self::KEYS, $keys, $this->group ); } // Save $value in an array. We need to do this because W3TC and Redis have bug with saving null. return wp_cache_set( $key, [ 'data' => $data ], $this->group, $expire ); } /** * Removes the cache contents matching key and group. */ public function flush_group_cache() { $keys = $this->get_keys(); foreach ( $keys as $key ) { wp_cache_delete( $key, $this->group ); } wp_cache_delete( self::KEYS, $this->group ); } public function execute_and_cache( $key, $callback ) { list( $result, $found ) = $this->get_with_found( $key ); if ( ! $found ) { $result = $callback(); $this->set( $key, $result ); } return $result; } /** * @param string $key * * @return array { * @type mixed $result @see Return value of \wp_cache_get. * @type bool $found @see `$found` argument of \wp_cache_get. * } */ public function get_with_found( $key ) { $found = false; $result = $this->get( $key, $found ); return [ $result, $found ]; } /** * Get stored group keys. * * @return array */ private function get_keys() { $found = false; $keys = wp_cache_get( self::KEYS, $this->group, false, $found ); if ( $found && is_array( $keys ) ) { return $keys; } return []; } } utilities/class-wpml-simple-language-selector.php 0000755 00000006204 14720342453 0016251 0 ustar 00 <?php class WPML_Simple_Language_Selector extends WPML_SP_User { function __construct( &$sitepress ) { parent::__construct( $sitepress ); self::enqueue_scripts(); } function render( $options = array() ) { $options = array_merge( array( 'id' => '', 'name' => '', 'show_please_select' => true, 'please_select_text' => __( '-- Please select --', 'sitepress' ), 'selected' => '', 'echo' => false, 'class' => '', 'data' => array(), 'show_flags' => true, 'languages' => null, 'disabled' => false, 'style' => '', ), $options ); if ( $options['languages'] ) { $languages = $options['languages']; } else { $languages = $this->sitepress->get_languages( $this->sitepress->get_admin_language() ); } $active_languages = $this->sitepress->get_active_languages(); $data = ''; foreach ( $options['data'] as $key => $value ) { $data .= ' data-' . $key . '="' . $value . '"'; } if ( $options['show_flags'] ) { $options['class'] .= ' js-simple-lang-selector-flags'; } if ( $options['disabled'] ) { $disabled = ' disabled="disabled" '; } else { $disabled = ''; } if ( ! $options['echo'] ) { ob_start(); } ?> <select title="wpml-simple-language-selector" <?php if ( $options['id'] ) { echo ' id="' . esc_attr( $options['id'] ) . '"'; } if ( $options['name'] ) { echo ' name="' . esc_attr( $options['name'] ) . '"'; } ?> class="wpml-simple-lang-selector js-simple-lang-selector <?php echo esc_attr( $options['class'] ); ?>" <?php echo esc_attr( $data ); ?> <?php echo esc_attr( $disabled ); ?> style="<?php echo esc_attr( $options['style'] ); ?>"> <?php if ( $options['show_please_select'] ) { ?> <option value="" <?php if ( '' == $options['selected'] ) { echo 'selected="selected"'; } ?> > <?php echo esc_html( $options['please_select_text'] ); ?> </option> <?php } foreach ( $languages as $lang ) { ?> <option value="<?php echo esc_attr( $lang['code'] ); ?>" <?php if ( $options['selected'] == $lang['code'] ) { echo 'selected="selected"'; } ?> data-flag_url="<?php echo esc_url( $this->sitepress->get_flag_url( $lang['code'] ) ); ?>" data-status="<?php echo in_array( $lang['code'], array_keys( $active_languages ) ) ? 'active' : ''; ?>"> <?php echo esc_html( $lang['display_name'] ); ?> </option> <?php } ?> </select> <?php if ( ! $options['echo'] ) { return ob_get_clean(); } return null; } public static function enqueue_scripts() { if ( ! wp_script_is( 'wpml-select-2' ) ) { // Enqueue in the footer because this is usually called late. wp_enqueue_script( 'wpml-select-2', ICL_PLUGIN_URL . '/lib/select2/select2.min.js', array( 'jquery' ), ICL_SITEPRESS_VERSION, true ); wp_enqueue_script( 'wpml-simple_language-selector', ICL_PLUGIN_URL . '/res/js/wpml-simple-language-selector.js', array( 'jquery' ), ICL_SITEPRESS_VERSION, true ); } } } utilities/class-wpml-string-functions.php 0000755 00000001363 14720342453 0014676 0 ustar 00 <?php class WPML_String_Functions { public static function is_css_color( $string ) { return (bool) preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/im', $string ); } public static function is_css_length( $string ) { $parts = explode( ' ', $string ); foreach ( $parts as $part ) { if ( ! (bool) preg_match( '/^[+-]?[0-9]+.?([0-9]+)?(px|em|ex|ch|rem|vw|vh|vmin|vmax|%|in|cm|mm|pt|pc)$/im', $part ) && '0' !== $part ) { return false; } } return true; } public static function is_numeric( $string ) { return (bool) is_numeric( $string ); } public static function is_not_translatable( $string ) { return self::is_css_color( $string ) || self::is_css_length( $string ) || self::is_numeric( $string ); } } utilities/user/class-wpml-wp-user-query-factory.php 0000755 00000000170 14720342453 0016545 0 ustar 00 <?php class WPML_WP_User_Query_Factory { public function create( $args ) { return new WP_User_Query( $args ); } } utilities/user/class-wpml-user.php 0000755 00000001734 14720342453 0013320 0 ustar 00 <?php class WPML_User extends WP_User { /** * @see \get_user_meta * * @param string $key * @param bool $single * * @return mixed */ public function get_meta( $key = '', $single = false ) { return get_user_meta( $this->ID, $key, $single ); } /** * @see \update_meta * * @param string $key * @param mixed $value * @param mixed $prev_value */ public function update_meta( $key, $value, $prev_value = '' ) { update_user_meta( $this->ID, $key, $value, $prev_value ); } /** * @see \get_user_option * * @param string $option * @return mixed */ public function get_option( $option ) { return get_user_option( $option, $this->ID ); } /** * @see \update_user_option * * @param string $option_name * @param mixed $new_value * @param bool $global * @return int|bool */ function update_option( $option_name, $new_value, $global = false ) { return update_user_option( $this->ID, $option_name, $new_value, $global ); } } utilities/user/class-wpml-wp-user-factory.php 0000755 00000000527 14720342453 0015410 0 ustar 00 <?php class WPML_WP_User_Factory { public function create( $user_id ) { return new WPML_User( $user_id ); } public function create_by_email( $user_email ) { $user = get_user_by( 'email', $user_email ); return new WPML_User( $user->ID ); } public function create_current() { return new WPML_User( get_current_user_id() ); } } utilities/admin/class-wpml-admin-url.php 0000755 00000000721 14720342453 0014337 0 ustar 00 <?php class WPML_Admin_URL { public static function multilingual_setup( $section = null ) { if ( defined( 'WPML_TM_VERSION' ) ) { $url = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS . '&sm=mcsetup' ); } else { $url = admin_url( 'admin.php?page=' . ICL_PLUGIN_FOLDER . '/menu/translation-options.php' ); } if ( $section ) { $url .= '#ml-content-setup-sec-' . $section; } return $url; } } utilities/admin/wpml-admin-table-sort.php 0000755 00000005521 14720342453 0014511 0 ustar 00 <?php class WPML_Admin_Table_Sort { /** @var string $primary_column */ private $primary_column; /** @var string $url_args */ private $url_args; /** @var string $current_url */ private $current_url; /** @var string */ private $orderby_param; /** @var string */ private $order_param; /** * @param string $orderby_param * @param string $order_param */ public function __construct( $orderby_param = 'orderby', $order_param = 'order' ) { $this->orderby_param = $orderby_param; $this->order_param = $order_param; } /** * @param string $primary_column */ public function set_primary_column( $primary_column ) { $this->primary_column = $primary_column; } /** * @param string $column * * @return string */ public function get_column_url( $column ) { $query_args = array( $this->orderby_param => $column, $this->order_param => 'desc', ); if ( $this->get_current_orderby() === $column && $this->get_current_order() === 'desc' ) { $query_args[ $this->order_param ] = 'asc'; } return add_query_arg( $query_args, $this->get_current_url() ); } /** * @param string $column * * @return string */ public function get_column_classes( $column ) { $classes = 'manage-column column-' . $column; if ( $this->is_primary( $column ) ) { $classes .= ' column-primary'; } if ( $this->get_current_orderby() === $column ) { $classes .= ' sorted ' . $this->get_current_order(); } else { $classes .= ' sortable asc'; } return $classes; } /** * @param string $column * * @return bool */ private function is_primary( $column ) { return $this->primary_column === $column; } /** * @return string|null */ private function get_current_orderby() { $url_args = $this->get_url_args(); return isset( $url_args[ $this->orderby_param ] ) ? $url_args[ $this->orderby_param ] : null; } /** * @return string|null */ private function get_current_order() { $url_args = $this->get_url_args(); return isset( $url_args[ $this->order_param ] ) ? $url_args[ $this->order_param ] : null; } /** * @return array */ public function get_current_sorters() { return array( $this->orderby_param => $this->get_current_orderby(), $this->order_param => $this->get_current_order(), ); } /** * @return array */ private function get_url_args() { if ( ! $this->url_args ) { $this->url_args = array(); $url_query = wpml_parse_url( $this->get_current_url(), PHP_URL_QUERY ); parse_str( $url_query, $this->url_args ); } return $this->url_args; } /** * @return string */ private function get_current_url() { if ( ! $this->current_url ) { $this->current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); $this->current_url = remove_query_arg( 'paged', $this->current_url ); } return $this->current_url; } } utilities/admin/wpml-admin-pagination.php 0000755 00000005611 14720342453 0014566 0 ustar 00 <?php /** * Class WPML_Admin_Pagination * * @author OnTheGoSystems */ class WPML_Admin_Pagination { /** @var int $items_per_page */ private $items_per_page; /** @var int $total_items */ private $total_items; /** @var int $current_page */ private $current_page; /** @var string $current_url */ private $current_url; /** @var string */ private $page_param_name = 'paged'; /** * @param string $page_param_name */ public function set_page_param_name( $page_param_name ) { $this->page_param_name = $page_param_name; } /** * @return string */ public function get_page_param_name() { return $this->page_param_name; } /** * @param int $items_per_page */ public function set_items_per_page( $items_per_page ) { $this->items_per_page = (int) $items_per_page; } /** * @return int */ public function get_items_per_page() { return $this->items_per_page; } /** * @param int $total_items */ public function set_total_items( $total_items ) { $this->total_items = (int) $total_items; } /** * @return int */ public function get_total_items() { return $this->total_items; } /** * @return int */ public function get_total_pages() { return ceil( $this->get_total_items() / $this->get_items_per_page() ); } /** * @param int $page */ public function set_current_page( $page ) { $this->current_page = (int) $page; } /** * @return int */ public function get_current_page() { return $this->current_page; } /** * @return null|string */ public function get_first_page_url() { $url = null; if ( 2 < $this->get_current_page() ) { $url = remove_query_arg( $this->page_param_name, $this->get_current_url() ); } return $url; } /** * @return null|string */ public function get_previous_page_url() { $url = null; if ( 1 < $this->get_current_page() ) { $url = add_query_arg( $this->page_param_name, $this->get_current_page() - 1, $this->get_current_url() ); } return $url; } /** * @return null|string */ public function get_next_page_url() { $url = null; if ( $this->get_current_page() < $this->get_total_pages() ) { $url = add_query_arg( $this->page_param_name, $this->get_current_page() + 1, $this->get_current_url() ); } return $url; } /** * @return null|string */ public function get_last_page_url() { $url = null; if ( $this->get_current_page() < $this->get_total_pages() - 1 ) { $url = add_query_arg( $this->page_param_name, $this->get_total_pages(), $this->get_current_url() ); } return $url; } /** * @return string */ private function get_current_url() { if ( ! $this->current_url ) { $removable_query_args = wp_removable_query_args(); $this->current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); $this->current_url = remove_query_arg( $removable_query_args, $this->current_url ); } return $this->current_url; } } utilities/admin/wpml-admin-pagination-factory.php 0000755 00000001621 14720342453 0016230 0 ustar 00 <?php class WPML_Admin_Pagination_Factory { /** * @var int */ private $items_per_page; public function __construct( $items_per_page ) { $this->items_per_page = $items_per_page; } /** * @return WPML_Admin_Pagination_Render */ public function create( $total_items, $page_param_name = 'paged' ) { $pagination = new WPML_Admin_Pagination(); $pagination->set_total_items( $total_items ); $pagination->set_items_per_page( $this->items_per_page ); $pagination->set_page_param_name( $page_param_name ); $page = 1; if ( isset( $_GET[ $page_param_name ] ) ) { $page = filter_var( $_GET[ $page_param_name ], FILTER_SANITIZE_NUMBER_INT ); } $pagination->set_current_page( $page ); $template = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . '/templates/pagination', ) ); return new WPML_Admin_Pagination_Render( $template->get_template(), $pagination ); } } utilities/admin/wpml-admin-pagination-render.php 0000755 00000003222 14720342453 0016037 0 ustar 00 <?php class WPML_Admin_Pagination_Render { const TEMPLATE = 'pagination.twig'; /** * @var IWPML_Template_Service */ private $template; /** * @var WPML_Admin_Pagination */ private $pagination; public function __construct( IWPML_Template_Service $template, WPML_Admin_Pagination $pagination ) { $this->template = $template; $this->pagination = $pagination; } public function get_model() { return [ 'strings' => self::get_strings( $this->pagination->get_total_items() ), 'pagination' => $this->pagination, 'total_items' => $this->pagination->get_total_items(), ]; } public static function get_strings( $totalItems ) { return [ 'listNavigation' => __( 'Navigation', 'sitepress' ), 'firstPage' => __( 'First page', 'sitepress' ), 'previousPage' => __( 'Previous page', 'sitepress' ), 'nextPage' => __( 'Next page', 'sitepress' ), 'lastPage' => __( 'Last page', 'sitepress' ), 'currentPage' => __( 'Current page', 'sitepress' ), 'of' => __( 'of', 'sitepress' ), 'totalItemsText' => sprintf( _n( '%s item', '%s items', $totalItems, 'sitepress' ), $totalItems ), ]; } /** * @param array $items * * @return array */ public function paginate( $items ) { $total = count( $items ); $limit = $this->pagination->get_items_per_page(); // per page $total_pages = ceil( $total / $limit ); $page = max( $this->pagination->get_current_page(), 1 ); $page = min( $page, $total_pages ); $offset = ( $page - 1 ) * $limit; if ( $offset < 0 ) { $offset = 0; } return array_slice( $items, (int) $offset, $limit ); } } utilities/class-wpml-temporary-switch-admin-language.php 0000755 00000001134 14720342453 0017546 0 ustar 00 <?php class WPML_Temporary_Switch_Admin_Language extends WPML_SP_User { private $old_lang = false; /** * @param SitePress $sitepress * @param string $target_lang */ public function __construct( &$sitepress, $target_lang ) { parent::__construct( $sitepress ); $this->old_lang = $sitepress->get_admin_language(); $sitepress->set_admin_language( $target_lang ); } public function __destruct() { $this->restore_lang(); } public function restore_lang () { if ( $this->old_lang ) { $this->sitepress->set_admin_language( $this->old_lang ); $this->old_lang = false; } } } utilities/Pager.php 0000755 00000003447 14720342453 0010345 0 ustar 00 <?php namespace WPML\Utils; use WPML\Collect\Support\Collection; class Pager { /** @var string */ protected $optionName; /** @var int */ protected $pageSize; /** * @param string $optionName * @param int $pageSize */ public function __construct( $optionName, $pageSize = 10 ) { $this->optionName = $optionName; $this->pageSize = $pageSize; } /** * @param Collection $collection * @param callable $callback * @param int $timeout * * @return int */ public function iterate( Collection $collection, callable $callback, $timeout = PHP_INT_MAX ) { $processedItems = $this->getProcessedCount(); $this->getItemsToProcess( $collection, $processedItems )->eachWithTimeout( function ( $item ) use ( &$processedItems, $callback ) { return $callback( $item ) && ++ $processedItems; }, $timeout ); $remainingPages = $this->getRemainingPages( $collection, $processedItems ); if ( $remainingPages ) { \update_option( $this->optionName, $processedItems ); } else { \delete_option( $this->optionName ); } return $remainingPages; } private function getItemsToProcess( Collection $collection, $processedItems ) { return $collection->slice( $processedItems, $this->pageSize ); } /** * @param Collection $collection * * @return int */ public function getPagesCount( Collection $collection ) { return (int) ceil( $collection->count() / $this->pageSize ); } /** * @param \WPML\Collect\Support\Collection $collection * * @return int */ protected function getRemainingPages( Collection $collection, $processedItems ) { return (int) ceil( $collection->slice( $processedItems )->count() / $this->pageSize ); } /** * @return int */ public function getProcessedCount() { return get_option( $this->optionName, 0 ); } } utilities/class-debug-backtrace.php 0000755 00000007737 14720342453 0013423 0 ustar 00 <?php namespace WPML\Utils; /** * Class DebugBackTrace * * @package WPML\Utils */ class DebugBackTrace { /** @var array */ private $debug_backtrace = []; /** @var int */ private $limit; /** @var bool */ private $provide_object; /** @var bool */ private $ignore_args; /** @var string */ private $debug_backtrace_function; /** * DebugBackTrace constructor. * * @param int $limit * @param bool $provide_object * @param bool $ignore_args * @param null|string $debug_backtrace_function */ public function __construct( $limit = 0, $provide_object = false, $ignore_args = true, $debug_backtrace_function = null ) { if ( ! $debug_backtrace_function ) { $debug_backtrace_function = 'debug_backtrace'; } $this->limit = $limit; $this->provide_object = $provide_object; $this->ignore_args = $ignore_args; $this->debug_backtrace_function = $debug_backtrace_function; } /** * @param array $functions * @param bool $refresh * * @return bool */ public function are_functions_in_call_stack( array $functions, $refresh = true ) { if ( empty( $this->debug_backtrace ) || $refresh ) { $this->get_backtrace(); } $found = false; foreach ( $this->debug_backtrace as $frame ) { if ( isset( $frame['class'] ) ) { $frame_function = [ $frame['class'], $frame['function'] ]; } else { $frame_function = $frame['function']; } if ( in_array( $frame_function, $functions, true ) ) { $found = true; break; } } return $found; } /** * @param string $function_name * @param bool $refresh * * @return bool */ public function is_function_in_call_stack( $function_name, $refresh = true ) { return $this->are_functions_in_call_stack( [ $function_name ], $refresh ); } /** * @param string $function_name * @param bool $refresh * * @return int */ public function count_function_in_call_stack( $function_name, $refresh = true ) { if ( empty( $this->debug_backtrace ) || $refresh ) { $this->get_backtrace(); } $count = 0; foreach ( $this->debug_backtrace as $frame ) { if ( ! isset( $frame['class'] ) && $frame['function'] === $function_name ) { $count ++; } } return $count; } /** * @param string $class_name * @param string $function_name * @param bool $refresh * * @return bool */ public function is_class_function_in_call_stack( $class_name, $function_name, $refresh = true ) { return $this->are_functions_in_call_stack( [ [ $class_name, $function_name ] ], $refresh ); } /** * @return array */ public function get_backtrace() { $options = false; // As of 5.3.6, 'options' parameter is a bit mask for the following options. if ( $this->provide_object ) { $options |= DEBUG_BACKTRACE_PROVIDE_OBJECT; } if ( $this->ignore_args ) { $options |= DEBUG_BACKTRACE_IGNORE_ARGS; } $actual_limit = 0 === $this->limit ? 0 : $this->limit + 4; $this->debug_backtrace = (array) call_user_func_array( $this->debug_backtrace_function, [ $options, $actual_limit, ] ); // Add one item to include the current frame. $this->remove_frames_for_this_class(); return $this->debug_backtrace; } private function remove_frames_for_this_class() { /** * We cannot rely on number of frames to remove. * php 5.6 and 7+ provides different call stacks. * php 5.6 adds call_user_func_array from get_backtrace() */ do { $found = false; if ( ! isset( $this->debug_backtrace[0] ) ) { break; } $frame = $this->debug_backtrace[0]; if ( ( isset( $frame['file'] ) && __FILE__ === $frame['file'] ) || ( isset( $frame['class'] ) && self::class === $frame['class'] ) ) { $found = true; $this->remove_last_frame(); } } while ( $found ); $this->remove_last_frame(); // Remove frame with the function called this class. } public function remove_last_frame() { if ( $this->debug_backtrace ) { array_shift( $this->debug_backtrace ); } } } utilities/class-wpml-deactivate-old-media-factory.php 0000755 00000000276 14720342453 0016773 0 ustar 00 <?php class WPML_Deactivate_Old_Media_Factory implements IWPML_Backend_Action_Loader { public function create() { return new WPML_Deactivate_Old_Media( new WPML_PHP_Functions() ); } } utilities/class-wpml-non-persistent-cache.php 0000755 00000004260 14720342453 0015412 0 ustar 00 <?php /** * Class WPML_Non_Persistent_Cache * * Implements non-persistent cache based on an array. Suitable to cache objects during single page load. */ class WPML_Non_Persistent_Cache { /** * @var array Cached objects. */ private static $cache = array(); /** * Retrieves the data contents from the cache, if it exists. * * @param string|int $key Cache key. * @param string $group Cache group. * @param bool $found Whether the key was found in the cache (passed by reference). * Disambiguates a return of false, a storable value. * * @return mixed|bool */ public static function get( $key, $group = 'default', &$found = null ) { if ( isset( self::$cache[ $group ] ) && ( isset( self::$cache[ $group ][ $key ] ) || array_key_exists( $key, self::$cache[ $group ] ) ) ) { $found = true; return self::$cache[ $group ][ $key ]; } $found = false; return false; } /** * Sets the data contents into the cache. * * @param string|int $key Cache key. * @param mixed $data Data to store in cache. * @param string $group Cache group. * * @return bool */ public static function set( $key, $data, $group = 'default' ) { if ( is_object( $data ) ) { $data = clone $data; } self::$cache[ $group ][ $key ] = $data; return true; } /** * Executes callback function and caches its result. * * @param string $key Cache key. * @param callable $callback Callback function. * @param string $group Cache group. * * @return bool */ public static function execute_and_cache( $key, $callback, $group = 'default' ) { $data = self::get( $key, $group, $found ); if ( ! $found ) { $data = $callback(); self::set( $key, $data, $group ); } return $data; } /** * Flush cache. * * @return bool */ public static function flush() { self::$cache = array(); return true; } /** * Flush cache group. * * @param array|string $groups Cache group name. * * @return bool */ public static function flush_group( $groups = 'default' ) { $groups = (array) $groups; foreach ( $groups as $group ) { unset( self::$cache[ $group ] ); } return true; } } utilities/class-wpml-locale.php 0000755 00000013713 14720342453 0012623 0 ustar 00 <?php use WPML\Collect\Support\Collection; class WPML_Locale { /** * @var wpdb */ private $wpdb; /** * @var SitePress */ private $sitepress; /** * @var string $locale */ private $locale; private $locale_cache; /** @var Collection $all_locales */ private $all_locales; /** * WPML_Locale constructor. * * @param wpdb $wpdb * @param SitePress $sitepress * @param string $locale */ public function __construct( wpdb &$wpdb, SitePress &$sitepress, &$locale ) { $this->wpdb =& $wpdb; $this->sitepress =& $sitepress; $this->locale =& $locale; $this->locale_cache = null; } public function init() { if ( $this->language_needs_title_sanitization() ) { add_filter( 'sanitize_title', array( $this, 'filter_sanitize_title' ), 10, 2 ); } } /** * @see \Test_Admin_Settings::test_locale * @fixme * Due to the way these tests work (global state issues) I had to create this method * to ensure we have full coverage of the code. * This method shouldn't be used anywhere else and should be removed once tests are migrated * to the new tests framework. */ public function reset_cached_data() { $this->locale_cache = null; $this->all_locales = null; } /** * Hooked to 'sanitize_title' in case the user is using a language that has either German or Danish locale, to * ensure that WP Core sanitization functions handle special chars accordingly. * * @param string $title * @param string $raw_title * * @return string */ public function filter_sanitize_title( $title, $raw_title ) { if ( $title !== $raw_title ) { remove_filter( 'sanitize_title', array( $this, 'filter_sanitize_title' ), 10 ); $chars = array(); $chars[ chr( 195 ) . chr( 132 ) ] = 'Ae'; $chars[ chr( 195 ) . chr( 133 ) ] = 'Aa'; $chars[ chr( 195 ) . chr( 134 ) ] = 'Ae'; $chars[ chr( 195 ) . chr( 150 ) ] = 'Oe'; $chars[ chr( 195 ) . chr( 152 ) ] = 'Oe'; $chars[ chr( 195 ) . chr( 156 ) ] = 'Ue'; $chars[ chr( 195 ) . chr( 159 ) ] = 'ss'; $chars[ chr( 195 ) . chr( 164 ) ] = 'ae'; $chars[ chr( 195 ) . chr( 165 ) ] = 'aa'; $chars[ chr( 195 ) . chr( 166 ) ] = 'ae'; $chars[ chr( 195 ) . chr( 182 ) ] = 'oe'; $chars[ chr( 195 ) . chr( 184 ) ] = 'oe'; $chars[ chr( 195 ) . chr( 188 ) ] = 'ue'; $title = sanitize_title( strtr( $raw_title, $chars ) ); add_filter( 'sanitize_title', array( $this, 'filter_sanitize_title' ), 10, 2 ); } return $title; } /** * @return bool|mixed */ public function locale() { if ( ! $this->locale_cache ) { add_filter( 'language_attributes', array( $this, '_language_attributes' ) ); $wp_api = $this->sitepress->get_wp_api(); $is_ajax = $wp_api->is_ajax(); if ( $is_ajax && isset( $_REQUEST['action'], $_REQUEST['lang'] ) ) { $locale_lang_code = preg_replace( '/[^-a-zA-Z0-9_]/', '', $_REQUEST['lang'] ); } elseif ( $wp_api->is_admin() && ( ! $is_ajax || $this->sitepress->check_if_admin_action_from_referer() ) ) { $locale_lang_code = $this->sitepress->user_lang_by_authcookie(); } else { $locale_lang_code = $this->sitepress->get_current_language(); } $locale = $this->get_locale( $locale_lang_code ); if ( did_action( 'plugins_loaded' ) ) { $this->locale_cache = $locale; } return $locale; } return $this->locale_cache; } /** * @param string $code * * @return false|string */ public function get_locale( $code ) { if ( ! $code ) { return false; } return $this->get_all_locales()->get( $code, $code ); } /** * @return Collection */ public function get_all_locales() { if ( ! $this->all_locales ) { $sql = " SELECT l.code, m.locale, l.default_locale FROM {$this->wpdb->prefix}icl_languages AS l LEFT JOIN {$this->wpdb->prefix}icl_locale_map AS m ON m.code = l.code "; $this->all_locales = wpml_collect( $this->wpdb->get_results( $sql ) ) ->mapWithKeys( function( $row ) { if ( $row->locale ) { $locale = $row->locale; } elseif ( $row->default_locale ) { $locale = $row->default_locale; } else { $locale = $row->code; } return [ $row->code => $locale ]; } ); } return $this->all_locales; } public function switch_locale( $lang_code = false ) { global $l10n; static $original_l10n; if ( ! empty( $lang_code ) ) { $original_l10n = isset( $l10n['sitepress'] ) ? $l10n['sitepress'] : null; if ( $original_l10n !== null ) { unset( $l10n['sitepress'] ); } load_textdomain( 'sitepress', WPML_PLUGIN_PATH . '/locale/sitepress-' . $this->get_locale( $lang_code ) . '.mo', is_string( $this->get_locale( $lang_code ) ) ? $this->get_locale( $lang_code ) : null ); } else { // switch back $l10n['sitepress'] = $original_l10n; } } public function get_locale_file_names() { $locales = array(); $res = $this->wpdb->get_results( " SELECT lm.code, locale FROM {$this->wpdb->prefix}icl_locale_map lm JOIN {$this->wpdb->prefix}icl_languages l ON lm.code = l.code AND l.active=1" ); foreach ( $res as $row ) { $locales[ $row->code ] = $row->locale; } return $locales; } private function language_needs_title_sanitization() { $lang_needs_filter = array( 'de_DE', 'da_DK' ); $current_lang = $this->sitepress->get_language_details( $this->sitepress->get_current_language() ); $needs_filter = false; if ( ! isset( $current_lang['default_locale'] ) ) { return $needs_filter; } if ( in_array( $current_lang['default_locale'], $lang_needs_filter, true ) ) { $needs_filter = true; } return $needs_filter; } function _language_attributes( $latr ) { return preg_replace( '#lang="([a-z]+)"#i', 'lang="' . str_replace( '_', '-', $this->locale ) . '"', $latr ); } /** * @return WPML_Locale */ public static function get_instance_from_sitepress() { /** SitePress $sitepress */ global $sitepress; return $sitepress->get_wpml_locale(); } } utilities/class-wpml-wp-cache-item.php 0000755 00000001453 14720342453 0014005 0 ustar 00 <?php class WPML_WP_Cache_Item { /** @var string $key */ private $key; /** @var WPML_WP_Cache $cache */ private $cache; /** * WPML_WP_Cache_Item constructor. * * @param WPML_WP_Cache $cache * @param string|array $key */ public function __construct( WPML_WP_Cache $cache, $key ) { if ( is_array( $key ) ) { $key = md5( (string) json_encode( $key ) ); } $this->cache = $cache; $this->key = $key; } /** * @return bool */ public function exists() { $found = false; $this->cache->get( $this->key, $found ); return $found; } /** * @return mixed */ public function get() { $found = false; return $this->cache->get( $this->key, $found ); } /** * @param mixed $value */ public function set( $value ) { $this->cache->set( $this->key, $value ); } } utilities/class-wpml-cache-factory.php 0000755 00000003221 14720342453 0014065 0 ustar 00 <?php class WPML_Cache_Factory { /** @var array */ private $valid_caches = [ 'TranslationManagement::get_translation_job_id' => [ 'clear_actions' => [ 'wpml_tm_save_post', 'wpml_cache_clear' ], ], 'WPML_Element_Type_Translation::get_language_for_element' => [ 'clear_actions' => [ 'wpml_translation_update' ], ], 'WPML_Post_Status::needs_update' => [ 'clear_actions' => [ 'wpml_translation_status_update' ], ], ]; public function __construct() { foreach ( $this->valid_caches as $cache_name => $clear_actions ) { $this->init_clear_actions( $cache_name, $clear_actions['clear_actions'] ); } } /** * @param string $cache_name * * @return WPML_WP_Cache * @throws InvalidArgumentException Exception. */ public function get( $cache_name ) { if ( isset( $this->valid_caches[ $cache_name ] ) ) { return new WPML_WP_Cache( $cache_name ); } else { throw new InvalidArgumentException( $cache_name . ' is not a valid cache for the WPML_Cache_Factory' ); } } /** * @param string $cache_name * @param array $clear_actions */ public function define( $cache_name, array $clear_actions ) { if ( isset( $this->valid_caches[ $cache_name ] ) ) { return; } $this->valid_caches[ $cache_name ] = [ 'clear_actions' => $clear_actions, ]; $this->init_clear_actions( $cache_name, $clear_actions ); } private function init_clear_actions( $cache_name, array $clear_actions ) { foreach ( $clear_actions as $clear_action ) { add_action( $clear_action, function () use ( $cache_name ) { $cache = new WPML_WP_Cache( $cache_name ); $cache->flush_group_cache(); } ); } } } utilities/class-wpml-encoding.php 0000755 00000003040 14720342453 0013142 0 ustar 00 <?php class WPML_Encoding { /** * @param string $string The string to decode. * @param string $encodings A comma separated list of encodings in the order that the data was encoded * * @return mixed */ public static function decode( $string, $encodings ) { $decoded_data = $string; // NOTE: We decode in the reverse order of the encodings given foreach ( array_reverse( explode( ',', $encodings ) ) as $encoding ) { switch ( $encoding ) { case 'json': $decoded_data = json_decode( $decoded_data, true ); break; case 'base64': $decoded_data = base64_decode( $decoded_data ); break; case 'urlencode': $decoded_data = urldecode( $decoded_data ); break; } } /** * @since 4.1.0 */ return apply_filters( 'wpml_decode_string', $decoded_data, $string, $encodings ); } /** * @param mixed $data The data to encode. * @param string $encodings A comma separated list of encodings in the order that the data was encoded * * @return string */ public static function encode( $data, $encodings ) { $encoded_data = $data; foreach ( explode( ',', $encodings ) as $encoding ) { switch ( $encoding ) { case 'json': $encoded_data = wp_json_encode( $encoded_data ); break; case 'base64': $encoded_data = base64_encode( $encoded_data ); break; case 'urlencode': $encoded_data = urlencode( $encoded_data ); break; } } /** * @since 4.1.0 */ return apply_filters( 'wpml_encode_string', $encoded_data, $data, $encodings ); } } utilities/DebugLog.php 0000755 00000001741 14720342453 0010772 0 ustar 00 <?php namespace WPML\Utilities; class DebugLog implements \IWPML_Backend_Action, \IWPML_AJAX_Action, \IWPML_REST_Action { public static $trace; public function add_hooks() { if ( ! defined( 'WPML_DEBUG_LOG' ) || ! WPML_DEBUG_LOG ) { return; } add_action( 'shutdown', [$this, 'onShutdown'] ); } public static function storeBackTrace() { if ( ! defined( 'WPML_DEBUG_LOG' ) || ! WPML_DEBUG_LOG ) { return; } $log_entry = sprintf( "%s [WPML Logs] - Req [%s] - URI [%s] - Message: %s", time(), $_SERVER['REQUEST_METHOD'] ?? '', $_SERVER['REQUEST_URI'] ?? '', print_r(debug_backtrace(0, 25), true) ); static::$trace[] = $log_entry; } public function onShutdown() { if ( ! static::$trace ) { return; } $fp = fopen(is_string( constant( 'WPML_DEBUG_LOG' ) ) ? constant( 'WPML_DEBUG_LOG' ) : ABSPATH . 'debug.wpml.log', 'a+'); if ( ! $fp ) { return; } fwrite( $fp, implode("\r\n ", static::$trace ) . PHP_EOL ); fclose($fp); } } utilities/class-wpml-wp-taxonomy-query.php 0000755 00000001531 14720342453 0015024 0 ustar 00 <?php class WPML_WP_Taxonomy_Query { private $taxonomies_query_vars; public function __construct( $wp_api ) { $wp_taxonomies = $wp_api->get_wp_taxonomies(); $this->taxonomies_query_vars = array(); foreach ( $wp_taxonomies as $k => $v ) { if ( $k === 'category' ) { continue; } if ( $k == 'post_tag' && ! $v->query_var ) { $tag_base = $wp_api->get_option( 'tag_base', 'tag' ); $v->query_var = $tag_base; } if ( $v->query_var ) { $this->taxonomies_query_vars[ $k ] = $v->query_var; } } } public function get_query_vars() { return $this->taxonomies_query_vars; } public function find( $taxonomy ) { $tax = false; if ( isset( $this->taxonomies_query_vars ) && is_array( $this->taxonomies_query_vars ) ) { $tax = array_search( $taxonomy, $this->taxonomies_query_vars ); } return $tax; } } utilities/wpml-queried-object.php 0000755 00000006227 14720342453 0013165 0 ustar 00 <?php /** * Class WPML_Queried_Object * * @author OnTheGoSystems */ class WPML_Queried_Object { /** @var SitePress $sitepress */ private $sitepress; /** @var null|object */ private $queried_object; /** @var stdClass $queried_object_details */ private $queried_object_details; /** * WPML_TF_Queried_Object constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $this->queried_object = get_queried_object(); } public function has_object() { return (bool) $this->queried_object; } /** * @return null|string */ public function get_source_language_code() { return $this->get_queried_object_detail( 'source_language_code' ); } /** * @return string */ public function get_language_code() { return $this->get_queried_object_detail( 'language_code' ); } /** * @param string $key * * @return null|mixed */ private function get_queried_object_detail( $key ) { $detail = null; if ( ! $this->queried_object_details ) { if ( $this->is_post() ) { $this->queried_object_details = $this->sitepress->get_element_language_details( $this->get_id(), $this->get_element_type() ); } } if ( isset( $this->queried_object_details->{$key} ) ) { $detail = $this->queried_object_details->{$key}; } return $detail; } /** * @return bool */ public function is_post() { return isset( $this->queried_object->ID, $this->queried_object->post_type ); } /** * @return null|int */ public function get_id() { $id = null; if ( isset( $this->queried_object->ID ) ) { $id = $this->queried_object->ID; } return $id; } /** * @return null|string */ public function get_element_type() { $type = null; if ( $this->is_instance_of_post_type() ) { $type = 'post_' . $this->get_post_type_name(); } if ( $this->is_instance_of_post() ) { $type = 'post_' . $this->get_post_type(); } if ( $this->is_instance_of_taxonomy() ) { $type = 'tax_' . $this->get_taxonomy(); } return $type; } /** * @return null|string */ public function get_source_url() { $url = null; $language_links = $this->sitepress->get_ls_languages(); if ( isset( $language_links[ $this->get_source_language_code() ]['url'] ) ) { $url = $language_links[ $this->get_source_language_code() ]['url']; } return $url; } public function get_post_type() { if ( $this->is_instance_of_post() ) { return $this->queried_object->post_type; } return null; } public function get_taxonomy() { if ( $this->is_instance_of_taxonomy() ) { return $this->queried_object->taxonomy; } } public function get_post_type_name() { if ( $this->is_instance_of_post_type() ) { return $this->queried_object->name; } } public function is_instance_of_post() { return $this->queried_object instanceof WP_Post; } public function is_instance_of_taxonomy() { return $this->queried_object instanceof WP_Term; } public function is_instance_of_post_type() { return $this->queried_object instanceof WP_Post_Type; } public function is_instance_of_user() { return $this->queried_object instanceof WP_User; } } utilities/wpml-languages-notices.php 0000755 00000013050 14720342453 0013663 0 ustar 00 <?php class WPML_Languages_Notices { const NOTICE_ID_MISSING_MENU_ITEMS = 'wpml-missing-menu-items'; const NOTICE_GROUP = 'wpml-core'; const NOTICE_ID_MISSING_DOWNLOADED_LANGUAGES = 'wpml-missing-downloaded-languages'; /** @var WPML_Notices */ private $admin_notices; private $translations = array(); /** * WPML_Languages_Notices constructor. * * @param WPML_Notices $admin_notices */ public function __construct( WPML_Notices $admin_notices ) { $this->admin_notices = $admin_notices; } function maybe_create_notice_missing_menu_items( $languages_count ) { if ( 1 === $languages_count ) { $text = __( 'You need to configure at least one more language in order to access "Theme and plugins localization" and "Media translation".', 'sitepress' ); $notice = new WPML_Notice( self::NOTICE_ID_MISSING_MENU_ITEMS, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( 'info' ); $notice->set_dismissible( true ); $this->admin_notices->add_notice( $notice ); } else { $this->admin_notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID_MISSING_MENU_ITEMS ); } } public function missing_languages( $not_found_languages ) { $list_items = array(); if ( $not_found_languages ) { $list_item_pattern = __( '%1$s (current locale: %2$s) - suggested locale(s): %3$s', 'sitepress' ); foreach ( (array) $not_found_languages as $not_found_language ) { $suggested_codes = $this->get_suggestions( $not_found_language ); if ( $suggested_codes ) { $suggestions = '<strong>' . implode( '</strong>, <strong>', $suggested_codes ) . '</strong>'; $current = $not_found_language['code']; if ( $not_found_language['default_locale'] ) { $current = $not_found_language['default_locale']; } $list_items[] = sprintf( $list_item_pattern, $not_found_language['display_name'], $current, $suggestions ); } } } if ( $list_items ) { $text = ''; $text .= '<p>'; $text .= __( 'WordPress cannot automatically download translations for the following languages:', 'sitepress' ); $text .= '</p>'; $text .= '<ul>'; $text .= '<li>'; $text .= implode( '</li><li>', $list_items ); $text .= '</li>'; $text .= '</ul>'; $languages_edit_url = admin_url( 'admin.php?page=' . WPML_PLUGIN_FOLDER . '/menu/languages.php&trop=1' ); $languages_edit_link = '<a href="' . $languages_edit_url . '">'; $languages_edit_link .= __( 'Edit Languages', 'sitepress' ); $languages_edit_link .= '</a>'; $text .= '<p>'; $text .= sprintf( __( 'To fix, open "%s" and set the "default locale" values as shown above.', 'sitepress' ), $languages_edit_link ); $text .= '</p>'; $notice = new WPML_Notice( self::NOTICE_ID_MISSING_DOWNLOADED_LANGUAGES, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( 'warning' ); $notice->add_display_callback( array( $this, 'is_not_languages_edit_page' ) ); $notice->set_dismissible( true ); $this->admin_notices->add_notice( $notice, true ); } else { $this->admin_notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID_MISSING_DOWNLOADED_LANGUAGES ); } } public function is_not_languages_edit_page() { $result = isset( $_GET['page'], $_GET['trop'] ) && WPML_PLUGIN_FOLDER . '/menu/languages.php' === $_GET['page'] && 1 === (int) $_GET['trop']; return ! $result; } private function get_suggestions( array $language ) { $suggestions = array(); if ( function_exists( 'translations_api' ) ) { if ( ! $this->translations ) { $api = translations_api( 'core', array( 'version' => $GLOBALS['wp_version'] ) ); if ( ! is_wp_error( $api ) ) { $this->translations = $api['translations']; } } } if ( $this->translations ) { foreach ( $this->translations as $translation ) { $default_locale = $this->get_matching_language( $language, $translation ); if ( $default_locale ) { $suggestions[] = $default_locale; } } } return $suggestions; } /** * @param string $language_attribute * @param array $language * @param array $translation * * @return string|null */ private function find_matching_attribute( $language_attribute, array $language, array $translation ) { if ( $translation && $language[ $language_attribute ] ) { $attribute_value = $language[ $language_attribute ]; $attribute_value = str_replace( '-', '_', $attribute_value ); $attribute_value = strtolower( $attribute_value ); $iso_1 = $iso_2 = ''; if ( array_key_exists( 1, $translation['iso'] ) ) { $iso_1 = strtolower( $translation['iso'][1] ); } if ( array_key_exists( 2, $translation['iso'] ) ) { $iso_2 = strtolower( $translation['iso'][2] ); } if ( $iso_1 === $attribute_value ) { return $translation['language']; } if ( $iso_2 === $attribute_value ) { return $translation['language']; } if ( $iso_1 . '_' . $iso_2 === $attribute_value ) { return $translation['language']; } if ( $iso_2 . '_' . $iso_1 === $attribute_value ) { return $translation['language']; } } return null; } /** * @param array $language * @param array $translation * * @return null|string */ private function get_matching_language( array $language, array $translation ) { $default_locale = $this->find_matching_attribute( 'default_locale', $language, $translation ); if ( ! $default_locale ) { $default_locale = $this->find_matching_attribute( 'tag', $language, $translation ); if ( ! $default_locale ) { $default_locale = $this->find_matching_attribute( 'code', $language, $translation ); } } return $default_locale; } } utilities/class-wpml-inactive-content.php 0000755 00000011107 14720342453 0014631 0 ustar 00 <?php class WPML_Inactive_Content { /** @var wpdb $wpdb */ private $wpdb; /** @var string $current_language */ private $current_language; /** @var array $content_types */ private $content_types; /** @var array $inactive */ private $inactive; public function __construct( wpdb $wpdb, $current_language ) { $this->wpdb = $wpdb; $this->current_language = $current_language; } /** @return bool */ public function has_entries() { return (bool) $this->get_inactive(); } /** @return array */ public function get_content_types() { foreach ( $this->get_inactive() as $types ) { foreach ( $types as $type => $slugs ) { foreach ( $slugs as $slug => $count ) { $this->content_types[ $type ][ $slug ] = $this->get_label( $type, $slug ); } } } return $this->content_types; } /** @return array */ public function get_languages() { return array_keys( $this->get_inactive() ); } /** @return array */ public function get_language_counts_rows() { $counts = array(); foreach ( $this->get_languages() as $language ) { foreach ( $this->get_content_types() as $type => $slugs ) { foreach ( $slugs as $slug => $label ) { $counts[ $language ][] = $this->count( $language, $type, $slug ); } } } return $counts; } /** * @param string $lang * @param string $type * @param string $slug * * @return int */ private function count( $lang, $type, $slug ) { if ( isset( $this->inactive[ $lang ][ $type ][ $slug ] ) ) { return (int) $this->inactive[ $lang ][ $type ][ $slug ]; } return 0; } /** * @param $langName * * @return string */ public function getLangCode( $langName ) { return \WPML\Element\API\Languages::getCodeByName($langName); } /** @return array */ private function get_inactive() { if ( null === $this->inactive ) { $this->inactive = array(); $post_query = $this->wpdb->prepare( " SELECT COUNT(posts.ID) AS c, posts.post_type, languages_translations.name AS language FROM {$this->wpdb->prefix}icl_translations translations JOIN {$this->wpdb->posts} posts ON translations.element_id = posts.ID AND translations.element_type LIKE %s JOIN {$this->wpdb->prefix}icl_languages languages ON translations.language_code = languages.code AND languages.active = 0 JOIN {$this->wpdb->prefix}icl_languages_translations languages_translations ON languages_translations.language_code = languages.code AND languages_translations.display_language_code = %s GROUP BY posts.post_type, translations.language_code ", array( wpml_like_escape( 'post_' ) . '%', $this->current_language ) ); $post_results = $this->wpdb->get_results( $post_query ); if ( $post_results ) { foreach ( $post_results as $r ) { $this->inactive[ $r->language ]['post'][ $r->post_type ] = $r->c; } } $tax_query = $this->wpdb->prepare( " SELECT COUNT(posts.term_taxonomy_id) AS c, posts.taxonomy, languages_translations.name AS language FROM {$this->wpdb->prefix}icl_translations translations JOIN {$this->wpdb->term_taxonomy} posts ON translations.element_id = posts.term_taxonomy_id JOIN {$this->wpdb->prefix}icl_languages languages ON translations.language_code = languages.code AND languages.active = 0 JOIN {$this->wpdb->prefix}icl_languages_translations languages_translations ON languages_translations.language_code = languages.code AND languages_translations.display_language_code = %s WHERE translations.element_type LIKE %s AND translations.element_type NOT LIKE %s GROUP BY posts.taxonomy, translations.language_code ", [ $this->current_language, wpml_like_escape( 'tax_' ) . '%', '%'. wpml_like_escape('tax_translation_priority') . '%' ] ); $tax_results = $this->wpdb->get_results( $tax_query ); if ( $tax_results ) { foreach ( $tax_results as $r ) { if ( ! $this->is_only_default_category( $r ) ) { $this->inactive[ $r->language ]['taxonomy'][ $r->taxonomy ] = $r->c; } } } } return $this->inactive; } /** * @param stdClass $r * * @return bool */ private function is_only_default_category( $r ) { return $r->taxonomy === 'category' && $r->c == 1; } /** * @param string $type * @param string $slug * * @return null|string */ private function get_label( $type, $slug ) { if ( 'post' === $type ) { $type_object = get_post_type_object( $slug ); } else { $type_object = get_taxonomy( $slug ); } if ( isset( $type_object->label ) ) { return $type_object->label; } return null; } } utilities/class-wpml-wp-post.php 0000755 00000001302 14720342453 0012764 0 ustar 00 <?php class WPML_WP_Post { /** @var wpdb $wpdb */ public $wpdb; /** @var int */ private $post_id; /** * @param wpdb $wpdb * @param int $post_id */ public function __construct( wpdb $wpdb, $post_id ) { $this->wpdb = $wpdb; $this->post_id = $post_id; } /** * @param array $post_data_array * @param bool $direct_db_update */ public function update( array $post_data_array, $direct_db_update = false) { if ( $direct_db_update ) { $this->wpdb->update( $this->wpdb->posts, $post_data_array, array( 'ID' => $this->post_id ) ); clean_post_cache( $this->post_id ); } else { $post_data_array['ID'] = $this->post_id; wpml_update_escaped_post( $post_data_array ); } } } utilities/class-wpml-flags-factory.php 0000755 00000001055 14720342453 0014121 0 ustar 00 <?php class WPML_Flags_Factory { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @return WPML_Flags */ public function create() { if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; } return new WPML_Flags( $this->wpdb, new icl_cache( 'flags', true ), new WP_Filesystem_Direct( null ) ); } } utilities/AutoAdjustIdsFactory.php 0000755 00000000342 14720342453 0013351 0 ustar 00 <?php namespace WPML\Utils; class AutoAdjustIdsFactory { /** * @return AutoAdjustIds */ public static function create() { global $sitepress; return new AutoAdjustIds( $sitepress, $sitepress->get_wp_api() ); } } utilities/class-wpml-wp-cache-factory.php 0000755 00000000411 14720342453 0014507 0 ustar 00 <?php class WPML_WP_Cache_Factory { public function create_cache_group( $group ) { return new WPML_WP_Cache( $group ); } public function create_cache_item( $group, $key ) { return new WPML_WP_Cache_Item( $this->create_cache_group( $group ), $key ); } } utilities/class-wpml-wp-query-api.php 0000755 00000000757 14720342453 0013730 0 ustar 00 <?php class WPML_WP_Query_API { private $wp_query; public function __construct( &$wp_query ) { $this->wp_query = $wp_query; } public function get_first_post_type() { $post_type = null; if ( isset( $this->wp_query->query_vars['post_type'] ) ) { $post_type = $this->wp_query->query_vars['post_type']; if ( is_array( $post_type ) ) { if ( count( $post_type ) ) { $post_type = $post_type[0]; } else { $post_type = null; } } } return $post_type; } } utilities/class-wpml-debug-backtrace.php 0000755 00000001133 14720342453 0014360 0 ustar 00 <?php /** * Class WPML_Debug_BackTrace * * @deprecated 4.2.8 */ class WPML_Debug_BackTrace extends WPML\Utils\DebugBackTrace { /** * @param string $php_version Deprecated. * @param int $limit * @param bool $provide_object * @param bool $ignore_args * @param string $debug_backtrace_function * @phpstan-ignore-next-line */ public function __construct( $php_version = null, $limit = 0, $provide_object = false, $ignore_args = true, $debug_backtrace_function = null ) { parent::__construct( $limit, $provide_object, $ignore_args, $debug_backtrace_function ); } } user-language/class-wpml-users-languages-dependencies.php 0000755 00000002734 14720342453 0017642 0 ustar 00 <?php /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_Users_Languages_Dependencies { public $WPML_User_Language_Switcher_Hooks; private $WPML_User_Language_Switcher_Resources; private $WPML_User_Language_Switcher_UI; public $WPML_Users_Languages; public $WPML_User_Language; private $WPML_User_Language_Switcher; private $WPML_Language_Code; private $WPML_WP_API; private $WPML_Upgrade_Admin_Users_Languages; function __construct( &$sitepress ) { $this->WPML_WP_API = new WPML_WP_API(); $this->WPML_Language_Code = new WPML_Language_Code( $sitepress ); $this->WPML_Users_Languages = new WPML_Users_Languages( $this->WPML_Language_Code, $this->WPML_WP_API ); $this->WPML_User_Language = new WPML_User_Language( $sitepress ); $this->WPML_User_Language_Switcher = new WPML_User_Language_Switcher( $this->WPML_Language_Code ); $this->WPML_User_Language_Switcher_Resources = new WPML_User_Language_Switcher_Resources(); $this->WPML_User_Language_Switcher_UI = new WPML_User_Language_Switcher_UI( $this->WPML_User_Language_Switcher, $this->WPML_User_Language_Switcher_Resources ); $this->WPML_User_Language_Switcher_Hooks = new WPML_User_Language_Switcher_Hooks( $this->WPML_User_Language_Switcher, $this->WPML_User_Language_Switcher_UI ); $this->WPML_Upgrade_Admin_Users_Languages = new WPML_Upgrade_Admin_Users_Languages( $sitepress ); } } user-language/class-wpml-user-language.php 0000755 00000026724 14720342453 0014655 0 ustar 00 <?php use WPML\Element\API\Languages; use WPML\FP\Maybe; use WPML\FP\Relation; use WPML\Language\Detection\CookieLanguage; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\User; use WPML\LIB\WP\Option; use WPML\UIPage; use WPML\UrlHandling\WPLoginUrlConverter; use function WPML\Container\make; use function WPML\FP\spreadArgs; /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_User_Language { /** @var SitePress $sitepress */ protected $sitepress; private $language_changes_history = array(); private $admin_language_changes_history = array(); /** * @var \wpdb|null */ private $wpdb; /** * WPML_User_Language constructor. * * @param SitePress $sitepress * @param wpdb|null $wpdb */ public function __construct( SitePress $sitepress, wpdb $wpdb = null ) { $this->sitepress = $sitepress; if ( ! $wpdb ) { global $wpdb; } $this->wpdb = $wpdb; $this->register_hooks(); } public function register_hooks() { Hooks::onAction( 'wp_login', 10, 2 ) ->then( spreadArgs( [ $this, 'update_user_lang_from_login' ] ) ); Hooks::onAction( 'init' ) ->then( [ $this, 'add_how_to_set_notice' ] ); Hooks::onAction( 'wpml_user_profile_options' ) ->then( [ $this, 'show_ui_to_enable_login_translation' ] ); add_action( 'wpml_switch_language_for_email', array( $this, 'switch_language_for_email_action' ), 10, 1 ); add_action( 'wpml_restore_language_from_email', array( $this, 'restore_language_from_email_action' ), 10, 0 ); add_action( 'profile_update', array( $this, 'sync_admin_user_language_action' ), 10, 1 ); add_action( 'wpml_language_cookie_added', array( $this, 'update_user_lang_on_cookie_update' ) ); if ( $this->is_editing_current_profile() || $this->is_editing_other_profile() ) { add_filter( 'get_available_languages', array( $this, 'intersect_wpml_wp_languages' ) ); } register_activation_hook( WPML_PLUGIN_PATH . '/' . WPML_PLUGIN_FILE, [ $this, 'update_user_lang_on_site_setup' ] ); } /** * @param array $wp_languages * * @return array */ public function intersect_wpml_wp_languages( $wp_languages ) { $active_wpml_languages = wp_list_pluck( $this->sitepress->get_active_languages(), 'default_locale' ); $active_wpml_codes = array_flip( $active_wpml_languages ); $intersect_languages_by_locale = array_intersect( $active_wpml_languages, $wp_languages ); $intersect_languages_by_code = array_intersect( $active_wpml_codes, $wp_languages ); return array_merge( $intersect_languages_by_code, $intersect_languages_by_locale ); } /** * @param string $email */ public function switch_language_for_email_action( $email ) { $this->switch_language_for_email( $email ); } /** * @param string $email */ private function switch_language_for_email( $email ) { $language = apply_filters( 'wpml_user_language', null, $email ); if ( $language ) { $user_language = $this->sitepress->get_current_language(); $admin_language = $this->sitepress->get_admin_language(); if ( $language !== $user_language || $language !== $admin_language ) { $this->language_changes_history[] = $user_language; $this->admin_language_changes_history[] = $admin_language; $this->sitepress->switch_lang( $language, true ); $this->sitepress->set_admin_language( $language ); } } } public function restore_language_from_email_action() { $this->wpml_restore_language_from_email(); } private function wpml_restore_language_from_email() { if ( count( $this->language_changes_history ) > 0 ) { $this->sitepress->switch_lang( array_pop( $this->language_changes_history ), true ); } if ( count( $this->admin_language_changes_history ) > 0 ) { $this->sitepress->set_admin_language( array_pop( $this->admin_language_changes_history ) ); } } /** * @param int $user_id */ public function sync_admin_user_language_action( $user_id ) { if ( $this->user_needs_sync_admin_lang() ) { $this->sync_admin_user_language( $user_id ); } } public function sync_default_admin_user_languages() { $sql_users = 'SELECT user_id FROM ' . $this->wpdb->usermeta . ' WHERE meta_key = %s AND meta_value = %s'; $query_users = $this->wpdb->prepare( $sql_users, array( 'locale', '' ) ); $user_ids = $this->wpdb->get_col( $query_users ); if ( $user_ids ) { $language = $this->sitepress->get_default_language(); $sql = 'UPDATE ' . $this->wpdb->usermeta . ' SET meta_value = %s WHERE meta_key = %s and user_id IN (' . wpml_prepare_in( $user_ids ) . ')'; $query = $this->wpdb->prepare( $sql, array( $language, 'icl_admin_language' ) ); $this->wpdb->query( $query ); } } /** * @param int $user_id */ private function sync_admin_user_language( $user_id ) { $wp_language = get_user_meta( $user_id, 'locale', true ); if ( $wp_language ) { $user_language = $this->select_language_code_from_locale( $wp_language ); } else { $user_language = $this->sitepress->get_default_language(); } update_user_meta( $user_id, 'icl_admin_language', $user_language ); if ( $this->user_admin_language_for_edit( $user_id ) && $this->is_editing_current_profile() ) { $this->set_language_cookie( $user_language ); } } /** * @param string $wp_locale * * @return null|string */ private function select_language_code_from_locale( $wp_locale ) { $code = $this->sitepress->get_language_code_from_locale( $wp_locale ); if ( ! $code ) { $guess_code = strtolower( substr( $wp_locale, 0, 2 ) ); $guess_locale = $this->sitepress->get_locale_from_language_code( $guess_code ); if ( $guess_locale ) { $code = $guess_code; } } return $code; } private function user_needs_sync_admin_lang() { $wp_api = $this->sitepress->get_wp_api(); return $wp_api->version_compare_naked( get_bloginfo( 'version' ), '4.7', '>=' ); } private function set_language_cookie( $user_language ) { global $wpml_request_handler; if ( is_object( $wpml_request_handler ) ) { $wpml_request_handler->set_language_cookie( $user_language ); } } /** * @param int $user_id * * @return mixed */ private function user_admin_language_for_edit( $user_id ) { return get_user_meta( $user_id, 'icl_admin_language_for_edit', true ); } /** * @param string $lang */ public function update_user_lang_on_cookie_update( $lang ) { $user_id = get_current_user_id(); if ( $this->user_needs_sync_admin_lang() && $user_id && $this->user_admin_language_for_edit( $user_id ) ) { update_user_meta( $user_id, 'icl_admin_language', $lang ); $wpLang = Maybe::of( $lang ) ->map( Languages::getLanguageDetails() ) ->map( Languages::getWPLocale() ) ->getOrElse( null ); update_user_meta( $user_id, 'locale', $wpLang ); } } private function is_editing_current_profile() { global $pagenow; return isset( $pagenow ) && 'profile.php' === $pagenow; } private function is_editing_other_profile() { global $pagenow; return isset( $pagenow ) && 'user-edit.php' === $pagenow; } public function update_user_lang_on_site_setup() { $current_user_id = get_current_user_id(); $wp_user_lang = get_user_meta( $current_user_id, 'locale', true ); if ( ! $wp_user_lang ) { return; } $lang_code_from_locale = $this->select_language_code_from_locale( $wp_user_lang ); $wpml_user_lang = get_user_meta( $current_user_id, 'icl_admin_language', true ); if ( $current_user_id && $lang_code_from_locale && ! $wpml_user_lang ) { update_user_meta( $current_user_id, 'icl_admin_language', $lang_code_from_locale ); } } public function update_user_lang_from_login( $username, WP_User $user ) { $cookieName = 'wp-wpml_login_lang'; Maybe::fromNullable( make( CookieLanguage::class, [ ':defaultLanguage' => '' ] )->get( $cookieName ) ) ->map( [ $this->sitepress, 'get_locale_from_language_code' ] ) ->reject( Relation::equals( User::getMetaSingle( $user->ID, 'locale' ) ) ) ->map( User::updateMeta( $user->ID, 'locale' ) ); $secure = ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) ); setcookie( $cookieName, '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, $secure ); } public function add_how_to_set_notice() { global $pagenow; $adminNotices = wpml_get_admin_notices(); $noticeId = self::class . 'how_to_set_notice'; $noticeGroup = self::class; if ( $pagenow !== 'profile.php' && ! Option::getOr( WPLoginUrlConverter::SETTINGS_KEY, false ) ) { $notice = new WPML_Notice( $noticeId, self::getNotice(), $noticeGroup ); $notice->set_css_class_types( [ 'info' ] ); $notice->add_capability_check( [ 'manage_options' ] ); $notice->set_dismissible( true ); $notice->add_exclude_from_page( UIPage::TM_PAGE ); $notice->add_user_restriction( User::getCurrentId() ); $adminNotices->add_notice( $notice ); } else { $adminNotices->remove_notice( $noticeGroup, $noticeId ); } } public static function getNotice() { ob_start(); ?> <h2><?php esc_html_e( 'Do you want the WordPress admin to be in a different language?', 'sitepress' ); ?></h2> <p> <?php esc_html_e( 'WPML lets each user choose the admin language, unrelated of the language in which visitors will see the front-end of the site.', 'sitepress' ); ?> <br/> <br/> <?php /* translators: %s is replaced with the word 'profile' wrapped in a link */ echo sprintf( __( 'Go to your %s to choose your admin language.', 'sitepress' ), '<a href="' . admin_url( 'profile.php' ) . '">' . __( 'profile', 'sitepress' ) . '</a>' ); ?> </p> <?php return ob_get_clean(); } public function show_ui_to_enable_login_translation() { if ( current_user_can( 'manage_options' ) && ! WPLoginUrlConverter::isEnabled() ) { $settingsPage = UIPage::getSettings() . '#ml-content-setup-sec-wp-login'; $settingsPageLink = '<a href="' . $settingsPage . '">' . __( 'WPML->Settings', 'sitepress' ) . '</a>'; // translators: %s link to WPML Settings page $message = esc_html__( 'WPML will include a language switcher on the WordPress login page. To change this, go to %s.', 'sitepress' ); ?> <tr class="user-language-wrap"> <th><?php esc_html_e( 'Login Page:', 'sitepress' ); ?></th> <td> <?php wp_nonce_field( 'icl_login_page_translation_nonce', 'icl_login_page_translation_nonce' ); ?> <div id="wpml-login-translation"> <p> <?php esc_html_e( 'Your site currently has language switching for the login page disabled.', 'sitepress' ); ?> <button type="button" class="button wpml-login-activate"> <?php esc_html_e( 'Activate', 'sitepress' ); ?> </button> <span class="spinner" style="float: none"></span> </p> </div> <div id="wpml-login-translation-updated" style="display:none"> <?php echo sprintf( $message, $settingsPageLink ); ?> </div> <script type="text/javascript"> jQuery(function ($) { $('.wpml-login-activate').click(function () { $(this).prop('disabled', true); $(this).parent().find('.spinner').css('visibility', 'visible'); $.ajax({ url: ajaxurl, type: "POST", data: { icl_ajx_action: 'icl_login_page_translation', _icl_nonce: $('#icl_login_page_translation_nonce').val(), login_page_translation: 1 }, success: function (response) { $('#wpml-login-translation').hide(); $('#wpml-login-translation-updated').css('display', 'block'); } }); }); }); </script> </td> </tr> <?php } } } user-language/class-wpml-users-languages.php 0000755 00000004545 14720342453 0015220 0 ustar 00 <?php /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_Users_Languages { /** * @var WPML_Language_Code */ private $WPML_Language_Code; /** * @var WPML_WP_API */ private $WPML_WP_API; /** * @param WPML_Language_Code $WPML_Language_Code * @param WPML_WP_API $WPML_WP_API */ public function __construct( &$WPML_Language_Code, &$WPML_WP_API ) { $this->WPML_Language_Code = &$WPML_Language_Code; $this->WPML_WP_API = &$WPML_WP_API; $this->register_hooks(); } public function register_hooks() { add_filter( 'wpml_user_language', array( $this, 'wpml_user_language_filter' ), 10, 2 ); } public function wpml_user_language_filter( $language, $email ) { return $this->wpml_user_language( $language, $email ); } private function wpml_user_language( $language, $email ) { $language_in_db = $this->get_recipient_language( $email ); if ( $language_in_db ) { $language = $language_in_db; } return $this->WPML_Language_Code->sanitize( $language ); } private function get_recipient_language( $email ) { $language = apply_filters( 'wpml_user_email_language', null, $email ); if ( ! $language && is_email( $email ) ) { $language = $this->get_language_from_globals(); } if ( ! $language ) { $language = $this->get_language_from_tables( $email ); } if ( ! $language ) { $language = $this->get_language_from_fallbacks(); } return $this->WPML_Language_Code->sanitize( $language ); } private function get_language_from_globals() { $lang = null; $inputs = array( $_POST, $_GET, $GLOBALS ); foreach ( $inputs as $input ) { if ( array_key_exists( 'wpml_user_email_language', $input ) ) { $lang = sanitize_title( $input['wpml_user_email_language'] ); $lang = $this->WPML_Language_Code->sanitize( $lang ); break; } } return $lang; } private function get_language_from_tables( $email ) { $lang = $this->WPML_Language_Code->get_from_user_meta( $email ); return $this->WPML_Language_Code->sanitize( $lang ); } private function get_language_from_fallbacks() { $lang = get_option( 'wpml_user_email_language' ); if ( ! $lang ) { $lang = apply_filters( 'wpml_default_language', null ); if ( $this->WPML_WP_API->is_front_end() ) { $lang = apply_filters( 'wpml_current_language', null ); } } return $this->WPML_Language_Code->sanitize( $lang ); } } user-language/class-wpml-user-language-switcher-hooks.php 0000755 00000004451 14720342453 0017615 0 ustar 00 <?php use WPML\API\Sanitize; /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_User_Language_Switcher_Hooks { private $nonce_name = 'wpml_user_language_switcher'; /** * @var WPML_User_Language_Switcher_UI */ private $user_language_switcher_ui; /** * @var WPML_User_Language_Switcher */ private $user_language_switcher; /** * @param WPML_User_Language_Switcher $WPML_User_Language_Switcher * @param WPML_User_Language_Switcher_UI $WPML_User_Language_Switcher_UI */ public function __construct( &$WPML_User_Language_Switcher, &$WPML_User_Language_Switcher_UI ) { $this->user_language_switcher = &$WPML_User_Language_Switcher; $this->user_language_switcher_ui = &$WPML_User_Language_Switcher_UI; add_action( 'wpml_user_language_switcher', array( $this, 'language_switcher_action' ), 10, 1 ); add_action( 'wp_ajax_wpml_user_language_switcher_form_ajax', array( $this, 'language_switcher_form_ajax_callback' ) ); add_action( 'wp_ajax_nopriv_wpml_user_language_switcher_form_ajax', array( $this, 'language_switcher_form_ajax_callback' ) ); } public function language_switcher_action( $args ) { $defaults = array( 'mail' => null, 'auto_refresh_page' => 0, ); $args = array_merge( $defaults, $args ); $model = $this->user_language_switcher->get_model( $args['mail'] ); echo $this->user_language_switcher_ui->language_switcher( $args, $model ); } public function language_switcher_form_ajax_callback() { $this->language_switcher_form_ajax(); } public function language_switcher_form_ajax() { $language = Sanitize::stringProp( 'language', $_POST ); $language = $this->user_language_switcher->sanitize( $language ); $email = filter_input( INPUT_POST, 'mail', FILTER_SANITIZE_EMAIL ); $valid = $this->is_valid_data( $_POST['nonce'], $email ); if ( ! $valid || ! $language ) { wp_send_json_error(); } $saved_by_third_party = $updated = apply_filters( 'wpml_user_language_switcher_save', false, $email, $language ); if ( ! $saved_by_third_party ) { $updated = $this->user_language_switcher->save_language_user_meta( $email, $language ); } wp_send_json_success( $updated ); } private function is_valid_data( $nonce, $email ) { return ( wp_verify_nonce( $nonce, $this->nonce_name ) && is_email( $email ) ); } } user-language/class-wpml-user-language-switcher.php 0000755 00000004236 14720342453 0016475 0 ustar 00 <?php /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_User_Language_Switcher { /** * @var WPML_Language_Code */ private $WPML_Language_Code; /** * WPML_User_Language_Switcher constructor. * * @param WPML_Language_Code $WPML_Language_Code */ public function __construct( &$WPML_Language_Code ) { $this->WPML_Language_Code = &$WPML_Language_Code; } /** * @param string $email * * @return false|mixed|string|null */ private function to_be_selected( $email ) { $language = $this->WPML_Language_Code->get_from_user_meta( $email ); if ( ! $language ) { $language = isset( $_POST['language'] ) ? $_POST['language'] : null; } return $language; } /** * @param string $email * @param string $language * * @return bool|int */ public function save_language_user_meta( $email, $language ) { $user = get_user_by( 'email', $email ); $updated = false; if ( $user && isset( $user->ID ) ) { $language = $this->WPML_Language_Code->sanitize( $language ); $updated = update_user_meta( $user->ID, 'icl_admin_language', $language ); } return $updated; } /** * @param string $language * * @return false|string|null */ public function sanitize( $language ) { return $this->WPML_Language_Code->sanitize( $language ); } /** * @param string $email * * @return array[] */ public function get_model( $email ) { $active_languages = apply_filters( 'wpml_active_languages', null, null ); $to_be_selected = $this->to_be_selected( $email ); $options = array(); $options[] = array( 'label' => __( 'Choose language:', 'sitepress' ), 'value' => 0, 'selected' => false, ); foreach ( $active_languages as $code => $lang ) { $selected = ( $to_be_selected === $code ); if ( array_key_exists( 'translated_name', $lang ) ) { $name = $lang['translated_name']; } elseif ( array_key_exists( 'native_name', $lang ) ) { $name = $lang['native_name']; } else { $name = $lang['display_name']; } $options[] = array( 'label' => $name, 'value' => $code, 'selected' => $selected, ); } return array( 'options' => $options, ); } } user-language/class-wpml-user-admin-language.php 0000755 00000003054 14720342453 0015732 0 ustar 00 <?php class WPML_User_Admin_Language { const CACHE_GROUP = 'get_user_admin_language'; /** @var SitePress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param int|string $user_id * @param bool $reload * * @return bool|mixed|null|string */ public function get( $user_id, $reload = false ) { $user_id = (int) $user_id; $lang = wp_cache_get( $user_id, self::CACHE_GROUP ); if ( ! $lang || $reload ) { $lang = $this->get_from_user_settings( $user_id ); if ( ! $lang ) { $lang = $this->get_from_global_settings(); } wp_cache_set( $user_id, $lang, self::CACHE_GROUP ); } return $lang; } /** * @param int $user_id * * @return null|false|string */ private function get_from_user_settings( $user_id ) { if ( get_user_meta( $user_id, 'icl_admin_language_for_edit', true ) ) { $lang = $this->sitepress->get_current_language(); } else { $lang = get_user_meta( $user_id, 'icl_admin_language', true ); if ( ! $lang ) { $user_locale = get_user_meta( $user_id, 'locale', true ); if ( $user_locale ) { $lang = $this->sitepress->get_language_code_from_locale( $user_locale ); } } } return $lang; } /** * @return string */ private function get_from_global_settings() { $lang = $this->sitepress->get_setting( 'admin_default_language' ); if ( ! $lang || $lang === '_default_' ) { $default = $this->sitepress->get_default_language(); $lang = $default ? $default : 'en'; } return $lang; } } user-language/class-wpml-user-language-switcher-ui.php 0000755 00000003504 14720342453 0017105 0 ustar 00 <?php use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Environment; /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_User_Language_Switcher_UI { /** * @var \WPML_User_Language_Switcher */ private $user_language_switcher; /** * @var \WPML_User_Language_Switcher_Resources */ private $resources; /** * WPML_User_Language_Switcher_UI constructor. * * @param WPML_User_Language_Switcher $WPML_User_Language_Switcher * @param WPML_User_Language_Switcher_Resources $WPML_User_Language_Switcher_Resources */ public function __construct( $WPML_User_Language_Switcher, $WPML_User_Language_Switcher_Resources ) { $this->user_language_switcher = $WPML_User_Language_Switcher; $this->resources = $WPML_User_Language_Switcher_Resources; } /** * @param array<string,mixed> $args * @param array<string,mixed> $model * * @return string * @throws \WPML\Core\Twig\Error\LoaderError * @throws \WPML\Core\Twig\Error\RuntimeError * @throws \WPML\Core\Twig\Error\SyntaxError */ public function language_switcher( $args, $model ) { $this->resources->enqueue_scripts( $args ); return $this->get_view( $model ); } /** * @param array<string,mixed> $model * * @return string * @throws \WPML\Core\Twig\Error\LoaderError * @throws \WPML\Core\Twig\Error\RuntimeError * @throws \WPML\Core\Twig\Error\SyntaxError */ protected function get_view( $model ) { $template_paths = array( WPML_PLUGIN_PATH . '/templates/user-language/', ); $template = 'language-switcher.twig'; $loader = new Twig_Loader_Filesystem( $template_paths ); $environment_args = array(); if ( WP_DEBUG ) { $environment_args['debug'] = true; } $twig = new Twig_Environment( $loader, $environment_args ); return $twig->render( $template, $model ); } } user-language/class-wpml-language-code.php 0000755 00000001746 14720342453 0014606 0 ustar 00 <?php /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_Language_Code extends WPML_SP_User { private $WPML_WP_API; function __construct( &$sitepress ) { parent::__construct( $sitepress ); $this->WPML_WP_API = $this->sitepress->get_wp_api(); } function sanitize( $code ) { $code = trim( (string) $code ); if ( $code ) { if ( strlen( $code ) < 2 ) { return false; } if ( strlen( $code ) > 2 ) { $code = substr( $code, 0, 2 ); } $code = strtolower( $code ); } $languages = $this->sitepress->get_languages(); if ( ! isset( $languages[ $code ] ) ) { return false; } if ( ! (bool) $code ) { $code = null; } return $code; } function get_from_user_meta( $email ) { $language = false; $user = get_user_by( 'email', $email ); if ( $user && isset( $user->ID ) ) { $language = $this->WPML_WP_API->get_user_meta( $user->ID, 'icl_admin_language', true ); } return $this->sanitize( $language ); } } user-language/class-wpml-user-language-switcher-resources.php 0000755 00000001464 14720342453 0020505 0 ustar 00 <?php /** * @package wpml-core * @subpackage wpml-user-language */ class WPML_User_Language_Switcher_Resources { private $nonce_name = 'wpml_user_language_switcher'; public function __construct() { } /** * @param array<string,mixed> $data */ public function enqueue_scripts( $data ) { wp_register_script( 'wpml-user-language', ICL_PLUGIN_URL . '/res/js/wpml-user-language.js', array( 'jquery' ) ); $wp_mail_script_data = array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'mail' => $data['mail'], 'auto_refresh_page' => $data['auto_refresh_page'], 'nonce' => wp_create_nonce( $this->nonce_name ), ); wp_localize_script( 'wpml-user-language', 'wpml_user_language_data', $wp_mail_script_data ); wp_enqueue_script( 'wpml-user-language' ); } } translation-mode/endpoints/SetTranslateEverything.php 0000755 00000002422 14720342453 0017225 0 ustar 00 <?php namespace WPML\TranslationMode\Endpoint; use WPML\Ajax\IHandler; use WPML\API\PostTypes; use WPML\Collect\Support\Collection; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Right; use WPML\Setup\Option; use function WPML\FP\partialRight; class SetTranslateEverything implements IHandler { public function run( Collection $data ) { if ( $data->has( 'translateEverything' ) ) { $useTranslateEverything = $data->get( 'translateEverything' ); if ( ! $useTranslateEverything ) { // Translate Some is enabled, reset pause of Translate Everything. Option::setIsPausedTranslateEverything( false ); } Option::setTranslateEverything( $useTranslateEverything ); do_action( 'wpml_set_translate_everything', $useTranslateEverything ); } if ( $data->has( 'onlyNew' ) ) { $markAsComplete = partialRight( [ Option::class, 'markPostTypeAsCompleted' ], $data->get( 'onlyNew', false ) ? Languages::getSecondaryCodes() : [] ); Fns::map( Fns::unary( $markAsComplete ), PostTypes::getAutomaticTranslatable() ); } if ( $data->has( 'reviewMode' ) ) { Option::setReviewMode( $data->get( 'reviewMode' ) ); } if ( $data->has( 'whoMode' ) ) { Option::setTranslationMode( $data->get( 'whoMode' ) ); } return Right::of( true ); } } class-wpml-tm-translated-field.php 0000755 00000001254 14720342453 0013206 0 ustar 00 <?php class WPML_TM_Translated_Field { private $original; private $translation; private $finished_state; /** * WPML_TM_Translated_Field constructor. * * @param string $original * @param string $translation * @param bool $finished_state */ public function __construct( $original, $translation, $finished_state ) { $this->original = $original; $this->translation = $translation; $this->finished_state = $finished_state; } public function get_translation() { return $this->translation; } public function is_finished( $original ) { if ( $original == $this->original ) { return $this->finished_state; } else { return false; } } } background-task/BackgroundTaskViewModel.php 0000755 00000003403 14720342453 0015061 0 ustar 00 <?php namespace WPML\BackgroundTask; use WPML\Core\BackgroundTask\Model\TaskEndpointInterface; use WPML\Core\BackgroundTask\Model\BackgroundTask; use function WPML\Container\make; class BackgroundTaskViewModel { /** * Prepares the endpoint data for the react component to consume it. * * @param BackgroundTask $backgroundTask * @param bool $getLock * * @return ?array{ * isPaused: bool, * progressTotal: int, * progressDone: int, * payload: object, * taskId: int, * taskStatus: string, * isCompleted: bool, * description: string, * taskType: string, * hasLock: bool * } **/ public static function get( $backgroundTask, $getLock = false ) { $className = $backgroundTask->getTaskType(); if ( ! class_exists( $className ) ) { return; } /** @var TaskEndpointInterface $endpointInstance */ $endpointInstance = make( $className ); $endpointLock = make( 'WPML\Utilities\Lock', [ ':name' => $backgroundTask->getTaskType() ] ); $hasLock = $getLock ? $endpointLock->create( $endpointInstance->getLockTime() ) : false; return [ 'isPaused' => $backgroundTask->isStatusPaused(), 'progressTotal' => $backgroundTask->getTotalCount(), 'progressDone' => $backgroundTask->getCompletedCount(), 'payload' => $backgroundTask->getPayload(), 'taskId' => $backgroundTask->getTaskId(), 'taskStatus' => $backgroundTask->getStatusName(), 'isCompleted' => $backgroundTask->isStatusCompleted(), 'description' => $endpointInstance->getDescription( wpml_collect( $backgroundTask->getPayload() ) ), 'taskType' => $backgroundTask->getTaskType(), 'isDisplayed' => $endpointInstance->isDisplayed(), 'hasLock' => $hasLock, ]; } } background-task/AbstractTaskEndpoint.php 0000755 00000004544 14720342453 0014441 0 ustar 00 <?php namespace WPML\BackgroundTask; use WPML\BackgroundTask\BackgroundTaskLoader; use WPML\BackgroundTask\BackgroundTaskViewModel; use WPML\Collect\Support\Collection; use WPML\Core\BackgroundTask\Exception\TaskIsNotRunnableException; use WPML\Core\BackgroundTask\Model\TaskEndpointInterface; use WPML\Core\BackgroundTask\Service\BackgroundTaskService; use WPML\Core\BackgroundTask\Command\UpdateBackgroundTask; use WPML\Core\BackgroundTask\Model\BackgroundTask; use WPML\FP\Either; use function WPML\Container\make; abstract class AbstractTaskEndpoint implements TaskEndpointInterface { const LOCK_TIME = 2*60; const MAX_RETRIES = 0; /** @var UpdateBackgroundTask $updateBackgroundTask */ protected $updateBackgroundTask; /** @var BackgroundTaskService $backgroundTaskService */ protected $backgroundTaskService; /** * @param UpdateBackgroundTask $updateBackgroundTask * @param BackgroundTaskService $backgroundTaskService */ public function __construct( UpdateBackgroundTask $updateBackgroundTask, BackgroundTaskService $backgroundTaskService ) { $this->updateBackgroundTask = $updateBackgroundTask; $this->backgroundTaskService = $backgroundTaskService; } public function isDisplayed() { return true; } public function getLockTime() { return static::LOCK_TIME; } public function getMaxRetries() { return static::MAX_RETRIES; } public function getType() { return static::class; } /** * @param BackgroundTask $task * * @return BackgroundTask */ abstract function runBackgroundTask( BackgroundTask $task ); public function run( Collection $data ) { try { $taskId = $data['taskId']; $task = $this->backgroundTaskService->startByTaskId( $taskId ); $task = $this->runBackgroundTask( $task ); $this->updateBackgroundTask->runUpdate( $task ); return $this->getResponse( $task ); } catch ( TaskIsNotRunnableException $e ) { return Either::of( [ 'error' => $e->getMessage() ] ); } } /** * @param BackgroundTask $backgroundTask * * @return callable|\WPML\FP\Right */ private function getResponse( BackgroundTask $backgroundTask ) { /** @var \WPML\Utilities\Lock $endpointLock */ $endpointLock = make( 'WPML\Utilities\Lock', [ ':name' => $backgroundTask->getTaskType() ] ); $endpointLock->release(); return Either::of( BackgroundTaskViewModel::get( $backgroundTask, true ) ); } } background-task/BackgroundTaskLoader.php 0000755 00000004527 14720342453 0014404 0 ustar 00 <?php namespace WPML\BackgroundTask; use WPML\Collect\Support\Collection; use WPML\Core\BackgroundTask\Command\UpdateBackgroundTask; use WPML\Core\BackgroundTask\Repository\BackgroundTaskRepository; use WPML\Core\WP\App\Resources; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\LIB\WP\Hooks; class BackgroundTaskLoader implements \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var UpdateBackgroundTask $updateBackgroundTaskCommand */ private $updateBackgroundTaskCommand; /** @var BackgroundTaskRepository $backgroundTaskRepository */ private $backgroundTaskRepository; /** * @param UpdateBackgroundTask $updateBackgroundTaskCommand * @param BackgroundTaskRepository $backgroundTaskRepository */ public function __construct( UpdateBackgroundTask $updateBackgroundTaskCommand, BackgroundTaskRepository $backgroundTaskRepository ) { $this->updateBackgroundTaskCommand = $updateBackgroundTaskCommand; $this->backgroundTaskRepository = $backgroundTaskRepository; } public function add_hooks() { Hooks::onAction( 'wp_loaded' ) ->then( function() { $tasks = $this->getSerializedTasks(); Resources::enqueueGlobalVariable('wpml_background_tasks', [ /** @phpstan-ignore-next-line */ 'endpoints' => array_merge( Lst::pluck('taskType', $tasks), [ BackgroundTaskLoader::class ] ), 'tasks' => $tasks, ] ); } ); } /** * @param \WPML\Collect\Support\Collection $data */ public function run( Collection $data ) { $taskId = $data['taskId']; $cmd = $data['cmd']; $task = $this->backgroundTaskRepository->getByTaskId( $taskId ); if ( 'stop' === $cmd ) { $this->updateBackgroundTaskCommand->runStop( $task ); } elseif ( 'pause' === $cmd ) { $this->updateBackgroundTaskCommand->saveStatusPaused( $task ); } elseif ( 'resume' === $cmd ) { $this->updateBackgroundTaskCommand->saveStatusResumed( $task ); } elseif ( 'restart' === $cmd ) { $this->updateBackgroundTaskCommand->saveStatusRestart( $task ); } $taskData = BackgroundTaskViewModel::get( $task ); return Either::of( $taskData ); } /** * @return array */ public function getSerializedTasks() { return Fns::map( function( $task ) { return BackgroundTaskViewModel::get( $task, true ); }, $this->backgroundTaskRepository->getAllRunnableTasks() ); } } xliff/class-wpml-tm-string-xliff-reader.php 0000755 00000001637 14720342453 0014755 0 ustar 00 <?php class WPML_TM_String_Xliff_Reader extends WPML_TM_Xliff_Reader { /** * Retrieve the string translations from a XLIFF * * @param string $content The XLIFF representing a set of strings * * @return WP_Error|array The string translation representation or WP_Error * on failure */ public function get_data( $content ) { $xliff = $this->load_xliff( $content ); $data = array(); if ( $xliff && ! $xliff instanceof WP_Error ) { /** @var SimpleXMLElement $node */ foreach ( $xliff->{'file'}->{'body'}->children() as $node ) { $target = $this->get_xliff_node_target( $node ); if ( ! $target && $target !== '0' ) { return $this->invalid_xliff_error(); } $target = $this->replace_xliff_new_line_tag_with_new_line( $target ); $attr = $node->attributes(); $data[ (string) $attr['id'] ] = $target; } } return $data; } } xliff/class-wpml-tm-general-xliff-reader.php 0000755 00000001403 14720342453 0015053 0 ustar 00 <?php class WPML_TM_General_Xliff_Reader extends WPML_TM_Xliff_Reader { public function get_xliff_job_identifier( $content ) { $xliff = $this->load_xliff( $content ); if ( is_wp_error( $xliff ) ) { $identifier = false; } else { $identifier = $this->identifier_from_xliff( $xliff ); } return $identifier; } /** * Retrieve the translation from a XLIFF * * @param string $content The XLIFF representing a job * * @return WP_Error|array */ public function get_data( $content ) { $xliff = $this->load_xliff( $content ); if ( is_wp_error( $xliff ) ) { $data = $xliff; } else { $job = $this->get_job_for_xliff( $xliff ); $data = is_wp_error( $job ) ? $job : $this->generate_job_data( $xliff, $job ); } return $data; } } xliff/wpml-tm-xliff.php 0000755 00000014423 14720342453 0011103 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_XLIFF { /** @var DOMElement */ private $body; /** @var DOMDocument */ private $dom; /** @var DOMDocumentType */ private $dtd; /** @var DOMElement */ private $file; /** @var DOMElement */ private $file_header; /** @var DOMElement */ private $file_reference; /** @var DOMElement */ private $phase_group; /** @var DOMElement */ private $root; /** @var array */ private $trans_units; /** @var string */ private $xliff_version; /** * WPML_TM_XLIFF constructor. * * @param string $xliff_version * @param string $xml_version * @param string $xml_encoding */ public function __construct( $xliff_version = '1.2', $xml_version = '1.0', $xml_encoding = 'utf-8' ) { $this->dom = new DOMDocument( $xml_version, $xml_encoding ); $this->root = $this->dom->createElement( 'xliff' ); $this->file = $this->dom->createElement( 'file' ); $this->file_header = $this->dom->createElement( 'header' ); $this->body = $this->dom->createElement( 'body' ); $this->xliff_version = $xliff_version; } /** * @param array $attributes * * @return $this */ public function setFileAttributes( $attributes ) { foreach ( $attributes as $name => $value ) { $this->file->setAttribute( $name, $value ); } return $this; } /** * @param array $args * * @return $this */ public function setPhaseGroup( array $args ) { if ( $args ) { $phase_items = array(); foreach ( $args as $name => $data ) { if ( $name && array_key_exists( 'process-name', $data ) && array_key_exists( 'note', $data ) && $data['note'] ) { $phase = $this->dom->createElement( 'phase' ); $phase->setAttribute( 'phase-name', $name ); $phase->setAttribute( 'process-name', $data['process-name'] ); $note = $this->dom->createElement( 'note' ); $note->nodeValue = $data['note']; $phase->appendChild( $note ); $phase_items[] = $phase; } } if ( $phase_items ) { $this->phase_group = $this->dom->createElement( 'phase-group' ); foreach ( $phase_items as $phase_item ) { $this->phase_group->appendChild( $phase_item ); } } } return $this; } /** * @param array $references * * @return $this */ public function setReferences( array $references ) { if ( $references ) { foreach ( $references as $name => $value ) { if ( ! $value ) { continue; } if ( ! $this->file_reference ) { $this->file_reference = $this->dom->createElement( 'reference' ); } $reference = $this->dom->createElement( $name ); $reference->setAttribute( 'href', $value ); $this->file_reference->appendChild( $reference ); } } return $this; } // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid /** * Set translation units for xliff. * * @param array $trans_units Translation units. * * @return $this */ public function setTranslationUnits( $trans_units ) { // phpcs:enable if ( $trans_units ) { foreach ( $trans_units as $trans_unit ) { $trans_unit_element = $this->dom->createElement( 'trans-unit' ); if ( array_key_exists( 'attributes', $trans_unit ) && is_array( $trans_unit['attributes'] ) ) { foreach ( $trans_unit['attributes'] as $name => $value ) { $trans_unit_element->setAttribute( $name, $value ); } } $this->appendData( 'source', $trans_unit, $trans_unit_element ); $this->appendData( 'target', $trans_unit, $trans_unit_element ); if ( $trans_unit['note']['content'] ) { $note = $this->dom->createElement( 'note' ); // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $note->nodeValue = 'wrap_tag:' . $trans_unit['note']['content']; // phpcs:enable $trans_unit_element->appendChild( $note ); } $this->trans_units[] = $trans_unit_element; } } return $this; } /** * @param string $type * @param array $trans_unit * @param DOMElement $trans_unit_element */ private function appendData( $type, $trans_unit, $trans_unit_element ) { if ( array_key_exists( $type, $trans_unit ) ) { $source = $this->dom->createElement( $type ); $datatype = isset( $trans_unit['attributes']['datatype'] ) ? $trans_unit['attributes']['datatype'] : ''; $source_cdata = $this->dom->createCDATASection( $this->validate( $datatype, $trans_unit[ $type ]['content'] ) ); $source->appendChild( $source_cdata ); if ( array_key_exists( 'attributes', $trans_unit[ $type ] ) ) { foreach ( $trans_unit[ $type ]['attributes'] as $name => $value ) { $source->setAttribute( $name, $value ); } } $trans_unit_element->appendChild( $source ); } } /** * Validate content. * * @param string $datatype Type of content data. * @param string $content Content. * * @return string */ private function validate( $datatype, $content ) { if ( 'html' === $datatype ) { $validator = new WPML_TM_Validate_HTML(); $validator->validate( $content ); return $validator->get_html(); } return $content; } public function toString() { $this->compose(); $xml = $this->dom->saveXML(); return $xml ? trim( $xml ) : ''; } private function compose() { $this->dom->xmlStandalone = false; $this->setRoot( $this->xliff_version ); if ( $this->dtd ) { $this->dom->appendChild( $this->dtd ); } if ( $this->phase_group ) { $this->file_header->appendChild( $this->phase_group ); } if ( $this->file_reference ) { $this->file_header->appendChild( $this->file_reference ); } if ( $this->trans_units ) { foreach ( $this->trans_units as $trans_unit ) { $this->body->appendChild( $trans_unit ); } } $this->file->appendChild( $this->file_header ); $this->file->appendChild( $this->body ); $this->root->appendChild( $this->file ); $this->dom->appendChild( $this->root ); } private function setRoot( $version ) { if ( $version === '1.0' ) { $implementation = new DOMImplementation(); $this->dtd = $implementation->createDocumentType( 'xliff', '-//XLIFF//DTD XLIFF//EN', 'http://www.oasis-open.org/committees/xliff/documents/xliff.dtd' ); } $this->root->setAttribute( 'version', $version ); $this->root->setAttribute( 'xmlns', 'urn:oasis:names:tc:xliff:document:' . $version ); } } xliff/classs-wpml-tm-xliff-phase.php 0000755 00000000656 14720342453 0013472 0 ustar 00 <?php abstract class WPML_TM_XLIFF_Phase { public function get() { $data = $this->get_data(); if ( $data ) { return array( $this->get_phase_name() => array( 'process-name' => $this->get_process_name(), 'note' => $data, ), ); } return array(); } abstract protected function get_data(); abstract protected function get_phase_name(); abstract protected function get_process_name(); } xliff/class-wpml-tm-xliff-factory.php 0000755 00000001267 14720342453 0013655 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_XLIFF_Factory { const WPML_XLIFF_DEFAULT_VERSION = WPML_XLIFF_DEFAULT_VERSION; const CREATE_FOR_WRITE = 'WPML_TM_Xliff_Writer'; const CREATE_FOR_FRONT_END = 'WPML_TM_Xliff_Frontend'; public function create_writer( $xliff_version = self::WPML_XLIFF_DEFAULT_VERSION ) { return new WPML_TM_Xliff_Writer( wpml_tm_load_job_factory(), $xliff_version, wpml_tm_xliff_shortcodes() ); } public function create_frontend() { global $sitepress; $support_info = new WPML_TM_Support_Info(); return new WPML_TM_Xliff_Frontend( wpml_tm_load_job_factory(), $sitepress, $support_info->is_simplexml_extension_loaded() ); } } xliff/class-wpml-tm-xliff-shared.php 0000755 00000015125 14720342453 0013452 0 ustar 00 <?php abstract class WPML_TM_Xliff_Shared extends WPML_TM_Job_Factory_User { /** @var ?WP_Error $error */ protected $error; /** @var WPML_TM_Validate_HTML */ private $validator = null; /** * @param $string * * @return mixed */ protected function replace_xliff_new_line_tag_with_new_line( $string ) { return WPML_TP_Xliff_Parser::restore_new_line( $string ); } /** * @param SimpleXMLElement $xliff * * @return string */ protected function identifier_from_xliff( $xliff ) { $file_attributes = $xliff->{'file'}->attributes(); return (string) $file_attributes['original']; } /** * @param SimpleXMLElement $xliff * * @return stdClass|WP_Error */ public function get_job_for_xliff( SimpleXMLElement $xliff ) { $identifier = $this->identifier_from_xliff( $xliff ); $job_identifier_parts = explode( '-', $identifier ); if ( count( $job_identifier_parts ) === 2 && is_numeric( $job_identifier_parts[0] ) ) { $job_id = $job_identifier_parts[0]; $job_id = apply_filters( 'wpml_job_id', $job_id ); $md5 = $job_identifier_parts[1]; /** @var stdClass $job */ $job = $this->job_factory->get_translation_job( (int) $job_id, false, 1, false ); if ( ! $job || $md5 !== md5( $job_id . $job->original_doc_id ) ) { $job = $this->does_not_belong_error(); } } else { $job = $this->invalid_xliff_error(); } return $job; } /** * @param $xliff_node * * @return string */ protected function get_xliff_node_target( $xliff_node ) { $target = ''; if ( isset( $xliff_node->target->mrk ) ) { $target = (string) $xliff_node->target->mrk; } elseif ( isset( $xliff_node->target ) ) { $target = (string) $xliff_node->target; } return $target; } /** * @param $validator WPML_TM_Validate_HTML */ public function set_validator( $validator ) { $this->validator = $validator; } /** * @return WPML_TM_Validate_HTML */ private function get_validator() { if ( null === $this->validator ) { $this->set_validator( new WPML_TM_Validate_HTML() ); } return $this->validator; } protected function generate_job_data( SimpleXMLElement $xliff, $job ) { $data = [ 'job_id' => $job->job_id, 'rid' => $job->rid, 'fields' => [], 'complete' => 1, ]; foreach ( $xliff->file->body->children() as $node ) { $attr = $node->attributes(); $type = (string) $attr['id']; $target = $this->get_xliff_node_target( $node ); if ( 'html' === (string) $attr['datatype'] ) { $target = $this->get_validator()->restore_html( $target ); } if ( ! $this->is_valid_target( $target ) ) { return $this->invalid_xliff_error( array( 'target' ) ); } foreach ( $job->elements as $element ) { if ( $element->field_type === $type ) { $target = $this->replace_xliff_new_line_tag_with_new_line( $target ); $field = array(); $field['data'] = $target; $field['finished'] = 1; $field['tid'] = $element->tid; $field['field_type'] = $element->field_type; $field['format'] = $element->field_format; $data['fields'][ $element->field_type ] = $field; break; } } } return $data; } /** * Validate XLIFF target on reading XLIFF. * * @param $target string * * @return bool */ private function is_valid_target( $target ) { return $target || '0' === $target; } protected function validate_file( $name, $content, $current_user ) { $xml = $this->check_xml_file( $name, $content ); if ( ! $xml ) { return null; } $this->error = null; if ( is_wp_error( $xml ) ) { $this->error = $xml; return null; } $job = $this->get_job_for_xliff( $xml ); if ( is_wp_error( $job ) ) { $this->error = $job; return null; } if ( ! $this->is_user_the_job_owner( $current_user, $job ) ) { $this->error = $this->not_the_job_owner_error( $job ); return null; } $job_data = $this->generate_job_data( $xml, $job ); if ( is_wp_error( $job_data ) ) { $this->error = $job_data; return null; } return array( $job, $job_data ); } /** * @param string $filename * @return bool */ function validate_file_name( $filename ) { $ignored_files = apply_filters( 'wpml_xliff_ignored_files', array( '__MACOSX' ) ); return ! ( '/' === substr( $filename, -1 ) || '/' === substr( $filename, 0, 1 ) || in_array( $filename, $ignored_files, false ) ); } protected function is_user_the_job_owner( $current_user, $job ) { return (int) $current_user->ID === (int) $job->translator_id; } protected function not_the_job_owner_error( $job ) { $message = sprintf( __( 'The translation job (%s) doesn\'t belong to you.', 'wpml-translation-management' ), $job->job_id ); return new WP_Error( 'not_your_job', $message ); } /** * @param string $name * @param string $content * * @return false|SimpleXMLElement|WP_Error */ protected function check_xml_file( $name, $content ) { set_error_handler( array( $this, 'error_handler' ) ); try { $xml = simplexml_load_string( $content ); } catch ( Exception $e ) { $xml = false; } restore_error_handler(); if ( ! $xml || ! isset( $xml->file ) ) { $xml = $this->not_xml_file_error( $name ); } return $xml; } /** * @param $errno * @param $errstr * @param $errfile * @param $errline * * @throws ErrorException */ protected function error_handler( $errno, $errstr, $errfile, $errline ) { throw new ErrorException( $errstr, $errno, 1, $errfile, $errline ); } /** * @param string $name * * @return WP_Error */ protected function not_xml_file_error( $name ) { $message = sprintf( __( '"%s" is not a valid XLIFF file.', 'wpml-translation-management' ), $name ); return new WP_Error( 'not_xml_file', $message ); } /** * @param array $missing_data * * @return WP_Error */ protected function invalid_xliff_error( array $missing_data = array() ) { $message = __( 'The uploaded xliff file does not seem to be properly formed.', 'wpml-translation-management' ); if ( $missing_data ) { $message .= '<br>' . __( 'Missing or wrong data:', 'wpml-translation-management' ); if ( count( $missing_data ) > 1 ) { $message .= '<ol>'; $message .= '<li><strong>' . implode( '</strong></li><li><strong>', $missing_data ) . '</strong></li>'; $message .= '</ol>'; } else { $message .= ' <strong>' . $missing_data[0] . '</strong>'; } } return new WP_Error( 'xliff_invalid', $message ); } /** * @return WP_Error */ protected function does_not_belong_error() { return new WP_Error( 'xliff_does_not_match', __( "The uploaded xliff file doesn't belong to this system.", 'wpml-translation-management' ) ); } } xliff/class-wpml-tm-general-xliff-import.php 0000755 00000002572 14720342453 0015133 0 ustar 00 <?php class WPML_TM_General_Xliff_Import extends WPML_TM_Job_Factory_User { /** * @var WPML_TM_Xliff_Reader_Factory $xliff_reader_factory */ private $xliff_reader_factory; /** * WPML_TM_General_Xliff_Import constructor. * * @param WPML_Translation_Job_Factory $job_factory * @param WPML_TM_Xliff_Reader_Factory $xliff_reader_factory */ public function __construct( &$job_factory, &$xliff_reader_factory ) { parent::__construct( $job_factory ); $this->xliff_reader_factory = &$xliff_reader_factory; } /** * Imports the data in the xliff string into an array representation * that fits to the given target translation id. * * @param string $xliff_string * @param int $target_translation_id * * @return WP_Error|array */ public function import( $xliff_string, $target_translation_id ) { $xliff_reader = $this->xliff_reader_factory->general_xliff_reader(); $job_data = $xliff_reader->get_data( $xliff_string ); if ( is_wp_error( $job_data ) ) { $job = $this->job_factory->job_by_translation_id( $target_translation_id ); if ( $job && ( $id_string = $xliff_reader->get_xliff_job_identifier( $xliff_string ) ) !== false ) { $job_data = $xliff_reader->get_data( str_replace( $id_string, $job->get_id() . '-' . md5( $job->get_id() . $job->get_original_element_id() ), $xliff_string ) ); } } return $job_data; } } xliff/class-wpml-tm-validate-html.php 0000755 00000027571 14720342453 0013641 0 ustar 00 <?php /** * Class WPML_TM_Validate_HTML */ class WPML_TM_Validate_HTML { /** @var string Validated html */ private $html = ''; /** @var array Tags currently open */ private $tags = array(); /** @var int Number of errors */ private $error_count = 0; /** * Get validated html. * * @return string */ public function get_html() { return $this->html; } /** * Validate html. * * @param string $html HTML to process. * * @return int Number of errors. */ public function validate( $html ) { $html = $this->hide_wp_bugs( $html ); $html = $this->hide_cdata( $html ); $html = $this->hide_comments( $html ); $html = $this->hide_self_closing_tags( $html ); $html = $this->hide_scripts( $html ); $html = $this->hide_styles( $html ); $processed_html = ''; $html_arr = array( 'processed' => $processed_html, 'next' => $html, ); while ( '' !== $html_arr['next'] ) { $html_arr = $this->validate_next( $html_arr['next'] ); if ( $html_arr ) { $processed_html .= $html_arr['processed']; } } $html = $processed_html; $html = $this->restore_styles( $html ); $html = $this->restore_scripts( $html ); $html = $this->restore_self_closing_tags( $html ); $html = $this->restore_comments( $html ); $html = $this->restore_wp_bugs( $html ); $this->html = $html; return $this->error_count; } /** * Validate first tag in html flow and return processed html and rest. * In processed part broken html is replaced by wpml comment. * * @param string $html HTML to process. * * @return array|null */ private function validate_next( $html ) { $regs = array(); // Get first opening or closing tag. $pattern = '<\s*?([a-z]+|/[a-z]+)((?:.|\s)*?)>'; mb_eregi( $pattern, $html, $regs ); if ( $regs ) { $full_tag = $regs[0]; $pos = mb_strpos( $html, $full_tag ); $next_html = mb_substr( $html, $pos + mb_strlen( $full_tag ) ); $tag = $regs[1]; $result = true; if ( '/' === mb_substr( $tag, 0, 1 ) ) { $result = $this->close_tag( mb_substr( $tag, 1 ) ); } else { $this->open_tag( $tag ); } if ( $result ) { $processed_html = mb_substr( $html, 0, $pos + mb_strlen( $full_tag ) ); } else { $processed_html = mb_substr( $html, 0, (int) $pos ) . '<!-- wpml:html_fragment ' . $full_tag . ' -->'; } return array( 'processed' => $processed_html, 'next' => $next_html, ); } return array( 'processed' => $html, 'next' => '', ); } /** * Convert WP bugs into wpml commented bugs. * * @param string $html HTML to process. * * @return string */ private function hide_wp_bugs( $html ) { // WP bug fix for comments - in case you REALLY meant to type '< !--' $html = str_replace( '< !--', '< !--', $html ); // WP bug fix for LOVE <3 (and other situations with '<' before a number) $pattern = '<([0-9]{1})'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_wp_bug_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert WP bugs into wpml commented bugs. * * @param array $matches * * @return string */ public function hide_wp_bug_callback( $matches ) { return '<!-- wpml:wp_bug ' . $matches[0] . ' -->'; } /** * Convert wpml commented bugs to WP bugs. * * @param $html * * @return string */ private function restore_wp_bugs( $html ) { $pattern = '<!-- wpml:wp_bug (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_bug_callback' ), $html, 'msri' ); $html = $filtered === false ? $html : $filtered; $html = str_replace( '< !--', '< !--', $html ); return $html; } /** * Callback to convert wpml commented bugs to WP bugs. * * @param array $matches * * @return mixed */ public function restore_bug_callback( $matches ) { return $matches[1]; } /** * Convert HTML comments into wpml comments. * * @param string $html HTML to process. * * @return string */ private function hide_comments( $html ) { $pattern = '<!--(.*?)-->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_comment_callback' ), $html, 'msri' ); $html = $filtered === false ? $html : $filtered; $pattern = '<!((?!-- wpml:).*?)>'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_declaration_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert HTML comment to wpml comment. * * @param array $matches * * @return string */ public function hide_comment_callback( $matches ) { return '<!-- wpml:html_comment ' . base64_encode( $matches[0] ) . ' -->'; } /** * Callback to convert HTML declaration to wpml declaration. * * @param array $matches * * @return string */ public function hide_declaration_callback( $matches ) { return '<!-- wpml:html_declaration ' . base64_encode( $matches[0] ) . ' -->'; } /** * Convert wpml comments to HTML comments. * * @param string $html * * @return string */ private function restore_comments( $html ) { $pattern = '<!-- wpml:html_comment (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_encoded_content_callback' ), $html, 'msri' ); $html = $filtered === false ? $html : $filtered; $pattern = '<!-- wpml:html_declaration (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_encoded_content_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert wpml base64 encoded content. * * @param array $matches * * @return string */ public function restore_encoded_content_callback( $matches ) { return base64_decode( $matches[1] ); } /** * Convert self-closing tags to wpml self-closing tags. * * @param string $html HTML to process. * * @return string */ private function hide_self_closing_tags( $html ) { $self_closing_tags = array( 'area', 'base', 'basefont', 'br', 'col', 'command', 'embed', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'command', 'keygen', 'menuitem', 'path', 'polyline', ); foreach ( $self_closing_tags as $self_closing_tag ) { $pattern = '<\s*?' . $self_closing_tag . '((?:.|\s)*?)(>|/>)'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_sct_callback' ), $html, 'msri' ); $html = $filtered === false ? $html : $filtered; } $pattern = '<\s*?[^>]*/>'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_sct_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert self-closing tags to wpml self-closing tags. * * @param $matches * * @return string */ public function hide_sct_callback( $matches ) { return '<!-- wpml:html_self_closing_tag ' . str_replace( array( '<', '>' ), '', $matches[0] ) . ' -->'; } /** * Convert wpml self-closing tags to HTML self-closing tags. * * @param $html * * @return string */ private function restore_self_closing_tags( $html ) { $pattern = '<!-- wpml:html_self_closing_tag (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_sct_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert wpml self-closing tags to self-closing tags. * * @param $matches * * @return string */ public function restore_sct_callback( $matches ) { return '<' . $matches[1] . '>'; } /** * Convert wpml comments to initial HTML. * * @param $html * * @return false|string */ public function restore_html( $html ) { $html = $this->restore_cdata( $html ); $html = $this->restore_html_fragments( $html ); return $html; } /** * Convert wpml fragments to HTML fragments. * * @param $html * * @return false|string */ private function restore_html_fragments( $html ) { $pattern = '<!-- wpml:html_fragment (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_html_fragment_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert wpml fragment to HTML fragment. * * @param $matches * * @return string */ public function restore_html_fragment_callback( $matches ) { return $matches[1]; } /** * Convert scripts to wpml scripts. * * @param string $html HTML to process. * * @return string */ private function hide_scripts( $html ) { $pattern = '<\s*?script\s*?>((?:.|\s)*?)</script\s*?>'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_script_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert script to wpml script. * * @param array $matches * * @return string */ public function hide_script_callback( $matches ) { return '<!-- wpml:script ' . base64_encode( $matches[0] ) . ' -->'; } /** * Convert wpml scripts to scripts. * * @param $html * * @return string */ private function restore_scripts( $html ) { $pattern = '<!-- wpml:script (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_encoded_content_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Convert CDATA to wpml cdata. * * @param string $html HTML to process. * * @return string */ private function hide_cdata( $html ) { $pattern = '<!\[CDATA\[((?:.|\s)*?)\]\]>'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_cdata_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert CDATA to wpml cdata. * * @param array $matches * * @return string */ public function hide_cdata_callback( $matches ) { return '<!-- wpml:cdata ' . base64_encode( $matches[0] ) . ' -->'; } /** * Convert wpml cdata to CDATA. * * @param $html * * @return string */ private function restore_cdata( $html ) { $pattern = '<!-- wpml:cdata (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_encoded_content_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Convert styles to wpml scripts. * * @param string $html HTML to process. * * @return string */ private function hide_styles( $html ) { $pattern = '<\s*?style\s*?>((?:.|\s)*?)</style\s*?>'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'hide_style_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Callback to convert style to wpml style. * * @param array $matches * * @return string */ public function hide_style_callback( $matches ) { return '<!-- wpml:style ' . base64_encode( $matches[0] ) . ' -->'; } /** * Convert wpml styles to styles. * * @param $html * * @return string */ private function restore_styles( $html ) { $pattern = '<!-- wpml:style (.*?) -->'; $filtered = mb_ereg_replace_callback( $pattern, array( $this, 'restore_encoded_content_callback' ), $html, 'msri' ); return $filtered === false ? $html : $filtered; } /** * Open tag encountered in html. * * @param string $tag Tag name. */ private function open_tag( $tag ) { $tag = mb_strtolower( $tag ); array_push( $this->tags, $tag ); } /** * Close tag encountered in html. * * @param string $tag Tag name. * * @return bool Closed successfully. */ private function close_tag( $tag ) { $tag = mb_strtolower( $tag ); $last_tag = end( $this->tags ); if ( $last_tag === $tag ) { array_pop( $this->tags ); return true; } else { $this->error_count ++; if ( in_array( $tag, $this->tags, true ) ) { do { array_pop( $this->tags ); } while ( $this->tags && end( $this->tags ) !== $tag ); array_pop( $this->tags ); } return false; } } } xliff/class-wpml-tm-xliff-post-type.php 0000755 00000000665 14720342453 0014153 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_XLIFF_Post_Type extends WPML_TM_XLIFF_Phase { private $post_type; public function __construct( $post_type = '' ) { $this->post_type = $post_type; } /** * @return string */ protected function get_data() { return $this->post_type; } protected function get_phase_name() { return 'post_type'; } protected function get_process_name() { return 'Post type'; } } xliff/class-wpml-tm-xliff-reader.php 0000755 00000001301 14720342453 0013435 0 ustar 00 <?php abstract class WPML_TM_Xliff_Reader extends WPML_TM_Xliff_Shared { /** * @param string $content Xliff file string content * * @return array */ abstract public function get_data( $content ); /** * Parse a XML containing the XLIFF * * @param string $content * * @return SimpleXMLElement|WP_Error The parsed XLIFF or a WP error in case it could not be parsed */ public function load_xliff( $content ) { try { $xml = simplexml_load_string( $content ); } catch ( Exception $e ) { $xml = false; } return $xml ? $xml : new WP_Error( 'not_xml_file', sprintf( __( 'The xliff file could not be read.', 'wpml-translation-management' ) ) ); } } xliff/class-wpml-tm-xliff-reader-factory.php 0000755 00000001011 14720342453 0015100 0 ustar 00 <?php class WPML_TM_Xliff_Reader_Factory extends WPML_TM_Job_Factory_User { /** * @return WPML_TM_General_Xliff_Reader */ public function general_xliff_reader() { return new WPML_TM_General_Xliff_Reader( $this->job_factory ); } public function general_xliff_import() { return new WPML_TM_General_Xliff_Import( $this->job_factory, $this ); } /** * @return WPML_TM_String_Xliff_Reader */ public function string_xliff_reader() { return new WPML_TM_String_Xliff_Reader( $this->job_factory ); } } xliff/class-wpml-tm-xliff-frontend.php 0000755 00000054500 14720342453 0014023 0 ustar 00 <?php /** * WPML_TM_Xliff_Frontend class file * * @package wpml-translation-management */ use WPML\FP\Obj; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once WPML_TM_PATH . '/inc/wpml_zip.php'; /** * Class WPML_TM_Xliff_Frontend */ class WPML_TM_Xliff_Frontend extends WPML_TM_Xliff_Shared { /** * Success admin notices * * @var array */ private $success; /** * Attachments * * @var array */ private $attachments = array(); /** * SitePress instance * * @var SitePress */ private $sitepress; /** * Name of archive * * @var string */ private $export_archive_name; /** * Priority of late initialisation * * @var int */ private $late_init_priority = 9999; /** * Is simple xml turned on * * @var bool */ private $simplexml_on; /** * WPML_TM_Xliff_Frontend constructor * * @param WPML_Translation_Job_Factory $job_factory Job factory. * @param SitePress $sitepress SitePress instance. * @param boolean $simplexml_on Is simple xml turned on. */ public function __construct( WPML_Translation_Job_Factory $job_factory, SitePress $sitepress, $simplexml_on ) { parent::__construct( $job_factory ); $this->sitepress = $sitepress; $this->simplexml_on = $simplexml_on; } /** * Get available xliff versions * * @return array */ public function get_available_xliff_versions() { return array( '10' => '1.0', '11' => '1.1', '12' => '1.2', ); } /** * Get init priority * * @return int */ public function get_init_priority() { return isset( $_POST['xliff_upload'] ) || ( isset( $_GET['wpml_xliff_action'] ) && $_GET['wpml_xliff_action'] === 'download' ) || isset( $_POST['wpml_xliff_export_all_filtered'] ) ? $this->get_late_init_priority() : 10; } /** * Get late init priority * * @return int */ public function get_late_init_priority() { return $this->late_init_priority; } /** * Init class * * @return bool * @throws Exception Throws an exception in case of errors. */ public function init() { $this->attachments = array(); $this->error = null; if ( $this->sitepress->get_wp_api()->is_admin() ) { add_action( 'admin_head', array( $this, 'js_scripts' ) ); add_action( 'wp_ajax_set_xliff_options', array( $this, 'ajax_set_xliff_options', ), 10, 0 ); if ( ! $this->sitepress->get_setting( 'xliff_newlines' ) ) { $this->sitepress->set_setting( 'xliff_newlines', WPML_XLIFF_TM_NEWLINES_ORIGINAL, true ); } if ( ! $this->sitepress->get_setting( 'tm_xliff_version' ) ) { $this->sitepress->set_setting( 'tm_xliff_version', '12', true ); } if ( 1 < count( $this->sitepress->get_languages( false, true ) ) ) { add_filter( 'wpml_translation_queue_actions', array( $this, 'translation_queue_add_actions', ) ); add_action( 'wpml_xliff_select_actions', array( $this, 'translation_queue_xliff_select_actions', ), 10, 4 ); add_action( 'wpml_translation_queue_after_display', array( $this, 'translation_queue_after_display', ), 10, 1 ); add_action( 'wpml_translator_notification', array( $this, 'translator_notification', ), 10, 0 ); add_filter( 'wpml_new_job_notification', array( $this, 'new_job_notification', ), 10, 2 ); add_filter( 'wpml_new_job_notification_attachments', array( $this, 'new_job_notification_attachments', ) ); } if ( isset( $_GET['wpml_xliff_action'] ) && $_GET['wpml_xliff_action'] === 'download' && wp_verify_nonce( $_GET['nonce'], 'xliff-export' ) ) { $archive = $this->get_xliff_archive( Obj::propOr( '', 'xliff_version', $_GET ), explode( ',', Obj::propOr( '', 'jobs', $_GET ) ) ); $this->stream_xliff_archive( $archive ); } add_action( 'wp_ajax_wpml_xliff_upload', function () { if ( $this->import_xliff( Obj::propOr( [], 'import', $_FILES ) ) ) { wp_send_json_success( $this->success ); } else { wp_send_json_error( $this->error ); } } ); } return true; } /** * Set xliff options */ public function ajax_set_xliff_options() { check_ajax_referer( 'icl_xliff_options_form_nonce', 'security' ); $newlines = isset( $_POST['icl_xliff_newlines'] ) ? (int) $_POST['icl_xliff_newlines'] : 0; $this->sitepress->set_setting( 'xliff_newlines', $newlines, true ); $version = isset( $_POST['icl_xliff_version'] ) ? (int) $_POST['icl_xliff_version'] : 0; $this->sitepress->set_setting( 'tm_xliff_version', $version, true ); wp_send_json_success( array( 'message' => 'OK', 'newlines_saved' => $newlines, 'version_saved' => $version, ) ); } /** * New job notification * * @param array $mail Email content. * @param int $job_id Job id. * * @return array */ public function new_job_notification( $mail, $job_id ) { $tm_settings = $this->sitepress->get_setting( 'translation-management', array() ); if ( isset( $tm_settings['notification']['include_xliff'] ) && $tm_settings['notification']['include_xliff'] ) { $xliff_version = $this->get_user_xliff_version(); $xliff_version = $xliff_version ?: WPML_XLIFF_DEFAULT_VERSION; $xliff_file = $this->get_xliff_file( $job_id, $xliff_version ); $temp_dir = get_temp_dir(); $file_name = $temp_dir . get_bloginfo( 'name' ) . '-translation-job-' . $job_id . '.xliff'; $fh = fopen( $file_name, 'w' ); if ( $fh ) { fwrite( $fh, $xliff_file ); fclose( $fh ); $mail['attachment'] = $file_name; $this->attachments[ $job_id ] = $file_name; $mail['body'] .= __( ' - A xliff file is attached.', 'wpml-translation-management' ); } } return $mail; } /** * Get zip name from jobs * * @param array $job_ids Job ids. * * @return string */ private function get_zip_name_from_jobs( $job_ids ) { $min_job = min( $job_ids ); $max_job = max( $job_ids ); if ( $max_job === $min_job ) { return get_bloginfo( 'name' ) . '-translation-job-' . $max_job . '.zip'; } else { return get_bloginfo( 'name' ) . '-translation-job-' . $min_job . '-' . $max_job . '.zip'; } } /** * New job notification attachments * * @param array $attachments Job notification attachments. * * @return array */ public function new_job_notification_attachments( $attachments ) { $found = false; $archive = new wpml_zip(); foreach ( $attachments as $index => $attachment ) { if ( in_array( $attachment, $this->attachments, true ) ) { $fh = fopen( $attachment, 'r' ); if ( $fh ) { $xliff_file = fread( $fh, (int) filesize( $attachment ) ); fclose( $fh ); $archive->addFile( $xliff_file, basename( $attachment ) ); unset( $attachments[ $index ] ); $found = true; } } } if ( $found ) { // Add the zip file to the attachments. $archive_data = $archive->getZipData(); $temp_dir = get_temp_dir(); $file_name = $temp_dir . $this->get_zip_name_from_jobs( array_keys( $this->attachments ) ); $fh = fopen( $file_name, 'w' ); if ( $fh ) { fwrite( $fh, $archive_data ); fclose( $fh ); $attachments[] = $file_name; } } return $attachments; } /** * Get xliff file * * @param int $job_id Job id. * @param string $xliff_version Xliff version. * * @return string */ private function get_xliff_file( $job_id, $xliff_version = WPML_XLIFF_DEFAULT_VERSION ) { return wpml_tm_xliff_factory() ->create_writer( $xliff_version ) ->generate_job_xliff( $job_id ); } /** * Get xliff archive * * @param string $xliff_version Xliff version. * @param array|null $job_ids Job ids. * * @return wpml_zip * * @throws Exception Throws an exception in case of errors. */ public function get_xliff_archive( $xliff_version, $job_ids = array() ) { global $wpdb, $current_user; if ( empty( $job_ids ) && isset( $_GET['xliff_export_data'] ) ) { // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $data = json_decode( base64_decode( $_GET['xliff_export_data'] ) ); // phpcs:enable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $job_ids = isset( $data->job ) ? array_keys( (array) $data->job ) : array(); } $archive = new wpml_zip(); foreach ( $job_ids as $job_id ) { $xliff_file = $this->get_xliff_file( $job_id, $xliff_version ); // Assign the job to this translator. $rid = $wpdb->get_var( $wpdb->prepare( "SELECT rid FROM {$wpdb->prefix}icl_translate_job WHERE job_id=%d ", $job_id ) ); $data_value = array( 'translator_id' => $current_user->ID ); $data_where = array( 'job_id' => $job_id ); $wpdb->update( $wpdb->prefix . 'icl_translate_job', $data_value, $data_where ); $data_where = array( 'rid' => $rid ); $wpdb->update( $wpdb->prefix . 'icl_translation_status', $data_value, $data_where ); $archive->addFile( $xliff_file, get_bloginfo( 'name' ) . '-translation-job-' . $job_id . '.xliff' ); } $this->export_archive_name = $this->get_zip_name_from_jobs( $job_ids ); $archive->finalize(); return $archive; } /** * Stream xliff archive * * @param wpml_zip $archive Zip archive. * * @throws Exception Throws an exception in case of errors. */ private function stream_xliff_archive( $archive ) { if ( is_a( $archive, 'wpml_zip' ) ) { if ( defined( 'WPML_SAVE_XLIFF_PATH' ) && trim( WPML_SAVE_XLIFF_PATH ) ) { $this->save_zip_file( WPML_SAVE_XLIFF_PATH, $archive ); } $archive->sendZip( $this->export_archive_name ); } exit; } /** * Save zip file * * @param string $path Where to save the archive. * @param wpml_zip $archive Zip archive. */ private function save_zip_file( $path, $archive ) { $path = trailingslashit( $path ); if ( ! is_dir( $path ) ) { $result = mkdir( $path ); if ( ! $result ) { return; } } $archive->setZipFile( $path . $this->export_archive_name ); } /** * Stops any redirects from happening when we call the * translation manager to save the translations. * * @return null */ public function stop_redirect() { return null; } /** * Import xliff file * * @param array $file Xliff file data. * * @return bool|WP_Error */ private function import_xliff( $file ) { global $current_user; // We don't want any redirects happening when we save the translation. add_filter( 'wp_redirect', array( $this, 'stop_redirect' ) ); $this->success = array(); $contents = array(); if ( 0 === (int) $file['size'] ) { $this->error = new WP_Error( 'empty_file', __( 'You are trying to import an empty file.', 'wpml-translation-management' ) ); return false; } elseif ( isset( $file['tmp_name'] ) && $file['tmp_name'] ) { $fh = fopen( $file['tmp_name'], 'r' ); $data = false; if ( $fh ) { $data = fread( $fh, 4 ); fclose( $fh ); } if ( $data && $data[0] == 'P' && $data[1] == 'K' && $data[2] == chr( 03 ) && $data[3] == chr( 04 ) ) { if ( class_exists( 'ZipArchive' ) ) { $z = new ZipArchive(); $zopen = $z->open( $file['tmp_name'], 4 ); if ( true !== $zopen ) { $this->error = new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.', 'wpml-translation-management' ) ); return false; } $empty_files = array(); for ( $i = 0; $i < $z->numFiles; $i ++ ) { if ( ! $info = $z->statIndex( $i ) ) { $this->error = new WP_Error( 'stat_failed', __( 'Could not retrieve file from archive.', 'wpml-translation-management' ) ); return false; } if ( $this->is_directory( $info['name'] ) ) { continue; } $content = $z->getFromIndex( $i ); if ( false === (bool) $content ) { $empty_files[] = $info['name']; } $contents[ $info['name'] ] = $content; } if ( $empty_files ) { $this->error = new WP_Error( 'extract_failed', __( 'The archive contains one or more empty files.', 'wpml-translation-management' ), $empty_files ); return false; } } else { require_once ABSPATH . 'wp-admin/includes/class-pclzip.php'; $archive = new PclZip( $file['tmp_name'] ); // Is the archive valid? $archive_files = $archive->extract( PCLZIP_OPT_EXTRACT_AS_STRING ); if ( false == $archive_files ) { $this->error = new WP_Error( 'incompatible_archive', __( 'You are trying to import an incompatible Archive.', 'wpml-translation-management' ), $archive->errorInfo( true ) ); return false; } if ( 0 === count( $archive_files ) ) { $this->error = new WP_Error( 'empty_archive', __( 'You are trying to import an empty archive.', 'wpml-translation-management' ) ); return false; } $empty_files = array(); foreach ( $archive_files as $content ) { if ( $this->is_directory( $content['filename'] ) ) { continue; } if ( false === (bool) $content['content'] ) { $empty_files[] = $content['filename']; } $contents[ $content['filename'] ] = $content['content']; } if ( $empty_files ) { $this->error = new WP_Error( 'extract_failed', __( 'The archive contains one or more empty files.', 'wpml-translation-management' ), $empty_files ); return false; } } } else { $fh = fopen( $file['tmp_name'], 'r' ); if ( $fh ) { $data = fread( $fh, $file['size'] ); fclose( $fh ); $contents[ $file['name'] ] = $data; } } foreach ( $contents as $name => $content ) { if ( $this->validate_file_name( (string) $name ) ) { list( $job, $job_data ) = $this->validate_file( $name, $content, $current_user ); if ( null !== $this->error ) { return $job_data; } kses_remove_filters(); wpml_tm_save_data( $job_data ); kses_init(); // translators: %s: job id. $this->success[] = $job->job_id; } } return true; } return false; } /** * Translation queue actions * * @param array $actions Actions. * @param string $action_name Action name. * @param array $translation_jobs Translation jobs. * @param string $action */ public function translation_queue_xliff_select_actions( $actions, $action_name, $translation_jobs, $action ) { if ( $this->has_translation_jobs( $translation_jobs ) && sizeof( $actions ) > 0 ) : $currentAction = $action ?: false; ?> <div class="alignleft actions"> <select name="<?php echo esc_html( $action_name ); ?>"> <option value="-1" <?php echo $currentAction == false ? "selected='selected'" : ''; ?>><?php _e( 'Bulk Actions' ); ?></option> <?php foreach ( $actions as $key => $action ) : ?> <option <?php disabled( ! $this->simplexml_on ); ?> value="<?php echo $key; ?>" <?php echo $currentAction == $key && $this->simplexml_on ? "selected='selected'" : ''; ?>><?php echo $action; ?></option> <?php endforeach; ?> </select> <input id="js-wpml-do-action" type="submit" value="<?php esc_attr_e( 'Apply' ); ?>" name="do<?php echo esc_html( $action_name ); ?>" class="button-secondary action"/> </div> <?php endif; } /** * Has translation jobs * * @param array $translation_jobs Translation jobs. * * @return bool */ private function has_translation_jobs( $translation_jobs ) { return $translation_jobs && array_key_exists( 'jobs', $translation_jobs ) && $translation_jobs['jobs']; } /** * Get xliff version select options * * @return string */ private function get_xliff_version_select_options() { $output = ''; $user_version = (int) $this->get_user_xliff_version(); foreach ( $this->get_available_xliff_versions() as $value => $label ) { $user_version = false === $user_version ? $value : $user_version; $output .= '<option value="' . $value . '"'; $output .= $user_version === $value ? 'selected="selected"' : ''; $output .= '>XLIFF ' . $label . '</option>'; } return $output; } /** * Adds the various possible XLIFF versions to translations queue page's export actions on display * * @param array $actions Actions. * * @return array */ public function translation_queue_add_actions( $actions ) { foreach ( $this->get_available_xliff_versions() as $key => $value ) { // translators: %s: XLIFF version. $actions[ $key ] = sprintf( __( 'Export XLIFF %s', 'wpml-translation-management' ), $value ); } return $actions; } /** * Show error messages in admin notices */ public function admin_notices_error() { if ( is_wp_error( $this->error ) ) { ?> <div class="message error"> <p><?php echo $this->error->get_error_message(); ?></p> <?php if ( $this->error->get_error_data() ) { ?> <ol> <li><?php echo implode( '</li><li>', $this->error->get_error_data() ); ?></li> </ol> <?php } ?> </div> <?php } } /** * Show success messages in admin notices */ public function admin_notices_success() { ?> <div class="message updated"><p> <ul> <?php foreach ( $this->success as $message ) { echo '<li>' . $message . '</li>'; } ?> </ul> </p></div> <?php } /** * Check translation queue after display * * @param array $translation_jobs Translation jobs. */ public function translation_queue_after_display( $translation_jobs = array() ) { if ( ! $this->has_translation_jobs( $translation_jobs ) ) { return; } $export_label = esc_html__( 'Export all jobs:', 'wpml-translation-management' ); $cookie_filters = WPML_Translations_Queue::get_cookie_filters(); if ( $cookie_filters ) { $type = __( 'All types', 'wpml-translation-management' ); if ( ! empty( $cookie_filters['type'] ) ) { $post_slug = preg_replace( '/^post_|^package_/', '', $cookie_filters['type'], 1 ); $post_types = $this->sitepress->get_translatable_documents( true ); $post_types = apply_filters( 'wpml_get_translatable_types', $post_types ); if ( array_key_exists( $post_slug, $post_types ) ) { $type = $post_types[ $post_slug ]->label; } } $from = ! empty( $cookie_filters['from'] ) ? $this->sitepress->get_display_language_name( $cookie_filters['from'] ) : __( 'Any language', 'wpml-translation-management' ); $to = ! empty( $cookie_filters['to'] ) ? $this->sitepress->get_display_language_name( $cookie_filters['to'] ) : __( 'Any language', 'wpml-translation-management' ); $status = ! empty( $cookie_filters['status'] ) && (int) $cookie_filters['status'] !== ICL_TM_WAITING_FOR_TRANSLATOR ? TranslationManagement::status2text( $cookie_filters['status'] ) : ( ! empty( $cookie_filters['status'] ) ? __( 'Available to translate', 'wpml-translation-management' ) : 'All statuses' ); $export_label = sprintf( // translators: %1: post type, %2: from language, %3: to language, %4: status. esc_html__( 'Export all filtered jobs of %1$s from %2$s to %3$s in %4$s:', 'wpml-translation-management' ), '<b>' . $type . '</b>', '<b>' . $from . '</b>', '<b>' . $to . '</b>', '<b>' . $status . '</b>' ); } ?> <br/> <table class="widefat"> <thead> <tr> <th><?php esc_html_e( 'Import / Export XLIFF', 'wpml-translation-management' ); ?></th> </tr> </thead> <tbody> <tr> <td> <?php if ( ! $this->simplexml_on ) : ?> <div class="otgs-notice error"> <p> <strong><?php esc_html_e( 'SimpleXML missing!', 'wpml-translation-management' ); ?></strong> </p> <p> <?php esc_html_e( 'SimpleXML extension is required for using XLIFF files in WPML Translation Management.', 'wpml-translation-management' ); ?> <a href="https://wpml.org/?page_id=716"><?php esc_html_e( 'WPML Minimum Requirements', 'wpml-translation-management' ); ?></a> </p> </div> <?php endif; ?> <form method="post" id="translation-xliff-export-all-filtered" action=""> <label for="wpml_xliff_export_all_filtered"><?php echo $export_label; ?></label> <select name="xliff_version" class="select" <?php disabled( ! $this->simplexml_on ); ?>> <?php echo $this->get_xliff_version_select_options(); ?> </select> <input type="submit" value="<?php esc_attr_e( 'Export', 'wpml-translation-management' ); ?>" <?php disabled( ! $this->simplexml_on ); ?> name="wpml_xliff_export_all_filtered" id="xliff_download" class="button-secondary action"/> <input type="hidden" value="<?php echo wp_create_nonce( 'xliff-export-all-filtered' ); ?>" name="nonce"> </form> <hr> <form enctype="multipart/form-data" method="post" id="translation-xliff-upload" action=""> <label for="upload-xliff-file"><?php _e( 'Select the xliff file or zip file to upload from your computer: ', 'wpml-translation-management' ); ?></label> <input type="file" id="upload-xliff-file" name="import" <?php disabled( ! $this->simplexml_on ); ?> /> <input type="submit" value="<?php _e( 'Upload', 'wpml-translation-management' ); ?>" name="xliff_upload" id="xliff_upload" class="button-secondary action" <?php disabled( ! $this->simplexml_on ); ?> /> </form> </td> </tr> </tbody> </table> <?php } /** * Print online js script */ public function js_scripts() { ?> <script type="text/javascript"> var wpml_xliff_ajax_nonce = '<?php echo wp_create_nonce( 'icl_xliff_options_form_nonce' ); ?>'; </script> <?php } /** * Provide translator notification */ public function translator_notification() { $checked = $this->sitepress->get_setting( 'include_xliff_in_notification' ) ? 'checked="checked"' : ''; ?> <input type="checkbox" name="include_xliff" id="icl_include_xliff" value="1" <?php echo $checked; ?>/> <label for="icl_include_xliff"><?php _e( 'Include XLIFF files in notification emails', 'wpml-translation-management' ); ?></label> <?php } /** * Get user xliff version * * @return string|false */ private function get_user_xliff_version() { return $this->sitepress->get_setting( 'tm_xliff_version', false ); } /** * Check if argument is a directory * * @param string $path * @return bool */ private function is_directory( $path ) { return '/' === substr( $path, -1 ); } } xliff/class-wpml-tm-xliff-writer.php 0000755 00000033636 14720342453 0013527 0 ustar 00 <?php /** * @package wpml-core */ use WPML\FP\Obj; use WPML\TM\Jobs\FieldId; class WPML_TM_Xliff_Writer { const TAB = "\t"; protected $job_factory; private $xliff_version; private $xliff_shortcodes; private $translator_notes; /** * WPML_TM_xliff constructor. * * @param WPML_Translation_Job_Factory $job_factory * @param string $xliff_version * @param \WPML_TM_XLIFF_Shortcodes|null $xliff_shortcodes */ public function __construct( WPML_Translation_Job_Factory $job_factory, $xliff_version = TRANSLATION_PROXY_XLIFF_VERSION, WPML_TM_XLIFF_Shortcodes $xliff_shortcodes = null ) { $this->job_factory = $job_factory; $this->xliff_version = $xliff_version; if ( ! $xliff_shortcodes ) { $xliff_shortcodes = wpml_tm_xliff_shortcodes(); } $this->xliff_shortcodes = $xliff_shortcodes; $this->translator_notes = new WPML_TM_XLIFF_Translator_Notes(); } /** * Generate a XLIFF file for a given job. * * @param int $job_id * * @return resource XLIFF representation of the job */ public function get_job_xliff_file( $job_id ) { return $this->generate_xliff_file( $this->generate_job_xliff( $job_id ) ); } /** * Generate a XLIFF string for a given post or external type (e.g. package) job. * * @param int $job_id * @param bool $apply_memory * * @return string XLIFF representation of the job */ public function generate_job_xliff( $job_id, $apply_memory = true ) { /** @var TranslationManagement $iclTranslationManagement */ global $iclTranslationManagement; // don't include not-translatable and don't auto-assign $job = $iclTranslationManagement->get_translation_job( (int) $job_id, false, false, 1 ); $translation_units = $this->get_job_translation_units_data( $job, $apply_memory ); $original = $job_id . '-' . md5( $job_id . $job->original_doc_id ); $original_post_type = isset( $job->original_post_type ) ? $job->original_post_type : null; $external_file_url = $this->get_external_url( $job ); $this->get_translator_notes( $job ); $xliff = $this->generate_xliff( $original, $job->source_language_code, $job->language_code, $translation_units, $external_file_url, $original_post_type ); return $xliff; } /** * Generate a XLIFF file for a given set of strings. * * @param array $strings * @param string $source_language * @param string $target_language * * @return resource XLIFF file */ public function get_strings_xliff_file( $strings, $source_language, $target_language ) { $strings = $this->pre_populate_strings_with_translation_memory( $strings, $source_language, $target_language ); return $this->generate_xliff_file( $this->generate_xliff( uniqid( 'string-', true ), $source_language, $target_language, $this->generate_strings_translation_units_data( $strings ) ) ); } private function generate_xliff( $original_id, $source_language, $target_language, array $translation_units = array(), $external_file_url = null, $original_post_type = null ) { $xliff = new WPML_TM_XLIFF( $this->get_xliff_version(), '1.0', 'utf-8' ); $phase_group = array(); $phase_group = array_merge( $phase_group, $this->xliff_shortcodes->get() ); $phase_group = array_merge( $phase_group, $this->translator_notes->get() ); $post_type_phase = new WPML_TM_XLIFF_Post_Type( $original_post_type ); $phase_group = array_merge( $phase_group, $post_type_phase->get() ); $string = $xliff ->setFileAttributes( array( 'original' => $original_id, 'source-language' => $source_language, 'target-language' => $target_language, 'datatype' => 'plaintext', ) ) ->setReferences( array( 'external-file' => $external_file_url, ) ) ->setPhaseGroup( $phase_group ) ->setTranslationUnits( $translation_units ) ->toString(); return $string; } private function get_xliff_version() { switch ( $this->xliff_version ) { case '10': return '1.0'; case '11': return '1.1'; case '12': default: return '1.2'; } } /** * Generate translation units for a given set of strings. * * The units are the actual content to be translated * Represented as a source and a target * * @param array $strings * * @return array The translation units representation */ private function generate_strings_translation_units_data( $strings ) { $translation_units = array(); foreach ( $strings as $string ) { $id = 'string-' . $string->id; $translation_units[] = $this->get_translation_unit_data( $id, 'string', $string->value, $string->translation, $string->translated_from_memory ); } return $translation_units; } /** * @param stdClass[] $strings * @param string $source_lang * @param string $target_lang * * @return stdClass[] */ private function pre_populate_strings_with_translation_memory( $strings, $source_lang, $target_lang ) { $strings_to_translate = wp_list_pluck( $strings, 'value' ); $original_translated_map = $this->get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang ); foreach ( $strings as &$string ) { $string->translated_from_memory = false; $string->translation = $string->value; if ( array_key_exists( $string->value, $original_translated_map ) ) { $string->translation = $original_translated_map[ $string->value ]; $string->translated_from_memory = true; } } return $strings; } /** * @param array $strings_to_translate * @param string $source_lang * @param string $target_lang * * @return array */ private function get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang ) { $args = array( 'strings' => $strings_to_translate, 'source_lang' => $source_lang, 'target_lang' => $target_lang, ); $strings_in_memory = apply_filters( 'wpml_st_get_translation_memory', array(), $args ); if ( $strings_in_memory ) { return wp_list_pluck( $strings_in_memory, 'translation', 'original' ); } return array(); } /** * Generate translation units. * * The units are the actual content to be translated * Represented as a source and a target * * @param stdClass $job * @param bool $apply_memory * * @return array The translation units data */ private function get_job_translation_units_data( $job, $apply_memory ) { $translation_units = array(); /** @var array $elements */ $elements = $job->elements; if ( $elements ) { $elements = $this->pre_populate_elements_with_translation_memory( $elements, $job->source_language_code, $job->language_code ); foreach ( $elements as $element ) { if ( 1 === (int) $element->field_translate ) { $field_data_translated = base64_decode( $element->field_data_translated ); $field_data = base64_decode( $element->field_data ); /** * It modifies the content of a single field data which represents, for example, one paragraph in post content. * * @since 2.10.0 * @param string $field_data */ $field_data = apply_filters( 'wpml_tm_xliff_unit_field_data', $field_data ); if ( 0 === strpos( $element->field_type, 'field-' ) ) { $field_data_translated = apply_filters( 'wpml_tm_xliff_export_translated_cf', $field_data_translated, $element ); $field_data = apply_filters( 'wpml_tm_xliff_export_original_cf', $field_data, $element ); } // check for untranslated fields and copy the original if required. if ( ! null === $field_data_translated || '' === $field_data_translated ) { $field_data_translated = $this->remove_invalid_chars( $field_data ); } if ( $this->is_valid_unit_content( $field_data ) ) { $translation_units[] = $this->get_translation_unit_data( $element->field_type, $element->field_type, $field_data, $apply_memory ? $field_data_translated : null, $apply_memory && $element->translated_from_memory, $element->field_wrap_tag, $this->get_field_title( $element, $job ) ); } } } } return $translation_units; } /** * @param \stdClass $field * @param \stdClass $job * * @return string */ private function get_field_title( $field, $job ) { $result = apply_filters( 'wpml_tm_adjust_translation_fields', [ (array) $field ], $job, null ); return Obj::pathOr( '', [ 0, 'title' ], $result ); } /** * @param array $elements * @param string $source_lang * @param string $target_lang * * @return array */ private function pre_populate_elements_with_translation_memory( array $elements, $source_lang, $target_lang ) { $strings_to_translate = array(); foreach ( $elements as &$element ) { if ( preg_match( '/^package-string/', $element->field_type ) ) { $strings_to_translate[ $element->tid ] = base64_decode( $element->field_data ); } $element->translated_from_memory = FieldId::is_any_term_field( $element->field_type ) && $element->field_data_translated && $element->field_data != $element->field_data_translated; } $original_translated_map = $this->get_original_translated_map_from_translation_memory( $strings_to_translate, $source_lang, $target_lang ); if ( $original_translated_map ) { foreach ( $elements as &$element ) { if ( array_key_exists( $element->tid, $strings_to_translate ) && array_key_exists( $strings_to_translate[ $element->tid ], $original_translated_map ) ) { $element->field_data_translated = base64_encode( $original_translated_map[ $strings_to_translate[ $element->tid ] ] ); $element->translated_from_memory = true; } } } return $elements; } /** * Get translation unit data. * * @param string $field_id Field ID. * @param string $field_name Field name. * @param string $field_data Field content. * @param string $field_data_translated Field translated content. * @param boolean $is_translated_from_memory Boolean flag - is translated from memory. * @param string $field_wrap_tag Field wrap tag (h1...h6, etc.) * @param string $title * * @return array */ private function get_translation_unit_data( $field_id, $field_name, $field_data, $field_data_translated, $is_translated_from_memory = false, $field_wrap_tag = '', $title = '' ) { global $sitepress; if ( $field_data === null ) { $field_data = ''; } if ( $field_data_translated === null ) { $field_data_translated = ''; } $field_data = $this->remove_invalid_chars( $field_data ); $translation_unit = array(); $field_data = $this->remove_line_breaks_inside_tags( $field_data ); $field_data_translated = $this->remove_line_breaks_inside_tags( $field_data_translated ); if ( $sitepress->get_setting( 'xliff_newlines' ) === WPML_XLIFF_TM_NEWLINES_REPLACE ) { $field_data = $this->replace_new_line_with_tag( $field_data ); $field_data_translated = $this->replace_new_line_with_tag( $field_data_translated ); } if ( $title ) { $translation_unit['attributes']['extradata'] = $title; } $translation_unit['attributes']['resname'] = $field_name; $translation_unit['attributes']['restype'] = 'string'; $translation_unit['attributes']['datatype'] = 'html'; $translation_unit['attributes']['id'] = $field_id; $translation_unit['source'] = array( 'content' => $field_data ); $translation_unit['target'] = array( 'content' => $field_data_translated ); $translation_unit['note'] = array( 'content' => $field_wrap_tag ); if ( $is_translated_from_memory ) { $translation_unit['target']['attributes'] = array( 'state' => 'needs-review-translation', 'state-qualifier' => 'tm-suggestion', ); } return $translation_unit; } /** * @param string $string * * @return string */ protected function replace_new_line_with_tag( $string ) { return str_replace( array( "\n", "\r" ), array( '<br class="xliff-newline" />', '' ), $string ); } private function remove_line_breaks_inside_tags( $string ) { return preg_replace_callback( '/(<[^>]*>)/m', array( $this, 'remove_line_breaks_inside_tag_callback' ), $string ); } /** * @param array $matches * * @return string */ private function remove_line_breaks_inside_tag_callback( array $matches ) { $tag_string = preg_replace( '/([\n\r\t ]+)/', ' ', $matches[0] ); $tag_string = preg_replace( '/(<[\s]+)/', '<', $tag_string ); return preg_replace( '/([\s]+>)/', '>', $tag_string ); } /** * @param string $string * * Remove all characters below 0x20 except for 0x09, 0x0A and 0x0D * @see https://www.w3.org/TR/xml/#charsets * * @return string */ private function remove_invalid_chars( $string ) { return preg_replace( '/[\x00-\x08\x0B-\x0C\x0E-\x1F]/', '', $string ); } /** * Save a xliff string to a temporary file and return the file ressource * handle * * @param string $xliff_content * * @return resource XLIFF */ private function generate_xliff_file( $xliff_content ) { $file = fopen( 'php://temp', 'rb+' ); if ( $file ) { fwrite( $file, $xliff_content ); rewind( $file ); } return $file; } /** * @param $job * * @return false|null|string */ private function get_external_url( $job ) { $external_file_url = null; if ( isset( $job->original_doc_id ) && 'post' === $job->element_type_prefix ) { $external_file_url = get_permalink( $job->original_doc_id ); return $external_file_url; } return $external_file_url; } /** * @param $content * * @return bool */ private function is_valid_unit_content( $content ) { $content = preg_replace( '/[^#\w]*/u', '', $content ); return $content || '0' === $content; } private function get_translator_notes( $job ) { $this->translator_notes = new WPML_TM_XLIFF_Translator_Notes( isset( $job->original_doc_id ) ? $job->original_doc_id : 0 ); } } xliff/class-wpml-tm-xliff-translator-notes.php 0000755 00000001001 14720342453 0015507 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_XLIFF_Translator_Notes extends WPML_TM_XLIFF_Phase { private $post_id; public function __construct( $post_id = 0 ) { $this->post_id = $post_id; } /** * @return string */ protected function get_data() { if ( $this->post_id ) { return WPML_TM_Translator_Note::get( $this->post_id ); } else { return ''; } } protected function get_phase_name() { return 'notes'; } protected function get_process_name() { return 'Notes'; } } xliff/class-wpml-tm-xliff-shortcodes.php 0000755 00000002016 14720342453 0014354 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_XLIFF_Shortcodes extends WPML_TM_XLIFF_Phase { const SHORTCODE_STORE_OPTION_KEY = 'wpml_xliff_shortcodes'; /** * @return string */ protected function get_data() { return implode( ',', $this->get_shortcodes() ); } /** * @return array */ private function get_shortcodes() { global $shortcode_tags; $registered_shortcodes = array(); if ( $shortcode_tags ) { $registered_shortcodes = array_keys( $shortcode_tags ); } $stored_shortcodes = get_option( self::SHORTCODE_STORE_OPTION_KEY, array() ); return $this->get_sanitized_shortcodes( $registered_shortcodes, $stored_shortcodes ); } private function get_sanitized_shortcodes( array $shortcodes1, array $shortcodes2 ) { return array_unique( array_filter( apply_filters( 'wpml_shortcode_list', array_merge( $shortcodes1, $shortcodes2 ) ) ) ); } protected function get_phase_name() { return 'shortcodes'; } protected function get_process_name() { return 'Shortcodes identification'; } } admin-resources/class-wpml-admin-resources-hooks.php 0000755 00000000777 14720342453 0016700 0 ustar 00 <?php class WPML_Admin_Resources_Hooks implements IWPML_Backend_Action { public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'register_resources' ) ); } public function register_resources() { wp_register_style( 'wpml-tooltip', ICL_PLUGIN_URL . '/res/css/tooltip/tooltip.css', array( 'wp-pointer' ), ICL_SITEPRESS_VERSION ); wp_register_script( 'wpml-tooltip', ICL_PLUGIN_URL . '/res/js/tooltip/tooltip.js', array( 'wp-pointer', 'jquery' ), ICL_SITEPRESS_VERSION ); } } class-wpml-tm-requirements.php 0000755 00000010366 14720342453 0012513 0 ustar 00 <?php class WPML_TM_Requirements { const INVALID_PHP_EXTENSIONS_OPTION = 'wpml-invalid-php-extensions'; private $missing; private $missing_one; public function __construct() { $this->missing = array(); $this->missing_one = false; add_action( 'admin_notices', array( $this, 'missing_plugins_warning' ) ); add_action( 'plugins_loaded', array( $this, 'plugins_loaded_action' ), 999999 ); add_action( 'wpml_loaded', array( $this, 'missing_php_extensions' ) ); } private function check_required_plugins() { $this->missing = array(); $this->missing_one = false; if ( ! defined( 'ICL_SITEPRESS_VERSION' ) || ICL_PLUGIN_INACTIVE || version_compare( ICL_SITEPRESS_VERSION, '2.0.5', '<' ) ) { $this->missing['WPML'] = array( 'url' => 'http://wpml.org', 'slug' => 'sitepress-multilingual-cms', ); $this->missing_one = true; } } public function plugins_loaded_action() { $this->check_required_plugins(); if ( ! $this->missing_one ) { do_action( 'wpml_tm_has_requirements' ); } } public function missing_php_extensions() { $extensions = $this->get_current_php_extensions(); if ( ! defined( 'ICL_HIDE_TRANSLATION_SERVICES' ) || ! ICL_HIDE_TRANSLATION_SERVICES ) { $wpml_wp_api_check = new WPML_WP_API(); if ( count( $extensions ) > 0 && $wpml_wp_api_check->is_tm_page() ) { $already_saved_invalid_extensions = get_option( self::INVALID_PHP_EXTENSIONS_OPTION ); if ( ! $already_saved_invalid_extensions || $already_saved_invalid_extensions !== $extensions ) { ICL_AdminNotifier::add_message( $this->build_invalid_php_extensions_message_args( $extensions ) ); update_option( self::INVALID_PHP_EXTENSIONS_OPTION, $extensions ); } } else { ICL_AdminNotifier::remove_message_group( 'wpml-tm-requirements' ); } } } private function build_invalid_php_extensions_message_args( array $extensions ) { $message = ''; $message .= '<p>'; $message .= __( 'WPML Translation Management requires the following PHP extensions and settings:', 'wpml-translation-management' ); $message .= '</p>'; $message .= '<ul>'; foreach ( $extensions as $id => $data ) { $message .= '<li>'; if ( 'setting' === $data['type'] ) { $message .= $data['type_description'] . ': <code>' . $id . '=' . $data['value'] . '</code>'; } if ( 'extension' === $data['type'] ) { $message .= $data['type_description'] . ': <strong>' . $id . '</strong>'; } $message .= '</li>'; } $message .= '</ul>'; return array( 'id' => 'wpml-tm-missing-extensions', 'group' => 'wpml-tm-requirements', 'msg' => $message, 'type' => 'error', 'admin_notice' => true, 'hide' => true, ); } private function get_current_php_extensions() { $extensions = array(); if ( ini_get( 'allow_url_fopen' ) !== '1' ) { $extensions['allow_url_fopen'] = array( 'type' => 'setting', 'type_description' => __( 'PHP Setting', 'wpml-translation-management' ), 'value' => '1', ); } if ( ! extension_loaded( 'openssl' ) ) { $extensions['openssl'] = array( 'type' => 'extension', 'type_description' => __( 'PHP Extension', 'wpml-translation-management' ), ); } return $extensions; } /** * Missing plugins warning. */ public function missing_plugins_warning() { if ( $this->missing ) { $missing = ''; $missing_slugs = array(); $counter = 0; foreach ( $this->missing as $title => $data ) { $url = $data['url']; $missing_slugs[] = 'wpml-missing-' . sanitize_title_with_dashes( $data['slug'] ); $counter ++; $sep = ', '; if ( count( $this->missing ) === $counter ) { $sep = ''; } elseif ( count( $this->missing ) - 1 === $counter ) { $sep = ' ' . __( 'and', 'wpml-translation-management' ) . ' '; } $missing .= '<a href="' . $url . '">' . $title . '</a>' . $sep; } $missing_slugs_classes = implode( ' ', $missing_slugs ); ?> <div class="message error wpml-admin-notice wpml-tm-inactive <?php echo $missing_slugs_classes; ?>"><p><?php printf( __( 'WPML Translation Management is enabled but not effective. It requires %s in order to work.', 'wpml-translation-management' ), $missing ); ?></p></div> <?php } } } editor/class-wpml-tm-editors.php 0000755 00000000166 14720342453 0012724 0 ustar 00 <?php class WPML_TM_Editors { const ATE = 'ate'; const WPML = 'wpml'; const WP = 'wp'; const NONE = 'none'; } editor/ATERetry.php 0000755 00000001532 14720342453 0010212 0 ustar 00 <?php namespace WPML\TM\Editor; use WPML\LIB\WP\Option; class ATERetry { /** * @param int $jobId * * @return bool */ public static function hasFailed( $jobId ) { return self::getCount( $jobId ) >= 0; } /** * @param int $jobId * * @return int */ public static function getCount( $jobId ) { return (int) Option::getOr( self::getOptionName( $jobId ), - 1 ); } /** * @param int $jobId */ public static function incrementCount( $jobId ) { Option::update( self::getOptionName( $jobId ), self::getCount( $jobId ) + 1 ); } /** * @param int $jobId */ public static function reset( $jobId ) { Option::delete( self::getOptionName( $jobId ) ); } /** * @param int $jobId * * @return string */ public static function getOptionName( $jobId ) { return sprintf( 'wpml-ate-job-retry-counter-%d', $jobId ); } } editor/class-wpml-tm-old-jobs-editor.php 0000755 00000003613 14720342453 0014250 0 ustar 00 <?php class WPML_TM_Old_Jobs_Editor { const OPTION_NAME = 'wpml-old-jobs-editor'; /** @var wpdb */ private $wpdb; /** @var WPML_Translation_Job_Factory */ private $job_factory; public function __construct( WPML_Translation_Job_Factory $job_factory ) { global $wpdb; $this->wpdb = $wpdb; $this->job_factory = $job_factory; } /** * @param int $job_id * * @return null|string */ public function get( $job_id ) { $current_editor = $this->get_current_editor( $job_id ); if ( WPML_TM_Editors::NONE === $current_editor || WPML_TM_Editors::ATE === $current_editor ) { return $current_editor; } else { return get_option( self::OPTION_NAME, null ); } } /** * @param int $job_id * * @return bool */ public function shouldStickToWPMLEditor( $job_id ) { $sql = " SELECT job.editor FROM {$this->wpdb->prefix}icl_translate_job job WHERE job.job_id < %d AND job.rid = ( SELECT rid FROM {$this->wpdb->prefix}icl_translate_job WHERE job_id = %s ) ORDER BY job.job_id DESC "; $previousJobEditor = $this->wpdb->get_var( $this->wpdb->prepare( $sql, $job_id, $job_id ) ); return $previousJobEditor === WPML_TM_Editors::WPML && get_option( self::OPTION_NAME, null ) === WPML_TM_Editors::WPML; } /** * @return string */ public function editorForTranslationsPreviouslyCreatedUsingCTE( ) { return get_option( self::OPTION_NAME, WPML_TM_Editors::WPML ); } public function set( $job_id, $editor ) { $data = [ 'editor' => $editor ]; if ( $editor !== WPML_TM_Editors::ATE ) { $data['editor_job_id'] = null; } $this->job_factory->update_job_data( $job_id, $data ); } /** * @param int $job_id * * @return null|string */ public function get_current_editor( $job_id ) { $sql = "SELECT editor FROM {$this->wpdb->prefix}icl_translate_job WHERE job_id = %d"; return $this->wpdb->get_var( $this->wpdb->prepare( $sql, $job_id ) ); } } editor/ManualJobCreationErrorNotice.php 0000755 00000007163 14720342453 0014272 0 ustar 00 <?php namespace WPML\TM\Editor; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use WPML\TM\API\Jobs; use WPML\UIPage; use function WPML\Container\make; use function WPML\FP\pipe; class ManualJobCreationErrorNotice implements \IWPML_Backend_Action { const RETRY_LIMIT = 3; public function add_hooks() { if ( \WPML_TM_ATE_Status::is_enabled() ) { Hooks::onAction( 'wp_loaded' ) ->then( function () { /** @var \WPML_Notices $notices */ $notices = make( \WPML_Notices::class ); if ( isset( $_GET['ateJobCreationError'] ) ) { $notice = $notices->create_notice( __CLASS__, $this->getContent( $_GET ) ); $notice->set_css_class_types( 'error' ); $notice->set_dismissible( false ); $notices->add_notice( $notice ); } else { $notices->remove_notice( 'default', __CLASS__ ); } } ); } } /** * @param array $params * * @return string */ private function getContent( array $params ) { $isATENotActiveError = pipe( Obj::prop( 'ateJobCreationError' ), Cast::toInt(), Relation::equals( Editor::ATE_IS_NOT_ACTIVE ) ); $isRetryLimitExceeded = pipe( Obj::prop( 'jobId' ), [ ATERetry::class, 'getCount' ], Relation::gt( self::RETRY_LIMIT ) ); return Logic::cond( [ [ $isATENotActiveError, [ self::class, 'ateNotActiveMessage' ] ], [ $isRetryLimitExceeded, [ self::class, 'retryMessage' ] ], [ Fns::always( true ), [ self::class, 'retryFailedMessage' ] ] ], $params ); } public static function retryMessage( array $params ) { $returnUrl = \remove_query_arg( [ 'ateJobCreationError', 'jobId' ], Jobs::getCurrentUrl() ); $jobId = Obj::prop( 'jobId', $params ); if ( ! is_numeric( $jobId ) ) { return sprintf( '<div class="wpml-display-flex wpml-display-flex-center">%1$s</div>', __( "WPML didn't manage to translate this page.", 'wpml-translation-management' ) ); } $jobEditUrl = Jobs::getEditUrl( $returnUrl, (int) $jobId ); $fallbackErrorMessage = sprintf( '<div class="wpml-display-flex wpml-display-flex-center">%1$s <a class="button wpml-margin-left-sm" href="%2$s">%3$s</a></div>', __( "WPML didn't manage to translate this page.", 'wpml-translation-management' ), $jobEditUrl, __( 'Try again', 'wpml-translation-management' ) ); $tryAgainTextLink = sprintf( '<a href="%1$s">%2$s</a>', $jobEditUrl, __( 'Try again', 'wpml-translation-management' ) ); $ateApiErrorMessage = ATEDetailedErrorMessage::readDetailedError( $tryAgainTextLink ); return $ateApiErrorMessage ?: $fallbackErrorMessage; } public static function retryFailedMessage() { $fallbackErrorMessage = '<div>' . sprintf( __( 'WPML tried to translate this page three times and failed. To get it fixed, contact %s', 'wpml-translation-management' ), '<a target=\'_blank\' href="https://wpml.org/forums/forum/english-support/">' . __( 'WPML support', 'wpml-translation-management' ) . '</a>' ) . '</div>'; $ateApiErrorMessage = ATEDetailedErrorMessage::readDetailedError(); return $ateApiErrorMessage ?: $fallbackErrorMessage; } public static function ateNotActiveMessage() { return '<div>' . sprintf( __( 'WPML’s Advanced Translation Editor is enabled but not activated. Go to %s to resolve the issue.', 'wpml-translation-management' ), '<a href="' . UIPage::getTMDashboard() . '">' . __( 'WPML Translation Management Dashboard', 'wpml-translation-management' ) . '</a>' ) . '</div>'; } } editor/Editor.php 0000755 00000030330 14720342453 0007777 0 ustar 00 <?php namespace WPML\TM\Editor; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Left; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Right; use WPML\LIB\WP\User; use WPML\Setup\Option as SetupOption; use WPML\TM\API\Jobs; use WPML\TM\ATE\Log\Entry; use WPML\TM\ATE\Log\Storage; use WPML\TM\ATE\Review\ReviewStatus; use WPML\TM\ATE\Sync\Trigger; use WPML\TM\Jobs\Manual; use WPML\TM\Menu\TranslationQueue\CloneJobs; use function WPML\Container\make; use function WPML\FP\curryN; use function WPML\FP\invoke; use function WPML\FP\pipe; class Editor { const ATE_JOB_COULD_NOT_BE_CREATED = 101; const ATE_EDITOR_URL_COULD_NOT_BE_FETCHED = 102; const ATE_IS_NOT_ACTIVE = 103; /** @var CloneJobs */ private $clone_jobs; /** @var Manual */ private $manualJobs; /** * Editor constructor. * * @param CloneJobs $clone_jobs * @param Manual $manualJobs */ public function __construct( CloneJobs $clone_jobs, Manual $manualJobs ) { $this->clone_jobs = $clone_jobs; $this->manualJobs = $manualJobs; } /** * @param array $params * * @return array */ public function open( $params ) { $shouldOpenCTE = function ( $jobObject ) use ( $params ) { if ( ! \WPML_TM_ATE_Status::is_enabled() ) { return true; } if ( $this->isNewJobCreated( $params, $jobObject ) ) { return wpml_tm_load_old_jobs_editor()->shouldStickToWPMLEditor( $jobObject->get_id() ); } return wpml_tm_load_old_jobs_editor()->editorForTranslationsPreviouslyCreatedUsingCTE() === \WPML_TM_Editors::WPML && wpml_tm_load_old_jobs_editor()->get_current_editor( $jobObject->get_id() ) === \WPML_TM_Editors::WPML; }; /** * It maybe needed when a job was translated via the Translation Proxy before and now, we want to open it in the editor. * * @param \WPML_Element_Translation_Job $jobObject * * @return \WPML_Element_Translation_Job */ $maybeUpdateTranslationServiceColumn = function ( $jobObject ) { if ( $jobObject->get_translation_service() !== 'local' ) { $jobObject->set_basic_data_property( 'translation_service', 'local' ); Jobs::setTranslationService( $jobObject->get_id(), 'local' ); } return $jobObject; }; $dataOfTranslationCreatedInNativeEditorViaConnection = $this->manualJobs->maybeGetDataIfTranslationCreatedInNativeEditorViaConnection( $params ); if ( $dataOfTranslationCreatedInNativeEditorViaConnection ) { update_post_meta( $dataOfTranslationCreatedInNativeEditorViaConnection['originalPostId'], \WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, 'yes' ); return $this->displayWPNative( $dataOfTranslationCreatedInNativeEditorViaConnection ); } return Either::of( $params ) ->map( [ $this->manualJobs, 'createOrReuse' ] ) ->filter( Logic::isTruthy() ) ->filter( invoke( 'user_can_translate' )->with( User::getCurrent() ) ) ->map( $maybeUpdateTranslationServiceColumn ) ->map( Logic::ifElse( $shouldOpenCTE, $this->displayCTE(), $this->tryToDisplayATE( $params ) ) ) ->getOrElse( [ 'editor' => \WPML_TM_Editors::NONE, 'jobObject' => null ] ); } /** * @param array $params * @param \WPML_Element_Translation_Job $jobObject * * @return array */ private function tryToDisplayATE( $params = null, $jobObject = null ) { $fn = curryN( 2, function ( $params, $jobObject ) { $handleNotActiveATE = Logic::ifElse( [ \WPML_TM_ATE_Status::class, 'is_active' ], Either::of(), pipe( $this->handleATEJobCreationError( $params, self::ATE_IS_NOT_ACTIVE ), Either::left() ) ); /** * Create a new ATE job when somebody clicks the "pencil" icon to edit existing translation. * * @param \WPML_Element_Translation_Job $jobObject * * @return Either<\WPML_Element_Translation_Job> */ $cloneCompletedATEJob = function ( $jobObject ) use ( $params ) { if ( $this->isValidATEJob( $jobObject ) && (int) $jobObject->get_status_value() === ICL_TM_COMPLETE ) { $sentFrom = isset( $params['preview'] ) ? Jobs::SENT_FROM_REVIEW : Jobs::SENT_MANUALLY; return $this->clone_jobs->cloneCompletedATEJob( $jobObject, $sentFrom ) ->bimap( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED ), Fns::identity() ); } return Either::of( $jobObject ); }; $handleMissingATEJob = function ( $jobObject ) use ( $params ) { // ATE editor is already set. All fine, we can proceed. if ( $this->isValidATEJob( $jobObject ) ) { return Either::of( $jobObject ); } /** * The new job has been created because either there was no translation at all or translation was "needs update". * The ATE job could not be created inside WPML_TM_ATE_Jobs_Actions::added_translation_jobs ,and we have to return the error message. */ if ( $this->isNewJobCreated( $params, $jobObject ) ) { return Either::left( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED, $jobObject ) ); } /** * It creates a corresponding job in ATE for already existing WPML job in such situations: * 1. Previously job was created in CTE, but a user selected the setting to translate existing CTE jobs in ATE * 2. The job used to be handled by the Translation Proxy or the native WP editor * 3. ATE job could not be created before and user clicked "Retry" button * 4. Job was sent via basket and ATE job could not be created */ return $this->createATECounterpartForExistingWPMLJob( $params, $jobObject ); }; return Either::of( $jobObject ) ->chain( $handleNotActiveATE ) ->chain( $cloneCompletedATEJob ) ->chain( $handleMissingATEJob ) ->map( Fns::tap( pipe( invoke( 'get_id' ), Jobs::setStatus( Fns::__, ICL_TM_IN_PROGRESS ) ) ) ) ->map( $this->openATE( $params ) ) ->coalesce( Fns::identity(), Fns::identity() ) ->get(); } ); return call_user_func_array( $fn, func_get_args() ); } /** * @param \WPML_Element_Translation_Job $jobObject * * @return array */ private function displayCTE( $jobObject = null ) { $fn = curryN( 1, function ( $jobObject ) { wpml_tm_load_old_jobs_editor()->set( $jobObject->get_id(), \WPML_TM_Editors::WPML ); return [ 'editor' => \WPML_TM_Editors::WPML, 'jobObject' => $jobObject ]; } ); return call_user_func_array( $fn, func_get_args() ); } /** * @param array $dataOfTranslationCreatedInNativeEditorViaConnection * * @return array */ private function displayWPNative( array $dataOfTranslationCreatedInNativeEditorViaConnection ) { $url = 'post.php?' . http_build_query( [ 'lang' => $dataOfTranslationCreatedInNativeEditorViaConnection['targetLanguageCode'], 'action' => 'edit', 'post_type' => str_replace( 'post_', '', $dataOfTranslationCreatedInNativeEditorViaConnection['postType'] ), 'post' => $dataOfTranslationCreatedInNativeEditorViaConnection['translatedPostId'] ] ); return [ 'editor' => \WPML_TM_Editors::WP, 'jobObject' => null, 'url' => $url ]; } /** * @param \WPML_Element_Translation_Job $jobObject * * @return void */ private function maybeSetReviewStatus( $jobObject ) { if ( Relation::propEq( 'review_status', ReviewStatus::NEEDS_REVIEW, $jobObject->to_array() ) ) { Jobs::setReviewStatus( $jobObject->get_id(), SetupOption::shouldBeReviewed() ? ReviewStatus::EDITING : null ); } } /** * It returns an url to place where a user should be redirected. The url contains a job id and error's code. * * @param array $params * @param int $code * @param \WPML_Element_Translation_Job $jobObject * * @return array */ private function handleATEJobCreationError( $params = null, $code = null, $jobObject = null ) { $fn = curryN( 3, function ( $params, $code, $jobObject ) { ATERetry::incrementCount( $jobObject->get_id() ); $retryCount = ATERetry::getCount( $jobObject->get_id() ); if ( $retryCount > 0 ) { Storage::add( Entry::retryJob( $jobObject->get_id(), [ 'retry_count' => ATERetry::getCount( $jobObject->get_id() ) ] ) ); } return [ 'editor' => \WPML_TM_Editors::ATE, 'url' => add_query_arg( [ 'ateJobCreationError' => $code, 'jobId' => $jobObject->get_id() ], $this->getReturnUrl( $params ) ) ]; } ); return call_user_func_array( $fn, func_get_args() ); } /** * It asserts a job's editor. * * @param string $editor * @param \WPML_Element_Translation_Job $jobObject * * @return bool */ private function isJobEditorEqualTo( $editor, $jobObject ) { return $jobObject->get_basic_data_property( 'editor' ) === $editor; } /** * It checks if we created a new entry in wp_icl_translate_job table. * It happens when none translation for a specific language has existed so far or when a translation has been "needs update". * * @param array $params * @param \WPML_Element_Translation_Job $jobObject * * @return bool */ private function isNewJobCreated( $params , $jobObject ) { return (int) $jobObject->get_id() !== (int) Obj::prop( 'job_id', $params ); } /** * @param array $params * @param \WPML_Element_Translation_Job $jobObject * * @return callable|Left<array>|Right<\WPML_Element_Translation_Job> */ private function createATECounterpartForExistingWPMLJob( $params, $jobObject ) { if ( $this->clone_jobs->cloneWPMLJob( $jobObject->get_id() ) ) { ATERetry::reset( $jobObject->get_id() ); $jobObject->set_basic_data_property( 'editor', \WPML_TM_Editors::ATE ); return Either::of( $jobObject ); } return Either::left( $this->handleATEJobCreationError( $params, self::ATE_JOB_COULD_NOT_BE_CREATED, $jobObject ) ); } /** * At this stage, we know that a corresponding job in ATE is created and we should open ATE editor. * We are trying to do that. * * @param array $params * @param \WPML_Element_Translation_Job $jobObject * * @return false|mixed */ private function openATE( $params = null, $jobObject = null ) { $fn = curryN( 2, function ( $params, $jobObject ) { $this->maybeSetReviewStatus( $jobObject ); $editor_url = apply_filters( 'wpml_tm_ate_jobs_editor_url', '', $jobObject->get_id(), $this->getReturnUrl( $params ) ); if ( $editor_url ) { $response['editor'] = \WPML_TM_Editors::ATE; $response['url'] = $editor_url; $response['jobObject'] = $jobObject; return $response; } return $this->handleATEJobCreationError( $params, self::ATE_EDITOR_URL_COULD_NOT_BE_FETCHED, $jobObject ); } ); return call_user_func_array( $fn, func_get_args() ); } /** * @return string */ private function getReturnUrl( $params ) { $return_url = ''; if ( array_key_exists( 'return_url', $params ) ) { $return_url = filter_var( $params['return_url'], FILTER_SANITIZE_URL ); $return_url_parts = wp_parse_url( (string) $return_url ); $admin_url = get_admin_url(); $admin_url_parts = wp_parse_url( $admin_url ); if ( strpos( $return_url_parts['path'], $admin_url_parts['path'] ) === 0 ) { $admin_url_parts['path'] = $return_url_parts['path']; } else { $admin_url_parts = $return_url_parts; } $admin_url_parts['query'] = $this->prepareQueryParameters( Obj::propOr( '', 'query', $return_url_parts ), Obj::prop( 'lang', $params ) ); $return_url = http_build_url( $admin_url_parts ); } return $return_url; } private function prepareQueryParameters( $query, $returnLanguage ) { $parameters = []; parse_str( $query, $parameters ); unset( $parameters['ate_original_id'] ); unset( $parameters['back'] ); unset( $parameters['complete'] ); if ( $returnLanguage ) { // We need the lang parameter to display the post list in the language which was used before ATE. $parameters['lang'] = $returnLanguage; } return http_build_query( $parameters ); } /** * @param \WPML_Element_Translation_Job $jobObject * * @return bool */ private function isValidATEJob( \WPML_Element_Translation_Job $jobObject ) { return $this->isJobEditorEqualTo( \WPML_TM_Editors::ATE, $jobObject ) && (int) $jobObject->get_basic_data_property( 'editor_job_id' ) > 0; } } editor/ATEDetailedErrorMessage.php 0000755 00000006676 14720342453 0013155 0 ustar 00 <?php namespace WPML\TM\Editor; use WPML\FP\Str; use WPML\FP\Cast; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Option; use WPML\TM\ATE\ClonedSites\ApiCommunication; use WPML\UIPage; use function WPML\FP\pipe; class ATEDetailedErrorMessage { const ERROR_DETAILS_OPTION = 'wpml_ate_error_details'; /** * Parses error data and saves it to options table. * * @param $errorResponse * * @return void */ public static function saveDetailedError( $errorResponse ) { $errorCode = $errorResponse->get_error_code(); $errorMessage = $errorResponse->get_error_message(); $errorData = $errorResponse->get_error_data( $errorCode ); $errorDetails = [ 'code' => $errorCode, 'message' => $errorMessage, 'error_data' => $errorData, ]; self::saveErrorDetailsInOptions( $errorDetails ); } /** * Returns single or multiple formatted error message depending on errors array existence in response. * * @param string $appendText * * @return string|null */ public static function readDetailedError( $appendText = null ) { $errorDetails = Option::getOr( self::ERROR_DETAILS_OPTION, [] ); $detailedError = self::hasValidExplainedMessage( $errorDetails ) ? self::formattedDetailedErrors( $errorDetails, $appendText ) : ( self::isSiteMigrationError( $errorDetails ) ? self::formattedSiteMigrationError( $errorDetails ) : null ); self::deleteErrorDetailsFromOptions(); // deleting the option after message is ready to be displayed. return $detailedError; } /** * Checks if valid explained message exists in error response. * * @param array $errorDetails * * @return mixed */ private static function hasValidExplainedMessage( $errorDetails ) { $hasExplainedMessage = pipe( Obj::path( [ 'error_data', 0, 'explained_message' ] ), Str::len(), Cast::toBool() ); return $hasExplainedMessage( $errorDetails ); } /** * Checks if error is "Site moved or copied" (Happens when error code is 426) * * @param array $errorDetails * * @return mixed */ private static function isSiteMigrationError( $errorDetails ) { $isSiteMigrationError = pipe( Obj::prop( 'code' ), Cast::toInt(), Relation::equals( ApiCommunication::SITE_CLONED_ERROR ) ); return $isSiteMigrationError( $errorDetails ); } /** * The purpose of this function is to avoid the case when redirect is happened and data saved in this static class is lost * * @return void */ private static function saveErrorDetailsInOptions( $errorDetails ) { Option::updateWithoutAutoLoad( self::ERROR_DETAILS_OPTION, $errorDetails ); } /** * Deletes the error details from options. * * @return void */ private static function deleteErrorDetailsFromOptions() { Option::delete( self::ERROR_DETAILS_OPTION ); } /** * Returns multiple formatted error messages from errors array. * * @param array $errorDetails * @param string $appendText * * @return string */ private static function formattedDetailedErrors( array $errorDetails, $appendText ) { $appendText = $appendText ? '<div>' . $appendText . '</div>' : ''; $allErrors = '<div>'; foreach ( Obj::prop( 'error_data', $errorDetails ) as $error ) { $allErrors .= '<div>' . Obj::propOr( '', 'explained_message', $error ) . '</div>'; } return $allErrors . $appendText . '</div>'; } private static function formattedSiteMigrationError( $errorDetails ) { return '<div>' . Obj::prop( 'message', $errorDetails ) . '</div>'; } } editor/ClassicEditorActions.php 0000755 00000002100 14720342453 0012614 0 ustar 00 <?php namespace WPML\TM\Editor; class ClassicEditorActions { public function addHooks() { add_action( 'wp_ajax_wpml_save_job_ajax', [ $this, 'saveJob' ] ); } public function saveJob() { if ( ! wpml_is_action_authenticated( 'wpml_save_job' ) ) { wp_send_json_error( 'Permission denied.' ); return; } $data = []; $post_data = \WPML_TM_Post_Data::strip_slashes_for_single_quote( $_POST['data'] ); parse_str( $post_data, $data ); /** * It filters job data * * @param array $data */ $data = apply_filters( 'wpml_translation_editor_save_job_data', $data ); $job = \WPML\Container\make( \WPML_TM_Editor_Job_Save::class ); $job_details = [ 'job_type' => $data['job_post_type'], 'job_id' => $data['job_post_id'], 'target' => $data['target_lang'], 'translation_complete' => isset( $data['complete'] ) ? true : false, ]; $job = apply_filters( 'wpml-translation-editor-fetch-job', $job, $job_details ); $ajax_response = $job->save( $data ); $ajax_response->send_json(); } } API/REST/class-wpml-tm-rest-tp-xliff-factory.php 0000755 00000000455 14720342453 0015325 0 ustar 00 <?php class WPML_TM_REST_TP_XLIFF_Factory extends WPML_REST_Factory_Loader { public function create() { return new WPML_TM_REST_TP_XLIFF( new WPML_TP_Translations_Repository( wpml_tm_get_tp_xliff_api(), wpml_tm_get_jobs_repository() ), new WPML_TM_Rest_Download_File() ); } } API/REST/class-wpml-rest.php 0000755 00000002105 14720342453 0011505 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Rest { private $http; /** * WPML_Rest constructor. * * @param WP_Http $http */ public function __construct( WP_Http $http ) { $this->http = $http; } public function is_available() { return function_exists( 'rest_get_server' ); } public function is_rest_request() { return defined( 'REST_REQUEST' ) && REST_REQUEST; } public function has_registered_routes() { return (bool) rest_get_server()->get_routes(); } public function has_discovered_routes() { return (bool) $this->get_discovered_routes(); } private function get_discovered_routes() { $url = $this->get_discovery_url(); $response = $this->http->get( $url ); $body = json_decode( $response['body'], true ); return array_key_exists( 'routes', $body ) ? $body['routes'] : array(); } public function get_discovery_url() { $url_prefix = 'wp-json'; if ( function_exists( 'rest_get_url_prefix' ) ) { $url_prefix = rest_get_url_prefix(); } return untrailingslashit( trailingslashit( get_site_url() ) . $url_prefix ); } } API/REST/Target.php 0000755 00000000251 14720342453 0007676 0 ustar 00 <?php namespace WPML\Rest; interface ITarget { function get_routes(); function get_allowed_capabilities( \WP_REST_Request $request ); function get_namespace(); } API/REST/BaseTM.php 0000755 00000000253 14720342453 0007565 0 ustar 00 <?php namespace WPML\TM\REST; abstract class Base extends \WPML\Rest\Base { /** * @return string */ public function get_namespace() { return 'wpml/tm/v1'; } } API/REST/class-wpml-rest-arguments-sanitation.php 0000755 00000002501 14720342453 0015657 0 ustar 00 <?php use WPML\API\Sanitize; /** * @author OnTheGo Systems * * The following method can be used as REST arguments sanitation callback */ class WPML_REST_Arguments_Sanitation { /** * @param mixed $value * * @return bool */ static function boolean( $value ) { /** * `FILTER_VALIDATE_BOOLEAN` returns `NULL` if not valid, but in all other cases, it sanitizes the value */ return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); } /** *@param mixed $value * * @return bool */ static function integer( $value ) { return (int) self::float( $value ); } /** *@param mixed $value * * @return bool */ static function float( $value ) { return (float) filter_var( $value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ); } /** *@param mixed $value * * @return bool */ static function string( $value ) { return Sanitize::string( $value ); } /** *@param mixed $value * * @return bool */ static function url( $value ) { return filter_var( $value, FILTER_SANITIZE_URL ); } /** *@param mixed $value * * @return bool */ static function email( $value ) { return filter_var( $value, FILTER_SANITIZE_EMAIL ); } /** *@param mixed $value * * @return array */ static function array_of_integers( $value ) { return array_map( 'intval', $value ); } } API/REST/class-wpml-tm-rest-apply-tp-translation.php 0000755 00000003301 14720342453 0016222 0 ustar 00 <?php use WPML\LIB\WP\User; class WPML_TM_REST_Apply_TP_Translation extends WPML_REST_Base { /** @var WPML_TP_Apply_Translations */ private $apply_translations; public function __construct( WPML_TP_Apply_Translations $apply_translations ) { parent::__construct( 'wpml/tm/v1' ); $this->apply_translations = $apply_translations; } public function add_hooks() { $this->register_routes(); } public function register_routes() { parent::register_route( '/tp/apply-translations', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'apply_translations' ), ) ); } /** * @return WP_Error|int|array */ public function apply_translations( WP_REST_Request $request ) { try { $params = $request->get_json_params(); if ( $params ) { if ( ! isset( $params['original_element_id'] ) ) { $params = array_filter( $params, array( $this, 'validate_job' ) ); if ( ! $params ) { return array(); } } } return $this->apply_translations->apply( $params )->map( array( $this, 'map_jobs_to_array' ) ); } catch ( Exception $e ) { return new WP_Error( 400, $e->getMessage() ); } } public function get_allowed_capabilities( WP_REST_Request $request ) { return [ User::CAP_ADMINISTRATOR, User::CAP_MANAGE_TRANSLATIONS, User::CAP_TRANSLATE ]; } public function map_jobs_to_array( WPML_TM_Job_Entity $job ) { return [ 'id' => $job->get_id(), 'type' => $job->get_type(), 'status' => $job->get_status(), ]; } /** * @param array $job * * @return bool */ private function validate_job( array $job ) { return isset( $job['id'], $job['type'] ) && \WPML_TM_Job_Entity::is_type_valid( $job['type'] ); } } API/REST/class-wpml-ate-proxy.php 0000755 00000011345 14720342453 0012466 0 ustar 00 <?php namespace WPML\TM\ATE; use WPML\LIB\WP\User; class Proxy extends \WPML_REST_Base { /** * @var \WPML_TM_ATE_AMS_Endpoints */ private $endpoints; public function __construct( \WPML_TM_ATE_AMS_Endpoints $endpoints ) { parent::__construct( 'wpml/ate/v1' ); $this->endpoints = $endpoints; } public function add_hooks() { $this->register_routes(); } public function register_routes() { parent::register_route( '/ate/proxy', array( 'methods' => \WP_REST_Server::ALLMETHODS, 'callback' => [ $this, 'proxy' ], ) ); } /** * @param \WP_REST_Request $request * * @return array */ private function get_args( \WP_REST_Request $request ) { $request_params = $this->get_request_params( $request ); $url = $request_params['url']; $headers = $request_params['headers']; $query = $request_params['query']; $content_type = $request_params['content_type']; if ( $content_type ) { if ( ! $headers ) { $headers = []; } $headers[] = 'Content-Type: ' . $content_type; } $args = [ 'method' => $request_params['method'], 'headers' => $headers, ]; $body = $request_params['body']; if ( $body ) { $args['body'] = $body; } return [ $url, $query, $args, $content_type ]; } /** * @param \WP_REST_Request $request * * @return true|\WP_Error */ private function validate_request( \WP_REST_Request $request ) { if ( ! $this->get_request_params( $request ) ) { return new \WP_Error( 'endpoint_without_parameters', 'Endpoint called with no parameters.', [ 'status' => 400 ] ); } list( $url, , $args ) = $this->get_args( $request ); $has_all_required_parameters = $url && $args['method'] && $args['headers']; if ( ! $has_all_required_parameters ) { return new \WP_Error( 'missing_required_parameters', 'Required parameters missing.', [ 'status' => 400 ] ); } if ( \strtolower( $request->get_method() ) !== \strtolower( $args['method'] ) ) { return new \WP_Error( 'invalid_method', 'Invalid method.', [ 'status' => 400 ] ); } $ateBaseUrl = $this->endpoints->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_ATE ); $amsBaseUrl = $this->endpoints->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_AMS ); if ( \strpos( \strtolower( $url ), $ateBaseUrl ) !== 0 && \strpos( \strtolower( $url ), $amsBaseUrl ) !== 0 ) { return new \WP_Error( 'invalid_url', 'Invalid URL.', [ 'status' => 400 ] ); } return true; } /** * @param \WP_REST_Request $request */ public function proxy( \WP_REST_Request $request ) { list( $url, $params, $args, $content_type ) = $this->get_args( $request ); $status_code = 200; $status_message = 'OK'; $validation = $this->validate_request( $request ); if ( \is_wp_error( $validation ) ) { $status_code = $validation->get_error_data()['status']; $status_message = $validation->get_error_message(); $response_body = ''; } else { if ( \is_array( $params ) ) { $params = \http_build_query( $params ); } $endpoint = \http_build_url( $url, [ 'query' => $params ] ); $response = \wp_remote_request( $endpoint, $args ); $response_body = \wp_remote_retrieve_body( $response ); } $protocol = ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ); if ( 200 === $status_code ) { header( "{$protocol} {$status_code} {$status_message}" ); } else { header( "Status: {$status_code} {$status_message}" ); } header( "Content-Type: {$content_type}" ); // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped echo $response_body; // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped $this->break_the_default_response_flow(); } /** * @param \WP_REST_Request $request * * @return string[]|string */ public function get_allowed_capabilities( \WP_REST_Request $request ) { return [ User::CAP_MANAGE_TRANSLATIONS, User::CAP_ADMINISTRATOR ]; } /** * @param \WP_REST_Request $request * * @return array */ private function get_request_params( \WP_REST_Request $request ) { $params = [ 'url' => null, 'method' => null, 'query' => null, 'headers' => null, 'body' => null, 'content_type' => 'application/json', ]; if ( $request->get_params() ) { $params = \array_merge( $params, $request->get_params() ); } return $params; } private function break_the_default_response_flow() { $shortcut = function () { return function () { die(); }; }; $die_handlers = [ 'wp_die_ajax_handler', 'wp_die_json_handler', 'wp_die_jsonp_handler', 'wp_die_xmlrpc_handler', 'wp_die_xml_handler', 'wp_die_handler', ]; foreach ( $die_handlers as $die_handler ) { \add_filter( $die_handler, $shortcut, 10 ); } \wp_die(); } } API/REST/class-wpml-tm-rest-apply-tp-translation-factory.php 0000755 00000000762 14720342453 0017677 0 ustar 00 <?php class WPML_TM_REST_Apply_TP_Translation_Factory extends WPML_REST_Factory_Loader { /** * @return WPML_TM_REST_Apply_TP_Translation */ public function create() { global $wpdb; return new WPML_TM_REST_Apply_TP_Translation( new WPML_TP_Apply_Translations( wpml_tm_get_jobs_repository(), new WPML_TP_Apply_Single_Job( wpml_tm_get_tp_translations_repository(), new WPML_TP_Apply_Translation_Strategies( $wpdb ) ), wpml_tm_get_tp_sync_jobs() ) ); } } API/REST/Adaptor.php 0000755 00000001217 14720342453 0010045 0 ustar 00 <?php namespace WPML\Rest; use \WP_REST_Request; class Adaptor extends \WPML_REST_Base { /** @var ITarget $target */ private $target; public function set_target( ITarget $target ) { $this->target = $target; $this->namespace = $target->get_namespace(); } public function add_hooks() { $routes = $this->target->get_routes(); foreach ( $routes as $route ) { $this->register_route( $route['route'], $route['args'] ); } } /** * @param WP_REST_Request $request * * @return array */ public function get_allowed_capabilities( WP_REST_Request $request ) { return $this->target->get_allowed_capabilities( $request ); } } API/REST/FactoryLoader.php 0000755 00000001500 14720342453 0011204 0 ustar 00 <?php namespace WPML\TM\REST; use IWPML_Deferred_Action_Loader; use IWPML_REST_Action_Loader; use WPML\TM\ATE\REST\Retry; use WPML\TM\ATE\REST\Sync; use \WPML\TM\ATE\REST\FixJob; use WPML\TM\ATE\REST\Download; use WPML\TM\ATE\REST\PublicReceive; use function WPML\Container\make; class FactoryLoader implements IWPML_REST_Action_Loader, IWPML_Deferred_Action_Loader { const REST_API_INIT_ACTION = 'rest_api_init'; /** * @return string */ public function get_load_action() { return self::REST_API_INIT_ACTION; } public function create() { return [ Sync::class => make( Sync::class ), Download::class => make( Download::class ), Retry::class => make( Retry::class ), PublicReceive::class => make( PublicReceive::class ), FixJob::class => make( FixJob::class ), ]; } } API/REST/class-wpml-tm-rest-download-file.php 0000755 00000001352 14720342453 0014650 0 ustar 00 <?php class WPML_TM_Rest_Download_File { public function send( $file_name, $content, $content_type = 'application/x-xliff+xml' ) { add_filter( 'rest_pre_echo_response', array( $this, 'force_wp_rest_server_download' ) ); header( 'Content-Description: File Transfer' ); header( 'Content-Type: ' . $content_type ); header( 'Content-Disposition: attachment; filename="' . $file_name . '"' ); header( 'Content-Transfer-Encoding: binary' ); header( 'Expires: 0' ); header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' ); header( 'Pragma: public' ); header( 'Content-Length: ' . strlen( $content ) ); return $content; } public function force_wp_rest_server_download( $content ) { echo $content; exit; } } API/REST/class-wpml-rest-arguments-validation.php 0000755 00000002456 14720342453 0015651 0 ustar 00 <?php /** * @author OnTheGo Systems * * The following method can be used as REST arguments validation callback */ class WPML_REST_Arguments_Validation { /** *@param mixed $value * * @return bool */ static function boolean( $value ) { return null !== filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); } /** *@param mixed $value * * @return bool */ static function integer( $value ) { return false !== filter_var( $value, FILTER_VALIDATE_INT ); } /** *@param mixed $value * * @return bool */ static function float( $value ) { return false !== filter_var( $value, FILTER_VALIDATE_FLOAT ); } /** *@param mixed $value * * @return bool */ static function url( $value ) { return false !== filter_var( $value, FILTER_VALIDATE_URL ); } /** *@param mixed $value * * @return bool */ static function email( $value ) { return false !== filter_var( $value, FILTER_VALIDATE_EMAIL ); } /** *@param mixed $value * * @return bool */ static function is_array( $value ) { return is_array( $value ); } /** * @param mixed $value * * @return bool */ static function date( $value ) { try { $d = new DateTime( $value ); return $d && $d->format( 'Y-m-d' ) == $value; } catch ( Exception $e ) { return false; } } } API/REST/class-wpml-rest-extend-args.php 0000755 00000005037 14720342453 0013733 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_REST_Extend_Args implements IWPML_Action { const REST_LANGUAGE_ARGUMENT = 'wpml_language'; /** @var \SitePress $sitepress */ private $sitepress; /** @var string $current_language_backup */ private $current_language_backup; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } function add_hooks() { add_filter( 'rest_endpoints', array( $this, 'rest_endpoints' ) ); add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 ); add_filter( 'rest_request_after_callbacks', array( $this, 'rest_request_after_callbacks' ) ); } /** * Adds the `wpml_language` argument (optional) to all REST calls with arguments. * * @param array $endpoints * * @return array */ public function rest_endpoints( array $endpoints ) { $valid_language_codes = $this->get_active_language_codes(); foreach ( $endpoints as $route => &$endpoint ) { foreach ( $endpoint as $key => &$data ) { if ( is_numeric( $key ) ) { $data['args'][ self::REST_LANGUAGE_ARGUMENT ] = array( 'type' => 'string', 'description' => "WPML's language code", 'required' => false, 'enum' => $valid_language_codes, ); } } } return $endpoints; } /** * If `wpml_language` is provided, backups the current language, then switch to the provided one. * * @param \WP_REST_Response|array|mixed $response * @param \WP_REST_Server|array|mixed $rest_server * @param \WP_REST_Request $request * * @return mixed */ public function rest_request_before_callbacks( $response, $rest_server, $request ) { $this->current_language_backup = null; $current_language = $this->sitepress->get_current_language(); $rest_language = $request->get_param( self::REST_LANGUAGE_ARGUMENT ); if ( $rest_language && $rest_language !== $current_language ) { $this->current_language_backup = $current_language; $this->sitepress->switch_lang( $rest_language ); } return $response; } /** * Restore the backup language, if set. * * @param \WP_REST_Response|array|mixed $response * * @return mixed */ public function rest_request_after_callbacks( $response ) { if ( $this->current_language_backup ) { $this->sitepress->switch_lang( $this->current_language_backup ); } return $response; } /** * @return array */ private function get_active_language_codes() { return array_keys( $this->sitepress->get_active_languages() ); } } API/REST/class-wpml-tm-rest-batch-sync.php 0000755 00000003310 14720342453 0014153 0 ustar 00 <?php use WPML\LIB\WP\User; class WPML_TM_REST_Batch_Sync extends WPML_REST_Base { /** @var WPML_TP_Batch_Sync_API */ private $batch_sync_api; public function __construct( WPML_TP_Batch_Sync_API $batch_sync_api ) { parent::__construct( 'wpml/tm/v1' ); $this->batch_sync_api = $batch_sync_api; } public function add_hooks() { $this->register_routes(); } public function register_routes() { parent::register_route( '/tp/batches/sync', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'init' ), 'args' => array( 'batchId' => array( 'required' => true, 'validate_callback' => array( $this, 'validate_batch_ids' ), 'sanitize_callback' => array( $this, 'sanitize_batch_ids' ), ), ), ) ); parent::register_route( '/tp/batches/status', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'check_progress' ), ) ); } public function init( WP_REST_Request $request ) { try { return $this->batch_sync_api->init_synchronization( $request->get_param( 'batchId' ) ); } catch ( Exception $e ) { return new WP_Error( 500, $e->getMessage() ); } } public function check_progress() { try { return $this->batch_sync_api->check_progress(); } catch ( Exception $e ) { return new WP_Error( 500, $e->getMessage() ); } } public function get_allowed_capabilities( WP_REST_Request $request ) { return [ User::CAP_ADMINISTRATOR, User::CAP_MANAGE_TRANSLATIONS, User::CAP_TRANSLATE ]; } public function validate_batch_ids( $batches ) { return is_array( $batches ); } public function sanitize_batch_ids( $batches ) { return array_map( 'intval', $batches ); } } API/REST/class-wpml-rest-base.php 0000755 00000004412 14720342453 0012420 0 ustar 00 <?php /** * Class WPML_REST_Base * * @author OnTheGo Systems */ abstract class WPML_REST_Base { const CAPABILITY_EXTERNAL = 'external'; const REST_NAMESPACE = 'wpml/v1'; /** * @var string */ protected $namespace; /** * WPML_REST_Base constructor. * * @param string|null $namespace Defaults to `\WPML_REST_Base::REST_NAMESPACE`. */ public function __construct( $namespace = null ) { if ( ! $namespace ) { $namespace = self::REST_NAMESPACE; } $this->namespace = $namespace; } abstract public function add_hooks(); public function validate_permission( WP_REST_Request $request ) { $user_can = $this->user_has_matching_capabilities( $request ); if ( ! $user_can ) { return false; } $nonce = $this->get_nonce( $request ); return $user_can && wp_verify_nonce( $nonce, 'wp_rest' ); } abstract public function get_allowed_capabilities( WP_REST_Request $request ); /** * @param string $route * @param array $args */ protected function register_route( $route, array $args ) { $args = $this->ensure_permission( $args ); register_rest_route( $this->namespace, $route, $args ); } /** * @param array $args * * @return array */ private function ensure_permission( array $args ) { if ( ! array_key_exists( 'permission_callback', $args ) || ! $args['permission_callback'] ) { $args['permission_callback'] = array( $this, 'validate_permission' ); } return $args; } /** * @param \WP_REST_Request $request * * @return bool */ private function user_has_matching_capabilities( WP_REST_Request $request ) { $capabilities = $this->get_allowed_capabilities( $request ); $user_can = false; if ( self::CAPABILITY_EXTERNAL === $capabilities ) { $user_can = true; } elseif ( is_string( $capabilities ) ) { $user_can = current_user_can( $capabilities ); } elseif ( is_array( $capabilities ) ) { foreach ( $capabilities as $capability ) { $user_can = $user_can || current_user_can( $capability ); } } return $user_can; } /** * @param \WP_REST_Request $request * * @return mixed|string|null */ private function get_nonce( WP_REST_Request $request ) { $nonce = $request->get_header( 'x_wp_nonce' ); if ( ! $nonce ) { $nonce = $request->get_param( '_wpnonce' ); } return $nonce; } } API/REST/class-wpml-tm-rest-jobs.php 0000755 00000022437 14720342453 0013070 0 ustar 00 <?php /** * WPML_TM_REST_Jobs class file. * * @package wpml-translation-management */ use WPML\FP\Obj; use WPML\FP\Fns; use WPML\FP\Maybe; use WPML\LIB\WP\User; use WPML\TM\ATE\Review\Cancel; use function WPML\FP\pipe; use function WPML\FP\partial; use function WPML\FP\invoke; use function WPML\FP\curryN; /** * Class WPML_TM_REST_Jobs */ class WPML_TM_REST_Jobs extends WPML_REST_Base { const CAPABILITY = 'translate'; /** * Jobs repository * * @var WPML_TM_Jobs_Repository */ private $jobs_repository; /** * Rest jobs criteria parser * * @var WPML_TM_Rest_Jobs_Criteria_Parser */ private $criteria_parser; /** * View model * * @var WPML_TM_Rest_Jobs_View_Model */ private $view_model; /** * Update jobs synchronisation * * @var WPML_TP_Sync_Update_Job */ private $update_jobs; /** * Last picked up jobs * * @var WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up */ private $wpml_tm_last_picked_up; /** * WPML_TM_REST_Jobs constructor. * * @param WPML_TM_Jobs_Repository $jobs_repository Jobs repository. * @param WPML_TM_Rest_Jobs_Criteria_Parser $criteria_parser Rest jobs criteria parser. * @param WPML_TM_Rest_Jobs_View_Model $view_model View model. * @param WPML_TP_Sync_Update_Job $update_jobs Update jobs synchronisation. * @param WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up Last picked up jobs. */ public function __construct( WPML_TM_Jobs_Repository $jobs_repository, WPML_TM_Rest_Jobs_Criteria_Parser $criteria_parser, WPML_TM_Rest_Jobs_View_Model $view_model, WPML_TP_Sync_Update_Job $update_jobs, WPML_TM_Last_Picked_Up $wpml_tm_last_picked_up ) { parent::__construct( 'wpml/tm/v1' ); $this->jobs_repository = $jobs_repository; $this->criteria_parser = $criteria_parser; $this->view_model = $view_model; $this->update_jobs = $update_jobs; $this->wpml_tm_last_picked_up = $wpml_tm_last_picked_up; } /** * Add hooks */ public function add_hooks() { $this->register_routes(); } /** * Register routes */ public function register_routes() { parent::register_route( '/jobs', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_jobs' ), 'args' => array( 'local_job_ids' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'scope' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_TM_Jobs_Search_Params', 'is_valid_scope' ), 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'id' => array( 'type' => 'integer', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ), ), 'title' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'source_language' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'target_language' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'status' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'needs_update' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_TM_Jobs_Needs_Update_Param', 'is_valid' ), ), 'limit' => array( 'type' => 'integer', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), ), 'offset' => array( 'type' => 'integer', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), ), 'sorting' => array( 'validate_callback' => array( $this, 'validate_sorting' ), ), 'translated_by' => array( 'type' => 'string', 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'string' ), ), 'sent_from' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ), ), 'sent_to' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ), ), 'deadline_from' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ), ), 'deadline_to' => array( 'type' => 'string', 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'date' ), ), ), ) ); parent::register_route( '/jobs/assign', array( 'methods' => 'POST', 'callback' => array( $this, 'assign_job' ), 'args' => array( 'jobId' => array( 'required' => true, 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ), ), 'type' => array( 'required' => false, 'validate_callback' => [ WPML_TM_Job_Entity::class, 'is_type_valid' ], ), 'translatorId' => array( 'required' => true, 'validate_callback' => array( 'WPML_REST_Arguments_Validation', 'integer' ), 'sanitize_callback' => array( 'WPML_REST_Arguments_Sanitation', 'integer' ), ), ), ) ); parent::register_route( '/jobs/cancel', array( 'methods' => 'POST', 'callback' => array( $this, 'cancel_jobs' ), ) ); } /** * Get jobs * * @param WP_REST_Request $request REST request. * * @return array|WP_Error */ public function get_jobs( WP_REST_Request $request ) { try { $criteria = $this->criteria_parser->build_criteria( $request ); $model = $this->view_model->build( $this->jobs_repository->get( $criteria ), $this->jobs_repository->get_count( $criteria ), $criteria ); $model['last_picked_up_date'] = $this->wpml_tm_last_picked_up->get(); return $model; } catch ( Exception $e ) { return new WP_Error( 500, $e->getMessage() ); } } /** * Assign job. * * @param WP_REST_Request $request REST request. * * @return array * @throws \InvalidArgumentException Exception on error. */ public function assign_job( WP_REST_Request $request ) { /** * It can be job_id from icl_translate_job or id from icl_string_translations * * @var int $job_id */ $job_id = $request->get_param( 'jobId' ); $job_type = $request->get_param( 'type' ) ? $request->get_param( 'type' ) : WPML_TM_Job_Entity::POST_TYPE; $assignJob = curryN( 4, 'wpml_tm_assign_translation_job'); return Maybe::of( $request->get_param( 'translatorId' ) ) ->filter( User::get() ) ->map( $assignJob( $job_id, Fns::__, 'local', $job_type ) ) ->map( Obj::objOf( 'assigned' ) ) ->getOrElse( null ); } /** * Cancel job * * @param WP_REST_Request $request REST request. * * @return array|WP_Error */ public function cancel_jobs( WP_REST_Request $request ) { try { // $validateParameter :: [id, type] -> bool $validateParameter = pipe( Obj::prop( 'type' ), [ \WPML_TM_Job_Entity::class, 'is_type_valid' ] ); // $getJob :: [id, type] -> \WPML_TM_Job_Entity $getJob = Fns::converge( [ $this->jobs_repository, 'get_job' ], [ Obj::prop( 'id' ), Obj::prop( 'type' ) ] ); // $jobEntityToArray :: \WPML_TM_Job_Entity -> [id, type] $jobEntityToArray = function ( \WPML_TM_Job_Entity $job ) { return [ 'id' => $job->get_id(), 'type' => $job->get_type(), ]; }; $jobs = \wpml_collect( $request->get_json_params() ) ->filter( $validateParameter ) ->map( $getJob ) ->filter() ->map( Fns::tap( invoke( 'set_status' )->with( ICL_TM_NOT_TRANSLATED ) ) ) ->map( Fns::tap( [ $this->update_jobs, 'update_state' ] ) ); do_action( 'wpml_tm_jobs_cancelled', $jobs->toArray() ); return $jobs->map( $jobEntityToArray )->values()->toArray(); } catch ( Exception $e ) { return new WP_Error( 500, $e->getMessage() ); } } /** * Get allowed capabilities * * @param WP_REST_Request $request REST request. * * @return array|string */ public function get_allowed_capabilities( WP_REST_Request $request ) { return [ User::CAP_ADMINISTRATOR, User::CAP_MANAGE_TRANSLATIONS, User::CAP_TRANSLATE ]; } /** * Validate sorting * * @param mixed $sorting Sorting parameters. * * @return bool */ public function validate_sorting( $sorting ) { if ( ! is_array( $sorting ) ) { return false; } try { foreach ( $sorting as $column => $asc_or_desc ) { new WPML_TM_Jobs_Sorting_Param( $column, $asc_or_desc ); } } catch ( Exception $e ) { return false; } return true; } /** * Validate job * * @param mixed $job Job. * * @return bool */ private function validate_job( $job ) { return is_array( $job ) && isset( $job['id'] ) && isset( $job['type'] ) && \WPML_TM_Job_Entity::is_type_valid( $job['type'] ); } } API/REST/class-wpml-tm-rest-tp-xliff.php 0000755 00000005022 14720342453 0013653 0 ustar 00 <?php use WPML\LIB\WP\User; class WPML_TM_REST_TP_XLIFF extends WPML_REST_Base { /** @var WPML_TP_Translations_Repository */ private $translation_repository; /** @var WPML_TM_Rest_Download_File */ private $download_file; public function __construct( WPML_TP_Translations_Repository $translation_repository, WPML_TM_Rest_Download_File $download_file ) { parent::__construct( 'wpml/tm/v1' ); $this->translation_repository = $translation_repository; $this->download_file = $download_file; } public function add_hooks() { $this->register_routes(); } public function register_routes() { parent::register_route( '/tp/xliff/download/(?P<job_id>\d+)/(?P<job_type>\w+)', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_job_translations_from_tp' ), 'args' => array( 'job_id' => array( 'required' => true, ), 'job_type' => array( 'required' => true, 'validate_callback' => array( $this, 'validate_job_type' ), ), 'json' => array( 'type' => 'boolean', ), ), ) ); } /** * @param WP_REST_Request $request * * @return array|string|WP_Error */ public function get_job_translations_from_tp( WP_REST_Request $request ) { try { if ( $request->get_param( 'json' ) ) { return $this->translation_repository->get_job_translations( $request->get_param( 'job_id' ), $request->get_param( 'job_type' ) )->to_array(); } else { return $this->download_job_translation( $request ); } } catch ( Exception $e ) { return new WP_Error( 400, $e->getMessage() ); } } /** * @param WP_REST_Request $request * * @return string */ private function download_job_translation( WP_REST_Request $request ) { try { $content = $this->translation_repository->get_job_translations( $request->get_param( 'job_id' ), $request->get_param( 'job_type' ), false ); } catch ( WPML_TP_API_Exception $e ) { return new WP_Error( 500, $e->getMessage() ); } $file_name = sprintf( 'job-%d.xliff', $request->get_param( 'job_id' ) ); return $this->download_file->send( $file_name, $content ); } public function get_allowed_capabilities( WP_REST_Request $request ) { return [ User::CAP_ADMINISTRATOR, User::CAP_MANAGE_TRANSLATIONS, User::CAP_TRANSLATE ]; } public function validate_job_type( $value ) { return in_array( $value, array( WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::STRING_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE, ) ); } } API/REST/class-wpml-ate-proxy-factory.php 0000755 00000000403 14720342453 0014124 0 ustar 00 <?php namespace WPML\TM\ATE\Factories; class Proxy extends \WPML_REST_Factory_Loader { /** * @return \WPML\TM\ATE\Proxy */ public function create() { $endpoints = new \WPML_TM_ATE_AMS_Endpoints(); return new \WPML\TM\ATE\Proxy( $endpoints ); } } API/REST/class-wpml-rest-factory-loader.php 0000755 00000000462 14720342453 0014422 0 ustar 00 <?php /** * @author OnTheGo Systems */ abstract class WPML_REST_Factory_Loader implements IWPML_REST_Action_Loader, IWPML_Deferred_Action_Loader { const REST_API_INIT_ACTION = 'rest_api_init'; /** * @return string */ public function get_load_action() { return self::REST_API_INIT_ACTION; } } API/REST/Base.php 0000755 00000001526 14720342453 0007330 0 ustar 00 <?php namespace WPML\Rest; use IWPML_Action; abstract class Base implements ITarget, IWPML_Action { /** @var Adaptor */ private $adaptor; public function __construct( Adaptor $adaptor ) { $this->adaptor = $adaptor; $adaptor->set_target( $this ); } /** * @return string */ abstract public function get_namespace(); public function add_hooks() { $this->adaptor->add_hooks(); } /** * @return array */ public static function getStringType() { return [ 'type' => 'string', 'sanitize_callback' => 'WPML_REST_Arguments_Sanitation::string', ]; } /** * @return array */ public static function getIntType() { return [ 'type' => 'int', 'validate_callback' => 'WPML_REST_Arguments_Validation::integer', 'sanitize_callback' => 'WPML_REST_Arguments_Sanitation::integer', ]; } } API/REST/jobs/class-wpml-tm-rest-job-translator-name.php 0000755 00000000357 14720342453 0016744 0 ustar 00 <?php class WPML_TM_Rest_Job_Translator_Name { public function get( $translator_id ) { $user = get_user_by( 'id', $translator_id ); if ( $user instanceof WP_User ) { return $user->display_name; } else { return ''; } } } API/REST/jobs/class-wpml-tm-rest-jobs-criteria-parser.php 0000755 00000013447 14720342453 0017120 0 ustar 00 <?php use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Relation; use WPML\LIB\WP\User; use WPML\TM\API\Translators; class WPML_TM_Rest_Jobs_Criteria_Parser { /** * @param WP_REST_Request $request * * @return WPML_TM_Jobs_Search_Params */ public function build_criteria( WP_REST_Request $request ) { $params = new WPML_TM_Jobs_Search_Params(); $params = $this->set_scope( $params, $request ); $params = $this->set_pagination( $params, $request ); $params = $this->set_filters( $params, $request ); $params = $this->set_sorting( $params, $request ); return $params; } /** * @param WPML_TM_Jobs_Search_Params $params * @param WP_REST_Request $request * * @return WPML_TM_Jobs_Search_Params */ private function set_scope( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) { $scope = $request->get_param( 'scope' ); if ( WPML_TM_Jobs_Search_Params::is_valid_scope( $scope ) ) { $params->set_scope( $scope ); } return $params; } /** * @param WPML_TM_Jobs_Search_Params $params * @param WP_REST_Request $request * * @return WPML_TM_Jobs_Search_Params */ private function set_pagination( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) { $limit = (int) $request->get_param( 'limit' ); if ( $limit > 0 ) { $params->set_limit( $limit ); $offset = (int) $request->get_param( 'offset' ); if ( $offset > 0 ) { $params->set_offset( $offset ); } } return $params; } private function set_filters( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) { foreach ( [ 'id', 'source_language', 'translated_by', 'element_type' ] as $key ) { $value = (string) $request->get_param( $key ); if ( $value ) { $params->{'set_' . $key}( $value ); } } foreach ( [ 'ids', 'local_job_ids', 'title', 'target_language', 'batch_name' ] as $key ) { $value = (string) $request->get_param( $key ); if ( strlen( $value ) ) { $params->{'set_' . $key}( explode( ',', $value ) ); } } if ( $request->get_param( 'status' ) !== null ) { $statuses = Fns::map( Cast::toInt(), explode( ',', $request->get_param( 'status' ) ) ); $params->set_status( Fns::reject( Relation::equals( ICL_TM_NEEDS_REVIEW ), $statuses ) ); $params->set_needs_review( Lst::includes( ICL_TM_NEEDS_REVIEW, $statuses ) ); } $params->set_exclude_cancelled(); if ( $request->get_param( 'needs_update' ) ) { $params->set_needs_update( new WPML_TM_Jobs_Needs_Update_Param( $request->get_param( 'needs_update' ) ) ); } if ( $request->get_param( 'only_automatic' ) ) { $params->set_exclude_manual( true ); } $date_range_values = array( 'sent', 'deadline' ); foreach ( $date_range_values as $date_range_value ) { $from = $request->get_param( $date_range_value . '_from' ); $to = $request->get_param( $date_range_value . '_to' ); if ( $from || $to ) { $from = $from ? new DateTime( $from ) : null; $to = $to ? new DateTime( $to ) : null; if ( $from && $to && $from > $to ) { continue; } $params->{'set_' . $date_range_value}( new WPML_TM_Jobs_Date_Range( $from, $to ) ); } } if ( $request->get_param( 'pageName' ) === \WPML_TM_Jobs_List_Script_Data::TRANSLATION_QUEUE_PAGE ) { global $wpdb; /** * On Translation Queue page, in general, you should only see the jobs assigned to you or unassigned. * Although, we want to make an exception for automatic jobs which require review. Those jobs shall not have assigned translator, * but due to some old bugs, a user can have corrupted data in the database. We want him to be able to see them even if due to the bug, * they are assigned to somebody else. */ $translatorCond = "( (translate_job.translator_id = %d OR translate_job.translator_id = 0 OR translate_job.translator_id IS NULL) OR (automatic = 1 OR review_status = 'NEEDS_REVIEW') )"; $where[] = $wpdb->prepare( $translatorCond, User::getCurrentId() ); if ( ! $request->get_param( 'includeTranslationServiceJobs' ) ) { $where[] = 'translation_status.translation_service = "local"'; } $where[] = "( automatic = 0 OR review_status = 'NEEDS_REVIEW' )"; $where[] = $this->buildLanguagePairsCriteria(); $params->set_custom_where_conditions( $where ); } return $params; } /** * @return string */ private function buildLanguagePairsCriteria() { $translator = Translators::getCurrent(); $buildWhereForPair = function ( $targets, $source ) { return sprintf( '( translations.source_language_code = "%s" AND translations.language_code IN (%s) )', $source, wpml_prepare_in( $targets ) ); }; return '( ' . \wpml_collect( $translator->language_pairs ) ->map( $buildWhereForPair ) ->implode( ' OR ' ) . ' ) '; } private function set_sorting( WPML_TM_Jobs_Search_Params $params, WP_REST_Request $request ) { $sorting = []; $sortingParams = $request->get_param( 'sorting' ); if ( $sortingParams ) { $sorting = $this->build_sorting_params( $sortingParams ); } if ( $request->get_param( 'pageName' ) === \WPML_TM_Jobs_List_Script_Data::TRANSLATION_QUEUE_PAGE ) { $sorting[] = new \WPML_TM_Jobs_Sorting_Param( "IF ((status = 10 AND (review_status = 'EDITING' OR review_status = 'NEEDS_REVIEW')) OR status = 1, 1,IF (status = 2, 2, IF (needs_update = 1, 3, 4)))", 'ASC' ); $sorting[] = new \WPML_TM_Jobs_Sorting_Param( 'translator_id', 'DESC' ); $sorting[] = new \WPML_TM_Jobs_Sorting_Param( 'translate_job_id', 'DESC' ); } $params->set_sorting( $sorting ); return $params; } /** * @param array $request_param * * @return WPML_TM_Jobs_Sorting_Param[] */ private function build_sorting_params( array $request_param ) { return \wpml_collect( $request_param )->map( function ( $direction, $column ) { return new WPML_TM_Jobs_Sorting_Param( $column, $direction ); } )->toArray(); } } API/REST/jobs/class-wpml-tm-rest-jobs-language-names.php 0000755 00000001412 14720342453 0016675 0 ustar 00 <?php class WPML_TM_Rest_Jobs_Language_Names { /** @var SitePress */ private $sitepress; /** @var array */ private $active_languages; /** * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param string $code * * @return string */ public function get( $code ) { $languages = $this->get_active_languages(); return isset( $languages[ $code ] ) ? $languages[ $code ] : $code; } /** * @return array */ public function get_active_languages() { if ( ! $this->active_languages ) { foreach ( $this->sitepress->get_active_languages() as $code => $data ) { $this->active_languages[ $code ] = $data['display_name']; } } return $this->active_languages; } } API/REST/jobs/class-wpml-tm-rest-jobs-package-helper-factory.php 0000755 00000000730 14720342453 0020330 0 ustar 00 <?php class WPML_TM_Rest_Jobs_Package_Helper_Factory { /** @var WPML_Package_Helper */ private $package_helper = false; /** * @return null|WPML_Package_Helper */ public function create() { if ( false === $this->package_helper ) { if ( defined( 'WPML_ST_VERSION' ) && class_exists( 'WPML_Package_Helper' ) ) { $this->package_helper = new WPML_Package_Helper(); } else { $this->package_helper = null; } } return $this->package_helper; } } API/REST/jobs/class-wpml-tm-rest-jobs-translation-service.php 0000755 00000001631 14720342453 0020010 0 ustar 00 <?php class WPML_TM_Rest_Jobs_Translation_Service { /** * @param string|int $service_id * * @return string */ public function get_name( $service_id ) { $name = ''; if ( is_numeric( $service_id ) ) { $service = $this->get_translation_service( (int) $service_id ); if ( $service ) { $name = $service->name; } } else { $name = __( 'Local', 'wpml-translation-management' ); } return $name; } private function get_translation_service( $service_id ) { $getService = function ( $service_id ) { $current_service = TranslationProxy::get_current_service(); if ( $current_service && $current_service->id === $service_id ) { return $current_service; } else { return TranslationProxy_Service::get_service( $service_id ); } }; $cachedGetService = \WPML\LIB\WP\Cache::memorize( 'wpml-tm-services', 3600, $getService ); return $cachedGetService( $service_id ); } } API/REST/jobs/class-wpml-tm-rest-jobs-view-model.php 0000755 00000015175 14720342453 0016074 0 ustar 00 <?php use WPML\Element\API\PostTranslations; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\TM\API\Jobs; use WPML\TM\ATE\Review\PreviewLink; use WPML\TM\ATE\Review\ReviewStatus; use WPML\TM\Jobs\Utils\ElementLink; class WPML_TM_Rest_Jobs_View_Model { /** @var WPML_TM_Rest_Jobs_Translation_Service */ private $translation_service; /** @var WPML_TM_Rest_Jobs_Element_Info */ private $element_info; /** @var WPML_TM_Rest_Jobs_Language_Names */ private $language_names; /** @var WPML_TM_Rest_Job_Translator_Name */ private $translator_name; /** @var WPML_TM_Rest_Job_Progress */ private $progress; /** @var ElementLink $element_link */ private $element_link; /** * @param WPML_TM_Rest_Jobs_Translation_Service $translation_service * @param WPML_TM_Rest_Jobs_Element_Info $element_info * @param WPML_TM_Rest_Jobs_Language_Names $language_names * @param WPML_TM_Rest_Job_Translator_Name $translator_name * @param WPML_TM_Rest_Job_Progress $progress * @param ElementLink $element_link */ public function __construct( WPML_TM_Rest_Jobs_Translation_Service $translation_service, WPML_TM_Rest_Jobs_Element_Info $element_info, WPML_TM_Rest_Jobs_Language_Names $language_names, WPML_TM_Rest_Job_Translator_Name $translator_name, WPML_TM_Rest_Job_Progress $progress, ElementLink $element_link ) { $this->translation_service = $translation_service; $this->element_info = $element_info; $this->language_names = $language_names; $this->translator_name = $translator_name; $this->progress = $progress; $this->element_link = $element_link; } /** * @param WPML_TM_Jobs_Collection $jobs * @param int $total_jobs_count * @param WPML_TM_Jobs_Search_Params $jobs_search_params * * @return array */ public function build( WPML_TM_Jobs_Collection $jobs, $total_jobs_count, WPML_TM_Jobs_Search_Params $jobs_search_params ) { $result = [ 'jobs' => [] ]; foreach ( $jobs as $job ) { $result['jobs'][] = $this->map_job( $job, $jobs_search_params ); } $result['total'] = $total_jobs_count; return $result; } /** * @param WPML_TM_Job_Entity $job * @param WPML_TM_Jobs_Search_Params $jobs_search_params * * @return array */ private function map_job( WPML_TM_Job_Entity $job, WPML_TM_Jobs_Search_Params $jobs_search_params ) { $extra_data = []; $viewUrl = ''; if ( $job instanceof WPML_TM_Post_Job_Entity ) { $extra_data['icl_translate_job_id'] = $job->get_translate_job_id(); $extra_data['editor_job_id'] = $job->get_editor_job_id(); $viewUrl = $this->getViewUrl( $job, $jobs_search_params ); } return [ 'id' => $job->get_rid(), 'type' => $job->get_type(), 'tp_id' => $job->get_tp_id(), 'status' => $job->get_status(), 'needs_update' => $job->does_need_update(), 'review_status' => $job->get_review_status(), 'language_codes' => [ 'source' => $job->get_source_language(), 'target' => $job->get_target_language(), ], 'languages' => [ 'source' => $this->language_names->get( $job->get_source_language() ), 'target' => $this->language_names->get( $job->get_target_language() ), ], 'translation_service_id' => $job->get_translation_service(), 'translation_service' => $this->translation_service->get_name( $job->get_translation_service() ), 'sent_date' => $job->get_sent_date()->format( 'Y-m-d' ), 'deadline' => $job->get_deadline() ? $job->get_deadline()->format( 'Y-m-d' ) : '', 'ts_status' => (string) $job->get_ts_status(), 'element' => $this->element_info->get( $job ), 'translator_name' => $job->get_translator_id() ? $this->translator_name->get( $job->get_translator_id() ) : '', 'translator_id' => $job->get_translator_id() ? (int) $job->get_translator_id() : '', 'is_ate_job' => $job->is_ate_job(), 'automatic' => $job->is_automatic(), 'progress' => $this->progress->get( $job ), 'batch' => [ 'id' => $job->get_batch()->get_id(), 'name' => $job->get_batch()->get_name(), 'tp_id' => $job->get_batch()->get_tp_id(), ], 'extra_data' => $extra_data, 'edit_url' => $this->get_edit_url( $job ), 'view_url' => $viewUrl, 'view_original_url' => $this->element_link->getOriginal( $job ), ]; } /** * @param WPML_TM_Job_Entity $job * * @return mixed|string|void */ private function get_edit_url( $job ) { $edit_url = ''; if ( $job->get_original_element_id() ) { $jobId = $job instanceof WPML_TM_Post_Job_Entity ? $job->get_translate_job_id() : $job->get_rid(); $translation_queue_page = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php&job_id=' . $jobId ); $edit_url = apply_filters( 'icl_job_edit_url', $translation_queue_page, $jobId ); } return $edit_url; } /** * @param WPML_TM_Post_Job_Entity $job * @param WPML_TM_Jobs_Search_Params $jobs_search_params * * @return string */ private function getViewUrl( WPML_TM_Post_Job_Entity $job, WPML_TM_Jobs_Search_Params $jobs_search_params ) { $needsReview = Lst::includes( $job->get_review_status(), [ ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ] ); return $needsReview ? $this->getReviewUrl( $job, $jobs_search_params ) : $this->element_link->getTranslation( $job ); } /** * @param WPML_TM_Post_Job_Entity $job * @param WPML_TM_Jobs_Search_Params $jobs_search_params * * @return string */ private function getReviewUrl( WPML_TM_Post_Job_Entity $job, WPML_TM_Jobs_Search_Params $jobs_search_params ) { $target_language = $jobs_search_params->get_target_language(); $element_type = $jobs_search_params->get_element_type(); $filterParams = ''; if ( is_string( $element_type ) && strlen( $element_type ) > 0 ) { $filterParams .= '&element_type=' . $element_type; } if ( is_array( $target_language ) && count( $target_language ) > 0 ) { $filterParams .= '&targetLanguages=' . implode( ',', $target_language ); } $returnUrl = admin_url( 'admin.php?page=' . WPML_TM_FOLDER . '/menu/translations-queue.php' . $filterParams ); $translation = PostTranslations::getInLanguage( $job->get_original_element_id(), $job->get_target_language() ); $element_id = $translation ? $translation->element_id : 0; return PreviewLink::getWithSpecifiedReturnUrl( $returnUrl, $element_id, $job->get_translate_job_id() ); } } API/REST/jobs/class-wpml-tm-rest-jobs-columns.php 0000755 00000002350 14720342453 0015473 0 ustar 00 <?php class WPML_TM_Rest_Jobs_Columns { /** * @return array */ public static function get_columns() { return array( 'id' => __( 'ID', 'wpml-translation-management' ), 'title' => __( 'Title', 'wpml-translation-management' ), 'languages' => __( 'Languages', 'wpml-translation-management' ), 'batch_name' => __( 'Batch name', 'wpml-translation-management' ), 'translator' => __( 'Translated by', 'wpml-translation-management' ), 'sent_date' => __( 'Sent on', 'wpml-translation-management' ), 'deadline' => __( 'Deadline', 'wpml-translation-management' ), 'status' => __( 'Status', 'wpml-translation-management' ), ); } /** * @return array */ public static function get_sortable() { return array( 'id' => __( 'ID', 'wpml-translation-management' ), 'title' => __( 'Title', 'wpml-translation-management' ), 'batch_name' => __( 'Batch name', 'wpml-translation-management' ), 'language' => __( 'Language', 'wpml-translation-management' ), 'sent_date' => __( 'Sent on', 'wpml-translation-management' ), 'deadline_date' => __( 'Deadline', 'wpml-translation-management' ), 'status' => __( 'Status', 'wpml-translation-management' ), ); } } API/REST/jobs/class-wpml-tm-rest-job-progress.php 0000755 00000002370 14720342453 0015476 0 ustar 00 <?php class WPML_TM_Rest_Job_Progress { /** @var wpdb */ private $wpdb; public function __construct() { global $wpdb; $this->wpdb = $wpdb; } /** * @param WPML_TM_Job_Entity $job * * @return string */ public function get( WPML_TM_Job_Entity $job ) { if ( $job->get_translation_service() !== 'local' ) { return ''; } if ( $job->get_status() !== ICL_TM_IN_PROGRESS ) { return ''; } if ( $job->get_type() === WPML_TM_Job_Entity::STRING_TYPE ) { return ''; } $sql = " SELECT field_finished FROM {$this->wpdb->prefix}icl_translate translate INNER JOIN {$this->wpdb->prefix}icl_translate_job translate_job ON translate_job.job_id = translate.job_id INNER JOIN {$this->wpdb->prefix}icl_translation_status translation_status ON translation_status.rid = translate_job.rid WHERE translation_status.rid = %d AND translate.field_translate = 1 AND LENGTH(translate.field_data) > 0 "; $sql = $this->wpdb->prepare( $sql, $job->get_id() ); $elements = $this->wpdb->get_col( $sql ); $translated = array_filter( $elements ); $percentage = (int) ( count( $translated ) / count( $elements ) * 100 ); return sprintf( _x( '%s completed', 'Translation jobs list', 'wpml-transation-manager' ), $percentage . '%' ); } } API/REST/jobs/class-wpml-tm-rest-jobs-element-info.php 0000755 00000007110 14720342453 0016374 0 ustar 00 <?php use WPML\FP\Obj; class WPML_TM_Rest_Jobs_Element_Info { /** @var WPML_TM_Rest_Jobs_Package_Helper_Factory */ private $package_helper_factory; /** @var array|null */ private $post_types; /** * @param WPML_TM_Rest_Jobs_Package_Helper_Factory $package_helper_factory */ public function __construct( WPML_TM_Rest_Jobs_Package_Helper_Factory $package_helper_factory ) { $this->package_helper_factory = $package_helper_factory; } /** * @param WPML_TM_Job_Entity $job * * @return array */ public function get( WPML_TM_Job_Entity $job ) { $type = $job->get_type(); $id = $job->get_original_element_id(); $result = []; switch ( $type ) { case WPML_TM_Job_Entity::POST_TYPE: /** @var WPML_TM_Post_Job_Entity $job */ $result = $this->get_for_post( $id, $job->get_element_id() ); break; case WPML_TM_Job_Entity::STRING_TYPE: case WPML_TM_Job_Entity::STRING_BATCH: $result = $this->get_for_title( $job->get_title() ); break; case WPML_TM_Job_Entity::PACKAGE_TYPE: $result = $this->get_for_package( $id ); break; } if ( empty( $result ) ) { $result = array( 'name' => '', 'url' => null, ); do_action( 'wpml_tm_jobs_log', 'WPML_TM_Rest_Jobs_Element_Info::get', array( $id, $type ), 'Empty result' ); } $result['url'] = apply_filters( 'wpml_tm_job_list_element_url', $result['url'], $id, $type ); if ( $job instanceof WPML_TM_Post_Job_Entity ) { $result['type'] = $this->get_type_info( $job ); } return $result; } /** * @param int $originalPostId * @param int $translatedPostId * * @return array */ private function get_for_post( $originalPostId, $translatedPostId ) { $result = array(); $post = get_post( $originalPostId ); if ( $post ) { $permalink = get_permalink( $post ); $result = [ 'name' => $post->post_title, 'url' => $permalink, 'status' => Obj::propOr( 'draft', 'post_status', get_post( $translatedPostId ) ), ]; } return $result; } /** * @param int $id * * @return array */ private function get_for_package( $id ) { $result = array(); $helper = $this->package_helper_factory->create(); if ( ! $helper ) { return array( 'name' => __( 'String package job', 'wpml-translation-management' ), 'url' => null, ); } $package = $helper->get_translatable_item( null, $id ); if ( $package ) { $result = array( 'name' => $package->title, 'url' => $package->edit_link, ); } return $result; } /** * @param string $title * * @return array */ private function get_for_title( $title ) { return [ 'name' => $title, 'url' => null, ]; } /** * @param WPML_TM_Post_Job_Entity $job * * @return array */ private function get_type_info( WPML_TM_Post_Job_Entity $job ) { $generalType = substr( $job->get_element_type(), 0, strpos( $job->get_element_type(), '_' ) ?: 0 ); switch ( $generalType ) { case 'post': case 'package': $specificType = substr( $job->get_element_type(), strlen( $generalType ) + 1 ); $label = Obj::pathOr( $job->get_element_type(), [ $specificType, 'labels', 'singular_name' ], $this->get_post_types() ); break; case 'st-batch': $label = __( 'Strings', 'wpml-translation-management' ); break; default: $label = $job->get_element_type(); } return [ 'value' => $job->get_element_type(), 'label' => $label, ]; } private function get_post_types() { if ( $this->post_types === null ) { $this->post_types = \WPML\API\PostTypes::getTranslatableWithInfo(); } return $this->post_types; } } API/REST/class-wpml-tm-rest-batch-sync-factory.php 0000755 00000000503 14720342453 0015621 0 ustar 00 <?php class WPML_TM_REST_Batch_Sync_Factory extends WPML_REST_Factory_Loader { /** * @return WPML_TM_REST_Batch_Sync */ public function create() { return new WPML_TM_REST_Batch_Sync( new WPML_TP_Batch_Sync_API( wpml_tm_get_tp_api_client(), wpml_tm_get_tp_project(), new WPML_TM_Log() ) ); } } API/REST/class-wpml-tm-rest-jobs-factory.php 0000755 00000001455 14720342453 0014532 0 ustar 00 <?php use WPML\TM\Jobs\Utils\ElementLinkFactory; class WPML_TM_REST_Jobs_Factory extends WPML_REST_Factory_Loader { /** * @return WPML_TM_REST_Jobs */ public function create() { global $sitepress, $wpdb; return new WPML_TM_REST_Jobs( wpml_tm_get_jobs_repository( true, false, true ), new WPML_TM_Rest_Jobs_Criteria_Parser(), new WPML_TM_Rest_Jobs_View_Model( new WPML_TM_Rest_Jobs_Translation_Service(), new WPML_TM_Rest_Jobs_Element_Info( new WPML_TM_Rest_Jobs_Package_Helper_Factory() ), new WPML_TM_Rest_Jobs_Language_Names( $sitepress ), new WPML_TM_Rest_Job_Translator_Name(), new WPML_TM_Rest_Job_Progress(), ElementLinkFactory::create() ), new WPML_TP_Sync_Update_Job( $wpdb, $sitepress ), new WPML_TM_Last_Picked_Up( $sitepress ) ); } } API/REST/CustomXMLConfig/Factory.php 0000755 00000000356 14720342453 0013046 0 ustar 00 <?php namespace WPML\REST\XMLConfig\Custom; class Factory extends \WPML_REST_Factory_Loader { public function create() { return new Actions( new \WPML_Custom_XML(), new \WPML_XML_Config_Validate( \WPML_Config::PATH_TO_XSD ) ); } } API/REST/CustomXMLConfig/Actions.php 0000755 00000003465 14720342453 0013043 0 ustar 00 <?php namespace WPML\REST\XMLConfig\Custom; use WP_REST_Request; class Actions extends \WPML_REST_Base { /** @var array<string> */ private $capabilities = [ 'manage_options' ]; /** * @var \WPML_Custom_XML */ private $custom_xml; /** * @var \WPML_XML_Config_Validate */ private $validate; public function __construct( \WPML_Custom_XML $custom_xml, \WPML_XML_Config_Validate $validate ) { parent::__construct(); $this->custom_xml = $custom_xml; $this->validate = $validate; } function add_hooks() { $this->register_routes(); } function register_routes() { parent::register_route( '/custom-xml-config', [ 'methods' => 'GET', 'callback' => [ $this, 'read_content' ], ] ); parent::register_route( '/custom-xml-config', [ 'methods' => 'POST', 'callback' => [ $this, 'update_content' ], ] ); parent::register_route( '/custom-xml-config/validate', [ 'methods' => 'POST', 'callback' => [ $this, 'validate_content' ], ] ); } /** * REST * * @param \WP_REST_Request $request * * @return string */ public function update_content( WP_REST_Request $request ) { $content = $request->get_param( 'content' ); $this->custom_xml->set( $content, false ); \WPML_Config::load_config_run(); return $this->custom_xml->get(); } /** * REST * * @param \WP_REST_Request $request * * @return \LibXMLError[] */ public function validate_content( WP_REST_Request $request ) { $content = $request->get_param( 'content' ); if ( ! $this->validate->from_string( $content ) ) { return $this->validate->get_errors(); } return []; } /** * REST */ public function read_content() { return $this->custom_xml->get(); } function get_allowed_capabilities( WP_REST_Request $request ) { return $this->capabilities; } } API/REST/class-wpml-rest-extend-args-factory.php 0000755 00000000424 14720342453 0015373 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_REST_Extend_Args_Factory implements IWPML_REST_Action_Loader { /** * @return IWPML_Action|IWPML_Action[]|null */ public function create() { global $sitepress; return new WPML_REST_Extend_Args( $sitepress ); } } API/Hooks/class-wpml-api-hook-permalink.php 0000755 00000002357 14720342453 0014536 0 ustar 00 <?php class WPML_API_Hook_Permalink implements IWPML_Action { /** @var WPML_URL_Converter $url_converter */ private $url_converter; /** @var IWPML_Resolve_Object_Url $absolute_resolver */ private $absolute_resolver; public function __construct( WPML_URL_Converter $url_converter, IWPML_Resolve_Object_Url $absolute_resolver ) { $this->url_converter = $url_converter; $this->absolute_resolver = $absolute_resolver; } public function add_hooks() { add_filter( 'wpml_permalink', array( $this, 'wpml_permalink_filter' ), 10, 3 ); } /** * @param string $url * @param null|string $lang * @param bool $absolute_url If `true`, WPML will try to resolve the object behind the URL * and try to find the matching translation's URL. * WARNING: This is a heavy process which could lead to performance hit. * * @return string */ public function wpml_permalink_filter( $url, $lang = null, $absolute_url = false ) { if ( $absolute_url ) { $new_url = $this->absolute_resolver->resolve_object_url( $url, $lang ); if ( $new_url ) { $url = $new_url; } } else { $url = $this->url_converter->convert_url( $url, $lang ); } return $url; } } API/Hooks/class-wpml-api-hook-translation-element.php 0000755 00000003637 14720342453 0016543 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_API_Hook_Translation_Element implements IWPML_Action { private $flags_factory; private $sitepress; private $translation_element_factory; /** * WPML_API_Hook_Post constructor. * * @param SitePress $sitepress * @param WPML_Translation_Element_Factory $translation_element_factory * @param WPML_Flags_Factory $flags_factory */ public function __construct( SitePress $sitepress, WPML_Translation_Element_Factory $translation_element_factory, WPML_Flags_Factory $flags_factory ) { $this->sitepress = $sitepress; $this->translation_element_factory = $translation_element_factory; $this->flags_factory = $flags_factory; } public function add_hooks() { /** * Use this filter to obtain the language flag URL of a given post * * @param string $default * @param int $element_id * @param string $element_type any of `WPML_Translation_Element_Factory::ELEMENT_TYPE_POST`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_TERM`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_MENU` */ add_filter( 'wpml_post_language_flag_url', array( $this, 'get_post_language_flag_url' ), 10, 3 ); } /** * @param string $default * @param int $element_id * @param string $element_type any of `WPML_Translation_Element_Factory::ELEMENT_TYPE_POST`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_TERM`, `WPML_Translation_Element_Factory::ELEMENT_TYPE_MENU` * * @return string */ public function get_post_language_flag_url( $default, $element_id, $element_type = WPML_Translation_Element_Factory::ELEMENT_TYPE_POST ) { if ( ! $element_id ) { return $default; } $wpml_post = $this->translation_element_factory->create( $element_id, $element_type ); $flag = $this->flags_factory->create(); return $flag->get_flag_url( $wpml_post->get_language_code() ); } } API/Hooks/class-wpml-tm-api-hook-links.php 0000755 00000001430 14720342453 0014301 0 ustar 00 <?php /** * Class WPML_TM_API_Hook_Links * * This class provides various links by hooks */ class WPML_TM_API_Hook_Links implements IWPML_Action { public function add_hooks() { // TODO: Use WPML_API_Hook_Links::POST_TRANSLATION_SETTINGS_PRIORITY + 1 instead of the hardcoded 11. // It's done this way right now so there's no potential for an error if TM is updated before Core for // the minor 3.9.1 release add_filter( 'wpml_get_post_translation_settings_link', array( $this, 'get_post_translation_settings_link', ), 11, 1 ); } public function get_post_translation_settings_link( $link ) { return admin_url( 'admin.php?page=' . WPML_TM_FOLDER . WPML_Translation_Management::PAGE_SLUG_SETTINGS . '&sm=mcsetup#icl_custom_posts_sync_options' ); } } API/Hooks/class-wpml-api-hook-translation-mode.php 0000755 00000002614 14720342453 0016030 0 ustar 00 <?php class WPML_API_Hook_Translation_Mode implements IWPML_Action { const OPTION_KEY = 'custom_posts_sync_option'; /** Allowed modes */ const DO_NOT_TRANSLATE = 'do_not_translate'; const TRANSLATE = 'translate'; const DISPLAY_AS_TRANSLATED = 'display_as_translated'; /** @var WPML_Settings_Helper $settings */ private $settings; public function __construct( WPML_Settings_Helper $settings ) { $this->settings = $settings; } public function add_hooks() { if ( is_admin() ) { add_action( 'wpml_set_translation_mode_for_post_type', array( $this, 'set_mode_for_post_type' ), 10, 2 ); } } /** * @param string $post_type * @param string $translation_mode any of * `WPML_API_Hook_Translation_Mode::DO_NOT_TRANSLATE`, * `WPML_API_Hook_Translation_Mode::TRANSLATE`, * `WPML_API_Hook_Translation_Mode::DISPLAY_AS_TRANSLATED` */ public function set_mode_for_post_type( $post_type, $translation_mode ) { switch ( $translation_mode ) { case self::DO_NOT_TRANSLATE: $this->settings->set_post_type_not_translatable( $post_type ); break; case self::TRANSLATE: $this->settings->set_post_type_translatable( $post_type ); break; case self::DISPLAY_AS_TRANSLATED: $this->settings->set_post_type_display_as_translated( $post_type ); break; } } } API/Hooks/class-wpml-api-hook-sync-custom-fields.php 0000755 00000001430 14720342453 0016273 0 ustar 00 <?php class WPML_API_Hook_Sync_Custom_Fields implements IWPML_Action { /** @var WPML_Sync_Custom_Fields $sync_custom_fields */ private $sync_custom_fields; public function __construct( WPML_Sync_Custom_Fields $sync_custom_fields ) { $this->sync_custom_fields = $sync_custom_fields; } public function add_hooks() { add_action( 'wpml_sync_custom_field', array( $this, 'sync_custom_field' ), 10, 2 ); add_action( 'wpml_sync_all_custom_fields', array( $this, 'sync_all_custom_fields' ), 10, 1 ); } public function sync_custom_field( $post_id, $custom_field_name ) { $this->sync_custom_fields->sync_to_translations( $post_id, $custom_field_name ); } public function sync_all_custom_fields( $post_id ) { $this->sync_custom_fields->sync_all_custom_fields( $post_id ); } } API/Hooks/class-wpml-api-hooks-factory.php 0000755 00000002350 14720342453 0014377 0 ustar 00 <?php class WPML_API_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { public function create() { global $wpdb, $sitepress, $wpml_post_translations, $wpml_url_converter; $hooks = array(); $hooks[] = new WPML_API_Hook_Sync_Custom_Fields( new WPML_Sync_Custom_Fields( new WPML_Translation_Element_Factory( $sitepress ), $sitepress->get_custom_fields_translation_settings( WPML_COPY_CUSTOM_FIELD ) ) ); $hooks[] = new WPML_API_Hook_Links( new WPML_Post_Status_Display_Factory( $sitepress ) ); $hooks[] = new WPML_API_Hook_Translation_Element( $sitepress, new WPML_Translation_Element_Factory( $sitepress ), new WPML_Flags_Factory( $wpdb ) ); $hooks[] = new WPML_API_Hook_Translation_Mode( new WPML_Settings_Helper( $wpml_post_translations, $sitepress ) ); $hooks[] = new WPML_API_Hook_Copy_Post_To_Language( new WPML_Post_Duplication( $wpdb, $sitepress ) ); $url_resolver_factory = new WPML_Resolve_Object_Url_Helper_Factory(); $absolute_resolver = $url_resolver_factory->create( WPML_Resolve_Object_Url_Helper_Factory::ABSOLUTE_URL_RESOLVER ); $hooks[] = new WPML_API_Hook_Permalink( $wpml_url_converter, $absolute_resolver ); return $hooks; } } API/Hooks/class-wpml-api-hook-links.php 0000755 00000002742 14720342453 0013672 0 ustar 00 <?php /** * Class WPML_API_Hook_Links * * This class provides various links by hooks */ class WPML_API_Hook_Links implements IWPML_Action { const POST_TRANSLATION_SETTINGS_PRIORITY = 10; const LINK_TO_TRANSLATION_PRIORITY = 9; /** @var WPML_Post_Status_Display_Factory */ private $post_status_display_factory; public function __construct( WPML_Post_Status_Display_Factory $post_status_display_factory ) { $this->post_status_display_factory = $post_status_display_factory; } public function add_hooks() { add_filter( 'wpml_get_post_translation_settings_link', array( $this, 'get_post_translation_settings_link', ), self::POST_TRANSLATION_SETTINGS_PRIORITY, 1 ); add_filter( 'wpml_get_link_to_edit_translation', array( $this, 'get_link_to_edit_translation', ), self::LINK_TO_TRANSLATION_PRIORITY, 3 ); } public function get_post_translation_settings_link( $link ) { return admin_url( 'admin.php?page=' . WPML_PLUGIN_FOLDER . '/menu/translation-options.php#icl_custom_posts_sync_options' ); } public function get_link_to_edit_translation( $link, $post_id, $lang ) { $status_display = $this->post_status_display_factory->create(); $status_data = $status_display->get_status_data( $post_id, $lang ); $status_link = $status_data[1]; $trid = $status_data[2]; $css_class = $status_data[3]; return apply_filters( 'wpml_link_to_translation', $status_link, $post_id, $lang, $trid, $css_class ); } } API/Hooks/class-wpml-tm-api-hooks-factory.php 0000755 00000000342 14720342453 0015014 0 ustar 00 <?php class WPML_TM_API_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { public function create() { $hooks = array(); $hooks[] = new WPML_TM_API_Hook_Links(); return $hooks; } } API/Hooks/class-wpml-api-hook-copy-post-to-language.php 0000755 00000001425 14720342453 0016705 0 ustar 00 <?php /** * Class WPML_API_Hook_Copy_Post_To_Language */ class WPML_API_Hook_Copy_Post_To_Language implements IWPML_Action { /** @var WPML_Post_Duplication $post_duplication */ private $post_duplication; public function __construct( WPML_Post_Duplication $post_duplication ) { $this->post_duplication = $post_duplication; } public function add_hooks() { add_filter( 'wpml_copy_post_to_language', array( $this, 'copy_post_to_language' ), 10, 3 ); } public function copy_post_to_language( $post_id, $target_language, $mark_as_duplicate ) { $duplicate_post_id = $this->post_duplication->make_duplicate( $post_id, $target_language ); if( ! $mark_as_duplicate ) { delete_post_meta( $duplicate_post_id, '_icl_lang_duplicate_of' ); } return $duplicate_post_id; } } user/wpml-user-jobs-notification-settings-render.php 0000755 00000001751 14720342453 0016733 0 ustar 00 <?php class WPML_User_Jobs_Notification_Settings_Render { private $section_template; /** * WPML_User_Jobs_Notification_Settings_Render constructor. * * @param WPML_User_Jobs_Notification_Settings_Template|null $notification_settings_template */ public function __construct( WPML_User_Jobs_Notification_Settings_Template $notification_settings_template ) { $this->section_template = $notification_settings_template; } public function add_hooks() { add_action( 'wpml_user_profile_options', array( $this, 'render_options' ) ); } /** * @param int $user_id */ public function render_options( $user_id ) { $field_checked = checked( true, WPML_User_Jobs_Notification_Settings::is_new_job_notification_enabled( $user_id ), false ); echo $this->get_notification_template()->get_setting_section( $field_checked ); } /** * @return null|WPML_User_Jobs_Notification_Settings_Template */ private function get_notification_template() { return $this->section_template; } } user/Hooks.php 0000755 00000001566 14720342453 0007335 0 ustar 00 <?php namespace WPML\TM\User; use WPML\LIB\WP\User; class Hooks implements \IWPML_Backend_Action { public function add_hooks() { add_action( 'clean_user_cache', [ $this, 'cleanUserCacheAction' ] ); add_action( 'updated_user_meta', [ $this, 'updatedUserMetaAction' ] ); add_filter( 'wpml_show_hidden_languages_options', [ $this, 'filter_show_hidden_languages_options' ] ); } public function cleanUserCacheAction() { $this->flushCache(); } public function updatedUserMetaAction() { $this->flushCache(); } public function filter_show_hidden_languages_options( $show_hidden_languages_options ) { if ( User::canManageTranslations() || User::hasCap(User::CAP_TRANSLATE) ) { return true; } return $show_hidden_languages_options; } private function flushCache() { wpml_get_cache( \WPML_Translation_Roles_Records::CACHE_GROUP )->flush_group_cache(); } } user/class-wpml-only-i-language-pairs.php 0000755 00000001443 14720342453 0014430 0 ustar 00 <?php class WPML_TM_Only_I_Language_Pairs implements IWPML_AJAX_Action, IWPML_DIC_Action { /** @var WPML_Language_Pair_Records $language_pair_records */ private $language_pair_records; public function __construct( WPML_Language_Pair_Records $language_pair_records ) { $this->language_pair_records = $language_pair_records; } public function add_hooks() { add_action( 'wpml_update_active_languages', array( $this, 'update_language_pairs' ) ); } public function update_language_pairs() { $users = get_users( [ 'meta_key' => WPML_TM_Wizard_Options::ONLY_I_USER_META, 'meta_value' => '1', ] ); $all_language_pairs = WPML_All_Language_Pairs::get(); foreach ( $users as $user ) { $this->language_pair_records->store( $user->ID, $all_language_pairs ); } } } user/class-wpml-translation-roles-records.php 0000755 00000020111 14720342453 0015434 0 ustar 00 <?php use WPML\LIB\WP\Cache; abstract class WPML_Translation_Roles_Records { const USERS_WITH_CAPABILITY = 'LIKE'; const USERS_WITHOUT_CAPABILITY = 'NOT LIKE'; const MIN_SEARCH_LENGTH = 3; const CACHE_GROUP = __CLASS__; const CACHE_PREFIX = 'wpml-cache-translators-'; const CACHE_KEY_KEYS = 'keys'; /** @var wpdb */ protected $wpdb; /** @var WPML_WP_User_Query_Factory */ private $user_query_factory; /** @var \WP_Roles */ protected $wp_roles; /** * WPML_Translation_Roles_Records constructor. * * @param \wpdb $wpdb * @param \WPML_WP_User_Query_Factory $user_query_factory * @param \WP_Roles $wp_roles */ public function __construct( wpdb $wpdb, WPML_WP_User_Query_Factory $user_query_factory, WP_Roles $wp_roles ) { $this->wpdb = $wpdb; $this->user_query_factory = $user_query_factory; $this->wp_roles = $wp_roles; add_action( 'wpml_update_translator', [ $this, 'delete_cache' ], -10 ); } public function has_users_with_capability() { $sql = " SELECT EXISTS( SELECT user_id FROM {$this->wpdb->usermeta} WHERE meta_key = '{$this->wpdb->prefix}capabilities' AND meta_value LIKE %s ) "; $sql = $this->wpdb->prepare( $sql, '%' . $this->get_capability() . '%' ); return (bool) $this->wpdb->get_var( $sql ); } /** * @return array */ public function get_users_with_capability() { return $this->get_records( self::USERS_WITH_CAPABILITY ); } /** * @return int */ public function get_number_of_users_with_capability() { return count( $this->get_users_with_capability() ); } /** * @param string $search * @param int $limit * * @return array */ public function search_for_users_without_capability( $search = '', $limit = -1 ) { return $this->get_records( self::USERS_WITHOUT_CAPABILITY, $search, $limit ); } /** * @param int $user_id * * @return bool */ public function does_user_have_capability( $user_id ) { $fn = Cache::memorize( self::CACHE_GROUP . '_does_user_have_capability', 3600, function ( $user_id ) { return $this->fetch_user_capability( $user_id ); } ); return $fn( $user_id ); } /** * Delete records for all users */ public function delete_all() { $users = $this->get_users_with_capability(); foreach ( $users as $user ) { $this->delete( $user->ID ); } } /** * Delete the record for the user * * @param int $user_id */ public function delete( $user_id ) { $user = new WP_User( $user_id ); $user->remove_cap( $this->get_capability() ); $this->delete_cache(); } public function delete_cache() { $translators_keys = get_option( self::CACHE_PREFIX . self::CACHE_KEY_KEYS ); if ( ! $translators_keys ) { return; } foreach ( $translators_keys as $cache_key ) { delete_option( self::CACHE_PREFIX . $cache_key ); } delete_option( self::CACHE_PREFIX . self::CACHE_KEY_KEYS ); } private function set_cache( $key, $translators ) { // Cache the results as option. // Not using transient here to avoid having WordPress delete the keys // entry without us being able to control it and it should only be // deleted if all registered keys are deleted before. $translators_keys = get_option( self::CACHE_PREFIX . self::CACHE_KEY_KEYS ); $translators_keys = $translators_keys ?: []; $translators_keys[] = $key; update_option( self::CACHE_PREFIX . self::CACHE_KEY_KEYS, $translators_keys, false ); update_option( self::CACHE_PREFIX . $key, $translators, false ); } /** * @param string $compare * @param string $search * @param int $limit * * @return array */ private function get_records( $compare, $search = '', $limit = -1 ) { $search = trim( $search ); $cache_key = md5( (string) wp_json_encode( [ get_class( $this ), $compare, $search, $limit ] ) ); $translators = get_option( self::CACHE_PREFIX . $cache_key ); if ( is_array( $translators ) ) { return $translators; } $preparedUserQuery = $this->wpdb->prepare( "SELECT u.id FROM {$this->wpdb->users} u INNER JOIN {$this->wpdb->usermeta} c ON c.user_id=u.ID AND CAST(c.meta_key AS BINARY)=%s AND c.meta_value {$compare} %s", "{$this->wpdb->prefix}capabilities", "%" . $this->get_capability() . "%" ); if ( self::USERS_WITHOUT_CAPABILITY === $compare ) { $required_wp_roles = $this->get_required_wp_roles(); foreach( $required_wp_roles as $required_wp_role ) { $preparedUserQuery .= $this->wpdb->prepare( " AND c.meta_value LIKE %s", "%{$required_wp_role}%" ); } } if ( $search ) { $preparedUserQuery .= $this->wpdb->prepare( " AND (u.user_login LIKE %s OR u.user_nicename LIKE %s OR u.user_email LIKE %s)", "%{$search}%", "%{$search}%", "%{$search}%" ); } $preparedUserQuery .= ' ORDER BY user_login ASC'; if ( $limit > 0 ) { $preparedUserQuery .= $this->wpdb->prepare(" LIMIT 0,%d", $limit ); } $users = $this->wpdb->get_col( $preparedUserQuery ); if ( $search && strlen( $search ) > self::MIN_SEARCH_LENGTH && ( $limit <= 0 || count( $users ) < $limit ) ) { $users_from_metas = $this->get_records_from_users_metas( $compare, $search, $limit ); $users_with_dupes = array_merge( $users, $users_from_metas ); $users = wpml_array_unique( $users_with_dupes, SORT_REGULAR ); } $translators = array(); foreach ( $users as $user_id ) { $user_data = get_userdata( $user_id ); if ( $user_data ) { $language_pair_records = new WPML_Language_Pair_Records( $this->wpdb, new WPML_Language_Records( $this->wpdb ) ); $language_pairs = $language_pair_records->get( $user_id ); $translators[] = (object) array( 'ID' => $user_data->ID, 'full_name' => trim( $user_data->first_name . ' ' . $user_data->last_name ), 'user_login' => $user_data->user_login, 'user_email' => $user_data->user_email, 'display_name' => $user_data->display_name, 'language_pairs' => $language_pairs, 'roles' => $user_data->roles, ); } } $this->set_cache( $cache_key, $translators ); return $translators; } /** * @param string $compare * @param string $search * @param int $limit * * @return array */ private function get_records_from_users_metas( $compare, $search, $limit = -1 ) { $search = trim( $search ); if ( ! $search ) { return array(); } $sanitized_search = preg_replace( '!\s+!', ' ', $search ); $words = explode( ' ', $sanitized_search ); if ( ! $words ) { return array(); } $search_by_names = array( 'relation' => 'OR' ); foreach ( $words as $word ) { $search_by_names[] = array( 'key' => 'first_name', 'value' => $word, 'compare' => 'LIKE', ); $search_by_names[] = array( 'key' => 'last_name', 'value' => $word, 'compare' => 'LIKE', ); $search_by_names[] = array( 'key' => 'last_name', 'value' => $word, 'compare' => 'LIKE', ); } $query_args = array( 'fields' => 'ID', 'meta_query' => array( 'relation' => 'AND', array( 'key' => "{$this->wpdb->prefix}capabilities", 'value' => $this->get_capability(), 'compare' => $compare, ), $search_by_names, ), 'number' => $limit, ); if ( 'NOT LIKE' === $compare ) { $required_wp_roles = $this->get_required_wp_roles(); if ( $required_wp_roles ) { $query_args['role__in'] = $required_wp_roles; } } $user_query = $this->user_query_factory->create( $query_args ); return $user_query->get_results(); } /** * Fetches the capability for the user from DB * * @param int $user_id */ private function fetch_user_capability( $user_id ) { $sql = " SELECT user_id FROM {$this->wpdb->usermeta} WHERE user_id = %d AND meta_key = %s AND meta_value LIKE %s LIMIT 1 "; $sql = $this->wpdb->prepare( $sql, $user_id, $this->wpdb->prefix . 'capabilities', '%' . $this->get_capability() . '%' ); return (bool) $this->wpdb->get_var( $sql ); } /** * @return string */ abstract protected function get_capability(); /** * @return array */ abstract protected function get_required_wp_roles(); } user/LanguagePairs/ILanguagePairs.php 0000755 00000000612 14720342453 0013616 0 ustar 00 <?php namespace WPML\User\LanguagePairs; interface ILanguagePairs { /** * Language pairs are returned in an array of the form * array( $from_lang => array( $to_lang_1, $to_lang_2 ) * * For example: * array( * 'en' => array( 'de', 'fr' ), * 'fr' => array( 'en' ), * ) * * @param int $userId * * @return array{string:string[]} */ public function get( $userId ); } user/UsersByCapsRepository.php 0000755 00000004317 14720342453 0012552 0 ustar 00 <?php namespace WPML\User; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\User\LanguagePairs\ILanguagePairs; use function WPML\FP\pipe; class UsersByCapsRepository { /** @var wpdb */ private $wpdb; /** @var ILanguagePairs */ private $languagePairs; public function __construct( \wpdb $wpdb, ILanguagePairs $languagePairs ) { $this->wpdb = $wpdb; $this->languagePairs = $languagePairs; } /** * @param string[] $ownedCaps * @param string[] $excludeCaps * * @return array{ * "ID": string, * "full_name": string, * "user_login": string, * "user_email": string, * "display_name": string, * "roles": string[], * "language_pairs": array{string:string[]} * }[] */ public function get( array $ownedCaps, array $excludeCaps = [] ) { $sql = " SELECT user_id FROM {$this->wpdb->usermeta} WHERE {$this->buildWhereCondition( $ownedCaps, $excludeCaps)} "; $userIds = $this->wpdb->get_col( $sql ); $buildUsers = pipe( Fns::map( 'get_userdata' ), Fns::filter( Fns::identity() ), Fns::map( function ( \WP_User $userData ) { return (object) [ 'ID' => $userData->ID, 'full_name' => trim( $userData->first_name . ' ' . $userData->last_name ), 'user_login' => $userData->user_login, 'user_email' => $userData->user_email, 'display_name' => $userData->display_name, 'roles' => $userData->roles ]; } ), Fns::map( function ( $user ) { $user->language_pairs = $this->languagePairs->get( $user->ID ); return $user; } ) ); return $buildUsers( $userIds ); } private function buildWhereCondition( array $ownedCaps, array $excludeCaps ) { $ownedCapsCondition = Lst::join( ' OR ', Fns::map( function ( $cap ) { return $this->wpdb->prepare('meta_value LIKE %s', '%' . $cap . '%' ); }, $ownedCaps ) ); if ( $excludeCaps ) { $excludeCapsCondition = ' AND ( ' . Lst::join( ' AND ', Fns::map( function ( $cap ) { return $this->wpdb->prepare('meta_value NOT LIKE %s', '%' . $cap . '%' ); }, $excludeCaps ) ) . ' )'; } else { $excludeCapsCondition = ''; } return "meta_key = '{$this->wpdb->prefix}capabilities' AND ( $ownedCapsCondition ) $excludeCapsCondition"; } } user/wpml-user-jobs-notification-settings-template.php 0000755 00000002571 14720342453 0017270 0 ustar 00 <?php /** * Class WPML_User_Jobs_Notification_Settings_Template */ class WPML_User_Jobs_Notification_Settings_Template { const TEMPLATE_FILE = 'job-email-notification.twig'; /** * @var WPML_Twig_Template */ private $template_service; /** * WPML_User_Jobs_Notification_Settings_Template constructor. * * @param IWPML_Template_Service $template_service */ public function __construct( IWPML_Template_Service $template_service ) { $this->template_service = $template_service; } /** * @param string $notification_input * * @return string */ public function get_setting_section( $notification_input ) { $model = $this->get_model( $notification_input ); return $this->template_service->show( $model, self::TEMPLATE_FILE ); } /** * @param string $notification_input * * @return array */ private function get_model( $notification_input ) { $model = array( 'strings' => array( 'section_title' => __( 'WPML Translator Settings', 'wpml-translation-management' ), 'field_title' => __( 'Notification emails:', 'wpml-translation-management' ), 'field_name' => WPML_User_Jobs_Notification_Settings::BLOCK_NEW_NOTIFICATION_FIELD, 'field_text' => __( 'Send me a notification email when there is something new to translate', 'wpml-translation-management' ), 'checked' => $notification_input, ), ); return $model; } } user/wpml-user-jobs-notification-settings.php 0000755 00000001643 14720342453 0015456 0 ustar 00 <?php /** * Class WPML_Jobs_Notification_Settings */ class WPML_User_Jobs_Notification_Settings { const BLOCK_NEW_NOTIFICATION_FIELD = 'wpml_block_new_email_notifications'; public function add_hooks() { add_action( 'personal_options_update', array( $this, 'save_new_job_notifications_setting' ) ); add_action( 'edit_user_profile_update', array( $this, 'save_new_job_notifications_setting' ) ); } /** * @param int $user_id */ public function save_new_job_notifications_setting( $user_id ) { $val = 1; if ( array_key_exists( self::BLOCK_NEW_NOTIFICATION_FIELD, $_POST ) ) { $val = filter_var( $_POST[ self::BLOCK_NEW_NOTIFICATION_FIELD ], FILTER_SANITIZE_NUMBER_INT ); } update_user_meta( $user_id, self::BLOCK_NEW_NOTIFICATION_FIELD, $val ); } public static function is_new_job_notification_enabled( $user_id ) { return ! get_user_meta( $user_id, self::BLOCK_NEW_NOTIFICATION_FIELD, true ); } } user/class-wpml-translator-records.php 0000755 00000002756 14720342453 0014164 0 ustar 00 <?php class WPML_Translator_Records extends WPML_Translation_Roles_Records { /** * @return string */ protected function get_capability() { return \WPML\LIB\WP\User::CAP_TRANSLATE; } /** * @return array */ protected function get_required_wp_roles() { return array(); } /** * @param string $source_language * @param array $target_languages * @param bool $require_all_languages - Translator must have all target languages if true otherwise they need at least one. * * @return array */ public function get_users_with_languages( $source_language, $target_languages, $require_all_languages = true ) { $translators = $this->get_users_with_capability(); $language_records = new WPML_Language_Records( $this->wpdb ); $language_pairs_records = new WPML_Language_Pair_Records( $this->wpdb, $language_records ); $translators_with_langs = array(); foreach ( $translators as $translator ) { $language_pairs_for_user = $language_pairs_records->get( $translator->ID ); if ( isset( $language_pairs_for_user[ $source_language ] ) ) { $lang_count = 0; foreach ( $target_languages as $target_language ) { $lang_count += in_array( $target_language, $language_pairs_for_user[ $source_language ], true ) ? 1 : 0; } if ( $require_all_languages && $lang_count === count( $target_languages ) || ! $require_all_languages && $lang_count > 0 ) { $translators_with_langs[] = $translator; } } } return $translators_with_langs; } } user/class-wpml-translation-manager-records.php 0000755 00000001444 14720342453 0015732 0 ustar 00 <?php use WPML\FP\Relation; class WPML_Translation_Manager_Records extends WPML_Translation_Roles_Records { /** * @return string */ protected function get_capability() { return \WPML\LIB\WP\User::CAP_MANAGE_TRANSLATIONS; } /** * @return array */ protected function get_required_wp_roles() { return wpml_collect( $this->wp_roles->role_objects ) ->filter( [ $this, 'is_required_role' ] ) ->keys() ->reject( Relation::equals( 'administrator' ) ) // Admins always have Translation Manager caps. ->all(); } /** * Determine if the role can be used for a manager. * * @param \WP_Role $role The role definition. * * @return bool */ public function is_required_role( WP_Role $role ) { return array_key_exists( 'edit_private_posts', $role->capabilities ); } } canonicals/class-wpml-canonicals.php 0000755 00000013720 14720342453 0013573 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Canonicals { const CANONICAL_FOR_DUPLICATED_POST = 'duplicate'; const CANONICAL_FOR_NON_TRANSLATABLE_POST = 'non-translatable'; /** @var SitePress */ private $sitepress; /** @var WPML_Translations */ private $wpml_translations; /** @var WPML_Translation_Element_Factory */ private $translation_element_factory; /** * WPML_Canonicals constructor. * * @param SitePress $sitepress * @param WPML_Translation_Element_Factory $translation_element_factory * @param WPML_Translations $wpml_translations */ public function __construct( SitePress $sitepress, WPML_Translation_Element_Factory $translation_element_factory, WPML_Translations $wpml_translations = null ) { $this->sitepress = $sitepress; $this->translation_element_factory = $translation_element_factory; $this->wpml_translations = $wpml_translations; } /** * @param int $post_id * * @return bool|string * @throws \InvalidArgumentException */ private function must_filter_permalink( $post_id ) { $this->init_wpml_translations(); $post_element = $this->translation_element_factory->create( $post_id, 'post' ); $must_handle_canonicals = $this->must_handle_a_canonical_url(); if ( $post_element->is_translatable() ) { if ( $must_handle_canonicals && $this->wpml_translations->is_a_duplicate_of( $post_element ) && $this->is_permalink_filter_from_rel_canonical() ) { return self::CANONICAL_FOR_DUPLICATED_POST; } } elseif ( $must_handle_canonicals ) { return self::CANONICAL_FOR_NON_TRANSLATABLE_POST; } return false; } /** * @param string $link * @param int $post_id * * @return null|string * @throws \InvalidArgumentException */ public function permalink_filter( $link, $post_id ) { switch ( $this->must_filter_permalink( $post_id ) ) { case self::CANONICAL_FOR_DUPLICATED_POST: $post_element = $this->translation_element_factory->create( $post_id, 'post' ); return $this->get_canonical_of_duplicate( $post_element ); case self::CANONICAL_FOR_NON_TRANSLATABLE_POST: return $this->get_url_in_default_language_if_rel_canonical( $link ); default: return null; } } /** * @param string $canonical_url * @param WP_Post $post * * @return string|bool */ public function get_canonical_url( $canonical_url, $post, $request_language ) { if ( $post && $this->sitepress->get_wp_api()->is_front_end() ) { try { /** @var WPML_Post_Element $post_element */ $post_element = $this->translation_element_factory->create( $post->ID, 'post' ); $should_translate_canonical_url = apply_filters( 'wpml_must_translate_canonical_url', true, $post_element ); if ( ! $should_translate_canonical_url ) { return $canonical_url; } if ( ! $post_element->is_translatable() ) { global $wpml_url_filters; $wpml_url_filters->remove_global_hooks(); $canonical_url = $this->sitepress->convert_url_string( $canonical_url, $this->sitepress->get_default_language() ); $wpml_url_filters->add_global_hooks(); } else { $this->init_wpml_translations(); if ( $this->wpml_translations->is_a_duplicate_of( $post_element ) ) { $canonical_url = (string) $this->get_canonical_of_duplicate( $post_element ); } elseif ( $post_element->get_language_code() != $request_language ) { $canonical_url = $this->sitepress->convert_url_string( $canonical_url, $post_element->get_language_code() ); } } } catch ( InvalidArgumentException $e ) { } } return $canonical_url; } /** * @param string $url * * @return string */ public function get_general_canonical_url( $url ) { global $wpml_url_filters; $wpml_url_filters->remove_global_hooks(); $canonical_url = $this->sitepress->convert_url_string( $url, $this->sitepress->get_current_language() ); $wpml_url_filters->add_global_hooks(); return $canonical_url; } private function has_wp_get_canonical_url() { return $this->sitepress->get_wp_api()->function_exists( 'wp_get_canonical_url' ); } /** * @return bool */ private function is_permalink_filter_from_rel_canonical() { $back_trace_stack = $this->sitepress->get_wp_api()->get_backtrace( 20 ); $keywords = array( 'rel_canonical', 'canonical', 'generate_canonical' ); $result = false; if ( $back_trace_stack ) { foreach ( $back_trace_stack as $key => $value ) { foreach ( $keywords as $keyword ) { if ( 'function' === $key && $keyword === $value ) { $result = true; break; } } } } return $result; } /** * @param string $link * * @return bool|string */ private function get_url_in_default_language_if_rel_canonical( $link ) { if ( $this->is_permalink_filter_from_rel_canonical() ) { $default_language = $this->sitepress->get_default_language(); $link = (string) $this->sitepress->convert_url( $link, $default_language ); } return $link; } /** * @param WPML_Translation_Element $post_element * * @return false|string */ private function get_canonical_of_duplicate( $post_element ) { $source_element = $post_element->get_source_element(); if ( $source_element ) { $source_element_id = $source_element->get_id(); $source_language_code = $source_element->get_language_code(); $current_language = $this->sitepress->get_current_language(); $this->sitepress->switch_lang( $source_language_code ); $new_link = get_permalink( $source_element_id ); $this->sitepress->switch_lang( $current_language ); } else { $new_link = get_permalink( $post_element->get_id() ); } return $new_link; } /** * @return bool */ private function must_handle_a_canonical_url() { return ! $this->has_wp_get_canonical_url() && $this->sitepress->get_wp_api()->is_front_end(); } private function init_wpml_translations() { if ( ! $this->wpml_translations ) { $this->wpml_translations = new WPML_Translations( $this->sitepress ); } } } canonicals/class-wpml-canonicals-hooks.php 0000755 00000013742 14720342453 0014720 0 ustar 00 <?php /** * Class WPML_Canonicals_Hooks */ class WPML_Canonicals_Hooks { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_URL_Converter $url_converter */ private $url_converter; /** @var callable $is_current_request_root_callback */ private $is_current_request_root_callback; /** * WPML_Canonicals_Hooks constructor. * * @param SitePress $sitepress * @param WPML_URL_Converter $url_converter * @param callable $is_current_request_root_callback */ public function __construct( SitePress $sitepress, WPML_URL_Converter $url_converter, $is_current_request_root_callback ) { $this->sitepress = $sitepress; $this->url_converter = $url_converter; $this->is_current_request_root_callback = $is_current_request_root_callback; } public function add_hooks() { $urls = $this->sitepress->get_setting( 'urls' ); $lang_negotiation = (int) $this->sitepress->get_setting( 'language_negotiation_type' ); if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY === $lang_negotiation && ! empty( $urls['directory_for_default_language'] ) ) { add_action( 'template_redirect', array( $this, 'redirect_pages_from_root_to_default_lang_dir' ) ); add_action( 'template_redirect', [ $this, 'redirectArchivePageToDefaultLangDir' ] ); } elseif ( WPML_LANGUAGE_NEGOTIATION_TYPE_PARAMETER === $lang_negotiation ) { add_filter( 'redirect_canonical', array( $this, 'prevent_redirection_with_translated_paged_content' ) ); } if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY === $lang_negotiation ) { add_filter( 'redirect_canonical', [ $this, 'prevent_redirection_of_frontpage_on_secondary_language' ], 10, 2 ); } } public function redirect_pages_from_root_to_default_lang_dir() { global $wp_query; if ( ! ( ( $wp_query->is_page() || $wp_query->is_posts_page ) && ! call_user_func( $this->is_current_request_root_callback ) ) ) { return; } $lang = $this->sitepress->get_current_language(); $current_uri = $_SERVER['REQUEST_URI']; $abs_home = $this->url_converter->get_abs_home(); $install_subdir = wpml_parse_url( $abs_home, PHP_URL_PATH ); $actual_uri = is_string( $install_subdir ) ? preg_replace( '#^' . $install_subdir . '#', '', $current_uri ) : $current_uri; $actual_uri = '/' . ltrim( $actual_uri, '/' ); if ( 0 === strpos( $actual_uri, '/' . $lang ) ) { return; } $canonical_uri = is_string( $install_subdir ) ? trailingslashit( $install_subdir ) . $lang . $actual_uri : '/' . $lang . $actual_uri; $canonical_uri = user_trailingslashit( $canonical_uri ); $this->redirectTo( $canonical_uri ); } /** * When : * * The current template that user tries to load is for archive page * * And the default language in a directory mode is active * * And the language code is not present in the current request URI * * Then: We make a redirect to the proper URI that contains the default language code as directory. */ public function redirectArchivePageToDefaultLangDir() { $isValidForRedirect = is_archive() && ! call_user_func( $this->is_current_request_root_callback ); if ( ! $isValidForRedirect ) { return; } $currentUri = $_SERVER['REQUEST_URI']; $lang = $this->sitepress->get_current_language(); $home_url = rtrim( $this->url_converter->get_abs_home(), '/' ); $parsed_site_url = wp_parse_url( $home_url ); if ( isset( $parsed_site_url['path'] ) ) { // Cater for site installed in sub-folder. $path = $parsed_site_url['path']; if ( ! empty( $path ) && strpos( $currentUri, $path ) === 0 ) { $currentUri = substr( $currentUri, strlen( $path ) ); } } if ( 0 !== strpos( $currentUri, '/' . $lang ) ) { $canonicalUri = user_trailingslashit( $home_url . '/' . $lang . $currentUri ); $this->redirectTo( $canonicalUri ); } } private function redirectTo( $uri ) { $this->sitepress->get_wp_api()->wp_safe_redirect( $uri, 301 ); } /** * First we have to check if we are on front page and the current language is different than the default one. * If not, then we don't have to do anything -> return the $redirect_url. * * Next we check if the $redirect_url is the same as the $requested_url + '/'. * Then, we have to check if the permalink structure does not have the trailing slash. * * If both conditions are true, then we return false, so the redirection will not happen. * * @param string $redirect_url * @param string $requested_url * * @return string|false */ public function prevent_redirection_of_frontpage_on_secondary_language( $redirect_url, $requested_url ) { if ( ! is_front_page() || $this->sitepress->get_current_language() === $this->sitepress->get_default_language() ) { return $redirect_url; } if ( substr( get_option( 'permalink_structure' ), - 1 ) !== '/' ) { if ( $redirect_url === $requested_url . '/' ) { return false; } /** * Notice that the permalink structure does not have the trailing slash in this place. * * If a user requests site like `http://www.develop.test/fr` while home_url is set to `http://develop.test`, * then the $redirect_url will be `http://develop.test/fr/` and the $requested_url will be `http://www.develop.test/fr`. * The trailing slash is added to $redirect_url by WP function `redirect_canonical` and is not desired. * * If we did not remove it, WP function `redirect_canonical` would redirect to `http://develop.test/fr` again. * Its internal safety mechanism does not allow multiple redirects. Therefore, the whole redirect would be skipped. */ $redirect_url = untrailingslashit( $redirect_url ); } return $redirect_url; } /** * @param string $redirect_url * * @return string|false */ public function prevent_redirection_with_translated_paged_content( $redirect_url ) { if ( ! is_singular() || ! isset( $_GET['lang'] ) ) { return $redirect_url; } $page = (int) get_query_var( 'page' ); if ( $page < 2 ) { return $redirect_url; } return false; } } compatibility/bbpress/class-wpml-bbpress-api.php 0000755 00000000341 14720342453 0016062 0 ustar 00 <?php class WPML_BBPress_API { public function bbp_get_user_profile_url( $user_id = 0, $user_nicename = '' ) { /** * @phpstan-ignore-next-line */ return bbp_get_user_profile_url( $user_id, $user_nicename ); } } compatibility/bbpress/class-wpml-bbpress-filters.php 0000755 00000002531 14720342453 0016764 0 ustar 00 <?php /** * WPML_BBPress_Filters class file. * * @package WPML\Core */ /** * Class WPML_BBPress_Filters */ class WPML_BBPress_Filters { /** * WPML_BBPress_API instance. * * @var WPML_BBPress_API */ private $wpml_bbpress_api; /** * WPML_BBPress_Filters constructor. * * @param WPML_BBPress_API $wpml_bbpress_api WPML_BBPress_API instance. */ public function __construct( $wpml_bbpress_api ) { $this->wpml_bbpress_api = $wpml_bbpress_api; } /** * Destruct instance. */ public function __destruct() { $this->remove_hooks(); } /** * Add hooks. */ public function add_hooks() { add_filter( 'author_link', array( $this, 'author_link_filter' ), 10, 3 ); } /** * Remove hooks. */ public function remove_hooks() { remove_filter( 'author_link', array( $this, 'author_link_filter' ), 10 ); } /** * Author link filter. * * @param string $link Author link. * @param int $author_id Author id. * @param string $author_nicename Author nicename. * * @return mixed */ public function author_link_filter( $link, $author_id, $author_nicename ) { if ( doing_action( 'wpseo_head' ) || doing_action( 'wp_head' ) || doing_filter( 'wpml_active_languages' ) ) { return $this->wpml_bbpress_api->bbp_get_user_profile_url( $author_id, $author_nicename ); } return $link; } } admin-language-switcher/AdminLanguageSwitcher.php 0000755 00000010372 14720342453 0016173 0 ustar 00 <?php namespace WPML\AdminLanguageSwitcher; use WPML\Element\API\Languages; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Str; use WPML\LIB\WP\Option; use WPML\UrlHandling\WPLoginUrlConverter; class AdminLanguageSwitcher implements \IWPML_Frontend_Action { const LANGUAGE_SWITCHER_KEY = 'wpml_show_login_page_language_switcher'; public function add_hooks() { add_action( 'login_footer', [ $this, 'triggerDropdown' ] ); add_action( 'plugins_loaded', [ $this, 'maybeSaveNewLanguage' ] ); } public function maybeSaveNewLanguage() { if ( ! $this->isLanguageSwitcherShown() ) { return; } $selectedLocale = $this->getSelectedLocale(); if ( $selectedLocale ) { $languageCode = Languages::localeToCode( $selectedLocale ); if ( ! is_string( $languageCode ) ) { return; } $secure = ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) ); setcookie( 'wp-wpml_login_lang', $languageCode, time() + 120, COOKIEPATH, COOKIE_DOMAIN, $secure ); setcookie( 'wp_lang', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, $secure ); global $sitepress; wp_safe_redirect( $this->prepareRedirectLink( $sitepress, $languageCode ) ); } } public function triggerDropdown() { if ( ! $this->isLanguageSwitcherShown() ) { return; } wp_register_style( 'wpml-login-language-switcher', ICL_PLUGIN_URL . '/res/css/login-language-switcher.css' ); wp_enqueue_style( 'wpml-login-language-switcher' ); $selectedLocale = determine_locale(); $languages = wpml_collect( Languages::getActive() ); $prepareOptions = function ( $languages ) use ( $selectedLocale ) { return $languages->sortBy( function ( $language ) use ( $selectedLocale ) { return $language['default_locale'] !== $selectedLocale; } ) ->map( Obj::addProp( 'selected', Relation::propEq( 'default_locale', $selectedLocale ) ) ) ->map( function ( $language ) { return sprintf( '<option value="%s" lang="%s" %s>%s</option>', esc_attr( Obj::prop( 'default_locale', $language ) ), esc_attr( Obj::prop( 'code', $language ) ), esc_attr( Obj::prop( 'selected', $language ) ? 'selected' : '' ), esc_html( Obj::prop( 'native_name', $language ) ) ); } ); }; AdminLanguageSwitcherRenderer::render( $prepareOptions( $languages )->all() ); } private function isOnWpLoginPage( $url ) { return Str::includes( 'wp-login.php', $url ) || Str::includes( 'wp-signup.php', $url ) || Str::includes( 'wp-activate.php', $url ); } /** * @return string|false */ private function getSelectedLocale() { return Maybe::of( $_GET ) ->map( Obj::prop('wpml_lang' ) ) ->map( 'sanitize_text_field' ) ->getOrElse( false ); } /** * @return bool */ private function isLanguageSwitcherShown() { return $this->isOnWpLoginPage( site_url( $_SERVER['REQUEST_URI'], 'login' ) ) && WPLoginUrlConverter::isEnabled() && self::isEnabled(); } /** * @param $sitepress * @param string $languageCode * * @return string */ private function prepareRedirectLink( $sitepress, $languageCode ) { $redirectTo = $sitepress->convert_url( site_url( 'wp-login.php' ), $languageCode ); $redirectToParam = Maybe::of( $_GET ) ->map( Obj::prop( 'redirect_to' ) ) ->map( 'esc_url_raw' ) ->getOrElse( false ); $action = Maybe::of( $_GET ) ->map( Obj::prop( 'action' ) ) ->map( 'esc_attr' ) ->getOrElse( false ); if ( $redirectToParam ) { $redirectTo = add_query_arg( 'redirect_to', $redirectToParam, $redirectTo ); } if ( $action ) { $redirectTo = add_query_arg( 'action', $action, $redirectTo ); } return $redirectTo; } /** * @param bool $state */ public static function saveState( $state ) { Option::updateWithoutAutoLoad( self::LANGUAGE_SWITCHER_KEY, $state ); } /** * @return bool */ public static function isEnabled() { return Option::getOr( self::LANGUAGE_SWITCHER_KEY, true ); } public static function enable() { self::saveState( true ); } public static function disable() { self::saveState( false ); } } admin-language-switcher/DisableWpLanguageSwitcher.php 0000755 00000001641 14720342453 0017014 0 ustar 00 <?php namespace WPML\AdminLanguageSwitcher; class DisableWpLanguageSwitcher implements \IWPML_Frontend_Action, \IWPML_Backend_Action { public function add_hooks() { add_filter( 'login_display_language_dropdown', '__return_false' ); add_filter( 'logout_redirect', [ $this, 'removeWPLangFromRedirectUrl' ], 10, 2 ); } /** * @param string $redirect_to * @param string $requested_redirect_to * @return string */ public function removeWPLangFromRedirectUrl( $redirect_to, $requested_redirect_to ) { if ( '' !== $requested_redirect_to ) { return $redirect_to; } if ( strpos( $redirect_to, '?' ) > -1 ) { $queryString = substr( $redirect_to, strpos( $redirect_to, '?' ) + 1 ); parse_str( $queryString, $queryStringParams ); unset( $queryStringParams['wp_lang'] ); $redirect_to = str_replace( $queryString, http_build_query( $queryStringParams ), $redirect_to ); } return $redirect_to; } } admin-language-switcher/AdminLanguageSwitcherRenderer.php 0000755 00000002316 14720342453 0017661 0 ustar 00 <?php namespace WPML\AdminLanguageSwitcher; class AdminLanguageSwitcherRenderer { public static function render( $languageOptions ) { ?> <div class="wpml-login-ls"> <form id="wpml-login-ls-form" action="" method="get"> <?php if ( isset( $_GET['redirect_to'] ) && '' !== $_GET['redirect_to'] ) { ?> <input type="hidden" name="redirect_to" value="<?php echo esc_url_raw( $_GET['redirect_to'] ); ?>"/> <?php } ?> <?php if ( isset( $_GET['action'] ) && '' !== $_GET['action'] ) { ?> <input type="hidden" name="action" value="<?php echo esc_attr( $_GET['action'] ); ?>"/> <?php } ?> <label for="language-switcher-locales"> <span class="dashicons dashicons-translation" aria-hidden="true"></span> <span class="screen-reader-text"><?php _e( 'Language' ); ?></span> </label> <select name="wpml_lang" id="wpml-language-switcher-locales"> <?php echo implode( '', $languageOptions ); ?> </select> <input type="submit" class="button" value="<?php esc_attr_e( "Change" ); ?>"> </form> </div> <?php } } requirements/class-wpml-tm-editor-notice.php 0000755 00000001157 14720342453 0015256 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 28/08/17 * Time: 11:54 AM */ class WPML_TM_Editor_Notice extends WPML_Notice { public function is_different( WPML_Notice $other_notice ) { if ( $this->get_id() !== $other_notice->get_id() || $this->get_group() !== $other_notice->get_group() ) { return true; } return $this->strip_nonce_field( $this->get_text() ) !== $this->strip_nonce_field( $other_notice->get_text() ); } private function strip_nonce_field( $text ) { return preg_replace( '/<input type="hidden" name="wpml_set_translation_editor_nonce" value=".*?">/', '', $text ); } } requirements/class-wpml-integrations-requirements.php 0000755 00000027332 14720342453 0017325 0 ustar 00 <?php use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Environment; /** * @author OnTheGo Systems */ class WPML_Integrations_Requirements { const NOTICE_GROUP = 'requirements'; const CORE_REQ_NOTICE_ID = 'core-requirements'; const MISSING_REQ_NOTICE_ID = 'missing-requirements'; const EDITOR_NOTICE_ID = 'enable-translation-editor'; const DOCUMENTATION_LINK = 'https://wpml.org/documentation/translating-your-contents/page-builders/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore'; const DOCUMENTATION_LINK_BLOCK_EDITOR = 'https://wpml.org/?page_id=2909360&utm_source=wpmlplugin&utm_campaign=gutenberg&utm_medium=translation-editor&utm_term=translating-content-created-using-gutenberg-editor'; private $core_issues = array(); private $issues = array(); private $tm_settings; private $should_create_editor_notice = false; private $integrations; private $requirements_scripts; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Third_Party_Dependencies $third_party_dependencies */ private $third_party_dependencies; /** @var WPML_Requirements_Notification $requirements_notification */ private $requirements_notification; /** * WPML_Integrations_Requirements constructor. * * @param SitePress $sitepress * @param WPML_Third_Party_Dependencies $third_party_dependencies * @param WPML_Requirements_Notification $requirements_notification * @param array $integrations */ public function __construct( SitePress $sitepress, WPML_Third_Party_Dependencies $third_party_dependencies = null, WPML_Requirements_Notification $requirements_notification = null, $integrations = null ) { $this->sitepress = $sitepress; $this->third_party_dependencies = $third_party_dependencies; $this->requirements_notification = $requirements_notification; $this->tm_settings = $this->sitepress->get_setting( 'translation-management' ); $this->integrations = $integrations ? $integrations : $this->get_integrations(); } public function init_hooks() { if ( $this->sitepress->get_setting( 'setup_complete' ) ) { add_action( 'admin_init', array( $this, 'init' ) ); add_action( 'wp_ajax_wpml_set_translation_editor', array( $this, 'set_translation_editor_callback' ) ); } } public function init() { if ( $this->sitepress->get_wp_api()->is_back_end() ) { $this->update_issues(); $this->update_notices(); if ( $this->core_issues || $this->issues ) { $requirements_scripts = $this->get_requirements_scripts(); $requirements_scripts->add_plugins_activation_hook(); } } } private function update_notices() { $wpml_admin_notices = wpml_get_admin_notices(); if ( ! $this->core_issues ) { $wpml_admin_notices->remove_notice( self::NOTICE_GROUP, self::CORE_REQ_NOTICE_ID ); } if ( ! $this->issues ) { $wpml_admin_notices->remove_notice( self::NOTICE_GROUP, self::MISSING_REQ_NOTICE_ID ); } if ( ! $this->should_create_editor_notice ) { $wpml_admin_notices->remove_notice( self::NOTICE_GROUP, self::EDITOR_NOTICE_ID ); } if ( $this->core_issues || $this->issues || $this->should_create_editor_notice ) { $notice_model = $this->get_notice_model(); $wp_api = $this->sitepress->get_wp_api(); $this->add_core_requirements_notice( $notice_model, $wpml_admin_notices, $wp_api ); $this->add_requirements_notice( $notice_model, $wpml_admin_notices, $wp_api ); $this->add_tm_editor_notice( $notice_model, $wpml_admin_notices, $wp_api ); } } private function update_issues() { $this->core_issues = $this->get_third_party_dependencies()->get_issues( WPML_Integrations::SCOPE_WP_CORE ); $this->issues = $this->get_third_party_dependencies()->get_issues(); $this->update_should_create_editor_notice(); } private function update_should_create_editor_notice() { $editor_translation_set = isset( $this->tm_settings['doc_translation_method'] ) && in_array( (string) $this->tm_settings['doc_translation_method'], array( (string) ICL_TM_TMETHOD_EDITOR, (string) ICL_TM_TMETHOD_ATE, ), true ); $requires_tm_editor = false; foreach ( $this->integrations as $integration_item ) { if ( in_array( 'wpml-translation-editor', $integration_item['notices-display'], true ) ) { $requires_tm_editor = true; break; } } $this->should_create_editor_notice = ! $editor_translation_set && ! $this->issues && $requires_tm_editor; } public function set_translation_editor_callback() { if ( ! $this->is_valid_request() ) { wp_send_json_error( __( 'This action is not allowed', 'sitepress' ) ); } else { $wpml_admin_notices = wpml_get_admin_notices(); $this->tm_settings['doc_translation_method'] = 1; $this->sitepress->set_setting( 'translation-management', $this->tm_settings, true ); $this->sitepress->set_setting( 'doc_translation_method', 1, true ); $wpml_admin_notices->remove_notice( self::NOTICE_GROUP, self::EDITOR_NOTICE_ID ); 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, 'wpml_set_translation_editor' ); if ( ! $nonce_is_valid ) { $valid_request = false; } } return $valid_request; } private function get_integrations() { $integrations = new WPML_Integrations( $this->sitepress->get_wp_api() ); return $integrations->get_results(); } /** * @param string $notice_type * * @return array */ private function get_integrations_names( $notice_type ) { $names = array(); foreach ( $this->integrations as $integration ) { if ( in_array( $notice_type, $integration['notices-display'], true ) && ! in_array( $integration['name'], $names, true ) ) { $names[] = $integration['name']; } } return $names; } /** * @return WPML_Requirements_Notification */ private function get_notice_model() { if ( ! $this->requirements_notification ) { $template_paths = array( WPML_PLUGIN_PATH . '/templates/warnings/', ); $twig_loader = new Twig_Loader_Filesystem( $template_paths ); $environment_args = array(); if ( WP_DEBUG ) { $environment_args['debug'] = true; } $twig = new Twig_Environment( $twig_loader, $environment_args ); $twig_service = new WPML_Twig_Template( $twig ); $this->requirements_notification = new WPML_Requirements_Notification( $twig_service ); } return $this->requirements_notification; } /** * @param WPML_Notice $notice */ private function add_actions_to_notice( WPML_Notice $notice ) { $dismiss_action = new WPML_Notice_Action( __( 'Dismiss', 'sitepress' ), '#', true, false, true, false ); $notice->add_action( $dismiss_action ); if ( $this->has_issues( 'page-builders' ) ) { $document_action = new WPML_Notice_Action( __( 'Translating content created with page builders', 'sitepress' ), self::DOCUMENTATION_LINK ); $notice->add_action( $document_action ); } } private function add_actions_to_core_notice( WPML_Notice $notice ) { $dismiss_action = new WPML_Notice_Action( __( 'Dismiss', 'sitepress' ), '#', true, false, true, false ); $notice->add_action( $dismiss_action ); if ( $this->has_issues( WPML_Integrations::SCOPE_WP_CORE ) ) { $document_action = new WPML_Notice_Action( __( 'How to translate Block editor content', 'sitepress' ), self::DOCUMENTATION_LINK_BLOCK_EDITOR ); $document_action->set_link_target( '_blank' ); $notice->add_action( $document_action ); } } /** * @param string $type * * @return bool */ private function has_issues( $type ) { $issues = WPML_Integrations::SCOPE_WP_CORE === $type ? $this->core_issues : $this->issues; if ( array_key_exists( 'causes', $issues ) ) { foreach ( (array) $issues['causes'] as $cause ) { if ( $type === $cause['type'] ) { return true; } } } return false; } /** * @param WPML_Notice $notice * @param WPML_WP_API $wp_api */ private function add_callbacks( WPML_Notice $notice, WPML_WP_API $wp_api ) { if ( method_exists( $notice, 'add_display_callback' ) ) { $notice->add_display_callback( array( $wp_api, 'is_core_page' ) ); $notice->add_display_callback( array( $wp_api, 'is_plugins_page' ) ); $notice->add_display_callback( array( $wp_api, 'is_themes_page' ) ); } } /** * @param WPML_Requirements_Notification $notice_model * @param WPML_Notices $wpml_admin_notices * @param WPML_WP_API $wp_api */ private function add_core_requirements_notice( WPML_Requirements_Notification $notice_model, WPML_Notices $wpml_admin_notices, WPML_WP_API $wp_api ) { if ( $this->core_issues ) { $message = $notice_model->get_core_message( $this->core_issues ); $requirements_notice = new WPML_Notice( self::CORE_REQ_NOTICE_ID, $message, self::NOTICE_GROUP ); $this->add_actions_to_core_notice( $requirements_notice ); $requirements_notice->set_css_class_types( 'warning' ); $wpml_admin_notices->add_notice( $requirements_notice, true ); } } /** * @param WPML_Requirements_Notification $notice_model * @param WPML_Notices $wpml_admin_notices * @param WPML_WP_API $wp_api */ private function add_requirements_notice( WPML_Requirements_Notification $notice_model, WPML_Notices $wpml_admin_notices, WPML_WP_API $wp_api ) { if ( $this->issues ) { $message = $notice_model->get_message( $this->issues, 1 ); $requirements_notice = new WPML_Notice( self::MISSING_REQ_NOTICE_ID, $message, self::NOTICE_GROUP ); $this->add_actions_to_notice( $requirements_notice ); $this->add_callbacks( $requirements_notice, $wp_api ); $wpml_admin_notices->add_notice( $requirements_notice, true ); } } /** * @param WPML_Requirements_Notification $notice_model * @param WPML_Notices $wpml_admin_notices * @param WPML_WP_API $wp_api */ private function add_tm_editor_notice( WPML_Requirements_Notification $notice_model, WPML_Notices $wpml_admin_notices, WPML_WP_API $wp_api ) { if ( $this->should_create_editor_notice ) { $requirements_scripts = $this->get_requirements_scripts(); $requirements_scripts->add_translation_editor_notice_hook(); $integrations_names = $this->get_integrations_names( 'wpml-translation-editor' ); $text = $notice_model->get_settings( $integrations_names ); $notice = new WPML_TM_Editor_Notice( self::EDITOR_NOTICE_ID, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( 'info' ); $enable_action = new WPML_Notice_Action( _x( 'Enable it now', 'Integration requirement notice title for translation editor: enable action', 'sitepress' ), '#', false, false, true ); $enable_action->set_js_callback( 'js-set-translation-editor' ); $notice->add_action( $enable_action ); $this->add_callbacks( $notice, $wp_api ); $this->add_actions_to_notice( $notice ); $wpml_admin_notices->add_notice( $notice ); } } /** * @return WPML_Integrations_Requirements_Scripts */ private function get_requirements_scripts() { if ( ! $this->requirements_scripts ) { return new WPML_Integrations_Requirements_Scripts(); } return $this->requirements_scripts; } /** * @return WPML_Third_Party_Dependencies */ private function get_third_party_dependencies() { if ( ! $this->third_party_dependencies ) { $integrations = new WPML_Integrations( $this->sitepress->get_wp_api() ); $requirements = new WPML_Requirements(); $this->third_party_dependencies = new WPML_Third_Party_Dependencies( $integrations, $requirements ); } return $this->third_party_dependencies; } } requirements/class-wpml-requirements-notifications.php 0000755 00000007471 14720342453 0017472 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Requirements_Notification { /** * @var \IWPML_Template_Service */ private $template_service; /** * WPML_Requirements_Notification constructor. * * @param IWPML_Template_Service $template_service */ public function __construct( IWPML_Template_Service $template_service ) { $this->template_service = $template_service; } public function get_core_message( $issues ) { if ( $issues ) { $strings = array( 'title' => __( 'Your WPML installation may cause problems with Block editor', 'sitepress' ), 'message' => __( 'You are using WPML Translation Management without String Translation. Some of the translations may not work this way. Please download and install WPML String Translation before you translate the content from Block editor.', 'sitepress' ), ); return $this->get_shared_message( $strings, $issues ); } return null; } public function get_message( $issues, $limit = 0 ) { if ( $issues ) { $strings = array( 'title' => sprintf( __( 'To easily translate %s, you need to add the following WPML components:', 'sitepress' ), $this->get_product_names( $issues ) ), ); return $this->get_shared_message( $strings, $issues, $limit ); } return null; } private function get_shared_message( $strings, $issues, $limit = 0 ) { $strings = array_merge( array( 'download' => __( 'Download', 'sitepress' ), 'install' => __( 'Install', 'sitepress' ), 'activate' => __( 'Activate', 'sitepress' ), 'activating' => __( 'Activating...', 'sitepress' ), 'activated' => __( 'Activated', 'sitepress' ), 'error' => __( 'Error', 'sitepress' ), ), $strings ); $model = array( 'strings' => $strings, 'shared' => array( 'install_link' => get_admin_url( null, 'plugin-install.php?tab=commercial' ), ), 'options' => array( 'limit' => $limit, ), 'data' => $issues, ); return $this->template_service->show( $model, 'plugins-status.twig' ); } public function get_settings( $integrations ) { if ( $integrations ) { $model = array( 'strings' => array( /* translators: %s will be replaced with a list of plugins or themes. */ 'title' => sprintf( __( 'One more step before you can translate on %s', 'sitepress' ), $this->build_items_in_sentence( $integrations ) ), 'message' => __( "You need to enable WPML's Translation Editor, to translate conveniently.", 'sitepress' ), 'enable_done' => __( 'Done.', 'sitepress' ), 'enable_error' => __( 'Something went wrong. Please try again or contact the support.', 'sitepress' ), ), 'nonces' => array( 'enable' => wp_create_nonce( 'wpml_set_translation_editor' ), ), ); return $this->template_service->show( $model, 'integrations-tm-settings.twig' ); } return null; } /** * @param array $issues * * @return string */ private function get_product_names( $issues ) { $products = wp_list_pluck( $issues['causes'], 'name' ); return $this->build_items_in_sentence( $products ); } /** * @param array<string> $items * * @return string */ private function build_items_in_sentence( $items ) { if ( count( $items ) <= 2 ) { /* translators: Used between elements of a two elements list */ $product_names = implode( ' ' . _x( 'and', 'Used between elements of a two elements list', 'sitepress' ) . ' ', $items ); return $product_names; } $last = array_slice( $items, - 1 ); $first = implode( ', ', array_slice( $items, 0, - 1 ) ); $both = array_filter( array_merge( array( $first ), $last ), 'strlen' ); /* translators: Used before the last element of a three or more elements list */ $product_names = implode( _x( ', and', 'Used before the last element of a three or more elements list', 'sitepress' ) . ' ', $both ); return $product_names; } } requirements/class-wpml-integrations.php 0000755 00000007525 14720342453 0014606 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Integrations { const SCOPE_WP_CORE = 'wp-core'; private $components = array( self::SCOPE_WP_CORE => array( 'block-editor' => array( 'name' => 'WordPress Block Editor', 'function' => 'parse_blocks', 'notices-display' => array(), ), ), 'page-builders' => array( 'js_composer' => array( 'name' => 'Visual Composer', 'constant' => 'WPB_VC_VERSION', 'notices-display' => array( 'wpml-translation-editor', ), ), 'divi' => array( 'name' => 'Divi', 'constant' => 'ET_BUILDER_DIR', 'notices-display' => array( 'wpml-translation-editor', ), ), 'layouts' => array( 'name' => 'Toolset Layouts', 'constant' => 'WPDDL_VERSION', 'notices-display' => array( 'wpml-translation-editor', ), ), 'x-theme' => array( 'name' => 'X Theme', 'constant' => 'X_VERSION', 'notices-display' => array( 'wpml-translation-editor', ), ), 'enfold' => array( 'name' => 'Enfold', 'constant' => 'AVIA_FW', 'notices-display' => array( 'wpml-translation-editor', ), ), 'avada' => array( 'name' => 'Avada', 'function' => 'Avada', 'notices-display' => array( 'wpml-translation-editor', ), ), 'oxygen' => array( 'name' => 'Oxygen', 'constant' => 'CT_VERSION', 'notices-display' => array( 'wpml-translation-editor', ), ), ), 'integrations' => array( 'bb-plugin' => array( 'name' => 'Beaver Builder Plugin', 'class' => 'FLBuilderLoader', 'notices-display' => array( 'wpml-translation-editor', ), ), 'elementor-plugin' => array( 'name' => 'Elementor', 'class' => '\Elementor\Plugin', 'notices-display' => array( 'wpml-translation-editor', ), ), ), ); private $items = array(); private $wpml_wp_api; /** * WPML_Integrations constructor. * * @param WPML_WP_API $wpml_wp_api */ function __construct( WPML_WP_API $wpml_wp_api ) { $this->wpml_wp_api = $wpml_wp_api; $this->fetch_items(); } private function fetch_items() { foreach ( $this->get_components() as $type => $components ) { foreach ( (array) $components as $slug => $data ) { if ( $this->component_has_constant( $data ) || $this->component_has_function( $data ) || $this->component_has_class( $data ) ) { $this->items[ $slug ] = array( 'name' => $this->get_component_name( $data ) ); $this->items[ $slug ]['type'] = $type; $this->items[ $slug ]['notices-display'] = isset( $data['notices-display'] ) ? $data['notices-display'] : array(); } } } } public function get_results() { return $this->items; } /** * @param array $data * * @return bool */ private function component_has_constant( array $data ) { return array_key_exists( 'constant', $data ) && $data['constant'] && $this->wpml_wp_api->defined( $data['constant'] ); } /** * @param array $data * * @return bool */ private function component_has_function( array $data ) { return array_key_exists( 'function', $data ) && $data['function'] && function_exists( $data['function'] ); } /** * @param array $data * * @return bool */ private function component_has_class( array $data ) { return array_key_exists( 'class', $data ) && $data['class'] && class_exists( $data['class'] ); } /** * @param array $data * * @return mixed */ private function get_component_name( array $data ) { return $data['name']; } /** * @return array */ private function get_components() { return apply_filters( 'wpml_integrations_components', $this->components ); } } requirements/class-wpml-third-party-dependencies.php 0000755 00000003763 14720342453 0016773 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Third_Party_Dependencies { private $integrations; private $requirements; /** * WPML_Third_Party_Dependencies constructor. * * @param WPML_Integrations $integrations * @param WPML_Requirements $requirements */ public function __construct( WPML_Integrations $integrations, WPML_Requirements $requirements ) { $this->integrations = $integrations; $this->requirements = $requirements; } public function get_issues( $scope = null ) { $issues = array( 'causes' => array(), 'requirements' => array(), ); $components = $this->get_components( $scope ); foreach ( (array) $components as $slug => $component_data ) { $issue = $this->get_issue( $component_data, $slug ); if ( $issue ) { $issues['causes'][] = $issue['cause']; foreach ( $issue['requirements'] as $requirement ) { $issues['requirements'][] = $requirement; } } } sort( $issues['causes'] ); sort( $issues['requirements'] ); $issues['causes'] = array_unique( $issues['causes'], SORT_REGULAR ); $issues['requirements'] = array_unique( $issues['requirements'], SORT_REGULAR ); if ( ! $issues || ! $issues['causes'] || ! $issues['requirements'] ) { return array(); } return $issues; } private function get_components( $scope ) { $components = $this->integrations->get_results(); foreach ( $components as $index => $component ) { if ( WPML_Integrations::SCOPE_WP_CORE === $component['type'] && WPML_Integrations::SCOPE_WP_CORE !== $scope || WPML_Integrations::SCOPE_WP_CORE !== $component['type'] && WPML_Integrations::SCOPE_WP_CORE === $scope ) { unset( $components[ $index ] ); } } return $components; } private function get_issue( $component_data, $slug ) { $requirements = $this->requirements->get_requirements( $component_data['type'], $slug ); if ( ! $requirements ) { return null; } return array( 'cause' => $component_data, 'requirements' => $requirements, ); } } requirements/class-wpml-whip-requirements.php 0000755 00000002430 14720342453 0015556 0 ustar 00 <?php /** * WPML_Whip_Requirements class file. * * @package wpml-core */ /** * Class WPML_Whip_Requirements */ class WPML_Whip_Requirements { /** * Add hooks. */ public function add_hooks() { add_action( 'plugins_loaded', array( $this, 'load_whip' ) ); } /** * Get host name for message about PHP. * * @return string */ public function whip_name_of_host() { return 'WPML'; } /** * Get WPML message about PHP. * * @return string */ public function whip_message_from_host_about_php() { $message = '<li>' . __( 'This will be the last version of WPML which works with the currently installed PHP version', 'sitepress' ) . '</li>' . '<li>' . __( 'This version of WPML will only receive security fixes for the next 12 months', 'sitepress' ) . '</li>'; return $message; } /** * Load Whip. */ public function load_whip() { if ( ! ( 'index.php' === $GLOBALS['pagenow'] && current_user_can( 'manage_options' ) ) ) { return; } add_filter( 'whip_hosting_page_url_wordpress', '__return_true' ); add_filter( 'whip_name_of_host', array( $this, 'whip_name_of_host' ) ); add_filter( 'whip_message_from_host_about_php', array( $this, 'whip_message_from_host_about_php' ) ); whip_wp_check_versions( array( 'php' => '>=5.6' ) ); } } requirements/class-wpml-requirements.php 0000755 00000015416 14720342453 0014621 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Requirements { private $active_plugins = array(); private $disabled_plugins = array(); private $missing_requirements = array(); private $plugins = array( 'wpml-media-translation' => array( 'version' => '2.1.24', 'name' => 'WPML Media Translation', ), 'wpml-string-translation' => array( 'version' => '2.5.2', 'name' => 'WPML String Translation', ), 'wpml-translation-management' => array( 'version' => '2.2.7', 'name' => 'WPML Translation Management', ), 'woocommerce-multilingual' => array( 'version' => '4.7.0', 'name' => 'WooCommerce Multilingual', 'url' => 'https://wpml.org/download/woocommerce-multilingual/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'gravityforms-multilingual' => array( 'name' => 'GravityForms Multilingual', 'url' => 'https://wpml.org/download/gravityforms-multilingual/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'buddypress-multilingual' => array( 'name' => 'BuddyPress Multilingual', 'url' => 'https://wpml.org/download/buddypress-multilingual/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), 'wp-seo-multilingual' => array( 'name' => 'Yoast SEO Multilingual', 'url' => 'https://wpml.org/download/yoast-seo-multilingual/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', ), ); private $modules = array( WPML_Integrations::SCOPE_WP_CORE => array( 'url' => 'https://wpml.org/?page_id=2909360&utm_source=wpmlplugin&utm_campaign=gutenberg&utm_medium=translation-editor&utm_term=translating-content-created-using-gutenberg-editor', 'requirements_class' => 'WPML_Integration_Requirements_Block_Editor', ), 'page-builders' => array( 'url' => 'https://wpml.org/?page_id=1129854', 'requirements' => array( 'wpml-string-translation', ), ), 'gravityforms' => array( 'url' => '#', 'requirements' => array( 'gravityforms-multilingual', 'wpml-string-translation', ), ), 'buddypress' => array( 'url' => '#', 'requirements' => array( 'buddypress-multilingual', ), ), 'bb-plugin' => array( 'url' => '#', 'requirements' => array( 'wpml-string-translation', ), ), 'elementor-plugin' => array( 'url' => '#', 'requirements' => array( 'wpml-string-translation', ), ), 'wordpress-seo' => array( 'url' => '#', 'requirements' => array( 'wp-seo-multilingual', ), ), ); /** * WPML_Requirements constructor. */ public function __construct() { if ( function_exists( 'get_plugins' ) ) { $installed_plugins = get_plugins(); foreach ( $installed_plugins as $plugin_file => $plugin_data ) { $plugin_slug = $this->get_plugin_slug( $plugin_data ); if ( is_plugin_active( $plugin_file ) ) { $this->active_plugins[ $plugin_slug ] = $plugin_data; } else { $this->disabled_plugins[ $plugin_slug ] = $plugin_file; } } } } public function is_plugin_active( $plugin_slug ) { return array_key_exists( $plugin_slug, $this->active_plugins ); } /** * @param array $plugin_data * * @return string|null */ public function get_plugin_slug( array $plugin_data ) { $plugin_slug = null; if ( array_key_exists( 'Plugin Slug', $plugin_data ) && $plugin_data['Plugin Slug'] ) { $plugin_slug = $plugin_data['Plugin Slug']; } elseif ( array_key_exists( 'TextDomain', $plugin_data ) && $plugin_data['TextDomain'] ) { $plugin_slug = $plugin_data['TextDomain']; } elseif ( array_key_exists( 'Name', $plugin_data ) && $plugin_data['Name'] ) { $plugin_slug = $plugin_data['Name']; } return $plugin_slug; } /** * @return array */ public function get_missing_requirements() { return $this->missing_requirements; } /** * @param string $type * @param string $slug * * @return array */ public function get_requirements( $type, $slug ) { $missing_plugins = $this->get_missing_plugins_for_type( $type, $slug ); $requirements = array(); if ( $missing_plugins ) { foreach ( $this->get_components_requirements_by_type( $type, $slug ) as $plugin_slug ) { $requirement = $this->get_plugin_data( $plugin_slug ); $requirement['missing'] = false; if ( in_array( $plugin_slug, $missing_plugins, true ) ) { $requirement['missing'] = true; if ( array_key_exists( $plugin_slug, $this->disabled_plugins ) ) { $requirement['disabled'] = true; $requirement['plugin_file'] = $this->disabled_plugins[ $plugin_slug ]; $requirement['activation_nonce'] = wp_create_nonce( 'activate_' . $this->disabled_plugins[ $plugin_slug ] ); } $this->missing_requirements[] = $requirement; } $requirements[] = $requirement; } } return $requirements; } /** * @param string $slug * * @return array */ function get_plugin_data( $slug ) { if ( array_key_exists( $slug, $this->plugins ) ) { return $this->plugins[ $slug ]; } return array(); } /** * @param string $type * @param string $slug * * @return array */ private function get_missing_plugins_for_type( $type, $slug ) { $requirements_keys = $this->get_components_requirements_by_type( $type, $slug ); $active_plugins_keys = array_keys( $this->active_plugins ); return array_diff( $requirements_keys, $active_plugins_keys ); } /** * @return array */ private function get_components() { return apply_filters( 'wpml_requirements_components', $this->modules ); } /** * @param string $type * @param string $slug * * @return array */ private function get_components_by_type( $type, $slug ) { $components = $this->get_components(); if ( array_key_exists( $type, $components ) ) { return $components[ $type ]; } if ( array_key_exists( $slug, $components ) ) { return $components[ $slug ]; } return array(); } /** * @param string $type * @param string $slug * * @return array */ private function get_components_requirements_by_type( $type, $slug ) { $components_requirements = $this->get_components_by_type( $type, $slug ); $requirements = array(); if ( array_key_exists( 'requirements', $components_requirements ) ) { $requirements = $components_requirements['requirements']; } elseif ( array_key_exists( 'requirements_class', $components_requirements ) ) { try { $class = $components_requirements['requirements_class']; /** @var IWPML_Integration_Requirements_Module $requirement_module */ $requirement_module = new $class( $this ); $requirements = $requirement_module->get_requirements(); } catch ( Exception $e ) { } } return $requirements; } } requirements/modules/interface-iwpml-integration-requirements-module.php 0000755 00000000141 14720342453 0023066 0 ustar 00 <?php interface IWPML_Integration_Requirements_Module { public function get_requirements(); } requirements/modules/class-wpml-integration-requirement-block-editor.php 0000755 00000000750 14720342453 0022776 0 ustar 00 <?php class WPML_Integration_Requirements_Block_Editor implements IWPML_Integration_Requirements_Module { /** @var WPML_Requirements $requirements */ private $requirements; public function __construct( WPML_Requirements $requirements ) { $this->requirements = $requirements; } public function get_requirements() { if ( $this->requirements->is_plugin_active( 'wpml-translation-management' ) ) { return array( 'wpml-string-translation', ); } return array(); } } requirements/wpml-integrations-requirements-scripts.php 0000755 00000001426 14720342453 0017703 0 ustar 00 <?php class WPML_Integrations_Requirements_Scripts { public function add_translation_editor_notice_hook() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_translation_editor_notice_script' ) ); } public function enqueue_translation_editor_notice_script() { wp_enqueue_script( 'wpml-integrations-requirements-scripts', ICL_PLUGIN_URL . '/res/js/requirements/integrations-requirements.js', array( 'jquery' ) ); } public function add_plugins_activation_hook() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_plugin_activation_script' ) ); } public function enqueue_plugin_activation_script() { wp_enqueue_script( 'wpml-requirements-plugins-activation', ICL_PLUGIN_URL . '/dist/js/wpml-requirements/app.js', array(), ICL_SITEPRESS_VERSION ); } } requirements/WordPress.php 0000755 00000001037 14720342453 0011740 0 ustar 00 <?php namespace WPML\Requirements; class WordPress { public static function checkMinimumRequiredVersion() { if ( version_compare( $GLOBALS['wp_version'], '4.4', '<' ) ) { add_action( 'admin_notices', [ __CLASS__, 'displayMissingVersionRequirementNotice' ] ); return false; } return true; } public static function displayMissingVersionRequirementNotice() { ?> <div class="error"> <p><?php esc_html_e( 'WPML is disabled because it requires WordPress version 4.4 or above.', 'sitepress' ); ?></p> </div> <?php } } ajax/Locale.php 0000755 00000000666 14720342453 0007416 0 ustar 00 <?php namespace WPML\Ajax; class Locale implements \IWPML_AJAX_Action, \IWPML_DIC_Action { /** @var \SitePress */ private $sitePress; public function __construct( \SitePress $sitePress ) { $this->sitePress = $sitePress; } public function add_hooks() { if ( is_admin() && ! $this->sitePress->check_if_admin_action_from_referer() ) { add_filter( 'determine_locale', [ $this->sitePress, 'locale_filter' ], 10, 1 ); } } } ajax/Factory.php 0000755 00000003424 14720342453 0007621 0 ustar 00 <?php namespace WPML\Ajax; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Json; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\System\System; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Nonce; use function WPML\Container\execute; use function WPML\Container\make; use function WPML\FP\pipe; use function WPML\FP\System\getFilterFor as filter; use function WPML\FP\System\getValidatorFor as validate; class Factory implements \IWPML_AJAX_Action { public function add_hooks() { // :: Collection -> Collection $filterEndPoint = filter( 'endpoint' )->using( 'wp_unslash' ); // :: Collection -> Collection $decodeData = filter( 'data' )->using( Json::toCollection() )->defaultTo( 'wpml_collect' ); // :: Collection -> Either::Left( string ) | Either::Right( Collection ) $validateData = validate( 'data' )->using( Logic::isNotNull() )->error( 'Invalid json data' ); // $handleRequest :: Collection -> Either::Left(string) | Either::Right(mixed) $handleRequest = function ( Collection $postData ) { try { return Maybe::of( $postData->get( 'endpoint' ) ) ->map( pipe( make(), Lst::makePair( Fns::__, 'run' ) ) ) ->map( execute( Fns::__, [ ':data' => $postData->get( 'data' ) ] ) ) ->getOrElse( Either::left( 'End point not found' ) ); } catch ( \Exception $e ) { return Either::left( $e->getMessage() ); } }; Hooks::onAction( 'wp_ajax_wpml_action' ) ->then( System::getPostData() ) // Either::right(Collection) ->then( $filterEndPoint ) ->then( Nonce::verifyEndPoint() ) ->then( $decodeData ) ->then( $validateData ) ->then( $handleRequest ) ->then( 'wp_send_json_success' ) ->onError( 'wp_send_json_error' ); } } ajax/interface-wpml-ajax-action-run.php 0000755 00000000103 14720342453 0014114 0 ustar 00 <?php interface IWPML_AJAX_Action_Run { public function run(); } ajax/class-wpml-tm-ajax.php 0000755 00000001120 14720342453 0011622 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_AJAX { /** * @param string $action * * @return bool */ protected function is_valid_request( $action = '' ) { if ( ! $action ) { $action = array_key_exists( 'action', $_POST ) ? $_POST['action'] : ''; } if ( ! array_key_exists( 'nonce', $_POST ) || ! $action || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), $action ) ) { wp_send_json_error( __( 'You have attempted to submit data in a not legit way.', 'wpml-translation-management' ) ); return false; } return true; } } ajax/class-wpml-ajax-factory.php 0000755 00000000462 14720342453 0012661 0 ustar 00 <?php abstract class WPML_Ajax_Factory { public function add_route( WPML_Ajax_Route $route ) { $class_names = $this->get_class_names(); foreach ( $class_names as $class_name ) { $route->add( $class_name ); } } abstract function get_class_names(); abstract function create( $class_name ); } ajax/class-wpml-ajax-route.php 0000755 00000001261 14720342453 0012346 0 ustar 00 <?php class WPML_Ajax_Route { const ACTION_PREFIX = 'wp_ajax_'; const ACTION_PREFIX_LENGTH = 8; /** @var WPML_Ajax_Factory $factory */ private $factory; public function __construct( WPML_Ajax_Factory $factory ) { $this->factory = $factory; $this->factory->add_route( $this ); } public function add( $class_name ) { add_action( self::ACTION_PREFIX . $class_name, array( $this, 'do_ajax' ) ); } public function do_ajax() { $action = current_filter(); $class_name = substr( $action, self::ACTION_PREFIX_LENGTH ); $ajax_handler = $this->factory->create( $class_name ); $ajax_response = $ajax_handler->run(); $ajax_response->send_json(); } } ajax/endpoints/Upload.php 0000755 00000005554 14720342453 0011447 0 ustar 00 <?php namespace WPML\Ajax\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Obj; use function WPML\FP\curryN; class Upload implements IHandler { public function run( Collection $data ) { $upload = curryN( 2, function ( $path, $file ) { $upload_dir = function ( $uploads ) use ( $path ) { if ( $path ) { $uploads['path'] = $uploads['basedir'] . '/' . $path; $uploads['url'] = $uploads['baseurl'] . '/' . $path; $uploads['subdir'] = $path; } return $uploads; }; add_filter( 'upload_dir', $upload_dir ); $upload = wp_handle_upload( $file, [ 'test_form' => false ] ); remove_filter( 'upload_dir', $upload_dir ); return $upload; } ); $resize = curryN( 2, function ( $settings, $file ) { list( $max_width, $max_height, $crop ) = Obj::props( [ 'max_w', 'max_h', 'crop' ], $settings ); if ( $max_width && $max_height && function_exists( 'wp_get_image_editor' ) && 'image/svg+xml' !== $file['type'] ) { $image = wp_get_image_editor( $file['file'] ); if ( ! is_wp_error( $image ) ) { $image->resize( $max_width, $max_height, (bool) $crop ); $image->save( $file['file'] ); } } return $file; } ); $handleError = function ( $file ) { if ( Obj::has( 'error', $file ) ) { $error_message = __( 'There was an error uploading the file, please try again!', 'sitepress' ); switch ( $file['error'] ) { case UPLOAD_ERR_INI_SIZE; $error_message = __( 'The uploaded file exceeds the upload_max_filesize directive in php.ini.', 'sitepress' ); break; case UPLOAD_ERR_FORM_SIZE; $error_message = sprintf( __( 'The uploaded file exceeds %s bytes.', 'sitepress' ), 100000 ); break; case UPLOAD_ERR_PARTIAL; $error_message = __( 'The uploaded file was only partially uploaded.', 'sitepress' ); break; case UPLOAD_ERR_NO_FILE; $error_message = __( 'No file was uploaded.', 'sitepress' ); break; case UPLOAD_ERR_NO_TMP_DIR; $error_message = __( 'Missing a temporary folder.', 'sitepress' ); break; case UPLOAD_ERR_CANT_WRITE; $error_message = __( 'Failed to write file to disk.', 'sitepress' ); break; case UPLOAD_ERR_EXTENSION; $error_message = __( 'A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help.', 'sitepress' ); break; } return Either::left( $error_message ); } else { return Either::right( $file ); } }; return Either::fromNullable( $data->get( 'name' ) ) ->map( Obj::prop( Fns::__, $_FILES ) ) ->map( $upload( $data->get( 'path' ) ) ) ->chain( $handleError ) ->map( $resize( $data->get( 'resize', [] ) ) ); } } ajax/class-wpml-ajax-response.php 0000755 00000001541 14720342453 0013047 0 ustar 00 <?php class WPML_Ajax_Response { private $success; private $response_data; public function __construct( $success, $response_data ) { $this->success = $success; $this->response_data = $response_data; } public function send_json() { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { if ( $this->success ) { wp_send_json_success( $this->response_data ); } else { wp_send_json_error( $this->response_data ); } } else { throw new WPML_Not_Doing_Ajax_On_Send_Exception( $this ); } } public function is_success() { return $this->success; } public function get_response() { return $this->response_data; } } class WPML_Not_Doing_Ajax_On_Send_Exception extends Exception { public $response; public function __construct( $response ) { parent::__construct( 'Not doing AJAX' ); $this->response = $response; } }; shortcodes/class-wpml-tm-shortcodes-catcher-factory.php 0000755 00000000467 14720342453 0017377 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Shortcodes_Catcher_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { /** * @return IWPML_Action|IWPML_Action[]|null */ public function create() { return new WPML_TM_Shortcodes_Catcher(); } } shortcodes/class-wpml-tm-shortcodes-catcher.php 0000755 00000001173 14720342453 0015725 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_Shortcodes_Catcher implements IWPML_Action { public function add_hooks() { add_filter( 'pre_do_shortcode_tag', array( $this, 'register_shortcode' ), 10, 2 ); } public function register_shortcode( $return, $tag ) { if ( $tag ) { $registered_shortcodes = get_option( WPML_TM_XLIFF_Shortcodes::SHORTCODE_STORE_OPTION_KEY, array() ); if ( ! in_array( $tag, $registered_shortcodes, true ) ) { $registered_shortcodes[] = $tag; update_option( WPML_TM_XLIFF_Shortcodes::SHORTCODE_STORE_OPTION_KEY, $registered_shortcodes, false ); } } return $return; } } class-wpml-current-screen.php 0000755 00000005416 14720342453 0012311 0 ustar 00 <?php /** * Created by PhpStorm. * User: andreasciamanna * Date: 22/05/2018 * Time: 08:44 */ class WPML_Current_Screen { private $translatable_types = array(); private $allowed_screen_ids_for_edit_posts_list = array(); private $allowed_screen_ids_for_edit_post = array(); public function is_edit_posts_list() { return $this->get() && in_array( $this->get()->id, $this->get_allowed_screen_ids_for_edit_posts_list() ) && $this->has_posts(); } public function is_edit_post() { return $this->get() && in_array( $this->get()->id, $this->get_allowed_screen_ids_for_edit_post() ) && $this->has_post(); } private function get_translatable_types() { if ( ! $this->translatable_types ) { $translatable_types = apply_filters( 'wpml_translatable_documents', array() ); if ( $translatable_types ) { $this->translatable_types = array_keys( $translatable_types ); } } return $this->translatable_types; } private function get_allowed_screen_ids_for_edit_posts_list() { if ( ! $this->allowed_screen_ids_for_edit_posts_list ) { foreach ( $this->get_translatable_types() as $translatable_type ) { $this->allowed_screen_ids_for_edit_posts_list[] = 'edit-' . $translatable_type; } } return $this->allowed_screen_ids_for_edit_posts_list; } private function get_allowed_screen_ids_for_edit_post() { if ( ! $this->allowed_screen_ids_for_edit_post ) { foreach ( $this->get_translatable_types() as $translatable_type ) { $this->allowed_screen_ids_for_edit_post[] = $translatable_type; } } return $this->allowed_screen_ids_for_edit_post; } public function get_posts() { if ( $this->has_posts() && $this->is_edit_posts_list() ) { return $GLOBALS['posts']; } elseif ( $this->has_post() && $this->is_edit_post() ) { $post = $this->get_post(); if ( $post ) { return array( $post ); } } return array(); } private function get_post() { if ( $this->has_post() && $this->is_edit_post() ) { $post_id = filter_var( $_GET['post'], FILTER_SANITIZE_NUMBER_INT ); $post_type = $this->get_post_type(); return get_post( (int) $post_id, $post_type ); } return null; } public function get_post_type() { return $this->get() ? $this->get()->post_type : null; } public function id_ends_with( $suffix ) { return $this->get() && ( substr( $this->get()->id, - strlen( $suffix ) ) === $suffix ); } /** * @return WP_Screen|null */ private function get() { return array_key_exists( 'current_screen', $GLOBALS ) ? $GLOBALS['current_screen'] : null; } private function has_posts() { return $this->get() && ( array_key_exists( 'posts', $GLOBALS ) ) && $GLOBALS['posts']; } private function has_post() { return $this->get() && array_key_exists( 'post', $_GET ); } } wizard/class-wpml-tm-wizard-options.php 0000755 00000000614 14720342453 0014254 0 ustar 00 <?php class WPML_TM_Wizard_Options { const CURRENT_STEP = 'WPML_TM_Wizard_For_Manager_Current_Step'; const WIZARD_COMPLETE_FOR_MANAGER = 'WPML_TM_Wizard_For_Manager_Complete'; const WIZARD_COMPLETE_FOR_ADMIN = 'WPML_TM_Wizard_For_Admin_Complete'; const WHO_WILL_TRANSLATE_MODE = 'WPML_TM_Wizard_Who_Mode'; const ONLY_I_USER_META = 'WPML_TM_Wizard_Only_I'; } translation-jobs/notices/class-wpml-tm-unsent-jobs-notice-hooks.php 0000755 00000004143 14720342453 0021570 0 ustar 00 <?php /** * Class WPML_TM_Unsent_Jobs_Notifications_Hooks */ class WPML_TM_Unsent_Jobs_Notice_Hooks { /** @var string */ protected $dismissed_option_key; /** * @var WPML_TM_Unsent_Jobs_Notice */ private $wpml_tm_notice_email_notice; /** * @var WPML_Notices */ private $wpml_admin_notices; /** * @var WPML_WP_API */ private $wp_api; /** * WPML_TM_Unsent_Jobs_Notice_Hooks constructor. * * @param WPML_TM_Unsent_Jobs_Notice $wpml_tm_notice_email_notice * @param WPML_WP_API $wp_api * @param string $dismissed_option_key */ public function __construct( WPML_TM_Unsent_Jobs_Notice $wpml_tm_notice_email_notice, WPML_WP_API $wp_api, $dismissed_option_key ) { $this->wpml_tm_notice_email_notice = $wpml_tm_notice_email_notice; $this->wpml_admin_notices = wpml_get_admin_notices(); $this->wp_api = $wp_api; $this->dismissed_option_key = $dismissed_option_key; } public function add_hooks() { add_action( 'wpml_tm_jobs_translator_notification', array( $this, 'email_for_job' ) ); add_action( 'wpml_tm_basket_committed', array( $this, 'add_notice' ) ); add_action( 'shutdown', array( $this, 'remove_notice' ) ); } /** * @param array $args */ public function email_for_job( $args ) { $job_set = array_key_exists( 'job', $args ) && $args['job']; $event_set = array_key_exists( 'event', $args ) && $args['event']; if ( $job_set && $event_set ) { if ( 'unsent' === $args['event'] ) { $this->wpml_tm_notice_email_notice->add_job( $args ); } else { $this->wpml_tm_notice_email_notice->remove_job( $args ); } } } public function add_notice() { $this->wpml_tm_notice_email_notice->add_notice( $this->wpml_admin_notices, $this->get_dismissed_option_key() ); } public function remove_notice() { if ( $this->wp_api->is_jobs_tab() ) { $this->wpml_admin_notices->remove_notice( WPML_TM_Unsent_Jobs_Notice::NOTICE_GROUP_ID, WPML_TM_Unsent_Jobs_Notice::NOTICE_ID ); } } /** * @return string */ private function get_dismissed_option_key() { return $this->dismissed_option_key; } } translation-jobs/notices/class-wpml-tm-unsent-jobs-notice.php 0000755 00000010022 14720342453 0020440 0 ustar 00 <?php use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Environment; /** * Class WPML_TM_Unsent_Jobs_Notice */ class WPML_TM_Unsent_Jobs_Notice { const OPT_JOBS_NOT_NOTIFIED = '_wpml_jobs_not_notified'; const NOTICE_ID = 'job-not-notified'; const NOTICE_GROUP_ID = 'tm-jobs-notification'; /** * @var string */ private $body; /** * @var WPML_WP_API */ private $wp_api; /** * @var WPML_TM_Unsent_Jobs_Notice_Template */ private $notice_template; /** * WPML_TM_Unsent_Jobs_Notice constructor. * * @param WPML_WP_API $wp_api * @param WPML_TM_Unsent_Jobs_Notice_Template|null $notice_template */ public function __construct( WPML_WP_API $wp_api, WPML_TM_Unsent_Jobs_Notice_Template $notice_template = null ) { $this->wp_api = $wp_api; $this->notice_template = $notice_template; } private function prepare_notice_body() { $this->body = $this->get_notice_template()->get_notice_body( $this->get_jobs() ); } /** * @return null|WPML_TM_Unsent_Jobs_Notice_Template */ private function get_notice_template() { if ( ! $this->notice_template ) { $template_paths = array( WPML_TM_PATH . '/templates/notices/', ); $twig_loader = new Twig_Loader_Filesystem( $template_paths ); $environment_args = array(); if ( WP_DEBUG ) { $environment_args['debug'] = true; } $twig = new Twig_Environment( $twig_loader, $environment_args ); $twig_service = new WPML_Twig_Template( $twig ); $this->notice_template = new WPML_TM_Unsent_Jobs_Notice_Template( $twig_service ); } return $this->notice_template; } /** * @param WPML_Notices $wpml_admin_notices */ public function add_notice( WPML_Notices $wpml_admin_notices, $dismissed_option_key ) { if ( $this->get_jobs() ) { $this->prepare_notice_body(); $notice = new WPML_Notice( self::NOTICE_ID, $this->body, 'tm-jobs-notification' ); $notice->set_css_class_types( 'info' ); $notice->add_display_callback( array( $this->wp_api, 'is_jobs_tab' ) ); $this->add_actions( $notice ); $this->remove_notice_from_dismissed_list( self::NOTICE_GROUP_ID, $dismissed_option_key ); $wpml_admin_notices->add_notice( $notice ); $this->update_jobs_option( array() ); } } private function remove_notice_from_dismissed_list( $notice_group_id, $dismissed_option_key ) { $dismissed_notices = get_option( $dismissed_option_key ); if ( is_array( $dismissed_notices ) ) { foreach ( (array) $dismissed_notices as $key => $notices ) { if ( $key === $notice_group_id ) { unset( $dismissed_notices[ $key ] ); } } update_option( $dismissed_option_key, $dismissed_notices ); } } /** * @param WPML_Notice $notice */ private function add_actions( WPML_Notice $notice ) { $dismiss_action = new WPML_Notice_Action( __( 'Dismiss', 'wpml-translation-management' ), '#', true, false, false, true ); $notice->add_action( $dismiss_action ); } /** * @param array $args */ public function add_job( $args ) { $job_id = $args['job']->get_id(); $lang_from = $args['job']->get_source_language_code( true ); $lang_to = $args['job']->get_language_code( true ); $jobs = $this->get_jobs(); if ( ! wp_filter_object_list( $jobs, array( 'job_id' => $job_id ) ) ) { $jobs[] = array( 'job_id' => $job_id, 'lang_from' => $lang_from, 'lang_to' => $lang_to, ); $this->update_jobs_option( $jobs ); } } /** * @param array $args */ public function remove_job( $args ) { $job_id = $args['job']->get_id(); $unsent_jobs = $this->get_jobs(); if ( $unsent_jobs ) { foreach ( $unsent_jobs as $key => $unsent_job ) { if ( $unsent_job['job_id'] === $job_id ) { unset( $unsent_jobs[ $key ] ); } } } $this->update_jobs_option( $unsent_jobs ); } /** * @param array $jobs */ private function update_jobs_option( $jobs ) { update_option( self::OPT_JOBS_NOT_NOTIFIED, $jobs ); } /** * @return array */ private function get_jobs() { return get_option( self::OPT_JOBS_NOT_NOTIFIED ); } } translation-jobs/notices/class-wpml-tm-unsent-jobs-notice-template.php 0000755 00000004332 14720342453 0022260 0 ustar 00 <?php /** * Class WPML_TM_Unsent_Jobs_Notice_Template */ class WPML_TM_Unsent_Jobs_Notice_Template { const TEMPLATE_FILE = 'jobs-not-notified.twig'; /** * @var WPML_Twig_Template */ private $template_service; /** * WPML_TM_Unsent_Jobs_Notice_Template constructor. * * @param IWPML_Template_Service $template_service */ public function __construct( IWPML_Template_Service $template_service ) { $this->template_service = $template_service; } /** * @param array $jobs * * @return string */ public function get_notice_body( $jobs ) { $model = $this->get_notice_model( $jobs ); return $this->template_service->show( $model, self::TEMPLATE_FILE ); } /** * @param array $jobs * * @return array */ private function get_notice_model( $jobs ) { $translators_tab = 'admin.php?page=' . WPML_TM_FOLDER . '/menu/main.php&sm=translators'; $jobs_formatted = $this->get_formatted_jobs( $jobs ); $model = array( 'strings' => array( 'title' => esc_html__( 'Translations may delay because translators did not receive notifications', 'wpml-translation-management' ), 'body' => esc_html__( 'You have sent documents to translation. WPML can send notification emails to assigned translators, but translators for some languages have selected not to receive this notification.', 'wpml-translation-management' ), 'jobs' => $jobs_formatted, 'bottom' => sprintf( esc_html__( 'You should contact your %1$s and ask them to enable the notification emails which will allow them to see when there is new work waiting for them. To enable notifications, translators need to log-in to this site, go to their user profile page and change the related option in the WPML language settings section.', 'wpml-translation-management' ), '<a href="' . admin_url( $translators_tab ) . '">' . esc_html__( 'translators' ) . '</a> ' ), ), ); return $model; } /** * @param array $jobs * * @return array */ private function get_formatted_jobs( $jobs ) { $jobs_formatted = array(); foreach ( $jobs as $job ) { $jobs_formatted[] = sprintf( __( 'Job %1$s: %2$s - %3$s', 'wpml-translation-management' ), $job['job_id'], $job['lang_from'], $job['lang_to'] ); } return $jobs_formatted; } } translation-jobs/class-wpml-tm-field-type-encoding.php 0000755 00000003530 14720342453 0017102 0 ustar 00 <?php /** * Class WPML_TM_Field_Type_Encoding */ class WPML_TM_Field_Type_Encoding { const CUSTOM_FIELD_KEY_SEPARATOR = ':::'; /** * @param string $custom_field_name * @param array $attributes * * @return array */ public static function encode( $custom_field_name, $attributes ) { $encoded_index = $custom_field_name; foreach ( $attributes as $index ) { $encoded_index .= '-' . self::encode_hyphen( $index ); } return array( 'field-' . $encoded_index, $encoded_index ); } /** * @param string $string * * @return string */ public static function encode_hyphen( $string ) { return str_replace( '-', self::CUSTOM_FIELD_KEY_SEPARATOR, $string ); } /** * Get the custom field name and the attributes from the custom field job. * * @param string $custom_field_job_type - e.g: field-my_custom_field-0-attribute. * * @return array An array with field name and attributes */ public static function decode( $custom_field_job_type ) { $custom_field_name = ''; $attributes = array(); $parts = explode( '-', $custom_field_job_type ); $count = count( $parts ); if ( $count > 2 && 'field' === $parts[0] ) { $custom_field_name = $parts[1]; $complete_custom_field_name_found = false; for ( $i = 2; $i < $count; $i ++ ) { if ( ! $complete_custom_field_name_found && is_numeric( $parts[ $i ] ) ) { $complete_custom_field_name_found = true; continue; } if ( ! $complete_custom_field_name_found ) { $custom_field_name .= '-' . $parts[ $i ]; } else { $attributes[] = self::decode_hyphen( $parts[ $i ] ); } } } return array( $custom_field_name, $attributes ); } /** * @param string $string * * @return string */ public static function decode_hyphen( $string ) { return str_replace( self::CUSTOM_FIELD_KEY_SEPARATOR, '-', $string ); } } translation-jobs/ExtraFieldDataInEditor.php 0000755 00000012661 14720342453 0015042 0 ustar 00 <?php namespace WPML\TM\Jobs; use WPML\FP\Obj; use WPML\FP\Str; use WPML\FP\Relation; use WPML\FP\Fns; use function WPML\FP\pipe; class ExtraFieldDataInEditor implements \IWPML_Backend_Action { const MAX_ALLOWED_SINGLE_LINE_LENGTH = 50; /** @var \WPML_Custom_Field_Editor_Settings */ private $customFieldEditorSettings; public function __construct( \WPML_Custom_Field_Editor_Settings $customFieldEditorSettings ) { $this->customFieldEditorSettings = $customFieldEditorSettings; } public function add_hooks() { add_filter( 'wpml_tm_adjust_translation_fields', [ $this, 'appendTitleAndStyle' ], 1, 3 ); } public function appendTitleAndStyle( array $fields, $job, $originalPost ) { $appendTitleAndStyleStrategy = $this->isExternalElement( $job ) ? $this->appendToExternalField( $originalPost ) : $this->addTitleAndAdjustStyle( $job, $originalPost ); return Fns::map( pipe( $appendTitleAndStyleStrategy, $this->adjustFieldStyleForUnsafeContent() ), $fields ); } private function addTitleAndAdjustStyle( $job, $originalPost ) { return function ( $field ) use ( $job, $originalPost ) { if ( FieldId::is_a_custom_field( $field['field_type'] ) ) { return $this->appendToCustomField( $field, $job, $originalPost ); } elseif ( FieldId::is_a_term( $field['field_type'] ) ) { return $this->appendToTerm( $field ); } return $this->appendToRegularField( $field ); }; } private function isExternalElement( $job ) { return isset( $job->element_type_prefix ) && wpml_load_core_tm()->is_external_type( $job->element_type_prefix ); } private function appendToExternalField( $originalPost ) { return function ( $field ) use ( $originalPost ) { $field['title'] = apply_filters( 'wpml_tm_editor_string_name', $field['field_type'], $originalPost ); $field['field_style'] = $this->applyStyleFilter( Obj::propOr( '', 'field_style', $field ), $field['field_type'], $originalPost ); return $field; }; } private function appendToCustomField( $field, $job, $originalPost ) { $title = $this->getCustomFieldTitle( $field ); $style = $this->getCustomFieldStyle( $field ); $field = (array) apply_filters( 'wpml_editor_cf_to_display', (object) $field, $job ); $field['title'] = $title; $field['field_style'] = $this->getAdjustedFieldStyle( $field, $style ); $field['field_style'] = $this->applyStyleFilter( $field['field_style'], $field['field_type'], $originalPost ); return $field; } private function appendToTerm( $field ) { $field['title'] = ''; return $field; } private function applyStyleFilter( $style, $type, $originalPost ) { return (string) apply_filters( 'wpml_tm_editor_string_style', $style, $type, $originalPost ); } private function appendToRegularField( $field ) { $field['title'] = \wpml_collect( [ 'title' => __( 'Title', 'wpml-translation-management' ), 'body' => __( 'Body', 'wpml-translation-management' ), 'excerpt' => __( 'Excerpt', 'wpml-translation-management' ), ] )->get( $field['field_type'], $field['field_type'] ); if ( $field['field_type'] === 'excerpt' ) { $field['field_style'] = '1'; } return $field; } private function getCustomFieldTitle( $field ) { $unfiltered_type = \WPML_TM_Field_Type_Sanitizer::sanitize( $field['field_type'] ); $element_field_type = $unfiltered_type; /** * @deprecated Use `wpml_editor_custom_field_name` filter instead * @since 3.2 */ $element_field_type = apply_filters( 'icl_editor_cf_name', $element_field_type ); $element_field_type = apply_filters( 'wpml_editor_custom_field_name', $element_field_type ); return $this->customFieldEditorSettings->filter_name( $unfiltered_type, $element_field_type ); } private function getCustomFieldStyle( $field ) { $type = \WPML_TM_Field_Type_Sanitizer::sanitize( $field['field_type'] ); $style = Str::includes( "\n", $field['field_data'] ) ? 1 : 0; /** * @deprecated Use `wpml_editor_custom_field_style` filter instead * @since 3.2 */ $style = apply_filters( 'icl_editor_cf_style', $style, $type ); $style = apply_filters( 'wpml_editor_custom_field_style', $style, $type ); return $this->customFieldEditorSettings->filter_style( $type, $style ); } private function getAdjustedFieldStyle( array $field, $style ) { /** * wpml_tm_editor_max_allowed_single_line_length filter * * Filters the value of `\WPML_Translation_Editor_UI::MAX_ALLOWED_SINGLE_LINE_LENGTH` * * @param int $max_allowed_single_line_length MAX_ALLOWED_SINGLE_LINE_LENGTH The length of the string, after which it must use a multiline input * @param array $field The generic field data * @param array $custom_field_data The custom field specific data * * @since 2.3.1 */ $maxAllowedLength = (int) apply_filters( 'wpml_tm_editor_max_allowed_single_line_length', self::MAX_ALLOWED_SINGLE_LINE_LENGTH, $field, [ $field['title'], $style, $field ] ); return 0 === (int) $style && strlen( $field['field_data'] ) > $maxAllowedLength ? '1' : $style; } private function adjustFieldStyleForUnsafeContent() { return function ( array $field ) { if ( Relation::propEq( 'field_style', '2', $field ) ) { $black_list = [ 'script', 'style', 'iframe' ]; $black_list_pattern = '#</?(' . implode( '|', $black_list ) . ')[^>]*>#i'; if ( preg_replace( $black_list_pattern, '', $field['field_data'] ) !== $field['field_data'] ) { $field['field_style'] = '1'; } } return $field; }; } } translation-jobs/class-wpml-tm-job-layout.php 0000755 00000010102 14720342453 0015332 0 ustar 00 <?php use \WPML\TM\Jobs\FieldId; class WPML_TM_Job_Layout { private $layout = array(); private $custom_fields = array(); private $grouped_custom_fields = array(); private $terms = array(); private $wp_api; public $wpdb; public function __construct( wpdb $wpdb, WPML_WP_API $wp_api ) { $this->wpdb = $wpdb; $this->wp_api = $wp_api; } public function get_wpdb() { return $this->wpdb; } public function run( array $fields, $tm_instance = null ) { foreach ( $fields as $field ) { $this->layout[] = $field['field_type']; } $this->order_main_fields(); $this->extract_terms(); $this->extract_custom_fields( $tm_instance ); $this->append_terms(); $this->append_grouped_custom_fields(); $this->append_custom_fields(); return apply_filters( 'wpml_tm_job_layout', array_values( $this->layout ) ); } private function order_main_fields() { $ordered_elements = array(); foreach ( array( 'title', 'body', 'excerpt' ) as $type ) { foreach ( $this->layout as $key => $element ) { if ( $element === $type ) { unset( $this->layout[ $key ] ); $ordered_elements[] = $type; } } } $this->layout = array_merge( $ordered_elements, $this->layout ); } private function extract_custom_fields( $tm_instance ) { foreach ( $this->layout as $key => $field ) { if ( FieldId::is_a_custom_field( $field ) ) { $group = $this->get_group_custom_field_belongs_to( $field, $tm_instance ); if ( $group ) { if ( ! isset( $this->grouped_custom_fields[ $group ] ) ) { $this->grouped_custom_fields[ $group ] = array(); } $this->grouped_custom_fields[ $group ][] = $field; } else { $this->custom_fields[] = $field; } unset( $this->layout[ $key ] ); } } } private function get_group_custom_field_belongs_to( $field, $tm_instance ) { $group = ''; if ( $tm_instance ) { $settings = new WPML_Custom_Field_Editor_Settings( new WPML_Custom_Field_Setting_Factory( $tm_instance ) ); $group = $settings->get_group( WPML_TM_Field_Type_Sanitizer::sanitize( $field ) ); } return $group; } private function extract_terms() { foreach ( $this->layout as $key => $field ) { if ( FieldId::is_any_term_field( $field ) ) { $this->terms[] = $field; unset( $this->layout[ $key ] ); } } } private function append_grouped_custom_fields() { foreach ( $this->grouped_custom_fields as $group => $fields ) { $data = array( 'field_type' => 'tm-section', 'title' => $group, 'fields' => $fields, 'empty' => false, 'empty_message' => '', 'sub_title' => '', ); $this->layout[] = $data; } } private function append_custom_fields() { if ( count( $this->custom_fields ) ) { $data = array( 'field_type' => 'tm-section', 'title' => __( 'Custom Fields', 'wpml-translation-management' ), 'fields' => $this->custom_fields, 'empty' => false, 'empty_message' => '', 'sub_title' => '', ); $this->layout[] = $data; } } private function append_terms() { if ( count( $this->terms ) ) { $taxonomy_fields = []; foreach ( $this->terms as $term ) { $term_id = FieldId::get_term_id( $term ); $query = $this->wpdb->prepare( "SELECT taxonomy FROM {$this->wpdb->term_taxonomy} WHERE term_taxonomy_id = %d", $term_id ); $taxonomy = $this->wpdb->get_var( $query ); if ( ! isset( $taxonomy_fields[ $taxonomy ] ) ) { $taxonomy_fields[ $taxonomy ] = []; } $taxonomy_fields[ $taxonomy ][] = $term; } foreach ( $taxonomy_fields as $taxonomy => $fields ) { $taxonomy = $this->wp_api->get_taxonomy( $taxonomy ); $data = array( 'field_type' => 'tm-section', 'title' => $taxonomy->labels->name, 'fields' => $fields, 'empty' => false, 'empty_message' => '', 'sub_title' => __( 'Changes in these translations will affect terms in general! (Not only for this post)', 'wpml-translation-management' ), ); $this->layout[] = $data; } } } } translation-jobs/ExtraFieldDataInEditorFactory.php 0000755 00000000532 14720342453 0016364 0 ustar 00 <?php namespace WPML\TM\Jobs; class ExtraFieldDataInEditorFactory implements \IWPML_Backend_Action_Loader { /** * @return ExtraFieldDataInEditor */ public function create() { return new ExtraFieldDataInEditor( new \WPML_Custom_Field_Editor_Settings( new \WPML_Custom_Field_Setting_Factory( wpml_load_core_tm() ) ) ); } } translation-jobs/class-wpml-tm-field-content-action.php 0000755 00000010077 14720342453 0017266 0 ustar 00 <?php use WPML\FP\Obj; use \WPML\FP\Relation; use WPML\FP\Lst; use function WPML\FP\pipe; class WPML_TM_Field_Content_Action extends WPML_TM_Job_Factory_User { /** @var int $job_id */ protected $job_id; /** * WPML_TM_Field_Content_Action constructor. * * @param WPML_Translation_Job_Factory $job_factory * @param int $job_id * * @throws \InvalidArgumentException */ public function __construct( $job_factory, $job_id ) { parent::__construct( $job_factory ); if ( ! ( is_int( $job_id ) && $job_id > 0 ) ) { throw new InvalidArgumentException( 'Invalid job id provided, received: ' . serialize( $job_id ) ); } $this->job_id = $job_id; } /** * Returns an array containing job fields * * @return array * @throws \RuntimeException */ public function run() { try { $job = $this->job_factory->get_translation_job( $this->job_id, false, 1 ); if ( ! $job ) { throw new RuntimeException( 'No job found for id: ' . $this->job_id ); } return $this->content_from_elements( $job ); } catch ( Exception $e ) { throw new RuntimeException( 'Could not retrieve field contents for job_id: ' . $this->job_id, 0, $e ); } } /** * Extracts the to be retrieved content from given job elements * * @param stdClass $job * * @return array */ private function content_from_elements( $job ) { /** * @var array $elements * @var array $previous_version_element * @var stdClass $element */ $elements = $job->elements; $previous_version_elements = isset( $job->prev_version ) ? $job->prev_version->elements : array(); $data = array(); foreach ( $elements as $index => $element ) { $previous_element = $this->find_previous_version_element( $element, $previous_version_elements, $index ); $data[] = array( 'field_type' => sanitize_title( str_replace( WPML_TM_Field_Type_Encoding::CUSTOM_FIELD_KEY_SEPARATOR, '-', $element->field_type ) ), 'tid' => $element->tid, 'field_style' => $element->field_type === 'body' ? '2' : '0', 'field_finished' => $element->field_finished, 'field_data' => $this->sanitize_field_content( $element->field_data ), 'field_data_translated' => $this->sanitize_field_content( $element->field_data_translated ), 'diff' => $this->get_diff( $element, $previous_element ), 'field_wrap_tag' => $element->field_wrap_tag, ); } return $data; } private function find_previous_version_element( $element, $previous_version_elements, $index ) { $findByMatchingFieldType = Lst::find( pipe( Obj::prop( 'field_type' ), Relation::equals( $element->field_type ) ) ); $findByIndex = Obj::prop( $index ); return $findByMatchingFieldType( $previous_version_elements ) ?: $findByIndex( $previous_version_elements ); } private function has_diff( $element, $previous_element ) { if ( null === $previous_element ) { return false; } $current_data = $this->sanitize_field_content( $element->field_data ); $previous_data = $this->sanitize_field_content( $previous_element->field_data ); return $current_data !== $previous_data; } private function get_diff( $element, $previous_element ) { if ( null === $previous_element || ! $this->has_diff( $element, $previous_element ) ) { return null; } $current_data = $this->sanitize_field_content( $element->field_data ); $previous_data = $this->sanitize_field_content( $previous_element->field_data ); return wp_text_diff( $previous_data, $current_data ); } /** * @param string $content base64-encoded translation job field content * * @return string base64-decoded field content, with linebreaks turned into * paragraph html tags */ private function sanitize_field_content( $content ) { $decoded = base64_decode( $content ); if ( ! $this->is_html( $decoded ) && false !== strpos( $decoded, '\n' ) ) { $decoded = wpautop( $decoded ); } return $decoded; } private function is_html( $string ) { return $string !== strip_tags( $string ); } } translation-jobs/Utils.php 0000755 00000001360 14720342453 0011655 0 ustar 00 <?php namespace WPML\TM\Jobs; class Utils { /** * Inserts an element into an array, nested by keys. * Input ['a', 'b'] for the keys, an empty array for $array and $x for the value would lead to * [ 'a' => ['b' => $x ] ] being returned. * * @param string[] $keys indexes ordered from highest to lowest level. * @param mixed[] $array array into which the value is to be inserted. * @param mixed $value to be inserted. * * @return mixed[] */ public static function insertUnderKeys( $keys, $array, $value ) { $array[ $keys[0] ] = count( $keys ) === 1 ? $value : self::insertUnderKeys( array_slice( $keys, 1 ), ( isset( $array[ $keys[0] ] ) ? $array[ $keys[0] ] : [] ), $value ); return $array; } } translation-jobs/class-wpml-tm-job-action-factory.php 0000755 00000000712 14720342453 0016745 0 ustar 00 <?php class WPML_TM_Job_Action_Factory extends WPML_TM_Job_Factory_User { /** * @param int $job_id * * @return WPML_TM_Field_Content_Action * @throws \InvalidArgumentException */ public function field_contents( $job_id ) { return new WPML_TM_Field_Content_Action( $this->job_factory, $job_id ); } public function save_action( array $data ) { return new WPML_Save_Translation_Data_Action( $data, $this->job_factory->tm_records() ); } } translation-jobs/FieldId.php 0000755 00000011731 14720342453 0012060 0 ustar 00 <?php namespace WPML\TM\Jobs; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Str; use function WPML\FP\curryN; use function WPML\FP\pipe; /** * Class FieldId * * @package WPML\TM\Jobs * @method static callable|int get_term_id( ...$field ) - Curried :: string → int * @method static callable|int is_a_term_meta( ...$field ) - Curried :: string → bool * @method static callable|int is_a_custom_field( ...$field ) - Curried :: string → bool * @method static callable|int is_any_term_field( ...$field ) - Curried :: string → bool * @method static callable|string forTerm( ...$termId ) - Curried :: int → string * @method static callable|string forTermDescription( ...$termId ) - Curried :: int → string * @method static callable|string forTermMeta( ...$termId, $key ) - Curried :: int → string → string */ class FieldId { use Macroable; const TERM_PREFIX = 't_'; const TERM_DESCRIPTION_PREFIX = 'tdesc_'; const TERM_META_FIELD_PREFIX = 'tfield-'; const CUSTOM_FIELD_PREFIX = 'field-'; public static function init() { self::macro( 'is_any_term_field', /** @phpstan-ignore-next-line */ Logic::anyPass( [ self::is_a_term(), self::is_a_term_description(), self::is_a_term_meta() ] ) ); self::macro( 'get_term_id', curryN( 1, Logic::cond( [ [ self::is_a_term(), Str::sub( Str::len( self::TERM_PREFIX ) ) ], [ self::is_a_term_description(), Str::sub( Str::len( self::TERM_DESCRIPTION_PREFIX ) ) ], [ Fns::always( true ), pipe( Str::split( '-' ), Lst::last() ) ], ] ) ) ); /** @phpstan-ignore-next-line */ self::macro( 'forTerm', Str::concat( self::TERM_PREFIX ) ); /** @phpstan-ignore-next-line */ self::macro( 'forTermDescription', Str::concat( self::TERM_DESCRIPTION_PREFIX ) ); self::macro( 'forTermMeta', curryN( 2, function ( $termId, $key ) { return 'tfield-' . $key . '-' . $termId; } ) ); } /** * @param string $maybe_term * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-template P1 of string * @phpstan-template R of bool * * @phpstan-param ?A1 $maybe_term * * @phpstan-return ($maybe_term is P1 ? R : callable(P1=):R) */ public static function is_a_term( $maybe_term = null ) { return call_user_func_array( curryN( 1, function( $maybe_term ) { return Str::startsWith( self::TERM_PREFIX, $maybe_term ); } ), func_get_args() ); } /** * @param string $maybe_term_description * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-template P1 of string * @phpstan-template R of bool * * @phpstan-param ?A1 $maybe_term_description * * @phpstan-return ($maybe_term_description is P1 ? R : callable(P1=):R) */ public static function is_a_term_description( $maybe_term_description = null ) { return call_user_func_array( curryN( 1, function( $maybe_term_description ) { return Str::startsWith( self::TERM_DESCRIPTION_PREFIX, $maybe_term_description ); } ), func_get_args() ); } /** * @param string $maybe_term_meta * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-template P1 of string * @phpstan-template R of bool * * @phpstan-param ?A1 $maybe_term_meta * * @phpstan-return ($maybe_term_meta is P1 ? R : callable(P1=):R) */ public static function is_a_term_meta( $maybe_term_meta = null ) { return call_user_func_array( curryN( 1, function( $maybe_term_meta ) { return Str::startsWith( self::TERM_META_FIELD_PREFIX, $maybe_term_meta ); } ), func_get_args() ); } /** * @param string $maybe_custom_field * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-template P1 of string * @phpstan-template R of bool * * @phpstan-param ?A1 $maybe_custom_field * * @phpstan-return ($maybe_custom_field is P1 ? R : callable(P1=):R) */ public static function is_a_custom_field( $maybe_custom_field = null ) { return call_user_func_array( curryN( 1, function( $maybe_custom_field ) { return Str::startsWith( self::CUSTOM_FIELD_PREFIX, $maybe_custom_field ); } ), func_get_args() ); } /** * @param string $termMeta * * @return callable|string * * @phpstan-template A1 of string|curried * @phpstan-template P1 of string * @phpstan-template R of string * * @phpstan-param ?A1 $termMeta * * @phpstan-return ($termMeta is null ? callable(P1=):R : R) */ public static function getTermMetaKey( $termMeta = null ) { return call_user_func_array( curryN( 1, function( $termMeta ) { $getKey = pipe( Str::sub( Str::len( self::TERM_META_FIELD_PREFIX ) ), // K-E-Y-ID Str::split( '-' ), // [ K, E, Y, ID ] Lst::dropLast( 1 ), // [ K, E, Y ] Lst::join( '-' ) // K-E-Y ); return $getKey( $termMeta ); } ), func_get_args() ); } } FieldId::init(); translation-jobs/class-wpml-tm-job-action.php 0000755 00000000547 14720342453 0015306 0 ustar 00 <?php abstract class WPML_TM_Job_Action { /** @var WPML_TM_Job_Action_Factory $job_action_factory */ protected $job_action_factory; /** * WPML_TM_Job_Action constructor. * * @param WPML_TM_Job_Action_Factory $job_action_factory */ public function __construct( &$job_action_factory ) { $this->job_action_factory = &$job_action_factory; } } translation-jobs/TermMeta.php 0000755 00000014746 14720342453 0012307 0 ustar 00 <?php namespace WPML\TM\Jobs; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Str; use function WPML\FP\pipe; class TermMeta { /** * It returns translated term description stored inside wp_icl_translate * * @param int $iclTranslateJobId * @param int $termTaxonomyId * * @return string */ public static function getTermDescription( $iclTranslateJobId, $termTaxonomyId ) { global $wpdb; $sql = "SELECT field_data_translated FROM {$wpdb->prefix}icl_translate WHERE job_id = %d AND field_type = 'tdesc_%d'"; $description = $wpdb->get_var( $wpdb->prepare( $sql, $iclTranslateJobId, $termTaxonomyId ) ); return $description ? base64_decode( $description ) : ''; } /** * It returns term meta stored inside wp_icl_translate table. * * Data has such format: * [ * (object)[ * field_type => 'some_scalar_field', * field_data_translated => 'Translated value' * ], * (object)[ * field_type => 'some_array_valued_field_like_checkboxes' * field_data_translated => [ * 'Translated option 1', 'Translated option 2', 'Translated option 3' * ] * ], * (object)[ * field_type => 'another_array_valued_field_like_checkboxes' * field_data_translated => [ * 'option1' => ['Translated option 1'], * 'option2' => ['Translated option 2'], * ] * ] * ] * * @param int $iclTranslateJobId * @param int $term_taxonomy_id * * @return array */ public static function getTermMeta( $iclTranslateJobId, $term_taxonomy_id ) { return array_merge( self::geRegularTermMeta( $iclTranslateJobId, $term_taxonomy_id ), self::getTermMetaWithArrayValue( $iclTranslateJobId, $term_taxonomy_id ) ); } /** * It returns term meta which have scalar values * * @param int $iclTranslateJobId * @param int $termTaxonomyId * * @return mixed[] */ private static function geRegularTermMeta( $iclTranslateJobId, $termTaxonomyId ) { global $wpdb; $sql = "SELECT field_data_translated, field_type FROM {$wpdb->prefix}icl_translate WHERE job_id = %d AND field_type LIKE 'tfield-%-%d'"; $rowset = $wpdb->get_results( $wpdb->prepare( $sql, $iclTranslateJobId, $termTaxonomyId ) ); return Fns::map( Obj::over( Obj::lensProp( 'field_data_translated' ), 'base64_decode' ), $rowset ); } /** * It returns term meta with array values grouped by term name. * * Custom field created by Toolset Types example: * * A term has checkboxes field with options: A, B, and C. They are stored in wp_icl_translate table as 3 entries under such field_type: * - tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1_0 * - tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-6cdwwdwdwdwdwwdwddwd2bb12fc2d1c4-1_0 * - tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-611111wdwdwdwwdwddwd2bb12fc2d1c4-1_0 * * Options translations are A fr, B fr, and C fr. * * Our goal is to group them into one entry: * (object) [ * field_type => 'tfield-wpcf-jakub-checkboxes-13' * field_data_translated => [ * wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1 => [ * 0 => 'A fr', * ], * wpcf-fields-checkboxes-option-6cdwwdwdwdwdwwdwddwd2bb12fc2d1c4-1 => [ * 0 => 'B fr', * ], * wpcf-fields-checkboxes-option-611111wdwdwdwwdwddwd2bb12fc2d1c4-1 => [ * 0 => 'C fr', * ], * ] * ] * * Custom field created by ACF example: * * ACF stores data in a slightly different way. Again, A term has checkboxes field with options A, B, C with the same translations A fr, B fr, C fr. * They are stored in wp_icl_translate in this way: * - tfield-jakub_checkboxes-13_0 * - tfield-jakub_checkboxes-13_1 * - tfield-jakub_checkboxes-13_2 * * Our goal is to group them into one entry: * (object)[ * field_type => 'tfield-jakub-checkboxes-13', * field_data_translated => [ * 0 => 'A fr', * 1 => 'B fr', * 2 => 'C fr' * ] * ] * * @param int $iclTranslateJobId * @param int $termTaxonomyId * * @return mixed[] */ private static function getTermMetaWithArrayValue( $iclTranslateJobId, $termTaxonomyId ) { global $wpdb; $sql = "SELECT field_data_translated, field_type FROM {$wpdb->prefix}icl_translate WHERE job_id = %d AND field_type LIKE 'tfield-%-%d_%'"; $rowset = $wpdb->get_results( $wpdb->prepare( $sql, $iclTranslateJobId, $termTaxonomyId ) ); /** * From field type like: tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1_0 * extracts core field name: wpcf-jakub-checkboxes-13 */ $extractFieldName = pipe( Obj::prop( 'field_type' ), Str::match( '/tfield-(.*)-\d/U' ), Obj::prop( 1 ) ); /** * From field type like: tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1_0 * extracts option name part: wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1_0 */ $extractOptions = function ( $row, $fieldName ) { return Str::pregReplace( "/tfield-{$fieldName}-\d+_/U", '', $row->field_type ); }; $groupOptions = function ( $carry, $row ) use ( $extractFieldName, $extractOptions ) { $fieldName = $extractFieldName( $row ); if ( ! isset( $carry[ $fieldName ] ) ) { $carry[ $fieldName ] = []; } /** @var string $options */ $options = $extractOptions( $row, $fieldName ); /** * If field_type is: tfield-wpcf-jakub-checkboxes-13_wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1_0 * then meta keys are: [wpcf-jakub-checkboxes-13, wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1, 0] */ $metaKeys = array_merge( [ $fieldName ], explode( '_', $options ) ); /** * Builds array like: * [wpcf-jakub-checkboxes-13 => [wpcf-fields-checkboxes-option-6c88acb978ec7f24eb6a2bb12fc2d1c4-1 => [0 => Translation_value ] ] ] * * If there are already data under wpcf-jakub-checkboxes-13 key, they are preserve too. The new values are appended. */ return Utils::insertUnderKeys( $metaKeys, $carry, base64_decode( $row->field_data_translated ) ); }; $recreateJobElement = function ( $data, $fieldType ) use ( $termTaxonomyId ) { return (object) [ 'field_type' => 'tfield-' . $fieldType . '-' . $termTaxonomyId, 'field_data_translated' => $data, ]; }; return Obj::values( Fns::map( $recreateJobElement, Fns::reduce( $groupOptions, [], $rowset ) ) ); } } translation-jobs/class-wpml-element-translation-package.php 0000755 00000037361 14720342453 0020225 0 ustar 00 <?php use \WPML\FP\Fns; use \WPML\TM\Jobs\FieldId; use \WPML\FP\Logic; use \WPML\FP\Lst; use \WPML\FP\Either; use \WPML\LIB\WP\Post; use function \WPML\FP\curryN; use function \WPML\FP\pipe; use function \WPML\FP\invoke; use WPML\TM\Jobs\Utils; use WPML\FP\Relation; /** * Class WPML_Element_Translation_Package * * @package wpml-core */ class WPML_Element_Translation_Package extends WPML_Translation_Job_Helper { /** @var WPML_WP_API $wp_api */ private $wp_api; /** * The constructor. * * @param WPML_WP_API $wp_api An instance of the WP API. */ public function __construct( WPML_WP_API $wp_api = null ) { global $sitepress; if ( $wp_api ) { $this->wp_api = $wp_api; } else { $this->wp_api = $sitepress->get_wp_api(); } } /** * Create translation package * * @param \WPML_Package|\WP_Post|int $post * @param bool $isOriginal * * @return array<string,string|array<string,string>> */ public function create_translation_package( $post, $isOriginal = false ) { $package = array(); $post = is_numeric( $post ) ? get_post( $post ) : $post; if ( apply_filters( 'wpml_is_external', false, $post ) ) { /** @var stdClass $post */ $post_contents = (array) $post->string_data; $original_id = isset( $post->post_id ) ? $post->post_id : $post->ID; $type = 'external'; if ( isset( $post->title ) ) { $package['title'] = apply_filters( 'wpml_tm_external_translation_job_title', $post->title, $original_id ); } if ( empty( $package['title'] ) ) { $package['title'] = sprintf( /* translators: The placeholder will be replaced with a number (an ID) */ __( 'External package ID: %d', 'wpml-translation-management' ), $original_id ); } } else { $home_url = get_home_url(); $package['url'] = htmlentities( $home_url . '?' . ( 'page' === $post->post_type ? 'page_id' : 'p' ) . '=' . ( $post->ID ) ); $package['title'] = $post->post_title; $post_contents = array( 'title' => $post->post_title, 'body' => $post->post_content, 'excerpt' => $post->post_excerpt, ); if ( wpml_get_setting_filter( false, 'translated_document_page_url' ) === 'translate' ) { $post_contents['URL'] = $post->post_name; } $original_id = $post->ID; $custom_fields_to_translate = \WPML\TM\Settings\Repository::getCustomFieldsToTranslate(); if ( ! empty( $custom_fields_to_translate ) ) { $package = $this->add_custom_field_contents( $package, $post, $custom_fields_to_translate, $this->get_tm_setting( array( 'custom_fields_encoding' ) ) ); } $post_contents = array_merge( $post_contents, $this->get_taxonomy_fields( $post ) ); $type = 'post'; } $package['contents']['original_id'] = array( 'translate' => 0, 'data' => $original_id, ); $package['type'] = $type; $package['contents'] = $this->buildEntries( $package['contents'], $post_contents ); return apply_filters( 'wpml_tm_translation_job_data', $package, $post, $isOriginal ); } private function buildEntries( $contents, $entries, $parentKey = '' ) { foreach ( $entries as $key => $entry ) { $fullKey = $parentKey ? $parentKey . '_' . $key : $key; if ( is_array( $entry ) ) { $contents = $this->buildEntries( $contents, $entry, $fullKey ); } else { $contents[ $fullKey ] = [ 'translate' => 1, 'data' => base64_encode( $entry ), 'format' => 'base64', ]; } } return $contents; } /** * @param array $translation_package * @param int $job_id * @param array $prev_translation */ public function save_package_to_job( array $translation_package, $job_id, $prev_translation ) { global $wpdb; $show = $wpdb->hide_errors(); foreach ( $translation_package['contents'] as $field => $value ) { $job_translate = array( 'job_id' => $job_id, 'content_id' => 0, 'field_type' => $field, 'field_wrap_tag' => isset( $value['wrap_tag'] ) ? $value['wrap_tag'] : '', 'field_format' => isset( $value['format'] ) ? $value['format'] : '', 'field_translate' => $value['translate'], 'field_data' => $value['data'], 'field_data_translated' => '', 'field_finished' => 0, ); if ( array_key_exists( $field, $prev_translation ) ) { $job_translate['field_data_translated'] = $prev_translation[ $field ]->get_translation(); $job_translate['field_finished'] = $prev_translation[ $field ]->is_finished( $value['data'] ); } $job_translate = $this->filter_non_translatable_fields( $job_translate ); $wpdb->insert( $wpdb->prefix . 'icl_translate', $job_translate ); } $wpdb->show_errors( $show ); } /** * @param array $job_translate * * @return mixed|void */ private function filter_non_translatable_fields( $job_translate ) { if ( $job_translate['field_translate'] ) { $data = $job_translate['field_data']; if ( 'base64' === $job_translate['field_format'] ) { // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $data = base64_decode( $data ); } $is_translatable = ! WPML_String_Functions::is_not_translatable( $data ) && apply_filters( 'wpml_translation_job_post_meta_value_translated', 1, $job_translate['field_type'] ); $is_translatable = (bool) apply_filters( 'wpml_tm_job_field_is_translatable', $is_translatable, $job_translate ); if ( ! $is_translatable ) { $job_translate['field_translate'] = 0; $job_translate['field_data_translated'] = $job_translate['field_data']; $job_translate['field_finished'] = 1; } } return $job_translate; } /** * @param object $job * @param int $post_id * @param array $fields */ public function save_job_custom_fields( $job, $post_id, $fields ) { $decode_translation = function ( $translation ) { // always decode html entities eg decode & to &. return html_entity_decode( str_replace( '�A;', "\n", $translation ) ); }; $get_field_id = function( $field_name, $el_data ) { if ( strpos( $el_data->field_data, (string) $field_name ) === 0 && 1 === preg_match( '/field-(.*?)-name/U', $el_data->field_type, $match ) && 1 === preg_match( '/field-' . $field_name . '-[0-9].*?-name/', $el_data->field_type ) ) { return $match[1]; } return null; }; $field_names = []; foreach ( $fields as $field_name => $val ) { if ( '' === (string) $field_name ) { continue; } // find it in the translation. foreach ( $job->elements as $el_data ) { $field_id_string = $get_field_id( $field_name, $el_data ); if ( $field_id_string ) { $field_names[ $field_name ] = isset( $field_names[ $field_name ] ) ? $field_names[ $field_name ] : []; $field_translation = false; foreach ( $job->elements as $v ) { if ( 'field-' . $field_id_string === $v->field_type ) { $field_translation = $this->decode_field_data( $v->field_data_translated, $v->field_format ); } if ( 'field-' . $field_id_string . '-type' === $v->field_type ) { $field_type = $v->field_data; break; } } if ( false !== $field_translation && isset( $field_type ) && 'custom_field' === $field_type ) { $field_id_string = $this->remove_field_name_from_start( $field_name, $field_id_string ); $meta_keys = wpml_collect( explode( '-', $field_id_string ) ) ->map( [ 'WPML_TM_Field_Type_Encoding', 'decode_hyphen' ] ) ->prepend( $field_name ) ->toArray(); $field_names = Utils::insertUnderKeys( $meta_keys, $field_names, $decode_translation( $field_translation ) ); } } } } $this->save_custom_field_values( $field_names, $post_id, $job->original_doc_id ); } /** * Remove the field from the start of the string. * * @param string $field_name The field to remove. * @param string $field_id_string The full field identifier. * @return string */ private function remove_field_name_from_start( $field_name, $field_id_string ) { return preg_replace( '#' . $field_name . '-?#', '', $field_id_string, 1 ); } /** * @param array $fields_in_job * @param int $post_id * @param int $original_post_id */ private function save_custom_field_values( $fields_in_job, $post_id, $original_post_id ) { $encodings = $this->get_tm_setting( array( 'custom_fields_encoding' ) ); foreach ( $fields_in_job as $name => $contents ) { $this->wp_api->delete_post_meta( $post_id, $name ); $contents = (array) $contents; $single = count( $contents ) === 1; $encoding = isset( $encodings[ $name ] ) ? $encodings[ $name ] : ''; foreach ( $contents as $value ) { $value = self::preserve_numerics( $value, $name, $original_post_id, $single ); $value = $encoding ? WPML_Encoding::encode( $value, $encoding ) : $value; $value = apply_filters( 'wpml_encode_custom_field', $value, $name ); $value = $this->prevent_strip_slash_on_json( $value, $encoding ); $this->wp_api->add_post_meta( $post_id, $name, $value, $single ); } } } /** * The core function `add_post_meta` always performs * a `stripslashes_deep` on the value. We need to escape * once more before to call the function. * * @param string $value * @param string $encoding * * @return string */ private function prevent_strip_slash_on_json( $value, $encoding ) { if ( in_array( 'json', explode( ',', $encoding ), true ) ) { $value = wp_slash( $value ); } return $value; } /** * @param array $package * @param object $post * @param array $fields_to_translate * @param array $fields_encoding * * @return array */ private function add_custom_field_contents( $package, $post, $fields_to_translate, $fields_encoding ) { foreach ( $fields_to_translate as $key ) { $encoding = isset( $fields_encoding[ $key ] ) ? $fields_encoding[ $key ] : ''; $custom_fields_values = array_values( array_filter( get_post_meta( $post->ID, $key ) ) ); foreach ( $custom_fields_values as $index => $custom_field_val ) { $custom_field_val = apply_filters( 'wpml_decode_custom_field', $custom_field_val, $key ); $package = $this->add_single_field_content( $package, $key, array( $index ), $custom_field_val, $encoding ); } } return $package; } /** * For array valued custom fields cf is given in the form field-{$field_name}-join('-', $indicies) * * @param array $package * @param string $key * @param array $custom_field_index * @param array|stdClass|string $custom_field_val * @param string $encoding * * @return array */ private function add_single_field_content( $package, $key, $custom_field_index, $custom_field_val, $encoding ) { if ( $encoding && is_scalar( $custom_field_val ) ) { $custom_field_val = WPML_Encoding::decode( $custom_field_val, $encoding ); $encoding = ''; } if ( is_scalar( $custom_field_val ) ) { list( $cf, $key_index ) = WPML_TM_Field_Type_Encoding::encode( $key, $custom_field_index ); // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $package['contents'][ $cf ] = array( 'translate' => 1, 'data' => base64_encode( (string) $custom_field_val ), 'format' => 'base64', ); $package['contents'][ $cf . '-name' ] = array( 'translate' => 0, 'data' => $key_index, ); $package['contents'][ $cf . '-type' ] = array( 'translate' => 0, 'data' => 'custom_field', ); } else { foreach ( (array) $custom_field_val as $ind => $value ) { $package = $this->add_single_field_content( $package, $key, array_merge( $custom_field_index, array( $ind ) ), $value, $encoding ); } } return $package; } /** * Ensure that any numerics are preserved in the given value. eg any string like '10' * will be converted to an integer if the corresponding original value was an integer. * * @param mixed $value * @param string $name * @param string|int $original_post_id * @param bool $single * * @return mixed */ public static function preserve_numerics( $value, $name, $original_post_id, $single ) { $get_original = function () use ( $original_post_id, $name, $single ) { $meta = get_post_meta( (int) $original_post_id, $name, $single ); return apply_filters( 'wpml_decode_custom_field', $meta, $name ); }; if ( is_numeric( $value ) && is_int( $get_original() ) ) { $value = intval( $value ); } elseif ( is_array( $value ) ) { $value = self::preserve_numerics_recursive( $get_original(), $value ); } return $value; } /** * Ensure that any numerics are preserved in the given value. eg any string like '10' * will be converted to an integer if the corresponding original value was an integer. * * @param mixed $original * @param mixed $value * * @return mixed */ private static function preserve_numerics_recursive( $original, $value ) { if ( is_array( $original ) ) { foreach ( $original as $key => $data ) { if ( isset( $value[ $key ] ) ) { if ( is_array( $data ) ) { $value[ $key ] = self::preserve_numerics_recursive( $data, $value[ $key ] ); } elseif ( is_int( $data ) && is_numeric( $value[ $key ] ) ) { $value[ $key ] = intval( $value[ $key ] ); } } } } return $value; } private function get_taxonomy_fields( $post ) { global $sitepress; $termMetaKeysToTranslate = self::getTermMetaKeysToTranslate(); // $getTermFields :: WP_Term → [[fieldId, fieldVal]] $getTermFields = function ( $term ) { return [ [ FieldId::forTerm( $term->term_taxonomy_id ), $term->name ], [ FieldId::forTermDescription( $term->term_taxonomy_id ), $term->description ], ]; }; // $getTermMetaFields :: [metakeys] → WP_Term → [[fieldId, fieldVal]] $getTermMetaFields = curryN( 2, function ( $termMetaKeysToTranslate, $term ) { // $getMeta :: int → string → object $getMeta = curryN( 2, function ( $termId, $key ) { return (object) [ 'id' => $termId, 'key' => $key, 'meta' => get_term_meta( $termId, $key ), ]; } ); // $hasMeta :: object → bool $hasMeta = function ( $termData ) { return isset( $termData->meta[0] ); }; // $makeField :: object → [fieldId, $fieldVal] $makeField = function ( $termData ) { return [ FieldId::forTermMeta( $termData->id, $termData->key ), $termData->meta[0] ]; }; // $get :: [metakeys] → [[fieldId, $fieldVal]] $get = pipe( Fns::map( $getMeta( $term->term_taxonomy_id ) ), Fns::filter( $hasMeta ), Fns::map( $makeField ) ); return $get( $termMetaKeysToTranslate ); } ); // $getAll :: [WP_Term] → [[fieldId, fieldVal]] $getAll = Fns::converge( Lst::concat(), [ $getTermFields, $getTermMetaFields( $termMetaKeysToTranslate ) ] ); return wpml_collect( $sitepress->get_translatable_taxonomies( false, $post->post_type ) ) // [taxonomies] ->map( Post::getTerms( $post->ID ) ) // [Either false|WP_Error [WP_Term]] ->filter( Fns::isRight() ) // [Right[WP_Term]] ->map( invoke( 'get' ) ) // [[WP_Term]] ->flatten() // [WP_Term] ->map( $getAll ) // [[fieldId, fieldVal]] ->mapWithKeys( Lst::fromPairs() ) // [fieldId => fieldVal] ->toArray(); } public static function getTermMetaKeysToTranslate() { $fieldTranslation = new WPML_Custom_Field_Setting_Factory( self::get_core_translation_management() ); $settingsFactory = self::get_core_translation_management()->settings_factory(); $translatableMetaKeys = pipe( [ $settingsFactory, 'term_meta_setting' ], invoke( 'status' ), Relation::equals( WPML_TRANSLATE_CUSTOM_FIELD ) ); return wpml_collect( $fieldTranslation->get_term_meta_keys() ) ->filter( $translatableMetaKeys ) ->values() ->toArray(); } } translation-jobs/class-wpml-tm-unsent-jobs.php 0000755 00000003221 14720342453 0015520 0 ustar 00 <?php /** * Class WPML_TM_Unsent_Jobs_Notice * * @group unsent-jobs-notification */ class WPML_TM_Unsent_Jobs { /** * @var WPML_TM_Blog_Translators */ private $blog_translators; /** * @var SitePress */ private $sitepress; /** * WPML_TM_Unsent_Jobs constructor. * * @param WPML_TM_Blog_Translators $blog_translators * @param SitePress $sitepress */ public function __construct( WPML_TM_Blog_Translators $blog_translators, SitePress $sitepress ) { $this->blog_translators = $blog_translators; $this->sitepress = $sitepress; } public function add_hooks() { add_action( 'wpml_tm_assign_job_notification', array( $this, 'prepare_unsent_job_for_notice' ) ); add_action( 'wpml_tm_new_job_notification', array( $this, 'prepare_unsent_job_for_notice' ), 10, 2 ); add_action( 'wpml_tm_local_string_sent', array( $this, 'prepare_unsent_job_for_notice' ) ); } /** * @param WPML_Translation_Job $job * @param null $translator_id */ public function prepare_unsent_job_for_notice( WPML_Translation_Job $job, $translator_id = null ) { if ( $translator_id ) { $translators = array( get_userdata( $translator_id ) ); } else { $translators = $this->blog_translators->get_blog_translators( array( 'from' => $job->get_source_language_code(), 'to' => $job->get_language_code(), ) ); } foreach ( $translators as $translator ) { $args = array( 'job' => $job, 'event' => WPML_User_Jobs_Notification_Settings::is_new_job_notification_enabled( $translator->ID ) ? 'sent' : 'unsent', ); do_action( 'wpml_tm_jobs_translator_notification', $args ); } } } translation-jobs/Dispatch/Elements.php 0000755 00000010577 14720342453 0014102 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; use Exception; use WPML\API\Sanitize; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\User; use WPML\TM\API\Jobs; use function WPML\Container\make; use function WPML\FP\pipe; abstract class Elements { /** * @param callable $sendBatch * @param Messages $messages * @param callable $buildBatch * @param array $data * @param string $type */ public static function dispatch( callable $sendBatch, Messages $messages, callable $buildBatch, $data, $type ) { $howToHandleExisting = Obj::propOr(\WPML_TM_Translation_Batch::HANDLE_EXISTING_LEAVE, 'wpml-how-to-handle-existing', $data ); $translateAutomatically = Relation::propEq( 'wpml-how-to-translate', 'automatic', $data ); $translationActions = filter_var_array( Obj::propOr( [], 'tr_action', $data ), FILTER_SANITIZE_NUMBER_INT ); $sourceLanguage = Sanitize::stringProp( 'translate_from', $data ); $targetLanguages = self::getTargetLanguages( $translationActions ); $translators = self::getTranslators( $sourceLanguage, $targetLanguages ); $elementsForTranslation = self::getElements( $messages, $data[ $type ], $targetLanguages, $howToHandleExisting, $translateAutomatically ); $batch = $buildBatch( $elementsForTranslation, $sourceLanguage, $translators ); if ( $batch ) { $batch->setTranslationMode( Relation::propEq( 'wpml-how-to-translate', 'automatic', $data ) ? 'auto' : 'manual' ); $batch->setHowToHandleExisting( $howToHandleExisting ); $sendBatch( $messages, $batch ); } } private static function getTargetLanguages( $translationActions ) { return array_keys( array_filter( $translationActions, function ( $action ) { return (int) $action === \TranslationManagement::TRANSLATE_ELEMENT_ACTION; } ) ); } private static function getTranslators( $sourceLanguage, $targetLanguages ) { $records = make( \WPML_Translator_Records::class ); $getTranslator = function ( $lang ) use ( $sourceLanguage, $records ) { $translators = $records->get_users_with_languages( $sourceLanguage, [ $lang ] ); return count( $translators ) ? $translators[0] : User::getCurrent(); }; $translators = wpml_collect( $targetLanguages ) ->map( $getTranslator ) ->map( Obj::prop( 'ID') ); return Lst::zipObj( $targetLanguages, $translators->toArray() ); } private static function getElements( Messages $messages, $data, $targetLanguages, $howToHandleExisting, $translateAutomatically ) { $getElementsToTranslate = pipe( Fns::filter( Obj::prop( 'checked' ) ), Lst::keyBy( 'checked' ) ); $elementsIds = $getElementsToTranslate( $data ); list( $elementsToTranslation, $ignoredElementsMessages ) = static::filterElements( $messages, $elementsIds, $targetLanguages, $howToHandleExisting, $translateAutomatically ); $messages->showForPosts( $ignoredElementsMessages, 'information' ); return array_filter( $elementsToTranslation, pipe( Obj::prop( 'target_languages' ), Lst::length() ) ); } /** * @param \stdClass $job * * @return bool */ protected static function isProgressJob( $job ) { return Lst::includes( (int) $job->status, [ ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ] ) && ! $job->needs_update; } /** * @param \stdClass $job * * @return bool */ protected static function isCompletedJob( $job ) { return (int) $job->status === ICL_TM_COMPLETE && ! $job->needs_update; } /** * @param Messages $messages * @param array $elementsData * @param array $targetLanguages * @param string $howToHandleExisting * @param bool $translateAutomatically * * phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn * @return array * @throws Exception Throws an exception if the method is not properly extended. */ protected static function filterElements( Messages $messages, $elementsData, $targetLanguages, $howToHandleExisting, $translateAutomatically ) { throw new Exception( ' this method is mandatory' ); } /** * @param \stdClass $job * @param string|null $howToHandleExisting * @param bool $translateAutomatically * * @return bool */ protected static function shouldJobBeIgnoredBecauseIsCompleted( $job, $howToHandleExisting, $translateAutomatically ) { return $translateAutomatically && $job && self::isCompletedJob( $job ) && $howToHandleExisting === \WPML_TM_Translation_Batch::HANDLE_EXISTING_LEAVE; } } translation-jobs/Dispatch/BatchBuilder.php 0000755 00000014764 14720342453 0014660 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; use WPML\FP\Curryable; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Str; use function WPML\FP\curryN; use function WPML\FP\pipe; /** * Class BatchBuilder * * @phpstan-type curried "__CURRIED_PLACEHOLDER__" */ class BatchBuilder { use Curryable; public static function init() {} /** * @param array $data * @param string $sourceLanguage * @param array $translators * * @return callable|\WPML_TM_Translation_Batch|null * * @phpstan-template A1 of array|curried * @phpstan-template A2 of string|curried * @phpstan-template A3 of array|curried * @phpstan-template P1 of array * @phpstan-template P2 of string * @phpstan-template P3 of array * @phpstan-template R of \WPML_TM_Translation_Batch|null * * @phpstan-param ?A1 $data * @phpstan-param ?A2 $sourceLanguage * @phpstan-param ?A3 $translators * * @phpstan-return ($data is P1 * ? ($sourceLanguage is P2 * ? ($translators is P3 * ? R * : callable(P3=):R) * : ($translators is P3 * ? callable(P2=):R * : callable(P2=,P3=):R) * ) * : ($sourceLanguage is P2 * ? ($translators is P3 * ? callable(P1=):R * : callable(P1=,P3=):R) * : ($translators is P3 * ? callable(P1=,P2=):R * : callable(P1=,P2=,P3=):R) * ) * ) */ public static function buildPostsBatch( $data = null, $sourceLanguage = null, $translators = null ) { return call_user_func_array( curryN( 3, function ( $data, $sourceLanguage, $translators ) { return self::build( 'Translation-%s-%s', self::getPostElements(), $data, $sourceLanguage, $translators ); } ), func_get_args() ); } /** * @param array $data * @param string $sourceLanguage * @param array $translators * * @return callable|\WPML_TM_Translation_Batch|null * * @phpstan-template A1 of array|curried * @phpstan-template A2 of string|curried * @phpstan-template A3 of array|curried * @phpstan-template P1 of array * @phpstan-template P2 of string * @phpstan-template P3 of array * @phpstan-template R of \WPML_TM_Translation_Batch|null * * @phpstan-param ?A1 $data * @phpstan-param ?A2 $sourceLanguage * @phpstan-param ?A3 $translators * * @phpstan-return ($data is P1 * ? ($sourceLanguage is P2 * ? ($translators is P3 * ? R * : callable(P3=):R) * : ($translators is P3 * ? callable(P2=):R * : callable(P2=,P3=):R) * ) * : ($sourceLanguage is P2 * ? ($translators is P3 * ? callable(P1=):R * : callable(P1=,P3=):R) * : ($translators is P3 * ? callable(P1=,P2=):R * : callable(P1=,P2=,P3=):R) * ) * ) */ public static function buildStringsBatch( $data = null, $sourceLanguage = null, $translators = null ) { return call_user_func_array( curryN( 3, function ( $data, $sourceLanguage, $translators ) { return self::build( 'Strings translation-%s-%s', self::getStringElements(), $data, $sourceLanguage, $translators ); } ), func_get_args() ); } /** * @param array $postsForTranslation * @param string $sourceLanguage * * @return callable|array * * @phpstan-template A1 of array|curried * @phpstan-template A2 of string|curried * @phpstan-template P1 of array * @phpstan-template P2 of string * @phpstan-template R of array * * @phpstan-param ?A1 $postsForTranslation * @phpstan-param ?A2 $sourceLanguage * * @phpstan-return ($postsForTranslation is P1 * ? ($sourceLanguage is P2 ? R : callable(P2=):R) * : ($sourceLanguage is P2 ? callable(P1=):R : callable(P1=,P2=):R) * ) */ public static function getPostElements( $postsForTranslation = null, $sourceLanguage = null ) { return call_user_func_array( curryN( 2, function ( $postsForTranslation, $sourceLanguage ) { $elements = []; foreach ( $postsForTranslation as $postId => $postData ) { $elements[] = new \WPML_TM_Translation_Batch_Element( $postId, $postData['type'], $sourceLanguage, array_fill_keys( $postData['target_languages'], \TranslationManagement::TRANSLATE_ELEMENT_ACTION ), Obj::propOr( [], 'media', $postData ) ); } return $elements; } ), func_get_args() ); } /** * @param array $stringsForTranslation * @param string $sourceLanguage * * @return callable|array * * @phpstan-template A1 of array|curried * @phpstan-template A2 of string|curried * @phpstan-template P1 of array * @phpstan-template P2 of string * @phpstan-template R of array * * @phpstan-param ?A1 $stringsForTranslation * @phpstan-param ?A2 $sourceLanguage * * @phpstan-return ($stringsForTranslation is P1 * ? ($sourceLanguage is P2 ? R : callable(P2=):R) * : ($sourceLanguage is P2 ? callable(P1=):R : callable(P1=,P2=):R) * ) */ public static function getStringElements( $stringsForTranslation = null, $sourceLanguage = null ) { return call_user_func_array( curryN( 2, function ( $stringsForTranslation, $sourceLanguage ) { $elements = []; $setTranslateAction = pipe( Fns::map( pipe( Lst::makePair( \TranslationManagement::TRANSLATE_ELEMENT_ACTION ), Lst::reverse() ) ), Lst::fromPairs() ); foreach ( $stringsForTranslation as $stringId => $targetLanguages ) { $elements[] = new \WPML_TM_Translation_Batch_Element( $stringId, 'string', $sourceLanguage, $setTranslateAction( $targetLanguages ) ); } return $elements; } ), func_get_args() ); } /** * @param string $batchNameTemplate * @param callable $buildElementStrategy * @param array $data * @param string $sourceLanguage * @param array $translators * * @return \WPML_TM_Translation_Batch|null */ private static function build( $batchNameTemplate, callable $buildElementStrategy, array $data, $sourceLanguage, array $translators ) { $targetLanguagesString = pipe( Lst::flatten(), 'array_unique', Lst::join( '|' ) ); $idsHash = pipe( 'array_keys', Lst::join( '-' ), 'md5', Str::sub( 16 ) ); $batchName = sprintf( $batchNameTemplate, $targetLanguagesString( $data ), $idsHash( $data ) ); $elements = apply_filters( 'wpml_tm_batch_factory_elements', $buildElementStrategy( $data, $sourceLanguage ), $batchName ); return $elements ? new \WPML_TM_Translation_Batch( $elements, $batchName, $translators, null ) : null; } } BatchBuilder::init(); translation-jobs/Dispatch/Strings.php 0000755 00000003505 14720342453 0013750 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\User; use function WPML\Container\make; class Strings { /** * @param callable $sendBatch * @param \WPML\TM\Jobs\Dispatch\Messages $messages * @param callable $buildBatch * @param $stringIds * @param $sourceLanguage * @param $targetLanguages */ public static function dispatch( callable $sendBatch, Messages $messages, callable $buildBatch, $stringIds, $sourceLanguage, $targetLanguages ) { $stringsForTranslation = self::filterStringsForTranslation( $messages, $stringIds, $targetLanguages ); $translators = array_fill_keys( $targetLanguages, User::getCurrentId() ); $batch = $buildBatch( $stringsForTranslation, $sourceLanguage, $translators ); $batch && $sendBatch( $messages, $batch ); } private static function filterStringsForTranslation( Messages $messages, $stringIds, $targetLanguages ) { $stringsToTranslation = []; $ignoredStringsMessages = []; /** @var \WPML_ST_String_Factory $stringFactory */ $stringFactory = make( \WPML_ST_String_Factory::class ); foreach ( $stringIds as $stringId ) { $stringsToTranslation[ $stringId ] = []; $string = $stringFactory->find_by_id( $stringId ); $statuses = wpml_collect( $string->get_translation_statuses() )->keyBy( 'language' )->map( Obj::prop( 'status' ) ); foreach ( $targetLanguages as $language ) { if ( (int) Obj::prop( $language, $statuses ) === ICL_TM_WAITING_FOR_TRANSLATOR ) { $ignoredStringsMessages[] = $messages->ignoreInProgressStringMessage( $string, $language ); } else { $stringsToTranslation[ $stringId ] [] = $language; } } } $messages->showForStrings( $ignoredStringsMessages, 'information' ); /** @phpstan-ignore-next-line */ return array_filter( $stringsToTranslation, Lst::length() ); } } translation-jobs/Dispatch/Messages.php 0000755 00000006152 14720342453 0014067 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; class Messages { /** * @param \WP_Post $post * @param string $language * * @return string */ public function ignoreOriginalPostMessage( $post, $language ) { return sprintf( __( 'Post "%1$s" will be ignored for %2$s, because it is an original post.', 'wpml-translation-management' ), $post->post_title, $language ); } /** * @param \WP_Post $post * @param string $language * * @return string */ public function ignoreInProgressPostMessage( $post, $language ) { return sprintf( __( 'Post "%1$s" will be ignored for %2$s, because translation is already in progress.', 'wpml-translation-management' ), $post->post_title, $language ); } /** * @param \WPML_ST_String $string * @param $language * * @return string */ public function ignoreInProgressStringMessage( \WPML_ST_String $string, $language ) { return sprintf( __( 'String "%1$s" will be ignored for %2$s, because translation is already waiting for translator.', 'wpml-translation-management' ), $string->get_value(), $language ); } /** * @param \WPML_Package $package * @param string $language * * @return string */ public function ignoreInProgressPackageMessage( $package, $language ) { return sprintf( __( 'Package "%1$s" will be ignored for %2$s, because translation is already in progress.', 'wpml-translation-management' ), $package->title, $language ); } /** * @param \WPML_Package $package * @param string $language * * @return string */ public function ignoreOriginalPackageMessage( $package, $language ) { return sprintf( __( 'Package "%1$s" will be ignored for %2$s, because it is an original post.', 'wpml-translation-management' ), $package->title, $language ); } /** * @param array $messages * @param string $type */ public function showForPosts( array $messages, $type ) { $this->show( 'translation-basket-notification', [ WPML_TM_FOLDER . '/menu/main.php' ], 'translation-basket-notification', $messages, $type ); } /** * @param array $messages * @param string $type */ public function showForStrings( array $messages, $type ) { if ( defined( 'WPML_ST_FOLDER' ) ) { $this->show( 'string-translation-top', [ WPML_ST_FOLDER . '/menu/string-translation.php' ], 'string-translation-top', $messages, $type ); } } /** * @param string $id * @param array $pages * @param string $group * @param array $messages * @param string $type */ private function show( $id, array $pages, $group, array $messages, $type ) { if ( $messages ) { $messageArgs = [ 'id' => $id, 'text' => '<ul><li>' . implode( '</li><li>', $messages ) . '</li></ul>', 'classes' => 'small', 'type' => $type, 'group' => $group, 'admin_notice' => true, 'hide_per_user' => false, 'dismiss_per_user' => false, 'limit_to_page' => $pages, 'show_once' => true, ]; \ICL_AdminNotifier::add_message( $messageArgs ); } } } translation-jobs/Dispatch/Posts.php 0000755 00000003176 14720342453 0013433 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; use WPML\Element\API\Post; use WPML\FP\Obj; use WPML\FP\Str; use WPML\TM\API\Jobs; class Posts extends Elements { public static function dispatch( callable $sendBatch, Messages $messages, callable $buildBatch, $data, $type = 'post' ) { parent::dispatch( $sendBatch, $messages, $buildBatch, $data, $type ); } protected static function filterElements( Messages $messages, $postsData, $targetLanguages, $howToHandleExisting, $translateAutomatically ) { $ignoredPostsMessages = []; $postsToTranslation = []; foreach ( $postsData as $postId => $postData ) { $postsToTranslation[ $postId ] = [ 'type' => $postData['type'], 'media' => Obj::propOr( [], 'media-translation', $postData ), 'target_languages' => [] ]; $post = self::getPost( $postId ); $postLang = Post::getLang( $postId ); foreach ( $targetLanguages as $language ) { if ( $postLang === $language ) { $ignoredPostsMessages [] = $messages->ignoreOriginalPostMessage( $post, $language ); continue; } $job = Jobs::getElementJob( (int) $post->ID, 'post_' . $post->post_type, (string) $language ); if ( self::shouldJobBeIgnoredBecauseIsCompleted( $job, $howToHandleExisting, $translateAutomatically ) ) { continue; } $postsToTranslation[ $postId ]['target_languages'] [] = $language; } } return [ $postsToTranslation, $ignoredPostsMessages ]; } private static function getPost( $postId ) { return Str::includes( 'external_', $postId ) ? apply_filters( 'wpml_get_translatable_item', null, $postId ) : get_post( $postId ); } } translation-jobs/Dispatch/Packages.php 0000755 00000003162 14720342453 0014034 0 ustar 00 <?php namespace WPML\TM\Jobs\Dispatch; use WPML\Element\API\Languages; use WPML\TM\API\Jobs; class Packages extends Elements { public static function dispatch( callable $sendBatch, Messages $messages, callable $buildBatch, $data, $type = 'package' ) { parent::dispatch( $sendBatch, $messages, $buildBatch, $data, $type ); } protected static function filterElements( Messages $messages, $packagesData, $targetLanguages, $howToHandleExisting, $translateAutomatically ) { $ignoredPackagesMessages = []; $packagesToTranslation = []; foreach ( $packagesData as $packageId => $packageData ) { $packagesToTranslation[ $packageId ] = [ 'type' => $packageData['type'], 'target_languages' => [] ]; $package = apply_filters( 'wpml_get_translatable_item', null, $packageId, 'package' ); $packageLang = apply_filters( 'wpml_language_for_element', Languages::getDefaultCode(), $package ); foreach ( $targetLanguages as $language ) { if ( $packageLang === $language ) { $ignoredPackagesMessages [] = $messages->ignoreOriginalPackageMessage( $package, $language ); continue; } $job = Jobs::getElementJob( (int) $package->ID, (string) $package->get_element_type_prefix() . '_' . $package->kind_slug, (string) $language ); if ( $job && ( self::isProgressJob( $job ) || self::shouldJobBeIgnoredBecauseIsCompleted( $job, $howToHandleExisting, $translateAutomatically) ) ) { continue; } $packagesToTranslation[ $packageId ]['target_languages'] [] = $language; } } return [ $packagesToTranslation, $ignoredPackagesMessages ]; } } translation-jobs/class-wpml-tm-translator-note.php 0000755 00000000475 14720342453 0016415 0 ustar 00 <?php class WPML_TM_Translator_Note { const META_FIELD_KEY = '_icl_translator_note'; public static function get( $post_id ) { return get_post_meta( $post_id, self::META_FIELD_KEY, true ); } public static function update( $post_id, $note ) { update_post_meta( $post_id, self::META_FIELD_KEY, $note ); } } class-wpml-active-plugin-provider.php 0000755 00000001026 14720342453 0013742 0 ustar 00 <?php class WPML_Active_Plugin_Provider { /** * @return array */ public function get_active_plugins() { $active_plugin_names = array(); if ( function_exists( 'get_plugins' ) ) { foreach ( get_plugins() as $plugin_file => $plugin_data ) { if ( is_plugin_active( $plugin_file ) ) { $active_plugin_names[] = $plugin_data; } } } return $active_plugin_names; } /** * @return array */ public function get_active_plugin_names() { return wp_list_pluck( $this->get_active_plugins(), 'Name' ); } } request-handling/class-wpml-language-resolution.php 0000755 00000012165 14720342453 0016607 0 ustar 00 <?php use WPML\FP\Str; /** * Class WPML_Language_Resolution * * @package wpml-core * @subpackage wpml-requests */ class WPML_Language_Resolution { private $active_language_codes = array(); private $current_request_lang = null; private $default_lang = null; /** * @var array|null $hidden_lang_codes if set to null, * indicates that the cache needs to be reloaded due to changing settings * or current user within the request */ private $hidden_lang_codes = null; /** * WPML_Language_Resolution constructor. * * @param string[] $active_language_codes * @param string $default_lang */ public function __construct( $active_language_codes, $default_lang ) { add_action( 'wpml_cache_clear', array( $this, 'reload' ), 11, 0 ); $this->active_language_codes = array_fill_keys( $active_language_codes, 1 ); $this->default_lang = $default_lang; $this->hidden_lang_codes = array_fill_keys( wpml_get_setting_filter( array(), 'hidden_languages' ), 1 ); } public function reload() { $this->active_language_codes = array(); $this->hidden_lang_codes = null; $this->default_lang = null; $this->maybe_reload(); } public function current_lang_filter( $lang, WPML_Request $wpml_request_handler ) { if ( $this->current_request_lang !== $lang ) { $preview_lang = apply_filters( 'wpml_should_filter_preview_lang', true ) ? $this->filter_preview_language_code() : null; if ( $preview_lang ) { $lang = $preview_lang; } elseif ( $this->use_cookie_language() ) { $lang = $wpml_request_handler->get_cookie_lang(); } } $this->current_request_lang = $this->filter_for_legal_langs( $lang ); return $this->current_request_lang; } public function get_active_language_codes() { $this->maybe_reload(); return array_keys( $this->active_language_codes ); } public function is_language_hidden( $lang_code ) { $this->maybe_reload(); return isset( $this->hidden_lang_codes[ $lang_code ] ); } public function is_language_active( $lang_code, $is_all_active = false ) { global $wpml_request_handler; $this->maybe_reload(); return ( $is_all_active === true && $lang_code === 'all' ) || isset( $this->active_language_codes[ $lang_code ] ) || ( $wpml_request_handler->show_hidden() && $this->is_language_hidden( $lang_code ) ); } private function maybe_reload() { $this->default_lang = $this->default_lang ? $this->default_lang : wpml_get_setting_filter( false, 'default_language' ); $this->active_language_codes = (bool) $this->active_language_codes === true ? $this->active_language_codes : array_fill_keys( wpml_reload_active_languages_setting( true ), 1 ); } /** * Returns the language_code of the http referrer's location from which a request originated. * Used to correctly determine the language code on ajax link lists for the post edit screen or * the flat taxonomy auto-suggest. * * @return string|null */ public function get_referrer_language_code() { if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) { $query_string = wpml_parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_QUERY ); $query = array(); parse_str( (string) $query_string, $query ); $language_code = isset( $query['lang'] ) ? $query['lang'] : null; } return isset( $language_code ) ? $language_code : null; } /** * * Sets the language of frontend requests to false, if they are not for * a hidden or active language code. The handling of permissions in case of * hidden languages is done in \SitePress::init. * * @param string $lang * * @return string */ private function filter_for_legal_langs( $lang ) { if ( Str::includes( 'wp-login.php', $_SERVER['REQUEST_URI'] ) ) { return $lang; } $this->maybe_reload(); $is_wp_cli_request = defined( 'WP_CLI' ) && WP_CLI; if ( $lang === 'all' && ( is_admin() || $is_wp_cli_request ) ) { return 'all'; } if ( ! isset( $this->hidden_lang_codes[ $lang ] ) && ! isset( $this->active_language_codes[ $lang ] ) ) { $lang = $this->default_lang ? $this->default_lang : icl_get_setting( 'default_language' ); } return $lang; } /** * @return bool */ private function use_cookie_language() { return ( isset( $_GET['action'] ) && $_GET['action'] === 'ajax-tag-search' ) || ( isset( $_POST['action'] ) && in_array( $_POST['action'], array( 'get-tagcloud', 'wp-link-ajax' ), true ) ); } /** * Adjusts the output of the filtering for the current language in case * the request is for a preview page. * * @return null|string */ private function filter_preview_language_code() { $preview_id = filter_var( isset( $_GET['preview_id'] ) ? $_GET['preview_id'] : '', FILTER_SANITIZE_NUMBER_INT ); $preview_flag = filter_input( INPUT_GET, 'preview' ) || $preview_id; $preview_id = $preview_id ? $preview_id : filter_input( INPUT_GET, 'p' ); $preview_id = $preview_id ? $preview_id : filter_input( INPUT_GET, 'page_id' ); $lang = null; if ( $preview_flag && $preview_id ) { global $wpml_post_translations; $lang = $wpml_post_translations->get_element_lang_code( $preview_id ); } return $lang; } } request-handling/class-wpml-frontend-request.php 0000755 00000002001 14720342453 0016114 0 ustar 00 <?php use WPML\Language\Detection\Frontend; /* * @deprecated deprecated since version 4.4.0 * This class has been replaced by WPML\Language\Detection\Frontend and is going to be removed in the next major release. * * * @package wpml-core * @subpackage wpml-requests * */ class WPML_Frontend_Request extends WPML_Request { /** @var \WPML\Language\Detection\Frontend */ private $frontend; public function __construct( $url_converter, $active_languages, $default_language, $cookieLanguage, $wp_api ) { parent::__construct( $url_converter, $active_languages, $default_language, $cookieLanguage ); $this->frontend = new Frontend( $url_converter, $active_languages, $default_language, $cookieLanguage, $wp_api ); } /** * @deprecated deprecated since version 4.4.0 * @return false|string */ public function get_requested_lang() { return $this->frontend->get_requested_lang(); } protected function get_cookie_name() { return $this->cookieLanguage->getFrontendCookieName(); } } request-handling/class-wpml-frontend-redirection.php 0000755 00000003401 14720342453 0016740 0 ustar 00 <?php use WPML\Language\Detection\Frontend; class WPML_Frontend_Redirection extends WPML_SP_User { /** @var Frontend $request_handler */ private $request_handler; /** @var WPML_Redirection */ private $redirect_helper; /** @var WPML_Language_Resolution $lang_resolution */ private $lang_resolution; /** * WPML_Frontend_Redirection constructor. * * @param SitePress $sitepress * @param Frontend $request_handler * @param WPML_Redirection $redir_helper * @param WPML_Language_Resolution $lang_resolution */ public function __construct( &$sitepress, &$request_handler, &$redir_helper, &$lang_resolution ) { parent::__construct( $sitepress ); $this->request_handler = &$request_handler; $this->redirect_helper = &$redir_helper; $this->lang_resolution = &$lang_resolution; } /** * Redirects to a URL corrected for the language information in it, in case request URI and $_REQUEST['lang'], * requested domain or $_SERVER['REQUEST_URI'] do not match and gives precedence to the explicit language parameter if * there. * * @return string The language code of the currently requested URL in case no redirection was necessary. */ public function maybe_redirect() { $target = $this->redirect_helper->get_redirect_target(); if ( false !== $target ) { $frontend_redirection_url = new WPML_Frontend_Redirection_Url( $target ); $target = $frontend_redirection_url->encode_apostrophes_in_url(); $this->sitepress->get_wp_api()->wp_safe_redirect( $target ); }; // allow forcing the current language when it can't be decoded from the URL. return $this->lang_resolution->current_lang_filter( $this->request_handler->get_requested_lang(), $this->request_handler ); } } request-handling/class-wpml-language-per-domain-sso.php 0000755 00000026663 14720342453 0017251 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_Language_Per_Domain_SSO */ class WPML_Language_Per_Domain_SSO { const SSO_NONCE = 'wpml_sso'; const TRANSIENT_SSO_STARTED = 'wpml_sso_started'; const TRANSIENT_DOMAIN = 'wpml_sso_domain_'; const TRANSIENT_USER = 'wpml_sso_user_'; const TRANSIENT_SESSION_TOKEN = 'wpml_sso_session_'; const IFRAME_USER_TOKEN_KEY = 'wpml_sso_token'; const IFRAME_USER_TOKEN_KEY_FOR_DOMAIN = 'wpml_sso_token_domain'; const IFRAME_DOMAIN_HASH_KEY = 'wpml_sso_iframe_hash'; const IFRAME_USER_STATUS_KEY = 'wpml_sso_user_status'; const SSO_TIMEOUT = MINUTE_IN_SECONDS; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_PHP_Functions $php_functions */ private $php_functions; /** @var WPML_Cookie */ private $wpml_cookie; /** @var string */ private $site_url; /** @var array */ private $domains; /** @var int $current_user_id */ private $current_user_id; public function __construct( SitePress $sitepress, WPML_PHP_Functions $php_functions, WPML_Cookie $wpml_cookie ) { $this->sitepress = $sitepress; $this->php_functions = $php_functions; $this->wpml_cookie = $wpml_cookie; $this->site_url = $this->sitepress->convert_url( get_home_url(), $this->sitepress->get_default_language() ); $this->domains = $this->get_domains(); } public function init_hooks() { if ( $this->is_sso_started() ) { add_action( 'init', [ $this, 'init_action' ] ); add_action( 'wp_footer', [ $this, 'add_iframes_to_footer' ] ); add_action( 'admin_footer', [ $this, 'add_iframes_to_footer' ] ); add_action( 'login_footer', [ $this, 'add_iframes_to_footer' ] ); } add_action( 'wp_login', [ $this, 'wp_login_action' ], 10, 2 ); add_filter( 'logout_redirect', [ $this, 'add_redirect_user_token' ], 10, 3 ); } public function init_action() { $this->send_headers(); $this->set_current_user_id(); if ( $this->is_iframe_request() ) { $this->process_iframe_request(); } } /** * @param string $user_login * @param WP_User $user */ public function wp_login_action( $user_login, WP_User $user ) { $this->init_sso_transients( (int) $user->ID ); } /** * @param string $redirect_to * @param string $requested_redirect_to * @param WP_User|WP_Error $user * * @return string */ public function add_redirect_user_token( $redirect_to, $requested_redirect_to, $user ) { if ( ! is_wp_error( $user ) && ! $this->is_sso_started() ) { $this->init_sso_transients( (int) $user->ID ); return add_query_arg( self::IFRAME_USER_TOKEN_KEY, $this->create_user_token( $user->ID ), $redirect_to ); } return $redirect_to; } public function add_iframes_to_footer() { $is_user_logged_in = is_user_logged_in(); if ( $is_user_logged_in && $this->is_sso_started() ) { $this->save_session_token( wp_get_session_token(), $this->current_user_id ); } foreach ( $this->domains as $domain ) { if ( $domain !== $this->get_current_domain() && $this->is_sso_started_for_domain( $domain ) ) { $iframe_url = add_query_arg( [ self::IFRAME_DOMAIN_HASH_KEY => $this->get_hash( $domain ), self::IFRAME_USER_STATUS_KEY => $is_user_logged_in ? 'wpml_user_signed_in' : 'wpml_user_signed_out', self::IFRAME_USER_TOKEN_KEY_FOR_DOMAIN => $this->create_user_token_for_domains( $this->current_user_id ), ], trailingslashit( $domain ) ); ?> <iframe class="wpml_iframe" style="display:none" src="<?php echo esc_url( $iframe_url ); ?>"></iframe> <?php } } } private function send_headers() { header( sprintf( 'Content-Security-Policy: frame-ancestors %s', implode( ' ', $this->domains ) ) ); } /** @param int $user_id */ private function set_current_user_id( $user_id = null ) { if ( $user_id ) { $this->current_user_id = $user_id; } else { $this->current_user_id = $this->get_user_id_from_token() ?: get_current_user_id(); } } private function process_iframe_request() { if ( $this->validate_user_sign_request() ) { nocache_headers(); wp_clear_auth_cookie(); if ( $_GET[ self::IFRAME_USER_STATUS_KEY ] == 'wpml_user_signed_in' ) { $user = get_user_by( 'id', $this->current_user_id ); if ( $user !== false ) { wp_set_current_user( $this->current_user_id ); if ( is_ssl() ) { $this->set_auth_cookie( $this->current_user_id, $this->get_session_token( $this->current_user_id ) ); } else { wp_set_auth_cookie( $this->current_user_id, false, '', $this->get_session_token( $this->current_user_id ) ); } do_action( 'wp_login', $user->user_login, $user ); } } else { $sessions = WP_Session_Tokens::get_instance( $this->current_user_id ); $sessions->destroy_all(); } $this->finish_sso_for_domain( $this->get_current_domain() ); } $this->php_functions->exit_php(); } /** @return bool */ private function validate_user_sign_request() { return isset( $_GET[ self::IFRAME_USER_STATUS_KEY ] ) && $this->is_sso_started_for_domain( $this->get_current_domain() ); } /** @return int */ private function get_user_id_from_token() { $user_id = 0; if ( isset( $_GET[ self::IFRAME_USER_TOKEN_KEY ] ) ) { $transient_key = $this->create_transient_key( self::TRANSIENT_USER, null, Sanitize::stringProp( self::IFRAME_USER_TOKEN_KEY, $_GET ) ); $user_id = (int) get_transient( $transient_key ); delete_transient( $transient_key ); } elseif ( isset( $_GET[ self::IFRAME_USER_TOKEN_KEY_FOR_DOMAIN ] ) ) { $transient_key = $this->create_transient_key( self::TRANSIENT_USER, $this->get_current_domain(), Sanitize::stringProp( self::IFRAME_USER_TOKEN_KEY_FOR_DOMAIN, $_GET ) ); $user_id = (int) get_transient( $transient_key ); delete_transient( $transient_key ); } return $user_id; } /** * @param int $user_id */ private function init_sso_transients( $user_id ) { set_transient( self::TRANSIENT_SSO_STARTED, true, self::SSO_TIMEOUT ); foreach ( $this->domains as $domain ) { if ( $this->get_current_domain() !== $domain ) { set_transient( $this->create_transient_key( self::TRANSIENT_DOMAIN, $domain, $user_id ), $this->get_hash( $domain ), self::SSO_TIMEOUT ); } } } /** * @param string $domain */ private function finish_sso_for_domain( $domain ) { delete_transient( $this->create_transient_key( self::TRANSIENT_DOMAIN, $domain, $this->current_user_id ) ); } /** * @param string $domain * * @return bool */ private function is_sso_started_for_domain( $domain ) { return (bool) get_transient( $this->create_transient_key( self::TRANSIENT_DOMAIN, $domain, $this->current_user_id ) ); } /** * @return string */ private function get_current_domain() { $host = ''; if ( array_key_exists( 'HTTP_HOST', $_SERVER ) ) { $host = (string) $_SERVER['HTTP_HOST']; } return $this->get_current_protocol() . $host; } /** * @return string */ private function get_current_protocol() { return is_ssl() ? 'https://' : 'http://'; } /** * @return array */ private function get_domains() { $domains = $this->sitepress->get_setting( 'language_domains', array() ); $active_codes = array_keys( $this->sitepress->get_active_languages() ); $sso_domains = array( $this->site_url ); foreach ( $domains as $language_code => $domain ) { if ( in_array( $language_code, $active_codes ) ) { $sso_domains[] = $this->get_current_protocol() . $domain; } } return $sso_domains; } /** * @return bool */ private function is_iframe_request() { return isset( $_GET[ self::IFRAME_DOMAIN_HASH_KEY ] ) && ! wpml_is_ajax() && $this->is_sso_started_for_domain( $this->get_current_domain() ) && $this->get_hash( $this->get_current_domain() ) === $_GET[ self::IFRAME_DOMAIN_HASH_KEY ]; } /** * @return bool */ private function is_sso_started() { return (bool) get_transient( self::TRANSIENT_SSO_STARTED ); } /** * @param int $user_id * * @return string */ private function create_user_token( $user_id ) { $token = wp_create_nonce( self::SSO_NONCE ); set_transient( $this->create_transient_key( self::TRANSIENT_USER, null, $token ), $user_id, self::SSO_TIMEOUT ); return $token; } /** * @param int $user_id * * @return bool|string */ private function create_user_token_for_domains( $user_id ) { $token = wp_create_nonce( self::SSO_NONCE ); foreach ( $this->domains as $domain ) { if ( $this->get_current_domain() !== $domain ) { set_transient( $this->create_transient_key( self::TRANSIENT_USER, $domain, $token ), $user_id, self::SSO_TIMEOUT ); } } return $token; } /** * @param string $session_token * @param int $user_id */ private function save_session_token( $session_token, $user_id ) { set_transient( $this->create_transient_key( self::TRANSIENT_SESSION_TOKEN, null, $user_id ), $session_token, self::SSO_TIMEOUT ); } /** * @param int $user_id * * @return string */ private function get_session_token( $user_id ) { return (string) get_transient( $this->create_transient_key( self::TRANSIENT_SESSION_TOKEN, null, $user_id ) ); } /** * @param string $prefix * @param string|null $domain * @param string|int|null|false $token * * @return string */ private function create_transient_key( $prefix, $domain = null, $token = null ) { return $prefix . ( $token ? (string) $token : '' ) . ( $domain ? '_' . $this->get_hash( $domain ) : '' ); } /** * @param string $value * * @return string */ private function get_hash( $value ) { return hash( 'sha256', self::SSO_NONCE . $value ); } /** * As the WP doesn't support "SameSite" parameter in cookies, we have to write our own * function for saving authentication cookies to work with iframes. * * @param int $user_id * @param string $token */ private function set_auth_cookie( $user_id, $token = '' ) { $expiration = time() + apply_filters( 'auth_cookie_expiration', 2 * DAY_IN_SECONDS, $user_id, false ); $expire = 0; $secure = apply_filters( 'secure_auth_cookie', is_ssl(), $user_id ); if ( $secure ) { $auth_cookie_name = SECURE_AUTH_COOKIE; $scheme = 'secure_auth'; } else { $auth_cookie_name = AUTH_COOKIE; $scheme = 'auth'; } if ( '' === $token ) { $manager = WP_Session_Tokens::get_instance( $user_id ); $token = $manager->create( $expiration ); } $auth_cookie = wp_generate_auth_cookie( $user_id, $expiration, $scheme, $token ); $logged_in_cookie = wp_generate_auth_cookie( $user_id, $expiration, 'logged_in', $token ); do_action( 'set_auth_cookie', $auth_cookie, $expire, $expiration, $user_id, $scheme, $token ); do_action( 'set_logged_in_cookie', $logged_in_cookie, $expire, $expiration, $user_id, 'logged_in', $token ); if ( ! apply_filters( 'send_auth_cookies', true ) ) { return; } $this->wpml_cookie->set_cookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, true, 'None' ); $this->wpml_cookie->set_cookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, true, 'None' ); $this->wpml_cookie->set_cookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, true, 'None' ); if ( COOKIEPATH != SITECOOKIEPATH ) { $this->wpml_cookie->set_cookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, true, 'None' ); } } } request-handling/class-wpml-language-domain-validation.php 0000755 00000004531 14720342453 0020001 0 ustar 00 <?php class WPML_Language_Domain_Validation { const VALIDATE_DOMAIN_KEY = '____icl_validate_domain'; /** @var WPML_WP_API $wp_api */ private $wp_api; /** @var WP_Http $http */ private $http; /** @var string $url */ private $url; /** @var string $validation_url */ private $validation_url; /** * @param WPML_WP_API $wp_api * @param WP_Http $http * * @throws \InvalidArgumentException */ public function __construct( WPML_WP_API $wp_api, WP_Http $http) { $this->wp_api = $wp_api; $this->http = $http; } /** * @param string $url * * @return bool */ public function is_valid( $url ) { $this->url = $url; $this->validation_url = $this->get_validation_url(); if ( ! $this->has_scheme_and_host() ) { return false; } if ( is_multisite() && defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) { return true; } $response = $this->get_validation_response(); if ( $this->is_valid_response( $response ) ) { return in_array( $response['body'], $this->get_accepted_responses( $this->validation_url ), true ); } return false; } /** * @return bool */ private function has_scheme_and_host() { $url_parts = wpml_parse_url( $this->url ); return array_key_exists( 'scheme', $url_parts ) && array_key_exists( 'host', $url_parts ); } /** * @return string */ private function get_validation_url() { return add_query_arg( array( self::VALIDATE_DOMAIN_KEY => 1 ), trailingslashit( $this->url ) ); } /** * @param string $url * * @return array */ private function get_accepted_responses( $url ) { $accepted_responses = array( '<!--' . untrailingslashit( $this->wp_api->get_home_url() ) . '-->', '<!--' . untrailingslashit( $this->wp_api->get_site_url() ) . '-->', ); if ( defined( 'SUNRISE' ) && SUNRISE === 'on' ) { $accepted_responses[] = '<!--' . str_replace( '?' . self::VALIDATE_DOMAIN_KEY . '=1', '', $url ) . '-->'; return $accepted_responses; } return $accepted_responses; } /** * @return array|WP_Error */ private function get_validation_response() { return $this->http->request( $this->validation_url, 'timeout=15' ); } /** * @param array|WP_Error $response * * @return bool */ private function is_valid_response( $response ) { return ! is_wp_error( $response ) && '200' === (string) $response['response']['code']; } } request-handling/class-wpml-rest-request-analyze-factory.php 0000755 00000000732 14720342453 0020371 0 ustar 00 <?php class WPML_REST_Request_Analyze_Factory { /** * @return WPML_REST_Request_Analyze */ public static function create() { /** * @var \WPML_URL_Converter $wpml_url_converter * @var \WPML_Language_Resolution $wpml_language_resolution */ global $wpml_url_converter, $wpml_language_resolution; return new WPML_REST_Request_Analyze( $wpml_url_converter, $wpml_language_resolution->get_active_language_codes(), new WP_Rewrite() ); } } request-handling/class-wpml-super-globals-validation.php 0000755 00000002240 14720342453 0017523 0 ustar 00 <?php class WPML_Super_Globals_Validation { /** * @param string $key * @param int $filter * @param mixed $options * * @return mixed|null */ public function get( $key, $filter = FILTER_SANITIZE_FULL_SPECIAL_CHARS, $options = null ) { return $this->get_value( $key, $_GET, $filter, $options ); } /** * @param string $key * @param int $filter * @param mixed $options * * @return mixed|null */ public function post( $key, $filter = FILTER_SANITIZE_FULL_SPECIAL_CHARS, $options = null ) { return $this->get_value( $key, $_POST, $filter, $options ); } /** * @param string $key * @param array $var * @param int $filter * @param mixed $options * * @return mixed|null */ private function get_value( $key, array $var, $filter = FILTER_SANITIZE_FULL_SPECIAL_CHARS, $options = null ) { $value = null; if ( array_key_exists( $key, $var ) ) { if ( is_array( $var[ $key ] ) ) { $value = filter_var_array( $var[ $key ], $filter ); } elseif ( null !== $options ) { $value = filter_var( $var[ $key ], $filter, $options ); } else { $value = filter_var( $var[ $key ], $filter ); } } return $value; } } request-handling/wpml-backend-request.class.php 0000755 00000001654 14720342453 0015702 0 ustar 00 <?php use WPML\Language\Detection\Backend; /** * @deprecated This class has been replaced by WPML\Language\Detection\Backend and is going to be removed in the next major release. * @since 4.4.0 * @see WPML\Language\Detection\Backend * * @package wpml-core * @subpackage wpml-requests */ class WPML_Backend_Request extends WPML_Request { /** @var Backend */ private $backend; public function __construct( $url_converter, $active_languages, $default_language, $cookieLanguage ) { parent::__construct( $url_converter, $active_languages, $default_language, $cookieLanguage ); $this->backend = new Backend( $url_converter, $active_languages, $default_language, $cookieLanguage ); } /** * @return false|string */ public function get_requested_lang() { return $this->backend->get_requested_lang(); } protected function get_cookie_name() { return $this->cookieLanguage->getBackendCookieName(); } } request-handling/class-wpml-rest-request-analyze.php 0000755 00000003536 14720342453 0016731 0 ustar 00 <?php class WPML_REST_Request_Analyze { /** @var WPML_URL_Converter $url_converter */ private $url_converter; /** @var array $active_language_codes */ private $active_language_codes; /** @var WP_Rewrite $wp_rewrite */ private $wp_rewrite; /** @var array $uri_parts */ private $uri_parts; public function __construct( WPML_URL_Converter $url_converter, array $active_language_codes, WP_Rewrite $wp_rewrite ) { $this->url_converter = $url_converter; $this->active_language_codes = $active_language_codes; $this->wp_rewrite = $wp_rewrite; } /** @return bool */ public function is_rest_request() { if ( array_key_exists( 'rest_route', $_REQUEST ) ) { return true; } $rest_url_prefix = 'wp-json'; if ( function_exists( 'rest_get_url_prefix' ) ) { $rest_url_prefix = rest_get_url_prefix(); } $uri_part = $this->get_uri_part( $this->has_valid_language_prefix() ? 1 : 0 ); return $uri_part === $rest_url_prefix; } /** @return bool */ private function has_valid_language_prefix() { if ( $this->url_converter->get_strategy() instanceof WPML_URL_Converter_Subdir_Strategy ) { $maybe_lang = $this->get_uri_part(); return in_array( $maybe_lang, $this->active_language_codes, true ); } return false; } /** * @param int $index * * @return string */ private function get_uri_part( $index = 0 ) { if ( null === $this->uri_parts ) { $request_uri = (string) filter_var( $_SERVER['REQUEST_URI'], FILTER_SANITIZE_URL ); $cleaned_uri = ltrim( wpml_strip_subdir_from_url( $request_uri ), '/' ); if ( $this->wp_rewrite->using_index_permalinks() ) { $cleaned_uri = preg_replace( '/^' . $this->wp_rewrite->index . '\//', '', $cleaned_uri, 1 ); } $this->uri_parts = explode( '/', $cleaned_uri ); } return isset( $this->uri_parts[ $index ] ) ? $this->uri_parts[ $index ] : ''; } } request-handling/class-wpml-frontend-redirection-url.php 0000755 00000000702 14720342453 0017541 0 ustar 00 <?php class WPML_Frontend_Redirection_Url { /** @var string $url */ private $url; /** * @param string $url */ public function __construct( $url ) { $this->url = $url; } /** * URL is being checked for apostrophes. If there are any, apostrophes are encoded. * * @return string URL with the encoded apostrophes. */ public function encode_apostrophes_in_url() { return str_replace( "'", rawurlencode( "'" ), $this->url ); } } request-handling/redirection/wpml-frontend-redirection.php 0000755 00000002446 14720342453 0020154 0 ustar 00 <?php /** * * @return WPML_Redirection */ function _wpml_get_redirect_helper() { global $wpml_url_converter, $wpml_request_handler, $wpml_language_resolution, $sitepress; $lang_neg_type = wpml_get_setting_filter( false, 'language_negotiation_type' ); switch ( $lang_neg_type ) { case 1: global $wpml_url_filters; if ( $wpml_url_filters->frontend_uses_root() !== false ) { $redirect_helper = new WPML_Rootpage_Redirect_By_Subdir( wpml_get_setting_filter( array(), 'urls' ), $wpml_request_handler, $wpml_url_converter, $wpml_language_resolution ); } else { $redirect_helper = new WPML_Redirect_By_Subdir( $wpml_url_converter, $wpml_request_handler, $wpml_language_resolution ); } break; case 2: $wp_api = new WPML_WP_API(); $redirect_helper = new WPML_Redirect_By_Domain( icl_get_setting( 'language_domains' ), $wp_api, $wpml_request_handler, $wpml_url_converter, $wpml_language_resolution ); break; case 3: default: $redirect_helper = new WPML_Redirect_By_Param( icl_get_setting( 'taxonomies_sync_option', array() ), $wpml_url_converter, $wpml_request_handler, $wpml_language_resolution, $sitepress ); $redirect_helper->init_hooks(); } return $redirect_helper; } request-handling/redirection/wpml-redirection.class.php 0000755 00000001702 14720342453 0017435 0 ustar 00 <?php abstract class WPML_Redirection extends WPML_URL_Converter_User { /** @var WPML_Request $request_handler */ protected $request_handler; /** @var WPML_Language_Resolution $lang_resolution */ protected $lang_resolution; /** * @param WPML_URL_Converter $url_converter * @param WPML_Request $request_handler * @param WPML_Language_Resolution $lang_resolution */ function __construct( &$url_converter, &$request_handler, &$lang_resolution ) { parent::__construct( $url_converter ); $this->request_handler = $request_handler; $this->lang_resolution = $lang_resolution; } abstract public function get_redirect_target(); protected function redirect_hidden_home() { $target = false; if ( $this->lang_resolution->is_language_hidden( $this->request_handler->get_request_uri_lang() ) && ! $this->request_handler->show_hidden() ) { $target = $this->url_converter->get_abs_home(); } return $target; } } request-handling/redirection/wpml-redirect-by-subdir.class.php 0000755 00000000272 14720342453 0020626 0 ustar 00 <?php class WPML_Redirect_By_Subdir extends WPML_Redirection { /** * @return bool|string */ public function get_redirect_target() { return $this->redirect_hidden_home(); } } request-handling/redirection/wpml-redirect-by-param.class.php 0000755 00000013067 14720342453 0020444 0 ustar 00 <?php class WPML_Redirect_By_Param extends WPML_Redirection { private $post_like_params = array( 'p' => 1, 'page_id' => 1, ); private $term_like_params = array( 'cat_ID' => 1, 'cat' => 1, 'tag' => 1, ); /** @var SitePress */ private $sitepress; /** * @param array $tax_sync_option * @param WPML_URL_Converter $url_converter * @param WPML_Request $request_handler * @param WPML_Language_Resolution $lang_resolution * @param SitePress $sitepress */ public function __construct( $tax_sync_option, &$url_converter, &$request_handler, &$lang_resolution, &$sitepress ) { parent::__construct( $url_converter, $request_handler, $lang_resolution ); global $wp_rewrite; $this->sitepress = &$sitepress; if ( ! isset( $wp_rewrite ) ) { require_once ABSPATH . WPINC . '/rewrite.php'; $wp_rewrite = new WP_Rewrite(); } $this->term_like_params = array_merge( $this->term_like_params, array_filter( $tax_sync_option ) ); } public function init_hooks() { add_action( 'template_redirect', array( $this, 'template_redirect_action' ), 1 ); } /** * @return bool|string */ public function get_redirect_target() { $target = $this->redirect_hidden_home(); if ( (bool) $target === false ) { $target = ( $new_qs = $this->get_target_link_querystring() ) !== false ? ( $new_qs !== '' ? '/?' . $new_qs : '/' ) : false; $qs_parts = explode( '?', $this->request_handler->get_request_uri() ); $path = array_shift( $qs_parts ); $target = $target !== false ? rtrim( $path, '/' ) . $target : false; } return $target; } private function find_potential_translation( $query_params, $lang_code ) { if ( count( $translatable_params = array_intersect_key( $query_params, $this->post_like_params ) ) === 1 ) { /** @var WPML_Post_Translation $wpml_post_translations */ global $wpml_post_translations; $potential_translation = $wpml_post_translations->element_id_in( $query_params[ ( $parameter = key( $translatable_params ) ) ], $lang_code ); } elseif ( count( $translatable_params = array_intersect_key( $query_params, $this->term_like_params ) ) === 1 ) { /** @var WPML_Term_Translation $wpml_term_translations */ global $wpml_term_translations; $termId = $query_params[ ( $parameter = key( $translatable_params ) ) ]; if ( is_array( $termId ) ) { $termId = $termId[0]; } $potential_translation = $wpml_term_translations->term_id_in( (int) $termId, $lang_code ); } /** @var String $parameter */ return isset( $potential_translation, $parameter ) ? array( $parameter, $potential_translation ) : false; } /** * @param string $query_params_string * @param string $lang_code * * @return array|false */ private function needs_redirect( $query_params_string, $lang_code ) { global $sitepress; $element_lang = false; parse_str( $query_params_string, $query_params ); if ( isset( $query_params['lang'] ) ) { if ( $sitepress->get_default_language() === $query_params['lang'] ) { unset( $query_params['lang'] ); $changed = true; } } else { $element_lang = $this->get_element_language( $query_params_string ); if ( $element_lang && $element_lang !== $sitepress->get_default_language() ) { $query_params['lang'] = $element_lang; $changed = true; } } if ( ( $potential_translation = $this->find_potential_translation( $query_params, $lang_code ) ) !== false && (int) $query_params[ $potential_translation[0] ] !== (int) $potential_translation[1] && ! $element_lang ) { $query_params[ $potential_translation[0] ] = $potential_translation[1]; $changed = true; } return isset( $changed ) ? $query_params : false; } private function get_target_link_querystring() { $raw_query_string = $this->request_handler->get_request_uri(); $qs_parts = explode( '?', $raw_query_string ); $query_string = array_pop( $qs_parts ); $query_params_new = $this->needs_redirect( $query_string, $this->url_converter->get_language_from_url( $raw_query_string ) ); return $query_params_new !== false ? rawurldecode( http_build_query( $query_params_new ) ) : false; } /** * @param string $query_params_string * * @return null|string */ private function get_element_language( $query_params_string ) { $language = ''; list( $element_id, $element_type ) = $this->get_element_details( $query_params_string ); if ( $element_id && $element_type ) { $language = $this->sitepress->get_language_for_element( $element_id, $element_type ); } return $language; } /** * @param string $url * * @return array */ private function get_element_details( $url ) { $element_id = ''; $element_type = ''; parse_str( $url, $query_args ); if ( isset( $query_args['p'] ) ) { $element_id = $query_args['p']; $element_type = 'post_' . get_post_type( (int) $element_id ); } elseif ( isset( $query_args['cat'] ) ) { $element_id = $query_args['cat']; $term = get_term( (int) $element_id ); $element_type = 'tax_' . $term->taxonomy; } return array( $element_id, $element_type ); } /** * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-2822 */ public function template_redirect_action() { if ( $this->sitepress->get_wp_api()->is_front_page() && $this->sitepress->get_wp_api()->get_query_var( 'page' ) && $this->sitepress->get_default_language() !== $this->sitepress->get_current_language() ) { remove_action( 'template_redirect', 'redirect_canonical' ); } } } request-handling/redirection/wpml-redirect-by-domain.class.php 0000755 00000002150 14720342453 0020602 0 ustar 00 <?php class WPML_Redirect_By_Domain extends WPML_Redirection { /** @var array $domains */ private $domains; /** @var WPML_WP_API $wp_api */ private $wp_api; /** * @param array $domains * @param WPML_WP_API $wp_api * @param WPML_URL_Converter $url_converter * @param WPML_Request $request_handler * @param WPML_Language_Resolution $lang_resolution */ public function __construct( $domains, &$wp_api, &$request_handler, &$url_converter, &$lang_resolution ) { parent::__construct( $url_converter, $request_handler, $lang_resolution ); $this->domains = $domains; $this->wp_api = &$wp_api; } public function get_redirect_target( $language = false ) { if ( $this->wp_api->is_admin() && $this->lang_resolution->is_language_hidden( $language ) && strpos( $_SERVER['REQUEST_URI'], 'wp-login.php' ) === false && ! $this->wp_api->user_can( wp_get_current_user(), 'manage_options' ) ) { $target = trailingslashit( $this->domains[ $language ] ) . 'wp-login.php'; } else { $target = $this->redirect_hidden_home(); } return $target; } } request-handling/redirection/wpml-rootpage-redirect-by-subdir.class.php 0000755 00000003113 14720342453 0022441 0 ustar 00 <?php class WPML_Rootpage_Redirect_By_Subdir extends WPML_Redirect_By_Subdir { /** @var array $urls */ private $urls; /** * @param array $urls * @param WPML_Request $request_handler * @param WPML_URL_Converter $url_converter * @param WPML_Language_Resolution $lang_resolution */ public function __construct( $urls, &$request_handler, &$url_converter, &$lang_resolution ) { parent::__construct( $url_converter, $request_handler, $lang_resolution ); $this->urls = $urls; } public function get_redirect_target() { global $wpml_url_filters; $request_uri = $this->request_handler->get_request_uri(); $target = parent::get_redirect_target(); $target = $target ? $target : ( ( $filtered_root_url = $wpml_url_filters->filter_root_permalink( wpml_strip_subdir_from_url( site_url() ) . $request_uri ) ) !== wpml_strip_subdir_from_url( site_url() ) . $request_uri ? $filtered_root_url : false ); if ( $target === false ) { $this->maybe_setup_rootpage(); } return $target; } private function maybe_setup_rootpage() { if ( WPML_Root_Page::is_current_request_root() ) { if ( WPML_Root_Page::uses_html_root() ) { $html_file = ( false === strpos( $this->urls['root_html_file_path'], '/' ) ? ABSPATH : '' ) . $this->urls['root_html_file_path']; /** @noinspection PhpIncludeInspection */ include $html_file; exit; } else { $root_page_actions = wpml_get_root_page_actions_obj(); $root_page_actions->wpml_home_url_setup_root_page(); } } } } request-handling/wpml-request.class.php 0000755 00000012224 14720342453 0014310 0 ustar 00 <?php /** * Class WPML_Request * * @package wpml-core * @subpackage wpml-requests * * @abstract */ use WPML\Language\Detection\CookieLanguage; use WPML\UrlHandling\WPLoginUrlConverterRules; use WPML\FP\Obj; abstract class WPML_Request { /** @var WPML_URL_Converter */ protected $url_converter; protected $active_languages; protected $default_language; /** @var CookieLanguage */ protected $cookieLanguage; /** * @param WPML_URL_Converter $url_converter * @param array $active_languages * @param string $default_language * @param CookieLanguage $cookieLanguage */ public function __construct( WPML_URL_Converter $url_converter, $active_languages, $default_language, CookieLanguage $cookieLanguage ) { $this->url_converter = $url_converter; $this->active_languages = $active_languages; $this->default_language = $default_language; $this->cookieLanguage = $cookieLanguage; } abstract protected function get_cookie_name(); /** * Determines the language of the current request. * * @return string|false language code of the current request, determined from the requested url and the user's * cookie. */ abstract public function get_requested_lang(); /** * Returns the current REQUEST_URI optionally filtered * * @param null|int $filter filter to apply to the REQUEST_URI, takes the same arguments * as filter_var for the filter type. * * @return string */ public function get_request_uri( $filter = null ) { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/'; if ( $filter !== null ) { $request_uri = filter_var( $request_uri, $filter ); } return $request_uri; } /** * @global $wpml_url_converter * * @return string|false language code that can be determined from the currently requested URI. */ public function get_request_uri_lang() { /** * Avoid returning language from URL when wpml_should_skip_saving_language_in_cookies filter hook returns TRUE * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1544 */ if ( apply_filters( 'wpml_should_skip_saving_language_in_cookies', false ) ) { return false; } $req_url = isset( $_SERVER['HTTP_HOST'] ) ? untrailingslashit( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) : ''; return $this->url_converter->get_language_from_url( $req_url ); } /** * @return string language code stored in the user's wp-wpml_current_language cookie */ public function get_cookie_lang() { return $this->cookieLanguage->get( $this->get_cookie_name() ); } /** * Checks whether hidden languages are to be displayed at the moment. * They are displayed in the frontend if the users has the respective option icl_show_hidden_languages set in his * user_meta. The are displayed in the backend for all admins with manage_option capabilities. * * @return bool true if hidden languages are to be shown */ public function show_hidden() { $queryVars = []; if ( isset( $_SERVER['QUERY_STRING'] ) ) { parse_str( $_SERVER['QUERY_STRING'], $queryVars ); } $isReviewPostPage = ( Obj::has( 'wpmlReviewPostType', $queryVars ) && Obj::has( 'preview_id', $queryVars ) && Obj::has( 'preview_nonce', $queryVars ) && Obj::has( 'preview', $queryVars ) && Obj::has( 'jobId', $queryVars ) && Obj::has( 'returnUrl', $queryVars ) ); $isPostsListPage = false; if ( isset( $_SERVER['REQUEST_URI'] ) ) { $uri = urldecode( $_SERVER['REQUEST_URI'] ); if ( is_admin() && $uri && strpos( $uri, 'edit.php' ) !== false ) { $isPostsListPage = true; } } return ! did_action( 'init' ) || ( get_user_meta( get_current_user_id(), 'icl_show_hidden_languages', true ) || ( ( is_admin() || wpml_is_rest_request() ) && $this->isAdmin() ) ) || ( ( $isPostsListPage || $isReviewPostPage ) && ( $this->isAdmin() || $this->isEditor() ) ) || ( $isReviewPostPage && is_user_logged_in() && $this->isSubscriber() ); } /** * @return boolean */ private function isAdmin() { return current_user_can( 'manage_options' ); } /** * @return boolean */ private function isEditor() { $can = $this->getCaps(); return $can['read'] && $can['publish'] && $can['edit'] && \WPML\LIB\WP\User::isTranslator(); } /** * @return boolean */ private function isSubscriber() { return current_user_can( 'read' ) && \WPML\LIB\WP\User::isTranslator(); } /** * @return array */ private function getCaps() { $canPublish = current_user_can( 'publish_pages' ) || current_user_can( 'publish_posts' ); $canRead = current_user_can( 'read_private_pages' ) || current_user_can( 'read_private_posts' ); $canEdit = current_user_can( 'edit_posts' ); return [ 'publish' => $canPublish, 'read' => $canRead, 'edit' => $canEdit, ]; } /** * Sets the language code of the current screen in the User's wp-wpml_current_language cookie * * When user is not logged we must set cookie with JS to avoid issues with cached pages * * @param string $lang_code */ public function set_language_cookie( $lang_code ) { $this->cookieLanguage->set( $this->get_cookie_name(), $lang_code ); } } class-wpml-translation-management.php 0000755 00000057455 14720342453 0014034 0 ustar 00 <?php use WPML\TM\ATE\ClonedSites\Lock as AteApiLock; use function WPML\Container\make; use WPML\LIB\WP\User; /** * Class WPML_Translation_Management */ class WPML_Translation_Management { const PAGE_SLUG_MANAGEMENT = '/menu/main.php'; const PAGE_SLUG_SETTINGS = '/menu/settings'; const PAGE_SLUG_QUEUE = '/menu/translations-queue.php'; var $load_priority = 200; /** @var SitePress $sitepress */ protected $sitepress; /** @var WPML_TM_Loader $tm_loader */ private $tm_loader; /** @var TranslationManagement $tm_instance */ private $tm_instance; /** @var WPML_Translations_Queue $tm_queue */ private $tm_queue; /** @var WPML_TM_Menus_Management $wpml_tm_menus_management */ private $wpml_tm_menus_management; /** @var WPML_Ajax_Route $ajax_route */ private $ajax_route; /** * @var WPML_TP_Translator */ private $wpml_tp_translator; /** @var WPML_UI_Screen_Options_Pagination $dashboard_screen_options */ private $dashboard_screen_options; /** * WPML_Translation_Management constructor. * * @param SitePress $sitepress * @param WPML_TM_Loader $tm_loader * @param TranslationManagement $tm_instance * @param WPML_TP_Translator $wpml_tp_translator */ function __construct( $sitepress, $tm_loader, $tm_instance, WPML_TP_Translator $wpml_tp_translator = null ) { $this->sitepress = $sitepress; $this->tm_loader = $tm_loader; $this->tm_instance = $tm_instance; $this->wpml_tp_translator = $wpml_tp_translator; } public function init() { global $wpdb; $this->disableAllNonWPMLNotices(); $template_service_loader = new WPML_Twig_Template_Loader( array( WPML_TM_PATH . '/templates/tm-menus/' ) ); $this->wpml_tm_menus_management = new WPML_TM_Menus_Management( $template_service_loader->get_template() ); $mcs_factory = new WPML_TM_Scripts_Factory(); $mcs_factory->init_hooks(); if ( null === $this->wpml_tp_translator ) { $this->wpml_tp_translator = new WPML_TP_Translator(); } $this->ajax_route = new WPML_Ajax_Route( new WPML_TM_Ajax_Factory( $wpdb, $this->sitepress, $_POST ) ); } public function load() { global $pagenow; $this->tm_loader->tm_after_load(); $wpml_wp_api = $this->sitepress->get_wp_api(); if ( $wpml_wp_api->is_admin() ) { $this->tm_loader->load_xliff_frontend(); } $this->plugin_localization(); add_action( 'wp_ajax_basket_extra_fields_refresh', array( $this, 'basket_extra_fields_refresh' ) ); if ( $this->notices_added_because_wpml_is_inactive_or_incomplete() ) { return false; } $this->handle_get_requests(); $this->tm_loader->load_pro_translation( $wpml_wp_api ); if ( $wpml_wp_api->is_admin() ) { $this->add_pre_tm_init_admin_hooks(); do_action( 'wpml_tm_init' ); $this->add_post_tm_init_admin_hooks( $pagenow ); } $this->api_hooks(); add_filter( 'wpml_config_white_list_pages', array( $this, 'filter_wpml_config_white_list_pages' ) ); do_action( 'wpml_tm_loaded' ); return true; } public function api_hooks() { add_action( 'wpml_save_custom_field_translation_option', array( $this, 'wpml_save_custom_field_translation_option' ), 10, 2 ); } /** * @return bool `true` if notices were added */ private function notices_added_because_wpml_is_inactive_or_incomplete() { $wpml_wp_api = $this->sitepress->get_wp_api(); if ( ! $wpml_wp_api->constant( 'ICL_SITEPRESS_VERSION' ) || $wpml_wp_api->constant( 'ICL_PLUGIN_INACTIVE' ) ) { if ( ! function_exists( 'is_multisite' ) || ! is_multisite() ) { add_action( 'admin_notices', array( $this, '_no_wpml_warning' ) ); } return true; } elseif ( ! $this->sitepress->get_setting( 'setup_complete' ) ) { $this->maybe_show_wpml_not_installed_warning(); return true; } return false; } public function filter_wpml_config_white_list_pages( array $white_list_pages ) { $white_list_pages[] = WPML_TM_FOLDER . self::PAGE_SLUG_MANAGEMENT; $white_list_pages[] = WPML_TM_FOLDER . self::PAGE_SLUG_SETTINGS; return $white_list_pages; } public function maybe_show_wpml_not_installed_warning() { if ( ! ( isset( $_GET['page'] ) && 'sitepress-multilingual-cms/menu/languages.php' === $_GET['page'] ) ) { add_action( 'admin_notices', array( $this, '_wpml_not_installed_warning' ) ); } } function trashed_post_actions( $post_id ) { // Removes trashed post from the basket TranslationProxy_Basket::delete_item_from_basket( $post_id ); } function is_jobs_tab() { return $this->is_tm_page( 'jobs' ); } function is_translators_tab() { return $this->is_tm_page( 'translators' ); } function admin_enqueue_scripts() { if ( ! defined( 'DOING_AJAX' ) ) { wp_register_script( 'wpml-tm-progressbar', WPML_TM_URL . '/res/js/wpml-progressbar.js', array( 'jquery', 'jquery-ui-progressbar', 'backbone', ), ICL_SITEPRESS_VERSION ); wp_register_script( 'wpml-tm-scripts', WPML_TM_URL . '/res/js/scripts-tm.js', array( 'jquery', 'sitepress-scripts', ), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'wpml-tm-scripts' ); wp_enqueue_style( 'wpml-tm-styles', WPML_TM_URL . '/res/css/style.css', array(), ICL_SITEPRESS_VERSION ); if ( $this->sitepress->get_wp_api()->is_translation_queue_page() ) { wp_enqueue_style( 'wpml-tm-queue', WPML_TM_URL . '/res/css/translations-queue.css', array(), ICL_SITEPRESS_VERSION ); } if ( filter_input( INPUT_GET, 'page' ) === WPML_TM_FOLDER . '/menu/main.php' ) { if ( isset( $_GET['sm'] ) && $_GET['sm'] === 'translators' ) { wp_enqueue_script( 'wpml-select-2', ICL_PLUGIN_URL . '/lib/select2/select2.min.js', array( 'jquery' ), ICL_SITEPRESS_VERSION, true ); wp_enqueue_script( 'wpml-tm-translation-roles-select2', WPML_TM_URL . '/res/js/translation-roles-select2.js', array(), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'wpml-tm-set-translation-roles', WPML_TM_URL . '/res/js/set-translation-role.js', array( 'underscore' ), ICL_SITEPRESS_VERSION ); } wp_enqueue_script( 'wpml-tm-translation-proxy', WPML_TM_URL . '/res/js/translation-proxy.js', array( 'wpml-tm-scripts', 'jquery-ui-dialog' ), ICL_SITEPRESS_VERSION ); } if ( WPML_TM_Page::is_settings() ) { WPML_Simple_Language_Selector::enqueue_scripts(); } wp_enqueue_style( 'wp-jquery-ui-dialog' ); wp_enqueue_script( 'thickbox' ); do_action( 'wpml_tm_scripts_enqueued' ); } } function admin_print_styles() { if ( ! defined( 'DOING_AJAX' ) ) { wp_enqueue_style( 'wpml-tm-styles', WPML_TM_URL . '/res/css/style.css', array( 'jquery-ui-theme', 'jquery-ui-theme' ), ICL_SITEPRESS_VERSION ); if ( $this->sitepress->get_wp_api()->is_translation_queue_page() ) { wp_enqueue_style( 'wpml-tm-editor-css', WPML_TM_URL . '/res/css/translation-editor/translation-editor.css', array(), ICL_SITEPRESS_VERSION ); wp_enqueue_style( OTGS_Assets_Handles::POPOVER_TOOLTIP ); wp_enqueue_script( OTGS_Assets_Handles::POPOVER_TOOLTIP ); } // TODO Load only in translation editor && taxonomy transaltion wp_enqueue_style( 'wpml-dialog' ); wp_enqueue_style( OTGS_Assets_Handles::SWITCHER ); } } function translation_service_js_data( $data ) { $data['nonce']['translation_service_authentication'] = wp_create_nonce( 'translation_service_authentication' ); $data['nonce']['translation_service_toggle'] = wp_create_nonce( 'translation_service_toggle' ); return $data; } function _no_wpml_warning() { ?> <div class="message error wpml-admin-notice wpml-tm-inactive wpml-inactive"><p> <?php printf( __( 'WPML Translation Management is enabled but not effective. It requires <a href="%s">WPML</a> in order to work.', 'wpml-translation-management' ), 'https://wpml.org/' ); ?> </p></div> <?php } function _wpml_not_installed_warning() { ?> <div class="message error wpml-admin-notice wpml-tm-inactive wpml-not-configured"> <p><?php printf( __( 'WPML Translation Management is enabled but not effective. Please finish the installation of WPML first.', 'wpml-translation-management' ) ); ?></p></div> <?php } function _old_wpml_warning() { ?> <div class="message error wpml-admin-notice wpml-tm-inactive wpml-outdated"><p> <?php printf( __( 'WPML Translation Management is enabled but not effective. It is not compatible with <a href="%s">WPML</a> versions prior 2.0.5.', 'wpml-translation-management' ), 'https://wpml.org/' ); ?> </p></div> <?php } function job_saved_message() { ?> <div class="message updated wpml-admin-notice"><p><?php printf( __( 'Translation saved.', 'wpml-translation-management' ) ); ?></p></div> <?php } function job_cancelled_message() { ?> <div class="message updated wpml-admin-notice"><p><?php printf( __( 'Translation cancelled.', 'wpml-translation-management' ) ); ?></p></div> <?php } /** * @param string $menu_id */ public function management_menu( $menu_id ) { if ( 'WPML' !== $menu_id ) { return; } $menu_label = __( 'Translation Management', 'wpml-translation-management' ); $menu = array(); $menu['order'] = 90; $menu['page_title'] = $menu_label; $menu['menu_title'] = $menu_label; $menu['capability'] = $this->get_required_cap_based_on_current_user_role(); $menu['menu_slug'] = WPML_TM_FOLDER . self::PAGE_SLUG_MANAGEMENT; $menu['function'] = array( $this, 'management_page' ); do_action( 'set_wpml_root_menu_capability', $menu['capability'] ); do_action( 'wpml_admin_menu_register_item', $menu ); } function management_page() { $this->wpml_tm_menus_management->display_main( $this->dashboard_screen_options ); } /** * Sets up the menu items for non-admin translators pointing at the TM * and ST translators interfaces * * @param string $menu_id */ public function translators_menu( $menu_id ) { if ( 'WPML' !== $menu_id ) { return; } $can_manage_translation_management = User::canManageTranslations() || User::hasCap( User::CAP_MANAGE_TRANSLATION_MANAGEMENT ); $menu = array(); $menu['order'] = 400; $menu['page_title'] = __( 'Translations', 'wpml-translation-management' ); $menu['menu_title'] = __( 'Translations', 'wpml-translation-management' ); $menu['menu_slug'] = WPML_TM_FOLDER . '/menu/translations-queue.php'; $menu['function'] = array( $this, 'translation_queue_page' ); $menu['icon_url'] = ICL_PLUGIN_URL . '/res/img/icon16.png'; if ( $can_manage_translation_management ) { $menu['capability'] = $this->get_required_cap_based_on_current_user_role(); do_action( 'wpml_admin_menu_register_item', $menu ); } else { $has_language_pairs = (bool) $this->tm_instance->get_current_translator()->language_pairs; $menu['capability'] = $has_language_pairs ? User::CAP_TRANSLATE : ''; $menu = apply_filters( 'wpml_menu_page', $menu ); do_action( 'wpml_admin_menu_register_item', $menu ); } } /** * Renders the TM queue * * @used-by \WPML_Translation_Management::menu */ function translation_queue_page() { if ( true !== apply_filters( 'wpml_tm_lock_ui', false ) && $this->is_the_main_request() && ! AteApiLock::isLocked() ) { $this->tm_queue->display(); } } /** * @param string $menu_id */ public function settings_menu( $menu_id ) { if ( 'WPML' !== $menu_id ) { return; } $menu_label = __( 'Settings', 'wpml-translation-management' ); $menu = array(); $menu['order'] = 9900; // see WPML_Main_Admin_Menu::MENU_ORDER_SETTINGS $menu['page_title'] = $menu_label; $menu['menu_title'] = $menu_label; $menu['capability'] = $this->get_required_cap_based_on_current_user_role(); $menu['menu_slug'] = WPML_TM_FOLDER . self::PAGE_SLUG_SETTINGS; $menu['function'] = array( $this, 'settings_page' ); do_action( 'wpml_admin_menu_register_item', $menu ); } public function settings_page() { $settings_page = new WPML_TM_Menus_Settings(); $settings_page->init(); $settings_page->display_main(); } private function is_the_main_request() { return ! isset( $_SERVER['HTTP_ACCEPT'] ) || false !== strpos( $_SERVER['HTTP_ACCEPT'], 'text/html' ); } function dismiss_icl_side_by_site() { global $iclTranslationManagement; $iclTranslationManagement->settings['doc_translation_method'] = ICL_TM_TMETHOD_MANUAL; $iclTranslationManagement->save_settings(); exit; } function plugin_action_links( $links, $file ) { $this_plugin = basename( WPML_TM_PATH ) . '/plugin.php'; if ( $file == $this_plugin ) { $links[] = '<a href="admin.php?page=' . basename( WPML_TM_PATH ) . '/menu/main.php">' . __( 'Configure', 'wpml-translation-management' ) . '</a>'; } return $links; } // Localization function plugin_localization() { load_plugin_textdomain( 'wpml-translation-management', false, plugin_basename( WPML_TM_PATH ) . '/locale' ); } function _icl_tm_toggle_promo() { global $sitepress; $value = filter_input( INPUT_POST, 'value', FILTER_VALIDATE_INT ); $iclsettings['dashboard']['hide_icl_promo'] = (int) $value; $sitepress->save_settings( $iclsettings ); exit; } public function automatic_service_selection_action() { $this->automatic_service_selection(); } public function basket_extra_fields_refresh() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'basket_extra_fields_refresh' ) ) { echo ( esc_html__( 'Invalid request!', 'sitepress' ) ); die(); } echo TranslationProxy_Basket::get_basket_extra_fields_inputs(); die(); } /** * If user display Translation Dashboard or Translators * * @return boolean */ function automatic_service_selection_pages() { return is_admin() && isset( $_GET['page'] ) && $_GET['page'] == WPML_TM_FOLDER . '/menu/main.php' && ( ! isset( $_GET['sm'] ) || $_GET['sm'] == 'translators' || $_GET['sm'] == 'dashboard' ); } public function add_com_log_link() { WPML_TranslationProxy_Com_Log::add_com_log_link(); } public function service_requires_translators() { $result = false; $service_has_translators = TranslationProxy::translator_selection_available(); if ( $service_has_translators ) { $result = ! $this->service_has_accepted_translators(); } return $result; } private function service_has_accepted_translators() { $result = false; $icl_data = $this->wpml_tp_translator->get_icl_translator_status(); if ( isset( $icl_data['icl_lang_status'] ) && is_array( $icl_data['icl_lang_status'] ) ) { foreach ( $icl_data['icl_lang_status'] as $translator ) { if ( isset( $translator['contract_id'] ) && $translator['contract_id'] != 0 ) { $result = true; break; } } } return $result; } private function is_tm_page( $tab = null ) { $result = is_admin() && isset( $_GET['page'] ) && $_GET['page'] == WPML_TM_FOLDER . '/menu/main.php'; if ( $tab ) { $result = $result && isset( $_GET['sm'] ) && $_GET['sm'] == $tab; } return $result; } private function automatic_service_selection() { if ( defined( 'DOING_AJAX' ) || ! $this->automatic_service_selection_pages() ) { return; } $done = wp_cache_get( 'done', 'automatic_service_selection' ); ICL_AdminNotifier::remove_message( 'automatic_service_selection' ); $tp_default_suid = TranslationProxy::get_tp_default_suid(); if ( ! $done && $tp_default_suid ) { $selected_service = TranslationProxy::get_current_service(); if ( isset( $selected_service->suid ) && $selected_service->suid == $tp_default_suid ) { return; } try { $service_by_suid = TranslationProxy_Service::get_service_by_suid( $tp_default_suid ); } catch ( Exception $ex ) { $service_by_suid = false; } if ( isset( $service_by_suid->id ) ) { $selected_service_id = isset( $selected_service->id ) ? $selected_service->id : false; if ( ! $selected_service_id || $selected_service_id != $service_by_suid->id ) { if ( $selected_service_id ) { TranslationProxy::deselect_active_service(); } $result = TranslationProxy::select_service( $service_by_suid->id ); if ( is_wp_error( $result ) ) { $error_data_string = $result->get_error_message(); } } } else { $error_data_string = __( "WPML can't find the translation service. Please contact WPML Support or your translation service provider.", 'wpml-translation-management' ); } } if ( isset( $error_data_string ) ) { $automatic_service_selection_args = array( 'id' => 'automatic_service_selection', 'group' => 'automatic_service_selection', 'msg' => $error_data_string, 'type' => 'error', 'admin_notice' => true, 'hide' => false, ); ICL_AdminNotifier::add_message( $automatic_service_selection_args ); } wp_cache_set( 'done', true, 'automatic_service_selection' ); } /** * @param $custom_field_name * @param $translation_option */ public function wpml_save_custom_field_translation_option( $custom_field_name, $translation_option ) { $custom_field_name = sanitize_text_field( $custom_field_name ); if ( ! $custom_field_name ) { return; } $available_options = array( WPML_IGNORE_CUSTOM_FIELD, WPML_COPY_CUSTOM_FIELD, WPML_COPY_ONCE_CUSTOM_FIELD, WPML_TRANSLATE_CUSTOM_FIELD, ); $translation_option = absint( $translation_option ); if ( ! in_array( $translation_option, $available_options ) ) { $translation_option = WPML_IGNORE_CUSTOM_FIELD; } $tm_settings = $this->sitepress->get_setting( 'translation-management', array() ); $tm_settings['custom_fields_translation'][ $custom_field_name ] = $translation_option; $this->sitepress->set_setting( 'translation-management', $tm_settings, true ); } private function handle_get_requests() { if ( isset( $_GET['wpml_tm_saved'] ) ) { add_action( 'admin_notices', array( $this, 'job_saved_message' ) ); } if ( isset( $_GET['wpml_tm_cancel'] ) ) { add_action( 'admin_notices', array( $this, 'job_cancelled_message' ) ); } if ( isset( $_GET['icl_action'] ) ) { $this->handle_icl_action_reminder_popup(); } } private function handle_icl_action_reminder_popup() { if ( $_GET['icl_action'] === 'reminder_popup' && isset( $_GET['_icl_nonce'] ) && wp_verify_nonce( $_GET['_icl_nonce'], 'reminder_popup_nonce' ) ) { add_action( 'init', array( 'TranslationProxy_Popup', 'display' ) ); } elseif ( $_GET['icl_action'] === 'dismiss_help' ) { $this->sitepress->set_setting( 'dont_show_help_admin_notice', true, true ); } } private function add_pre_tm_init_admin_hooks() { add_action( 'init', array( $this, 'automatic_service_selection_action' ) ); add_action( 'translation_service_authentication', array( $this, 'translation_service_authentication' ) ); add_action( 'trashed_post', array( $this, 'trashed_post_actions' ), 10, 1 ); add_action( 'wp_ajax_wpml-flush-website-details-cache', array( 'TranslationProxy_Translator', 'flush_website_details_cache_action' ) ); add_action( 'wpml_updated_translation_status', array( 'TranslationProxy_Batch', 'maybe_assign_generic_batch' ), 10, 1 ); add_filter( 'translation_service_js_data', array( $this, 'translation_service_js_data' ) ); add_filter( 'wpml_string_status_text', array( 'WPML_Remote_String_Translation', 'string_status_text_filter' ), 10, 2 ); } /** * @param $pagenow */ private function add_translation_in_progress_warning( $pagenow ) { if ( in_array( $pagenow, array( 'post-new.php', 'post.php', 'admin-ajax.php' ), true ) ) { $post_edit_notices_factory = new WPML_TM_Post_Edit_Notices_Factory(); $post_edit_notices_factory->create() ->add_hooks(); } } /** * @param $pagenow */ private function add_post_tm_init_admin_hooks( $pagenow ) { $this->add_non_theme_customizer_hooks( $pagenow ); $this->add_menu_items(); add_filter( 'plugin_action_links', array( $this, 'plugin_action_links' ), 10, 2 ); $this->add_translation_queue_hooks(); $this->add_dashboard_screen_options(); // Add a nice warning message if the user tries to edit a post manually and it's actually in the process of being translated $this->add_translation_in_progress_warning( $pagenow ); add_action( 'wp_ajax_dismiss_icl_side_by_site', array( $this, 'dismiss_icl_side_by_site' ) ); add_action( 'wp_ajax_icl_tm_toggle_promo', array( $this, '_icl_tm_toggle_promo' ) ); add_action( 'wpml_support_page_after', array( $this, 'add_com_log_link' ) ); add_action( 'wpml_translation_basket_page_after', array( $this, 'add_com_log_link' ) ); $this->translate_independently(); } /** * @param $pagenow */ private function add_non_theme_customizer_hooks( $pagenow ) { if ( $pagenow !== 'customize.php' ) { // stop TM scripts from messing up theme customizer add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); add_action( 'admin_print_styles', array( $this, 'admin_print_styles' ), 11 ); $this->add_custom_xml_config(); } } private function add_custom_xml_config() { $hooks = wpml_tm_custom_xml_ui_hooks(); if ( $hooks ) { $hooks->init(); } } private function add_menu_items() { add_action( 'wpml_admin_menu_configure', array( $this, 'management_menu' ) ); add_action( 'wpml_admin_menu_configure', array( $this, 'translators_menu' ) ); add_action( 'wpml_admin_menu_configure', array( $this, 'settings_menu' ) ); } private function add_translation_queue_hooks() { if ( \WPML\UIPage::isTranslationQueue( $_GET ) ) { $this->tm_queue = make(\WPML_Translations_Queue::class); $this->tm_queue->init_hooks(); } } private function add_dashboard_screen_options() { if ( $this->sitepress->get_wp_api() ->is_dashboard_tab() ) { $screen_options_factory = wpml_ui_screen_options_factory(); $this->dashboard_screen_options = $screen_options_factory->create_pagination( 'tm_dashboard_per_page', ICL_TM_DOCS_PER_PAGE ); } } private function translate_independently() { if ( ( isset( $_GET['sm'] ) && 'basket' === $_GET['sm'] ) || ( $this->sitepress->get_wp_api() ->constant( 'DOING_AJAX' ) && isset( $_POST['action'] ) && 'icl_disconnect_posts' === $_POST['action'] ) ) { $translate_independently = wpml_tm_translate_independently(); $translate_independently->init(); } } /** * We want to disable any admin notices on the TM Dashboard page to avoid UI pollution. * Only relevant notices which are added when content is sent to translation should be displayed. * * Nevertheless, there are a few cases when we want to make an exception. * * Therefore, we load all notices which are defined by WPML via "wpml_get_admin_notices()" interface. * Moreover, you can enforce a notice to be displayed by adding it to the "wpml_tm_dashboard_notices" filter. * * Additionally, we have the cases when TM Dashboard is completely disabled. In this case, we want to display the notice about it. * It is checked by `apply_filters( 'wpml_tm_lock_ui', false )` condition. * * @return void */ private function disableAllNonWPMLNotices() { if ( \WPML\UIPage::isTMDashboard( $_GET ) ) { add_action( 'admin_head', function () { if ( ! apply_filters( 'wpml_tm_lock_ui', false ) ) { remove_all_actions( 'admin_notices' ); wpml_get_admin_notices()->add_admin_notices_action(); // Restore WPML admin notices. foreach ( (array) apply_filters( 'wpml_tm_dashboard_notices', [] ) as $notice ) { if ( is_callable( $notice ) ) { add_action( 'admin_notices', $notice ); } } } }, 1 ); } } /** * If a user should have either "administrator" or "manage_translations" or "wpml_manage_translation_management" capability * to access a TM Dashboard tab. * * @return string */ private function get_required_cap_based_on_current_user_role() { $capability = User::CAP_MANAGE_TRANSLATION_MANAGEMENT; if ( User::hasCap( User::CAP_ADMINISTRATOR ) ) { $capability = User::CAP_ADMINISTRATOR; } else if ( User::hasCap( User::CAP_MANAGE_TRANSLATIONS ) ) { $capability = User::CAP_MANAGE_TRANSLATIONS; } return $capability; } } wp-core-hooks/post/class-wpml-remove-pages-not-in-current-language.php 0000755 00000011314 14720342453 0022063 0 ustar 00 <?php use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Fns; use WPML\FP\Lst; use function WPML\FP\pipe; use WPML\API\PostTypes; class WPML_Remove_Pages_Not_In_Current_Language { /** @var \wpdb */ private $wpdb; /** @var \SitePress */ private $sitepress; /** * @param wpdb $wpdb * @param SitePress $sitepress */ public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } /** * @param array $posts Array of posts to filter * @param array $get_page_arguments Arguments passed to the `get_pages` function * @param \WP_Post[]|array[]|int[] $posts Array of posts or post IDs to filter (post IDs are required in tests but it might not be a real case) * * @return array */ function filter_pages( $posts, $get_page_arguments ) { $filtered_posts = $posts; $current_language = $this->sitepress->get_current_language(); if ( 'all' !== $current_language && 0 !== count( $posts ) ) { $post_type = $this->find_post_type( $get_page_arguments, $posts ); if ( $post_type && Lst::includes( $post_type, PostTypes::getTranslatable() ) ) { $white_list = $this->get_posts_in_current_languages( $post_type, $current_language ); if ( $this->sitepress->is_display_as_translated_post_type( $post_type ) ) { $original_posts_in_other_language = $this->get_original_posts_in_other_languages( $post_type, $current_language ); $white_list = Lst::concat( $white_list, $original_posts_in_other_language ); } $get_post_id = Logic::ifElse( 'is_numeric', Fns::identity(), Obj::prop( 'ID' ) ); $filter_not_belonging = Fns::filter( pipe( $get_post_id, Lst::includes( Fns::__, $white_list ) ) ); $filtered_posts = $filter_not_belonging( $posts ); } } return $filtered_posts; } /** * @param string $post_type * @param string $current_language * * @return array */ private function get_posts_in_current_languages( $post_type, $current_language ) { $query = " SELECT p.ID FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON p.ID = wpml_translations.element_id WHERE wpml_translations.element_type=%s AND p.post_type=%s AND wpml_translations.language_code = %s "; $args = [ 'post_' . $post_type, $post_type, $current_language ]; /** @phpstan-ignore-next-line WP doc issue: When $query isset the return is not null. */ return $this->get_post_ids( $this->wpdb->prepare( $query, $args )); } /** * @param string $post_type * @param string $current_language * * @return array */ private function get_original_posts_in_other_languages( $post_type, $current_language ) { $query = " SELECT p.ID FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON p.ID = wpml_translations.element_id WHERE wpml_translations.element_type=%s AND p.post_type=%s AND wpml_translations.language_code <> %s AND wpml_translations.source_language_code IS NULL AND NOT EXISTS ( SELECT translation_id FROM {$this->wpdb->prefix}icl_translations as other_translation WHERE other_translation.trid = wpml_translations.trid AND other_translation.language_code = %s ) "; $args = [ 'post_' . $post_type, $post_type, $current_language, $current_language ]; /** @phpstan-ignore-next-line WP doc issue: When $query isset the return is not null. */ return $this->get_post_ids( $this->wpdb->prepare( $query, $args ) ); } /** * @param string $query * * @return int[] */ private function get_post_ids( $query ) { return Fns::map( Fns::unary( 'intval' ), $this->wpdb->get_col( $query ) ); } /** * @param array<string,string> $get_page_arguments * @param array<array|int|WP_Post> $new_arr * * @return false|string */ private function find_post_type( $get_page_arguments, $new_arr ) { $post_type = 'page'; if ( array_key_exists( 'post_type', $get_page_arguments ) ) { $post_type = $get_page_arguments['post_type']; return $post_type; } else { $temp_items = array_values( $new_arr ); $first_item = $temp_items[0]; if ( is_object( $first_item ) ) { $first_item = object_to_array( $first_item ); } if ( is_array( $first_item ) ) { if ( array_key_exists( 'post_type', $first_item ) ) { $post_type = $first_item['post_type']; return $post_type; } elseif ( array_key_exists( 'ID', $first_item ) ) { $post_type = $this->sitepress->get_wp_api()->get_post_type( $first_item['ID'] ); return $post_type; } return $post_type; } elseif ( is_numeric( $first_item ) ) { $post_type = $this->sitepress->get_wp_api()->get_post_type( $first_item ); return $post_type; } return $post_type; } } } automatic-translation/ActionsFactory.php 0000755 00000000735 14720342453 0014543 0 ustar 00 <?php namespace WPML\TM\AutomaticTranslation\Actions; use WPML\LIB\WP\User; use function WPML\Container\make; use WPML\Setup\Option; class ActionsFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { /** * @return Actions|null * @throws \WPML\Auryn\InjectionException */ public function create() { return \WPML_TM_ATE_Status::is_enabled_and_activated() && Option::shouldTranslateEverything() ? make( Actions::class ) : null; } } automatic-translation/Actions.php 0000755 00000015670 14720342453 0013217 0 ustar 00 <?php namespace WPML\TM\AutomaticTranslation\Actions; use WPML\Element\API\Languages; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Post; use WPML\Settings\PostType\Automatic; use WPML\TM\API\ATE\LanguageMappings; use WPML\TM\API\Job\Map; use function WPML\FP\invoke; use WPML\LIB\WP\User; use WPML\Setup\Option; use function WPML\FP\partial; use WPML\TM\API\Jobs; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class Actions implements \IWPML_Action { /** @see \WPML\PB\Shutdown\Hooks */ const PRIORITY_AFTER_PB_PROCESS = 100; /** @var \WPML_Translation_Element_Factory */ private $translationElementFactory; public function __construct( \WPML_Translation_Element_Factory $translationElementFactory ) { $this->translationElementFactory = $translationElementFactory; } public function add_hooks() { Hooks::onAction( 'wpml_after_save_post', 100 ) ->then( spreadArgs( Fns::memorize( [ $this, 'sendToTranslation' ] ) ) ); } /** * @param int $postId * @param callable|null $onComplete * * @throws \WPML\Auryn\InjectionException */ public function sendToTranslation( $postId, $onComplete = null ) { $execOnComplete = function() use ( $postId, $onComplete ) { if ( is_callable( $onComplete ) ) { $onComplete( $postId ); } }; if ( empty( $_POST['icl_minor_edit'] ) ) { $postElement = $this->translationElementFactory->create_post( $postId ); if ( $postElement->is_translatable() && ! $postElement->is_display_as_translated() && Automatic::isAutomatic( $postElement->get_type() ) ) { Hooks::onAction( 'shutdown', self::PRIORITY_AFTER_PB_PROCESS ) ->then( function() use ( $postId, $execOnComplete ) { $postElement = $this->translationElementFactory->create_post( $postId ); if ( $postElement->get_wp_object()->post_status === 'publish' && $postElement->get_language_code() === Languages::getDefaultCode() && $postElement->get_source_language_code() === null /** * Allows excluding some posts from automatic translation. * * @param bool false Is the post excluded. * @param int $postId The post ID to check. */ && ! apply_filters( 'wpml_exclude_post_from_auto_translate', false, $postId ) ) { $secondaryLanguageCodes = LanguageMappings::geCodesEligibleForAutomaticTranslations(); $this->cancelExistingTranslationJobs( $postElement, $secondaryLanguageCodes ); $this->createTranslationJobs( $postElement, $secondaryLanguageCodes ); } $execOnComplete(); } ); } else { $execOnComplete(); } } else { $execOnComplete(); } } /** * @param \WPML_Post_Element $postElement * @param array $languages * * @throws \WPML\Auryn\InjectionException */ private function cancelExistingTranslationJobs( \WPML_Post_Element $postElement, $languages ) { $getJobEntity = function ( $jobId ) { return wpml_tm_get_jobs_repository()->get_job( Map::fromJobId( $jobId ), \WPML_TM_Job_Entity::POST_TYPE ); }; wpml_collect( $languages ) ->map( Jobs::getPostJob( $postElement->get_element_id(), $postElement->get_type() ) ) ->filter() ->reject( self::isCompleteAndUpToDateJob() ) ->map( Obj::prop( 'job_id' ) ) ->map( Jobs::clearReviewStatus() ) ->map( Jobs::setNotTranslatedStatus() ) ->map( Jobs::clearTranslated() ) ->map( $getJobEntity ) ->map( Fns::tap( partial( 'do_action', 'wpml_tm_job_cancelled' ) ) ); } /** * @return callable :: \stdClass -> bool */ private static function isCompleteAndUpToDateJob() { return function ( $job ) { return Cast::toInt( $job->needs_update ) !== 1 && Cast::toInt( $job->status ) === ICL_TM_COMPLETE; }; } public function createTranslationJobs( \WPML_Post_Element $postElement, $targetLanguages ) { if ( Option::isPausedTranslateEverything() ) { return; } $isNotCompleteAndUpToDate = Logic::complement( self::isCompleteAndUpToDateJob() ); $sendToTranslation = function ( $language ) use ( $postElement, $isNotCompleteAndUpToDate ) { /** @var \stdClass|false $job */ $job = Jobs::getPostJob( $postElement->get_element_id(), $postElement->get_type(), $language ); if ( ! $job || ( $isNotCompleteAndUpToDate( $job ) && $this->canJobBeReTranslatedAutomatically( $job->job_id ) ) ) { $this->createJob( $postElement, $language ); } }; Fns::map( $sendToTranslation, $targetLanguages ); } /** * @param int $jobId * * @return bool */ private function canJobBeReTranslatedAutomatically( $jobId ) { return wpml_tm_load_old_jobs_editor()->get( $jobId ) === \WPML_TM_Editors::ATE; } /** * @param \WPML_Post_Element $postElement * @param string $language */ private function createJob( \WPML_Post_Element $postElement, $language ) { $batch = new \WPML_TM_Translation_Batch( [ new \WPML_TM_Translation_Batch_Element( $postElement->get_element_id(), 'post', $postElement->get_language_code(), [ $language => 1 ] ), ], \TranslationProxy_Batch::get_generic_batch_name( true ), [ $language => User::getCurrentId() ] ); wpml_load_core_tm()->send_jobs( $batch, 'post', Jobs::SENT_AUTOMATICALLY ); } /** * @param $sourceLanguage * @param array $elements E.g. [ [1, 'fr'], [1, 'de'], [2, 'fr'] ] */ public function createNewTranslationJobs( $sourceLanguage, array $elements ) { $getTargetLang = Lst::nth( 1 ); $setTranslateAction = Obj::objOf( Fns::__, \TranslationManagement::TRANSLATE_ELEMENT_ACTION ); $setTranslatorId = Obj::objOf( Fns::__, User::getCurrentId() ); $targetLanguages = \wpml_collect( $elements ) ->map( $getTargetLang ) ->unique() ->mapWithKeys( $setTranslatorId ) ->toArray(); $makeBatchElement = function ( $targetLanguages, $postId ) use ( $sourceLanguage ) { return new \WPML_TM_Translation_Batch_Element( $postId, 'post', $sourceLanguage, $targetLanguages->toArray() ); }; $batchElements = \wpml_collect( $elements ) ->groupBy( 0 ) ->map( Fns::map( $getTargetLang ) ) ->map( invoke( 'mapWithKeys' )->with( $setTranslateAction ) ) ->map( $makeBatchElement ) ->values() ->toArray(); $batch = new \WPML_TM_Translation_Batch( $batchElements, \TranslationProxy_Batch::get_generic_batch_name( true ), $targetLanguages ); wpml_load_core_tm()->send_jobs( $batch, 'post', Jobs::SENT_AUTOMATICALLY ); $getJobId = pipe( Fns::converge( Jobs::getPostJob(), [ Obj::prop( 'postId' ), Obj::prop( 'postType' ), Obj::prop( 'lang' ) ] ), Obj::prop( 'job_id' ), Fns::unary( 'intval' ) ); return \wpml_collect( $elements ) ->map( Lst::zipObj( [ 'postId', 'lang' ] ) ) ->map( Obj::addProp( 'postType', pipe( Obj::prop( 'postId' ), Post::getType() ) ) ) ->map( Obj::addProp( 'jobId', $getJobId ) ) ->toArray(); } } container/functions.php 0000755 00000005301 14720342453 0011255 0 ustar 00 <?php namespace WPML\Container; use function WPML\FP\curryN; if ( ! function_exists( 'WPML\Container\make' ) ) { /** * Curried function * * Make returns a new instance otherwise returns a shared instance if the * class_name or an instance is set as shared using the share function * * @param string $class_name * @param array $args * * @return object * @throws \WPML\Auryn\InjectionException * * * @phpstan-template T of object * @phpstan-param class-string<T> $class_name * * @phpstan-return T */ function make( $class_name = null, array $args = null ) { $make = function ( $class_name, $args = [] ) { if ( class_exists( $class_name ) || interface_exists( $class_name ) ) { return Container::make( $class_name, $args ); } return null; }; return call_user_func_array( curryN( 1, $make ), func_get_args() ); } } if ( ! function_exists( 'WPML\Container\share' ) ) { /** * class names or instances that should be shared. * Shared means that only one instance is ever created when calling the make function. * * @param array $names_or_instances * * @throws \WPML\Auryn\ConfigException */ function share( array $names_or_instances ) { Container::share( $names_or_instances ); } } if ( ! function_exists( 'WPML\Container\alias' ) ) { /** * This allows to define aliases classes to be used in place of type hints. * e.g. [ * // generic => specific * 'wpdb' => 'QM_DB', * ] * * @param array $aliases * * @throws \WPML\Auryn\ConfigException */ function alias( array $aliases ) { Container::alias( $aliases ); } } if ( ! function_exists( 'WPML\Container\delegate' ) ) { /** * This allows to delegate the object instantiation to a factory. * It can be any kind of callable (class or function). * * @param array $delegated [ $class_name => $instantiator ] * * @throws \WPML\Auryn\ConfigException */ function delegate( array $delegated ) { Container::delegate( $delegated ); } } if ( ! function_exists( 'WPML\Container\execute' ) ) { /** * Curried function * * Invoke the specified callable or class::method string, provisioning dependencies along the way * * @param mixed $callableOrMethodStr A valid PHP callable or a provisionable ClassName::methodName string * @param array $args array specifying params with which to invoke the provisioned callable * * @return mixed Returns the invocation result returned from calling the generated executable * @throws \WPML\Auryn\InjectionException */ function execute( $callableOrMethodStr = null, $args = null ) { return call_user_func_array( curryN( 1, [ Container::class, 'execute' ] ), func_get_args() ); } } container/class-config.php 0000755 00000004112 14720342453 0011614 0 ustar 00 <?php namespace WPML\Container; use WPML\TM\ATE\AutoTranslate\Endpoint\GetJobsCount; class Config { public static function getSharedInstances() { global $wpdb; return [ $wpdb, ]; } public static function getSharedClasses() { return [ '\SitePress', '\WPML\WP\OptionManager', '\WP_Http', '\WPML_WP_User_Query_Factory', '\WPML_WP_User_Factory', '\WPML_Notices', \WPML_Locale::class, \WPML_URL_Filters::class, ]; } public static function getAliases() { global $wpdb; $aliases = []; $wpdb_class = get_class( $wpdb ); if ( 'wpdb' !== $wpdb_class ) { $aliases['wpdb'] = $wpdb_class; } return $aliases; } public static function getDelegated() { return [ '\WPML_Notices' => 'wpml_get_admin_notices', \WPML_REST_Request_Analyze::class => [ \WPML_REST_Request_Analyze_Factory::class, 'create' ], \WP_Filesystem_Direct::class => 'wpml_get_filesystem_direct', \WPML_Locale::class => [ \WPML_Locale::class, 'get_instance_from_sitepress' ], \WPML_Post_Translation::class => [ \WPML_Post_Translation::class, 'getGlobalInstance' ], \WPML_Term_Translation::class => [ \WPML_Term_Translation::class, 'getGlobalInstance' ], \WPML_URL_Converter::class => [ \WPML_URL_Converter::class, 'getGlobalInstance' ], \WPML_Post_Status::class => 'wpml_get_post_status_helper', '\WPML_Language_Resolution' => function () { global $wpml_language_resolution; return $wpml_language_resolution; }, \TranslationManagement::class => 'wpml_load_core_tm', \WPML\User\UsersByCapsRepository::class => function () { global $wpdb; $languagePairs = new \WPML_Language_Pair_Records( $wpdb, new \WPML_Language_Records( $wpdb ) ); return new \WPML\User\UsersByCapsRepository( $wpdb, $languagePairs ); }, GetJobsCount::class => function() { return new GetJobsCount( new \WPML\TM\ATE\AutoTranslate\Repository\CachedJobsCount( new \WPML\TM\ATE\AutoTranslate\Repository\JobsCount( new \WPML\TM\ATE\Jobs() ) ) ); }, ]; } } container/class-config-tm.php 0000755 00000002434 14720342453 0012237 0 ustar 00 <?php namespace WPML\TM\Container; use WPML\TM\ATE\ClonedSites\ApiCommunication; use WPML\TM\ATE\ClonedSites\FingerprintGeneratorForOriginalSite; use WPML\TM\ATE\ClonedSites\Lock; use WPML\TM\ATE\Log\Storage; class Config { public static function getDelegated() { return [ '\WPML_Translation_Job_Factory' => 'wpml_tm_load_job_factory', \WPML_TM_ATE_Job_Repository::class => 'wpml_tm_get_ate_jobs_repository', \WPML_TM_Email_Notification_View::class => function () { $factory = new \WPML_TM_Email_Twig_Template_Factory(); return new \WPML_TM_Email_Notification_View( $factory->create() ); }, ]; } public static function getSharedClasses() { return [ '\WPML_TM_AMS_API', '\WPML_TM_ATE_API', '\WPML_TM_ATE_AMS_Endpoints', '\WPML_TM_ATE_Authentication', '\WPML_TM_AMS_ATE_Console_Section', '\WPML_TM_Admin_Sections', '\WPML_Translator_Records', '\WPML_Translator_Admin_Records', '\WPML_Translation_Manager_Records', '\WPML_TM_MCS_ATE_Strings', '\WPML_TM_AMS_Users', '\WPML_TM_AMS_Translator_Activation_Records', '\WPML_TM_REST_AMS_Clients', '\WPML_TM_AMS_Check_Website_ID', '\WPML_Translation_Job_Factory', \WPML_TM_Translation_Status::class, Storage::class, ApiCommunication::class, Lock::class, ]; } } container/class-wpml-container.php 0000755 00000005722 14720342453 0013316 0 ustar 00 <?php namespace WPML\Container; use WPML\Auryn\Injector as AurynInjector; class Container { /** @var Container $instance */ private static $instance = null; /** @var AurynInjector|null */ private $injector = null; private function __construct() { $this->injector = new AurynInjector(); } /** * @return Container */ public static function get_instance() { if ( ! self::$instance ) { self::$instance = new Container(); } return self::$instance; } /** * class names or instances that should be shared. * Shared means that only one instance is ever created when calling the make function. * * @param array $names_or_instances * * @throws \WPML\Auryn\ConfigException */ public static function share( array $names_or_instances ) { $injector = self::get_instance()->injector; wpml_collect( $names_or_instances )->each( function ( $name_or_instance ) use ( $injector ) { $injector->share( $name_or_instance ); } ); } /** * This allows to define aliases classes to be used in place of type hints. * e.g. [ * // generic => specific * 'wpdb' => 'QM_DB', * ] * * @param array $aliases * * @throws \WPML\Auryn\ConfigException */ public static function alias( array $aliases ) { $injector = self::get_instance()->injector; wpml_collect( $aliases )->each( function ( $alias, $original ) use ( $injector ) { $injector->alias( $original, $alias ); } ); } /** * This allows to delegate the object instantiation to a factory. * It can be any kind of callable (class or function). * * @param array $delegated [ $class_name => $instantiator ] * * @throws \WPML\Auryn\ConfigException */ public static function delegate( array $delegated ) { $injector = self::get_instance()->injector; wpml_collect( $delegated )->each( function ( $instantiator, $class_name ) use ( $injector ) { $injector->delegate( $class_name, $instantiator ); } ); } /** * Make returns a new instance otherwise returns a shared instance if the * class_name or an instance is set as shared using the share function * * @param string $class_name * @param array $args * * @return mixed * @throws \WPML\Auryn\InjectionException */ public static function make( $class_name, array $args = array() ) { return self::get_instance()->injector->make( $class_name, $args ); } /** * Invoke the specified callable or class::method string, provisioning dependencies along the way * * @param mixed $callableOrMethodStr A valid PHP callable or a provisionable ClassName::methodName string * @param array $args Optional array specifying params with which to invoke the provisioned callable * @throws \WPML\Auryn\InjectionException * @return mixed Returns the invocation result returned from calling the generated executable */ public static function execute( $callableOrMethodStr, array $args = [] ) { return self::get_instance()->injector->execute( $callableOrMethodStr, $args ); } } class-wpml-tm-ajax-factory.php 0000755 00000001706 14720342453 0012356 0 ustar 00 <?php abstract class WPML_TM_AJAX_Factory_Obsolete { protected $ajax_actions; /** * @var WPML_WP_API */ protected $wpml_wp_api; public function __construct( &$wpml_wp_api ) { $this->wpml_wp_api = &$wpml_wp_api; } protected function init() { $this->add_ajax_actions(); } protected function add_ajax_action( $handle, $callback ) { $this->ajax_actions[ $handle ] = $callback; } private function add_ajax_actions() { if ( ! $this->wpml_wp_api->is_cron_job() ) { foreach ( $this->ajax_actions as $handle => $callback ) { if ( $this->wpml_wp_api->is_ajax() ) { if ( stripos( $handle, 'wp_ajax_' ) !== 0 ) { $handle = 'wp_ajax_' . $handle; } add_action( $handle, $callback ); } if ( $this->wpml_wp_api->is_back_end() && $this->ajax_actions ) { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_resources' ) ); } } } } abstract public function enqueue_resources( $hook_suffix ); } xml-config/class-wpml-config-display-as-translated.php 0000755 00000002364 14720342453 0017064 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 24/10/17 * Time: 11:02 AM */ use WPML\FP\Obj; class WPML_Config_Display_As_Translated { /** * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4859 * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4941 * * @param array $config * * @return array */ public static function merge_to_translate_mode( $config ) { $config = self::merge_to_translate_mode_for_key( $config, 'custom-types', 'custom-type' ); $config = self::merge_to_translate_mode_for_key( $config, 'taxonomies', 'taxonomy' ); return $config; } private static function merge_to_translate_mode_for_key( $config, $key_plural, $key_singular ) { foreach ( Obj::pathOr( [], [ 'wpml-config', $key_plural, $key_singular ], $config ) as $index => $settings ) { if ( WPML_CONTENT_TYPE_TRANSLATE == Obj::path( [ 'attr', 'translate' ], $settings ) && 1 == Obj::path( [ 'attr', 'display-as-translated' ], $settings ) ) { $settings['attr']['translate'] = WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED; unset( $settings['attr']['display-as-translated'] ); $config['wpml-config'][ $key_plural ][ $key_singular ][ $index ] = $settings; } } return $config; } } xml-config/class-wpml-custom-xml.php 0000755 00000000315 14720342453 0013516 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_XML extends WPML_WP_Option { public function get_key() { return 'wpml-tm-custom-xml'; } public function get_default() { return ''; } } xml-config/class-wpml-config-update-log.php 0000755 00000003672 14720342453 0014723 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Config_Update_Log implements WPML_Log { const OPTION_NAME = 'wpml-xml-config-update-log'; public function get( $page_size = 0, $page = 0 ) { $data = get_option( self::OPTION_NAME ); if ( ! $data ) { $data = array(); } return $this->paginate( $data, $page_size, $page ); } /** * @param string|int|float $timestamp * @param array $entry */ public function insert( $timestamp, array $entry ) { if ( $entry && is_array( $entry ) ) { $log = $this->get(); if ( ! $log ) { $log = array(); } $log[ (string) $timestamp ] = $entry; $this->save( $log ); } } public function clear() { $this->save( array() ); } public function save( array $data ) { if ( $data === array() ) { delete_option( self::OPTION_NAME ); return; } update_option( self::OPTION_NAME, $data, false ); } public function is_empty() { return ! $this->get(); } /** * @param array $data * @param int $page_size * @param int $page * * @return array */ protected function paginate( array $data, $page_size, $page ) { if ( (int) $page_size > 0 ) { $total = count( $data ); // total items in array $limit = $page_size; // per page $totalPages = ceil( $total / $limit ); // calculate total pages $page = max( $page, 1 ); // get 1 page when$page <= 0 $page = min( $page, $totalPages ); // get last page when$page > $totalPages $offset = ( $page - 1 ) * $limit; if ( $offset < 0 ) { $offset = 0; } $data = array_slice( $data, (int) $offset, $limit ); } return $data; } /** * @return string */ public function get_log_url() { return add_query_arg( array( 'page' => self::get_support_page_log_section() ), get_admin_url( null, 'admin.php#xml-config-log' ) ); } /** @return string */ public static function get_support_page_log_section() { return WPML_PLUGIN_FOLDER . '/menu/support.php'; } } xml-config/class-wpml-config.php 0000755 00000036760 14720342453 0012670 0 ustar 00 <?php use WPML\Settings\PostType\Automatic; class WPML_Config { const PATH_TO_XSD = WPML_PLUGIN_PATH . '/res/xsd/wpml-config.xsd'; static $wpml_config_files = array(); static $active_plugins = array(); static function load_config() { global $pagenow, $sitepress; if ( ! is_admin() || wpml_is_ajax() || ( isset( $_POST['action'] ) && $_POST['action'] === 'heartbeat' ) || ! $sitepress || ! $sitepress->get_default_language() ) { return; } $white_list_pages = array( 'theme_options', 'plugins.php', 'themes.php', WPML_PLUGIN_FOLDER . '/menu/languages.php', WPML_PLUGIN_FOLDER . '/menu/theme-localization.php', WPML_PLUGIN_FOLDER . '/menu/translation-options.php', ); if ( defined( 'WPML_ST_FOLDER' ) ) { $white_list_pages[] = WPML_ST_FOLDER . '/menu/string-translation.php'; } $white_list_pages = apply_filters( 'wpml_config_white_list_pages', $white_list_pages ); // Runs the load config process only on specific pages $current_page = isset( $_GET['page'] ) ? $_GET['page'] : null; if ( ( isset( $current_page ) && in_array( $current_page, $white_list_pages ) ) || ( isset( $pagenow ) && in_array( $pagenow, $white_list_pages ) ) ) { self::load_config_run(); } } static function load_config_run() { global $sitepress; self::load_config_pre_process(); self::load_plugins_wpml_config(); self::load_theme_wpml_config(); self::parse_wpml_config_files(); self::load_config_post_process(); $sitepress->save_settings(); } static function get_custom_fields_translation_settings( $translation_actions = array( 0 ) ) { $iclTranslationManagement = wpml_load_core_tm(); $section = 'custom_fields_translation'; $result = array(); $tm_settings = $iclTranslationManagement->settings; if ( isset( $tm_settings[ $section ] ) ) { foreach ( $tm_settings[ $section ] as $meta_key => $translation_type ) { if ( in_array( $translation_type, $translation_actions ) ) { $result[] = $meta_key; } } } return $result; } static function parse_wpml_config_post_process( $config ) { self::parse_custom_fields( $config ); self::parseTaxonomies( $config ); self::parsePostTypes( $config ); do_action( 'wpml_reset_ls_settings', $config['wpml-config']['language-switcher-settings'] ); return $config; } static function parseTaxonomies( $config ) { self::parseTMSetting( 'taxonomy', 'taxonomies', $config ); } static function parsePostTypes( $config ) { self::parseTMSetting( 'custom-type', 'custom-types', $config ); Automatic::saveFromConfig( $config ); } static function parseTMSetting( $singular, $plural, $config ) { global $sitepress, $iclTranslationManagement; $tm_settings = new WPML_TM_Settings_Update( $singular, $plural, $iclTranslationManagement, $sitepress, wpml_load_settings_helper() ); $tm_settings->update_from_config( $config['wpml-config'] ); } static function load_config_post_process() { global $iclTranslationManagement; $post_process = new WPML_TM_Settings_Post_Process( $iclTranslationManagement ); $post_process->run(); } static function load_config_pre_process() { global $iclTranslationManagement; $tm_settings = $iclTranslationManagement->settings; if ( ( isset( $tm_settings['custom_types_readonly_config'] ) && is_array( $tm_settings['custom_types_readonly_config'] ) ) ) { $iclTranslationManagement->settings['__custom_types_readonly_config_prev'] = $tm_settings['custom_types_readonly_config']; } else { $iclTranslationManagement->settings['__custom_types_readonly_config_prev'] = array(); } $iclTranslationManagement->settings['custom_types_readonly_config'] = array(); if ( ( isset( $tm_settings['custom_fields_readonly_config'] ) && is_array( $tm_settings['custom_fields_readonly_config'] ) ) ) { $iclTranslationManagement->settings['__custom_fields_readonly_config_prev'] = $tm_settings['custom_fields_readonly_config']; } else { $iclTranslationManagement->settings['__custom_fields_readonly_config_prev'] = array(); } $iclTranslationManagement->settings['custom_fields_readonly_config'] = array(); if ( ( isset( $tm_settings['custom_term_fields_readonly_config'] ) && is_array( $tm_settings['custom_term_fields_readonly_config'] ) ) ) { $iclTranslationManagement->settings['__custom_term_fields_readonly_config_prev'] = $tm_settings['custom_term_fields_readonly_config']; } else { $iclTranslationManagement->settings['__custom_term_fields_readonly_config_prev'] = array(); } $iclTranslationManagement->settings['custom_term_fields_readonly_config'] = array(); } static function load_plugins_wpml_config() { if ( is_multisite() ) { // Get multi site plugins $plugins = get_site_option( 'active_sitewide_plugins' ); if ( ! empty( $plugins ) ) { foreach ( $plugins as $p => $dummy ) { if ( ! self::check_on_config_file( $p ) ) { continue; } $plugin_slug = dirname( $p ); $config_file = WPML_PLUGINS_DIR . '/' . $plugin_slug . '/wpml-config.xml'; if ( trim( $plugin_slug, '\/.' ) && file_exists( $config_file ) ) { self::$wpml_config_files[] = $config_file; } } } } // Get single site or current blog active plugins $plugins = get_option( 'active_plugins' ); if ( ! empty( $plugins ) ) { foreach ( $plugins as $p ) { if ( ! self::check_on_config_file( $p ) ) { continue; } $plugin_slug = dirname( $p ); $config_file = WPML_PLUGINS_DIR . '/' . $plugin_slug . '/wpml-config.xml'; if ( trim( $plugin_slug, '\/.' ) && file_exists( $config_file ) ) { self::$wpml_config_files[] = $config_file; } } } // Get the must-use plugins $mu_plugins = wp_get_mu_plugins(); if ( ! empty( $mu_plugins ) ) { foreach ( $mu_plugins as $mup ) { if ( ! self::check_on_config_file( $mup ) ) { continue; } $plugin_dir_name = dirname( $mup ); $plugin_base_name = basename( $mup, '.php' ); $plugin_sub_dir = $plugin_dir_name . '/' . $plugin_base_name; if ( file_exists( $plugin_sub_dir . '/wpml-config.xml' ) ) { $config_file = $plugin_sub_dir . '/wpml-config.xml'; self::$wpml_config_files[] = $config_file; } } } return self::$wpml_config_files; } static function check_on_config_file( $name ) { if ( empty( self::$active_plugins ) ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } self::$active_plugins = get_plugins(); } $config_index_file_data = maybe_unserialize( get_option( 'wpml_config_index' ) ); $config_files_arr = maybe_unserialize( get_option( 'wpml_config_files_arr' ) ); if ( ! $config_index_file_data || ! $config_files_arr ) { return true; } if ( isset( self::$active_plugins[ $name ] ) ) { $plugin_info = self::$active_plugins[ $name ]; $plugin_slug = dirname( $name ); $name = $plugin_info['Name']; $config_data = $config_index_file_data->plugins; $config_files_arr = $config_files_arr->plugins; $config_file = WPML_PLUGINS_DIR . '/' . $plugin_slug . '/wpml-config.xml'; $type = 'plugin'; } else { $config_data = $config_index_file_data->themes; $config_files_arr = $config_files_arr->themes; $config_file = get_template_directory() . '/wpml-config.xml'; $type = 'theme'; } foreach ( $config_data as $item ) { if ( $name == $item->name && isset( $config_files_arr[ $item->name ] ) ) { if ( $item->override_local || ! file_exists( $config_file ) ) { end( self::$wpml_config_files ); $key = key( self::$wpml_config_files ) + 1; self::$wpml_config_files[ $key ] = new stdClass(); self::$wpml_config_files[ $key ]->config = icl_xml2array( $config_files_arr[ $item->name ] ); self::$wpml_config_files[ $key ]->type = $type; self::$wpml_config_files[ $key ]->admin_text_context = basename( dirname( $config_file ) ); return false; } else { return true; } } } return true; } static function load_theme_wpml_config() { $theme_data = wp_get_theme(); if ( ! self::check_on_config_file( $theme_data->get( 'Name' ) ) ) { return self::$wpml_config_files; } $parent_theme = $theme_data->parent_theme; if ( $parent_theme && ! self::check_on_config_file( $parent_theme ) ) { return self::$wpml_config_files; } if ( get_template_directory() != get_stylesheet_directory() ) { $config_file = get_stylesheet_directory() . '/wpml-config.xml'; if ( file_exists( $config_file ) ) { self::$wpml_config_files[] = $config_file; } } $config_file = get_template_directory() . '/wpml-config.xml'; if ( file_exists( $config_file ) ) { self::$wpml_config_files[] = $config_file; } return self::$wpml_config_files; } static function get_theme_wpml_config_file() { if ( get_template_directory() != get_stylesheet_directory() ) { $config_file = get_stylesheet_directory() . '/wpml-config.xml'; if ( file_exists( $config_file ) ) { return $config_file; } } $config_file = get_template_directory() . '/wpml-config.xml'; if ( file_exists( $config_file ) ) { return $config_file; } return false; } static function parse_wpml_config_files() { $config_all['wpml-config'] = array( 'custom-fields' => array(), 'custom-fields-texts' => array(), 'custom-term-fields' => array(), 'custom-types' => array(), 'taxonomies' => array(), 'admin-texts' => array(), 'language-switcher-settings' => array(), 'shortcodes' => array(), 'shortcode-list' => array(), 'gutenberg-blocks' => array(), 'built-with-page-builder' => array(), ); $config_all_updated = false; $validate = new WPML_XML_Config_Validate(); // Validate with no XSD file (see wpmlcore-8444). $transform = new WPML_XML2Array(); if ( ! empty( self::$wpml_config_files ) ) { foreach ( self::$wpml_config_files as $file ) { if ( is_object( $file ) ) { $config = $file->config; } else { $xml_config_file = new WPML_XML_Config_Read_File( $file, $validate, $transform ); $config = $xml_config_file->get(); } do_action( 'wpml_parse_config_file', $file ); $config_all = self::merge_with( $config_all, $config ); $config_all_updated = true; } } $config_all = self::append_custom_xml_config( $config_all, $config_all_updated ); if ( $config_all_updated ) { $config_all = apply_filters( 'icl_wpml_config_array', $config_all ); $config_all = apply_filters( 'wpml_config_array', $config_all ); } $config_all = WPML_Config_Display_As_Translated::merge_to_translate_mode( $config_all ); self::parse_wpml_config_post_process( $config_all ); } /** * @param array<string,array<string,mixed>> $config_files * @param bool|null $updated * * @return array */ private static function append_custom_xml_config( $config_files, &$updated = null ) { $validate = new WPML_XML_Config_Validate( self::PATH_TO_XSD ); $transform = new WPML_XML2Array(); $custom_config = self::get_custom_xml_config( $validate, $transform ); if ( $custom_config ) { $config_files = self::merge_with( $config_files, $custom_config ); $updated = true; } return $config_files; } /** * @param \WPML_XML_Config_Validate $validate * @param \WPML_XML_Transform $transform * * @return mixed */ private static function get_custom_xml_config( $validate, $transform ) { if ( class_exists( 'WPML_Custom_XML' ) ) { $custom_xml_option = new WPML_Custom_XML(); $custom_xml_config = new WPML_XML_Config_Read_Option( $custom_xml_option, $validate, $transform ); $custom_config = $custom_xml_config->get(); if ( $custom_config ) { $config_object = (object) array( 'config' => $custom_config, 'type' => 'wpml-custom-xml', 'admin_text_context' => 'wpml-custom-xml', ); do_action( 'wpml_parse_custom_config', $config_object ); return $custom_config; } } return null; } /** * @param array<string,array<string,mixed>> $all_configs * @param array<string,array<string,mixed>> $config * * @return mixed */ private static function merge_with( $all_configs, $config ) { if ( isset( $config['wpml-config'] ) ) { $wpml_config = $config['wpml-config']; $wpml_config_all = $all_configs['wpml-config']; $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'custom-field', 'custom-fields' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'custom-term-field', 'custom-term-fields' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'custom-type', 'custom-types' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'taxonomy', 'taxonomies' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'shortcode', 'shortcodes' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'gutenberg-block', 'gutenberg-blocks' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'key', 'custom-fields-texts' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'widget', 'elementor-widgets' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'widget', 'beaver-builder-widgets' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'widget', 'cornerstone-widgets' ); $wpml_config_all = self::parse_config_index( $wpml_config_all, $wpml_config, 'widget', 'siteorigin-widgets' ); // language-switcher-settings if ( isset( $wpml_config['language-switcher-settings']['key'] ) ) { if ( ! is_numeric( key( $wpml_config['language-switcher-settings']['key'] ) ) ) { // single $wpml_config_all['language-switcher-settings']['key'][] = $wpml_config['language-switcher-settings']['key']; } else { foreach ( $wpml_config['language-switcher-settings']['key'] as $cf ) { $wpml_config_all['language-switcher-settings']['key'][] = $cf; } } } if ( isset( $wpml_config['shortcode-list']['value'] ) ) { $wpml_config_all['shortcode-list'] = array_merge( $wpml_config_all['shortcode-list'], explode( ',', $wpml_config['shortcode-list']['value'] ) ); } if ( isset( $wpml_config['built-with-page-builder']['value'] ) ) { $wpml_config_all['built-with-page-builder'] = $wpml_config['built-with-page-builder']['value']; } $all_configs['wpml-config'] = $wpml_config_all; } return $all_configs; } /** * @param array<string,array<string,mixed>> $config */ protected static function parse_custom_fields( $config ) { /** @var TranslationManagement $iclTranslationManagement */ global $iclTranslationManagement; $setting_factory = $iclTranslationManagement->settings_factory(); $import = new WPML_Custom_Field_XML_Settings_Import( $setting_factory, $config['wpml-config'] ); $import->run(); } private static function parse_config_index( $config_all, $wpml_config, $index_sing, $index_plur ) { if ( isset( $wpml_config[ $index_plur ][ $index_sing ] ) ) { if ( isset( $wpml_config[ $index_plur ][ $index_sing ]['value'] ) ) { // single $config_all[ $index_plur ][ $index_sing ][] = $wpml_config[ $index_plur ][ $index_sing ]; } else { foreach ( (array) $wpml_config[ $index_plur ][ $index_sing ] as $cf ) { $config_all[ $index_plur ][ $index_sing ][] = $cf; } } } return $config_all; } } xml-config/class-wpml-xml-config-validate.php 0000755 00000002430 14720342453 0015240 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XML_Config_Validate { /** * @var \LibXMLError[] */ private $errors = []; private $path_to_xsd; function __construct( $path_to_xsd = null ) { $this->path_to_xsd = $path_to_xsd ? realpath( $path_to_xsd ) : null; } /** * @return \LibXMLError[] */ public function get_errors() { return $this->errors; } /** * @param string $file_full_path * * @return bool */ function from_file( $file_full_path ) { $this->errors = array(); $xml = file_get_contents( $file_full_path ); return $xml ? $this->from_string( $xml ) : false; } /** * @param string $xml * * @return bool */ function from_string( $xml ) { if ( '' === preg_replace( '/(\W)+/', '', $xml ) ) { return false; } $this->errors = array(); libxml_use_internal_errors( true ); $xml_object = $this->get_xml( $xml ); if ( $this->path_to_xsd && ! $xml_object->schemaValidate( $this->path_to_xsd ) ) { $this->errors = libxml_get_errors(); } libxml_clear_errors(); return count($this->errors) === 0; } /** * @param string $content The string representation of the XML file * * @return DOMDocument */ private function get_xml( $content ) { $xml = new DOMDocument(); $xml->loadXML( $content ); return $xml; } } xml-config/class-wpml-config-built-with-page-builders.php 0000755 00000001736 14720342453 0017472 0 ustar 00 <?php class WPML_Config_Built_With_Page_Builders extends WPML_WP_Option implements IWPML_Action, IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { const CONFIG_KEY = 'built-with-page-builder'; public function create() { return $this; // Use same instance for action } public function get_key() { return 'wpml_built_with_page_builder'; } public function get_default() { return array(); } public function add_hooks() { add_filter( 'wpml_config_array', array( $this, 'wpml_config_filter' ) ); } /** * @param array $config_data * * @return array */ public function wpml_config_filter( $config_data ) { if ( isset( $config_data['wpml-config'][ self::CONFIG_KEY ] ) && $config_data['wpml-config'][ self::CONFIG_KEY ] ) { $data_saved = $this->get(); $data_saved = $data_saved ? $data_saved : array(); $data_saved[] = $config_data['wpml-config'][ self::CONFIG_KEY ]; $this->set( array_unique( $data_saved ) ); } return $config_data; } } xml-config/read/class-wpml-xml-config-read-option.php 0000755 00000001441 14720342453 0016604 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XML_Config_Read_Option implements WPML_XML_Config_Read { private $option; private $transform; private $validate; /** * WPML_XML_Config_Read_Option constructor. * * @param \WPML_WP_Option $option * @param \WPML_XML_Config_Validate $validate * @param \WPML_XML_Transform $transform */ function __construct( WPML_WP_Option $option, WPML_XML_Config_Validate $validate, WPML_XML_Transform $transform ) { $this->option = $option; $this->validate = $validate; $this->transform = $transform; } function get() { if ( $this->option->get() ) { $content = $this->option->get(); if ( $this->validate->from_string( $content ) ) { return $this->transform->get( $content ); } } return null; } } xml-config/read/class-wpml-xml-config-read-file.php 0000755 00000001212 14720342453 0016207 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XML_Config_Read_File implements WPML_XML_Config_Read { private $file_full_path; private $transform; private $validate; function __construct( $file_full_path, WPML_XML_Config_Validate $validate, WPML_XML_Transform $transform ) { $this->file_full_path = $file_full_path; $this->validate = $validate; $this->transform = $transform; } function get() { if ( file_exists( $this->file_full_path ) && $this->validate->from_file( $this->file_full_path ) ) { $xml = file_get_contents( $this->file_full_path ); return $this->transform->get( $xml ); } return null; } } xml-config/read/interface-wpml-xml-config-read.php 0000755 00000000115 14720342453 0016126 0 ustar 00 <?php /** * @author OnTheGo Systems */ interface WPML_XML_Config_Read { } xml-config/class-wpml-config-shortcode-list.php 0000755 00000001662 14720342453 0015622 0 ustar 00 <?php class WPML_Config_Shortcode_List extends WPML_WP_Option implements IWPML_Action, IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { public function create() { return $this; // Use same instance for action } public function get_key() { return 'wpml_shortcode_list'; } public function get_default() { return array(); } public function add_hooks() { add_filter( 'wpml_config_array', array( $this, 'wpml_config_filter' ) ); add_filter( 'wpml_shortcode_list', array( $this, 'filter_shortcode_list' ) ); } public function wpml_config_filter( $config_data ) { $shortcode_data = array(); if ( isset( $config_data['wpml-config']['shortcode-list'] ) ) { $shortcode_data = $config_data['wpml-config']['shortcode-list']; } $this->set( $shortcode_data ); return $config_data; } public function filter_shortcode_list( $shortcodes ) { return array_unique( array_merge( $shortcodes, $this->get() ) ); } } xml-config/transform/class-wpml-xml2array.php 0000755 00000005736 14720342453 0015356 0 ustar 00 <?php class WPML_XML2Array implements WPML_XML_Transform { private $contents; private $get_attributes; public function get( $contents, $get_attributes = true ) { $this->contents = $contents; $this->get_attributes = (bool) $get_attributes; $xml_values = array(); if ( $this->contents && function_exists( 'xml_parser_create' ) ) { $parser = xml_parser_create(); xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 ); xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 ); xml_parse_into_struct( $parser, $this->contents, $xml_values ); xml_parser_free( $parser ); } // Initializations $xml_array = array(); $current = &$xml_array; // Go through the tags. foreach ( $xml_values as $data ) { unset( $attributes, $value );// Remove existing values, or there will be trouble $tag = $data['tag']; $type = $data['type']; $level = $data['level']; $value = isset( $data['value'] ) ? $data['value'] : ''; $attributes = isset( $data['attributes'] ) ? $data['attributes'] : array(); $item = $this->get_item( $value, $attributes ); // See tag status and do the needed. if ( 'open' === $type ) {// The starting of the tag '<tag>' $parent[ $level - 1 ] = &$current; if ( ! is_array( $current ) || ( ! isset( $current[ $tag ] ) ) ) { // Insert New tag $current[ $tag ] = $item; $current = &$current[ $tag ]; } else { // There was another element with the same tag name if ( isset( $current[ $tag ][0] ) ) { $current[ $tag ][] = $item; } else { $current[ $tag ] = array( $current[ $tag ], $item ); } $last = count( $current[ $tag ] ) - 1; $current = &$current[ $tag ][ $last ]; } } elseif ( 'complete' === $type ) { // Tags that ends in 1 line '<tag />' // See if the key is already taken. if ( ! isset( $current[ $tag ] ) ) { // New Key $current[ $tag ] = $item; } else { // If taken, put all things inside a list(array) if ( ( is_array( $current[ $tag ] ) && ! $this->get_attributes )// If it is already an array... || ( isset( $current[ $tag ][0] ) && is_array( $current[ $tag ][0] ) && $this->get_attributes ) ) { $current[ $tag ][] = $item; } else { // If it is not an array... $current[ $tag ] = array( $current[ $tag ], $item ); } } } elseif ( 'close' === $type && isset( $parent ) ) { // End of tag '</tag>' $current = &$parent[ $level - 1 ]; } } return $xml_array; } /** * @param mixed|null $value * @param array $attributes * * @return array */ private function get_item( $value, array $attributes ) { $item = array(); if ( $this->get_attributes ) {// The second argument of the function decides this. if ( null !== $value ) { $item['value'] = $value; } if ( null !== $attributes ) { foreach ( $attributes as $attr => $val ) { $item['attr'][ $attr ] = $val; } } } else { $item = $value; } return $item; } } xml-config/transform/interface-wpml-xml-transform.php 0000755 00000000204 14720342453 0017062 0 ustar 00 <?php /** * @author OnTheGo Systems */ interface WPML_XML_Transform { public function get( $source, $get_attributes = true ); } xml-config/log/class-wpml-xml-config-log-ui-factory.php 0000755 00000001175 14720342453 0017076 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XML_Config_Log_Factory { private $log; function create_log() { if ( ! $this->log ) { $this->log = new WPML_Config_Update_Log(); } return $this->log; } function create_ui() { $template_paths = array( WPML_PLUGIN_PATH . '/templates/xml-config/log/', ); $template_loader = new WPML_Twig_Template_Loader( $template_paths ); $template_service = $template_loader->get_template(); return new WPML_XML_Config_Log_UI( $this->create_log(), $template_service ); } function create_notice() { return new WPML_XML_Config_Log_Notice( $this->create_log() ); } } xml-config/log/class-wpml-xml-config-log-ui.php 0000755 00000006103 14720342453 0015425 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_XML_Config_Log_UI { /** @var IWPML_Template_Service */ private $template_service; /** * @var \WPML_Config_Update_Log */ private $log; function __construct( WPML_Config_Update_Log $log, IWPML_Template_Service $template_service ) { $this->log = $log; $this->template_service = $template_service; } /** * @return string */ public function show() { $model = $this->get_model(); return $this->template_service->show( $model, 'main.twig' ); } /** @return array */ private function get_model() { $entries = $this->log->get(); krsort( $entries ); $table_data = array(); $columns = array(); foreach ( $entries as $entry ) { $columns += array_keys( $entry ); } $columns = array_unique( $columns ); foreach ( $entries as $timestamp => $entry ) { $table_row = array( 'timestamp' => $this->getDateTimeFromMicroseconds( $timestamp ) ); foreach ( $columns as $column ) { $table_row[ $column ] = null; if ( array_key_exists( $column, $entry ) ) { $table_row[ $column ] = $entry[ $column ]; } } $table_data[] = $table_row; } array_unshift( $columns, 'timestamp' ); $model = array( 'strings' => array( 'title' => __( 'Remote XML Config Log', 'sitepress' ), 'message' => __( "WPML needs to load configuration files, which tell it how to translate your theme and the plugins that you use. If there's a problem, use the Retry button. If the problem continues, contact WPML support, show the error details and we'll help you resolve it.", 'sitepress' ), 'details' => __( 'Details', 'sitepress' ), 'empty_log' => __( 'The remote XML Config Log is empty', 'sitepress' ), ), 'buttons' => array( 'retry' => array( 'label' => __( 'Retry', 'sitepress' ), 'url' => null, 'type' => 'primary', ), 'clear' => array( 'label' => __( 'Clear log', 'sitepress' ), 'url' => null, 'type' => 'secondary', ), ), 'columns' => $columns, 'entries' => $table_data, ); if ( $table_data ) { $clear_log_url = add_query_arg( array( WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-action' => 'wpml_xml_update_clear', WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-nonce' => wp_create_nonce( 'wpml_xml_update_clear' ), ), $this->log->get_log_url() ); $model['buttons']['clear']['url'] = $clear_log_url; $retry_url = add_query_arg( array( WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-action' => 'wpml_xml_update_refresh', WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-nonce' => wp_create_nonce( 'wpml_xml_update_refresh' ), ), $this->log->get_log_url() ); $model['buttons']['retry']['url'] = $retry_url; } return $model; } private function getDateTimeFromMicroseconds( $time ) { $dFormat = 'Y-m-d H:i:s'; if ( strpos( $time, '.' ) === false ) { return $time; } list( $sec, $usec ) = explode( '.', $time ); // split the microtime on . return date( $dFormat, (int) $sec ) . $usec; } } xml-config/log/class-wpml-xml-config-log-notice.php 0000755 00000005263 14720342453 0016277 0 ustar 00 <?php use WPML\API\Sanitize; /** * @author OnTheGo Systems */ class WPML_XML_Config_Log_Notice { const NOTICE_ERROR_GROUP = 'wpml-config-update'; const NOTICE_ERROR_ID = 'wpml-config-update-error'; /** @var WPML_Config_Update_Log */ private $log; public function __construct( WPML_Log $log ) { $this->log = $log; } public function add_hooks() { if ( is_admin() ) { add_action( 'wpml_loaded', array( $this, 'refresh_notices' ) ); } } public function refresh_notices() { $notices = wpml_get_admin_notices(); if ( $this->log->is_empty() ) { $notices->remove_notice( self::NOTICE_ERROR_GROUP, self::NOTICE_ERROR_ID ); return; } $text = '<p>' . esc_html__( 'WPML could not load configuration files, which your site needs.', 'sitepress' ) . '</p>'; $notice = $notices->create_notice( self::NOTICE_ERROR_ID, $text, self::NOTICE_ERROR_GROUP ); $notice->set_css_class_types( array( 'error' ) ); $log_url = add_query_arg( array( 'page' => WPML_Config_Update_Log::get_support_page_log_section() ), get_admin_url( null, 'admin.php#xml-config-log' ) ); $show_logs = $notices->get_new_notice_action( __( 'Detailed error log', 'sitepress' ), $log_url ); $return_url = null; if ( $this->is_admin_user_action() ) { $admin_uri = preg_replace( '#^/wp-admin/#', '', $_SERVER['SCRIPT_NAME'] ); $return_url = get_admin_url( null, $admin_uri ); $return_url_qs = $_GET; unset( $return_url_qs[ self::NOTICE_ERROR_GROUP . '-action' ], $return_url_qs[ self::NOTICE_ERROR_GROUP . '-nonce' ] ); $return_url = add_query_arg( $return_url_qs, $return_url ); } $retry_url = add_query_arg( array( self::NOTICE_ERROR_GROUP . '-action' => 'wpml_xml_update_refresh', self::NOTICE_ERROR_GROUP . '-nonce' => wp_create_nonce( 'wpml_xml_update_refresh' ), ), $return_url ); $retry = $notices->get_new_notice_action( __( 'Retry', 'sitepress' ), $retry_url, false, false, true ); $notice->add_action( $show_logs ); $notice->add_action( $retry ); $notice->set_dismissible( true ); $notice->set_restrict_to_page_prefixes( array( 'sitepress-multilingual-cms', 'wpml-translation-management', 'wpml-package-management', 'wpml-string-translation', ) ); $notice->set_restrict_to_screen_ids( array( 'dashboard', 'plugins', 'themes' ) ); $notice->add_exclude_from_page( WPML_Config_Update_Log::get_support_page_log_section() ); $notices->add_notice( $notice ); } /** * @return bool */ private function is_admin_user_action() { return is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && ( 'heartbeat' !== Sanitize::stringProp( 'action', $_POST ) ) && ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ); } } abstract/class-wpml-tm-job-factory-user.php 0000755 00000000511 14720342453 0014755 0 ustar 00 <?php abstract class WPML_TM_Job_Factory_User { /** @var WPML_Translation_Job_Factory $tm_job_factory */ protected $job_factory; /** * WPML_TM_Xliff_Reader constructor. * * @param WPML_Translation_Job_Factory $job_factory */ public function __construct( $job_factory ) { $this->job_factory = $job_factory; } } abstract/class-wpml-tm-record-user.php 0000755 00000000431 14720342453 0014015 0 ustar 00 <?php class WPML_TM_Record_User { /** @var WPML_TM_Records $tm_records */ protected $tm_records; /** * WPML_TM_Record_User constructor. * * @param WPML_TM_Records $tm_records */ public function __construct( &$tm_records ) { $this->tm_records = &$tm_records; } } language-switcher/class-wpml-ls-template.php 0000755 00000013617 14720342453 0015214 0 ustar 00 <?php class WPML_LS_Template extends WPML_Templates_Factory { const FILENAME = 'template.twig'; /* @var array $template */ private $template; /* @var array $model */ private $model; /* @var string $prefix */ private $prefix = 'wpml-ls-'; /** * WPML_Language_Switcher_Menu constructor. * * @param array $template_data * @param array $template_model */ public function __construct( $template_data, $template_model = array() ) { $this->template = $this->format_data($template_data); $this->template['js'] = self::remove_non_minified_duplicates( $this->template['js'] ); $this->template['css'] = self::remove_non_minified_duplicates( $this->template['css'] ); if ( array_key_exists( 'template_string', $this->template ) ) { $this->template_string = $this->template['template_string']; } $this->model = $template_model; parent::__construct(); } /** * Make sure some elements are of array type * * @param array $template_data * * @return array */ private function format_data( $template_data ) { foreach ( array( 'path', 'js', 'css' ) as $k ) { $template_data[ $k ] = isset( $template_data[ $k ] ) ? $template_data[ $k ] : array(); $template_data[ $k ] = is_array( $template_data[ $k ] ) ? $template_data[ $k ] : array( $template_data[ $k ] ); } return $template_data; } /** * @param array $model */ public function set_model( $model ) { $this->model = is_array( $model ) ? $model : array( $model ); } /** * @return string * @throws \WPML\Core\Twig\Error\LoaderError * @throws \WPML\Core\Twig\Error\RuntimeError * @throws \WPML\Core\Twig\Error\SyntaxError */ public function get_html( $sandbox = false ) { $ret = ''; if ( $this->template_paths || $this->template_string ) { if ( $sandbox ) { $ret = parent::get_sandbox_view( null, null ); } else { $ret = parent::get_view( null, null ); } } return $ret; } /** * @param bool $with_version * * @return array */ public function get_styles( $with_version = false ) { $styles = $with_version ? array_map( array( $this, 'add_resource_version' ), $this->template['css'] ) : $this->template['css']; return array_values( $styles ); } /** * @return bool */ public function has_styles() { return ! empty( $this->template['css'] ); } /** * @param bool $with_version * * @return array */ public function get_scripts( $with_version = false ) { $scripts = $with_version ? array_map( array( $this, 'add_resource_version' ), $this->template['js'] ) : $this->template['js']; return array_values( $scripts ); } /** * @param string $url * * @return string */ private function add_resource_version( $url ) { return $url . '?ver=' . $this->get_version(); } /** * @param int $index * * @return string */ public function get_resource_handler( $index ) { $slug = isset( $this->template['slug'] ) ? $this->template['slug'] : ''; $prefix = $this->is_core() ? '' : $this->prefix; return $prefix . $slug . '-' . $index; } /** * @return mixed|string|bool */ public function get_inline_style_handler() { $count = count( $this->template['css'] ); return $count > 0 ? $this->get_resource_handler( $count - 1 ) : null; } /** * @return string */ public function get_version() { return $this->template['version']; } protected function init_template_base_dir() { $this->template_paths = $this->template['path']; } /** * @return string Template filename */ public function get_template() { $template = self::FILENAME; if ( isset( $this->template_string ) ) { $template = $this->template_string; } elseif ( array_key_exists( 'filename', $this->template ) ) { $template = $this->template['filename']; } return $template; } /** * @return array */ public function get_model() { return $this->model; } /** * @return array */ public function get_template_data() { return $this->template; } /** * @param array $template */ public function set_template_data( $template ) { $this->template = $template; } /** * return bool */ public function is_core() { return isset( $this->template['is_core'] ) ? (bool) $this->template['is_core'] : false; } /** * @return array */ public function supported_slot_types() { return isset( $this->template['for'] ) ? $this->template['for'] : array(); } /** * @return array */ public function force_settings() { return isset( $this->template['force_settings'] ) ? $this->template['force_settings'] : array(); } public function is_path_valid() { $valid = true; $this->template_paths = apply_filters( 'wpml_ls_template_paths', $this->template_paths ); foreach ( $this->template_paths as $path ) { if ( ! file_exists( $path ) ) { $valid = false; break; } } return $valid; } /** * @param string $template_string */ public function set_template_string( $template_string ) { if ( method_exists( $this, 'is_string_template' ) ) { $this->template_string = $template_string; } } /** * If an asset has a minified and a non-minified version, * we remove the non-minified version. * * @param array $assets * * @return array */ public static function remove_non_minified_duplicates( array $assets ) { $hasMinifiedVersion = function( $url ) use ( $assets ) { $extension = pathinfo( $url, PATHINFO_EXTENSION ); $basename = pathinfo( $url, PATHINFO_BASENAME ); $filename = pathinfo( $url, PATHINFO_FILENAME ); $url_start = substr( $url, 0, strlen( $url ) - strlen( $basename ) ); $minified_file = $filename . '.min.' . $extension; if ( in_array( $url_start . $minified_file, $assets, true ) ) { return true; } return false; }; return wpml_collect( $assets ) ->reject( $hasMinifiedVersion ) ->toArray(); } } language-switcher/class-wpml-ls-model-build.php 0000755 00000031763 14720342453 0015600 0 ustar 00 <?php class WPML_LS_Model_Build extends WPML_SP_User { const LINK_CSS_CLASS = 'wpml-ls-link'; /* @var WPML_LS_Settings $settings */ private $settings; /* @var WPML_Mobile_Detect $mobile_detect */ private $mobile_detect; /* @var bool $is_touch_screen */ private $is_touch_screen = false; /* @var string $css_prefix */ private $css_prefix; private $allowed_vars = [ 'languages' => 'array', 'current_language_code' => 'string', 'css_classes' => 'string', 'css_classes_link' => 'string', 'backward_compatibility' => 'array', ]; private $allowed_language_vars = [ 'code' => 'string', 'url' => 'string', 'flag_url' => 'string', 'flag_title' => 'string', 'flag_alt' => 'string', 'native_name' => 'string', 'display_name' => 'string', 'is_current' => 'bool', 'css_classes' => 'string', 'db_id' => 'string', 'menu_item_parent' => 'mixed', 'is_parent' => 'bool', 'backward_compatibility' => 'array', 'flag_width' => 'int', 'flag_height' => 'int', ]; /** * WPML_Language_Switcher_Render_Model constructor. * * @param WPML_LS_Settings $settings * @param SitePress $sitepress * @param string $css_prefix */ public function __construct( $settings, $sitepress, $css_prefix ) { $this->settings = $settings; $this->css_prefix = $css_prefix; parent::__construct( $sitepress ); } /** * @param WPML_LS_Slot $slot * @param array $template_data * * @return array */ public function get( $slot, $template_data = [] ) { $vars = []; $vars['current_language_code'] = $this->sitepress->get_current_language(); $vars['languages'] = $this->get_language_items( $slot, $template_data ); $vars['css_classes'] = $this->get_slot_css_classes( $slot ); $vars['css_classes_link'] = self::LINK_CSS_CLASS; $vars = $this->add_backward_compatibility_to_wrapper( $vars, $slot ); return $this->sanitize_vars( $vars, $this->allowed_vars ); } /** * @param WPML_LS_Slot $slot * * @return string */ public function get_slot_css_classes( $slot ) { $classes = [ $this->get_slot_css_main_class( $slot->group(), $slot->slug() ) ]; $classes[] = trim( $this->css_prefix, '-' ); if ( $this->sitepress->is_rtl( $this->sitepress->get_current_language() ) ) { $classes[] = $this->css_prefix . 'rtl'; } $classes = $this->add_user_agent_touch_device_classes( $classes ); /** * Filter the css classes for the language switcher wrapper * The wrapper is not available for menus * * @param array $classes */ $classes = apply_filters( 'wpml_ls_model_css_classes', $classes ); return implode( ' ', $classes ); } /** * @param string $group * @param string $slug * * @return string */ public function get_slot_css_main_class( $group, $slug ) { return $this->css_prefix . $group . '-' . $slug; } /** * @return string */ public function get_css_prefix() { return $this->css_prefix; } /** * @param WPML_LS_Slot $slot * @param array $template_data * * @return array */ private function get_language_items( $slot, $template_data ) { $ret = []; $get_ls_args = [ 'skip_missing' => ! $this->settings->get_setting( 'link_empty' ), ]; if ( $slot->is_post_translations() ) { $get_ls_args['skip_missing'] = true; } elseif ( ( WPML_Root_Page::uses_html_root() || WPML_Root_Page::get_root_id() ) && WPML_Root_Page::is_current_request_root() ) { $get_ls_args['skip_missing'] = false; } $flag_width = $slot->get( 'include_flag_width' ); $flag_height = $slot->get( 'include_flag_height' ); $languages = $this->sitepress->get_ls_languages( $get_ls_args ); $languages = is_array( $languages ) ? $languages : []; if ( $languages ) { foreach ( $languages as $code => $data ) { $is_current_language = $code === $this->sitepress->get_current_language(); if ( ! $slot->get( 'display_link_for_current_lang' ) && $is_current_language ) { continue; } $ret[ $code ] = [ 'code' => $code, 'url' => $data['url'], ]; if ( $flag_width ) { $ret[ $code ]['flag_width'] = $flag_width; } if ( $flag_height ) { $ret[ $code ]['flag_height'] = $flag_height; } /* @deprecated Use 'wpml_ls_language_url' instead */ $ret[ $code ]['url'] = apply_filters( 'WPML_filter_link', $ret[ $code ]['url'], $data ); /** * This filter allows to change the URL for each languages links in the switcher * * @param string $ret [ $code ]['url'] The language URL to be filtered * @param array $data The language information */ $ret[ $code ]['url'] = apply_filters( 'wpml_ls_language_url', $ret[ $code ]['url'], $data ); $ret[ $code ]['url'] = $this->sitepress->get_wp_api()->is_admin() ? '#' : $ret[ $code ]['url']; $css_classes = $this->get_language_css_classes( $slot, $code ); $display_native = $slot->get( 'display_names_in_native_lang' ); $display_name = $slot->get( 'display_names_in_current_lang' ); if ( $slot->get( 'display_flags' ) ) { $ret[ $code ]['flag_url'] = $this->filter_flag_url( $data['country_flag_url'], $template_data ); $ret[ $code ]['flag_title'] = $data['native_name']; $ret[ $code ]['flag_alt'] = ( $display_name || $display_native ) ? '' : $data['translated_name']; } if ( $display_native ) { $ret[ $code ]['native_name'] = $data['native_name']; } if ( $display_name ) { $ret[ $code ]['display_name'] = $data['translated_name']; } if ( $is_current_language ) { $ret[ $code ]['is_current'] = true; array_push( $css_classes, $this->css_prefix . 'current-language' ); } if ( $slot->is_menu() ) { $ret[ $code ]['db_id'] = $this->get_menu_item_id( $code, $slot ); $ret[ $code ]['menu_item_parent'] = $slot->get( 'is_hierarchical' ) && ! $is_current_language && $slot->get( 'display_link_for_current_lang' ) ? $this->get_menu_item_id( $this->sitepress->get_current_language(), $slot ) : 0; $ret[ $code ]['is_parent'] = $slot->get( 'is_hierarchical' ) && $is_current_language; array_unshift( $css_classes, 'menu-item' ); array_push( $css_classes, $this->css_prefix . 'menu-item' ); } $ret[ $code ]['css_classes'] = $css_classes; } $i = 1; foreach ( $ret as &$lang ) { if ( $i === 1 ) { array_push( $lang['css_classes'], $this->css_prefix . 'first-item' ); } if ( $i === count( $ret ) ) { array_push( $lang['css_classes'], $this->css_prefix . 'last-item' ); } $lang = $this->add_backward_compatibility_to_languages( $lang, $slot ); /** * Filter the css classes for each language item * * @param array $lang ['css_classes'] */ $lang['css_classes'] = apply_filters( 'wpml_ls_model_language_css_classes', $lang['css_classes'] ); $lang['css_classes'] = implode( ' ', $lang['css_classes'] ); $lang = $this->sanitize_vars( $lang, $this->allowed_language_vars ); $i++; } } return $ret; } /** * @param string $url * @param array $template_data * * @return string */ private function filter_flag_url( $url, $template_data = [] ) { $wp_upload_dir = wp_upload_dir(); $has_custom_flag = strpos( $url, $wp_upload_dir['baseurl'] . '/flags/' ) === 0 ? true : false; if ( ! $has_custom_flag && ! empty( $template_data['flags_base_uri'] ) ) { $url = trailingslashit( $template_data['flags_base_uri'] ) . basename( $url ); if ( isset( $template_data['flag_extension'] ) ) { $old_ext = pathinfo( $url, PATHINFO_EXTENSION ); $url = preg_replace( '#' . $old_ext . '$#', $template_data['flag_extension'], $url, 1 ); } } return $url; } /** * @param WPML_LS_Slot $slot * @param string $code * * @return array */ private function get_language_css_classes( $slot, $code ) { return [ $this->css_prefix . 'slot-' . $slot->slug(), $this->css_prefix . 'item', $this->css_prefix . 'item-' . $code, ]; } /** * @param array $classes * * @return array */ private function add_user_agent_touch_device_classes( $classes ) { if ( is_null( $this->mobile_detect ) ) { require_once WPML_PLUGIN_PATH . '/lib/mobile-detect.php'; $this->mobile_detect = new WPML_Mobile_Detect(); $this->is_touch_screen = $this->mobile_detect->isMobile() || $this->mobile_detect->isTablet(); } if ( $this->is_touch_screen ) { $classes[] = $this->css_prefix . 'touch-device'; } return $classes; } /** * @return bool */ private function needs_backward_compatibility() { return (bool) $this->settings->get_setting( 'migrated' ); } /** * @param string $code * @param WPML_LS_Slot $slot * * @return string */ private function get_menu_item_id( $code, $slot ) { return $this->css_prefix . $slot->slug() . '-' . $code; } /** * @param array $vars * @param array $allowed_vars * * @return array */ private function sanitize_vars( $vars, $allowed_vars ) { $sanitized = []; foreach ( $allowed_vars as $allowed_var => $type ) { if ( isset( $vars[ $allowed_var ] ) ) { switch ( $type ) { case 'array': $sanitized[ $allowed_var ] = (array) $vars[ $allowed_var ]; break; case 'string': $sanitized[ $allowed_var ] = (string) $vars[ $allowed_var ]; break; case 'int': $sanitized[ $allowed_var ] = (int) $vars[ $allowed_var ]; break; case 'bool': $sanitized[ $allowed_var ] = (bool) $vars[ $allowed_var ]; break; case 'mixed': $sanitized[ $allowed_var ] = $vars[ $allowed_var ]; break; } } } return $sanitized; } /** * @param array $lang * @param WPML_LS_Slot $slot * * @return array */ private function add_backward_compatibility_to_languages( $lang, $slot ) { if ( $this->needs_backward_compatibility() ) { $is_current_language = isset( $lang['is_current'] ) && $lang['is_current']; if ( $slot->is_menu() ) { if ( $is_current_language ) { array_unshift( $lang['css_classes'], 'menu-item-language-current' ); } array_unshift( $lang['css_classes'], 'menu-item-language' ); } if ( $slot->is_sidebar() || $slot->is_shortcode_actions() ) { if ( $this->is_legacy_template( $slot->template(), 'list-vertical' ) || $this->is_legacy_template( $slot->template(), 'list-horizontal' ) ) { array_unshift( $lang['css_classes'], 'icl-' . $lang['code'] ); $lang['backward_compatibility']['css_classes_a'] = $is_current_language ? 'lang_sel_sel' : 'lang_sel_other'; } if ( $this->is_legacy_template( $slot->template(), 'dropdown' ) || $this->is_legacy_template( $slot->template(), 'dropdown-click' ) ) { if ( $is_current_language ) { $lang['backward_compatibility']['css_classes_a'] = 'lang_sel_sel icl-' . $lang['code']; } else { array_unshift( $lang['css_classes'], 'icl-' . $lang['code'] ); } } } } return $lang; } /** * @param array $vars * @param WPML_LS_Slot $slot * * @return mixed */ private function add_backward_compatibility_to_wrapper( $vars, $slot ) { if ( $this->needs_backward_compatibility() ) { if ( $slot->is_sidebar() || $slot->is_shortcode_actions() ) { if ( $this->is_legacy_template( $slot->template(), 'list-vertical' ) || $this->is_legacy_template( $slot->template(), 'list-horizontal' ) ) { $vars['backward_compatibility']['css_id'] = 'lang_sel_list'; if ( $this->is_legacy_template( $slot->template(), 'list-horizontal' ) ) { $vars['css_classes'] = 'lang_sel_list_horizontal ' . $vars['css_classes']; } else { $vars['css_classes'] = 'lang_sel_list_vertical ' . $vars['css_classes']; } } if ( $this->is_legacy_template( $slot->template(), 'dropdown' ) ) { $vars['backward_compatibility']['css_id'] = 'lang_sel'; } if ( $this->is_legacy_template( $slot->template(), 'dropdown-click' ) ) { $vars['backward_compatibility']['css_id'] = 'lang_sel_click'; } } if ( $slot->is_post_translations() ) { $vars['css_classes'] = 'icl_post_in_other_langs ' . $vars['css_classes']; } if ( $slot->is_footer() ) { $vars['backward_compatibility']['css_id'] = 'lang_sel_footer'; } $vars['backward_compatibility']['css_classes_flag'] = 'iclflag'; $vars['backward_compatibility']['css_classes_native'] = 'icl_lang_sel_native'; $vars['backward_compatibility']['css_classes_display'] = 'icl_lang_sel_translated'; $vars['backward_compatibility']['css_classes_bracket'] = 'icl_lang_sel_bracket'; } return $vars; } /** * @param string $template_slug * @param string|null $type * * @return bool */ private function is_legacy_template( $template_slug, $type = null ) { $templates = $this->settings->get_core_templates(); $ret = in_array( $template_slug, $templates, true ); if ( $ret && array_key_exists( $type, $templates ) ) { $ret = $templates[ $type ] === $template_slug; } return $ret; } } language-switcher/class-wpml-ls-display-as-translated-link.php 0000755 00000006317 14720342453 0020540 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 17/10/17 * Time: 10:56 PM */ use WPML\FP\Obj; class WPML_LS_Display_As_Translated_Link { /** @var SitePress $sitepress */ private $sitepress; /** @var IWPML_URL_Converter_Strategy $url_converter */ private $url_converter; /** @var WP_Query $wp_query */ private $wp_query; /** @var WPML_Translation_Element_Factory $element_factory */ private $element_factory; /** @var string $default_language */ private $default_language; /** @var string $processed_language */ private $processed_language; public function __construct( SitePress $sitepress, IWPML_URL_Converter_Strategy $url_converter, WP_Query $wp_query, WPML_Translation_Element_Factory $element_factory ) { $this->sitepress = $sitepress; $this->url_converter = $url_converter; $this->wp_query = $wp_query; $this->element_factory = $element_factory; $this->default_language = $sitepress->get_default_language(); } public function get_url( $translations, $lang ) { $queried_object = $this->wp_query->get_queried_object(); if ( $queried_object instanceof WP_Post ) { return $this->get_post_url( $translations, $lang, $queried_object ); } elseif ( $queried_object instanceof WP_Term ) { return $this->get_term_url( $translations, $lang, $queried_object ); } else { return null; } } private function get_post_url( $translations, $lang, $queried_object ) { $url = null; if ( $this->sitepress->is_display_as_translated_post_type( $queried_object->post_type ) && isset( $translations[ $this->default_language ] ) ) { $this->sitepress->switch_lang( $this->default_language ); $this->processed_language = $lang; $setLanguageCode = Obj::set( Obj::lensProp( 'language_code' ), $lang ); add_filter( 'post_link_category', array( $this, 'adjust_category_in_post_permalink' ) ); add_filter( 'wpml_st_post_type_link_filter_language_details', $setLanguageCode ); $url = get_permalink( $translations[ $this->default_language ]->element_id ); remove_filter( 'wpml_st_post_type_link_filter_language_details', $setLanguageCode ); remove_filter( 'post_link_category', array( $this, 'adjust_category_in_post_permalink' ) ); $this->sitepress->switch_lang(); $url = $this->url_converter->convert_url_string( $url, $lang ); } return $url; } private function get_term_url( $translations, $lang, $queried_object ) { $url = null; if ( $this->sitepress->is_display_as_translated_taxonomy( $queried_object->taxonomy ) && isset( $translations[ $this->default_language ] ) ) { $url = get_term_link( (int) $translations[ $this->default_language ]->term_id, $queried_object->taxonomy ); $url = $this->url_converter->convert_url_string( $url, $lang ); } return $url; } /** * The permalink needs to be adjusted when the URL structure contains the category tag (%category%). * * @param WP_Term $cat * * @return WP_Term */ public function adjust_category_in_post_permalink( $cat ) { $cat_element = $this->element_factory->create( $cat->term_id, 'term' ); $translation = $cat_element->get_translation( $this->processed_language ); if ( $translation ) { $cat = $translation->get_wp_object() ?: $cat; } return $cat; } } language-switcher/class-wpml-ls-inline-styles.php 0000755 00000025024 14720342453 0016173 0 ustar 00 <?php class WPML_LS_Inline_Styles { /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Settings $settings */ private $settings; /* @var WPML_LS_Model_Build $model_build */ private $model_build; /** * WPML_Language_Switcher_Render_Assets constructor. * * @param WPML_LS_Templates $templates * @param WPML_LS_Settings $settings * @param WPML_LS_Model_Build $model_build */ public function __construct( $templates, $settings, $model_build ) { $this->templates = $templates; $this->settings = $settings; $this->model_build = $model_build; } public function init_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts_action' ), 20 ); } /** * @param WPML_LS_Slot $slot * * @return string */ private function get_slot_color_picker_css( $slot ) { $css = ''; if ( $slot->is_menu() ) { $css = $this->get_slot_color_picker_css_for_menus( $slot ); } elseif ( ! $slot->is_post_translations() || $slot->is_footer() ) { $css = $this->get_slot_color_picker_css_for_widgets_and_statics( $slot ); } return $this->sanitize_css( $css ); } /** * @param WPML_LS_Slot $slot * * @return string */ private function get_slot_color_picker_css_for_menus( $slot ) { $css = ''; $prefix = '.' . $this->model_build->get_css_prefix(); $menu_item_class = $prefix . 'slot-' . $slot->slug(); if ( $slot->get( 'background_other_normal' ) || $slot->get( 'font_other_normal' ) ) { $css .= "$menu_item_class,"; $css .= " $menu_item_class a,"; $css .= " $menu_item_class a:visited{"; $css .= $slot->get( 'background_other_normal' ) ? "background-color:{$slot->get( 'background_other_normal' )};" : ''; $css .= $slot->get( 'font_other_normal' ) ? "color:{$slot->get( 'font_other_normal' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_other_hover' ) || $slot->get( 'background_other_hover' ) ) { $css .= "$menu_item_class:hover,"; $css .= " $menu_item_class:hover a,"; $css .= " $menu_item_class a:hover{"; $css .= $slot->get( 'font_other_hover' ) ? "color:{$slot->get( 'font_other_hover' )};" : ''; $css .= $slot->get( 'background_other_hover' ) ? "background-color:{$slot->get( 'background_other_hover' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_current_normal' ) || $slot->get( 'background_current_normal' ) ) { $css .= "$menu_item_class{$prefix}current-language,"; $css .= " $menu_item_class{$prefix}current-language a,"; $css .= " $menu_item_class{$prefix}current-language a:visited{"; $css .= $slot->get( 'font_current_normal' ) ? "color:{$slot->get( 'font_current_normal' )};" : ''; $css .= $slot->get( 'background_current_normal' ) ? "background-color:{$slot->get( 'background_current_normal' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_current_hover' ) || $slot->get( 'background_current_hover' ) ) { $css .= "$menu_item_class{$prefix}current-language:hover,"; $css .= " $menu_item_class{$prefix}current-language:hover a,"; $css .= " $menu_item_class{$prefix}current-language a:hover{"; $css .= $slot->get( 'font_current_hover' ) ? "color:{$slot->get( 'font_current_hover' )};" : ''; $css .= $slot->get( 'background_current_hover' ) ? "background-color:{$slot->get( 'background_current_hover' )};" : ''; $css .= '}'; } // Override parent menu styles for hierarchical menus if ( $slot->get( 'is_hierarchical' ) ) { if ( $slot->get( 'background_other_normal' ) || $slot->get( 'font_other_normal' ) ) { $css .= "$menu_item_class{$prefix}current-language $menu_item_class,"; $css .= " $menu_item_class{$prefix}current-language $menu_item_class a,"; $css .= " $menu_item_class{$prefix}current-language $menu_item_class a:visited{"; $css .= $slot->get( 'background_other_normal' ) ? "background-color:{$slot->get( 'background_other_normal' )};" : ''; $css .= $slot->get( 'font_other_normal' ) ? "color:{$slot->get( 'font_other_normal' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_other_hover' ) || $slot->get( 'background_other_hover' ) ) { $css .= "$menu_item_class{$prefix}current-language $menu_item_class:hover,"; $css .= " $menu_item_class{$prefix}current-language $menu_item_class:hover a,"; $css .= " $menu_item_class{$prefix}current-language $menu_item_class a:hover {"; $css .= $slot->get( 'font_other_hover' ) ? "color:{$slot->get( 'font_other_hover' )};" : ''; $css .= $slot->get( 'background_other_hover' ) ? "background-color:{$slot->get( 'background_other_hover' )};" : ''; $css .= '}'; } } return $css; } /** * @param WPML_LS_Slot $slot * * @return string */ private function get_slot_color_picker_css_for_widgets_and_statics( $slot ) { $css = ''; $prefix = '.' . $this->model_build->get_css_prefix(); $wrapper_class = '.' . $this->model_build->get_slot_css_main_class( $slot->group(), $slot->slug() ); if ( $slot->get( 'background_normal' ) ) { $css .= "$wrapper_class{"; $css .= "background-color:{$slot->get( 'background_normal' )};"; $css .= '}'; } if ( $slot->get( 'border_normal' ) ) { $css .= "$wrapper_class, $wrapper_class {$prefix}sub-menu, $wrapper_class a {"; $css .= "border-color:{$slot->get( 'border_normal' )};"; $css .= '}'; } if ( $slot->get( 'font_other_normal' ) || $slot->get( 'background_other_normal' ) ) { $css .= "$wrapper_class a, $wrapper_class .wpml-ls-sub-menu a, $wrapper_class .wpml-ls-sub-menu a:link, $wrapper_class li:not(.wpml-ls-current-language) .wpml-ls-link, $wrapper_class li:not(.wpml-ls-current-language) .wpml-ls-link:link {"; $css .= $slot->get( 'font_other_normal' ) ? "color:{$slot->get( 'font_other_normal' )};" : ''; $css .= $slot->get( 'background_other_normal' ) ? "background-color:{$slot->get( 'background_other_normal' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_other_hover' ) || $slot->get( 'background_other_hover' ) ) { $css .= "$wrapper_class a, $wrapper_class .wpml-ls-sub-menu a:hover,$wrapper_class .wpml-ls-sub-menu a:focus, $wrapper_class .wpml-ls-sub-menu a:link:hover, $wrapper_class .wpml-ls-sub-menu a:link:focus {"; $css .= $slot->get( 'font_other_hover' ) ? "color:{$slot->get( 'font_other_hover' )};" : ''; $css .= $slot->get( 'background_other_hover' ) ? "background-color:{$slot->get( 'background_other_hover' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_current_normal' ) || $slot->get( 'background_current_normal' ) ) { $css .= "$wrapper_class {$prefix}current-language > a {"; $css .= $slot->get( 'font_current_normal' ) ? "color:{$slot->get( 'font_current_normal' )};" : ''; $css .= $slot->get( 'background_current_normal' ) ? "background-color:{$slot->get( 'background_current_normal' )};" : ''; $css .= '}'; } if ( $slot->get( 'font_current_hover' ) || $slot->get( 'background_current_hover' ) ) { $css .= "$wrapper_class {$prefix}current-language:hover>a, $wrapper_class {$prefix}current-language>a:focus {"; $css .= $slot->get( 'font_current_hover' ) ? "color:{$slot->get( 'font_current_hover' )};" : ''; $css .= $slot->get( 'background_current_hover' ) ? "background-color:{$slot->get( 'background_current_hover' )};" : ''; $css .= '}'; } return $css; } /** * @param array $slots * * @return string */ public function get_slots_inline_styles( $slots ) { $all_styles = ''; if ( $this->settings->can_load_styles() ) { foreach ( $slots as $slot ) { /* @var WPML_LS_Slot $slot */ $css = $this->get_slot_color_picker_css( $slot ); if ( $css ) { $style_id = 'wpml-ls-inline-styles-' . $slot->group() . '-' . $slot->slug(); $all_styles .= '<style type="text/css" id="' . $style_id . '">' . $css . '</style>' . PHP_EOL; } } } return $all_styles; } /** * @return string */ public function get_additional_style() { $additional_css = $this->settings->get_setting( 'additional_css' ); if ($additional_css) { $css = $this->sanitize_css($additional_css); if ( $css ) { $css = '<style type="text/css" id="wpml-ls-inline-styles-additional-css">' . $css . '</style>' . PHP_EOL; } return $css; } return ''; } public function wp_enqueue_scripts_action() { $this->enqueue_inline_styles(); } private function enqueue_inline_styles() { if ( $this->settings->can_load_styles() ) { $active_slots = $this->settings->get_active_slots(); $first_valid_handler = $this->get_first_valid_style_handler( $active_slots ); foreach ( $active_slots as $slot ) { /* @var WPML_LS_Slot $slot */ $css = $this->get_slot_color_picker_css( $slot ); if ( empty( $css ) ) { continue; } $template = $this->templates->get_template( $slot->template() ); if ( $template->has_styles() ) { wp_add_inline_style( $template->get_inline_style_handler(), $css ); } elseif ( $first_valid_handler ) { wp_add_inline_style( $first_valid_handler, $css ); } else { echo $this->get_raw_inline_style_tag( $slot, $css ); } } if ( $first_valid_handler ) { $additional_css = $this->sanitize_css( $this->settings->get_setting( 'additional_css' ) ); if ( ! empty( $additional_css ) ) { wp_add_inline_style( $first_valid_handler, $additional_css ); } } else { echo $this->get_additional_style(); } } } /** * @param array $active_slots * * @return bool|mixed|null|string */ private function get_first_valid_style_handler( $active_slots ) { $first_handler = null; foreach ( $active_slots as $slot ) { /* @var WPML_LS_Slot $slot */ $template = $this->templates->get_template( $slot->template() ); $handler = $template->get_inline_style_handler(); if ( $handler ) { $first_handler = $handler; break; } } return $first_handler; } public function admin_output() { if ( $this->settings->can_load_styles() ) { $active_slots = $this->settings->get_active_slots(); foreach ( $active_slots as $slot ) { /* @var WPML_LS_Slot $slot */ $css = $this->get_slot_color_picker_css( $slot ); echo $this->get_raw_inline_style_tag( $slot, $css ); } echo $this->get_additional_style(); } } /** * @param WPML_LS_Slot $slot * @param string $css * * @return string */ private function get_raw_inline_style_tag( $slot, $css ) { $style_id = 'wpml-ls-inline-styles-' . $slot->group() . '-' . $slot->slug(); return '<style type="text/css" id="' . $style_id . '">' . $css . '</style>' . PHP_EOL; } /** * @param string $css * * @return string */ private function sanitize_css( $css ) { $css = wp_strip_all_tags( $css ); $css = preg_replace( '/\s+/S', ' ', trim( $css ) ); return $css; } } language-switcher/class-wpml-ls-migration.php 0000755 00000025651 14720342453 0015373 0 ustar 00 <?php class WPML_LS_Migration { const ICL_OPTIONS_SLUG = 'icl_sitepress_settings'; /* @var WPML_LS_Settings $settings */ private $settings; /* @var SitePress $sitepress */ private $sitepress; /* @var WPML_LS_Slot_Factory $slot_factory */ private $slot_factory; /* @var array $old_settings */ private $old_settings; /** * WPML_LS_Migration constructor. * * @param WPML_LS_Settings $settings * @param SitePress $sitepress * @param WPML_LS_Slot_Factory $slot_factory */ public function __construct( $settings, $sitepress, $slot_factory ) { $this->settings = $settings; $this->sitepress = $sitepress; $this->slot_factory = $slot_factory; } /** * @param array $old_settings * * @return mixed */ public function get_converted_settings( $old_settings ) { $this->old_settings = is_array( $old_settings ) ? $old_settings : array(); $new_settings = array( 'migrated' => 0 ); if ( $this->has_old_keys() ) { $new_settings = $this->get_converted_global_settings(); $new_settings['menus'] = $this->get_converted_menus_settings(); $new_settings['sidebars'] = $this->get_converted_sidebars_settings(); $new_settings['statics']['footer'] = $this->get_converted_footer_settings(); $new_settings['statics']['post_translations'] = $this->get_converted_post_translations_settings(); $new_settings['statics']['shortcode_actions'] = $this->get_converted_shortcode_actions_settings(); $new_settings['migrated'] = 1; } return $new_settings; } /** * @return array */ private function get_converted_global_settings() { $new_settings['additional_css'] = isset( $this->old_settings['icl_additional_css'] ) ? $this->old_settings['icl_additional_css'] : ''; $new_settings['copy_parameters'] = isset( $this->old_settings['icl_lang_sel_copy_parameters'] ) ? $this->old_settings['icl_lang_sel_copy_parameters'] : ''; return $new_settings; } /** * @return array */ private function get_converted_menus_settings() { $menus_settings = array(); if ( $this->get_old_setting( 'display_ls_in_menu' ) ) { $menu_id = $this->get_old_setting( 'menu_for_ls' ); $menu_id = $this->sitepress->get_object_id( $menu_id, 'nav_menu', true, $this->sitepress->get_default_language() ); if ( $menu_id ) { $s = array( 'slot_group' => 'menus', 'slot_slug' => $menu_id, 'show' => 1, 'template' => $this->get_template_for( 'menus' ), 'display_flags' => $this->get_old_setting( 'icl_lso_flags' ), 'display_names_in_native_lang' => $this->get_old_setting( 'icl_lso_native_lang' ), 'display_names_in_current_lang' => $this->get_old_setting( 'icl_lso_display_lang' ), 'display_link_for_current_lang' => 1, 'position_in_menu' => 'after', 'is_hierarchical' => $this->get_old_setting( 'icl_lang_sel_type' ) === 'dropdown' ? 1 : 0, ); $menus_settings[ $menu_id ] = $this->slot_factory->get_slot( $s ); } } return $menus_settings; } /** * @return array */ private function get_converted_sidebars_settings() { $sidebars_settings = array(); $sidebars_widgets = wp_get_sidebars_widgets(); foreach ( $sidebars_widgets as $sidebar_slug => $widgets ) { $sidebar_has_selector = false; if ( is_array( $widgets ) ) { foreach ( $widgets as $widget ) { if ( strpos( $widget, WPML_LS_Widget::SLUG ) === 0 ) { $sidebar_has_selector = true; break; } } } if ( $sidebar_has_selector ) { $s = array( 'slot_group' => 'sidebars', 'slot_slug' => $sidebar_slug, 'show' => 1, 'template' => $this->get_template_for( 'sidebars' ), 'display_flags' => $this->get_old_setting( 'icl_lso_flags' ), 'display_names_in_native_lang' => $this->get_old_setting( 'icl_lso_native_lang' ), 'display_names_in_current_lang' => $this->get_old_setting( 'icl_lso_display_lang' ), 'display_link_for_current_lang' => 1, 'widget_title' => $this->get_old_setting( 'icl_widget_title_show' ) ? esc_html__( 'Languages', 'sitepress' ) : '', ); $s = array_merge( $s, $this->get_color_picker_settings_for( 'sidebars' ) ); $sidebars_settings[ $sidebar_slug ] = $this->slot_factory->get_slot( $s ); } } return $sidebars_settings; } /** * @return array */ private function get_converted_footer_settings() { $s = array( 'slot_group' => 'statics', 'slot_slug' => 'footer', 'show' => $this->get_old_setting( 'icl_lang_sel_footer' ), 'template' => $this->get_template_for( 'footer' ), 'display_flags' => $this->get_old_setting( 'icl_lso_flags' ), 'display_names_in_native_lang' => $this->get_old_setting( 'icl_lso_native_lang' ), 'display_names_in_current_lang' => $this->get_old_setting( 'icl_lso_display_lang' ), 'display_link_for_current_lang' => 1, ); $s = array_merge( $s, $this->get_color_picker_settings_for( 'footer' ) ); return $this->slot_factory->get_slot( $s ); } /** * @return array */ private function get_converted_post_translations_settings() { $s = array( 'slot_group' => 'statics', 'slot_slug' => 'post_translations', 'show' => $this->get_old_setting( 'icl_post_availability' ), 'template' => $this->get_template_for( 'post_translations' ), 'display_before_content' => $this->get_old_setting( 'icl_post_availability_position' ) === 'above' ? 1 : 0, 'display_after_content' => $this->get_old_setting( 'icl_post_availability_position' ) === 'below' ? 1 : 0, 'availability_text' => $this->get_old_setting( 'icl_post_availability_text' ), 'display_flags' => 0, 'display_names_in_native_lang' => 0, 'display_names_in_current_lang' => 1, 'display_link_for_current_lang' => 0, ); return $this->slot_factory->get_slot( $s ); } /** * @return array */ private function get_converted_shortcode_actions_settings() { $s = array( 'slot_group' => 'statics', 'slot_slug' => 'shortcode_actions', 'show' => 1, 'template' => $this->get_template_for( 'shortcode_actions' ), 'display_flags' => $this->get_old_setting( 'icl_lso_flags' ), 'display_names_in_native_lang' => $this->get_old_setting( 'icl_lso_native_lang' ), 'display_names_in_current_lang' => $this->get_old_setting( 'icl_lso_display_lang' ), 'display_link_for_current_lang' => 1, ); $s = array_merge( $s, $this->get_color_picker_settings_for( 'shortcode_actions' ) ); return $this->slot_factory->get_slot( $s ); } /** * @param string $context * * @return array */ private function get_color_picker_settings_for( $context ) { $ret = array(); $map = array( 'font-current-normal' => 'font_current_normal', 'font-current-hover' => 'font_current_hover', 'background-current-normal' => 'background_current_normal', 'background-current-hover' => 'background_current_hover', 'font-other-normal' => 'font_other_normal', 'font-other-hover' => 'font_other_hover', 'background-other-normal' => 'background_other_normal', 'background-other-hover' => 'background_other_hover', 'border' => 'border_normal', 'background' => 'background_normal', ); $key = $context !== 'footer' ? 'icl_lang_sel_config' : 'icl_lang_sel_footer_config'; $settings = isset( $this->old_settings[ $key ] ) ? $this->old_settings[ $key ] : array(); foreach ( $settings as $k => $v ) { $ret[ $map[ $k ] ] = $v; } return array_filter( $ret ); } /** * @param string $key * * @return mixed|string|int|null */ private function get_old_setting( $key ) { return isset( $this->old_settings[ $key ] ) ? $this->old_settings[ $key ] : null; } /** * @param string $slot_type * * @return mixed */ private function get_template_for( $slot_type ) { $templates = $this->settings->get_core_templates(); $type = 'dropdown'; $old_type = $this->get_old_setting( 'icl_lang_sel_type' ); // dropdown | list $old_stype = $this->get_old_setting( 'icl_lang_sel_stype' ); // classic | mobile-auto | mobile $old_orientation = $this->get_old_setting( 'icl_lang_sel_orientation' ); // vertical | horizontal if ( $slot_type === 'menus' ) { $type = 'menu-item'; } elseif ( $slot_type === 'sidebars' ) { $type = $old_type === 'dropdown' ? 'dropdown' : ( $old_orientation === 'vertical' ? 'list-vertical' : 'list-horizontal' ); } elseif ( $slot_type === 'shortcode_actions' ) { $type = $old_type === 'dropdown' ? 'dropdown' : ( $old_orientation === 'vertical' ? 'list-vertical' : 'list-horizontal' ); } elseif ( $slot_type === 'footer' ) { $type = 'list-horizontal'; } elseif ( $slot_type === 'post_translations' ) { $type = 'post-translations'; } if ( $type === 'dropdown' ) { $type = $old_stype === 'mobile' ? 'dropdown-click' : 'dropdown'; } return $templates[ $type ]; } /** * @return bool */ private function has_old_keys() { $result = false; $old_keys = array( 'icl_lang_sel_config', 'icl_lang_sel_footer_config', 'icl_language_switcher_sidebar', 'icl_widget_title_show', 'icl_lang_sel_type', 'icl_lso_flags', 'icl_lso_native_lang', 'icl_lso_display_lang', 'icl_lang_sel_footer', 'icl_post_availability', 'icl_post_availability_position', 'icl_post_availability_text', ); foreach ( $old_keys as $old_key ) { if ( array_key_exists( $old_key, $this->old_settings ) ) { $result = true; break; } } return $result; } /** * @since 3.7.0 Convert menu LS handled now by ID instead of slugs previously * * @param array $settings * * @return array */ public function convert_menu_ids( $settings ) { if ( $settings['menus'] ) { foreach ( $settings['menus'] as $slug => $menu_slot ) { /** @var WPML_LS_Menu_Slot $menu_slot */ if ( is_string( $slug ) ) { $current_lang = $this->sitepress->get_current_language(); $this->sitepress->switch_lang( $this->sitepress->get_default_language() ); $menu = wp_get_nav_menu_object( $slug ); $new_id = $menu->term_id; $slot_args = $menu_slot->get_model(); $slot_args['slot_slug'] = $new_id; $new_slot = $this->slot_factory->get_slot( $slot_args ); unset( $settings['menus'][ $slug ] ); $settings['menus'][ $new_id ] = $new_slot; $this->sitepress->switch_lang( $current_lang ); } } } $settings['converted_menu_ids'] = 1; return $settings; } } language-switcher/class-wpml-ls-settings-color-presets.php 0000755 00000004510 14720342453 0020030 0 ustar 00 <?php class WPML_LS_Settings_Color_Presets { /** * @return array */ public function get_defaults() { $void = array( 'font_current_normal' => '', 'font_current_hover' => '', 'background_current_normal' => '', 'background_current_hover' => '', 'font_other_normal' => '', 'font_other_hover' => '', 'background_other_normal' => '', 'background_other_hover' => '', 'border_normal' => '', 'background_normal' => '', ); $gray = array( 'font_current_normal' => '#222222', 'font_current_hover' => '#000000', 'background_current_normal' => '#eeeeee', 'background_current_hover' => '#eeeeee', 'font_other_normal' => '#222222', 'font_other_hover' => '#000000', 'background_other_normal' => '#e5e5e5', 'background_other_hover' => '#eeeeee', 'border_normal' => '#cdcdcd', 'background_normal' => '#e5e5e5', ); $white = array( 'font_current_normal' => '#444444', 'font_current_hover' => '#000000', 'background_current_normal' => '#ffffff', 'background_current_hover' => '#eeeeee', 'font_other_normal' => '#444444', 'font_other_hover' => '#000000', 'background_other_normal' => '#ffffff', 'background_other_hover' => '#eeeeee', 'border_normal' => '#cdcdcd', 'background_normal' => '#ffffff', ); $blue = array( 'font_current_normal' => '#ffffff', 'font_current_hover' => '#000000', 'background_current_normal' => '#95bedd', 'background_current_hover' => '#95bedd', 'font_other_normal' => '#000000', 'font_other_hover' => '#ffffff', 'background_other_normal' => '#cbddeb', 'background_other_hover' => '#95bedd', 'border_normal' => '#0099cc', 'background_normal' => '#cbddeb', ); return array( 'void' => array( 'label' => esc_html__( 'Clear all colors', 'sitepress' ), 'values' => $void, ), 'gray' => array( 'label' => esc_html__( 'Gray', 'sitepress' ), 'values' => $gray, ), 'white' => array( 'label' => esc_html__( 'White', 'sitepress' ), 'values' => $white, ), 'blue' => array( 'label' => esc_html__( 'Blue', 'sitepress' ), 'values' => $blue, ), ); } } language-switcher/class-wpml-ls-dependencies-factory.php 0000755 00000006573 14720342453 0017477 0 ustar 00 <?php class WPML_LS_Dependencies_Factory { /* @var SitePress $sitepress */ private $sitepress; /* @var array $parameters */ private $parameters; /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Slot_Factory $slot_factory */ private $slot_factory; /* @var WPML_LS_Settings $settings */ private $settings; /* @var WPML_LS_Model_Build $model_build */ private $model_build; /* @var WPML_LS_Inline_Styles $inline_styles */ private $inline_styles; /* @var WPML_LS_Render $render */ private $render; /* @var WPML_LS_Admin_UI $admin_ui */ private $admin_ui; /** @var WPML_LS_Shortcodes */ private $shortcodes; /** @var WPML_LS_Actions */ private $actions; /** * WPML_LS_Dependencies_Factory constructor. * * @param SitePress $sitepress * @param array $parameters */ public function __construct( SitePress $sitepress, array $parameters = [] ) { $this->sitepress = $sitepress; $this->parameters = $parameters; } /** * @return SitePress */ public function sitepress() { return $this->sitepress; } /** * @param string $key * * @return mixed */ public function parameter( $key ) { return isset( $this->parameters[ $key ] ) ? $this->parameters[ $key ] : null; } /** * @return WPML_LS_Templates */ public function templates() { if ( ! $this->templates ) { $this->templates = new WPML_LS_Templates(); } return $this->templates; } /** * @return WPML_LS_Slot_Factory */ public function slot_factory() { if ( ! $this->slot_factory ) { $this->slot_factory = new WPML_LS_Slot_Factory(); } return $this->slot_factory; } /** * @return WPML_LS_Settings */ public function settings() { if ( ! $this->settings ) { $this->settings = new WPML_LS_Settings( $this->templates(), $this->sitepress(), $this->slot_factory() ); } return $this->settings; } /** * @return WPML_LS_Model_Build */ public function model_build() { if ( ! $this->model_build ) { $this->model_build = new WPML_LS_Model_Build( $this->settings(), $this->sitepress(), $this->parameter( 'css_prefix' ) ); } return $this->model_build; } /** * @return WPML_LS_Inline_Styles */ public function inline_styles() { if ( ! $this->inline_styles ) { $this->inline_styles = new WPML_LS_Inline_Styles( $this->templates(), $this->settings(), $this->model_build() ); } return $this->inline_styles; } /** * @return WPML_LS_Render */ public function render() { if ( ! $this->render ) { $this->render = new WPML_LS_Render( $this->templates(), $this->settings(), $this->model_build(), $this->inline_styles(), $this->sitepress() ); } return $this->render; } /** * @return WPML_LS_Admin_UI */ public function admin_ui() { if ( ! $this->admin_ui ) { $this->admin_ui = new WPML_LS_Admin_UI( $this->templates(), $this->settings(), $this->render(), $this->inline_styles(), $this->sitepress() ); } return $this->admin_ui; } /** * @return WPML_LS_Shortcodes */ public function shortcodes() { if ( ! $this->shortcodes ) { $this->shortcodes = new WPML_LS_Shortcodes( $this->settings(), $this->render(), $this->sitepress() ); } return $this->shortcodes; } /** * @return WPML_LS_Shortcodes */ public function actions() { if ( ! $this->actions ) { $this->actions = new WPML_LS_Actions( $this->settings(), $this->render(), $this->sitepress() ); } return $this->actions; } } language-switcher/class-wpml-ls-settings-strings.php 0000755 00000007142 14720342453 0016724 0 ustar 00 <?php class WPML_LS_Settings_Strings { private $strings_meta = array( 'availability_text' => array( 'domain' => 'WPML', 'name' => 'Text for alternative languages for posts', ), 'widget_title' => array( 'domain' => 'Widgets', 'name' => 'widget title', ), ); /* @var WPML_LS_Slot_Factory $slot_factory */ private $slot_factory; public function __construct( $slot_factory ) { $this->slot_factory = $slot_factory; } /** * @param array $new_settings * @param array $old_settings */ public function register_all( $new_settings, $old_settings ) { $void_slot = array( 'show' => false ); foreach ( $new_settings['sidebars'] as $slug => $slot_settings ) { $old_slot_settings = isset( $old_settings['sidebars'][ $slug ] ) ? $old_settings['sidebars'][ $slug ] : $this->slot_factory->get_slot( $void_slot ); $this->register_slot_strings( $slot_settings, $old_slot_settings ); } $post_translations_settings = isset( $new_settings['statics']['post_translations'] ) ? $new_settings['statics']['post_translations'] : null; if ( $post_translations_settings ) { $old_slot_settings = isset( $old_settings['statics']['post_translations'] ) ? $old_settings['statics']['post_translations'] : $this->slot_factory->get_slot( $void_slot ); $this->register_slot_strings( $post_translations_settings, $old_slot_settings ); } } /** * @param array $settings * * @return array */ public function translate_all( $settings ) { if ( isset( $settings['sidebars'] ) ) { foreach ( $settings['sidebars'] as &$slot_settings ) { $slot_settings = $this->translate_slot_strings( $slot_settings ); } } if ( isset( $settings['statics']['post_translations'] ) ) { $settings['statics']['post_translations'] = $this->translate_slot_strings( $settings['statics']['post_translations'] ); } return $settings; } /** * @param WPML_LS_Slot $slot * @param WPML_LS_Slot $old_slot */ private function register_slot_strings( WPML_LS_Slot $slot, WPML_LS_Slot $old_slot ) { foreach ( $this->strings_meta as $key => $string_meta ) { if ( $slot->get( $key ) ) { $old_string = $old_slot->get( $key ); if ( $key === 'widget_title' && $old_string && function_exists( 'icl_st_update_string_actions' ) ) { icl_st_update_string_actions( 'Widgets', $this->get_string_name( $key, $old_string ), $old_string, $slot->get( $key ) ); } else { do_action( 'wpml_register_single_string', $this->strings_meta[ $key ]['domain'], $this->get_string_name( $key, $slot->get( $key ) ), $slot->get( $key ) ); } } } } /** * @param WPML_LS_Slot $slot * * @return WPML_LS_Slot */ private function translate_slot_strings( $slot ) { foreach ( $this->strings_meta as $key => $string_meta ) { if ( $slot->get( $key ) ) { if ( $key === 'title' && function_exists( 'icl_sw_filters_widget_title' ) ) { $translation = icl_sw_filters_widget_title( $slot->get( $key ) ); $slot->set( $key, $translation ); } else { $string_name = $this->get_string_name( $key, $slot->get( $key ) ); $domain = $this->strings_meta[ $key ]['domain']; $translation = apply_filters( 'wpml_translate_single_string', $slot->get( $key ), $domain, $string_name ); $slot->set( $key, $translation ); } } } return $slot; } /** * @param string $key * @param string $string_value * * @return string */ private function get_string_name( $key, $string_value ) { $name = $this->strings_meta[ $key ]['name']; if ( $key === 'widget_title' ) { $name = $name . ' - ' . md5( $string_value ); } return $name; } } language-switcher/class-wpml-ls-render.php 0000755 00000022501 14720342453 0014650 0 ustar 00 <?php class WPML_LS_Render extends WPML_SP_User { const THE_CONTENT_FILTER_PRIORITY = 100; /* @var WPML_LS_Template $current_template */ private $current_template; /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Settings $settings */ private $settings; /* @var WPML_LS_Model_Build $model_build */ private $model_build; /* @var WPML_LS_Inline_Styles &$inline_styles */ private $inline_styles; /* @var WPML_LS_Assets $assets */ private $assets; /** @var bool */ private $wpml_ls_exclude_in_menu; /** * WPML_Language_Switcher_Menu constructor. * * @param WPML_LS_Templates $templates * @param WPML_LS_Settings $settings * @param WPML_LS_Model_Build $model_build * @param WPML_LS_Inline_Styles $inline_styles * @param SitePress $sitepress */ public function __construct( $templates, $settings, $model_build, $inline_styles, $sitepress ) { $this->templates = $templates; $this->settings = $settings; $this->model_build = $model_build; $this->inline_styles = $inline_styles; $this->assets = new WPML_LS_Assets( $this->templates, $this->settings ); parent::__construct( $sitepress ); } public function init_hooks() { if ( $this->sitepress->get_setting( 'setup_complete' ) ) { add_filter( 'wp_get_nav_menu_items', array( $this, 'wp_get_nav_menu_items_filter' ), 10, 2 ); add_filter( 'wp_setup_nav_menu_item', array( $this, 'maybe_repair_menu_item' ), PHP_INT_MAX ); add_filter( 'the_content', array( $this, 'the_content_filter' ), self::THE_CONTENT_FILTER_PRIORITY ); if ( ! $this->is_widgets_page() ) { add_action( 'wp_footer', array( $this, 'wp_footer_action' ), 19 ); } $this->assets->init_hooks(); } } /** * @param WPML_LS_Slot $slot * * @return string */ public function render( $slot ) { $html = ''; $model = array(); if ( ! $this->is_hidden( $slot ) ) { $this->assets->maybe_late_enqueue_template( $slot->template() ); $this->current_template = clone $this->templates->get_template( $slot->template() ); $sandbox = false; if ( $slot->template_string() ) { $this->current_template->set_template_string( $slot->template_string() ); $sandbox = true; } $model = $this->model_build->get( $slot, $this->current_template->get_template_data() ); if ( $model['languages'] ) { $this->current_template->set_model( $model ); $html = $this->current_template->get_html( $sandbox ); } } return $this->filter_html( $html, $model, $slot ); } /** * @param string $html * @param array $model * @param WPML_LS_Slot $slot * * @return string */ private function filter_html( $html, $model, $slot ) { /** * @param string $html The HTML for the language switcher * @param array $model The model passed to the template * @param WPML_LS_Slot $slot The language switcher settings for this slot */ return apply_filters( 'wpml_ls_html', $html, $model, $slot ); } /** * @param WPML_LS_Slot $slot * * @return array */ public function get_preview( $slot ) { $ret = []; if ( $slot->is_menu() ) { $ret['html'] = $this->render_menu_preview( $slot ); } elseif ( $slot->is_post_translations() ) { $ret['html'] = $this->post_translations_label( $slot ); } else { $ret['html'] = $this->render( $slot ); } $ret['css'] = $this->current_template->get_styles( true ); $ret['js'] = $this->current_template->get_scripts( true ); $ret['styles'] = $this->inline_styles->get_slots_inline_styles( [ $slot ] ); return $ret; } /** * @param array $items * @param WP_Term $menu * * @return array */ public function wp_get_nav_menu_items_filter( $items, $menu ) { if ( $this->should_not_alter_menu() ) { return $items; } if ( $this->is_for_menu_panel_in_customizer() ) { return $items; } $slot = $this->settings->get_menu_settings_from_id( $menu->term_id ); if ( $slot->is_enabled() && ! $this->is_hidden( $slot ) ) { $is_before = 'before' === $slot->get( 'position_in_menu' ); $lang_items = $this->get_menu_items( $slot ); if ( $items && $lang_items ) { $items = $this->merge_menu_items( $items, $lang_items, $is_before ); } elseif ( ! $items && $lang_items ) { $items = $lang_items; } } return $items; } private function is_for_menu_panel_in_customizer() { return is_customize_preview() && ! did_action( 'template_redirect' ); } private function should_not_alter_menu() { if ( null === $this->wpml_ls_exclude_in_menu ) { $this->wpml_ls_exclude_in_menu = apply_filters( 'wpml_ls_exclude_in_menu', true ); } return is_admin() && $this->wpml_ls_exclude_in_menu; } /** * @param WP_Post[] $items * @param WPML_LS_Menu_Item[] $lang_items * @param bool $is_before * * @return array */ private function merge_menu_items( $items, $lang_items, $is_before ) { if ( $is_before ) { $items_to_prepend = $lang_items; $items_to_append = $items; } else { $items_to_prepend = $items; $items_to_append = $lang_items; } $menu_orders = wp_list_pluck( $items_to_prepend, 'menu_order' ); $offset = max( $menu_orders ); foreach ( $items_to_append as $item ) { $item->menu_order = $item->menu_order + $offset; } return array_merge( $items_to_prepend, $items_to_append ); } /** * @param WPML_LS_Slot $slot * * @return array */ private function get_menu_items( $slot ) { $lang_items = array(); $model = $this->model_build->get( $slot ); if ( isset( $model['languages'] ) ) { $this->current_template = $this->templates->get_template( $slot->template() ); $menu_order = 1; foreach ( $model['languages'] as $language_model ) { $this->current_template->set_model( $language_model ); $item_content = $this->filter_html( $this->current_template->get_html(), $language_model, $slot ); $ls_menu_item = new WPML_LS_Menu_Item( $language_model, $item_content ); $ls_menu_item->menu_order = $menu_order; $menu_order++; $lang_items[] = $ls_menu_item; } } return $lang_items; } /** * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-4706#comment=102-231339 * * @param WP_Post|WPML_LS_Menu_Item|object $item * * @return object $item */ public function maybe_repair_menu_item( $item ) { if ( $this->should_not_alter_menu() ) { return $item; } if ( ! $item instanceof WPML_LS_Menu_Item ) { return $item; } if ( ! $item->db_id ) { $item->db_id = $item->ID; } return $item; } /** * @param WPML_LS_Slot $slot * * @return string */ private function render_menu_preview( $slot ) { $items = $this->get_menu_items( $slot ); $class = $slot->get( 'is_hierarchical' ) ? 'wpml-ls-menu-hierarchical-preview' : 'wpml-ls-menu-flat-preview'; $defaults = array( 'before' => '', 'after' => '', 'link_before' => '', 'link_after' => '', 'theme_location' => '', ); $defaults = (object) $defaults; $output = walk_nav_menu_tree( $items, 2, $defaults ); $dummy_item = esc_html__( 'menu items', 'sitepress' ); if ( $slot->get( 'position_in_menu' ) === 'before' ) { $output .= '<li class="dummy-menu-item"><a href="#">' . $dummy_item . '...</a></li>'; } else { $output = '<li class="dummy-menu-item"><a href="#">...' . $dummy_item . '</a></li>' . $output; } return '<div><ul class="wpml-ls-menu-preview ' . $class . '">' . $output . '</ul></div>'; } /** * @param WPML_LS_Slot $slot * * @return bool true if the switcher is to be hidden */ private function is_hidden( $slot ) { if ( ! function_exists( 'wpml_home_url_ls_hide_check' ) ) { require WPML_PLUGIN_PATH . '/inc/post-translation/wpml-root-page-actions.class.php'; } return wpml_home_url_ls_hide_check() && ! $slot->is_shortcode_actions(); } public function is_widgets_page() { global $pagenow; return is_admin() && 'widgets.php' === $pagenow; } /** * @param string $content * * @return string */ public function the_content_filter( $content ) { $post_translations = ''; $slot = $this->settings->get_slot( 'statics', 'post_translations' ); if ( $slot->is_enabled() && $this->sitepress->get_wp_api()->is_singular() ) { $post_translations = $this->post_translations_label( $slot ); } if ( $post_translations ) { if ( $slot->get( 'display_before_content' ) ) { $content = $post_translations . $content; } if ( $slot->get( 'display_after_content' ) ) { $content = $content . $post_translations; } } return $content; } /** * @param WPML_LS_Slot $slot * * @return mixed|string|void */ public function post_translations_label( $slot ) { $css_classes = $this->model_build->get_slot_css_classes( $slot ); $html = $this->render( $slot ); if ( $html ) { $html = sprintf( $slot->get( 'availability_text' ), $html ); $html = '<p class="' . $css_classes . '">' . $html . '</p>'; /* @deprecated use 'wpml_ls_post_alternative_languages' instead */ $html = apply_filters( 'icl_post_alternative_languages', $html ); /** * @param string $html */ $html = apply_filters( 'wpml_ls_post_alternative_languages', $html ); } return $html; } public function wp_footer_action() { $slot = $this->settings->get_slot( 'statics', 'footer' ); if ( $slot->is_enabled() ) { echo $this->render( $slot ); } } } language-switcher/class-wpml-ls-languages-cache.php 0000755 00000002060 14720342453 0016376 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 17/10/17 * Time: 5:18 PM */ class WPML_LS_Languages_Cache { private $cache_key; private $cache; public function __construct( $template_args, $current_language, $default_language, $wp_query ) { $cache_key_args = $template_args ? array_filter( $template_args ) : array( 'default' ); $cache_key_args[] = $current_language; $cache_key_args[] = $default_language; if ( isset( $wp_query->request ) ) { $cache_key_args[] = $wp_query->request; } $cache_key_args = array_filter( $cache_key_args ); $this->cache_key = md5( (string) wp_json_encode( $cache_key_args ) ); $cache_group = 'ls_languages'; $this->cache = new WPML_WP_Cache( $cache_group ); wp_cache_add_non_persistent_groups( $cache_group ); } public function get() { $found = false; $result = $this->cache->get( $this->cache_key, $found ); if ( $found ) { return $result; } else { return null; } } public function set( $ls_languages ) { $this->cache->set( $this->cache_key, $ls_languages ); } } language-switcher/class-wpml-ls-widget.php 0000755 00000011163 14720342453 0014656 0 ustar 00 <?php use WPML\FP\Obj; class WPML_LS_Widget extends WP_Widget { const SLUG = 'icl_lang_sel_widget'; const ANCHOR_BASE = '#sidebars/'; public function __construct() { parent::__construct( self::SLUG, // Base ID __( 'Language Switcher', 'sitepress' ), // Name array( 'description' => __( 'Language Switcher', 'sitepress' ), ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_action' ) ); } public static function register() { register_widget( __CLASS__ ); } /** * @param string $hook */ public function admin_enqueue_scripts_action( $hook ) { global $sitepress; if ( 'widgets.php' === $hook ) { $suffix = $sitepress->get_wp_api()->constant( 'SCRIPT_DEBUG' ) ? '' : '.min'; wp_enqueue_script( 'wpml-widgets', ICL_PLUGIN_URL . '/res/js/widgets' . $suffix . '.js', array( 'jquery' ) ); } } /** * @param array<string,mixed> $args * @param array $instance */ public function widget( $args, $instance ) { /* @var WPML_Language_Switcher $wpml_language_switcher */ global $wpml_language_switcher; $sidebar = isset( $args['id'] ) ? $args['id'] : ''; /* @var WPML_LS_Slot $slot */ $slot = $wpml_language_switcher->get_slot( 'sidebars', $sidebar ); if ( ! $slot instanceof WPML_LS_Sidebar_Slot ) { $instanceSlot = Obj::prop( 'slot', $instance ); $slot = $instanceSlot instanceof WPML_LS_Sidebar_Slot ? $instanceSlot : $slot; } $ret = $wpml_language_switcher->render( $slot ); if ( $ret ) { if ( $slot->get( 'widget_title' ) ) { remove_filter( 'widget_title', 'icl_sw_filters_widget_title', 0 ); $ret = $args['before_title'] . apply_filters( 'widget_title', $slot->get( 'widget_title' ) ) . $args['after_title'] . $ret; if ( function_exists( 'icl_sw_filters_widget_title' ) ) { add_filter( 'widget_title', 'icl_sw_filters_widget_title', 0 ); } } echo $args['before_widget'] . $ret . $args['after_widget']; } } /** * @param array $instance * * @return void */ public function form( $instance ) { /* @var WPML_Language_Switcher $wpml_language_switcher */ global $wpml_language_switcher; $slug = isset( $instance['slot'] ) ? $instance['slot']->slug() : ''; echo $wpml_language_switcher->get_button_to_edit_slot( 'sidebars', $slug ); } /** * @param array $new_instance * @param array $old_instance * * @return array */ public function update( $new_instance, $old_instance ) { if ( ! $new_instance && ! $old_instance ) { $slot_factory = new WPML_LS_Slot_Factory(); $args = $slot_factory->get_default_slot_arguments( 'sidebars' ); $args['slot_slug'] = isset( $_POST['sidebar'] ) ? $_POST['sidebar'] : ''; $new_instance = array( 'slot' => $slot_factory->get_slot( $args ), ); } return $new_instance; } /** * @param WPML_LS_Slot $slot * * @return string */ public function create_new_instance( WPML_LS_Slot $slot ) { require_once ABSPATH . '/wp-admin/includes/widgets.php'; $number = next_widget_id_number( $this->id_base ); $this->_set( $number ); $this->_register_one( $number ); $all_instances = $this->get_settings(); $all_instances[ $number ] = $this->get_instance_options_from_slot( $slot ); $this->save_settings( $all_instances ); return $this->id; } /** * @param WPML_LS_Slot $slot * @param int $widget_id */ public function update_instance( WPML_LS_Slot $slot, $widget_id = null ) { $number = isset( $widget_id ) ? $this->get_number_from_widget_id( $widget_id ) : $this->number; $all_instances = $this->get_settings(); $all_instances[ $number ] = $this->get_instance_options_from_slot( $slot ); $this->save_settings( $all_instances ); } /** * @param int $widget_id */ public function delete_instance( $widget_id = null ) { $number = isset( $widget_id ) ? $this->get_number_from_widget_id( $widget_id ) : $this->number; $all_instances = $this->get_settings(); unset( $all_instances[ $number ] ); $this->save_settings( $all_instances ); } /** * @param string|int $widget_id * * @return int */ public function get_number_from_widget_id( $widget_id ) { return (int) preg_replace( '/^' . self::SLUG . '-/', '', (string) $widget_id, 1 ); } /** * @param WPML_LS_Slot $slot * * @return array */ private function get_instance_options_from_slot( WPML_LS_Slot $slot ) { return array( 'slot' => $slot ); } /** * @param string $slug * * @return string */ public function get_settings_page_url( $slug ) { return admin_url( 'admin.php?page=' . WPML_LS_Admin_UI::get_page_hook() . self::ANCHOR_BASE . $slug ); } } language-switcher/class-wpml-language-switcher.php 0000755 00000004776 14720342453 0016404 0 ustar 00 <?php /** * Class WPML_Language_Switcher * * Main class */ class WPML_Language_Switcher extends WPML_SP_User { /* @var array $dependencies */ private $dependencies; /** * WPML_Language_Switcher constructor. * * @param SitePress $sitepress * @param WPML_LS_Dependencies_Factory $dependencies */ public function __construct( SitePress $sitepress, WPML_LS_Dependencies_Factory $dependencies = null ) { parent::__construct( $sitepress ); $this->dependencies = $dependencies ? $dependencies : new WPML_LS_Dependencies_Factory( $sitepress, self::parameters() ); } public function init_hooks() { if ( $this->sitepress->get_setting( 'setup_complete' ) ) { add_action( 'widgets_init', array( 'WPML_LS_Widget', 'register' ), 20 ); } $this->dependencies->templates()->init_hooks(); $this->dependencies->settings()->init_hooks(); $this->dependencies->render()->init_hooks(); $this->dependencies->shortcodes()->init_hooks(); $this->dependencies->actions()->init_hooks(); $this->dependencies->inline_styles()->init_hooks(); if ( is_admin() ) { if ( did_action( 'set_current_user' ) ) { $this->init_admin_hooks(); } else { add_action( 'set_current_user', array( $this, 'init_admin_hooks' ) ); } } } public function init_admin_hooks() { if ( current_user_can( 'manage_options' ) ) { $this->dependencies->admin_ui()->init_hooks(); } } /** * @param string $group * @param string $slot * * @return WPML_LS_Slot */ public function get_slot( $group, $slot ) { return $this->dependencies->settings()->get_slot( $group, $slot ); } /** * @param WPML_LS_Slot $slot * * @return string */ public function render( $slot ) { return $this->dependencies->render()->render( $slot ); } /** * @param string $type 'sidebars', 'menus', 'statics' * @param string|int $slug_or_id * * @return string */ public function get_button_to_edit_slot( $type, $slug_or_id ) { return $this->dependencies->admin_ui()->get_button_to_edit_slot( $type, $slug_or_id ); } /** * @return array */ public static function parameters() { return array( 'css_prefix' => 'wpml-ls-', 'core_templates' => array( 'dropdown' => 'wpml-legacy-dropdown', 'dropdown-click' => 'wpml-legacy-dropdown-click', 'list-vertical' => 'wpml-legacy-vertical-list', 'list-horizontal' => 'wpml-legacy-horizontal-list', 'post-translations' => 'wpml-legacy-post-translations', 'menu-item' => 'wpml-menu-item', ), ); } } language-switcher/class-wpml-ls-admin-ui.php 0000755 00000064441 14720342453 0015105 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_LS_Admin_UI extends WPML_Templates_Factory { const NONCE_NAME = 'wpml-language-switcher-admin'; const MAIN_UI_TEMPLATE = 'layout-main.twig'; const RESET_UI_TEMPLATE = 'layout-reset.twig'; const BUTTON_TEMPLATE = 'layout-slot-edit-button.twig'; const SLOT_SLUG_PLACEHOLDER = '%id%'; const RESET_NONCE_NAME = 'wpml-language-switcher-reset'; /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Settings $settings */ private $settings; /* @var WPML_LS_Render $render */ private $render; /* @var WPML_LS_Inline_Styles $inline_styles */ private $inline_styles; /* @var WPML_LS_Assets $assets */ private $assets; /* @var SitePress $sitepress */ private $sitepress; /** * WPML_Language_Switcher_Menu constructor. * * @param WPML_LS_Templates $templates * @param WPML_LS_Settings $settings * @param WPML_LS_Render $render * @param WPML_LS_Inline_Styles $inline_styles * @param SitePress $sitepress * @param WPML_LS_Assets $assets */ public function __construct( $templates, $settings, $render, $inline_styles, $sitepress, $assets = null ) { $this->templates = $templates; $this->settings = $settings; $this->render = $render; $this->inline_styles = $inline_styles; $this->assets = $assets ? $assets : new WPML_LS_Assets( $this->templates, $this->settings ); $this->sitepress = $sitepress; parent::__construct(); } public function init_hooks() { add_filter( 'wpml_admin_languages_navigation_items', array( $this, 'languages_navigation_items_filter' ) ); add_action( 'wpml_admin_after_languages_url_format', array( $this, 'after_languages_url_format_action' ) ); add_action( 'wpml_admin_after_wpml_love', array( $this, 'after_wpml_love_action' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_action' ) ); add_action( 'admin_head', array( $this, 'admin_head_action' ) ); add_action( 'wp_ajax_wpml-ls-save-settings', array( $this, 'save_settings_action' ) ); add_action( 'wp_ajax_wpml-ls-update-preview', array( $this, 'update_preview_action' ) ); } /** * @return string */ public static function get_page_hook() { return WPML_PLUGIN_FOLDER . '/menu/languages.php'; } /** * @param string $hook */ public function admin_enqueue_scripts_action( $hook ) { if ( self::get_page_hook() === $hook ) { $suffix = $this->sitepress->get_wp_api()->constant( 'SCRIPT_DEBUG' ) ? '' : '.min'; wp_register_script( 'wpml-language-switcher-settings', ICL_PLUGIN_URL . '/res/js/language-switchers-settings' . $suffix . '.js', array( 'jquery', 'wp-util', 'jquery-ui-sortable', 'jquery-ui-dialog', 'wp-color-picker', 'wp-pointer' ) ); wp_enqueue_script( 'wpml-language-switcher-settings' ); wp_enqueue_style( 'wp-pointer' ); wp_enqueue_style( 'wp-color-picker' ); wp_register_style( 'wpml-language-switcher-settings', ICL_PLUGIN_URL . '/res/css/wpml-language-switcher-settings.css' ); wp_enqueue_style( 'wpml-language-switcher-settings' ); $js_vars = array( 'nonce' => wp_create_nonce( self::NONCE_NAME ), 'menus' => $this->settings->get_available_menus(), 'sidebars' => $this->settings->get_registered_sidebars(), 'color_schemes' => $this->settings->get_default_color_schemes(), 'strings' => $this->get_javascript_strings(), 'templates' => $this->templates->get_all_templates_data(), ); wp_localize_script( 'wpml-language-switcher-settings', 'wpml_language_switcher_admin', $js_vars ); $this->assets->wp_enqueue_scripts_action(); } } public function admin_head_action() { $this->inline_styles->admin_output(); } public function save_settings_action() { if ( $this->has_valid_nonce() && isset( $_POST['settings'] ) ) { $new_settings = $this->parse_request_settings( 'settings' ); $this->settings->save_settings( $new_settings ); $this->maybe_complete_setup_wizard_step( $new_settings ); $this->sitepress->get_wp_api()->wp_send_json_success( esc_html__( 'Settings saved', 'sitepress' ) ); } else { $this->sitepress->get_wp_api()->wp_send_json_error( esc_html__( "You can't do that!", 'sitepress' ) ); } } /** * @param array $new_settings */ private function maybe_complete_setup_wizard_step( $new_settings ) { if ( isset( $new_settings['submit_setup_wizard'] ) && $new_settings['submit_setup_wizard'] == 1 ) { $setup_instance = wpml_get_setup_instance(); $setup_instance->finish_step3(); } } public function update_preview_action() { $preview = false; if ( $this->has_valid_nonce() ) { $settings = $this->parse_request_settings( 'slot_settings' ); foreach ( array( 'menus', 'sidebars', 'statics' ) as $group ) { if ( isset( $settings[ $group ] ) ) { $slot_slug = key( $settings[ $group ] ); if ( preg_match( '/' . self::SLOT_SLUG_PLACEHOLDER . '/', $slot_slug ) ) { $new_slug = preg_replace( '/' . self::SLOT_SLUG_PLACEHOLDER . '/', '__id__', $slot_slug ); $settings[ $group ][ $new_slug ] = $settings[ $group ][ $slot_slug ]; unset( $settings[ $group ][ $slot_slug ] ); $slot_slug = $new_slug; } $settings[ $group ][ $slot_slug ]['slot_slug'] = $slot_slug; $settings[ $group ][ $slot_slug ]['slot_group'] = $group; $settings = $this->settings->convert_slot_settings_to_objects( $settings ); $slot = $settings[ $group ][ $slot_slug ]; $preview = $this->render->get_preview( $slot ); $this->sitepress->get_wp_api()->wp_send_json_success( $preview ); } } } if ( ! $preview ) { $this->sitepress->get_wp_api()->wp_send_json_error( esc_html__( 'Preview update failed', 'sitepress' ) ); } } /** * @param string $key * * @return array */ private function parse_request_settings( $key ) { $settings = array_key_exists( $key, $_POST ) ? $_POST[ $key ] : null; $settings = Sanitize::string($settings, ENT_NOQUOTES); if ( $settings ) { parse_str( urldecode( $settings ), $settings_array ); return $settings_array; } return []; } /** * @return bool */ private function has_valid_nonce() { $nonce = Sanitize::stringProp( 'nonce', $_POST ); return $nonce ? (bool) wp_verify_nonce( $nonce, self::NONCE_NAME ) : false; } /** * @param array $items * * @return array */ public function languages_navigation_items_filter( $items ) { $item_to_insert = array( '#wpml-ls-settings-form' => esc_html__( 'Language switcher options', 'sitepress' ) ); $insert_position = array_search( '#lang-sec-2', array_keys( $items ), true ) + 1; $items = array_merge( array_slice( $items, 0, $insert_position ), $item_to_insert, array_slice( $items, $insert_position ) ); $items['#wpml_ls_reset'] = esc_html__( 'Reset settings', 'sitepress' ); return $items; } public function after_languages_url_format_action() { $setup_wizard_step = (int) $this->sitepress->get_setting( 'setup_wizard_step' ); $setup_complete = $this->sitepress->get_setting( 'setup_complete' ); $active_languages = $this->sitepress->get_active_languages(); if ( 3 === $setup_wizard_step || ( ! empty( $setup_complete ) && count( $active_languages ) > 1 ) ) { $this->show( self::MAIN_UI_TEMPLATE, $this->get_main_ui_model() ); } } /** * @param string|bool $theme_wpml_config_file */ public function after_wpml_love_action( $theme_wpml_config_file ) { $setup_complete = $this->sitepress->get_setting( 'setup_complete' ); $active_languages = $this->sitepress->get_active_languages(); if ( $setup_complete && count( $active_languages ) > 1 ) { $this->show( self::RESET_UI_TEMPLATE, $this->get_reset_ui_model( $theme_wpml_config_file ) ); } } /** * @param string $type 'sidebars', 'menus', 'statics' * @param string|int $slug_or_id * * @return string */ public function get_button_to_edit_slot( $type, $slug_or_id ) { $slug = $slug_or_id; if ( 'menus' === $type ) { $menu = wp_get_nav_menu_object( $slug_or_id ); $slug = isset( $menu->term_id ) ? $menu->term_id : null; } $slot = $this->settings->get_slot( $type, $slug ); $url = admin_url( 'admin.php?page=' . self::get_page_hook() . '#' . $type . '/' . $slug ); if ( $slot->slug() === $slug ) { $model = array( 'action' => 'edit', 'url' => $url, 'label' => __( 'Customize the language switcher', 'sitepress' ), ); } else { $model = array( 'action' => 'add', 'url' => $url, 'label' => __( 'Add a language switcher', 'sitepress' ), ); } return $this->get_view( self::BUTTON_TEMPLATE, $model ); } protected function init_template_base_dir() { $this->template_paths = array( WPML_PLUGIN_PATH . '/templates/language-switcher-admin-ui/', ); } /** * @return string */ public function get_template() { return self::MAIN_UI_TEMPLATE; } /** * @return array */ private function get_all_previews() { $previews = array(); foreach ( array( 'menus', 'sidebars', 'statics' ) as $slot_group ) { $previews[ $slot_group ] = array(); foreach ( $this->settings->get_setting( $slot_group ) as $slug => $slot_settings ) { $prev = $this->render->get_preview( $slot_settings ); foreach ( array( 'html', 'css', 'js', 'styles' ) as $preview_part ) { $previews[ $slot_group ][ $slug ][ $preview_part ] = $prev[ $preview_part ]; } } } return $previews; } /** * This method is compulsory but should not be used * Use "get_main_ui_model" and "get_reset_ui_model" instead * * @return array */ public function get_model() { return array(); } /** * @return array */ public function get_main_ui_model() { $slot_factory = new WPML_LS_Slot_Factory(); $model = array( 'strings' => array( 'misc' => $this->get_misc_strings(), 'tooltips' => $this->get_tooltip_strings(), 'color_picker' => $this->get_color_picker_strings(), 'options' => $this->get_options_section_strings(), 'menus' => $this->get_menus_section_strings(), 'sidebars' => $this->get_sidebars_section_strings(), 'footer' => $this->get_footer_section_strings(), 'post_translations' => $this->get_post_translations_strings(), 'shortcode_actions' => $this->get_shortcode_actions_strings(), ), 'data' => array( 'templates' => $this->templates->get_templates(), 'menus' => $this->settings->get_available_menus(), 'sidebars' => $this->settings->get_registered_sidebars(), ), 'ordered_languages' => $this->settings->get_ordered_languages(), 'settings' => $this->settings->get_settings_model(), 'settings_slug' => $this->settings->get_settings_base_slug(), 'previews' => $this->get_all_previews(), 'color_schemes' => $this->settings->get_default_color_schemes(), 'notifications' => $this->get_notifications(), 'default_menus_slot' => $slot_factory->get_default_slot_arguments( 'menus' ), 'default_sidebars_slot' => $slot_factory->get_default_slot_arguments( 'sidebars' ), 'setup_complete' => $this->sitepress->get_setting( 'setup_complete' ), 'setup_step_2_nonce_field' => wp_nonce_field( 'setup_got_to_step2_nonce', '_icl_nonce_gts2', true, false ), ); return $model; } /** * @return array */ public function get_misc_strings() { return array( 'no_templates' => __( 'There are no templates available.', 'sitepress' ), 'label_preview' => _x( 'Preview', 'Language switcher preview', 'sitepress' ), 'label_position' => _x( 'Position', 'Language switcher preview', 'sitepress' ), 'label_actions' => _x( 'Actions', 'Language switcher preview', 'sitepress' ), 'label_action' => _x( 'Action', 'Language switcher preview', 'sitepress' ), 'button_save' => __( 'Save', 'sitepress' ), 'button_cancel' => __( 'Cancel', 'sitepress' ), 'title_what_to_include' => __( 'What to include in the language switcher:', 'sitepress' ), 'label_include_flag' => __( 'Flag', 'sitepress' ), 'label_include_native_lang' => __( 'Native language name', 'sitepress' ), 'label_include_display_lang' => __( 'Language name in current language', 'sitepress' ), 'label_include_current_lang' => __( 'Current language', 'sitepress' ), 'label_include_flag_width' => __( 'Width', 'sitepress' ), 'label_include_flag_height' => __( 'Height', 'sitepress' ), 'label_include_flag_width_placeholder' => __( 'auto', 'sitepress' ), 'label_include_flag_height_placeholder' => __( 'auto', 'sitepress' ), 'label_include_flag_width_suffix' => __( 'px', 'sitepress' ), 'label_include_flag_height_suffix' => __( 'px', 'sitepress' ), 'templates_dropdown_label' => __( 'Language switcher style:', 'sitepress' ), 'templates_wpml_group' => __( 'WPML', 'sitepress' ), 'templates_custom_group' => __( 'Custom', 'sitepress' ), 'title_action_edit' => __( 'Edit language switcher', 'sitepress' ), 'title_action_delete' => __( 'Delete language switcher', 'sitepress' ), 'button_back' => __( 'Back', 'sitepress' ), 'button_next' => __( 'Next', 'sitepress' ), ); } /** * @return array */ public function get_tooltip_strings() { return array( 'languages_order' => array( 'text' => __( 'This is the order in which the languages will be displayed in the language switcher.', 'sitepress' ), ), 'languages_without_translation' => array( 'text' => __( 'Some content may not be translated to all languages. Choose what should appear in the language switcher when translation is missing.', 'sitepress' ), ), 'preserve_url_arguments' => array( 'text' => __( 'Add a comma-separated list of URL arguments that you want WPML to pass when switching languages.', 'sitepress' ), 'link' => array( 'text' => __( 'Preserving URL arguments', 'sitepress' ), 'url' => 'https://wpml.org/documentation/getting-started-guide/language-setup/language-switcher-options/preserve-url-arguments-when-switching-languages/?utm_source=plugin&utm_medium=gui&utm_campaign=languages', 'target' => '_blank', ), ), 'additional_css' => array( 'text' => __( 'Enter CSS to add to the page. This is useful when you want to add styling to the language switcher, without having to edit the CSS file on the server.', 'sitepress' ), 'link' => array( 'text' => __( 'Styling the language switcher with additional CSS', 'sitepress' ), 'url' => 'https://wpml.org/documentation/getting-started-guide/language-setup/language-switcher-options/how-to-fix-styling-and-css-issues-for-the-language-switchers/?utm_source=plugin&utm_medium=gui&utm_campaign=languages', 'target' => '_blank', ), ), 'section_post_translations' => array( 'text' => __( 'You can display links to translation of posts before the post and after it. These links look like "This post is also available in..."', 'sitepress' ), ), 'add_menu_all_assigned' => array( 'text' => __( 'The button is disabled because all existing menus have language switchers. You can edit the settings of the existing language switchers.', 'sitepress' ), ), 'add_menu_no_menu' => array( 'text' => __( 'The button is disabled because there are no menus in the site. Add a menu and you can later enable a language switcher in it.', 'sitepress' ), ), 'add_sidebar_all_assigned' => array( 'text' => __( 'The button is disabled because all existing widget areas have language switchers. You can edit the settings of the existing language switchers.', 'sitepress' ), ), 'add_sidebar_no_sidebar' => array( 'text' => __( 'The button is disabled because there are no registered widget areas in the site.', 'sitepress' ), ), 'what_to_include' => array( 'text' => __( 'Elements to include in the language switcher.', 'sitepress' ), ), 'available_menus' => array( 'text' => __( 'Select the menus, in which to display the language switcher.', 'sitepress' ), ), 'available_sidebars' => array( 'text' => __( 'Select the widget area where to include the language switcher.', 'sitepress' ), ), 'available_templates' => array( 'text' => __( 'Select the style of the language switcher.', 'sitepress' ), ), 'menu_style_type' => array( 'text' => __( 'Select how to display the language switcher in the menu. Choose "List of languages" to display all the items at the same level or "Dropdown" to display the current language as parent and other languages as children.', 'sitepress' ), ), 'menu_position' => array( 'text' => __( 'Select the position to display the language switcher in the menu.', 'sitepress' ), ), 'widget_title' => array( 'text' => __( 'Enter the title of the widget or leave empty for no title.', 'sitepress' ), ), 'post_translation_position' => array( 'text' => __( 'Select the position to display the post translations links.', 'sitepress' ), ), 'alternative_languages_text' => array( 'text' => __( 'This text appears before the list of languages. Your text needs to include the string %s which is a placeholder for the actual links.', 'sitepress' ), ), 'backwards_compatibility' => array( 'text' => __( "Since WPML 3.6.0, the language switchers are not using CSS IDs and the CSS classes have changed. This was required to fix some bugs and match the latest standards. If your theme or your custom CSS is not relying on these old selectors, it's recommended to skip the backwards compatibility. However, it's still possible to re-activate this option later.", 'sitepress' ), ), 'show_in_footer' => array( 'text' => __( "You can display a language switcher in the site's footer. You can customize and style it here.", 'sitepress' ), ), ); } /** * @return array */ public function get_options_section_strings() { return array( 'section_title' => __( 'Language switcher options', 'sitepress' ), 'section_description' => __( 'All language switchers in your site are affected by the settings in this section.', 'sitepress' ), 'label_language_order' => __( 'Order of languages', 'sitepress' ), 'tip_drag_languages' => __( 'Drag and drop the languages to change their order', 'sitepress' ), 'label_languages_with_no_translations' => __( 'How to handle languages without translation', 'sitepress' ), 'option_skip_link' => __( 'Skip language', 'sitepress' ), 'option_link_home' => __( 'Link to home of language for missing translations', 'sitepress' ), 'label_preserve_url_args' => __( 'Preserve URL arguments', 'sitepress' ), 'label_additional_css' => __( 'Additional CSS', 'sitepress' ), 'label_migrated_toggle' => __( 'Backwards compatibility', 'sitepress' ), 'label_skip_backwards_compatibility' => __( 'Skip backwards compatibility', 'sitepress' ), ); } /** * @return array */ public function get_menus_section_strings() { return array( 'section_title' => __( 'Menu language switcher', 'sitepress' ), 'add_button_label' => __( 'Add a new language switcher to a menu', 'sitepress' ), 'select_label' => __( 'Menu', 'sitepress' ), 'select_option_choose' => __( 'Choose a menu', 'sitepress' ), 'position_label' => __( 'Position:', 'sitepress' ), 'position_first_item' => __( 'First menu item', 'sitepress' ), 'position_last_item' => __( 'Last menu item', 'sitepress' ), 'is_hierarchical_label' => __( 'Language menu items style:', 'sitepress' ), 'flat' => __( 'List of languages', 'sitepress' ), 'flat_desc' => __( 'good for menus that display items as a list', 'sitepress' ), 'hierarchical' => __( 'Dropdown', 'sitepress' ), 'hierarchical_desc' => __( 'good for menus that support drop-downs', 'sitepress' ), 'dialog_title' => __( 'Edit Menu Language Switcher', 'sitepress' ), 'dialog_title_new' => __( 'New Menu Language Switcher', 'sitepress' ), ); } /** * @return array */ public function get_sidebars_section_strings() { return array( 'section_title' => __( 'Widget language switcher', 'sitepress' ), 'add_button_label' => __( 'Add a new language switcher to a widget area', 'sitepress' ), 'select_label' => __( 'Widget area', 'sitepress' ), 'select_option_choose' => __( 'Choose a widget area', 'sitepress' ), 'label_widget_title' => __( 'Widget title:', 'sitepress' ), 'dialog_title' => __( 'Edit Widget Area Language Switcher', 'sitepress' ), 'dialog_title_new' => __( 'New Widget Area language switcher', 'sitepress' ), ); } /** * @return array */ public function get_footer_section_strings() { return array( 'section_title' => __( 'Footer language switcher', 'sitepress' ), 'show' => __( 'Show language switcher in footer', 'sitepress' ), 'dialog_title' => __( 'Edit Footer Language Switcher', 'sitepress' ), ); } /** * @return array */ public function get_post_translations_strings() { return array( 'section_title' => __( 'Links to translation of posts', 'sitepress' ), 'show' => __( 'Show links above or below posts, offering them in other languages', 'sitepress' ), 'position_label' => __( 'Position of link(s):', 'sitepress' ), 'position_above' => __( 'Above post', 'sitepress' ), 'position_below' => __( 'Below post', 'sitepress' ), 'label_alternative_languages_text' => __( 'Text for alternative languages for posts:', 'sitepress' ), 'default_alternative_languages_text' => __( 'This post is also available in: %s', 'sitepress' ), 'dialog_title' => __( 'Edit Post Translations Language Switcher', 'sitepress' ), ); } /** * @return array */ public function get_shortcode_actions_strings() { $description_link_text = _x( "insert WPML's switchers in custom locations", 'Custom languuage switcher description: external link text', 'sitepress' ); $description_link_url = 'https://wpml.org/documentation/getting-started-guide/language-setup/language-switcher-options/adding-language-switchers-using-php-and-shortcodes/?utm_source=plugin&utm_medium=gui&utm_campaign=languages'; $description_link = '<a href="' . $description_link_url . '" target="_blank">' . $description_link_text . '</a>'; $description = _x( 'Need more options? See how you can %s.', 'Custom languuage switcher description: text', 'sitepress' ); return array( 'section_title' => __( 'Custom language switchers', 'sitepress' ), 'section_description' => sprintf( $description, $description_link ), 'show' => __( 'Enable', 'sitepress' ), 'customize_button_label' => __( 'Customize', 'sitepress' ), 'dialog_title' => __( 'Edit Shortcode Actions Language Switcher', 'sitepress' ), ); } /** * @return array */ public function get_color_picker_strings() { return array( 'panel_title' => __( 'Language switcher colors', 'sitepress' ), 'label_color_preset' => __( 'Color themes:', 'sitepress' ), 'select_option_choose' => __( 'Select a preset', 'sitepress' ), 'label_normal_scheme' => __( 'Normal', 'sitepress' ), 'label_hover_scheme' => __( 'Hover', 'sitepress' ), 'background' => __( 'Background', 'sitepress' ), 'border' => __( 'Border', 'sitepress' ), 'font_current' => __( 'Current language font color', 'sitepress' ), 'background_current' => __( 'Current language background color', 'sitepress' ), 'font_other' => __( 'Other language font color', 'sitepress' ), 'background_other' => __( 'Other language background color', 'sitepress' ), ); } /** * @return array */ public function get_javascript_strings() { return array( 'confirmation_item_remove' => esc_html__( 'Do you really want to remove this item?', 'sitepress' ), 'leave_text_box_to_save' => esc_html__( 'Leave the text box to auto-save', 'sitepress' ), ); } /** * @param string|bool $theme_wpml_config_file * * @return array */ public function get_reset_ui_model( $theme_wpml_config_file ) { $reset_locations = esc_html__( 'in options, menus, widgets, footer and shortcode', 'sitepress' ); $model = array( 'title' => __( 'Reset settings', 'sitepress' ), 'description' => sprintf( esc_html__( 'This will change the settings of your language switchers %s to their defaults as set by the theme. Please note that some switchers may be removed and others may be added.', 'sitepress' ), '<strong>(' . $reset_locations . ')</strong>' ), 'theme_config_file' => $theme_wpml_config_file, 'explanation_text' => sprintf( esc_html__( '* Your theme has a %s file, which sets the default values for WPML.', 'sitepress' ), '<strong title="' . esc_attr( (string) $theme_wpml_config_file ) . '">wpml-config.xml</strong>' ), 'confirmation_message' => __( 'Are you sure you want to reset to the default settings?', 'sitepress' ), 'restore_page_url' => admin_url( 'admin.php?page=' . self::get_page_hook() . '&restore_ls_settings=1&nonce=' . wp_create_nonce( self::RESET_NONCE_NAME ) ), 'restore_button_label' => __( 'Restore default', 'sitepress' ), ); return $model; } /** * @return array */ private function get_notifications() { $notifications = array(); if ( $this->sitepress->get_wp_api()->constant( 'ICL_DONT_LOAD_LANGUAGE_SELECTOR_CSS' ) ) { $notifications['css_not_loaded'] = sprintf( __( "%s is defined in your theme. The language switcher can only be customized using the theme's CSS.", 'sitepress' ), 'ICL_DONT_LOAD_LANGUAGE_SELECTOR_CSS' ); } return $notifications; } } language-switcher/class-wpml-ls-settings-sanitize.php 0000755 00000003456 14720342453 0017065 0 ustar 00 <?php class WPML_LS_Settings_Sanitize { /** * @return array */ private function get_global_settings_keys() { return array( 'migrated' => array( 'type' => 'int', 'force_missing_to' => 1, ), 'converted_menu_ids' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'languages_order' => array( 'type' => 'array' ), 'link_empty' => array( 'type' => 'int' ), 'additional_css' => array( 'type' => 'string' ), 'copy_parameters' => array( 'type' => 'string' ), // Slot groups 'menus' => array( 'type' => 'array', 'force_missing_to' => array(), ), 'sidebars' => array( 'type' => 'array', 'force_missing_to' => array(), ), 'statics' => array( 'type' => 'array', 'force_missing_to' => array(), ), ); } /** * @param array $s * @return array */ public function sanitize_all_settings( $s ) { $s = $this->sanitize_settings( $s, $this->get_global_settings_keys() ); return $s; } /** * @param array $settings_slice * @param array $allowed_keys * * @return array */ private function sanitize_settings( $settings_slice, $allowed_keys ) { $ret = array(); foreach ( $allowed_keys as $key => $expected ) { if ( array_key_exists( $key, $settings_slice ) ) { switch ( $expected['type'] ) { case 'string': $ret[ $key ] = (string) $settings_slice[ $key ]; break; case 'int': $ret[ $key ] = (int) $settings_slice[ $key ]; break; case 'array': $ret[ $key ] = (array) $settings_slice[ $key ]; break; } } elseif ( array_key_exists( 'force_missing_to', $expected ) ) { $ret[ $key ] = $expected['force_missing_to']; } } return $ret; } } language-switcher/public-api/class-wpml-ls-actions.php 0000755 00000002425 14720342453 0017061 0 ustar 00 <?php class WPML_LS_Actions extends WPML_LS_Public_API { public function init_hooks() { if ( $this->sitepress->get_setting( 'setup_complete' ) ) { add_action( 'wpml_language_switcher', array( $this, 'callback' ), 10, 2 ); /** * Backward compatibility * * @deprecated see 'wpml_language_switcher' */ add_action( 'icl_language_selector', array( $this, 'callback' ) ); add_action( 'wpml_add_language_selector', array( $this, 'callback' ) ); add_action( 'wpml_footer_language_selector', array( $this, 'callback' ) ); } } /** * @param array $args * @param string|null $twig_template */ public function callback( $args, $twig_template = null ) { if ( '' === $args ) { $args = array(); } $args = $this->parse_legacy_actions( $args ); $args = $this->convert_shortcode_args_aliases( $args ); echo $this->render( $args, $twig_template ); } /** * @param array $args * * @return array */ private function parse_legacy_actions( $args ) { $current_filter = current_filter(); if ( in_array( $current_filter, array( 'icl_language_selector', 'wpml_add_language_selector' ) ) ) { $args['type'] = 'custom'; } elseif ( 'wpml_footer_language_selector' === $current_filter ) { $args['type'] = 'footer'; } return $args; } } language-switcher/public-api/class-wpml-ls-shortcodes.php 0000755 00000002227 14720342453 0017576 0 ustar 00 <?php /** * Class WPML_LS_Shortcodes */ class WPML_LS_Shortcodes extends WPML_LS_Public_API { public function init_hooks() { if ( $this->sitepress->get_setting( 'setup_complete' ) ) { add_shortcode( 'wpml_language_switcher', array( $this, 'callback' ) ); // Backward compatibility add_shortcode( 'wpml_language_selector_widget', array( $this, 'callback' ) ); add_shortcode( 'wpml_language_selector_footer', array( $this, 'callback' ) ); } } /** * @param array|string $args * @param string|null $content * @param string $tag * * @return string */ public function callback( $args, $content = null, $tag = '' ) { $args = (array) $args; $args = $this->parse_legacy_shortcodes( $args, $tag ); $args = $this->convert_shortcode_args_aliases( $args ); return $this->render( $args, $content ); } /** * @param array $args * @param string $tag * * @return mixed */ private function parse_legacy_shortcodes( $args, $tag ) { if ( 'wpml_language_selector_widget' === $tag ) { $args['type'] = 'custom'; } elseif ( 'wpml_language_selector_footer' === $tag ) { $args['type'] = 'footer'; } return $args; } } language-switcher/public-api/class-wpml-ls-public-api.php 0000755 00000005720 14720342453 0017447 0 ustar 00 <?php /** * Class WPML_LS_Public_API */ class WPML_LS_Public_API { /** @var WPML_LS_Settings $settings */ private $settings; /** @var WPML_LS_Render $render */ private $render; /** @var SitePress $sitepress */ protected $sitepress; /** @var WPML_LS_Slot_Factory */ private $slot_factory; /** * WPML_LS_Public_API constructor. * * @param WPML_LS_Settings $settings * @param WPML_LS_Render $render * @param SitePress $sitepress * @param WPML_LS_Slot_Factory $slot_factory */ public function __construct( WPML_LS_Settings $settings, WPML_LS_Render $render, SitePress $sitepress, WPML_LS_Slot_Factory $slot_factory = null ) { $this->settings = $settings; $this->render = $render; $this->sitepress = $sitepress; $this->slot_factory = $slot_factory; } /** * @param array $args * @param string|null $twig_template * * @return string */ protected function render( $args, $twig_template = null ) { $defaults_slot_args = $this->get_default_slot_args( $args ); $slot_args = array_merge( $defaults_slot_args, $args ); $slot = $this->get_slot_factory()->get_slot( $slot_args ); $slot->set( 'show', 1 ); $slot->set( 'template_string', $twig_template ); if ( $slot->is_post_translations() ) { $output = $this->render->post_translations_label( $slot ); } else { $output = $this->render->render( $slot ); } return $output; } /** * @param array $args * * @return array */ private function get_default_slot_args( $args ) { $type = 'custom'; if ( isset( $args['type'] ) ) { $type = $args['type']; } switch ( $type ) { case 'footer': $default_slot = $this->settings->get_slot( 'statics', 'footer' ); break; case 'post_translations': $default_slot = $this->settings->get_slot( 'statics', 'post_translations' ); break; case 'widget': $default_slot = $this->get_slot_factory()->get_default_slot( 'sidebars' ); break; case 'custom': default: $default_slot = $this->settings->get_slot( 'statics', 'shortcode_actions' ); break; } return $default_slot->get_model(); } /** * @param array $args * * @return array */ protected function convert_shortcode_args_aliases( $args ) { $aliases_map = self::get_argument_aliases(); foreach ( $aliases_map as $alias => $key ) { if ( array_key_exists( $alias, $args ) ) { $args[ $key ] = $args[ $alias ]; unset( $args[ $alias ] ); } } return $args; } /** * @return array */ public static function get_argument_aliases() { return array( 'flags' => 'display_flags', 'link_current' => 'display_link_for_current_lang', 'native' => 'display_names_in_native_lang', 'translated' => 'display_names_in_current_lang', ); } /** * @return WPML_LS_Slot_Factory */ private function get_slot_factory() { if ( ! $this->slot_factory ) { $this->slot_factory = new WPML_LS_Slot_Factory(); } return $this->slot_factory; } } language-switcher/class-wpml-ls-settings.php 0000755 00000053755 14720342453 0015250 0 ustar 00 <?php use WPML\API\Sanitize; use WPML\FP\Obj; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Maybe; use function WPML\FP\partial; use function WPML\FP\invoke; class WPML_LS_Settings { const SETTINGS_SLUG = 'wpml_language_switcher'; const DEFAULT_FLAG_WIDTH = 18; const DEFAULT_FLAG_HEIGHT = 12; /** @var SitePress $sitepress */ protected $sitepress; /* @var array $settings */ private $settings; /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Slot_Factory $slot_factory */ private $slot_factory; /* @var WPML_LS_Migration $migration */ private $migration; /* @var WPML_LS_Settings_Sanitize $sanitizer */ private $sanitizer; /* @var WPML_LS_Settings_Strings $strings */ private $strings; /* @var WPML_LS_Settings_Color_Presets $color_presets */ private $color_presets; /** * WPML_LS_Settings constructor. * * @param WPML_LS_Templates $templates * @param SitePress $sitepress * @param WPML_LS_Slot_Factory $slot_factory * @param WPML_LS_Migration $migration */ public function __construct( $templates, $sitepress, $slot_factory, $migration = null ) { $this->templates = $templates; $this->sitepress = &$sitepress; $this->slot_factory = $slot_factory; $this->migration = $migration; $this->sanitizer = new WPML_LS_Settings_Sanitize(); $this->strings = new WPML_LS_Settings_Strings( $this->slot_factory ); $this->color_presets = new WPML_LS_Settings_Color_Presets(); } public function init_hooks() { add_filter( 'widget_update_callback', array( $this, 'widget_update_callback_filter' ), 10, 4 ); add_action( 'update_option_sidebars_widgets', array( $this, 'update_option_sidebars_widgets_action' ), 10, 2 ); add_action( 'wpml_reset_ls_settings', array( $this, 'reset_ls_settings_action' ) ); } /** * @param array $ls_config */ public function reset_ls_settings_action( array $ls_config ) { $restore_ls_settings = ( isset( $_GET['restore_ls_settings'] ) && 1 == $_GET['restore_ls_settings'] ); $has_valid_nonce = isset( $_GET['nonce'] ) && wp_create_nonce( WPML_LS_Admin_UI::RESET_NONCE_NAME ) === $_GET['nonce']; $restore_ls_settings = $restore_ls_settings && $has_valid_nonce; if ( ! $this->sitepress->get_setting( 'language_selector_initialized' ) || $restore_ls_settings ) { delete_option( self::SETTINGS_SLUG ); $this->settings = null; if ( ! empty( $ls_config ) ) { $this->sitepress->set_setting( 'language_selector_initialized', 1 ); $reset_settings = $this->read_config_settings_recursive( $ls_config['key'] ); $converted_settings = $this->migration()->get_converted_settings( $reset_settings ); if ( isset( $converted_settings['migrated'] ) && 1 === $converted_settings['migrated'] ) { $reset_settings = $converted_settings; } else { $reset_settings['migrated'] = 0; } $this->save_settings( $reset_settings ); } else { $this->maybe_init_settings(); } if ( $this->sitepress->get_setting( 'setup_complete' ) && $restore_ls_settings ) { $this->sitepress->get_wp_api()->wp_safe_redirect( $this->get_restore_redirect_url() ); } } } /** * @return string|void */ public function get_restore_redirect_url() { return admin_url( 'admin.php?page=' . WPML_LS_Admin_UI::get_page_hook() . '&ls_reset=default' ); } /** * @param array $arr * * @return array */ public function read_config_settings_recursive( $arr ) { $ret = array(); if ( is_array( $arr ) ) { foreach ( $arr as $v ) { if ( isset( $v['key'] ) && is_array( $v['key'] ) ) { $partial = ! is_numeric( key( $v['key'] ) ) ? array( $v['key'] ) : $v['key']; $ret[ $v['attr']['name'] ] = $this->read_config_settings_recursive( $partial ); } else { $ret[ $v['attr']['name'] ] = $v['value']; } } } return $ret; } /** * @return string */ public function get_settings_base_slug() { return self::SETTINGS_SLUG; } /** * @return array */ public function get_settings() { $this->maybe_init_settings(); return $this->settings; } /** * @return array */ public function get_settings_model() { $settings = $this->get_settings(); foreach ( array( 'menus', 'sidebars', 'statics' ) as $group ) { $slots = $this->get_setting( $group ); foreach ( $slots as $slot_slug => $slot_setting ) { $slot_vars = $slot_setting->get_model(); $slot = new \WPML_LS_Slot( $slot_vars ); $settings[ $group ][ $slot_slug ] = $slot->get_model(); } } return $settings; } /** * @return array */ private function get_default_settings() { $core_templates = $this->get_core_templates(); $footer_slot = array( 'show' => 0, 'display_names_in_current_lang' => 1, 'template' => $core_templates['list-horizontal'], 'slot_group' => 'statics', 'slot_slug' => 'footer', ); $post_translations = array( 'show' => 0, 'display_names_in_current_lang' => 1, 'display_before_content' => 1, 'availability_text' => esc_html__( 'This post is also available in: %s', 'sitepress' ), 'template' => $core_templates['post-translations'], 'slot_group' => 'statics', 'slot_slug' => 'post_translations', ); $shortcode_actions = array( 'show' => 0, 'display_names_in_current_lang' => 1, 'template' => $core_templates['list-horizontal'], 'slot_group' => 'statics', 'slot_slug' => 'shortcode_actions', ); $getMergedArgs = function ( $args ) { $shared = [ 'include_flag_width' => \WPML_LS_Settings::DEFAULT_FLAG_WIDTH, 'include_flag_height' => \WPML_LS_Settings::DEFAULT_FLAG_HEIGHT, ]; return array_merge( $shared, $args ); }; return array( 'menus' => array(), 'sidebars' => array(), 'statics' => array( 'footer' => $this->slot_factory->get_slot( $getMergedArgs( $footer_slot ) ), 'post_translations' => $this->slot_factory->get_slot( $getMergedArgs( $post_translations ) ), 'shortcode_actions' => $this->slot_factory->get_slot( $getMergedArgs( $shortcode_actions ) ), ), 'additional_css' => '', ); } /** * @return array */ private function get_shared_settings_keys() { return array( // SitePress::settings => WPML_LS_Settings::settings 'languages_order' => 'languages_order', 'icl_lso_link_empty' => 'link_empty', 'icl_lang_sel_copy_parameters' => 'copy_parameters', ); } private function init_shared_settings() { foreach ( $this->get_shared_settings_keys() as $sp_key => $ls_key ) { $this->settings[ $ls_key ] = $this->sitepress->get_setting( $sp_key ); } } /** * @param array<string,mixed> $new_settings */ private function persist_shared_settings( $new_settings ) { foreach ( $this->get_shared_settings_keys() as $sp_key => $ls_key ) { if ( array_key_exists( $ls_key, $new_settings ) ) { $this->sitepress->set_setting( $sp_key, $new_settings[ $ls_key ] ); } } $this->sitepress->save_settings(); } private function maybe_init_settings() { if ( null === $this->settings ) { $this->settings = get_option( self::SETTINGS_SLUG ); $this->handle_corrupted_settings(); if ( ! $this->settings || ! isset( $this->settings['migrated'] ) ) { $this->settings = $this->migration()->get_converted_settings( $this->sitepress->get_settings() ); $default_settings = $this->get_default_settings(); $this->settings = wp_parse_args( $this->settings, $default_settings ); $this->save_settings( $this->settings ); } if ( ! isset( $this->settings['converted_menu_ids'] ) ) { $this->settings = $this->migration()->convert_menu_ids( $this->settings ); $this->persist_settings( $this->settings ); } $this->init_shared_settings(); if ( ! $this->sitepress->get_wp_api()->is_admin() ) { $this->settings = $this->strings->translate_all( $this->settings ); } } } private function handle_corrupted_settings() { $corrupted_settings = []; foreach ( array( 'menus', 'sidebars', 'statics' ) as $group ) { if ( ! isset( $this->settings[ $group ] ) ) { continue; } foreach ( $this->settings[ $group ] as $key => $slot ) { if ( $slot instanceof __PHP_Incomplete_Class ) { unset( $this->settings[ $group ][ $key ] ); $corrupted_settings[] = ucfirst( $group ) . ' - ' . $key; } } } if ( $corrupted_settings ) { $this->add_corrupted_settings_notice( $corrupted_settings ); } } /** * @param array $corrupted_settings */ private function add_corrupted_settings_notice( $corrupted_settings ) { $message = __( 'Some WPML Language Switcher settings were reinitialized because they were corrupted. Please re-configure them:', 'sitepress' ); $message .= ' ' . join( ', ', $corrupted_settings ) . '.'; $admin_notices = wpml_get_admin_notices(); $notice = $admin_notices->create_notice( 'corrupted_settings', $message, 'wpml-ls-settings' ); $notice->set_css_class_types( 'error' ); $notice->set_dismissible( true ); $notice->set_flash( true ); $admin_notices->add_notice( $notice ); } /** * @return array */ public function get_registered_sidebars() { global $wp_registered_sidebars; return is_array( $wp_registered_sidebars ) ? $wp_registered_sidebars : array(); } /** * @return array */ public function get_available_menus() { $has_term_filter = remove_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1 ); $ret = array(); $default_lang = $this->sitepress->get_default_language(); $menus = wp_get_nav_menus( array( 'orderby' => 'name' ) ); if ( $menus ) { foreach ( $menus as $menu ) { $menu_details = $this->sitepress->get_element_language_details( $menu->term_taxonomy_id, 'tax_nav_menu' ); if ( isset( $menu_details->language_code ) && $menu_details->language_code === $default_lang ) { $ret[ $menu->term_id ] = $menu; } } } if ( $has_term_filter ) { add_filter( 'get_term', array( $this->sitepress, 'get_term_adjust_id' ), 1, 1 ); } return $ret; } /** * @param array<string,mixed> $new_settings */ private function persist_settings( $new_settings ) { $this->persist_shared_settings( $new_settings ); if ( null !== $new_settings && count( $new_settings ) > 0 ) { update_option( self::SETTINGS_SLUG, $new_settings ); } } /** * @param string $slot_group * @param string|int $slot_slug * * @return WPML_LS_Slot */ public function get_slot( $slot_group, $slot_slug ) { $void_settings = array( 'show' => 0 ); $groups = $this->get_settings(); $slot = isset( $groups[ $slot_group ][ $slot_slug ] ) ? $groups[ $slot_group ][ $slot_slug ] : $this->slot_factory->get_slot( $void_settings ); return $slot; } /** * @param int $term_id * * @return WPML_LS_Slot */ public function get_menu_settings_from_id( $term_id ) { $menu_slot = $this->get_slot( 'menus', $term_id ); if ( $menu_slot->is_enabled() ) { return $menu_slot; } if ( $term_id > 0 ) { $menu_element = new WPML_Menu_Element( $term_id, $this->sitepress ); $default_lang = $this->sitepress->get_default_language(); $source_language_code = $menu_element->get_source_language_code(); $findSettingsInLang = function ( $lang ) use ( $menu_element ) { return Maybe::of( $lang ) ->map( [ $menu_element, 'get_translation' ] ) ->map( invoke( 'get_wp_object' ) ) ->reject( 'is_wp_error' ) ->map( Obj::prop( 'term_id' ) ) ->map( partial( [ $this, 'get_slot' ], 'menus' ) ) ->filter( invoke( 'is_enabled' ) ) ->getOrElse( null ); }; if ( $menu_element->get_language_code() !== $default_lang ) { $menu_slot = $findSettingsInLang( $default_lang ) ?: $menu_slot; } if ( $source_language_code && $source_language_code !== $default_lang && $menu_element->get_language_code() !== $source_language_code ) { $menu_slot = $findSettingsInLang( $source_language_code ) ?: $menu_slot; } } return $menu_slot; } /** * @return array */ public function get_active_slots() { $ret = array(); foreach ( array( 'menus', 'sidebars', 'statics' ) as $group ) { $slots = $this->get_setting( $group ); foreach ( $slots as $slot_slug => $slot ) { /* @var WPML_LS_Slot $slot */ if ( $slot->is_enabled() ) { $ret[] = $slot; } } } return $ret; } /** * @return array */ public function get_active_templates() { $ret = array(); $active_slots = $this->get_active_slots(); foreach ( $active_slots as $slot ) { /* @var WPML_LS_Slot $slot */ if ( $slot->is_enabled() ) { $ret[] = $slot->template(); } } return array_unique( $ret ); } /** * @param string $key * * @return mixed string|array|null */ public function get_setting( $key ) { $this->maybe_init_settings(); return Obj::propOr( null, $key, $this->settings ); } /** * @param array $new_settings */ public function save_settings( $new_settings ) { $this->maybe_init_settings(); $new_settings = $this->sanitizer->sanitize_all_settings( $new_settings ); $new_settings['menus'] = array_intersect_key( $new_settings['menus'], $this->get_available_menus() ); $new_settings['sidebars'] = array_intersect_key( $new_settings['sidebars'], $this->get_registered_sidebars() ); $new_settings = $this->convert_slot_settings_to_objects( $new_settings ); if ( $this->sitepress->is_setup_complete() ) { $this->strings->register_all( $new_settings, $this->settings ); } $this->synchronize_widget_instances( $new_settings['sidebars'] ); $this->persist_settings( $new_settings ); $this->settings = $new_settings; } /** * @param array $settings * * @return array */ public function convert_slot_settings_to_objects( array $settings ) { foreach ( array( 'menus', 'sidebars', 'statics' ) as $group ) { if ( isset( $settings[ $group ] ) ) { foreach ( $settings[ $group ] as $slot_slug => $slot_settings ) { if ( is_array( $slot_settings ) ) { $slot_settings['slot_slug'] = $slot_slug; $slot_settings['slot_group'] = $group; } $settings[ $group ][ $slot_slug ] = $this->slot_factory->get_slot( $slot_settings ); } } } return $settings; } /** * @param array<string,\WPML_LS_Slot> $sidebar_slots */ private function synchronize_widget_instances( $sidebar_slots ) { require_once ABSPATH . '/wp-admin/includes/widgets.php'; $wpml_ls_widget = new WPML_LS_Widget(); $sidebars_widgets = $this->get_refreshed_sidebars_widgets(); if ( is_array( $sidebars_widgets ) ) { foreach ( $sidebars_widgets as $sidebar => $widgets ) { if ( 'wp_inactive_widgets' === $sidebar ) { continue; } $found = false; if ( is_array( $widgets ) ) { foreach ( $widgets as $key => $widget_id ) { if ( strpos( $widget_id, WPML_LS_Widget::SLUG ) === 0 ) { if ( $found ) { // Only synchronize the first LS widget instance per sidebar unset( $sidebars_widgets[ $sidebar ][ $key ] ); continue; } $found = true; if ( ! isset( $sidebar_slots[ $sidebar ] ) ) { $wpml_ls_widget->delete_instance( $widget_id ); unset( $sidebars_widgets[ $sidebar ][ $key ] ); } else { $wpml_ls_widget->update_instance( $sidebar_slots[ $sidebar ], $widget_id ); } } } } if ( ! $found ) { if ( isset( $sidebar_slots[ $sidebar ] ) ) { $new_instance_id = $wpml_ls_widget->create_new_instance( $sidebar_slots[ $sidebar ] ); $sidebars_widgets[ $sidebar ] = is_array( $sidebars_widgets[ $sidebar ] ) ? $sidebars_widgets[ $sidebar ] : array(); array_unshift( $sidebars_widgets[ $sidebar ], $new_instance_id ); } } } } $is_hooked = has_action( 'update_option_sidebars_widgets', array( $this, 'update_option_sidebars_widgets_action' ) ); if ( $is_hooked ) { remove_action( 'update_option_sidebars_widgets', array( $this, 'update_option_sidebars_widgets_action' ), 10 ); } wp_set_sidebars_widgets( $sidebars_widgets ); if ( $is_hooked ) { add_action( 'update_option_sidebars_widgets', array( $this, 'update_option_sidebars_widgets_action' ), 10, 2 ); } } /** @return array */ private function get_refreshed_sidebars_widgets() { global $_wp_sidebars_widgets; // Clear cached value used in wp_get_sidebars_widgets(). $_wp_sidebars_widgets = null; return wp_get_sidebars_widgets(); } /** * @param array $old_sidebars * @param array $sidebars */ public function update_option_sidebars_widgets_action( $old_sidebars, $sidebars ) { unset( $sidebars['wp_inactive_widgets'], $sidebars['array_version'] ); $this->maybe_init_settings(); if ( is_array( $sidebars ) ) { foreach ( $sidebars as $sidebar_slug => $widgets ) { $this->synchronize_sidebar_settings( $sidebar_slug, $widgets ); } } $this->save_settings( $this->settings ); } /** * @param string $sidebar_slug * @param array $widgets */ private function synchronize_sidebar_settings( $sidebar_slug, $widgets ) { $this->settings['sidebars'][ $sidebar_slug ] = isset( $this->settings['sidebars'][ $sidebar_slug ] ) ? $this->settings['sidebars'][ $sidebar_slug ] : array(); $widget_id = $this->find_first_ls_widget( $widgets ); if ( $widget_id === false ) { unset( $this->settings['sidebars'][ $sidebar_slug ] ); } else { $instance_number = str_replace( WPML_LS_Widget::SLUG . '-', '', $widget_id ); $widget_class_options = get_option( 'widget_' . WPML_LS_Widget::SLUG ); $widget_instance_options = isset( $widget_class_options[ $instance_number ] ) ? $widget_class_options[ $instance_number ] : array(); $this->settings['sidebars'][ $sidebar_slug ] = $this->get_slot_from_widget_instance( $widget_instance_options ); } } /** * @param array $instance * @param array $new_instance * @param array|null $old_instance * @param WP_Widget $widget * * @return array */ public function widget_update_callback_filter( array $instance, array $new_instance, $old_instance, WP_Widget $widget ) { if ( strpos( $widget->id_base, WPML_LS_Widget::SLUG ) === 0 ) { $sidebar_id = Sanitize::stringProp( 'sidebar', $_POST ); $sidebar_id = $sidebar_id ? $sidebar_id : $this->find_parent_sidebar( $widget->id ); if ( $sidebar_id ) { $this->maybe_init_settings(); if ( isset( $this->settings['sidebars'][ $sidebar_id ] ) ) { $this->settings['sidebars'][ $sidebar_id ] = $this->get_slot_from_widget_instance( $instance ); } $this->save_settings( $this->settings ); } } return $instance; } /** * @param array $widget_instance * * @return WPML_LS_Slot */ private function get_slot_from_widget_instance( $widget_instance ) { $slot = isset( $widget_instance['slot'] ) ? $widget_instance['slot'] : array(); if ( ! is_a( $slot, 'WPML_LS_Sidebar_Slot' ) ) { $slot = $this->slot_factory->get_default_slot_arguments( 'sidebars' ); $slot = $this->slot_factory->get_slot( $slot ); } return $slot; } /** * Find in which sidebar a language switcher instance is set * * @param mixed $widget_to_find * * @return bool|string */ private function find_parent_sidebar( $widget_to_find ) { $sidebars_widgets = wp_get_sidebars_widgets(); if ( is_array( $sidebars_widgets ) ) { foreach ( $sidebars_widgets as $sidebar_id => $widgets ) { if ( is_array( $widgets ) ) { foreach ( $widgets as $widget ) { if ( $widget_to_find === $widget ) { return $sidebar_id; } } } } } return false; } /** * Find the first language switcher in an array of widgets * * @param array $widgets * * @return string */ private function find_first_ls_widget( $widgets ) { $ret = false; $widgets = is_array( $widgets ) ? $widgets : array(); foreach ( $widgets as $widget_id ) { if ( strpos( $widget_id, WPML_LS_Widget::SLUG ) === 0 ) { $ret = $widget_id; break; } } return $ret; } /** * @return array */ public function get_ordered_languages() { $active_languages = $this->sitepress->get_active_languages(); foreach ( $active_languages as $code => $language ) { $active_languages[ $code ]['flag_img'] = $this->sitepress->get_flag_image( $code ); } return $this->sitepress->order_languages( $active_languages ); } /** * @return array */ public function get_default_color_schemes() { return $this->color_presets->get_defaults(); } /** * @param mixed|null|string $slug * * @return mixed|array|string */ public function get_core_templates( $slug = null ) { $parameters = WPML_Language_Switcher::parameters(); $core_templates = isset( $parameters['core_templates'] ) ? $parameters['core_templates'] : array(); $return = $core_templates; if ( ! empty( $slug ) ) { $return = isset( $return[ $slug ] ) ? $return[ $slug ] : null; } return $return; } /** * @param string|null $template_slug * * @return bool */ public function can_load_styles( $template_slug = null ) { if ( $template_slug ) { $template = $this->templates->get_template( $template_slug ); $can_load = ! ( $template->is_core() && $this->sitepress->get_wp_api()->constant( 'ICL_DONT_LOAD_LANGUAGE_SELECTOR_CSS' ) ); } else { $can_load = ! $this->sitepress->get_wp_api()->constant( 'ICL_DONT_LOAD_LANGUAGE_SELECTOR_CSS' ); } return $can_load; } /** * @param string|null $template_slug * * @return bool */ public function can_load_script( $template_slug = null ) { if ( $template_slug ) { $template = $this->templates->get_template( $template_slug ); $can_load = ! ( $template->is_core() && $this->sitepress->get_wp_api()->constant( 'ICL_DONT_LOAD_LANGUAGES_JS' ) ); } else { $can_load = ! $this->sitepress->get_wp_api()->constant( 'ICL_DONT_LOAD_LANGUAGES_JS' ); } return $can_load; } /** * @return WPML_LS_Migration */ private function migration() { if ( ! $this->migration ) { $this->migration = new WPML_LS_Migration( $this, $this->sitepress, $this->slot_factory ); } return $this->migration; } } language-switcher/LsTemplateDomainUpdater.php 0000755 00000006154 14720342453 0015432 0 ustar 00 <?php namespace WPML\LanguageSwitcher; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Str; use WPML\Collect\Support\Collection; use WPML\FP\Either; class LsTemplateDomainUpdater { public function run( Collection $data, \wpdb $wpdb ) { $this->runUpdate(); return Either::of( true ); } public function runUpdate( $siteurl = null, $homepath = null ) { $data = get_option( \WPML_LS_Templates::OPTION_NAME ); if ( ! $data ) { return; } if ( is_null( $siteurl ) ) { $siteurl = site_url(); } if ( is_null( $homepath ) ) { $homepath = get_home_path(); } $homepath = untrailingslashit( trim( $homepath ) ); $siteurl = untrailingslashit( trim( $siteurl ) ); $propsWithUrls = [ 'css', 'js', 'base_uri', 'flags_base_uri' ]; $getTemplateData = function ( $template ) { return method_exists( $template, 'get_template_data' ) ? $template->get_template_data() : $template; }; $updatePath = function ( $templatePropValue ) use ( $homepath ) { return Fns::map( function ( $value ) use ( $homepath ) { return $this->setPath( $value, $homepath ); }, $templatePropValue ); }; $updateUrlOfSingleValue = function ( $templatePropValue ) use ( $siteurl ) { return $this->setUrl( $templatePropValue, $siteurl ); }; $updateUrl = function ( $templatePropValue ) use ( $updateUrlOfSingleValue ) { return is_array( $templatePropValue ) ? Fns::map( $updateUrlOfSingleValue, $templatePropValue ) : $updateUrlOfSingleValue( $templatePropValue ); }; $transformations = [ 'path' => $updatePath ]; foreach ( $propsWithUrls as $prop ) { $transformations[ $prop ] = $updateUrl; } $updateTemplateData = function ( $template ) use ( $getTemplateData, $transformations ) { $updatedTemplateData = Obj::evolve( $transformations, $getTemplateData( $template ) ); if ( method_exists( $template, 'set_template_data' ) ) { $template->set_template_data( $updatedTemplateData ); } else { $template = $updatedTemplateData; } return $template; }; $data = Fns::map( $updateTemplateData, $data ); update_option( \WPML_LS_Templates::OPTION_NAME, $data ); return $data; } private function setPath( $path, $homepath ) { $pathParts = explode( '/wp-content', $path ); $origHomepath = $pathParts[0]; return Str::replace( $origHomepath, $homepath, $path ); } private function setUrl( $url, $siteurl ) { $url = trim( $url ); return $this->maybeSetUrl( $this->maybeSetProtocol( $url, $siteurl ), $siteurl ); } private function maybeSetProtocol( $url, $siteurl ) { if ( Str::startsWith( 'http', $url ) ) { return $url; } $protocol = ( Str::startsWith( 'https', $siteurl ) ) ? 'https' : 'http'; if ( ! Str::startsWith( ':', $url ) ) { $protocol .= ':'; } if ( ! Str::startsWith( '//', $url ) ) { $protocol .= '//'; } return $protocol . $url; } private function maybeSetUrl( $url, $siteurl ) { if ( Str::startsWith( $siteurl, $url ) ) { return $url; } $urlData = wp_parse_url( $url ); $urlDomain = $urlData['scheme'] . '://' . $urlData['host']; return Str::replace( $urlDomain, $siteurl, $url ); } } language-switcher/AjaxNavigation/Hooks.php 0000755 00000001455 14720342453 0014670 0 ustar 00 <?php namespace WPML\LanguageSwitcher\AjaxNavigation; class Hooks implements \IWPML_Frontend_Action, \IWPML_DIC_Action { public function add_hooks() { add_action( 'wp_enqueue_scripts', [ $this, 'enqueueScripts' ] ); } public function enqueueScripts() { if ( $this->isEnabled() ) { wp_enqueue_script( 'wpml-ajax-navigation', ICL_PLUGIN_URL . '/dist/js/ajaxNavigation/app.js', [], ICL_SITEPRESS_VERSION ); } } /** * @return bool */ private function isEnabled() { /** * This filter allows to enable/disable the feature to automatically * refresh the language switchers on AJAX navigation. * * @since 4.4.0 * * @param bool $is_enabled Is the feature enabled (default: false). */ return apply_filters( 'wpml_ls_enable_ajax_navigation', false ); } } language-switcher/class-wpml-get-ls-languages-status.php 0000755 00000001311 14720342453 0017431 0 ustar 00 <?php class WPML_Get_LS_Languages_Status { private static $the_instance; private $in_get_ls_languages = false; public function is_getting_ls_languages() { return $this->in_get_ls_languages; } public function start() { $this->in_get_ls_languages = true; } public function end() { $this->in_get_ls_languages = false; } /** * @return WPML_Get_LS_Languages_Status */ public static function get_instance() { if ( ! self::$the_instance ) { self::$the_instance = new WPML_Get_LS_Languages_Status(); } return self::$the_instance; } /** * @param WPML_Get_LS_Languages_Status $instance */ public static function set_instance( $instance ) { self::$the_instance = $instance; } } language-switcher/class-wpml-ls-assets.php 0000755 00000004327 14720342453 0014701 0 ustar 00 <?php class WPML_LS_Assets { /* @var array $enqueued_templates */ private $enqueued_templates = array(); /* @var WPML_LS_Templates $templates */ private $templates; /* @var WPML_LS_Settings $settings */ private $settings; /** * WPML_Language_Switcher_Render_Assets constructor. * * @param WPML_LS_Templates $templates * @param WPML_LS_Settings $settings */ public function __construct( $templates, &$settings ) { $this->templates = $templates; $this->settings = $settings; } public function init_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'wp_enqueue_scripts_action' ) ); } public function wp_enqueue_scripts_action() { $active_templates_slugs = $this->settings->get_active_templates(); /** * Filter the templates to be enqueued (CSS & JS) * To use if a language switcher is rendered * with a specific template later in the script * * @param array $active_templates */ $active_templates_slugs = apply_filters( 'wpml_ls_enqueue_templates', $active_templates_slugs ); $templates = $this->templates->get_templates( $active_templates_slugs ); foreach ( $templates as $slug => $template ) { $this->enqueue_template_resources( $slug, $template ); } } /** * @param string $slug */ public function maybe_late_enqueue_template( $slug ) { if ( ! in_array( $slug, $this->enqueued_templates ) ) { $template = $this->templates->get_template( $slug ); $this->enqueue_template_resources( $slug, $template ); } } /** * @param string $slug * @param WPML_LS_Template $template */ private function enqueue_template_resources( $slug, $template ) { $this->enqueued_templates[] = $slug; if ( $this->settings->can_load_script( $slug ) ) { foreach ( $template->get_scripts() as $k => $url ) { $site_scheme_url = set_url_scheme( $url ); wp_enqueue_script( $template->get_resource_handler( $k ), $site_scheme_url, array(), $template->get_version() ); } } if ( $this->settings->can_load_styles( $slug ) ) { foreach ( $template->get_styles() as $k => $url ) { $site_scheme_url = set_url_scheme( $url ); wp_enqueue_style( $template->get_resource_handler( $k ), $site_scheme_url, array(), $template->get_version() ); } } } } language-switcher/slots/class-wpml-ls-menu-slot.php 0000755 00000001734 14720342453 0016465 0 ustar 00 <?php class WPML_LS_Menu_Slot extends WPML_LS_Slot { /** * @return bool */ public function is_enabled() { return true; } /** * @return array */ protected function get_allowed_properties() { $allowed_properties = array( 'position_in_menu' => array( 'type' => 'string', 'force_missing_to' => 'after', ), 'is_hierarchical' => array( 'type' => 'int', 'force_missing_to' => 1, ), 'show' => array( 'type' => 'int', 'force_missing_to' => 1, ), 'template' => array( 'type' => 'string', 'force_missing_to' => $this->get_core_template( 'menu-item' ), ), 'slot_group' => array( 'type' => 'string', 'force_missing_to' => 'menus', ), 'slot_slug' => array( 'type' => 'int', 'force_missing_to' => 0, ), ); return array_merge( parent::get_allowed_properties(), $allowed_properties ); } } language-switcher/slots/class-wpml-ls-shortcode-actions-slot.php 0000755 00000002002 14720342453 0021136 0 ustar 00 <?php class WPML_LS_Shortcode_Actions_Slot extends WPML_LS_Slot { /** * @return array */ protected function get_allowed_properties() { $allowed_properties = array( 'show' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'template' => array( 'type' => 'string', 'force_missing_to' => $this->get_core_template( 'list-horizontal' ), ), 'slot_group' => array( 'type' => 'string', 'force_missing_to' => 'statics', ), 'slot_slug' => array( 'type' => 'string', 'force_missing_to' => 'shortcode_actions', ), ); return array_merge( parent::get_allowed_properties(), $allowed_properties ); } public function is_enabled() { /** * This filter allows to programmatically enable/disable the custom language switcher. * * @since 4.3.0 * * @param bool $is_enabled The original status. */ return apply_filters( 'wpml_custom_language_switcher_is_enabled', parent::is_enabled() ); } } language-switcher/slots/class-wpml-ls-slot.php 0000755 00000014345 14720342453 0015525 0 ustar 00 <?php /** * Class WPML_LS_Slot */ class WPML_LS_Slot { /* @var array $properties */ private $properties = array(); /* @var array $protected_properties */ private $protected_properties = array( 'slot_group', 'slot_slug', ); /** * WPML_Language_Switcher_Slot constructor. * * @param array $args */ public function __construct( array $args = array() ) { $this->set_properties( $args ); } /** * @param string $property * * @return mixed */ public function get( $property ) { return isset( $this->properties[ $property ] ) ? $this->properties[ $property ] : null; } /** * @param string $property * @param mixed $value */ public function set( $property, $value ) { if ( ! in_array( $property, $this->protected_properties ) ) { $allowed_properties = $this->get_allowed_properties(); if ( array_key_exists( $property, $allowed_properties ) ) { $meta_data = $allowed_properties[ $property ]; $this->properties[ $property ] = $this->sanitize( $value, $meta_data ); } } } /** * @return mixed|string|null */ public function group() { return $this->get( 'slot_group' ); } /** * @return mixed|string|null */ public function slug() { return $this->get( 'slot_slug' ); } /** * @return bool */ public function is_menu() { return $this->group() === 'menus'; } /** * @return bool */ public function is_sidebar() { return $this->group() === 'sidebars'; } /** * @return bool */ public function is_footer() { return $this->group() === 'statics' && $this->slug() === 'footer'; } /** * @return bool */ public function is_post_translations() { return $this->group() === 'statics' && $this->slug() === 'post_translations'; } /** * @return bool */ public function is_shortcode_actions() { return $this->group() === 'statics' && $this->slug() === 'shortcode_actions'; } /** * @return bool */ public function is_enabled() { return $this->get( 'show' ) ? true : false; } /** * @return mixed */ public function template() { return $this->get( 'template' ); } /** * @return mixed */ public function template_string() { return $this->get( 'template_string' ); } /** * @param array $args */ private function set_properties( array $args ) { foreach ( $this->get_allowed_properties() as $allowed_property => $meta_data ) { $value = null; if ( isset( $args[ $allowed_property ] ) ) { $value = $args[ $allowed_property ]; } elseif ( isset( $meta_data['set_missing_to'] ) ) { $value = $meta_data['set_missing_to']; } $this->properties[ $allowed_property ] = $this->sanitize( $value, $meta_data ); } } /** * @return array */ protected function get_allowed_properties() { return array( 'slot_group' => array( 'type' => 'string', 'force_missing_to' => '', ), 'slot_slug' => array( 'type' => 'string', 'force_missing_to' => '', ), 'show' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'is_hierarchical' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'template' => array( 'type' => 'string' ), 'display_flags' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'display_link_for_current_lang' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'display_names_in_native_lang' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'display_names_in_current_lang' => array( 'type' => 'int', 'force_missing_to' => 0, ), 'include_flag_width' => array( 'type' => 'int', 'set_missing_to' => \WPML_LS_Settings::DEFAULT_FLAG_WIDTH, ), 'include_flag_height' => array( 'type' => 'int', 'set_missing_to' => \WPML_LS_Settings::DEFAULT_FLAG_HEIGHT, ), // Colors 'background_normal' => array( 'type' => 'string' ), 'border_normal' => array( 'type' => 'string' ), 'font_current_normal' => array( 'type' => 'string' ), 'font_current_hover' => array( 'type' => 'string' ), 'background_current_normal' => array( 'type' => 'string' ), 'background_current_hover' => array( 'type' => 'string' ), 'font_other_normal' => array( 'type' => 'string' ), 'font_other_hover' => array( 'type' => 'string' ), 'background_other_normal' => array( 'type' => 'string' ), 'background_other_hover' => array( 'type' => 'string' ), 'template_string' => array( 'type' => 'string', 'twig_string' => 1, ), ); } /** * @param mixed $value * @param array $meta_data * * @return mixed */ private function sanitize( $value, array $meta_data ) { if ( ! is_null( $value ) ) { switch ( $meta_data['type'] ) { case 'string': $value = (string) $value; if ( array_key_exists( 'stripslashes', $meta_data ) && $meta_data['stripslashes'] ) { $value = stripslashes( $value ); } if ( array_key_exists( 'twig_string', $meta_data ) ) { $value = preg_replace( '/<br\W*?\/>/', '', $value ); } else { $value = sanitize_text_field( $value ); } break; case 'bool': $value = (bool) $value; break; case 'int': $value = (int) $value; break; } } elseif ( array_key_exists( 'force_missing_to', $meta_data ) ) { $value = $meta_data['force_missing_to']; } return $value; } /** * The use of a plain object does not work in Twig * e.g: slot_settings[ option.name ~ "_normal" ] (see in panel-colors.twig) * * @return array */ public function get_model() { $model = array(); foreach ( $this->properties as $property => $value ) { $model[ $property ] = $value; } return $model; } /** * @param string $slug * * @return string|null */ protected function get_core_template( $slug ) { $parameters = WPML_Language_Switcher::parameters(); $core_templates = isset( $parameters['core_templates'] ) ? $parameters['core_templates'] : array(); return isset( $core_templates[ $slug ] ) ? $core_templates[ $slug ] : null; } } language-switcher/slots/class-wpml-ls-post-translations-slot.php 0000755 00000001506 14720342453 0021222 0 ustar 00 <?php class WPML_LS_Post_Translations_Slot extends WPML_LS_Slot { /** * @return array */ protected function get_allowed_properties() { $allowed_properties = array( 'display_before_content' => array( 'type' => 'int', 'force_missing_to' => 0 ), 'display_after_content' => array( 'type' => 'int', 'force_missing_to' => 0 ), 'availability_text' => array( 'type' => 'string', 'stripslashes' => true ), 'template' => array( 'type' => 'string', 'force_missing_to' => $this->get_core_template( 'post-translations' ) ), 'slot_group' => array( 'type' => 'string', 'force_missing_to' => 'statics' ), 'slot_slug' => array( 'type' => 'string', 'force_missing_to' => 'post_translations' ), ); return array_merge( parent::get_allowed_properties(), $allowed_properties ); } } language-switcher/slots/class-wpml-ls-slot-factory.php 0000755 00000004206 14720342453 0017165 0 ustar 00 <?php class WPML_LS_Slot_Factory { /** * @param array|WPML_LS_Slot $args * * @return WPML_LS_Slot */ public function get_slot( $args ) { if ( is_array( $args ) ) { $args['slot_group'] = isset( $args['slot_group'] ) ? $args['slot_group'] : null; $args['slot_slug'] = isset( $args['slot_slug'] ) ? $args['slot_slug'] : null; $slot = new WPML_LS_Slot( $args ); switch ( $args['slot_group'] ) { case 'menus': $slot = new WPML_LS_Menu_Slot( $args ); break; case 'sidebars': $slot = new WPML_LS_Sidebar_Slot( $args ); break; case 'statics': switch ( $args['slot_slug'] ) { case 'footer': $slot = new WPML_LS_Footer_Slot( $args ); break; case 'post_translations': $slot = new WPML_LS_Post_Translations_Slot( $args ); break; case 'shortcode_actions': $slot = new WPML_LS_Shortcode_Actions_Slot( $args ); break; } break; } } else { $slot = $args; } return $slot; } /** * @param string $slot_group * * @return array */ public function get_default_slot_arguments( $slot_group ) { $args = array( 'slot_group' => $slot_group, 'display_link_for_current_lang' => 1, 'display_names_in_native_lang' => 1, 'display_names_in_current_lang' => 1, ); if ( $slot_group === 'menus' ) { $args['template'] = $this->get_core_templates( 'menu-item' ); $args['is_hierarchical'] = 1; } elseif ( $slot_group === 'sidebars' ) { $args['template'] = $this->get_core_templates( 'dropdown' ); } return $args; } /** * @param string $slot_group * * @return WPML_LS_Slot */ public function get_default_slot( $slot_group ) { $slot_args = $this->get_default_slot_arguments( $slot_group ); return $this->get_slot( $slot_args ); } /** * @param string $slug * * @return string|null */ public function get_core_templates( $slug ) { $parameters = WPML_Language_Switcher::parameters(); $templates = isset( $parameters['core_templates'] ) ? $parameters['core_templates'] : array(); return isset( $templates[ $slug ] ) ? $templates[ $slug ] : null; } } language-switcher/slots/class-wpml-ls-footer-slot.php 0000755 00000001133 14720342453 0017010 0 ustar 00 <?php class WPML_LS_Footer_Slot extends WPML_LS_Slot { /** * @return array */ protected function get_allowed_properties() { $allowed_properties = array( 'template' => array( 'type' => 'string', 'force_missing_to' => $this->get_core_template( 'list-horizontal' ), ), 'slot_group' => array( 'type' => 'string', 'force_missing_to' => 'statics', ), 'slot_slug' => array( 'type' => 'string', 'force_missing_to' => 'footer', ), ); return array_merge( parent::get_allowed_properties(), $allowed_properties ); } } language-switcher/slots/class-wpml-ls-sidebar-slot.php 0000755 00000001376 14720342453 0017134 0 ustar 00 <?php class WPML_LS_Sidebar_Slot extends WPML_LS_Slot { /** * @return bool */ public function is_enabled() { return true; } /** * @return array */ protected function get_allowed_properties() { $allowed_properties = array( 'widget_title' => array( 'type' => 'string', 'stripslashes' => true, ), 'show' => array( 'type' => 'int', 'force_missing_to' => 1, ), 'template' => array( 'type' => 'string', 'force_missing_to' => $this->get_core_template( 'dropdown' ), ), 'slot_group' => array( 'type' => 'string', 'force_missing_to' => 'sidebars', ), ); return array_merge( parent::get_allowed_properties(), $allowed_properties ); } } language-switcher/class-wpml-ls-menu-item.php 0000755 00000006565 14720342453 0015305 0 ustar 00 <?php #[AllowDynamicProperties] class WPML_LS_Menu_Item { /** * @see wp_setup_nav_menu_item() to decorate the object */ public $ID; // The term_id if the menu item represents a taxonomy term. public $attr_title; // The title attribute of the link element for this menu item. public $classes = array(); // The array of class attribute values for the link element of this menu item. public $db_id; // The DB ID of this item as a nav_menu_item object, if it exists (0 if it doesn't exist). public $description; // The description of this menu item. public $menu_item_parent; // The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise. public $object = 'wpml_ls_menu_item'; // The type of object originally represented, such as "category," "post", or "attachment." public $object_id; // The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories. public $post_parent; // The DB ID of the original object's parent object, if any (0 otherwise). public $post_title; // A "no title" label if menu item represents a post that lacks a title. public $target; // The target attribute of the link element for this menu item. public $title; // The title of this menu item. public $type = 'wpml_ls_menu_item'; // The family of objects originally represented, such as "post_type" or "taxonomy." public $type_label; // The singular label used to describe this type of menu item. public $url; // The URL to which this menu item points. public $xfn; // The XFN relationship expressed in the link of this menu item. public $_invalid = false; // Whether the menu item represents an object that no longer exists. public $menu_order; public $post_type = 'nav_menu_item'; // * Extra property => see [wpmlcore-3855] /** * WPML_LS_Menu_Item constructor. * * @param array $language * @param string $item_content */ public function __construct( $language, $item_content ) { $this->decorate_object( $language, $item_content ); } /** * @param array $lang * @param string $item_content */ private function decorate_object( $lang, $item_content ) { $this->ID = isset( $lang['db_id'] ) ? $lang['db_id'] : null; $this->object_id = isset( $lang['db_id'] ) ? $lang['db_id'] : null; $this->db_id = isset( $lang['db_id'] ) ? $lang['db_id'] : null; $this->menu_item_parent = isset( $lang['menu_item_parent'] ) ? $lang['menu_item_parent'] : null; $this->attr_title = isset( $lang['display_name'] ) ? $lang['display_name'] : ( isset( $lang['native_name'] ) ? $lang['native_name'] : '' ); $this->title = $item_content; $this->post_title = $item_content; $this->url = isset( $lang['url'] ) ? $lang['url'] : null; if ( isset( $lang['css_classes'] ) ) { $this->classes = $lang['css_classes']; if ( is_string( $lang['css_classes'] ) ) { $this->classes = explode( ' ', $lang['css_classes'] ); } } } /** * @param string $property * * @return mixed */ public function __get( $property ) { return isset( $this->{$property} ) ? $this->{$property} : null; } } language-switcher/class-wpml-ls-templates.php 0000755 00000025026 14720342453 0015374 0 ustar 00 <?php class WPML_LS_Templates { const CONFIG_FILE = 'config.json'; const OPTION_NAME = 'wpml_language_switcher_template_objects'; /** @var string $uploads_path */ private $uploads_path; /** * @var WPML_File */ private $wpml_file; /** @var array $templates Collection of WPML_LS_Template */ private $templates = false; /** @var string $ds */ private $ds = DIRECTORY_SEPARATOR; /** @var boolean $are_templates_loaded_from_cache */ private $are_templates_loaded_from_cache = false; public function __construct( WPML_File $wpml_file = null ) { if ( ! $wpml_file ) { $wpml_file = new WPML_File(); } $this->wpml_file = $wpml_file; } public function init_hooks() { add_action( 'after_setup_theme', array( $this, 'action_after_setup_theme_action' ) ); add_action( 'activated_plugin', array( $this, 'activated_plugin_action' ) ); add_action( 'deactivated_plugin', array( $this, 'activated_plugin_action' ) ); add_action( 'switch_theme', array( $this, 'activated_plugin_action' ) ); } public function action_after_setup_theme_action() { $this->after_setup_theme_action(); } /** * @return boolean */ public function are_templates_loaded_from_cache() { return $this->are_templates_loaded_from_cache; } /** * @return array */ public function after_setup_theme_action() { return $this->init_available_templates(); } public function activated_plugin_action() { delete_option( self::OPTION_NAME ); } /** * @param null|array $in_array * * @return array */ public function get_templates( $in_array = null ) { if ( null === $in_array ) { $ret = $this->templates; } else { // PHP 5.3 Bug https://bugs.php.net/bug.php?id=34857º $in_array = $in_array ? array_combine( $in_array, $in_array ) : $in_array; $ret = array_intersect_key( $this->templates, $in_array ); } return $ret; } /** * @param string $template_slug * * @return WPML_LS_Template */ public function get_template( $template_slug ) { $ret = new WPML_LS_Template( array() ); if ( $this->templates && array_key_exists( $template_slug, $this->templates ) ) { $ret = $this->templates[ $template_slug ]; } return $ret; } public function get_all_templates_data() { $ret = array(); foreach ( $this->get_templates() as $slug => $template ) { /* @var WPML_LS_Template $template */ $ret[ $slug ] = $template->get_template_data(); } return $ret; } /** * @return array */ private function init_available_templates() { $is_admin_ui_page = isset( $_GET['page'] ) && WPML_LS_Admin_UI::get_page_hook() === $_GET['page']; if ( ! $is_admin_ui_page ) { $this->templates = $this->get_templates_from_transient(); } if ( $this->templates === false ) { $this->templates = array(); $dirs_to_scan = array(); /** * Filter the directories to scan * * @param array $dirs_to_scan */ $dirs_to_scan = apply_filters( 'wpml_ls_directories_to_scan', $dirs_to_scan ); $sub_dir = $this->ds . 'templates' . $this->ds . 'language-switchers'; $wpml_core_path = WPML_PLUGIN_PATH . $sub_dir; $theme_path = get_template_directory() . $this->ds . 'wpml' . $sub_dir; $child_theme_path = get_stylesheet_directory() . $this->ds . 'wpml' . $sub_dir; $uploads_path = $this->get_uploads_path() . $this->ds . 'wpml' . $sub_dir; array_unshift( $dirs_to_scan, $wpml_core_path, $theme_path, $child_theme_path, $uploads_path ); // We want to save only raw array data without WPML_LS_Template objects. // If we save with objects some WP CLI tools like search-replace will not process those serialized objects. $templates_array_data = []; $templates_paths = $this->scan_template_paths( $dirs_to_scan ); foreach ( $templates_paths as $template_path ) { $template_path = $this->wpml_file->fix_dir_separator( $template_path ); if ( file_exists( $template_path . $this->ds . WPML_LS_Template::FILENAME ) ) { $tpl = array(); $config = $this->parse_template_config( $template_path ); $tpl['path'] = [ $wpml_core_path, $template_path ]; $tpl['version'] = isset( $config['version'] ) ? $config['version'] : '1'; $tpl['name'] = isset( $config['name'] ) ? $config['name'] : null; $tpl['name'] = $this->get_unique_name( $tpl['name'], $template_path ); $tpl['slug'] = sanitize_title_with_dashes( $tpl['name'] ); $tpl['base_uri'] = trailingslashit( $this->wpml_file->get_uri_from_path( $template_path, false ) ); $tpl['css'] = $this->get_files( 'css', $template_path, $config ); $tpl['js'] = $this->get_files( 'js', $template_path, $config ); $tpl['flags_base_uri'] = isset( $config['flags_dir'] ) // todo: check with ../ ? $this->wpml_file->get_uri_from_path( $template_path . $this->ds . $config['flags_dir'], false ) : null; $tpl['flags_base_uri'] = ! isset( $tpl['flags_base_uri'] ) && file_exists( $template_path . $this->ds . 'flags' ) ? $this->wpml_file->get_uri_from_path( $template_path . $this->ds . 'flags', false ) : $tpl['flags_base_uri']; $tpl['flag_extension'] = isset( $config['flag_extension'] ) ? $config['flag_extension'] : null; if ( $this->is_core_template( $template_path ) ) { $tpl['is_core'] = true; $tpl['slug'] = isset( $config['slug'] ) ? $config['slug'] : $tpl['slug']; } $tpl['for'] = isset( $config['for'] ) ? $config['for'] : array( 'menus', 'sidebars', 'footer', 'post_translations', 'shortcode_actions' ); $tpl['force_settings'] = isset( $config['settings'] ) ? $config['settings'] : array(); $templates_array_data[ $tpl['slug'] ] = $tpl; $this->templates[ $tpl['slug'] ] = new WPML_LS_Template( $tpl ); } } $this->save_templates( $templates_array_data ); } return $this->templates; } /** * @param array $dirs_to_scan * * @return array */ private function scan_template_paths( $dirs_to_scan ) { $templates_paths = array(); foreach ( $dirs_to_scan as $dir ) { if ( ! is_dir( $dir ) ) { continue; } $files = scandir( $dir ); $files = $files ? array_diff( $files, array( '..', '.' ) ) : []; if ( count( $files ) > 0 ) { foreach ( $files as $file ) { $template_path = $dir . '/' . $file; if ( is_dir( $template_path ) && file_exists( $template_path . $this->ds . WPML_LS_Template::FILENAME ) && file_exists( $template_path . $this->ds . self::CONFIG_FILE ) ) { $templates_paths[] = $template_path; } } } } return $templates_paths; } /** * @param string $template_path * * @return array */ private function parse_template_config( $template_path ) { $config = array(); $configuration_file = $template_path . $this->ds . self::CONFIG_FILE; if ( file_exists( $configuration_file ) ) { $json_content = file_get_contents( $configuration_file ); $config = $json_content ? json_decode( $json_content, true ) : []; } return $config; } /** * @param string $ext * @param string $template_path * @param array $config * * @return array|null */ private function get_files( $ext, $template_path, $config ) { $resources = array(); $trimProtocol = false; if ( isset( $config[ $ext ] ) ) { $config[ $ext ] = is_array( $config[ $ext ] ) ? $config[ $ext ] : array( $config[ $ext ] ); foreach ( $config[ $ext ] as $file ) { $file = untrailingslashit( $template_path ) . $this->ds . $file; $resources[] = $this->wpml_file->get_uri_from_path( $file, $trimProtocol ); } } else { $search_path = $template_path . $this->ds . '*.' . $ext; if ( glob( $search_path ) ) { foreach ( glob( $search_path ) as $file ) { $resources[] = $this->wpml_file->get_uri_from_path( $file, $trimProtocol ); } } } return $resources; } /** * @param mixed|string|null $name * @param string $path * * @return string */ private function get_unique_name( $name, $path ) { if ( is_null( $name ) ) { $name = basename( $path ); } if ( strpos( $path, $this->wpml_file->fix_dir_separator( get_template_directory() ) ) === 0 ) { $theme = wp_get_theme(); $name = $theme . ' - ' . $name; } elseif ( strpos( $path, $this->wpml_file->fix_dir_separator( $this->get_uploads_path() ) ) === 0 ) { $name = __( 'Uploads', 'sitepress' ) . ' - ' . $name; } elseif ( strpos( $path, $this->wpml_file->fix_dir_separator( WPML_PLUGINS_DIR ) ) === 0 && ! $this->is_core_template( $path ) ) { $plugin_dir = $this->wpml_file->fix_dir_separator( WPML_PLUGINS_DIR ); $plugin_dir = preg_replace( '#' . preg_quote( $plugin_dir ) . '#', '', $path, 1 ); $plugin_dir = ltrim( $plugin_dir, $this->ds ); $plugin_dir = explode( $this->ds, $plugin_dir ); if ( isset( $plugin_dir[0] ) ) { $require = ABSPATH . 'wp-admin' . $this->ds . 'includes' . $this->ds . 'plugin.php'; require_once $require; foreach ( get_plugins() as $slug => $plugin ) { if ( strpos( $slug, $plugin_dir[0] ) === 0 ) { $name = $plugin['Name'] . ' - ' . $name; break; } } } else { $name = substr( md5( $path ), 0, 8 ) . ' - ' . $name; } } return $name; } /** * @param string $path * * @return bool */ private function is_core_template( $path ) { return strpos( $path, $this->wpml_file->fix_dir_separator( WPML_PLUGIN_PATH ) ) === 0; } private function get_templates_from_transient() { $this->are_templates_loaded_from_cache = false; $templates = get_option( self::OPTION_NAME ); if ( $templates ) { foreach ( $templates as $slug => $data ) { if ( is_array( $data ) ) { $templates[ $slug ] = new WPML_LS_Template( $data ); } } if ( $templates && ! $this->are_template_paths_valid( $templates ) ) { return false; } $this->are_templates_loaded_from_cache = true; } return $templates; } /** * @param array $data */ private function save_templates( $data ) { update_option( self::OPTION_NAME, $data ); } /** * @param WPML_LS_Template[] $templates * * @return bool */ private function are_template_paths_valid( $templates ) { $paths_are_valid = true; foreach ( $templates as $template ) { if ( ! method_exists( $template, 'is_path_valid' ) || ! $template->is_path_valid() ) { $paths_are_valid = false; break; } } return $paths_are_valid; } /** * @return null|string */ private function get_uploads_path() { if ( ! $this->uploads_path ) { $uploads = wp_upload_dir( null, false ); if ( isset( $uploads['basedir'] ) ) { $this->uploads_path = $uploads['basedir']; } } return $this->uploads_path; } } translation-feedback/notices/wpml-tf-promote-notices.php 0000755 00000004103 14720342453 0017521 0 ustar 00 <?php /** * Class WPML_TF_Promote_Notices * * @author OnTheGoSystems */ class WPML_TF_Promote_Notices { const NOTICE_GROUP = 'wpml-tf-promote'; const NOTICE_NEW_SITE = 'notice-new-site'; const DOC_URL = 'https://wpml.org/documentation/getting-started-guide/getting-visitor-feedback-about-your-sites-translations/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore'; /** @var SitePress $sitepress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param int $user_id */ public function show_notice_for_new_site( $user_id ) { $notices = wpml_get_admin_notices(); $settings_url = admin_url( '?page=' . WPML_PLUGIN_FOLDER . '/menu/languages.php#wpml-translation-feedback-options' ); $user_lang = $this->sitepress->get_user_admin_language( $user_id ); $this->sitepress->switch_lang( $user_lang ); $text = '<h2>' . __( 'Want to know if recent translations you received have problems?', 'sitepress' ) . '</h2>'; $text .= '<p>'; $text .= __( 'You got back several jobs from translation and they now appear on your site.', 'sitepress' ); $text .= ' ' . __( 'WPML lets you open these pages for feedback, so that visitors can tell you if they notice anything wrong.', 'sitepress' ); $text .= '<br><br>'; $text .= '<a href="' . $settings_url . '" class="button-secondary">' . __( 'Enable Translation Feedback', 'sitepress' ) . '</a>'; $text .= ' <a href="' . self::DOC_URL . '" target="_blank">' . __( 'Learn more about translation feedback', 'sitepress' ) . '</a>'; $text .= '</p>'; $notice = $notices->get_new_notice( self::NOTICE_NEW_SITE, $text, self::NOTICE_GROUP ); $notice->set_dismissible( true ); $notice->set_css_class_types( 'notice-info' ); $notice->add_user_restriction( $user_id ); if ( ! $notices->is_notice_dismissed( $notice ) ) { $notices->add_notice( $notice ); } $this->sitepress->switch_lang( null ); } public function remove() { $notices = wpml_get_admin_notices(); $notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_NEW_SITE ); } } translation-feedback/notices/wpml-tf-backend-notices.php 0000755 00000004200 14720342453 0017421 0 ustar 00 <?php /** * Class WPML_TF_Backend_Notices * * @author OnTheGoSystems */ class WPML_TF_Backend_Notices { const GROUP = 'wpml_tf_backend_notices'; const BULK_UPDATED = 'bulk_updated'; /** @var WPML_Notices $admin_notices */ private $admin_notices; /** * @param array $updated_feedback_ids * @parem string */ public function add_bulk_updated_notice( array $updated_feedback_ids, $action ) { $count_feedback = count( $updated_feedback_ids ); $message = _n( '%d feedback was updated.', '%d feedback were updated.', $count_feedback, 'sitepress' ); if ( 'trash' === $action ) { $permanent_trash_delay = defined( 'EMPTY_TRASH_DAYS' ) ? EMPTY_TRASH_DAYS : 30; $message = _n( '%d feedback was trashed.', '%d feedback were trashed.', $count_feedback, 'sitepress' ); $message .= ' ' . sprintf( __( 'The trashed feedback will be permanently deleted after %d days.', 'sitepress' ), $permanent_trash_delay ); } elseif ( 'untrash' === $action ) { $message = _n( '%d feedback was restored.', '%d feedback were restored.', $count_feedback, 'sitepress' ); } elseif ( 'delete' === $action ) { $message = _n( '%d feedback was permanently deleted.', '%d feedback were permanently deleted.', $count_feedback, 'sitepress' ); } $text = sprintf( $message, $count_feedback ); $new_notice = $this->get_admin_notices()->get_new_notice( self::BULK_UPDATED, $text, self::GROUP ); $new_notice->set_hideable( true ); $new_notice->set_css_class_types( 'notice-success' ); $this->get_admin_notices()->add_notice( $new_notice ); } /** * Add action to remove updated notice after display */ public function remove_bulk_updated_notice_after_display() { add_action( 'admin_notices', array( $this, 'remove_bulk_updated_notice' ), PHP_INT_MAX ); } /** * Remove bulk_updated notice */ public function remove_bulk_updated_notice() { $this->get_admin_notices()->remove_notice( self::GROUP, self::BULK_UPDATED ); } /** * @return WPML_Notices */ private function get_admin_notices() { if ( ! $this->admin_notices ) { $this->admin_notices = wpml_get_admin_notices(); } return $this->admin_notices; } } translation-feedback/wpml-tm-tf-module.php 0000755 00000002501 14720342453 0014631 0 ustar 00 <?php /** * Class WPML_TM_TF_Module * * @author OnTheGoSystems */ class WPML_TM_TF_Module { /** @var WPML_Action_Filter_Loader $action_filter_loader */ private $action_filter_loader; /** @var WPML_TF_Settings $settings */ private $settings; /** * WPML_TF_Module constructor. * * @param WPML_Action_Filter_Loader $action_filter_loader * @param WPML_TF_Settings $settings */ public function __construct( WPML_Action_Filter_Loader $action_filter_loader, WPML_TF_Settings $settings ) { $this->action_filter_loader = $action_filter_loader; $this->settings = $settings; } public function run() { $this->action_filter_loader->load( $this->get_actions_to_load_always() ); if ( $this->settings->is_enabled() ) { $this->action_filter_loader->load( $this->get_actions_to_load_when_module_enabled() ); } } /** * @return array */ private function get_actions_to_load_always() { return array( 'WPML_TF_WP_Cron_Events_Factory', ); } /** * @return array */ private function get_actions_to_load_when_module_enabled() { return array( 'WPML_TF_XML_RPC_Hooks_Factory', 'WPML_TF_Translation_Service_Change_Hooks_Factory', 'WPML_TF_Translation_Queue_Hooks_Factory', 'WPML_TM_TF_Feedback_List_Hooks_Factory', 'WPML_TM_TF_AJAX_Feedback_List_Hooks_Factory', ); } } translation-feedback/cron/wpml-tf-wp-cron-events.php 0000755 00000004024 14720342453 0016560 0 ustar 00 <?php /** * Class WPML_TF_WP_Cron_Events * * @author OnTheGoSystems */ class WPML_TF_WP_Cron_Events implements IWPML_Action { const SYNCHRONIZE_RATINGS_EVENT = 'wpml_tf_synchronize_ratings_event'; /** @var WPML_TF_Settings_Read $settings_read */ private $settings_read; /** @var WPML_TF_Settings $settings */ private $settings; /** @var WPML_TF_TP_Ratings_Synchronize_Factory $ratings_synchronize_factory */ private $ratings_synchronize_factory; /** * WPML_TF_WP_Cron_Events constructor. * * @param WPML_TF_Settings_Read $settings_read * @param WPML_TF_TP_Ratings_Synchronize_Factory $ratings_synchronize_factory */ public function __construct( WPML_TF_Settings_Read $settings_read, WPML_TF_TP_Ratings_Synchronize_Factory $ratings_synchronize_factory ) { $this->settings_read = $settings_read; $this->ratings_synchronize_factory = $ratings_synchronize_factory; } public function add_hooks() { add_action( 'init', array( $this, 'init_action' ) ); add_action( self::SYNCHRONIZE_RATINGS_EVENT, array( $this, 'synchronize_ratings' ) ); } public function init_action() { if ( $this->get_settings()->is_enabled() ) { $this->add_synchronize_ratings_event(); } else { $this->remove_synchronize_ratings_event(); } } private function add_synchronize_ratings_event() { if ( ! wp_next_scheduled( self::SYNCHRONIZE_RATINGS_EVENT ) ) { wp_schedule_event( time(), 'twicedaily', self::SYNCHRONIZE_RATINGS_EVENT ); } } private function remove_synchronize_ratings_event() { $timestamp = wp_next_scheduled( self::SYNCHRONIZE_RATINGS_EVENT ); $timestamp && wp_unschedule_event( $timestamp, self::SYNCHRONIZE_RATINGS_EVENT ); } public function synchronize_ratings() { $ratings_synchronize = $this->ratings_synchronize_factory->create(); $ratings_synchronize->run(); } /** @return WPML_TF_Settings */ private function get_settings() { if ( ! $this->settings ) { $this->settings = $this->settings_read->get( 'WPML_TF_Settings' ); } return $this->settings; } } translation-feedback/cron/actions/wpml-tf-tp-ratings-synchronize.php 0000755 00000006032 14720342453 0021773 0 ustar 00 <?php /** * Class WPML_TF_TP_Ratings_Synchronize * * @author OnTheGoSystems */ class WPML_TF_TP_Ratings_Synchronize { const MAX_RATINGS_TO_SYNCHRONIZE = 5; const PENDING_SYNC_RATING_IDS_OPTION = 'wpml_tf_pending_sync_rating_ids'; const MAX_ATTEMPTS_TO_SYNC = 3; /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_TP_API_TF_Ratings $tp_ratings */ private $tp_ratings; /** @var array $pending_ids */ private $pending_ids; /** * WPML_TF_TP_Ratings_Synchronize constructor. * * @param WPML_TF_Data_Object_Storage $feedback_storage * @param WPML_TP_API_TF_Ratings $tp_ratings */ public function __construct( WPML_TF_Data_Object_Storage $feedback_storage, WPML_TP_API_TF_Ratings $tp_ratings ) { $this->feedback_storage = $feedback_storage; $this->tp_ratings = $tp_ratings; } /** @param bool $clear_all_pending_ratings */ public function run( $clear_all_pending_ratings = false ) { $this->pending_ids = get_option( self::PENDING_SYNC_RATING_IDS_OPTION, array() ); $filter_args = array( 'pending_tp_ratings' => $clear_all_pending_ratings ? -1 : self::MAX_RATINGS_TO_SYNCHRONIZE, ); $feedback_filter = new WPML_TF_Feedback_Collection_Filter( $filter_args ); /** @var WPML_TF_Feedback_Collection $feedback_collection */ $feedback_collection = $this->feedback_storage->get_collection( $feedback_filter ); $time_threshold = 5 * MINUTE_IN_SECONDS; foreach ( $feedback_collection as $feedback ) { /** @var WPML_TF_Feedback $feedback */ $time_since_creation = time() - strtotime( $feedback->get_date_created() ); if ( ! $clear_all_pending_ratings && $time_since_creation < $time_threshold ) { continue; } $tp_rating_id = $this->tp_ratings->send( $feedback ); if ( $tp_rating_id || $clear_all_pending_ratings ) { $this->set_tp_rating_id( $feedback, $tp_rating_id ); } else { $this->handle_pending_rating_sync( $feedback ); } } if ( $this->pending_ids ) { update_option( self::PENDING_SYNC_RATING_IDS_OPTION, $this->pending_ids, false ); } else { delete_option( self::PENDING_SYNC_RATING_IDS_OPTION ); } } private function set_tp_rating_id( WPML_TF_Feedback $feedback, $tp_rating_id ) { $feedback->get_tp_responses()->set_rating_id( (int) $tp_rating_id ); $this->feedback_storage->persist( $feedback ); } private function handle_pending_rating_sync( WPML_TF_Feedback $feedback ) { $this->increment_attempts( $feedback->get_id() ); if ( $this->exceeds_max_attempts( $feedback->get_id() ) ) { $this->set_tp_rating_id( $feedback, 0 ); unset( $this->pending_ids[ $feedback->get_id() ] ); } } /** * @param int $id * * @return bool */ private function exceeds_max_attempts( $id ) { return isset( $this->pending_ids[ $id ] ) && $this->pending_ids[ $id ] >= self::MAX_ATTEMPTS_TO_SYNC; } /** @param int $id */ private function increment_attempts( $id ) { if ( ! isset( $this->pending_ids[ $id ] ) ) { $this->pending_ids[ $id ] = 1; } else { $this->pending_ids[ $id ]++; } } } translation-feedback/xml-rpc/wpml-tf-xml-rpc-feedback-update.php 0000755 00000003346 14720342453 0020704 0 ustar 00 <?php /** * Class WPML_TF_XML_RPC_Feedback_Update * * @author OnTheGoSystems */ class WPML_TF_XML_RPC_Feedback_Update { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_TP_Project $tp_project */ private $tp_project; public function __construct( WPML_TF_Data_Object_Storage $feedback_storage, WPML_TP_Project $tp_project ) { $this->feedback_storage = $feedback_storage; $this->tp_project = $tp_project; } public function set_status( array $args ) { if ( $this->valid_arguments( $args ) ) { $feedback = $this->get_feedback( $args['feedback']['id'] ); if ( $feedback ) { /** @var WPML_TF_Feedback $feedback */ $feedback->set_status( $args['feedback']['status'] ); $this->feedback_storage->persist( $feedback ); } } } /** * @param array $args * * @return bool */ private function valid_arguments( array $args ) { $valid = false; if ( isset( $args['feedback']['id'], $args['feedback']['status'], $args['authorization_hash'] ) ) { $expected_hash = sha1( $this->tp_project->get_id() . $this->tp_project->get_access_key() . $args['feedback']['id'] ); if ( $expected_hash === $args['authorization_hash'] ) { $valid = true; } } return $valid; } /** * @param int $tp_feedback_id * * @return null|WPML_TF_Feedback */ private function get_feedback( $tp_feedback_id ) { $feedback = null; $filter_args = array( 'tp_feedback_id' => $tp_feedback_id, ); $collection_filter = new WPML_TF_Feedback_Collection_Filter( $filter_args ); $collection = $this->feedback_storage->get_collection( $collection_filter ); if ( $collection->count() ) { $feedback = $collection->current(); } return $feedback; } } translation-feedback/styles/wpml-tf-backend-styles.php 0000755 00000000645 14720342453 0017170 0 ustar 00 <?php /** * Class WPML_TF_Backend_Styles * * @author OnTheGoSystems */ class WPML_TF_Backend_Styles { const HANDLE = 'wpml-tf-backend'; /** * method enqueue */ public function enqueue() { $style = ICL_PLUGIN_URL . '/res/css/translation-feedback/backend-feedback-list.css'; wp_register_style( self::HANDLE, $style, array( 'otgs-icons' ), ICL_SITEPRESS_VERSION ); wp_enqueue_style( self::HANDLE ); } } translation-feedback/styles/wpml-tf-backend-options-styles.php 0000755 00000000651 14720342453 0020656 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_Styles * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_Styles { const HANDLE = 'wpml-tf-backend-options'; /** * method enqueue */ public function enqueue() { $style = ICL_PLUGIN_URL . '/res/css/translation-feedback/backend-options.css'; wp_register_style( self::HANDLE, $style, array(), ICL_SITEPRESS_VERSION ); wp_enqueue_style( self::HANDLE ); } } translation-feedback/styles/wpml-tf-frontend-styles.php 0000755 00000000656 14720342453 0017422 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Styles * * @author OnTheGoSystems */ class WPML_TF_Frontend_Styles { const HANDLE = 'wpml-tf-frontend'; /** * method enqueue */ public function enqueue() { $style = ICL_PLUGIN_URL . '/res/css/translation-feedback/front-style.css'; wp_register_style( self::HANDLE, $style, array( 'otgs-dialogs', 'otgs-icons' ), ICL_SITEPRESS_VERSION ); wp_enqueue_style( self::HANDLE ); } } translation-feedback/scripts/wpml-tf-frontend-scripts.php 0000755 00000000733 14720342453 0017726 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Scripts * * @author OnTheGoSystems */ class WPML_TF_Frontend_Scripts { const HANDLE = 'wpml-tf-frontend'; /** * method enqueue */ public function enqueue() { $script = ICL_PLUGIN_URL . '/res/js/translation-feedback/wpml-tf-frontend-script.js'; wp_register_script( self::HANDLE, $script, array(), ICL_SITEPRESS_VERSION ); wp_script_add_data( self::HANDLE, 'strategy', 'defer' ); wp_enqueue_script( self::HANDLE ); } } translation-feedback/scripts/wpml-tf-backend-scripts.php 0000755 00000000661 14720342453 0017476 0 ustar 00 <?php /** * Class WPML_TF_Backend_Scripts * * @author OnTheGoSystems */ class WPML_TF_Backend_Scripts { const HANDLE = 'wpml-tf-backend'; /** * method enqueue */ public function enqueue() { $script = ICL_PLUGIN_URL . '/res/js/translation-feedback/wpml-tf-backend-script.js'; wp_register_script( self::HANDLE, $script, array( 'jquery', 'wp-util' ), ICL_SITEPRESS_VERSION ); wp_enqueue_script( self::HANDLE ); } } translation-feedback/scripts/wpml-tf-backend-options-scripts.php 0000755 00000000721 14720342453 0021164 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_Scripts * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_Scripts { const HANDLE = 'wpml-tf-backend-options'; /** * method enqueue */ public function enqueue() { $script = ICL_PLUGIN_URL . '/res/js/translation-feedback/wpml-tf-backend-options-script.js'; wp_register_script( self::HANDLE, $script, array( 'jquery', 'wp-util' ), ICL_SITEPRESS_VERSION ); wp_enqueue_script( self::HANDLE ); } } translation-feedback/factories/wpml-tf-xml-rpc-feedback-update-factory.php 0000755 00000001240 14720342453 0022735 0 ustar 00 <?php /** * Class WPML_TF_XML_RPC_Feedback_Update_Factory * * @author OnTheGoSystems */ class WPML_TF_XML_RPC_Feedback_Update_Factory { /** @return WPML_TF_XML_RPC_Feedback_Update */ public function create() { global $sitepress; $translation_service = $sitepress->get_setting( 'translation_service' ); $translation_projects = $sitepress->get_setting( 'icl_translation_projects' ); $tp_project = new WPML_TP_Project( $translation_service, $translation_projects ); $feedback_storage = new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ); return new WPML_TF_XML_RPC_Feedback_Update( $feedback_storage, $tp_project ); } } translation-feedback/factories/wpml-tf-backend-feedback-list-view-factory.php 0000755 00000001543 14720342453 0023411 0 ustar 00 <?php /** * Class WPML_TF_Backend_Feedback_List_View_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Feedback_List_View_Factory { public function create() { /** @var SitePress $sitepress*/ global $sitepress; $template_loader = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . WPML_TF_Backend_Feedback_List_View::TEMPLATE_FOLDER ) ); $feedback_query = new WPML_TF_Feedback_Query( new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ), new WPML_TF_Data_Object_Storage( new WPML_TF_Message_Post_Convert() ), new WPML_TF_Collection_Filter_Factory() ); return new WPML_TF_Backend_Feedback_List_View( $template_loader->get_template(), $feedback_query, new WPML_Admin_Pagination(), new WPML_Admin_Table_Sort(), new WPML_TF_Feedback_Page_Filter( $sitepress, $feedback_query ) ); } } translation-feedback/factories/wpml-tf-tp-ratings-synchronize-factory.php 0000755 00000000733 14720342453 0023020 0 ustar 00 <?php /** * Class WPML_TF_TP_Ratings_Synchronize * * @author OnTheGoSystems */ class WPML_TF_TP_Ratings_Synchronize_Factory { /** * @return WPML_TF_TP_Ratings_Synchronize */ public function create() { $tp_client_factory = new WPML_TP_Client_Factory(); $tp_client = $tp_client_factory->create(); return new WPML_TF_TP_Ratings_Synchronize( new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ), $tp_client->ratings() ); } } translation-feedback/factories/wpml-tf-backend-bulk-actions-factory.php 0000755 00000000616 14720342453 0022337 0 ustar 00 <?php /** * Class WPML_TF_Backend_Bulk_Actions_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Bulk_Actions_Factory { /** * @return WPML_TF_Backend_Bulk_Actions */ public function create() { return new WPML_TF_Backend_Bulk_Actions( new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ), new WPML_WP_API(), new WPML_TF_Backend_Notices() ); } } translation-feedback/factories/action-loaders/wpml-backend-ajax-feedback-edit-hooks-factory.php 0000755 00000002425 14720342453 0026752 0 ustar 00 <?php /** * Class WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory extends WPML_AJAX_Base_Factory implements IWPML_Backend_Action_Loader { const AJAX_ACTION = 'wpml-tf-backend-feedback-edit'; public function create() { $hooks = null; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { $feedback_storage = new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ); $message_storage = new WPML_TF_Data_Object_Storage( new WPML_TF_Message_Post_Convert() ); $feedback_query = new WPML_TF_Feedback_Query( $feedback_storage, $message_storage, new WPML_TF_Collection_Filter_Factory() ); $feedback_edit = new WPML_TF_Feedback_Edit( $feedback_query, $feedback_storage, $message_storage, class_exists( 'WPML_TP_Client_Factory' ) ? new WPML_TP_Client_Factory() : null ); $template_loader = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . WPML_TF_Backend_Feedback_Row_View::TEMPLATE_FOLDER ) ); $row_view = new WPML_TF_Backend_Feedback_Row_View( $template_loader->get_template() ); $hooks = new WPML_TF_Backend_AJAX_Feedback_Edit_Hooks( $feedback_edit, $row_view, $_POST ); } return $hooks; } } translation-feedback/factories/action-loaders/wpml-tf-backend-hooks-factory.php 0000755 00000000761 14720342453 0023774 0 ustar 00 <?php /** * Class WPML_TF_Backend_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Hooks_Factory implements IWPML_Backend_Action_Loader { /** * @return WPML_TF_Backend_Hooks */ public function create() { /** @var wpdb $wpdb */ global $wpdb; return new WPML_TF_Backend_Hooks( new WPML_TF_Backend_Bulk_Actions_Factory(), new WPML_TF_Backend_Feedback_List_View_Factory(), new WPML_TF_Backend_Styles(), new WPML_TF_Backend_Scripts(), $wpdb ); } } translation-feedback/factories/action-loaders/wpml-tf-xml-rpc-hooks-factory.php 0000755 00000000531 14720342453 0023762 0 ustar 00 <?php /** * Class WPML_TF_XML_RPC_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_XML_RPC_Hooks_Factory implements IWPML_Frontend_Action_Loader { /** @return WPML_TF_XML_RPC_Hooks */ public function create() { return new WPML_TF_XML_RPC_Hooks( new WPML_TF_XML_RPC_Feedback_Update_Factory(), new WPML_WP_API() ); } } translation-feedback/factories/action-loaders/wpml-tf-frontend-hooks-factory.php 0000755 00000005134 14720342453 0024223 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Frontend_Hooks_Factory implements IWPML_Frontend_Action_Loader, IWPML_Deferred_Action_Loader { /** @var WPML_Queried_Object $queried_object */ private $queried_object; /** @var WPML_TF_Frontend_Display_Requirements $display_requirements */ private $display_requirements; /** * WPML_TF_Frontend_Hooks_Factory constructor. * * @param WPML_Queried_Object $queried_object * @param WPML_TF_Frontend_Display_Requirements $display_requirements */ public function __construct( WPML_Queried_Object $queried_object = null, WPML_TF_Frontend_Display_Requirements $display_requirements = null ) { $this->queried_object = $queried_object; $this->display_requirements = $display_requirements; } /** * The frontend hooks must be loaded when the request has been parsed (in "wp") * to avoid unnecessary instantiation if the current page is not a translation * * @return string */ public function get_load_action() { return 'wp'; } /** * @return null|WPML_TF_Frontend_Hooks */ public function create() { global $sitepress; $hooks = null; if ( $this->get_display_requirements()->verify() ) { $template_loader = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . WPML_TF_Frontend_Feedback_View::TEMPLATE_FOLDER ) ); $template_service = $template_loader->get_template(); $settings_reader = new WPML_TF_Settings_Read(); /** @var WPML_TF_Settings $settings */ $settings = $settings_reader->get( 'WPML_TF_Settings' ); $frontend_feedback_form = new WPML_TF_Frontend_Feedback_View( $template_service, $sitepress, $this->get_queried_object(), $settings ); $hooks = new WPML_TF_Frontend_Hooks( $frontend_feedback_form, new WPML_TF_Frontend_Scripts(), new WPML_TF_Frontend_Styles() ); } return $hooks; } /** * @return WPML_Queried_Object */ private function get_queried_object() { global $sitepress; if ( ! $this->queried_object ) { $this->queried_object = new WPML_Queried_Object( $sitepress ); } return $this->queried_object; } /** * @return WPML_TF_Frontend_Display_Requirements */ private function get_display_requirements() { if ( ! $this->display_requirements ) { $settings_read = new WPML_TF_Settings_Read(); $settings = $settings_read->get( 'WPML_TF_Settings' ); /** @var WPML_TF_Settings $settings */ $this->display_requirements = new WPML_TF_Frontend_Display_Requirements( $this->get_queried_object(), $settings ); } return $this->display_requirements; } } translation-feedback/factories/action-loaders/wpml-tf-frontend-ajax-hooks-factory.php 0000755 00000001403 14720342453 0025137 0 ustar 00 <?php /** * Class WPML_TF_Frontend_AJAX_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Frontend_AJAX_Hooks_Factory extends WPML_AJAX_Base_Factory { const AJAX_ACTION = 'wpml-tf-frontend-feedback'; /** * @return IWPML_Action|null */ public function create() { /** @var SitePress $sitepress */ /** @var wpdb $wpdb */ global $sitepress, $wpdb; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { return new WPML_TF_Frontend_AJAX_Hooks( new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ), new WPML_TF_Document_Information( $sitepress ), new WPML_TF_Post_Rating_Metrics( $wpdb ), class_exists( 'WPML_TP_Client_Factory' ) ? new WPML_TP_Client_Factory() : null, $_POST ); } return null; } } translation-feedback/factories/action-loaders/wpml-tm-tf-feedback-list-hooks-factory.php 0000755 00000000652 14720342453 0025517 0 ustar 00 <?php /** * Class WPML_TM_TF_Feedback_List_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TM_TF_Feedback_List_Hooks_Factory extends WPML_Current_Screen_Loader_Factory { /** @return string */ public function get_screen_regex() { return '/wpml-translation-feedback-list/'; } /** @return WPML_TM_TF_Feedback_List_Hooks */ public function create_hooks() { return new WPML_TM_TF_Feedback_List_Hooks(); } } translation-feedback/factories/action-loaders/wpml-tf-wp-cron-event-factory.php 0000755 00000000601 14720342453 0023761 0 ustar 00 <?php /** * Class WPML_TF_Common_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_WP_Cron_Events_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { /** @return WPML_TF_WP_Cron_Events */ public function create() { return new WPML_TF_WP_Cron_Events( new WPML_TF_Settings_Read(), new WPML_TF_TP_Ratings_Synchronize_Factory() ); } } translation-feedback/factories/action-loaders/wpml-tf-common-hooks-factory.php 0000755 00000000634 14720342453 0023674 0 ustar 00 <?php /** * Class WPML_TF_Common_Hooks_Factory * @author OnTheGoSystems */ class WPML_TF_Common_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { /** * @return WPML_TF_Common_Hooks */ public function create() { $feedback_storage = new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ); return new WPML_TF_Common_Hooks( $feedback_storage ); } } translation-feedback/factories/action-loaders/wpml-tf-backend-promote-hooks-factory.php 0000755 00000001721 14720342453 0025454 0 ustar 00 <?php /** * Class WPML_TF_Backend_Promote_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Promote_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Deferred_Action_Loader { /** @return string */ public function get_load_action() { return 'wpml_after_tm_loaded'; } public function create() { /** @var SitePress $sitepress */ global $sitepress; $hooks = null; $settings_read = new WPML_TF_Settings_Read(); /** @var WPML_TF_Settings $tf_settings */ $tf_settings = $settings_read->get( 'WPML_TF_Settings' ); if ( ! $tf_settings->is_enabled() ) { $setup_complete = $sitepress->is_setup_complete(); $translation_service = new WPML_TF_Translation_Service( class_exists( 'WPML_TP_Client_Factory' ) ? new WPML_TP_Client_Factory() : null ); $hooks = new WPML_TF_Backend_Promote_Hooks( new WPML_TF_Promote_Notices( $sitepress ), $setup_complete, $translation_service ); } return $hooks; } } translation-feedback/factories/action-loaders/wpml-tf-translation-service-change-hooks-factory.php 0000755 00000000716 14720342453 0027624 0 ustar 00 <?php /** * Class WPML_TF_Translation_Service_Change_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Translation_Service_Change_Hooks_Factory implements IWPML_Backend_Action_Loader { /** @return WPML_TF_Translation_Service_Change_Hooks */ public function create() { return new WPML_TF_Translation_Service_Change_Hooks( new WPML_TF_Settings_Read(), new WPML_TF_Settings_Write(), new WPML_TF_TP_Ratings_Synchronize_Factory() ); } } translation-feedback/factories/action-loaders/wpml-tf-translation-queue-hooks-factory.php 0000755 00000001031 14720342453 0026054 0 ustar 00 <?php /** * Class WPML_TF_Translation_Queue_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Translation_Queue_Hooks_Factory extends WPML_Current_Screen_Loader_Factory { /** @return string */ protected function get_screen_regex() { return '/translations-queue/'; } /** @return WPML_TF_Translation_Queue_Hooks */ protected function create_hooks() { $feedback_storage = new WPML_TF_Data_Object_Storage( new WPML_TF_Feedback_Post_Convert() ); return new WPML_TF_Translation_Queue_Hooks( $feedback_storage ); } } translation-feedback/factories/action-loaders/wpml-tf-backend-options-hooks-factory.php 0000755 00000002370 14720342453 0025463 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_Hooks_Factory extends WPML_Current_Screen_Loader_Factory { /** @return string */ protected function get_screen_regex() { return '#' . WPML_PLUGIN_FOLDER . '/menu/languages#'; } /** @return null|WPML_TF_Backend_Options_Hooks */ protected function create_hooks() { /** @var SitePress $sitepress */ global $sitepress; if ( $sitepress->is_setup_complete() ) { $template_loader = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . WPML_TF_Backend_Options_View::TEMPLATE_FOLDER ) ); $settings_read = new WPML_TF_Settings_Read(); /** @var WPML_TF_Settings $tf_settings */ $tf_settings = $settings_read->get( 'WPML_TF_Settings' ); $options_view = new WPML_TF_Backend_Options_View( $template_loader->get_template(), $tf_settings, $sitepress ); $translation_service = new WPML_TF_Translation_Service( class_exists( 'WPML_TP_Client_Factory' ) ? new WPML_TP_Client_Factory() : null ); return new WPML_TF_Backend_Options_Hooks( $options_view, new WPML_TF_Backend_Options_Scripts(), new WPML_TF_Backend_Options_Styles(), $translation_service ); } return null; } } translation-feedback/factories/action-loaders/wpml-tm-tf-ajax-feedback-list-hooks-factory.php 0000755 00000000471 14720342453 0026437 0 ustar 00 <?php /** * Class WPML_TM_TF_AJAX_Feedback_List_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TM_TF_AJAX_Feedback_List_Hooks_Factory implements IWPML_AJAX_Action_Loader { /** @return WPML_TM_TF_Feedback_List_Hooks */ public function create() { return new WPML_TM_TF_Feedback_List_Hooks(); } } translation-feedback/factories/action-loaders/wpml-tf-backend-options-ajax-hooks-factory.php 0000755 00000001415 14720342453 0026403 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_AJAX_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_AJAX_Hooks_Factory extends WPML_AJAX_Base_Factory implements IWPML_Backend_Action_Loader { const AJAX_ACTION = 'wpml-tf-backend-options'; /** * @return IWPML_Action|null */ public function create() { global $sitepress; $hooks = null; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { $settings_read = new WPML_TF_Settings_Read(); /** @var WPML_TF_Settings $tf_settings */ $tf_settings = $settings_read->get( 'WPML_TF_Settings' ); $hooks = new WPML_TF_Backend_Options_AJAX_Hooks( $tf_settings, new WPML_TF_Settings_Write(), new WPML_TF_Promote_Notices( $sitepress ), $_POST ); } return $hooks; } } translation-feedback/factories/action-loaders/wpml-tf-backend-post-list-hooks-factory.php 0000755 00000001276 14720342453 0025732 0 ustar 00 <?php /** * Class WPML_TF_Backend_Post_List_Hooks_Factory * * @author OnTheGoSystems */ class WPML_TF_Backend_Post_List_Hooks_Factory extends WPML_Current_Screen_Loader_Factory { /** @return string */ protected function get_screen_regex() { return '/^edit$/'; } /** @return WPML_TF_Backend_Post_List_Hooks */ protected function create_hooks() { global $wpdb, $sitepress; $post_rating_metrics = new WPML_TF_Post_Rating_Metrics( $wpdb ); $document_information = new WPML_TF_Document_Information( $sitepress ); $backend_styles = new WPML_TF_Backend_Styles(); return new WPML_TF_Backend_Post_List_Hooks( $post_rating_metrics, $document_information, $backend_styles ); } } translation-feedback/views/wpml-tf-backend-feedback-row-view.php 0000755 00000007434 14720342453 0020763 0 ustar 00 <?php /** * Class WPML_TF_Backend_Feedback_Row_View */ class WPML_TF_Backend_Feedback_Row_View { const TEMPLATE_FOLDER = '/templates/translation-feedback/backend/'; const SUMMARY_TEMPLATE = 'feedback-list-page-table-row.twig'; const DETAILS_TEMPLATE = 'feedback-list-page-table-row-details.twig'; /** @var IWPML_Template_Service $template_service */ private $template_service; /** * WPML_TF_Backend_Feedback_Row_View constructor. * * @param IWPML_Template_Service $template_service */ public function __construct( IWPML_Template_Service $template_service ) { $this->template_service = $template_service; } /** @param WPML_TF_Feedback $feedback */ public function render_summary_row( WPML_TF_Feedback $feedback ) { $model = array( 'strings' => self::get_summary_strings(), 'has_admin_capabilities' => current_user_can( 'manage_options' ), 'feedback' => $feedback, ); return $this->template_service->show( $model, self::SUMMARY_TEMPLATE ); } /** @param WPML_TF_Feedback $feedback */ public function render_details_row( WPML_TF_Feedback $feedback ) { $model = array( 'strings' => self::get_details_strings(), 'has_admin_capabilities' => current_user_can( 'manage_options' ), 'feedback' => $feedback, ); return $this->template_service->show( $model, self::DETAILS_TEMPLATE ); } /** @return array */ public static function get_columns_strings() { return array( 'feedback' => __( 'Feedback', 'sitepress' ), 'rating' => __( 'Rating', 'sitepress' ), 'status' => __( 'Status', 'sitepress' ), 'document' => __( 'Translated post', 'sitepress' ), 'date' => __( 'Date', 'sitepress' ), ); } /** @return array */ public static function get_summary_strings() { return array( 'select_validation' => __( 'Select Validation', 'sitepress' ), 'inline_actions' => array( 'review' => __( 'Review', 'sitepress' ), 'trash' => __( 'Trash', 'sitepress' ), 'view_post' => __( 'View post', 'sitepress' ), 'untrash' => __( 'Restore', 'sitepress' ), 'delete' => __( 'Delete permanently', 'sitepress' ), ), 'columns' => self::get_columns_strings(), ); } /** @return array */ public static function get_details_strings() { return array( 'title' => __( 'Translation Feedback', 'sitepress' ), 'translation' => __( 'Translation:', 'sitepress' ), 'edit_translation' => __( 'Edit translation', 'sitepress' ), 'original_post' => __( 'Original post:', 'sitepress' ), 'rating' => __( 'Rating:', 'sitepress' ), 'feedback' => __( 'Feedback:', 'sitepress' ), 'edit_feedback' => __( 'Edit feedback', 'sitepress' ), 'status' => __( 'Status:', 'sitepress' ), 'refresh_status' => __( 'Check for updates', 'sitepress' ), 'translated_by' => __( 'Translated by:', 'sitepress' ), 'corrected_by' => __( 'Corrected by:', 'sitepress' ), 'reviewed_by' => __( 'Reviewed by:', 'sitepress' ), 'add_note_to_translator' => __( 'Add a note to the translator', 'sitepress' ), 'add_note_to_admin' => __( 'Add a note to the administrator', 'sitepress' ), 'communication' => __( 'Communication:', 'sitepress' ), 'reply_to_translator_label' => __( 'Reply to translator:', 'sitepress' ), 'reply_to_admin_label' => __( 'Reply to admin:', 'sitepress' ), 'cancel_button' => __( 'Cancel', 'sitepress' ), 'trash_button' => __( 'Trash', 'sitepress' ), 'translation_fixed_button' => __( 'Mark as fixed', 'sitepress' ), 'no_translator_available' => __( 'No translator available for this language pair.', 'sitepress' ), ); } } translation-feedback/views/wpml-tf-frontend-feedback-view.php 0000755 00000014314 14720342453 0020401 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Feedback_View * * @author OnTheGoSystems */ class WPML_TF_Frontend_Feedback_View { const TEMPLATE_FOLDER = '/templates/translation-feedback/frontend/'; const FORM_TEMPLATE = 'feedback-form.twig'; const OPEN_TEMPLATE = 'feedback-open-button.twig'; const CUSTOM_OPEN_LINK_TEMPLATE = 'feedback-custom-open-link.twig'; const JS_OPEN_NODE_CLASS = 'js-wpml-tf-feedback-icon'; /** @var IWPML_Template_Service $template_service */ private $template_service; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Queried_Object $queried_object */ private $queried_object; /** @var WPML_TF_Settings $settings */ private $settings; /** * WPML_TF_Frontend_Hooks constructor. * * @param IWPML_Template_Service $template_service * @param SitePress $sitepress * @param WPML_Queried_Object $queried_object * @param WPML_TF_Settings $settings */ public function __construct( IWPML_Template_Service $template_service, SitePress $sitepress, WPML_Queried_Object $queried_object, WPML_TF_Settings $settings ) { $this->template_service = $template_service; $this->sitepress = $sitepress; $this->queried_object = $queried_object; $this->settings = $settings; } /** * @return string */ public function render_form() { $jsAssets = [ includes_url() . 'js/jquery/jquery.min.js', includes_url() . 'js/jquery/jquery-migrate.min.js', includes_url() . 'js/jquery/ui/core.min.js', includes_url() . 'js/jquery/ui/mouse.min.js', includes_url() . 'js/jquery/ui/resizable.min.js', includes_url() . 'js/jquery/ui/draggable.min.js', includes_url() . 'js/jquery/ui/controlgroup.min.js', includes_url() . 'js/jquery/ui/checkboxradio.min.js', includes_url() . 'js/jquery/ui/button.min.js', includes_url() . 'js/jquery/ui/dialog.min.js', includes_url() . 'js/underscore.min.js', includes_url() . 'js/wp-util.min.js', ]; $model = array( 'strings' => array( 'dialog_title' => __( 'Rate translation', 'sitepress' ), 'thank_you_rating' => __( 'Thank you for your rating!', 'sitepress' ), 'thank_you_comment' => __( 'Thank you for your rating and comment!', 'sitepress' ), 'translated_from' => __( 'This page was translated from:', 'sitepress' ), 'please_rate' => __( 'Please rate this translation:', 'sitepress' ), 'your_rating' => __( 'Your rating:', 'sitepress' ), 'star5_title' => __( 'It is perfect!', 'sitepress' ), 'star4_title' => __( 'It is OK', 'sitepress' ), 'star3_title' => __( 'It could be improved', 'sitepress' ), 'star2_title' => __( 'I can see a lot of language errors', 'sitepress' ), 'star1_title' => __( "I can't understand anything", 'sitepress' ), 'change_rating' => __( 'Change', 'sitepress' ), 'error_examples' => __( 'Please give some examples of errors and how would you improve them:', 'sitepress' ), 'send_button' => __( 'Send', 'sitepress' ), 'honeypot_label' => __( 'If you are a human, do not fill in this field.', 'sitepress' ), ), 'flag_url' => $this->sitepress->get_flag_url( $this->queried_object->get_source_language_code() ), 'language_name' => $this->sitepress->get_display_language_name( $this->queried_object->get_source_language_code() ), 'document_id' => $this->queried_object->get_id(), 'document_type' => $this->queried_object->get_element_type(), 'action' => WPML_TF_Frontend_AJAX_Hooks_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_TF_Frontend_AJAX_Hooks_Factory::AJAX_ACTION ), 'source_url' => $this->queried_object->get_source_url(), 'asset_url' => [ 'js' => implode( ';', $jsAssets ), ], 'ajax' => array( 'url' => admin_url( 'admin-ajax.php', 'relative' ), ), ); return $this->template_service->show( $model, self::FORM_TEMPLATE ); } /** * @return string */ public function render_open_button() { $rendering = ''; if ( WPML_TF_Settings::BUTTON_MODE_CUSTOM !== $this->settings->get_button_mode() ) { $model = array( 'strings' => array( 'form_open_title' => __( 'Rate translation of this page', 'sitepress' ), ), 'wrapper_css_classes' => $this->get_wrapper_css_classes(), 'icon_css_class' => $this->get_icon_css_class(), ); $rendering = $this->template_service->show( $model, self::OPEN_TEMPLATE ); } return $rendering; } /** @return string */ private function get_wrapper_css_classes() { $base_classes = self::JS_OPEN_NODE_CLASS . ' wpml-tf-feedback-icon '; $class = $base_classes . 'wpml-tf-feedback-icon-left'; if ( WPML_TF_Settings::BUTTON_MODE_RIGHT === $this->settings->get_button_mode() ) { $class = $base_classes . 'wpml-tf-feedback-icon-right'; } return $class; } /** @return string */ private function get_icon_css_class() { $icon_style = $this->settings->get_icon_style(); $css_classes = self::get_icon_css_classes(); if ( array_key_exists( $icon_style, $css_classes ) ) { return $css_classes[ $icon_style ]; } return $css_classes[ WPML_TF_Settings::ICON_STYLE_LEGACY ]; } /** * @param string|array $args * * @return string */ public function render_custom_open_link( $args ) { $model = wp_parse_args( $args, self::get_default_arguments_for_open_link() ); $model['classes'] = self::JS_OPEN_NODE_CLASS . ' ' . $model['classes']; return $this->template_service->show( $model, self::CUSTOM_OPEN_LINK_TEMPLATE ); } /** @return array */ public static function get_default_arguments_for_open_link() { return array( 'title' => __( 'Rate translation of this page', 'sitepress' ), 'classes' => 'wpml-tf-feedback-custom-link', ); } /** @return array */ public static function get_icon_css_classes() { return array( WPML_TF_Settings::ICON_STYLE_LEGACY => 'otgs-ico-translation', WPML_TF_Settings::ICON_STYLE_STAR => 'otgs-ico-star', WPML_TF_Settings::ICON_STYLE_THUMBSUP => 'otgs-ico-thumbsup', WPML_TF_Settings::ICON_STYLE_BULLHORN => 'otgs-ico-bullhorn', WPML_TF_Settings::ICON_STYLE_COMMENT => 'otgs-ico-comment', WPML_TF_Settings::ICON_STYLE_QUOTE => 'otgs-ico-quote', ); } } translation-feedback/views/wpml-tf-backend-options-view.php 0000755 00000021464 14720342453 0020124 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_View * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_View { const TEMPLATE_FOLDER = '/templates/translation-feedback/backend/'; const TEMPLATE = 'options-ui.twig'; const MAX_EXPIRATION_QUANTITY = 10; /** @var IWPML_Template_Service $template_service */ private $template_service; /** @var WPML_TF_Settings $settings */ private $settings; /** @var SitePress $sitepress */ private $sitepress; /** * WPML_TF_Frontend_Hooks constructor. * * @param IWPML_Template_Service $template_service * @param WPML_TF_Settings $settings * @param SitePress $sitepress */ public function __construct( IWPML_Template_Service $template_service, WPML_TF_Settings $settings, SitePress $sitepress ) { $this->template_service = $template_service; $this->settings = $settings; $this->sitepress = $sitepress; } /** * @return string */ public function render() { $model = array( 'strings' => self::get_strings(), 'action' => WPML_TF_Backend_Options_AJAX_Hooks_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_TF_Backend_Options_AJAX_Hooks_Factory::AJAX_ACTION ), 'module_toggle' => $this->get_module_toggle(), 'button_modes' => $this->get_button_modes(), 'icon_styles' => $this->get_icon_styles(), 'languages_to' => $this->get_languages_to(), 'display_modes' => $this->get_display_modes(), 'expiration_modes' => $this->get_expiration_modes(), 'expiration_quantities' => $this->get_expiration_quantities(), 'expiration_units' => $this->get_expiration_units(), ); return $this->template_service->show( $model, self::TEMPLATE ); } /** * @return array */ public static function get_strings() { return array( 'section_title' => __( 'Translation Feedback', 'sitepress' ), 'button_mode_section_title' => __( 'Translation Feedback button on front-end:', 'sitepress' ), 'icon_style_section_title' => __( 'Icon style:', 'sitepress' ), 'languages_to_section_title' => __( 'Show Translation Feedback module for these languages:', 'sitepress' ), 'expiration_section_title' => __( 'Expiration date for Translation Feedback module:', 'sitepress' ), ); } /** * @return array */ private function get_module_toggle() { return array( 'value' => 1, 'label' => __( 'Enable Translation Feedback module', 'sitepress' ), 'selected' => $this->settings->is_enabled(), ); } /** * @return array */ private function get_button_modes() { $modes = array( WPML_TF_Settings::BUTTON_MODE_LEFT => array( 'value' => WPML_TF_Settings::BUTTON_MODE_LEFT, 'label' => __( 'Show on the left side of the screen', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::BUTTON_MODE_RIGHT => array( 'value' => WPML_TF_Settings::BUTTON_MODE_RIGHT, 'label' => __( 'Show on the right side of the screen', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::BUTTON_MODE_CUSTOM => array( 'value' => WPML_TF_Settings::BUTTON_MODE_CUSTOM, 'label' => __( 'I will add it manually (%1$sinstructions%2$s)', 'sitepress' ), 'link' => 'https://wpml.org/wpml-hook/wpml_tf_feedback_open_link/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore', 'selected' => false, ), WPML_TF_Settings::BUTTON_MODE_DISABLED => array( 'value' => WPML_TF_Settings::BUTTON_MODE_DISABLED, 'label' => __( 'Do not show it', 'sitepress' ), 'selected' => false, ), ); if ( isset( $modes[ $this->settings->get_button_mode() ] ) ) { $modes[ $this->settings->get_button_mode() ]['selected'] = true; } return $modes; } private function get_icon_styles() { $css_classes = WPML_TF_Frontend_Feedback_View::get_icon_css_classes(); $styles = array( WPML_TF_Settings::ICON_STYLE_LEGACY => array( 'value' => WPML_TF_Settings::ICON_STYLE_LEGACY, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_LEGACY ], 'selected' => false, ), WPML_TF_Settings::ICON_STYLE_STAR => array( 'value' => WPML_TF_Settings::ICON_STYLE_STAR, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_STAR ], 'selected' => false, ), WPML_TF_Settings::ICON_STYLE_THUMBSUP => array( 'value' => WPML_TF_Settings::ICON_STYLE_THUMBSUP, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_THUMBSUP ], 'selected' => false, ), WPML_TF_Settings::ICON_STYLE_BULLHORN => array( 'value' => WPML_TF_Settings::ICON_STYLE_BULLHORN, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_BULLHORN ], 'selected' => false, ), WPML_TF_Settings::ICON_STYLE_COMMENT => array( 'value' => WPML_TF_Settings::ICON_STYLE_COMMENT, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_COMMENT ], 'selected' => false, ), WPML_TF_Settings::ICON_STYLE_QUOTE => array( 'value' => WPML_TF_Settings::ICON_STYLE_QUOTE, 'image_class' => $css_classes[ WPML_TF_Settings::ICON_STYLE_QUOTE ], 'selected' => false, ), ); if ( isset( $styles[ $this->settings->get_icon_style() ] ) ) { $styles[ $this->settings->get_icon_style() ]['selected'] = true; } return $styles; } /** * @return array */ private function get_languages_to() { $languages_to = array(); $active_languages = $this->sitepress->get_active_languages(); $allowed_languages = $this->settings->get_languages_to(); if ( null === $allowed_languages ) { $allowed_languages = array_keys( $active_languages ); } foreach ( $active_languages as $code => $language ) { $languages_to[ $code ] = array( 'value' => $code, 'label' => $language['display_name'], 'flag_url' => $this->sitepress->get_flag_url( $code ), 'selected' => false, ); if ( in_array( $code, $allowed_languages ) ) { $languages_to[ $code ]['selected'] = true; } } return $languages_to; } /** * @return array */ private function get_display_modes() { $modes = array( WPML_TF_Settings::DISPLAY_CUSTOM => array( 'value' => WPML_TF_Settings::DISPLAY_CUSTOM, 'label' => esc_html__( 'Ask for feedback about translated content that was %1$s in the last %2$s %3$s', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::DISPLAY_ALWAYS => array( 'value' => WPML_TF_Settings::DISPLAY_ALWAYS, 'label' => __( 'Always ask for feedback (no time limit for feedback)', 'sitepress' ), 'selected' => false, ), ); if ( isset( $modes[ $this->settings->get_display_mode() ] ) ) { $modes[ $this->settings->get_display_mode() ]['selected'] = true; } return $modes; } /** * @return array */ private function get_expiration_modes() { $modes = array( WPML_TF_Settings::EXPIRATION_ON_PUBLISH_OR_UPDATE => array( 'value' => WPML_TF_Settings::EXPIRATION_ON_PUBLISH_OR_UPDATE, 'label' => __( 'published or updated', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::EXPIRATION_ON_PUBLISH_ONLY => array( 'value' => WPML_TF_Settings::EXPIRATION_ON_PUBLISH_ONLY, 'label' => __( 'published', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::EXPIRATION_ON_UPDATE_ONLY => array( 'value' => WPML_TF_Settings::EXPIRATION_ON_UPDATE_ONLY, 'label' => __( 'updated', 'sitepress' ), 'selected' => false, ), ); if ( isset( $modes[ $this->settings->get_expiration_mode() ] ) ) { $modes[ $this->settings->get_expiration_mode() ]['selected'] = true; } return $modes; } /** * @return array */ private function get_expiration_quantities() { $quantities = array(); for ( $i = 1; $i < self::MAX_EXPIRATION_QUANTITY + 1; $i++ ) { $quantities[ $i ] = array( 'value' => $i, 'selected' => false, ); } if ( isset( $quantities[ $this->settings->get_expiration_delay_quantity() ] ) ) { $quantities[ $this->settings->get_expiration_delay_quantity() ]['selected'] = true; } return $quantities; } /** * @return array */ private function get_expiration_units() { $units = array( WPML_TF_Settings::DELAY_DAY => array( 'value' => WPML_TF_Settings::DELAY_DAY, 'label' => __( 'day(s)', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::DELAY_WEEK => array( 'value' => WPML_TF_Settings::DELAY_WEEK, 'label' => __( 'week(s)', 'sitepress' ), 'selected' => false, ), WPML_TF_Settings::DELAY_MONTH => array( 'value' => WPML_TF_Settings::DELAY_MONTH, 'label' => __( 'month(s)', 'sitepress' ), 'selected' => false, ), ); if ( isset( $units[ $this->settings->get_expiration_delay_unit() ] ) ) { $units[ $this->settings->get_expiration_delay_unit() ]['selected'] = true; } return $units; } } translation-feedback/views/wpml-tf-backend-feedback-list-view.php 0000755 00000016566 14720342453 0021135 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_TF_Backend_Feedback_List_View * * @author OnTheGoSystems */ class WPML_TF_Backend_Feedback_List_View { const TEMPLATE_FOLDER = '/templates/translation-feedback/backend/'; const TEMPLATE_NAME = 'feedback-list-page.twig'; const ITEMS_PER_PAGE = 20; /** @var IWPML_Template_Service $template_service */ private $template_service; /** @var WPML_TF_Feedback_Query $feedback_query */ private $feedback_query; /** @var WPML_Admin_Pagination $pagination */ private $pagination; /** @var WPML_Admin_Table_Sort $table_sort */ private $table_sort; /** @var WPML_TF_Feedback_Page_Filter $page_filter */ private $page_filter; /** * WPML_TF_Backend_Feedback_List_View constructor. * * @param IWPML_Template_Service $template_service * @param WPML_TF_Feedback_Query $feedback_query * @param WPML_Admin_Pagination $pagination * @param WPML_Admin_Table_Sort $table_sort * @param WPML_TF_Feedback_Page_Filter $page_filter */ public function __construct( IWPML_Template_Service $template_service, WPML_TF_Feedback_Query $feedback_query, WPML_Admin_Pagination $pagination, WPML_Admin_Table_Sort $table_sort, WPML_TF_Feedback_Page_Filter $page_filter ) { $this->template_service = $template_service; $this->feedback_query = $feedback_query; $this->pagination = $pagination; $this->table_sort = $table_sort; $this->page_filter = $page_filter; } /** @return string */ public function render_page() { $args = $this->parse_request_args(); $feedback_collection = $this->feedback_query->get( $args ); $model = array( 'strings' => $this->get_strings(), 'columns' => $this->get_columns(), 'pagination' => $this->get_pagination( $args ), 'page_filters' => $this->get_page_filters(), 'admin_page_hook' => WPML_TF_Backend_Hooks::PAGE_HOOK, 'current_query' => $this->get_current_query(), 'nonce' => wp_create_nonce( WPML_TF_Backend_Hooks::PAGE_HOOK ), 'ajax_action' => WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory::AJAX_ACTION, 'ajax_nonce' => wp_create_nonce( WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory::AJAX_ACTION ), 'has_admin_capabilities' => current_user_can( 'manage_options' ), 'is_in_trash' => $this->feedback_query->is_in_trash(), 'feedback_collection' => $feedback_collection, ); return $this->template_service->show( $model, self::TEMPLATE_NAME ); } /** @return array */ private function parse_request_args() { $args = array( 'paged' => 1, 'items_per_page' => self::ITEMS_PER_PAGE, ); if ( isset( $_GET['paged'] ) ) { $args['paged'] = filter_var( $_GET['paged'], FILTER_SANITIZE_NUMBER_INT ); } if ( isset( $_GET['order'], $_GET['orderby'] ) ) { $args['order'] = Sanitize::stringProp( 'order', $_GET ); $args['orderby'] = Sanitize::stringProp( 'orderby', $_GET ); } if ( isset( $_GET['status'] ) ) { $args['status'] = Sanitize::stringProp( 'status', $_GET ); } elseif ( isset( $_GET['language'] ) ) { $args['language'] = Sanitize::stringProp( 'language', $_GET ); } if ( isset( $_GET['post_id'] ) ) { $args['post_id'] = filter_var( $_GET['post_id'], FILTER_SANITIZE_NUMBER_INT ); } return $args; } /** @return array */ private function get_strings() { $strings = array( 'page_title' => __( 'Translation Feedback', 'sitepress' ), 'page_subtitle' => __( 'Issues with translations', 'sitepress' ), 'table_header' => WPML_TF_Backend_Feedback_Row_View::get_columns_strings(), 'screen_reader' => array( 'select_all' => __( 'Select All', 'sitepress' ), ), 'pagination' => array( 'list_navigation' => __( 'Feedback list navigation', 'sitepress' ), 'first_page' => __( 'First page', 'sitepress' ), 'previous_page' => __( 'Previous page', 'sitepress' ), 'next_page' => __( 'Next page', 'sitepress' ), 'last_page' => __( 'Last page', 'sitepress' ), 'current_page' => __( 'Current page', 'sitepress' ), 'of' => __( 'of', 'sitepress' ), ), 'bulk_actions' => array( 'select' => __( 'Select bulk action', 'sitepress' ), 'apply_button' => __( 'Apply', 'sitepress' ), 'options' => array( '-1' => __( 'Bulk Actions', 'sitepress' ), 'fixed' => __( 'Mark as fixed', 'sitepress' ), 'pending' => __( 'Mark as new', 'sitepress' ), 'trash' => __( 'Move to trash', 'sitepress' ), ), ), 'row_summary' => WPML_TF_Backend_Feedback_Row_View::get_summary_strings(), 'row_details' => WPML_TF_Backend_Feedback_Row_View::get_details_strings(), 'no_feedback' => __( 'No feedback found.', 'sitepress' ), ); if ( $this->feedback_query->is_in_trash() ) { $strings['bulk_actions']['options'] = array( '-1' => __( 'Bulk Actions', 'sitepress' ), 'untrash' => __( 'Restore', 'sitepress' ), 'delete' => __( 'Delete permanently', 'sitepress' ), ); } return $strings; } /** @return array */ private function get_columns() { $this->table_sort->set_primary_column( 'feedback' ); return array( 'feedback' => array( 'url' => $this->table_sort->get_column_url( 'feedback' ), 'classes' => $this->table_sort->get_column_classes( 'feedback' ), ), 'rating' => array( 'url' => $this->table_sort->get_column_url( 'rating' ), 'classes' => $this->table_sort->get_column_classes( 'rating' ), ), 'status' => array( 'url' => $this->table_sort->get_column_url( 'status' ), 'classes' => $this->table_sort->get_column_classes( 'status' ), ), 'document_url' => array( 'url' => $this->table_sort->get_column_url( 'document_title' ), 'classes' => $this->table_sort->get_column_classes( 'document_title' ), ), 'date' => array( 'url' => $this->table_sort->get_column_url( 'date' ), 'classes' => $this->table_sort->get_column_classes( 'date' ), ), ); } /** * @param array $args * * @return array */ private function get_pagination( array $args ) { $this->pagination->set_total_items( $this->feedback_query->get_filtered_items_count() ); $this->pagination->set_items_per_page( $args['items_per_page'] ); $this->pagination->set_current_page( $args['paged'] ); return array( 'first_page' => $this->pagination->get_first_page_url(), 'previous_page' => $this->pagination->get_previous_page_url(), 'next_page' => $this->pagination->get_next_page_url(), 'last_page' => $this->pagination->get_last_page_url(), 'current_page' => $this->pagination->get_current_page(), 'total_pages' => $this->pagination->get_total_pages(), 'total_items' => sprintf( _n( '%s item', '%s items', $this->pagination->get_total_items() ), $this->pagination->get_total_items() ), ); } /** @return array */ private function get_page_filters() { $this->page_filter->populate_counters_and_labels(); return array( 'all_and_trash' => $this->page_filter->get_all_and_trash_data(), 'statuses' => $this->page_filter->get_statuses_data(), 'languages' => $this->page_filter->get_languages_data(), ); } /** @return array */ private function get_current_query() { return array( 'filters' => $this->page_filter->get_current_filters(), 'sorters' => $this->table_sort->get_current_sorters(), ); } } translation-feedback/bulk-actions/wpml-tf-backend-bulk-actions.php 0000755 00000006754 14720342453 0021317 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_TF_Backend_Bulk_Actions * * @author OnTheGoSystems */ class WPML_TF_Backend_Bulk_Actions { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_WP_API $wp_api */ private $wp_api; /** @var WPML_TF_Backend_Notices $backend_notices */ private $backend_notices; /** * WPML_TF_Feedback_List_Bulk_Action_Hooks constructor. * * @param WPML_TF_Data_Object_Storage $feedback_storage * @param WPML_WP_API $wp_api * @param WPML_TF_Backend_Notices $backend_notices */ public function __construct( WPML_TF_Data_Object_Storage $feedback_storage, WPML_WP_API $wp_api, WPML_TF_Backend_Notices $backend_notices ) { $this->feedback_storage = $feedback_storage; $this->wp_api = $wp_api; $this->backend_notices = $backend_notices; } /** * Method bulk_action_callback */ public function process() { if ( $this->is_valid_request() && current_user_can( 'manage_options' ) ) { $feedback_ids = array_map( 'intval', $_GET['feedback_ids'] ); $bulk_action = Sanitize::stringProp( 'bulk_action', $_GET ); $updated_feedback_ids = $feedback_ids; switch ( $bulk_action ) { case 'trash': $this->delete( $feedback_ids ); break; case 'untrash': $this->untrash( $feedback_ids ); break; case 'delete': $this->delete( $feedback_ids, true ); break; default: $updated_feedback_ids = $this->change_status( $feedback_ids, $bulk_action ); break; } $this->backend_notices->add_bulk_updated_notice( $updated_feedback_ids, $bulk_action ); $this->redirect(); } else { $this->backend_notices->remove_bulk_updated_notice_after_display(); } } private function change_status( array $feedback_ids, $new_status ) { $updated_feedback_ids = array(); foreach ( $feedback_ids as $feedback_id ) { $feedback = $this->feedback_storage->get( $feedback_id ); if ( $feedback ) { /** @var WPML_TF_Feedback $feedback */ $feedback->set_status( $new_status ); $updated_feedback_ids[] = $this->feedback_storage->persist( $feedback ); } } return $updated_feedback_ids; } private function delete( array $feedback_ids, $force_delete = false ) { foreach ( $feedback_ids as $feedback_id ) { $this->feedback_storage->delete( $feedback_id, $force_delete ); } } private function untrash( array $feedback_ids ) { foreach ( $feedback_ids as $feedback_id ) { $this->feedback_storage->untrash( $feedback_id ); } } /** * @return bool */ private function is_valid_request() { $is_valid = false; if ( isset( $_GET['bulk_action'], $_GET['bulk_action2'] ) ) { if ( '-1' === $_GET['bulk_action'] ) { $_GET['bulk_action'] = $_GET['bulk_action2']; } if ( isset( $_GET['nonce'], $_GET['feedback_ids'] ) ) { $is_valid = $this->is_valid_action( $_GET['bulk_action'] ) && wp_verify_nonce( $_GET['nonce'], WPML_TF_Backend_Hooks::PAGE_HOOK ); } } return $is_valid; } private function is_valid_action( $action ) { return in_array( $action, array( 'pending', 'fixed', 'trash', 'untrash', 'delete' ), true ); } /** * Redirect after processing the bulk action */ private function redirect() { $args_to_remove = array( 'feedback_ids', 'bulk_action', 'bulk_action2' ); $url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); $url = remove_query_arg( $args_to_remove, $url ); $this->wp_api->wp_safe_redirect( $url ); } } translation-feedback/exceptions/wpml-tf-feedback-update-exception.php 0000755 00000000226 14720342453 0022111 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Update_Exception * * @author OnTheGoSystems */ class WPML_TF_Feedback_Update_Exception extends Exception { } translation-feedback/exceptions/wpml-tf-ajax-exception.php 0000755 00000000200 14720342453 0020020 0 ustar 00 <?php /** * Class WPML_TF_AJAX_Exception * * @author OnTheGoSystems */ class WPML_TF_AJAX_Exception extends Exception { } translation-feedback/utils/wpml-tf-post-rating-metrics.php 0000755 00000004435 14720342453 0020011 0 ustar 00 <?php /** * Class WPML_TF_Rating_Average * * @author OnTheGoSystems */ class WPML_TF_Post_Rating_Metrics { const QUANTITY_KEY = 'wpml_tf_post_rating_quantity'; const AVERAGE_KEY = 'wpml_tf_post_rating_average'; /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param int $post_id * * @return string */ public function get_display( $post_id ) { $quantity = (int) get_post_meta( $post_id, self::QUANTITY_KEY, true ); if ( 0 === $quantity ) { return ''; } $average = get_post_meta( $post_id, self::AVERAGE_KEY, true ); $title = sprintf( __( 'Average rating - %s', 'sitepress' ), $average ); $stars = round( $average ); $url = admin_url( 'admin.php?page=' . WPML_TF_Backend_Hooks::PAGE_HOOK . '&post_id=' . $post_id ); $output = '<div class="wpml-tf-rating" title="' . esc_attr( $title ) . '"><a href="'. esc_url( $url ) . '">'; for ( $i = 1; $i < 6; $i++ ) { $output .= '<span class="otgs-ico-star'; if ( $i <= $stars ) { $output .= ' full-star'; } $output .= '"></span>'; } $output .= '</a> <span class="rating-quantity">(' . esc_html( (string) $quantity ) . ')</span></div>'; return $output; } /** @param int $post_id */ public function refresh( $post_id ) { $document_id_key = WPML_TF_Data_Object_Storage::META_PREFIX . 'document_id'; $rating_key = WPML_TF_Data_Object_Storage::META_PREFIX . 'rating'; $subquery = $this->wpdb->prepare( "SELECT post_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND meta_value = %d", $document_id_key, $post_id ); $query = $this->wpdb->prepare( "SELECT meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = %s AND post_id IN( {$subquery} )", $rating_key ); $ratings = $this->wpdb->get_results( $query ); if ( is_array( $ratings ) && count( $ratings ) ) { $total = 0; foreach ( $ratings as $rating ) { $total += (int) $rating->meta_value; } $quantity = count( $ratings ); $average = round( $total / $quantity, 1 ); update_post_meta( $post_id, self::QUANTITY_KEY, $quantity ); update_post_meta( $post_id, self::AVERAGE_KEY, $average ); } else { delete_post_meta( $post_id, self::QUANTITY_KEY ); delete_post_meta( $post_id, self::AVERAGE_KEY ); } } } translation-feedback/utils/wpml-tf-translation-service.php 0000755 00000001464 14720342453 0020071 0 ustar 00 <?php /** * Class WPML_TF_Translation_Service * * @author OnTheGoSystems */ class WPML_TF_Translation_Service { /** @var WPML_TP_Client_Factory $tp_client_factory */ private $tp_client_factory; /** * WPML_TF_Translation_Service constructor. * * @param WPML_TP_Client_Factory $tp_client_factory */ public function __construct( WPML_TP_Client_Factory $tp_client_factory = null ) { $this->tp_client_factory = $tp_client_factory; } /** @return bool */ public function allows_translation_feedback() { if ( ! $this->tp_client_factory ) { return true; } $translation_service = $this->tp_client_factory->create()->services()->get_active(); if ( isset( $translation_service->translation_feedback ) && ! $translation_service->translation_feedback ) { return false; } return true; } } translation-feedback/utils/wpml-tf-backend-document-information.php 0000755 00000011300 14720342453 0021611 0 ustar 00 <?php /** * Class WPML_TF_Backend_Document_Information * * @author OnTheGoSystems */ class WPML_TF_Backend_Document_Information extends WPML_TF_Document_Information { /** @var WPML_TP_Client_Factory|null $tp_client_factory */ private $tp_client_factory; /** @var WPML_TP_Client|null $tp_client */ private $tp_client; /** * WPML_TF_Backend_Document_Information constructor. * * @param SitePress $sitepress * @param WPML_TP_Client_Factory $tp_client_factory */ public function __construct( SitePress $sitepress, WPML_TP_Client_Factory $tp_client_factory = null ) { parent::__construct( $sitepress ); $this->tp_client_factory = $tp_client_factory; } /** * @return false|null|string */ public function get_url() { $url = null; if ( $this->is_post_document() ) { $url = get_permalink( $this->id ); } return $url; } /** * @return null|string */ public function get_title() { $title = null; if ( $this->is_post_document() ) { $title = $this->get_post_title( $this->id ); } return $title; } /** * @return bool */ private function is_post_document() { return 0 === strpos( $this->type, 'post_' ); } /** * @param string $language_code * * @return string */ public function get_flag_url( $language_code ) { return $this->sitepress->get_flag_url( $language_code ); } /** * @return string */ public function get_edit_url() { $this->load_link_to_translation_tm_filters(); $url = 'post.php?' . http_build_query( array( 'lang' => $this->get_language(), 'action' => 'edit', 'post_type' => $this->type, 'post' => $this->id, ) ); return apply_filters( 'wpml_link_to_translation', $url, $this->get_source_id(), $this->get_language(), $this->get_trid() ); } private function load_link_to_translation_tm_filters() { if ( ! did_action( 'wpml_pre_status_icon_display' ) ) { do_action( 'wpml_pre_status_icon_display' ); } } /** * @return null|int */ public function get_source_id() { $source_id = null; $translations = $this->get_translations(); if ( isset( $translations[ $this->get_source_language() ]->element_id ) ) { $source_id = (int) $translations[ $this->get_source_language() ]->element_id; } return $source_id; } /** * @return null|string */ public function get_source_url() { $url = null; if ( $this->get_source_id() ) { $url = get_the_permalink( $this->get_source_id() ); } return $url; } /** * @return null|string */ public function get_source_title() { $title = null; if ( $this->get_source_id() ) { $title = $this->get_post_title( $this->get_source_id() ); } return $title; } /** * @return array|bool|mixed */ private function get_translations() { return $this->sitepress->get_element_translations( $this->get_trid(), $this->type ); } /** * @param int $job_id * * @return string */ public function get_translator_name( $job_id ) { $translation_job = $this->get_translation_job( $job_id ); if ( isset( $translation_job->translation_service ) ) { if ( 'local' === $translation_job->translation_service && isset( $translation_job->translator_id ) ) { $translator_name = get_the_author_meta( 'display_name', $translation_job->translator_id ); $translator_name .= ' (' . __( 'local translator', 'sitepress' ) . ')'; } else { $translator_name = __( 'Unknown remote translation service', 'sitepress' ); $tp_client = $this->get_tp_client(); if ( $tp_client ) { $translator_name = $tp_client->services()->get_name( $translation_job->translation_service ); } } } else { $post_author_id = get_post_field( 'post_author', $this->id ); $translator_name = get_the_author_meta( 'display_name', (int) $post_author_id ); } return $translator_name; } /** * @param string $from * @param string $to * * @return array */ public function get_available_translators( $from, $to ) { $translators = array(); if ( function_exists( 'wpml_tm_load_blog_translators' ) ) { $args = array( 'from' => $from, 'to' => $to, ); $users = wpml_tm_load_blog_translators()->get_blog_translators( $args ); $translators = wp_list_pluck( $users, 'display_name', 'ID' ); } return $translators; } /** * @param int $post_id * * @return null|string */ private function get_post_title( $post_id ) { $title = null; $post = get_post( $post_id ); if ( isset( $post->post_title ) ) { $title = $post->post_title; } return $title; } /** @return null|WPML_TP_Client */ private function get_tp_client() { if ( ! $this->tp_client && $this->tp_client_factory ) { $this->tp_client = $this->tp_client_factory->create(); } return $this->tp_client; } } translation-feedback/utils/wpml-tf-frontend-display-requirements.php 0000755 00000004160 14720342453 0022074 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Display_Requirements * * @author OnTheGoSystems */ class WPML_TF_Frontend_Display_Requirements { /** @var WPML_Queried_Object $queried_object */ private $queried_object; /** @var WPML_TF_Settings $settings */ private $settings; /** * WPML_TF_Frontend_Display_Requirements constructor. * * @param WPML_Queried_Object $queried_object * @param WPML_TF_Settings $settings */ public function __construct( WPML_Queried_Object $queried_object, WPML_TF_Settings $settings ) { $this->queried_object = $queried_object; $this->settings = $settings; } /** * @return bool */ public function verify() { return $this->is_enabled_on_frontend() && $this->is_translation() && $this->is_allowed_language() && $this->is_not_expired(); } /** * @return bool */ private function is_enabled_on_frontend() { return $this->settings->is_enabled() && $this->settings->get_button_mode() !== WPML_TF_Settings::BUTTON_MODE_DISABLED; } /** * @return bool */ private function is_translation() { return (bool) $this->queried_object->get_source_language_code(); } /** * @return bool */ private function is_allowed_language() { return is_array( $this->settings->get_languages_to() ) && in_array( $this->queried_object->get_language_code(), $this->settings->get_languages_to(), true ); } /** * @return bool */ private function is_not_expired() { $is_expired = false; if ( $this->settings->get_display_mode() === WPML_TF_Settings::DISPLAY_CUSTOM && $this->queried_object->is_post() ) { $post = get_post( $this->queried_object->get_id() ); $now = strtotime( 'now' ); if ( $this->settings->get_expiration_mode() === WPML_TF_Settings::EXPIRATION_ON_PUBLISH_ONLY ) { $post_date = strtotime( $post->post_date ); } else { $post_date = max( strtotime( $post->post_date ), strtotime( $post->post_modified ) ); } $post_age_in_days = ( $now - $post_date ) / DAY_IN_SECONDS; if ( $post_age_in_days > $this->settings->get_expiration_delay_in_days() ) { $is_expired = true; } } return ! $is_expired; } } translation-feedback/utils/wpml-tf-feedback-page-filter.php 0000755 00000013521 14720342453 0020013 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Page_Filter * * @author OnTheGoSystems */ class WPML_TF_Feedback_Page_Filter { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_TF_Feedback_Query $feedback_query */ private $feedback_query; /** @var array $statuses */ private $statuses = array(); /** @var array $languages */ private $languages = array(); /** @var array $url_args */ private $url_args; /** @var string $current_url */ private $current_url; /** * WPML_TF_Feedback_Page_Filter constructor. * * @param SitePress $sitepress * @param WPML_TF_Feedback_Query $feedback_query */ public function __construct( SitePress $sitepress, WPML_TF_Feedback_Query $feedback_query ) { $this->sitepress = $sitepress; $this->feedback_query = $feedback_query; } /** * @return array */ public static function get_filter_keys() { return array( 'status', 'language', 'post_id', ); } /** * Will not create filters inside the trash * And will not include the "trash" status in the status row */ public function populate_counters_and_labels() { if ( $this->feedback_query->is_in_trash() ) { return; } foreach ( $this->feedback_query->get_unfiltered_collection() as $feedback ) { /** @var WPML_TF_Feedback $feedback */ if ( 'trash' === $feedback->get_status() ) { continue; } if ( ! array_key_exists( $feedback->get_status(), $this->statuses ) ) { $this->statuses[ $feedback->get_status() ] = array( 'count' => 1, 'label' => $feedback->get_text_status(), ); } else { $this->statuses[ $feedback->get_status() ]['count']++; } if ( ! array_key_exists( $feedback->get_language_to(), $this->languages ) ) { $lang_details = $this->sitepress->get_language_details( $feedback->get_language_to() ); $this->languages[ $feedback->get_language_to() ] = array( 'count' => 1, 'label' => isset( $lang_details['display_name'] ) ? $lang_details['display_name'] : null, ); } else { $this->languages[ $feedback->get_language_to() ]['count']++; } } ksort( $this->statuses ); ksort( $this->languages ); } /** * @return array */ public function get_all_and_trash_data() { $main_data = array( 'all' => array( 'count' => $this->feedback_query->get_total_items_count(), 'label' => __( 'All', 'sitepress' ), 'url' => $this->get_reset_filters_url(), 'current' => $this->get_current_url() === $this->get_reset_filters_url(), ), ); if ( $this->feedback_query->get_total_trashed_items_count() ) { $main_data['trashed'] = array( 'count' => $this->feedback_query->get_total_trashed_items_count(), 'label' => __( 'Trash', 'sitepress' ), 'url' => $this->get_filter_url( 'status', 'trash' ), 'current' => $this->get_current_url() === $this->get_filter_url( 'status', 'trash' ), ); } if ( $this->is_current_filter( 'post_id' ) ) { $filters = $this->get_current_filters(); $post = get_post( $filters['post_id'] ); if ( isset( $post->post_title ) ) { $main_data['post_id'] = array( 'label' => sprintf( __( 'For "%s"', 'sitepress' ), mb_strimwidth( $post->post_title, 0, 40, '...' ) ), 'current' => true, ); } } return $main_data; } /** * @return array */ public function get_statuses_data() { foreach ( $this->statuses as $status => $data ) { $this->statuses[ $status ]['url'] = $this->get_filter_url( 'status', $status ); $this->statuses[ $status ]['current'] = false; if ( $this->is_current_filter( 'status', $status ) ) { $this->statuses[ $status ]['current'] = true; } } return $this->statuses; } /** * @return array */ public function get_languages_data() { foreach ( $this->languages as $language_code => $data ) { $this->languages[ $language_code ]['url'] = $this->get_filter_url( 'language', $language_code ); $this->languages[ $language_code ]['current'] = false; if ( $this->is_current_filter( 'language', $language_code ) ) { $this->languages[ $language_code ]['current'] = true; } } return $this->languages; } /** * @param string $filter_name * @param string $filter_value * * @return string */ private function get_filter_url( $filter_name, $filter_value ) { return add_query_arg( $filter_name, $filter_value, $this->get_reset_filters_url() ); } /** * @param string $filter_key * @param string $filter_value * * @return bool */ private function is_current_filter( $filter_key, $filter_value = null ) { $is_current_filter = false; $query_args = $this->get_url_args(); if ( array_key_exists( $filter_key, $query_args ) && ( ! $filter_value || $query_args[ $filter_key ] === $filter_value ) ) { $is_current_filter = true; } return $is_current_filter; } /** * @return array */ public function get_current_filters() { $filters = array(); $query_args = $this->get_url_args(); foreach ( self::get_filter_keys() as $filter_key ) { if ( array_key_exists( $filter_key, $query_args ) ) { $filters[ $filter_key ] = $query_args[ $filter_key ]; } } return $filters; } /** * @return array */ private function get_url_args() { if ( ! $this->url_args ) { $this->url_args = array(); $url_query = wpml_parse_url( $this->get_current_url(), PHP_URL_QUERY ); parse_str( $url_query, $this->url_args ); } return $this->url_args; } /** * @return string */ private function get_current_url() { if ( ! $this->current_url ) { $this->current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); $this->current_url = remove_query_arg( 'paged', $this->current_url ); } return $this->current_url; } /** * @return string */ private function get_reset_filters_url() { return remove_query_arg( self::get_filter_keys(), $this->get_current_url() ); } } translation-feedback/utils/wpml-tf-document-information.php 0000755 00000004446 14720342453 0020241 0 ustar 00 <?php /** * Class WPML_TF_Document_Information * * @author OnTheGoSystems */ class WPML_TF_Document_Information { /** @var SitePress $sitepress */ protected $sitepress; /** @var int $id */ protected $id; /** @var string $type */ protected $type; /** @var stdClass $language_details */ protected $language_details; /** @var null|int|stdClass */ private $translation_job; /** * WPML_TF_Document_Information constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param int $id * @param string $type */ public function init( $id, $type ) { $this->id = $id; $this->type = $type; $this->language_details = $this->sitepress->get_element_language_details( $this->id, $this->type ); } /** * @return string|null */ public function get_source_language() { return isset( $this->language_details->source_language_code ) ? $this->language_details->source_language_code : null; } /** * @return string */ public function get_language() { return isset( $this->language_details->language_code ) ? $this->language_details->language_code : null; } /** * @return null|int */ public function get_job_id() { $args = array( 'trid' => $this->get_trid(), 'language_code' => $this->get_language(), ); $job_id = apply_filters( 'wpml_translation_job_id', false, $args ); return $job_id ? $job_id : null; } /** * @return null|int */ protected function get_trid() { $trid = null; if ( isset( $this->language_details->trid ) ) { $trid = $this->language_details->trid; } return $trid; } /** * @param int $job_id * * @return bool */ public function is_local_translation( $job_id ) { $is_local = true; $translation_job = $this->get_translation_job( $job_id ); if ( isset( $translation_job->translation_service ) && 'local' !== $translation_job->translation_service ) { $is_local = false; } return $is_local; } /** * @param int $job_id * * @return int|stdClass */ protected function get_translation_job( $job_id ) { if ( ! $this->translation_job ) { $this->translation_job = apply_filters( 'wpml_get_translation_job', $job_id ); } return $this->translation_job; } } translation-feedback/model/iwpml-tf-data-object.php 0000755 00000000524 14720342453 0016357 0 ustar 00 <?php /** * Interface IWPML_TF_Data_Object * * @author OnTheGoSystems */ interface IWPML_TF_Data_Object { /** * @return int */ public function get_id(); /** * @return int|null */ public function get_feedback_id(); /** * @param \WPML_TF_Message $message */ public function add_message( WPML_TF_Message $message ); } translation-feedback/model/wpml-tf-data-object-storage.php 0000755 00000005772 14720342453 0017662 0 ustar 00 <?php /** * Class WPML_TF_Data_Object_Storage * * @author OnTheGoSystems */ class WPML_TF_Data_Object_Storage { const META_PREFIX = 'wpml_tf_'; /** @var WPML_TF_Data_Object_Post_Convert */ private $post_convert; /** * WPML_TF_Data_Object_Storage constructor. * * @param WPML_TF_Data_Object_Post_Convert $post_convert */ public function __construct( WPML_TF_Data_Object_Post_Convert $post_convert ) { $this->post_convert = $post_convert; } /** * @param int $id * * @return IWPML_TF_Data_Object|null */ public function get( $id ) { $result = null; $post_data = array(); $post_data['post'] = get_post( $id ); if ( $post_data['post'] ) { foreach ( $this->post_convert->get_meta_fields() as $meta_field ) { $post_data['metadata'][ $meta_field ] = get_post_meta( $id, self::META_PREFIX . $meta_field, true ); } $result = $this->post_convert->to_object( $post_data ); } return $result; } /** * @param IWPML_TF_Data_Object $data_object * * @return int|WP_Error */ public function persist( IWPML_TF_Data_Object $data_object ) { $post_data = $this->post_convert->to_post_data( $data_object ); /** @var int|WP_Error $updated_id */ $updated_id = wp_insert_post( $post_data['post'] ); if ( $updated_id && ! is_wp_error( $updated_id ) ) { foreach ( $post_data['metadata'] as $key => $value ) { update_post_meta( $updated_id, self::META_PREFIX . $key, $value ); } } return $updated_id; } /** * @param int $id * @param bool $force_delete */ public function delete( $id, $force_delete = false ) { if ( $force_delete ) { wp_delete_post( $id ); } else { wp_trash_post( $id ); } } /** @param int $id */ public function untrash( $id ) { wp_untrash_post( $id ); } /** * @param IWPML_TF_Collection_Filter $collection_filter * * @return WPML_TF_Collection */ public function get_collection( IWPML_TF_Collection_Filter $collection_filter ) { $collection = $collection_filter->get_new_collection(); $posts_args = $collection_filter->get_posts_args(); if ( isset( $posts_args['meta_query']['relation'] ) && 'OR' === $posts_args['meta_query']['relation'] ) { $object_posts = $this->get_posts_from_split_queries( $posts_args ); } else { $object_posts = get_posts( $posts_args ); } foreach ( $object_posts as $object_post ) { $collection->add( $this->get( $object_post->ID ) ); } return $collection; } /** * For more than 2 meta queries with "OR" relation, the standard WP query has a very bad performance. * It's much more efficient to make one query for each meta query. * * @param array $posts_args * * @return array */ private function get_posts_from_split_queries( array $posts_args ) { $object_posts = array(); $meta_query_parts = $posts_args['meta_query']; unset( $meta_query_parts['relation'] ); foreach ( $meta_query_parts as $meta_query ) { $posts_args['meta_query'] = $meta_query; $object_posts = $object_posts + get_posts( $posts_args ); } return $object_posts; } } translation-feedback/model/wpml-tf-collection.php 0000755 00000002244 14720342453 0016165 0 ustar 00 <?php /** * Class WPML_TF_Collection * * @author OnTheGoSystems */ class WPML_TF_Collection implements Iterator, Countable { /** @var array<\IWPML_TF_Data_Object> */ protected $collection = array(); /** * @param \IWPML_TF_Data_Object $data_object */ public function add( IWPML_TF_Data_Object $data_object ) { $this->collection[ $data_object->get_id() ] = $data_object; } /** * @return array */ public function get_ids() { return array_keys( $this->collection ); } /** * @param int $id * * @return IWPML_TF_Data_Object|null */ public function get( $id ) { return array_key_exists( $id, $this->collection ) ? $this->collection[ $id ] : null; } public function count(): int { return count( $this->collection ); } public function rewind(): void { reset( $this->collection ); } #[\ReturnTypeWillChange] public function current() { return current( $this->collection ); } #[\ReturnTypeWillChange] public function key() { return key( $this->collection ); } public function next(): void { next( $this->collection ); } /** * @return bool */ public function valid(): bool { return key( $this->collection ) !== null; } } translation-feedback/model/message/wpml-tf-message-collection.php 0000755 00000000220 14720342453 0021223 0 ustar 00 <?php /** * Class WPML_TF_Message_Collection * * @author OnTheGoSystems */ class WPML_TF_Message_Collection extends WPML_TF_Collection { } translation-feedback/model/message/wpml-tf-message.php 0000755 00000004021 14720342453 0017075 0 ustar 00 <?php /** * Class WPML_TF_Message * * @author OnTheGoSystems */ class WPML_TF_Message implements IWPML_TF_Data_Object { /** @var int $id */ private $id; /** @var int $feedback_id */ private $feedback_id; /** @var string $date_created */ private $date_created; /** @var string $content */ private $content; /** @var string $author_id */ private $author_id; /** * WPML_Translation_Feedback constructor. * * @param array $data */ public function __construct( $data = array() ) { $this->id = array_key_exists( 'id', $data ) ? (int) $data['id'] : null; $this->feedback_id = array_key_exists( 'feedback_id', $data ) ? (int) $data['feedback_id'] : null; $this->date_created = array_key_exists( 'date_created', $data ) ? sanitize_text_field( $data['date_created'] ) : null; $this->content = array_key_exists( 'content', $data ) ? sanitize_text_field( $data['content'] ) : null; $this->author_id = array_key_exists( 'author_id', $data ) ? (int) $data['author_id'] : null; } /** * @return int|mixed|null */ public function get_id() { return $this->id; } /** * @return int|null */ public function get_feedback_id() { return $this->feedback_id; } /** * @param \WPML_TF_Message $message */ public function add_message( WPML_TF_Message $message ) { return; } /** * @return mixed|null|string */ public function get_date_created() { return $this->date_created; } /** * @return mixed|null|string */ public function get_content() { return $this->content; } /** * @return int|null */ public function get_author_id() { return $this->author_id; } /** @return string */ public function get_author_display_label() { $label = __( 'Translator', 'sitepress' ); if ( user_can( $this->get_author_id(), 'manage_options' ) ) { $label = __( 'Admin', 'sitepress' ); } return $label; } /** @return bool */ public function author_is_current_user() { $current_user = wp_get_current_user(); return $current_user->ID === $this->author_id; } } translation-feedback/model/message/wpml-tf-message-collection-filter.php 0000755 00000002706 14720342453 0022521 0 ustar 00 <?php /** * Class WPML_TF_Message_Collection_Filter * * @author OnTheGoSystems */ class WPML_TF_Message_Collection_Filter implements IWPML_TF_Collection_Filter { /** @var int $feedback_id */ private $feedback_id; /** @var array|null */ private $feedback_ids; /** * WPML_TF_Feedback_Collection_Filter constructor. * * @param array $args */ public function __construct( array $args = array() ) { if ( isset( $args['feedback_id'] ) ) { $this->feedback_id = (int) $args['feedback_id']; } if ( isset( $args['feedback_ids'] ) && is_array( $args['feedback_ids'] ) ) { $this->feedback_ids = $args['feedback_ids']; } } /** * @return int|null */ private function get_feedback_id() { return $this->feedback_id; } /** * @return array|null */ private function get_feedback_ids() { return $this->feedback_ids; } /** * @return array */ public function get_posts_args() { $posts_args = array( 'posts_per_page' => -1, 'post_type' => WPML_TF_Message_Post_Convert::POST_TYPE, 'suppress_filters' => false, 'post_status' => 'any', ); if ( $this->get_feedback_id() ) { $posts_args['post_parent'] = $this->get_feedback_id(); } if ( $this->get_feedback_ids() ) { $posts_args['post_parent__in'] = $this->get_feedback_ids(); } return $posts_args; } /** * @return WPML_TF_Message_Collection */ public function get_new_collection() { return new WPML_TF_Message_Collection(); } } translation-feedback/model/message/wpml-tf-message-post-convert.php 0000755 00000002763 14720342453 0021551 0 ustar 00 <?php /** * Class WPML_TF_Message_Post_Convert * * @author OnTheGoSystems */ class WPML_TF_Message_Post_Convert extends WPML_TF_Data_Object_Post_Convert { const POST_TYPE = 'wpml_tf_message'; /** * @return array */ public function get_post_fields() { return array( 'id' => 'ID', 'date_created' => 'post_date', 'content' => 'post_content', 'feedback_id' => 'post_parent', ); } /** * @return array */ public function get_meta_fields() { return array( 'author_id', ); } /** * @param IWPML_TF_Data_Object $message * * @return array * @throws Exception */ public function to_post_data( IWPML_TF_Data_Object $message ) { if( ! $message instanceof WPML_TF_Message ) { throw new Exception( 'The $message argument must be an instance of WPML_TF_Message' ); } /** @var WPML_TF_Message $message */ $post = new stdClass(); $post->ID = $message->get_id(); $post->post_date = $message->get_date_created(); $post->post_content = $message->get_content(); $post->post_parent = $message->get_feedback_id(); $post->post_type = self::POST_TYPE; return array( 'post' => $post, 'metadata' => array( 'author_id' => $message->get_author_id(), ), ); } /** * @param array $post_data * * @return WPML_TF_Message */ public function to_object( array $post_data ) { $message_data = $this->build_object_data_for_constructor( $post_data ); return new WPML_TF_Message( $message_data ); } } translation-feedback/model/feedback/wpml-tf-feedback-status.php 0000755 00000005714 14720342453 0020630 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Status * * @author OnTheGoSystems */ class WPML_TF_Feedback_Status { /** @var string $status */ private $status = 'pending'; /** * WPML_TF_Feedback_Status constructor. * * @param string $status */ public function __construct( $status = null ) { if ( $status ) { $this->set_value( $status ); } } /** @param string $status*/ public function set_value( $status ) { $this->status = sanitize_text_field( $status ); } /** @return string */ public function get_value() { return $this->status; } /** @return null|string */ public function get_display_text() { switch ( $this->get_value() ) { case 'pending': return __( 'New', 'sitepress' ); case 'sent_to_translator': if ( $this->is_admin_user() ) { return __( 'Sent to translator', 'sitepress' ); } return __( 'New', 'sitepress' ); case 'translator_replied': if ( $this->is_admin_user() ) { return __( 'Translator replied', 'sitepress' ); } return __( 'Replied', 'sitepress' ); case 'admin_replied': if ( $this->is_admin_user() ) { return __( 'Sent to translator', 'sitepress' ); } return __( 'Admin replied', 'sitepress' ); case 'sent_to_ts_api': case 'sent_to_ts_manual': return __( 'Sent to translation service', 'sitepress' ); case 'sent_to_ts_email': return __( 'E-mail sent to translation service', 'sitepress' ); case 'fixed': return __( 'Translation fixed', 'sitepress' ); case 'publish': return __( 'Approved', 'sitepress' ); } return null; } /** @return bool */ private function is_admin_user() { return current_user_can( 'manage_options' ); } /** * This is used by the blue button on the feedback list * * @return array|null */ public function get_next_status() { if ( $this->is_admin_user() ) { switch ( $this->get_value() ) { case 'pending': case 'sent_to_translator': return array( 'value' => 'sent_to_translator', 'label' => __( 'Send to translator', 'sitepress' ), ); case 'translator_replied': return array( 'value' => 'admin_replied', 'label' => __( 'Reply to translator', 'sitepress' ), ); case 'admin_replied': return array( 'value' => 'admin_replied', 'label' => __( 'Send to translator', 'sitepress' ), ); } } else { switch ( $this->get_value() ) { case 'sent_to_translator': case 'translator_replied': case 'admin_replied': return array( 'value' => 'translator_replied', 'label' => __( 'Reply to admin', 'sitepress' ), ); } } return null; } /** @return bool */ public function is_pending() { $pending_statuses = array( 'pending' ); if ( current_user_can( 'manage_options' ) ) { $pending_statuses[] = 'translator_replied'; } else { $pending_statuses[] = 'admin_replied'; $pending_statuses[] = 'sent_to_translator'; } return in_array( $this->status, $pending_statuses, true ); } } translation-feedback/model/feedback/wpml-tf-tp-responses.php 0000755 00000006247 14720342453 0020227 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_TF_TP_Responses * * @author OnTheGoSystems */ class WPML_TF_TP_Responses { const FEEDBACK_FORWARD_MANUAL = 'manual'; const FEEDBACK_FORWARD_EMAIL = 'email'; const FEEDBACK_FORWARD_API = 'api'; const FEEDBACK_TP_URL_ENDPOINT = '/feedbacks/{feedback_id}/external'; /** * @var string|int $tp_rating_id * * - empty string for rating never sent (or problems occurred during TP transmission) * - 0 for local jobs (which does not need to be sent) * - positive integer for ratings already sent * * This will allow to have a shorter DB query to select feedback to be sent */ private $rating_id = ''; /** @var null|int $feedback_id */ private $feedback_id; /** @var null|string $feedback_forward_method */ private $feedback_forward_method; public function __construct( array $args = array() ) { if( isset( $args['tp_rating_id'] ) ) { $this->set_rating_id( $args['tp_rating_id'] ); } if( isset( $args['tp_feedback_id'] ) ) { $this->set_feedback_id( $args['tp_feedback_id'] ); } if( isset( $args['feedback_forward_method'] ) ) { $this->set_feedback_forward_method( $args['feedback_forward_method'] ); } } /** @param string|int $rating_id */ public function set_rating_id( $rating_id ) { if ( is_numeric( $rating_id ) ) { $rating_id = (int) $rating_id; } $this->rating_id = $rating_id; } /** @return string|int */ public function get_rating_id() { return $this->rating_id; } /** @param int $feedback_id */ public function set_feedback_id( $feedback_id ) { $this->feedback_id = (int) $feedback_id; } /** @return null|int */ public function get_feedback_id() { return $this->feedback_id; } /** @param string $method */ public function set_feedback_forward_method( $method ) { $this->feedback_forward_method = Sanitize::string( $method ); } /** @return null|string */ public function get_feedback_forward_method() { return $this->feedback_forward_method; } /** @return bool */ public function is_manual_feedback() { return self::FEEDBACK_FORWARD_MANUAL === $this->feedback_forward_method; } /** @return bool */ public function is_email_feedback() { return self::FEEDBACK_FORWARD_EMAIL === $this->feedback_forward_method; } /** @return bool */ public function is_api_feedback() { return self::FEEDBACK_FORWARD_API === $this->feedback_forward_method; } /** @return null|string */ public function get_feedback_tp_url() { $url = null; if ( $this->is_api_feedback() && defined( 'OTG_TRANSLATION_PROXY_URL' ) ) { $url = OTG_TRANSLATION_PROXY_URL . self::FEEDBACK_TP_URL_ENDPOINT; $url = preg_replace( '/{feedback_id}/', (string) $this->get_feedback_id(), $url ); } return $url; } /** @return array */ public function get_strings() { return array( 'display_for_manual' => __( '%1s cannot receive feedback about the translation automatically. Please log-in to %1s website and report these issues manually.', 'sitepress' ), 'display_for_email' => __( 'An email has been sent to %s to report the issue. Please check your email for a feedback from their part.', 'sitepress' ), 'display_for_api' => __( 'Issue tracking in %s', 'sitepress' ), ); } } translation-feedback/model/feedback/wpml-tf-feedback-collection-filter.php 0000755 00000010500 14720342453 0022710 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Collection_Filter * * @author OnTheGoSystems */ class WPML_TF_Feedback_Collection_Filter implements IWPML_TF_Collection_Filter { /** @var bool $exclude_rating_only */ private $exclude_rating_only; /** @var array $language_pairs */ private $language_pairs; /** @var int $pending_tp_ratings */ private $pending_tp_ratings; /** @var int tp_feedback_id */ private $tp_feedback_id; /** @var int $post_id */ private $post_id; /** @var int $reviewer_id */ private $reviewer_id; /** * WPML_TF_Feedback_Collection_Filter constructor. * * @param array $args */ public function __construct( array $args ) { if ( isset( $args['exclude_rating_only'] ) ) { $this->exclude_rating_only = $args['exclude_rating_only']; } if ( isset( $args['language_pairs'] ) ) { $this->language_pairs = $args['language_pairs']; } if ( isset( $args['pending_tp_ratings'] ) ) { $this->pending_tp_ratings = (int) $args['pending_tp_ratings']; } if ( isset( $args['tp_feedback_id'] ) ) { $this->tp_feedback_id = (int) $args['tp_feedback_id']; } if ( isset( $args['post_id'] ) ) { $this->post_id = (int) $args['post_id']; } if ( isset( $args['reviewer_id'] ) ) { $this->reviewer_id = (int) $args['reviewer_id']; } } /** @return null|bool */ private function get_exclude_rating_only() { return $this->exclude_rating_only; } /** @return null|array */ private function get_language_pairs() { return $this->language_pairs; } /** @return null|int */ private function get_pending_tp_ratings() { return $this->pending_tp_ratings; } /** @return null|int */ private function get_tp_feedback_id() { return $this->tp_feedback_id; } /** @return null|int */ private function get_reviewer_id() { return $this->reviewer_id; } /** @return null|int */ private function get_post_id() { return $this->post_id; } /** @return array */ public function get_posts_args() { $args = array( 'posts_per_page' => -1, 'post_type' => WPML_TF_Feedback_Post_Convert::POST_TYPE, 'suppress_filters' => false, 'post_status' => array( 'any', 'trash' ), ); if ( $this->get_exclude_rating_only() ) { $args['exclude_tf_rating_only'] = true; } if ( is_array( $this->get_language_pairs() ) ) { $meta_query = array( 'relation' => 'OR', ); foreach ( $this->get_language_pairs() as $from => $targets ) { foreach ( $targets as $to => $value ) { $meta_query[] = array( 'relation' => 'AND', array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'language_from', 'value' => $from, 'compare' => '=', ), array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'language_to', 'value' => $to, 'compare' => '=', ), ); } } $args['meta_query'] = $meta_query; } elseif ( $this->get_pending_tp_ratings() ) { $args['posts_per_page'] = $this->get_pending_tp_ratings(); $args['orderby'] = 'ID'; $args['order'] = 'ASC'; $args['meta_query'] = array( array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'tp_rating_id', 'value' => '', 'compare' => '=', 'type' => 'CHAR', ), ); } elseif ( $this->get_tp_feedback_id() ) { $args['posts_per_page'] = 1; $args['meta_query'] = array( array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'tp_feedback_id', 'value' => $this->get_tp_feedback_id(), 'compare' => '=', ), ); } elseif ( $this->get_post_id() ) { $args['meta_query'] = array( 'relation' => 'AND', array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'document_id', 'value' => $this->get_post_id(), 'compare' => '=', ), array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'document_type', 'value' => 'post_' . get_post_type( $this->get_post_id() ), 'compare' => '=', ), ); } elseif ( $this->get_reviewer_id() ) { $args['meta_query'] = array( array( 'key' => WPML_TF_Data_Object_Storage::META_PREFIX . 'reviewer_id', 'value' => $this->get_reviewer_id(), 'compare' => '=', ), ); } return $args; } /** @return WPML_TF_Feedback_Collection */ public function get_new_collection() { return new WPML_TF_Feedback_Collection(); } } translation-feedback/model/feedback/wpml-tf-feedback-collection.php 0000755 00000013231 14720342453 0021431 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Collection * * @author OnTheGoSystems */ class WPML_TF_Feedback_Collection extends WPML_TF_Collection { private $order; private $filter_value; /** * @param int $offset * @param int $length */ public function reduce_collection( $offset, $length ) { $this->collection = array_slice( $this->collection, $offset, $length, true ); } /** * @param string $property * @param string $order */ public function sort_collection( $property, $order ) { $this->order = $order; $method = 'compare_by_' . $property; if ( method_exists( $this, $method ) ) { // Use @ to avoid warnings in unit tests => see bug https://bugs.php.net/bug.php?id=50688 /** @phpstan-ignore-next-line Refactor class to support PHPStan */ @uasort( $this->collection, array( $this, $method ) ); } } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_pending( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( $a->is_pending() && ! $b->is_pending() ) { $compare = -1; } elseif ( ! $a->is_pending() && $b->is_pending() ) { $compare = 1; } else { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_feedback( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { $compare = strcasecmp( $a->get_content(), $b->get_content() ); } else { $compare = strcasecmp( $b->get_content(), $a->get_content() ); } if ( 0 === $compare ) { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_rating( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { $compare = $a->get_rating() - $b->get_rating(); } else { $compare = $b->get_rating() - $a->get_rating(); } if ( 0 === $compare ) { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_status( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { $compare = strcmp( $a->get_text_status(), $b->get_text_status() ); } else { $compare = strcmp( $b->get_text_status(), $a->get_text_status() ); } if ( 0 === $compare ) { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_document_title( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { $compare = strcasecmp( $a->get_document_information()->get_title(), $b->get_document_information()->get_title() ); } else { $compare = strcasecmp( $b->get_document_information()->get_title(), $a->get_document_information()->get_title() ); } if ( 0 === $compare ) { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return mixed */ private function compare_by_date( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { $compare = strtotime( $a->get_date_created() ) - strtotime( $b->get_date_created() ); } else { $compare = strtotime( $b->get_date_created() ) - strtotime( $a->get_date_created() ); } if ( 0 === $compare ) { $compare = $this->compare_by_id( $a, $b ); } return $compare; } /** * @param WPML_TF_Feedback $a * @param WPML_TF_Feedback $b * * @return int */ private function compare_by_id( WPML_TF_Feedback $a, WPML_TF_Feedback $b ) { if ( 'asc' === $this->order ) { return $a->get_id() - $b->get_id(); } else { return $b->get_id() - $a->get_id(); } } /** * @param WPML_TF_Message_Collection $message_collection */ public function link_messages_to_feedback( WPML_TF_Message_Collection $message_collection ) { foreach ( $message_collection as $message ) { if ( array_key_exists( $message->get_feedback_id(), $this->collection ) ) { $this->collection[ $message->get_feedback_id() ]->add_message( $message ); } } } /** * @param string $property * @param string $value */ public function filter_by( $property, $value ) { $this->filter_value = $value; $method = 'filter_by_' . $property; if ( method_exists( $this, $method ) ) { /** @phpstan-ignore-next-line Refactor class to support PHPStan */ $this->collection = array_filter( $this->collection, array( $this, $method ) ); } } /** * @param WPML_TF_Feedback $feedback * * @return bool */ private function filter_by_status( WPML_TF_Feedback $feedback ) { if ( $feedback->get_status() === $this->filter_value ) { return true; } return false; } /** * @param WPML_TF_Feedback $feedback * * @return bool */ private function filter_by_language( WPML_TF_Feedback $feedback ) { if ( $feedback->get_language_to() === $this->filter_value ) { return true; } return false; } /** * @param WPML_TF_Feedback $feedback * * @return bool */ private function filter_by_post_id( WPML_TF_Feedback $feedback ) { if ( 0 === strpos( $feedback->get_document_type(), 'post_' ) && $feedback->get_document_id() === (int) $this->filter_value ) { return true; } return false; } public function remove_trashed() { foreach ( $this->collection as $id => $feedback ) { /** @var WPML_TF_Feedback $feedback */ if ( 'trash' === $feedback->get_status() ) { unset( $this->collection[ $id ] ); } } } } translation-feedback/model/feedback/wpml-tf-feedback-post-convert.php 0000755 00000004570 14720342453 0021747 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Post_Convert * * @author OnTheGoSystems */ class WPML_TF_Feedback_Post_Convert extends WPML_TF_Data_Object_Post_Convert { const POST_TYPE = 'wpml_tf_feedback'; /** * @return array */ public function get_post_fields() { return array( 'id' => 'ID', 'date_created' => 'post_date', 'content' => 'post_content', 'status' => 'post_status', ); } /** * @return array */ public function get_meta_fields() { return array( 'rating', 'document_id', 'document_type', 'language_from', 'language_to', 'job_id', 'reviewer_id', 'tp_rating_id', 'tp_feedback_id', 'feedback_forward_method' ); } /** * @param IWPML_TF_Data_Object $feedback * * @return array * @throws Exception */ public function to_post_data( IWPML_TF_Data_Object $feedback ) { if( ! $feedback instanceof WPML_TF_Feedback ) { throw new Exception( 'The $feedback argument must be an instance of WPML_TF_Feedback' ); } /** @var WPML_TF_Feedback $feedback */ $post = new stdClass(); $post->ID = $feedback->get_id(); $post->post_date = $feedback->get_date_created(); $post->post_content = $feedback->get_content(); $post->post_status = $feedback->get_status(); $post->post_type = self::POST_TYPE; return array( 'post' => $post, 'metadata' => array( 'rating' => $feedback->get_rating(), 'document_id' => $feedback->get_document_id(), 'document_type' => $feedback->get_document_type(), 'language_from' => $feedback->get_language_from(), 'language_to' => $feedback->get_language_to(), 'job_id' => $feedback->get_job_id(), 'reviewer_id' => $feedback->get_reviewer()->get_id(), 'tp_rating_id' => $feedback->get_tp_responses()->get_rating_id(), 'tp_feedback_id' => $feedback->get_tp_responses()->get_feedback_id(), 'feedback_forward_method' => $feedback->get_tp_responses()->get_feedback_forward_method(), ), ); } /** * @param array $post_data * * @return WPML_TF_Feedback */ public function to_object( array $post_data ) { $feedback_data = $this->build_object_data_for_constructor( $post_data ); $feedback_factory = new WPML_TF_Feedback_Factory(); return $feedback_factory->create( $feedback_data ); } } translation-feedback/model/feedback/wpml-tf-feedback-reviewer.php 0000755 00000001222 14720342453 0021123 0 ustar 00 <?php /** * @author OnTheGoSystems */ class WPML_TF_Feedback_Reviewer { /** @var int $id WP_User ID */ private $id; /** * WPML_TF_Feedback_Reviewer constructor. * * @param int $id */ public function __construct( $id ) { $this->id = (int) $id; } /** * @return int|null */ public function get_id() { return $this->id; } /** * @return string */ public function get_reviewer_display_name() { $display_name = __( 'Unknown reviewer', 'sitepress' ); $reviewer = get_user_by( 'id', $this->get_id() ); if ( isset( $reviewer->display_name ) ) { $display_name = $reviewer->display_name; } return $display_name; } } translation-feedback/model/feedback/wpml-tf-feedback.php 0000755 00000015067 14720342453 0017311 0 ustar 00 <?php /** * Class WPML_TF_Feedback * * @author OnTheGoSystems */ class WPML_TF_Feedback implements IWPML_TF_Data_Object { /** @var int */ private $id; /** @var string */ private $date_created; /** @var WPML_TF_Feedback_Status */ private $status; /** @var int */ private $rating; /** @var string */ private $content; /** @var int */ private $document_id; /** @var string */ private $document_type; /** @var string */ private $language_from; /** @var string */ private $language_to; /** @var int|null */ private $job_id; /** @var WPML_TF_Feedback_Reviewer */ private $reviewer; /** @var WPML_TF_Collection $messages */ private $messages; /** @var WPML_TF_Backend_Document_Information $document_information */ private $document_information; /** @var WPML_TF_TP_Responses $tp_rating_responses */ private $tp_responses; /** * WPML_Translation_Feedback constructor. * * @param array $data * @param WPML_TF_Backend_Document_Information $document_information */ public function __construct( $data = array(), WPML_TF_Backend_Document_Information $document_information = null ) { $this->id = array_key_exists( 'id', $data ) ? (int) $data['id'] : null; $this->date_created = array_key_exists( 'date_created', $data ) ? sanitize_text_field( $data['date_created'] ) : null; $this->rating = array_key_exists( 'rating', $data ) ? (int) $data['rating'] : null; $this->content = array_key_exists( 'content', $data ) ? sanitize_text_field( $data['content'] ) : ''; $this->document_id = array_key_exists( 'document_id', $data ) ? (int) $data['document_id'] : null; $this->document_type = array_key_exists( 'document_type', $data ) ? sanitize_text_field( $data['document_type'] ) : null; $this->language_from = array_key_exists( 'language_from', $data ) ? sanitize_text_field( $data['language_from'] ) : null; $this->language_to = array_key_exists( 'language_to', $data ) ? sanitize_text_field( $data['language_to'] ) : null; $this->job_id = array_key_exists( 'job_id', $data ) ? (int) $data['job_id'] : null; $this->messages = array_key_exists( 'messages', $data ) && $data['messages'] instanceof WPML_TF_Collection ? $data['messages'] : new WPML_TF_Message_Collection(); if ( array_key_exists( 'reviewer_id', $data ) ) { $this->set_reviewer( $data['reviewer_id'] ); } $this->status = array_key_exists( 'status', $data ) ? new WPML_TF_Feedback_Status( $data['status'] ) : new WPML_TF_Feedback_Status( 'pending' ); $this->set_tp_responses( new WPML_TF_TP_Responses( $data ) ); if ( $document_information ) { $this->set_document_information( $document_information ); } } /** * @return int|mixed|null */ public function get_id() { return $this->id; } /** * @return int|null */ public function get_feedback_id() { return $this->id; } /** * @param \WPML_TF_Message $message */ public function add_message( WPML_TF_Message $message ) { $this->messages->add( $message ); } /** * @return mixed|null|string */ public function get_date_created() { return $this->date_created; } /** * @return string */ public function get_status() { return $this->status->get_value(); } /** * @param string $status */ public function set_status( $status ) { $this->status->set_value( $status ); } /** * @return int */ public function get_rating() { return $this->rating; } /** * @param int $rating */ public function set_rating( $rating ) { $this->rating = (int) $rating; } /** * @return mixed|null|string */ public function get_content() { return $this->content; } /** * @param string $content */ public function set_content( $content ) { $this->content = sanitize_text_field( $content ); } /** * @return int|null */ public function get_document_id() { return $this->document_id; } /** * @return null|string */ public function get_document_type() { return $this->document_type; } /** * @return null|string */ public function get_language_from() { return $this->language_from; } /** * @return null|string */ public function get_language_to() { return $this->language_to; } /** * @return int|null */ public function get_job_id() { return $this->job_id; } /** * @return WPML_TF_Feedback_Reviewer */ public function get_reviewer() { if ( ! isset( $this->reviewer ) ) { $this->set_reviewer( 0 ); } return $this->reviewer; } /** * @param int $reviewer_id */ public function set_reviewer( $reviewer_id ) { $this->reviewer = new WPML_TF_Feedback_Reviewer( $reviewer_id ); } /** * @return WPML_TF_Collection */ public function get_messages() { return $this->messages; } /** @param WPML_TF_TP_Responses $tp_responses */ public function set_tp_responses( WPML_TF_TP_Responses $tp_responses ) { $this->tp_responses = $tp_responses; } /** @return WPML_TF_TP_Responses */ public function get_tp_responses() { return $this->tp_responses; } /** * @return string|null */ public function get_text_status() { return $this->status->get_display_text(); } /** @return array */ public function get_next_status() { return $this->status->get_next_status(); } /** @return bool */ public function is_pending() { return $this->status->is_pending(); } /** * @return string */ public function get_document_flag_url() { return $this->document_information->get_flag_url( $this->get_language_to() ); } /** * @return string */ public function get_source_document_flag_url() { return $this->document_information->get_flag_url( $this->get_language_from() ); } /** * @return bool */ public function is_local_translation() { return $this->document_information->is_local_translation( $this->get_job_id() ); } /** * @return string */ public function get_translator_name() { return $this->document_information->get_translator_name( $this->get_job_id() ); } /** * @return array */ public function get_available_translators() { return $this->document_information->get_available_translators( $this->language_from, $this->language_to ); } /** * @param WPML_TF_Backend_Document_Information $document_information */ public function set_document_information( WPML_TF_Backend_Document_Information $document_information ) { $this->document_information = $document_information; $this->document_information->init( $this->get_document_id(), $this->get_document_type() ); } /** @return WPML_TF_Backend_Document_Information */ public function get_document_information() { return $this->document_information; } } translation-feedback/model/feedback/wpml-tf-feedback-factory.php 0000755 00000000773 14720342453 0020754 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Factory * * @author OnTheGoSystems */ class WPML_TF_Feedback_Factory { /** * @param array $feedback_data * * @return WPML_TF_Feedback */ public function create( array $feedback_data ) { global $sitepress; $document_information = new WPML_TF_Backend_Document_Information( $sitepress, class_exists( 'WPML_TP_Client_Factory' ) ? new WPML_TP_Client_Factory() : null ); return new WPML_TF_Feedback( $feedback_data, $document_information ); } } translation-feedback/model/iwpml-tf-collection-filter.php 0000755 00000000415 14720342453 0017617 0 ustar 00 <?php /** * Interface IWPML_TF_Collection_Filter * * @author OnTheGoSystems */ interface IWPML_TF_Collection_Filter { /** * @return array */ public function get_posts_args(); /** * @return WPML_TF_Collection */ public function get_new_collection(); } translation-feedback/model/wpml-tf-feedback-query.php 0000755 00000015232 14720342453 0016722 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Query * * @author OnTheGoSystems */ class WPML_TF_Feedback_Query { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_TF_Data_Object_Storage $message_storage */ private $message_storage; /** @var WPML_TF_Collection_Filter_Factory $collection_filter_factory */ private $collection_filter_factory; /** @var WPML_TF_Feedback_Collection $unfiltered_feedback_collection */ private $unfiltered_feedback_collection; /** @var int $unfiltered_items_count */ private $unfiltered_items_count; /** @var int $trashed_items_count */ private $trashed_items_count; /** @var int $total_items_count */ private $total_items_count; /** @var int $filtered_items_count */ private $filtered_items_count; /** @var bool $is_in_trash */ private $is_in_trash = false; /** * WPML_TF_Feedback_Collection_Factory constructor. * * @param WPML_TF_Data_Object_Storage $feedback_storage * @param WPML_TF_Data_Object_Storage $message_storage * @param WPML_TF_Collection_Filter_Factory $collection_filter_factory */ public function __construct( WPML_TF_Data_Object_Storage $feedback_storage, WPML_TF_Data_Object_Storage $message_storage, WPML_TF_Collection_Filter_Factory $collection_filter_factory ) { $this->feedback_storage = $feedback_storage; $this->message_storage = $message_storage; $this->collection_filter_factory = $collection_filter_factory; } /** * @return WPML_TF_Feedback_Collection */ public function get_unfiltered_collection() { if ( ! $this->unfiltered_feedback_collection ) { $storage_filters = array( 'exclude_rating_only' => true, ); if ( ! current_user_can( 'manage_options' ) ) { $storage_filters['reviewer_id'] = get_current_user_id(); } $feedback_collection_filter = $this->collection_filter_factory->create( 'feedback', $storage_filters ); $this->unfiltered_feedback_collection = $this->feedback_storage->get_collection( $feedback_collection_filter ); $this->unfiltered_items_count = count( $this->unfiltered_feedback_collection ); } return $this->unfiltered_feedback_collection; } /** * @param array $args * * @return WPML_TF_Feedback_Collection */ public function get( array $args ) { $feedback_collection = clone $this->get_unfiltered_collection(); $feedback_collection = $this->trash_filter_collection( $feedback_collection, $args ); $feedback_collection = $this->filter_collection( $feedback_collection, $args ); $feedback_collection = $this->sort_collection( $feedback_collection, $args ); $feedback_collection = $this->apply_pagination( $feedback_collection, $args ); $message_filter_args = array( 'feedback_ids' => $feedback_collection->get_ids() ); $message_collection_filter = $this->collection_filter_factory->create( 'message', $message_filter_args ); /** @var WPML_TF_Message_Collection $message_collection */ $message_collection = $this->message_storage->get_collection( $message_collection_filter ); $feedback_collection->link_messages_to_feedback( $message_collection ); return $feedback_collection; } /** * @param WPML_TF_Feedback_Collection $feedback_collection * @param array $args * * @return WPML_TF_Feedback_Collection */ public function trash_filter_collection( WPML_TF_Feedback_Collection $feedback_collection, array $args ) { if ( isset( $args['status'] ) && 'trash' === $args['status'] ) { $this->is_in_trash = true; $feedback_collection->filter_by( 'status', 'trash' ); $this->trashed_items_count = count( $feedback_collection ); $this->total_items_count = $this->unfiltered_items_count - $this->trashed_items_count; } else { $feedback_collection->remove_trashed(); $this->total_items_count = count( $feedback_collection ); $this->trashed_items_count = $this->unfiltered_items_count - $this->total_items_count; } return $feedback_collection; } /** * @param WPML_TF_Feedback_Collection $feedback_collection * @param array $args * * @return WPML_TF_Feedback_Collection */ private function filter_collection( WPML_TF_Feedback_Collection $feedback_collection, array $args ) { if ( isset( $args['status'] ) && 'trash' !== $args['status'] ) { $feedback_collection->filter_by( 'status', $args['status'] ); } elseif ( isset( $args['language'] ) ) { $feedback_collection->filter_by( 'language', $args['language'] ); } elseif ( isset( $args['post_id'] ) ) { $feedback_collection->filter_by( 'post_id', $args['post_id'] ); } $this->filtered_items_count = count( $feedback_collection ); return $feedback_collection; } /** * @param WPML_TF_Feedback_Collection $feedback_collection * @param array $args * * @return WPML_TF_Feedback_Collection */ private function sort_collection( WPML_TF_Feedback_Collection $feedback_collection, array $args ) { $order_by = 'pending'; $order = 'desc'; if ( isset( $args['orderby'] ) ) { $order_by = $args['orderby']; } if ( isset( $args['order'] ) ) { $order = $args['order']; } $feedback_collection->sort_collection( $order_by, $order ); return $feedback_collection; } /** * @param WPML_TF_Feedback_Collection $feedback_collection * @param array $args * * @return WPML_TF_Feedback_Collection */ private function apply_pagination( WPML_TF_Feedback_Collection $feedback_collection, array $args ) { if ( isset( $args['paged'], $args['items_per_page'] ) ) { $offset = $args['items_per_page'] * max( 0, $args['paged'] - 1 ); $feedback_collection->reduce_collection( $offset, $args['items_per_page'] ); } return $feedback_collection; } /** * @return int */ public function get_total_items_count() { return $this->total_items_count; } /** @return int */ public function get_total_trashed_items_count() { return $this->trashed_items_count; } /** @return int */ public function get_filtered_items_count() { return $this->filtered_items_count; } /** @return bool */ public function is_in_trash() { return $this->is_in_trash; } /** * @param int $feedback_id * @param bool $with_messages * * @return null|\IWPML_TF_Data_Object */ public function get_one( $feedback_id, $with_messages = true ) { $feedback = $this->feedback_storage->get( $feedback_id ); if ( $feedback && $with_messages ) { $filter_args = array( 'feedback_id' => $feedback_id, ); $filter = new WPML_TF_Message_Collection_Filter( $filter_args ); $messages = $this->message_storage->get_collection( $filter ); foreach ( $messages as $message ) { $feedback->add_message( $message ); } } return $feedback; } } translation-feedback/model/wpml-tf-data-object-post-convert.php 0000755 00000002176 14720342453 0020654 0 ustar 00 <?php /** * Class WPML_TF_Data_Object_Post_Convert * * @author OnTheGoSystems */ abstract class WPML_TF_Data_Object_Post_Convert { /** * @return array */ abstract public function get_post_fields(); /** * @return array */ abstract public function get_meta_fields(); /** * @param IWPML_TF_Data_Object $data_object * * @return array */ abstract public function to_post_data( IWPML_TF_Data_Object $data_object ); /** * @param array $post_data * * @return object */ abstract public function to_object( array $post_data ); /** * @param array $post_data * * @return array */ protected function build_object_data_for_constructor( array $post_data ) { $object_data = array(); foreach ( $this->get_post_fields() as $feedback_field => $post_field ) { $object_data[ $feedback_field ] = isset( $post_data['post']->{$post_field} ) ? $post_data['post']->{$post_field} : null; } foreach ( $this->get_meta_fields() as $meta_field ) { $object_data[ $meta_field ] = isset( $post_data['metadata'][ $meta_field ] ) ? $post_data['metadata'][ $meta_field ] : null; } return $object_data; } } translation-feedback/model/wpml-tf-collection-filter-factory.php 0000755 00000001000 14720342453 0021102 0 ustar 00 <?php class WPML_TF_Collection_Filter_Factory { /** * @param string $type * @param array $args * * @return null|IWPML_TF_Collection_Filter */ public function create( $type, array $args = array() ) { $collection_filter = null; switch ( $type ) { case 'feedback': $collection_filter = new WPML_TF_Feedback_Collection_Filter( $args ); break; case 'message': $collection_filter = new WPML_TF_Message_Collection_Filter( $args ); break; } return $collection_filter; } }translation-feedback/model/wpml-tf-feedback-edit.php 0000755 00000016701 14720342453 0016504 0 ustar 00 <?php /** * Class WPML_TF_Feedback_Edit * * @author OnTheGoSystems */ class WPML_TF_Feedback_Edit { /** @var WPML_TF_Feedback_Query */ private $feedback_query; /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_TF_Data_Object_Storage $message_storage */ private $message_storage; /** @var null|WPML_TP_Client_Factory $tp_client_factory */ private $tp_client_factory; /** @var null|WPML_TP_Client $tp_client */ private $tp_client; /** * WPML_TF_Feedback_Edit constructor. * * @param \WPML_TF_Feedback_Query $feedback_query * @param \WPML_TF_Data_Object_Storage $feedback_storage * @param \WPML_TF_Data_Object_Storage $message_storage * @param \WPML_TP_Client_Factory|null $tp_client_factory */ public function __construct( WPML_TF_Feedback_Query $feedback_query, WPML_TF_Data_Object_Storage $feedback_storage, WPML_TF_Data_Object_Storage $message_storage, WPML_TP_Client_Factory $tp_client_factory = null ) { $this->feedback_query = $feedback_query; $this->feedback_storage = $feedback_storage; $this->message_storage = $message_storage; $this->tp_client_factory = $tp_client_factory; } /** * @param int $feedback_id * @param array $args * * @return null|WPML_TF_Feedback * @throws \WPML_TF_Feedback_Update_Exception */ public function update( $feedback_id, array $args ) { $feedback = $this->feedback_query->get_one( $feedback_id ); if ( $feedback instanceof WPML_TF_Feedback ) { $this->update_feedback_content( $feedback, $args ); $this->add_message_to_feedback( $feedback, $args ); $this->assign_feedback_to_reviewer( $feedback, $args ); $this->update_feedback_status( $feedback, $args ); $this->feedback_storage->persist( $feedback ); $feedback = $this->feedback_query->get_one( $feedback_id, true ); } return $feedback; } /** * @param WPML_TF_Feedback $feedback * @param array $args */ private function update_feedback_content( WPML_TF_Feedback $feedback, array $args ) { if ( isset( $args['feedback_content'] ) && $this->is_admin_user() ) { $feedback->set_content( $args['feedback_content'] ); } } /** * @param WPML_TF_Feedback $feedback * @param array $args */ private function add_message_to_feedback( WPML_TF_Feedback $feedback, array $args ) { if ( isset( $args['message_content'] ) ) { $message_args = array( 'feedback_id' => $feedback->get_id(), 'content' => $args['message_content'], 'author_id' => get_current_user_id(), ); $message = new WPML_TF_Message( $message_args ); $feedback->add_message( $message ); $this->message_storage->persist( $message ); } } /** * @param WPML_TF_Feedback $feedback * @param array $args */ private function assign_feedback_to_reviewer( WPML_TF_Feedback $feedback, array $args ) { if ( isset( $args['feedback_reviewer_id'] ) && $this->is_admin_user() ) { $feedback->set_reviewer( $args['feedback_reviewer_id'] ); } } /** * @param WPML_TF_Feedback $feedback * @param array $args * * @throws \WPML_TF_Feedback_Update_Exception */ private function update_feedback_status( WPML_TF_Feedback $feedback, array $args ) { if ( isset( $args['feedback_status'] ) && in_array( $args['feedback_status'], $this->get_feedback_statuses(), true ) ) { if ( 'sent_to_translator' === $args['feedback_status'] && ! $feedback->is_local_translation() ) { $this->send_feedback_to_tp( $feedback ); } elseif ( 'sent_to_ts_api' === $args['feedback_status'] ) { $this->update_feedback_status_from_tp( $feedback ); } else { $feedback->set_status( $args['feedback_status'] ); } } } /** * @param int $feedback_id * * @return bool */ public function delete( $feedback_id ) { if ( $this->is_admin_user() ) { $this->feedback_storage->delete( $feedback_id ); return true; } return false; } /** @return bool */ private function is_admin_user() { return current_user_can( 'manage_options' ); } /** @return array */ private function get_feedback_statuses() { return array( 'pending', 'sent_to_translator', 'translator_replied', 'admin_replied', 'fixed', 'sent_to_ts_api', ); } /** * @param WPML_TF_Feedback $feedback * * @throws WPML_TF_Feedback_Update_Exception */ private function send_feedback_to_tp( WPML_TF_Feedback $feedback ) { $current_user = wp_get_current_user(); $args = array( 'email' => $current_user->user_email, ); $tp_feedback_id = $this->get_tp_client()->feedback()->send( $feedback, $args ); if ( ! $tp_feedback_id ) { throw new WPML_TF_Feedback_Update_Exception( $this->get_communication_error_message( 'send' ) ); } $feedback->get_tp_responses()->set_feedback_id( $tp_feedback_id ); $active_service = $this->get_tp_client()->services()->get_active(); $feedback->get_tp_responses()->set_feedback_forward_method( $active_service->get_feedback_forward_method() ); $new_status = 'sent_to_ts_' . $active_service->get_feedback_forward_method(); $feedback->set_status( $new_status ); } /** * @param WPML_TF_Feedback $feedback * * @throws WPML_TF_Feedback_Update_Exception */ private function update_feedback_status_from_tp( WPML_TF_Feedback $feedback ) { $tp_feedback_status = $this->get_tp_client()->feedback()->status( $feedback ); if ( ! $tp_feedback_status ) { throw new WPML_TF_Feedback_Update_Exception( $this->get_communication_error_message( 'status' ) ); } elseif ( 'closed' === $tp_feedback_status ) { $feedback->set_status( 'fixed' ); } } /** * @param string $endpoint * * @return string * @throws \WPML_TF_Feedback_Update_Exception */ private function get_communication_error_message( $endpoint ) { $active_service = $this->get_tp_client()->services()->get_active(); $service_name = isset( $active_service->name ) ? $active_service->name : esc_html__( 'Translation Service', 'sitepress' ); if ( 'send' === $endpoint ) { $error_message = sprintf( esc_html__( 'Could not send the report to %s.', 'sitepress' ), $service_name ); $error_message .= ' ' . sprintf( esc_html__( "This means that %s isn't yet aware of the problem in the translation and cannot fix it.", 'sitepress' ), $service_name ); } else { $error_message = sprintf( esc_html__( 'Could not fetch the status from %s.', 'sitepress' ), $service_name ); } $error_message .= ' ' . sprintf( esc_html__( "Let's get it working for you. Please contact %1sWPML support%2s and give them the following error details:", 'sitepress' ), '<a href="https://wpml.org/forums/forum/english-support/" target="_blank">', '</a>' ); $error_message .= '<br><div class="js-wpml-tf-error-details"><a href="#">' . esc_html__( 'Show details', 'sitepress' ) . '</a>' . '<pre style="display:none;">' . esc_html( $this->get_tp_client()->feedback()->get_error_message() ) . '</pre></div>'; return $error_message; } /** * @return null|WPML_TP_Client * * @throws WPML_TF_Feedback_Update_Exception */ private function get_tp_client() { if ( ! $this->tp_client && $this->tp_client_factory ) { $this->tp_client = $this->tp_client_factory->create(); if ( ! $this->tp_client ) { throw new WPML_TF_Feedback_Update_Exception( esc_html__( 'WPML cannot communicate with the remote translation service. Please make sure WPML Translation Management is active.', 'sitepress' ) ); } } return $this->tp_client; } } translation-feedback/wpml-tf-module.php 0000755 00000002663 14720342453 0014224 0 ustar 00 <?php /** * Class WPML_TF_Module * * @author OnTheGoSystems */ class WPML_TF_Module { /** @var WPML_Action_Filter_Loader $action_filter_loader */ private $action_filter_loader; /** @var WPML_TF_Settings $settings */ private $settings; /** * WPML_TF_Module constructor. * * @param WPML_Action_Filter_Loader $action_filter_loader * @param IWPML_TF_Settings $settings */ public function __construct( WPML_Action_Filter_Loader $action_filter_loader, IWPML_TF_Settings $settings ) { $this->action_filter_loader = $action_filter_loader; $this->settings = $settings; } public function run() { $this->action_filter_loader->load( $this->get_actions_to_load_always() ); if ( $this->settings->is_enabled() ) { $this->action_filter_loader->load( $this->get_actions_to_load_when_module_enabled() ); } } /** * @return array */ private function get_actions_to_load_always() { return array( 'WPML_TF_Backend_Options_Hooks_Factory', 'WPML_TF_Backend_Options_AJAX_Hooks_Factory', 'WPML_TF_Backend_Promote_Hooks_Factory', ); } /** * @return array */ private function get_actions_to_load_when_module_enabled() { return array( 'WPML_TF_Common_Hooks_Factory', 'WPML_TF_Backend_Hooks_Factory', 'WPML_TF_Frontend_Hooks_Factory', 'WPML_TF_Frontend_AJAX_Hooks_Factory', 'WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory', 'WPML_TF_Backend_Post_List_Hooks_Factory', ); } } translation-feedback/hooks/wpml-tf-translation-queue-hooks.php 0000755 00000002472 14720342453 0020661 0 ustar 00 <?php /** * Class WPML_TF_Translation_Queue_Hooks * * @author OnTheGoSystems */ class WPML_TF_Translation_Queue_Hooks implements IWPML_Action { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; public function __construct( WPML_TF_Data_Object_Storage $feedback_storage ) { $this->feedback_storage = $feedback_storage; } public function add_hooks() { add_filter( 'wpml_tm_allowed_translators_for_job', array( $this, 'add_reviewer_to_allowed_translators' ), 10, 2 ); } public function add_reviewer_to_allowed_translators( array $allowed_translators, WPML_Translation_Job $translation_job ) { $current_user_id = get_current_user_id(); if ( $translation_job->get_translator_id() !== $current_user_id ) { $filter_args = array( 'reviewer_id' => $current_user_id, ); $collection_filter = new WPML_TF_Feedback_Collection_Filter( $filter_args ); $feedback_collection = $this->feedback_storage->get_collection( $collection_filter ); foreach ( $feedback_collection as $feedback ) { /** @var WPML_TF_Feedback $feedback */ if ( $feedback->get_job_id() === (int) $translation_job->get_id() && 'fixed' !== $feedback->get_status() ) { $allowed_translators[] = $current_user_id; break; } } } return $allowed_translators; } } translation-feedback/hooks/wpml-tf-frontend-hooks.php 0000755 00000003130 14720342453 0017010 0 ustar 00 <?php /** * Class WPML_TF_Frontend_Hooks * * @author OnTheGoSystems */ class WPML_TF_Frontend_Hooks implements IWPML_Action { /** @var WPML_TF_Frontend_Feedback_View $feedback_view */ private $feedback_view; /** @var WPML_TF_Frontend_Scripts $scripts */ private $scripts; /** @var WPML_TF_Frontend_Styles $styles */ private $styles; /** * WPML_TF_Frontend_Hooks constructor. * * @param WPML_TF_Frontend_Feedback_View $feedback_view * @param WPML_TF_Frontend_Scripts $scripts * @param WPML_TF_Frontend_Styles $styles */ public function __construct( WPML_TF_Frontend_Feedback_View $feedback_view, WPML_TF_Frontend_Scripts $scripts, WPML_TF_Frontend_Styles $styles ) { $this->feedback_view = $feedback_view; $this->scripts = $scripts; $this->styles = $styles; } /** * method init */ public function add_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts_action' ) ); add_action( 'wp_footer', array( $this, 'render_feedback_form' ) ); add_action( 'wpml_tf_feedback_open_link', array( $this, 'render_custom_form_open_link' ) ); } /** * method enqueue_scripts_action */ public function enqueue_scripts_action() { $this->scripts->enqueue(); $this->styles->enqueue(); } /** * method render_feedback_form */ public function render_feedback_form() { echo $this->feedback_view->render_open_button(); echo $this->feedback_view->render_form(); } /** @param string|array $args */ public function render_custom_form_open_link( $args ) { echo $this->feedback_view->render_custom_open_link( $args ); } } translation-feedback/hooks/wpml-tf-backend-ajax-feedback-edit-hooks.php 0000755 00000004177 14720342453 0022162 0 ustar 00 <?php /** * Class WPML_TF_Backend_AJAX_Feedback_Edit_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_AJAX_Feedback_Edit_Hooks implements IWPML_Action { /** @var WPML_TF_Feedback_Edit $feedback_edit */ private $feedback_edit; /** @var WPML_TF_Backend_Feedback_Row_View $row_view */ private $row_view; /** @var array $post_data */ private $post_data; /** * WPML_TF_Backend_AJAX_Feedback_Edit_Hooks constructor. * * @param WPML_TF_Feedback_Edit $feedback_edit * @param WPML_TF_Backend_Feedback_Row_View $row_view * @param array $post_data */ public function __construct( WPML_TF_Feedback_Edit $feedback_edit, WPML_TF_Backend_Feedback_Row_View $row_view, array $post_data ) { $this->feedback_edit = $feedback_edit; $this->row_view = $row_view; $this->post_data = $post_data; } public function add_hooks() { add_action( 'wp_ajax_' . WPML_TF_Backend_AJAX_Feedback_Edit_Hooks_Factory::AJAX_ACTION, array( $this, 'edit_feedback_callback' ) ); } public function edit_feedback_callback() { try { $this->check_post_data_key( 'feedback_id' ); $feedback_id = filter_var( $this->post_data['feedback_id'], FILTER_SANITIZE_NUMBER_INT ); $feedback = $this->feedback_edit->update( $feedback_id, $this->post_data ); if ( ! $feedback ) { throw new WPML_TF_AJAX_Exception( esc_html__( 'Failed to update the feedback.', 'sitepress' ) ); } $response = array( 'summary_row' => $this->row_view->render_summary_row( $feedback ), 'details_row' => $this->row_view->render_details_row( $feedback ), ); wp_send_json_success( $response ); } catch ( WPML_TF_AJAX_Exception $e ) { wp_send_json_error( $e->getMessage() ); } catch ( WPML_TF_Feedback_Update_Exception $e ) { wp_send_json_error( $e->getMessage() ); } } /** * @param string $key * * @throws WPML_TF_AJAX_Exception */ private function check_post_data_key( $key ) { if ( ! isset( $this->post_data[ $key ] ) ) { $message = sprintf( esc_html__( 'Missing key "%s".', 'sitepress' ), $key ); throw new WPML_TF_AJAX_Exception( $message ); } } } translation-feedback/hooks/wpml-tf-xml-rpc-hooks.php 0000755 00000002255 14720342453 0016562 0 ustar 00 <?php /** * Class WPML_TF_XML_RPC_Hooks * * @author OnTheGoSystems */ class WPML_TF_XML_RPC_Hooks implements IWPML_Action { /** @var WPML_TF_XML_RPC_Feedback_Update_Factory $xml_rpc_feedback_update_factory */ private $xml_rpc_feedback_update_factory; /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_TF_XML_RPC_Feedback_Update_Factory $xml_rpc_feedback_update_factory, WPML_WP_API $wp_api ) { $this->xml_rpc_feedback_update_factory = $xml_rpc_feedback_update_factory; $this->wp_api = $wp_api; } public function add_hooks() { if ( $this->wp_api->constant( 'XMLRPC_REQUEST' ) ) { add_filter( 'xmlrpc_methods', array( $this, 'add_tf_xmlrpc_methods' ) ); } } /** * @param array $methods * * @return array */ public function add_tf_xmlrpc_methods( $methods ) { $methods['translationproxy.update_feedback_status'] = array( $this, 'update_feedback_status' ); return $methods; } /** @param array $args */ public function update_feedback_status( array $args ) { $xml_rpc_feedback_update = $this->xml_rpc_feedback_update_factory->create(); $xml_rpc_feedback_update->set_status( $args ); } } translation-feedback/hooks/wpml-tf-backend-promote-hooks.php 0000755 00000003147 14720342453 0020253 0 ustar 00 <?php use WPML\FP\Obj; /** * Class WPML_TF_Backend_Promote_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_Promote_Hooks implements IWPML_Action { /** @var WPML_TF_Promote_Notices $promote_notices */ private $promote_notices; /** @var WPML_TF_Translation_Service $translation_service */ private $translation_service; /** @var bool $is_setup_complete */ private $is_setup_complete; /** * WPML_TF_Backend_Promote_Hooks constructor. * * @param WPML_TF_Promote_Notices $promote_notices * @param bool $is_setup_complete * @param WPML_TF_Translation_Service $translation_service */ public function __construct( WPML_TF_Promote_Notices $promote_notices, $is_setup_complete, WPML_TF_Translation_Service $translation_service ) { $this->promote_notices = $promote_notices; $this->is_setup_complete = $is_setup_complete; $this->translation_service = $translation_service; } public function add_hooks() { if ( $this->translation_service->allows_translation_feedback() ) { if ( $this->is_setup_complete && get_option( WPML_Installation::WPML_START_VERSION_KEY ) ) { add_action( 'wpml_pro_translation_completed', [ $this, 'add_notice_for_manager_on_job_completed' ], 10, 3 ); } } } /** * @param int $new_post_id * @param array $fields * @param stdClass $job */ public function add_notice_for_manager_on_job_completed( $new_post_id, $fields, $job ) { if ( Obj::prop( 'translation_service', $job ) !== 'local' ) { $this->promote_notices->show_notice_for_new_site( (int) $job->manager_id ); } } } translation-feedback/hooks/wpml-tf-backend-hooks.php 0000755 00000007747 14720342453 0016602 0 ustar 00 <?php /** * Class WPML_TF_Backend_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_Hooks implements IWPML_Action { const PAGE_HOOK = 'wpml-translation-feedback-list'; /** @var WPML_TF_Backend_Bulk_Actions_Factory */ private $bulk_actions_factory; /** @var WPML_TF_Backend_Feedback_List_View_Factory feedback_list_view_factory */ private $feedback_list_view_factory; /** @var WPML_TF_Backend_Styles $backend_styles */ private $backend_styles; /** @var WPML_TF_Backend_Scripts */ private $backend_scripts; /** @var wpdb $wpdb */ private $wpdb; /** * WPML_TF_Backend_Hooks constructor. * * @param WPML_TF_Backend_Bulk_Actions_Factory $bulk_actions_factory * @param WPML_TF_Backend_Feedback_List_View_Factory $feedback_list_view_factory * @param WPML_TF_Backend_Styles $backend_styles * @param WPML_TF_Backend_Scripts $backend_scripts * @param wpdb $wpdb */ public function __construct( WPML_TF_Backend_Bulk_Actions_Factory $bulk_actions_factory, WPML_TF_Backend_Feedback_List_View_Factory $feedback_list_view_factory, WPML_TF_Backend_Styles $backend_styles, WPML_TF_Backend_Scripts $backend_scripts, wpdb $wpdb ) { $this->bulk_actions_factory = $bulk_actions_factory; $this->feedback_list_view_factory = $feedback_list_view_factory; $this->backend_styles = $backend_styles; $this->backend_scripts = $backend_scripts; $this->wpdb = $wpdb; } /** * method add_hooks */ public function add_hooks() { add_action( 'wpml_admin_menu_configure', array( $this, 'add_translation_feedback_list_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_action' ) ); add_action( 'current_screen', array( $this, 'bulk_actions_callback' ) ); add_filter( 'posts_where', array( $this, 'maybe_exclude_rating_only_status' ), 10, 2 ); } /** * Define translation feedback list menu and callback * * @param string $menu_id */ public function add_translation_feedback_list_menu( $menu_id ) { if ( 'WPML' !== $menu_id ) { return; } if ( current_user_can( 'manage_options' ) ) { $menu = array(); $menu['order'] = 1200; $menu['page_title'] = __( 'Translation Feedback', 'sitepress' ); $menu['menu_title'] = __( 'Translation Feedback', 'sitepress' ); $menu['capability'] = 'manage_options'; $menu['menu_slug'] = self::PAGE_HOOK; $menu['function'] = array( $this, 'translation_feedback_list_display' ); do_action( 'wpml_admin_menu_register_item', $menu ); } else { add_menu_page( __( 'Translation Feedback', 'sitepress' ), __( 'Translation Feedback', 'sitepress' ), 'read', self::PAGE_HOOK, array( $this, 'translation_feedback_list_display' ) ); } } /** * Callback to display the feedback list page */ public function translation_feedback_list_display() { $view = $this->feedback_list_view_factory->create(); echo $view->render_page(); } /** * @param string $hook */ public function admin_enqueue_scripts_action( $hook ) { if ( $this->is_page_hook( $hook ) ) { $this->backend_styles->enqueue(); $this->backend_scripts->enqueue(); } } public function bulk_actions_callback( $current_screen ) { $hook = isset( $current_screen->id ) ? $current_screen->id : null; if ( $this->is_page_hook( $hook ) ) { $bulk_actions = $this->bulk_actions_factory->create(); $bulk_actions->process(); } } /** * @param string $hook * * @return bool */ private function is_page_hook( $hook ) { return strpos( $hook, self::PAGE_HOOK ) !== false; } /** * @param string $where * @param WP_Query $query * * @return string */ public function maybe_exclude_rating_only_status( $where, $query ) { if ( isset( $query->query_vars['exclude_tf_rating_only'] ) && $query->query_vars['exclude_tf_rating_only'] ) { $where .= " AND {$this->wpdb->posts}.post_status <> 'rating_only'"; } return $where; } } translation-feedback/hooks/wpml-tf-common-hooks.php 0000755 00000002352 14720342453 0016466 0 ustar 00 <?php /** * Class WPML_TF_Common_Hooks * @author OnTheGoSystems */ class WPML_TF_Common_Hooks implements IWPML_Action { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; public function __construct( WPML_TF_Data_Object_Storage $feedback_storage ) { $this->feedback_storage = $feedback_storage; } /** * method init */ public function add_hooks() { add_action( 'init', array( $this, 'init_action' ) ); add_action( 'deleted_post', array( $this, 'cleanup_post_feedback_data' ) ); } /** * method init_action */ public function init_action() { register_post_type( WPML_TF_Feedback_Post_Convert::POST_TYPE ); register_post_type( WPML_TF_Message_Post_Convert::POST_TYPE ); } /** @param int $post_id */ public function cleanup_post_feedback_data( $post_id ) { if ( WPML_TF_Feedback_Post_Convert::POST_TYPE === get_post_type( $post_id ) ) { return; } $args = array( 'post_id' => $post_id, ); $collection_filter = new WPML_TF_Feedback_Collection_Filter( $args ); $feedback_collection = $this->feedback_storage->get_collection( $collection_filter ); foreach ( $feedback_collection as $feedback ) { $this->feedback_storage->delete( $feedback->get_id(), true ); } } } translation-feedback/hooks/wpml-tm-tf-feedback-list-hooks.php 0000755 00000001451 14720342453 0020310 0 ustar 00 <?php /** * Class WPML_TM_TF_Feedback_List_Hooks * * @author OnTheGoSystems */ class WPML_TM_TF_Feedback_List_Hooks implements IWPML_Action { public function add_hooks() { /** * Set high priority to let other users to override this value later to fulfill the requirements of * https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1161/Create-a-Hook-Api-to-disable-ATE-Editor-per-post */ add_filter( 'wpml_use_tm_editor', [ $this, 'maybe_force_to_use_translation_editor' ], 1, 1 ); } /** * @param int|bool $use_translation_editor * * @return int|bool */ public function maybe_force_to_use_translation_editor( $use_translation_editor ) { if ( ! current_user_can( 'edit_others_posts' ) ) { $use_translation_editor = true; } return $use_translation_editor; } } translation-feedback/hooks/wpml-tf-backend-options-ajax-hooks.php 0000755 00000005171 14720342453 0021201 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_AJAX_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_AJAX_Hooks implements IWPML_Action { /** @var WPML_TF_Settings $settings */ private $settings; /** @var WPML_TF_Settings_Write $settings_write */ private $settings_write; /** @var WPML_TF_Promote_Notices $promote_notices */ private $promote_notices; /** @var array $post_data */ private $post_data; /** * WPML_TF_Frontend_AJAX_Hooks constructor. * * @param WPML_TF_Settings $settings * @param WPML_TF_Settings_Write $settings_write * @param WPML_TF_Promote_Notices $promote_notices * @param array $post_data */ public function __construct( WPML_TF_Settings $settings, WPML_TF_Settings_Write $settings_write, WPML_TF_Promote_Notices $promote_notices, array $post_data ) { $this->settings = $settings; $this->settings_write = $settings_write; $this->promote_notices = $promote_notices; $this->post_data = $post_data; } public function add_hooks() { add_action( 'wp_ajax_' . WPML_TF_Backend_Options_AJAX_Hooks_Factory::AJAX_ACTION, array( $this, 'save_settings_callback' ) ); } public function save_settings_callback() { $new_settings = array(); if ( isset( $this->post_data['settings'] ) ) { parse_str( $this->post_data['settings'], $new_settings ); } if ( isset( $new_settings['enabled'] ) ) { $this->settings->set_enabled( true ); $this->promote_notices->remove(); } else { $this->settings->set_enabled( false ); } if ( isset( $new_settings['button_mode'] ) ) { $this->settings->set_button_mode( $new_settings['button_mode'] ); } if ( isset( $new_settings['icon_style'] ) ) { $this->settings->set_icon_style( $new_settings['icon_style'] ); } if ( isset( $new_settings['languages_to'] ) ) { $this->settings->set_languages_to( $new_settings['languages_to'] ); } else { $this->settings->set_languages_to( array() ); } if ( isset( $new_settings['display_mode'] ) ) { $this->settings->set_display_mode( $new_settings['display_mode'] ); } if ( isset( $new_settings['expiration_mode'] ) ) { $this->settings->set_expiration_mode( $new_settings['expiration_mode'] ); } if ( isset( $new_settings['expiration_delay_quantity'] ) ) { $this->settings->set_expiration_delay_quantity( $new_settings['expiration_delay_quantity'] ); } if ( isset( $new_settings['expiration_delay_unit'] ) ) { $this->settings->set_expiration_delay_unit( $new_settings['expiration_delay_unit'] ); } $this->settings_write->save( $this->settings ); wp_send_json_success( __( 'Settings saved', 'sitepress' ) ); } } translation-feedback/hooks/wpml-tf-frontend-ajax-hooks.php 0000755 00000011031 14720342453 0017730 0 ustar 00 <?php /** * Class WPML_TF_Frontend_AJAX_Hooks * * @author OnTheGoSystems */ class WPML_TF_Frontend_AJAX_Hooks implements IWPML_Action { /** @var WPML_TF_Data_Object_Storage $feedback_storage */ private $feedback_storage; /** @var WPML_TF_Document_Information $document_information */ private $document_information; /** @var WPML_TF_Post_Rating_Metrics $post_rating_metrics */ private $post_rating_metrics; /** @var WPML_TP_Client_Factory $tp_client_factory */ private $tp_client_factory; /** @var WPML_TP_Client $tp_client */ private $tp_client; private $post_data; /** * WPML_TF_Frontend_AJAX_Hooks constructor. * * @param WPML_TF_Data_Object_Storage $feedback_storage * @param WPML_TF_Document_Information $document_information * @param WPML_TF_Post_Rating_Metrics $post_rating_metrics * @param WPML_TP_Client_Factory|null $tp_client_factory * @param mixed[]|null $post_data */ public function __construct( WPML_TF_Data_Object_Storage $feedback_storage, WPML_TF_Document_Information $document_information, WPML_TF_Post_Rating_Metrics $post_rating_metrics, WPML_TP_Client_Factory $tp_client_factory = null, array $post_data = null ) { $this->feedback_storage = $feedback_storage; $this->document_information = $document_information; $this->post_rating_metrics = $post_rating_metrics; $this->tp_client_factory = $tp_client_factory; $this->post_data = $post_data; } /** * method init */ public function add_hooks() { add_action( 'wp_ajax_nopriv_' . WPML_TF_Frontend_AJAX_Hooks_Factory::AJAX_ACTION, array( $this, 'save_feedback_callback' ) ); add_action( 'wp_ajax_' . WPML_TF_Frontend_AJAX_Hooks_Factory::AJAX_ACTION, array( $this, 'save_feedback_callback' ) ); } /** * Method callback */ public function save_feedback_callback() { $feedback = null; if ( isset( $this->post_data['feedback_id'] ) ) { $feedback = $this->update_feedback( $this->post_data['feedback_id'] ); } elseif ( isset( $this->post_data['rating'], $this->post_data['document_id'], $this->post_data['document_type'] ) ) { $feedback = $this->create_feedback(); } if ( $feedback ) { $feedback_id = $this->feedback_storage->persist( $feedback ); $this->post_rating_metrics->refresh( $feedback->get_document_id() ); wp_send_json_success( array( 'feedback_id' => $feedback_id ) ); } else { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } } /** * @param int $feedback_id * * @return WPML_TF_Feedback|null|false */ private function update_feedback( $feedback_id ) { $feedback = $this->feedback_storage->get( $feedback_id ); if ( ! $feedback ) { return false; } /** @var WPML_TF_Feedback $feedback */ if ( isset( $this->post_data['content'] ) ) { $feedback->set_content( $this->post_data['content'] ); } if ( isset( $this->post_data['rating'] ) ) { $feedback->set_rating( $this->post_data['rating'] ); } $feedback->set_status( $this->get_filtered_status() ); return $feedback; } /** * @return WPML_TF_Feedback */ private function create_feedback() { $this->document_information->init( $this->post_data['document_id'], $this->post_data['document_type'] ); $args = array( 'rating' => $this->post_data['rating'], 'status' => $this->get_filtered_status(), 'document_id' => $this->post_data['document_id'], 'document_type' => $this->post_data['document_type'], 'language_from' => $this->document_information->get_source_language(), 'language_to' => $this->document_information->get_language(), 'job_id' => $this->document_information->get_job_id(), ); if ( $this->document_information->is_local_translation( $args['job_id'] ) ) { $args['tp_rating_id'] = 0; } elseif ( $this->get_tp_client() ) { $active_service = $this->get_tp_client()->services()->get_active(); if ( isset( $active_service->feedback_forward_method ) ) { $args['feedback_forward_method'] = $active_service->feedback_forward_method; } } return new WPML_TF_Feedback( $args ); } /** @return string */ private function get_filtered_status() { $rating = isset( $this->post_data['rating'] ) ? $this->post_data['rating'] : null; $status = 'pending'; if ( 3 < (int) $rating || empty( $this->post_data['content'] ) ) { $status = 'rating_only'; } return $status; } /** @return null|WPML_TP_Client */ private function get_tp_client() { if ( $this->tp_client_factory && ! $this->tp_client ) { $this->tp_client = $this->tp_client_factory->create(); } return $this->tp_client; } } translation-feedback/hooks/wpml-tf-backend-post-list-hooks.php 0000755 00000007011 14720342453 0020516 0 ustar 00 <?php /** * Class WPML_TF_Backend_Post_List_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_Post_List_Hooks implements IWPML_Action { const RATING_COLUMN_ID = 'translation_rating'; /** @var WPML_TF_Post_Rating_Metrics $post_rating_metrics*/ private $post_rating_metrics; /** @var WPML_TF_Document_Information $document_information */ private $document_information; /** @var WPML_TF_Backend_Styles $styles */ private $styles; public function __construct( WPML_TF_Post_Rating_Metrics $post_rating_metrics, WPML_TF_Document_Information $document_information, WPML_TF_Backend_Styles $styles ) { $this->post_rating_metrics = $post_rating_metrics; $this->document_information = $document_information; $this->styles = $styles; } /** @return array */ private function get_post_types() { return array( 'edit-post' => 'posts', 'edit-page' => 'pages', ); } public function add_hooks() { foreach ( $this->get_post_types() as $screen_id => $post_type ) { add_filter( 'manage_' . $post_type . '_columns' , array( $this, 'add_rating_column_header' ) ); add_action( 'manage_' . $post_type . '_custom_column', array( $this, 'add_rating_column_content' ), 10, 2 ); add_filter( 'manage_' . $screen_id .'_sortable_columns', array( $this, 'add_rating_sortable_column' ) ); } add_action( 'pre_get_posts', array( $this, 'order_posts_by_rating' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts_action' ) ); } /** * @param array $columns * * @return array */ public function add_rating_column_header( array $columns ) { $key_to_insert_before = 'date'; $column_name = __( 'Translation rating', 'sitepress' ); if ( array_key_exists( $key_to_insert_before, $columns ) ) { /** @var int $insert_position The if makes sure the following will return an int. */ $insert_position = array_search( $key_to_insert_before, array_keys( $columns ) ); $columns_before = array_slice( $columns, 0, $insert_position, true ); $columns_to_insert = array( self::RATING_COLUMN_ID => $column_name ); $columns_after = array_slice( $columns, $insert_position, null, true ); $columns = array_merge( $columns_before, $columns_to_insert, $columns_after ); } else { $columns[ self::RATING_COLUMN_ID ] = $column_name; } return $columns; } /** * @param string $column_name * @param int $post_id */ public function add_rating_column_content( $column_name, $post_id ) { if ( self::RATING_COLUMN_ID !== $column_name ) { return; } $post_type = get_post_type( $post_id ); $this->document_information->init( $post_id, 'post_' . $post_type ); if ( $this->document_information->get_source_language() ) { echo $this->post_rating_metrics->get_display( $post_id ); } } /** * @param array $columns * * @return array */ public function add_rating_sortable_column( array $columns ) { $columns[ self::RATING_COLUMN_ID ] = self::RATING_COLUMN_ID; return $columns; } public function order_posts_by_rating( WP_Query $query ) { $orderby = $query->get( 'orderby'); if( self::RATING_COLUMN_ID === $orderby ) { $query->set( 'meta_key', WPML_TF_Post_Rating_Metrics::AVERAGE_KEY ); $query->set( 'orderby', 'meta_value_num' ); $query->set( 'meta_type', 'NUMERIC' ); } } public function admin_enqueue_scripts_action() { $current_screen = get_current_screen(); if ( isset( $current_screen->id ) && array_key_exists( $current_screen->id, $this->get_post_types() ) ) { $this->styles->enqueue(); } } } translation-feedback/hooks/wpml-tf-translation-service-change-hooks.php 0000755 00000003233 14720342453 0022414 0 ustar 00 <?php /** * Class WPML_TF_Translation_Service_Change_Hooks * * @author OnTheGoSystems */ class WPML_TF_Translation_Service_Change_Hooks implements IWPML_Action { /** @var WPML_TF_Settings_Read $settings_read */ private $settings_read; /** @var WPML_TF_Settings_Write $settings_write */ private $settings_write; /** @var WPML_TF_TP_Ratings_Synchronize_Factory $tp_ratings_synchronize_factory */ private $tp_ratings_synchronize_factory; public function __construct( WPML_TF_Settings_Read $settings_read, WPML_TF_Settings_Write $settings_write, WPML_TF_TP_Ratings_Synchronize_Factory $tp_ratings_synchronize_factory ) { $this->settings_read = $settings_read; $this->settings_write = $settings_write; $this->tp_ratings_synchronize_factory = $tp_ratings_synchronize_factory; } public function add_hooks() { add_action( 'wpml_tm_before_set_translation_service', array( $this, 'before_set_translation_service_callback' ) ); } public function before_set_translation_service_callback( stdClass $service ) { $this->cleanup_pending_ratings_queue(); $this->disable_tf_if_not_allowed_by_ts( $service ); } private function cleanup_pending_ratings_queue() { $tp_ratings_sync = $this->tp_ratings_synchronize_factory->create(); $tp_ratings_sync->run( true ); } private function disable_tf_if_not_allowed_by_ts( stdClass $service ) { if ( isset( $service->translation_feedback ) && ! $service->translation_feedback ) { /** @var WPML_TF_Settings $settings */ $settings = $this->settings_read->get( 'WPML_TF_Settings' ); $settings->set_enabled( false ); $this->settings_write->save( $settings ); } } } translation-feedback/hooks/wpml-tf-backend-options-hooks.php 0000755 00000004436 14720342453 0020263 0 ustar 00 <?php /** * Class WPML_TF_Backend_Options_Hooks * * @author OnTheGoSystems */ class WPML_TF_Backend_Options_Hooks implements IWPML_Action { const WPML_LOVE_ID = '#lang-sec-10'; /** @var WPML_TF_Backend_Options_View $options_view */ private $options_view; /** @var WPML_TF_Backend_Options_Scripts $scripts */ private $scripts; /** @var WPML_TF_Backend_Options_Styles $styles */ private $styles; /** @var WPML_TF_Translation_Service $translation_service */ private $translation_service; /** * WPML_TF_Backend_Options_Hooks constructor. * * @param WPML_TF_Backend_Options_View $options_view * @param WPML_TF_Backend_Options_Scripts $scripts * @param WPML_TF_Backend_Options_Styles $styles * @param WPML_TF_Translation_Service $translation_service */ public function __construct( WPML_TF_Backend_Options_View $options_view, WPML_TF_Backend_Options_Scripts $scripts, WPML_TF_Backend_Options_Styles $styles, WPML_TF_Translation_Service $translation_service ) { $this->options_view = $options_view; $this->scripts = $scripts; $this->styles = $styles; $this->translation_service = $translation_service; } /** * Method add_hooks */ public function add_hooks() { if ( $this->translation_service->allows_translation_feedback() ) { add_action( 'wpml_after_settings', array( $this, 'display_options_ui' ) ); add_filter( 'wpml_admin_languages_navigation_items', array( $this, 'insert_navigation_item' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts_action' ) ); } } /** * Method to render the options UI */ public function display_options_ui() { echo $this->options_view->render(); } /** * @param array $items * * @return mixed */ public function insert_navigation_item( $items ) { $insert_key_index = array_search( self::WPML_LOVE_ID, array_keys( $items ) ); $insert_position = false === $insert_key_index ? count( $items ) : $insert_key_index + 1; return array_merge( array_slice( $items, 0, $insert_position ), array( '#wpml-translation-feedback-options' => esc_html__( 'Translation Feedback', 'sitepress' ) ), array_slice( $items, $insert_position ) ); } public function enqueue_scripts_action() { $this->scripts->enqueue(); $this->styles->enqueue(); } } translation-feedback/settings/iwpml-tf-settings.php 0000755 00000000415 14720342453 0016601 0 ustar 00 <?php /** * Interface WPML_Settings_Interface * * @author OnTheGoSystems */ interface IWPML_TF_Settings { /** * @return array of name/value pairs * * Each property should have its own setter "set_{$property_name}" */ public function get_properties(); } translation-feedback/settings/wpml-tf-settings-write.php 0000755 00000000570 14720342453 0017562 0 ustar 00 <?php /** * Class WPML_TF_Settings_Write * * @author OnTheGoSystems */ class WPML_TF_Settings_Write extends WPML_TF_Settings_Handler { /** * @param IWPML_TF_Settings $settings * * @return bool */ public function save( IWPML_TF_Settings $settings ) { return update_option( $this->get_option_name( get_class( $settings ) ), $settings->get_properties() ); } } translation-feedback/settings/wpml-tf-settings-read.php 0000755 00000003124 14720342453 0017341 0 ustar 00 <?php /** * Class WPML_TF_Settings_Read * * @author OnTheGoSystems */ class WPML_TF_Settings_Read extends WPML_TF_Settings_Handler { /** * @param string $settings_class * * @return IWPML_TF_Settings * * @throws InvalidArgumentException */ public function get( $settings_class ) { if ( ! class_exists( $settings_class ) ) { throw new InvalidArgumentException( $settings_class . ' does not exist.' ); } if ( ! in_array( 'IWPML_TF_Settings', class_implements( $settings_class ) ) ) { throw new InvalidArgumentException( $settings_class . ' should implement IWPML_TF_Settings.' ); } $settings_properties = get_option( $this->get_option_name( $settings_class ) ); /** @var IWPML_TF_Settings $settings */ $settings = new $settings_class(); if ( is_array( $settings_properties ) ) { $this->set_properties( $settings, $settings_properties ); } return $settings; } /** * @param IWPML_TF_Settings $settings * @param array $settings_properties * * @throws BadMethodCallException */ private function set_properties( IWPML_TF_Settings $settings, array $settings_properties ) { foreach ( $settings->get_properties() as $property_name => $property_value ) { if ( method_exists( $settings, 'set_' . $property_name ) ) { if ( isset( $settings_properties[ $property_name ] ) ) { call_user_func( array( $settings, 'set_' . $property_name ), $settings_properties[ $property_name ] ); } } else { throw new BadMethodCallException( 'The method set_' . $property_name . ' is required in ' . get_class( $settings ) . '.' ); } } } } translation-feedback/settings/wpml-tf-settings.php 0000755 00000007547 14720342453 0016445 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_TF_Settings * * @author OnTheGoSystems */ class WPML_TF_Settings implements IWPML_TF_Settings { const BUTTON_MODE_DISABLED = 'disabled'; const BUTTON_MODE_LEFT = 'left'; const BUTTON_MODE_RIGHT = 'right'; const BUTTON_MODE_CUSTOM = 'custom'; const ICON_STYLE_LEGACY = 'translation'; const ICON_STYLE_STAR = 'star'; const ICON_STYLE_THUMBSUP = 'thumbsup'; const ICON_STYLE_BULLHORN = 'bullhorn'; const ICON_STYLE_COMMENT = 'comment'; const ICON_STYLE_QUOTE = 'quote'; const DISPLAY_ALWAYS = 'always'; const DISPLAY_CUSTOM = 'custom'; const EXPIRATION_ON_PUBLISH_OR_UPDATE = 'publish_or_update'; const EXPIRATION_ON_PUBLISH_ONLY = 'publish_only'; const EXPIRATION_ON_UPDATE_ONLY = 'update_only'; const DELAY_DAY = 1; const DELAY_WEEK = 7; const DELAY_MONTH = 30; /** @var bool $enabled */ private $enabled = false; /** @var string $button_mode */ private $button_mode = self::BUTTON_MODE_LEFT; /** @var string $icon_style */ private $icon_style = self::ICON_STYLE_LEGACY; /** @var null|array $languages_to */ private $languages_to = null; /** @var string $display_mode */ private $display_mode = self::DISPLAY_CUSTOM; /** @var string $expiration_mode */ private $expiration_mode = self::EXPIRATION_ON_PUBLISH_OR_UPDATE; /** @var int $expiration_delay_quantity */ private $expiration_delay_quantity = 1; /** @var int $expiration_delay_unit */ private $expiration_delay_unit = self::DELAY_MONTH; /** * @param bool $enabled */ public function set_enabled( $enabled ) { $this->enabled = (bool) $enabled; } /** * @return bool */ public function is_enabled() { return $this->enabled; } /** * @param string $button_mode */ public function set_button_mode( $button_mode ) { $this->button_mode = Sanitize::string( $button_mode ); } /** * @return string */ public function get_button_mode() { return $this->button_mode; } /** @param string $style */ public function set_icon_style( $style ) { $this->icon_style = Sanitize::string( $style ); } /** @return string */ public function get_icon_style() { return $this->icon_style; } /** * @param array $languages_to */ public function set_languages_to( array $languages_to ) { $this->languages_to = array_map( 'sanitize_title', $languages_to ); } /** * @return null|array */ public function get_languages_to() { return $this->languages_to; } /** * @param string $display_mode */ public function set_display_mode( $display_mode ) { $this->display_mode = Sanitize::string( $display_mode ); } /** * @return string */ public function get_display_mode() { return $this->display_mode; } /** * @param string $expiration_mode */ public function set_expiration_mode( $expiration_mode ) { $this->expiration_mode = Sanitize::string( $expiration_mode ); } /** * @return string */ public function get_expiration_mode() { return $this->expiration_mode; } /** * @param int $expiration_delay_quantity */ public function set_expiration_delay_quantity( $expiration_delay_quantity ) { $this->expiration_delay_quantity = (int) $expiration_delay_quantity; } /** * @return int */ public function get_expiration_delay_quantity() { return $this->expiration_delay_quantity; } /** * @param int $expiration_delay_unit */ public function set_expiration_delay_unit( $expiration_delay_unit ) { $this->expiration_delay_unit = (int) $expiration_delay_unit; } /** * @return int */ public function get_expiration_delay_unit() { return $this->expiration_delay_unit; } /** * @return int delay in days before expiration */ public function get_expiration_delay_in_days() { return $this->expiration_delay_quantity * $this->expiration_delay_unit; } /** * @return array */ public function get_properties() { return get_object_vars( $this ); } } translation-feedback/settings/wpml-tf-settings-handler.php 0000755 00000000432 14720342453 0020042 0 ustar 00 <?php /** * Class WPML_TF_Settings_Handler * * @author OnTheGoSystems */ abstract class WPML_TF_Settings_Handler { /** * @param string $class_name * * @return string */ protected function get_option_name( $class_name ) { return sanitize_title( $class_name ); } } filters/class-wpml-tm-post-target-lang-filter.php 0000755 00000003121 14720342453 0016102 0 ustar 00 <?php class WPML_TM_Post_Target_Lang_Filter extends WPML_TM_Record_User { /** @var WPML_TM_Translation_Status */ private $tm_status; /** @var WPML_Post_Translation $post_translations */ private $post_translations; public function __construct( &$tm_records, &$tm_status, &$post_translations ) { parent::__construct( $tm_records ); $this->tm_status = &$tm_status; $this->post_translations = &$post_translations; } /** * @param string[] $allowed_langs * @param int $element_id * @param string $element_type_prefix * * @return string[] */ public function filter_target_langs( $allowed_langs, $element_id, $element_type_prefix ) { if ( TranslationProxy_Basket::anywhere_in_basket( $element_id, $element_type_prefix ) ) { $allowed_langs = array(); } elseif ( $element_type_prefix === 'post' ) { if ( (bool) ( $this->post_translations->get_element_lang_code( $element_id ) ) === true ) { $allowed_langs = array_fill_keys( $allowed_langs, 1 ); $translations = $this ->tm_records ->icl_translations_by_element_id_and_type_prefix( $element_id, 'post' ) ->translations(); foreach ( $translations as $lang_code => $element ) { if ( isset( $allowed_langs[ $lang_code ] ) && ( ( $element->element_id() && ! $element->source_language_code() ) || $this->tm_status->is_in_active_job( $element_id, $lang_code, 'post' ) ) ) { unset( $allowed_langs[ $lang_code ] ); } } $allowed_langs = array_keys( $allowed_langs ); } } return $allowed_langs; } } filters/class-wpml-tm-translation-status.php 0000755 00000007023 14720342453 0015313 0 ustar 00 <?php class WPML_TM_Translation_Status { /** @var WPML_TM_Records $tm_records */ protected $tm_records; private $element_id_cache; public function __construct( WPML_TM_Records $tm_records ) { $this->tm_records = $tm_records; } public function init() { add_filter( 'wpml_translation_status', array( $this, 'filter_translation_status' ), 1, 3 ); add_action( 'wpml_cache_clear', array( $this, 'reload' ) ); } public function filter_translation_status( $status, $trid, $target_lang_code ) { if ( ! $trid ) { return $status; } $getNewStatus = function ( $trid, $target_lang_code ) { /** @var WPML_TM_Element_Translations $wpml_tm_element_translations */ $wpml_tm_element_translations = wpml_tm_load_element_translations(); $element_ids = array_filter( $this->get_element_ids( $trid ) ); $element_type_prefix = $wpml_tm_element_translations->get_element_type_prefix( $trid, $target_lang_code ); foreach ( $element_ids as $id ) { if ( $this->is_in_basket( $id, $target_lang_code, $element_type_prefix ) ) { return ICL_TM_IN_BASKET; } } if ( $wpml_tm_element_translations->is_update_needed( $trid, $target_lang_code ) ) { return ICL_TM_NEEDS_UPDATE; } foreach ( $element_ids as $id ) { if ( $job_status = $this->is_in_active_job( $id, $target_lang_code, $element_type_prefix, true ) ) { return $job_status; } } return false; }; $cachedGetNewStatus = \WPML\LIB\WP\Cache::memorize( WPML_ELEMENT_TRANSLATIONS_CACHE_GROUP, 0, $getNewStatus ); $newStatus = $cachedGetNewStatus( $trid, $target_lang_code ); return $newStatus !== false ? $newStatus : $status; } public function reload() { $this->element_id_cache = array(); \WPML\LIB\WP\Cache::flushGroup( WPML_ELEMENT_TRANSLATIONS_CACHE_GROUP ); $oldCache = new WPML_WP_Cache( WPML_ELEMENT_TRANSLATIONS_CACHE_GROUP ); $oldCache->flush_group_cache(); } public function is_in_active_job( $element_id, $target_lang_code, $element_type_prefix, $return_status = false ) { $translations = $this->tm_records->icl_translations_by_element_id_and_type_prefix( $element_id, $element_type_prefix )->translations(); if ( ! isset( $translations[ $target_lang_code ] ) ) { return false; } $element_translated = $translations[ $target_lang_code ]; if ( ! $element_translated->source_language_code() && $element_translated->element_id() == $element_id ) { $res = $return_status ? ICL_TM_COMPLETE : false; } else { $res = $this->tm_records ->icl_translation_status_by_translation_id( $element_translated->translation_id() ) ->status(); $res = $return_status ? $res : in_array( $res, array( ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR, ), true ); } return $res; } private function is_in_basket( $element_id, $lang, $element_type_prefix ) { return TranslationProxy_Basket::anywhere_in_basket( $element_id, $element_type_prefix, array( $lang => 1 ) ); } private function get_element_ids( $trid ) { if ( ! isset( $this->element_id_cache[ $trid ] ) ) { $elements = $this->tm_records->get_post_translations()->get_element_translations( null, $trid ); $elements = $elements ? $elements : $this->tm_records->get_term_translations()->get_element_translations( null, $trid ); if ( $elements ) { $this->element_id_cache[ $trid ] = array_values( $elements ); } else { $this->element_id_cache[ $trid ] = $this->tm_records->get_element_ids_from_trid( $trid ); } } return $this->element_id_cache[ $trid ]; } } filters/class-wpml-tm-translation-status-display.php 0000755 00000043022 14720342453 0016755 0 ustar 00 <?php use WPML\FP\Fns; use WPML\FP\Maybe; use WPML\Settings\PostType\Automatic; use WPML\Setup\Option; use WPML\TM\ATE\TranslateEverything; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Logic; use WPML\TM\API\ATE\CachedLanguageMappings; use WPML\Element\API\Languages; use WPML\LIB\WP\Post; use WPML\API\PostTypes; use WPML\TM\API\Jobs; use function WPML\FP\partial; use WPML\LIB\WP\User; class WPML_TM_Translation_Status_Display { private $statuses = array(); private $stats_preloaded = false; /** * @var WPML_Post_Status */ private $status_helper; /** * @var WPML_Translation_Job_Factory */ private $job_factory; /** * @var WPML_TM_API */ protected $tm_api; /** * @var WPML_Post_Translation */ private $post_translations; /** * @var SitePress */ protected $sitepress; private $original_links = array(); private $tm_editor_links = array(); /** * @var \wpdb */ private $wpdb; /** * WPML_TM_Translation_Status_Display constructor. * * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_Post_Status $status_helper * @param WPML_Translation_Job_Factory $job_factory * @param WPML_TM_API $tm_api */ public function __construct( wpdb $wpdb, SitePress $sitepress, WPML_Post_Status $status_helper, WPML_Translation_Job_Factory $job_factory, WPML_TM_API $tm_api ) { $this->post_translations = $sitepress->post_translations(); $this->wpdb = $wpdb; $this->status_helper = $status_helper; $this->job_factory = $job_factory; $this->tm_api = $tm_api; $this->sitepress = $sitepress; } public function init() { add_action( 'wpml_cache_clear', array( $this, 'init' ), 11, 0 ); add_filter( 'wpml_css_class_to_translation', array( $this, 'filter_status_css_class', ), 10, 4 ); add_filter( 'wpml_link_to_translation', array( $this, 'filter_status_link', ), 10, 4 ); add_filter( 'wpml_text_to_translation', array( $this, 'filter_status_text', ), 10, 4 ); add_filter( 'wpml_post_status_display_html', array( $this, 'add_links_data_attributes' ), 10, 4 ); $this->statuses = array(); } private function preload_stats() { $this->load_stats( $this->post_translations->get_trids() ); $this->stats_preloaded = true; } private function load_stats( $trids ) { if ( ! $trids ) { return; } $trids = wpml_prepare_in( $trids ); $trids_query = "translations.trid IN ( {$trids} )"; $stats = $this->wpdb->get_results( "SELECT translation_status.status, languages.code, translation_status.translator_id, translation_status.translation_service, translation_status.needs_update, translations.trid, translate_job.job_id, translate_job.editor, translate_job.automatic FROM {$this->wpdb->prefix}icl_languages languages LEFT JOIN {$this->wpdb->prefix}icl_translations translations ON languages.code = translations.language_code JOIN {$this->wpdb->prefix}icl_translation_status translation_status ON translations.translation_id = translation_status.translation_id JOIN {$this->wpdb->prefix}icl_translate_job translate_job ON translate_job.rid = translation_status.rid AND translate_job.revision IS NULL WHERE languages.active = 1 AND {$trids_query} OR translations.trid IS NULL", ARRAY_A ); foreach ( $stats as $element ) { $this->statuses[ $element['trid'] ][ $element['code'] ] = $element; } } public function filter_status_css_class( $css_class, $post_id, $lang, $trid ) { $this->maybe_load_stats( $trid ); $element_id = $this->post_translations->get_element_id( $lang, $trid ); $source_lang = $this->post_translations->get_source_lang_code( $element_id ); $post_status = $this->get_post_status( $post_id ); if ( $this->is_in_progress( $trid, $lang ) ) { $css_class = 'otgs-ico-in-progress'; } elseif ( $this->is_in_basket( $trid, $lang ) || ( ! $this->is_lang_pair_allowed( $lang, $source_lang, $post_id ) && $element_id ) ) { $css_class .= ' otgs-ico-edit-disabled'; } elseif ( ! $this->is_lang_pair_allowed( $lang, $source_lang, $post_id ) && ! $element_id ) { $css_class .= ' otgs-ico-add-disabled'; } elseif ( ! $this->has_user_rights_to_translate( $trid, $lang ) ) { $css_class .= ' otgs-ico-edit-disabled'; } if ( ( $this->isTranslateEverythingInProgress( $trid, $post_id, $lang ) && ( 'draft' !== $post_status || $this->is_in_progress( $trid, $lang ) ) ) ) { $css_class .= ' otgs-ico-waiting'; } return $css_class; } private function get_post_status( $post_id ) { return Maybe::of( $post_id ) ->map( 'get_post' ) ->map( Obj::prop( 'post_status' ) ) ->getOrElse( partial( 'get_post_status', $post_id ) ); } public function filter_status_text( $text, $original_post_id, $lang, $trid ) { $source_lang = $this->post_translations->get_element_lang_code( $original_post_id ); $this->maybe_load_stats( $trid ); if ( ( $this->is_remote( $trid, $lang ) && $this->is_in_progress( $trid, $lang ) ) || $this->it_needs_retry( $trid, $lang ) ) { $language = $this->sitepress->get_language_details( $lang ); $text = sprintf( __( "You can't edit this translation, because this translation to %s is already in progress.", 'wpml-translation-management' ), $language['display_name'] ); } elseif ( $this->is_in_basket( $trid, $lang ) ) { $text = __( 'Cannot edit this item, because it is currently in the translation basket.', 'wpml-translation-management' ); } elseif ( $this->is_lang_pair_allowed( $lang, null, $original_post_id ) && $this->is_in_progress( $trid, $lang ) ) { $language = $this->sitepress->get_language_details( $lang ); if ( $this->shouldAutoTranslate( $trid, $original_post_id, $lang ) ) { $text = sprintf( __( '%s: Waiting for automatic translation', 'wpml-translation-management' ), $language['display_name'] ); } else { $text = sprintf( __( 'Edit the %s translation', 'wpml-translation-management' ), $language['display_name'] ); } } elseif ( ! $this->is_lang_pair_allowed( $lang, $source_lang, $original_post_id ) ) { $language = $this->sitepress->get_language_details( $lang ); $source_language = $this->sitepress->get_language_details( $source_lang ); $text = sprintf( __( 'You don\'t have the rights to translate from %1$s to %2$s', 'wpml-translation-management' ), $source_language['display_name'], $language['display_name'] ); } elseif ( ! $this->has_user_rights_to_translate( $trid, $lang ) ) { $text = __( "You can only edit translations assigned to you.", 'wpml-translation-management' ); } if ( $this->isTranslateEverythingInProgress( $trid, $original_post_id, $lang ) ) { $text = __( 'WPML is translating your content automatically. You can monitor the progress in the admin bar.', 'wpml-translation-management' ); } return $text; } /** * Determine if there is no manual translation for the given trid * and language. * * @param int $trid * @param string $lang * * @return bool */ private function has_no_manual_translation( $trid, $lang ) { if ( ! array_key_exists( $trid, $this->statuses ) || ! array_key_exists( $lang, $this->statuses[ $trid ] ) ) { // There is no job yet for this language. return true; } // The ICL_TM_NOT_TRANSLATED flag is used for jobs which were canceled. // I.e. Job created on post update while a Translation Service is // active, but before that service has been able to translate the post, // the user switched to Translate Everything. $job_is_canceled = Obj::assocPath( [ $trid, $lang, 'status' ], ICL_TM_NOT_TRANSLATED ); $job_is_automatic = Obj::path( [ $trid, $lang, 'automatic' ] ); return Logic::anyPass( [ $job_is_canceled, $job_is_automatic, ], $this->statuses ); } /** * @param string $link * @param int $post_id * @param string $lang * @param int $trid * * @return string */ public function filter_status_link( $link, $post_id, $lang, $trid ) { $this->original_links[ $post_id ][ $lang ][ $trid ] = $link; $translated_element_id = $this->post_translations->get_element_id( $lang, $trid ); $source_lang = $this->post_translations->get_source_lang_code( $translated_element_id ); if ( (bool) $translated_element_id && (bool) $source_lang === false ) { $this->tm_editor_links[ $post_id ][ $lang ][ $trid ] = $link; return $link; } $this->maybe_load_stats( $trid ); $is_remote = $this->is_remote( $trid, $lang ); $is_in_progress = $this->is_in_progress( $trid, $lang ); $does_need_update = (bool) Obj::pathOr( false, [ $trid, $lang, 'needs_update' ], $this->statuses ); $use_tm_editor = $this->shouldUseTMEditor( $post_id ); $source_lang_code = $this->post_translations->get_element_lang_code( $post_id ); $is_local_job_in_progress = $is_in_progress && ! $is_remote; $is_remote_job_in_progress = $is_remote && $is_in_progress; $translation_exists = (bool) $translated_element_id; $tm_editor_link = ''; if ( $is_remote_job_in_progress || $this->is_in_basket( $trid, $lang ) || ! $this->is_lang_pair_allowed( $lang, $source_lang, $post_id ) || $this->it_needs_retry( $trid, $lang ) ) { $link = ''; $this->original_links[ $post_id ][ $lang ][ $trid ] = ''; // Also block the native editor } elseif ( $source_lang_code !== $lang ) { $job_id = null; if ( $is_local_job_in_progress || $translation_exists ) { $job_id = $this->job_factory->job_id_by_trid_and_lang( $trid, $lang ); if ( $job_id && ! is_admin() ) { $job_object = $this->job_factory->get_translation_job( $job_id, false, 0, true ); if ( $job_object && ! $job_object->user_can_translate( wp_get_current_user() ) ) { return $link; } } } if ( $job_id ) { if ( $does_need_update && $this->shouldAutoTranslate( $trid, $post_id, $lang ) ) { $tm_editor_link = '#'; } else { $tm_editor_link = $this->get_link_for_existing_job( $job_id ); } } else { $tm_editor_link = $this->get_link_for_new_job( $post_id, $trid, $lang, $source_lang_code ); } if ( $is_local_job_in_progress || $use_tm_editor ) { $link = $tm_editor_link; } } $this->tm_editor_links[ $post_id ][ $lang ][ $trid ] = $tm_editor_link; return $link; } private function shouldUseTMEditor( $postId ) { static $cachedKeys; if ( ! isset( $cachedKeys[ $postId ] ) ) { list( $useTmEditor, $isWpmlEditorBlocked, $reason ) = \WPML_TM_Post_Edit_TM_Editor_Mode::get_editor_settings( $this->sitepress, $postId ); $cachedKeys[ $postId ] = $useTmEditor && ! $isWpmlEditorBlocked; } return $cachedKeys[ $postId ]; } /** * @param string $html * @param int $post_id * @param string $lang * @param int $trid * * @return string */ public function add_links_data_attributes( $html, $post_id, $lang, $trid ) { if ( ! isset( $this->original_links[ $post_id ][ $lang ][ $trid ], $this->tm_editor_links[ $post_id ][ $lang ][ $trid ] ) ) { return $html; } $this->maybe_load_stats( $trid ); $Attributes = [ 'original-link' => $this->original_links[ $post_id ][ $lang ][ $trid ], 'tm-editor-link' => $this->tm_editor_links[ $post_id ][ $lang ][ $trid ], 'auto-translate' => $this->shouldAutoTranslate( $trid, $post_id, $lang ), 'trid' => $trid, 'language' => $lang, 'user-can-translate' => $this->is_lang_pair_allowed( $lang, null, $post_id ) ? 'yes' : 'no', 'should-ate-sync' => $this->shouldATESync( $trid, $lang ) ? '1' : '0', ]; if ( isset( $this->statuses[ $trid ][ $lang ]['job_id'] ) ) { $Attributes['tm-job-id'] = esc_attr( $this->statuses[ $trid ][ $lang ]['job_id'] ); } $createAttribute = function ( $item, $key ) { return 'data-' . $key . '="' . $item . '"'; }; $data = wpml_collect( $Attributes ) ->map( $createAttribute ) ->implode( ' ' ); return str_replace( '<a ', '<a ' . $data . ' ', $html ); } private function get_link_for_new_job( $post_id, $trid, $lang, $source_lang_code ) { if ( $this->shouldAutoTranslate( $trid, $post_id, $lang ) ) { return '#'; } else { $args = array( 'trid' => $trid, 'language_code' => $lang, 'source_language_code' => $source_lang_code, ); return add_query_arg( $args, self::get_tm_editor_base_url() ); } } public static function get_link_for_existing_job( $job_id ) { $args = array( 'job_id' => $job_id ); return add_query_arg( $args, self::get_tm_editor_base_url() ); } private static function get_tm_editor_base_url() { $returnUrl = self::get_return_url(); $returnUrl = $returnUrl ? rawurlencode( esc_url_raw( stripslashes( $returnUrl ) ) ) : ''; $args = array( 'page' => WPML_TM_FOLDER . '/menu/translations-queue.php', 'return_url' => $returnUrl, 'lang' => Languages::getCurrentCode(), // We pass this param later to ATE in order to return to the page list in the same language. ); return add_query_arg( $args, 'admin.php' ); } private static function get_return_url() { $args = [ 'wpml_tm_saved', 'wpml_tm_cancel' ]; if ( wpml_is_ajax() || ! is_admin() ) { if ( ! isset( $_SERVER['HTTP_REFERER'] ) ) { return null; } $url = remove_query_arg( $args, $_SERVER['HTTP_REFERER'] ); } else { $url = remove_query_arg( $args ); } // We add the lang parameter to the return url to return from CTE to the post list in the same language. return add_query_arg( [ 'lang' => Languages::getCurrentCode(), 'referer' => 'ate', ], $url ); } /** * @param string $lang_to * @param string $lang_from * @param int $post_id * * @return bool */ protected function is_lang_pair_allowed( $lang_to, $lang_from = null, $post_id = 0 ) { return $this->tm_api->is_translator_filter( false, $this->sitepress->get_wp_api()->get_current_user_id(), [ 'lang_from' => $lang_from ?: Languages::getCurrentCode(), 'lang_to' => $lang_to, 'admin_override' => User::isAdministrator(), 'post_id' => $post_id, ] ); } /** * It checks whether a current user has rights to edit a translation created by another user. * All admins and editors can edit any translation. * Other translators can edit only translations which either are assigned to them or unassigned. * * @param int $trid * @param string $lang * * @return bool */ private function has_user_rights_to_translate( $trid, $lang ) { $user = User::getCurrent(); if ( User::isAdministrator( $user ) || User::isEditor( $user ) ) { return true; } $job = Jobs::getTridJob( $trid, $lang ); if ( ! $job ) { return true; } if ( ! Obj::prop( 'translator_id', $job ) ) { // nobody is currently assigned return true; } if ( (int) Obj::propOr( 0, 'translator_id', $job ) === (int) $user->ID ) { return true; } // Neither admin, nor editor, nor the translator of $trid. return false; } /** * @param int $trid * * @todo make this into a proper active record user * */ private function maybe_load_stats( $trid ) { if ( ! $this->stats_preloaded ) { $this->preload_stats(); } if ( ! isset( $this->statuses[ $trid ] ) ) { $this->statuses[ $trid ] = array(); $this->load_stats( array( $trid ) ); } } private function is_remote( $trid, $lang ) { return isset( $this->statuses[ $trid ][ $lang ]['translation_service'] ) && (bool) $this->statuses[ $trid ][ $lang ]['translation_service'] !== false && $this->statuses[ $trid ][ $lang ]['translation_service'] !== 'local'; } private function is_in_progress( $trid, $lang ) { return Lst::includes( (int) Obj::path( [ $trid, $lang, 'status' ], $this->statuses ), [ ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_ATE_NEEDS_RETRY ] ); } private function it_needs_retry( $trid, $lang ) { return (int) Obj::path( [ $trid, $lang, 'status' ], $this->statuses ) === ICL_TM_ATE_NEEDS_RETRY; } private function is_in_basket( $trid, $lang ) { return $this->status_helper ->get_status( false, $trid, $lang ) === ICL_TM_IN_BASKET; } /** * @param int $trid * @param int $postId * @param string $language * * @return bool */ private function isTranslateEverythingInProgress( $trid, $postId, $language ) { /** @var string $postType */ $postType = Post::getType( $postId ); return $postType && Option::shouldTranslateEverything() && ! Option::isPausedTranslateEverything() && $this->shouldAutoTranslate( $trid, $postId, $language ) && ! TranslateEverything::isEverythingProcessedForPostTypeAndLanguage( $postType, $language ) && Lst::includes( Post::getType( $postId ), PostTypes::getAutomaticTranslatable() ); } private function shouldAutoTranslate( $trid, $postId, $targetLang ) { $isOriginalPost = ! (bool) $this->post_translations->get_source_lang_code( $postId ); $postLanguage = $this->post_translations->get_element_lang_code( $postId ); return $isOriginalPost && $postLanguage === Languages::getDefaultCode() && $this->shouldUseTMEditor( $postId ) && Automatic::shouldTranslate( get_post_type( $postId ) ) && CachedLanguageMappings::isCodeEligibleForAutomaticTranslations( $targetLang ) && $this->has_no_manual_translation( $trid, $targetLang ); } /** * @param int $trid * @param string $lang * * @return bool */ private function shouldATESync( $trid, $lang ) { $job = Obj::path( [ $trid, $lang ], $this->statuses ); return Jobs::shouldBeATESynced( $job ); } } sticky-posts/wpml-sticky-posts-loader.php 0000755 00000001463 14720342453 0014641 0 ustar 00 <?php class WPML_Sticky_Posts_Loader { /** @var SitePress */ private $sitepress; /** * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { if ( $this->sitepress->get_setting( 'sync_sticky_flag' ) ) { $sticky_post_sync = wpml_sticky_post_sync( $this->sitepress ); add_filter( 'pre_option_sticky_posts', array( $sticky_post_sync, 'pre_option_sticky_posts_filter' ), 10, 0 ); add_action( 'post_stuck', array( $sticky_post_sync, 'on_post_stuck' ) ); add_action( 'post_unstuck', array( $sticky_post_sync, 'on_post_unstuck' ) ); add_filter( 'pre_update_option_sticky_posts', array( $sticky_post_sync, 'pre_update_option_sticky_posts' ), 10, 1 ); } } } sticky-posts/wpml-sticky-posts-sync.php 0000755 00000013072 14720342453 0014346 0 ustar 00 <?php class WPML_Sticky_Posts_Sync { /** @var SitePress */ private $sitepress; /** @var WPML_Post_Translation $post_translation */ private $post_translation; /** @var WPML_Sticky_Posts_Lang_Filter */ private $populate_lang_option; /** * @param SitePress $sitepress * @param WPML_Post_Translation $post_translation * @param WPML_Sticky_Posts_Lang_Filter $populate_lang_option */ public function __construct( SitePress $sitepress, WPML_Post_Translation $post_translation, WPML_Sticky_Posts_Lang_Filter $populate_lang_option ) { $this->sitepress = $sitepress; $this->post_translation = $post_translation; $this->populate_lang_option = $populate_lang_option; } /** * It returns only those sticky posts which belong to a current language * * @return array|false */ public function pre_option_sticky_posts_filter() { $current_language = $this->sitepress->get_current_language(); if ( 'all' === $current_language ) { return false; } $option = 'sticky_posts_' . $current_language; $posts = get_option( $option ); if ( false === $posts ) { $posts = $this->get_unfiltered_sticky_posts_option(); if ( $posts ) { $posts = $this->populate_lang_option->filter_by_language( $this->get_unfiltered_sticky_posts_option(), $current_language ); update_option( $option, $posts, true ); } } return $posts; } /** * Ensure that the original main `sticky_posts` option contains sticky posts from ALL languages * * @param array $posts * * @return array */ public function pre_update_option_sticky_posts( $posts ) { $langs = array_keys( $this->sitepress->get_active_languages() ); $langs = array_diff( $langs, array( $this->sitepress->get_current_language() ) ); $lang_parts = array_map( array( $this, 'get_option_by_lang' ), $langs ); $lang_parts[] = array_map( 'intval', $posts ); return array_unique( call_user_func_array( 'array_merge', $lang_parts ) ); } /** * It marks as `sticky` all posts which are translation of the post or have the same original post. * Basically, it means that they have the same trid in icl_translations table. * * @param int $post_id */ public function on_post_stuck( $post_id ) { $translations = $this->get_post_translations( $post_id ); if ( $translations ) { foreach ( $translations as $lang => $translated_post_id ) { $this->add_post_id( 'sticky_posts_' . $lang, (int) $translated_post_id ); $this->add_post_id_to_original_option( (int) $translated_post_id ); } } else { $this->add_post_id( 'sticky_posts_' . $this->sitepress->get_current_language(), (int) $post_id ); } } /** * It un-marks as `sticky` all posts which are translation of the post or have the same original post. * * @param int $post_id */ public function on_post_unstuck( $post_id ) { foreach ( $this->get_post_translations( $post_id ) as $lang => $translated_post_id ) { $this->remove_post_id( 'sticky_posts_' . $lang, (int) $translated_post_id ); $this->remove_post_id_from_original_option( (int) $translated_post_id ); } } /** * It returns an original, unfiltered `sticky_posts` option which contains sticky posts from ALL languages * * @return array|false */ public function get_unfiltered_sticky_posts_option() { remove_filter( 'pre_option_sticky_posts', array( $this, 'pre_option_sticky_posts_filter' ) ); $posts = get_option( 'sticky_posts' ); add_filter( 'pre_option_sticky_posts', array( $this, 'pre_option_sticky_posts_filter' ), 10, 0 ); return $posts; } /** * @param int $post_id * * @return array */ private function get_post_translations( $post_id ) { $this->post_translation->reload(); $trid = $this->post_translation->get_element_trid( $post_id ); return $this->post_translation->get_element_translations( false, $trid, false ); } /** * @param int $post_id */ private function add_post_id_to_original_option( $post_id ) { $this->update_original_option( $post_id, array( $this, 'add_post_id' ) ); } /** * @param string $option * @param int $post_id */ private function add_post_id( $option, $post_id ) { $sticky_posts = get_option( $option, array() ); if ( ! in_array( $post_id, $sticky_posts, true ) ) { $sticky_posts[] = $post_id; update_option( $option, $sticky_posts, true ); } } /** * @param int $post_id */ private function remove_post_id_from_original_option( $post_id ) { $this->update_original_option( $post_id, array( $this, 'remove_post_id' ) ); } /** * @param int $post_id * @param callable $callback */ private function update_original_option( $post_id, $callback ) { remove_filter( 'pre_option_sticky_posts', array( $this, 'pre_option_sticky_posts_filter' ) ); remove_filter( 'pre_update_option_sticky_posts', array( $this, 'pre_update_option_sticky_posts' ) ); call_user_func( $callback, 'sticky_posts', $post_id ); add_filter( 'pre_update_option_sticky_posts', array( $this, 'pre_update_option_sticky_posts' ), 10, 1 ); add_filter( 'pre_option_sticky_posts', array( $this, 'pre_option_sticky_posts_filter' ), 10, 0 ); } /** * @param string $option * @param int $post_id */ private function remove_post_id( $option, $post_id ) { $sticky_posts = get_option( $option, array() ); if ( ( $key = array_search( $post_id, $sticky_posts ) ) !== false ) { unset( $sticky_posts[ $key ] ); update_option( $option, array_values( $sticky_posts ), true ); } } /** * @param string $lang * * @return array */ private function get_option_by_lang( $lang ) { return get_option( 'sticky_posts_' . $lang, array() ); } } sticky-posts/wpml-sticky-posts-lang-filter.php 0000755 00000002763 14720342453 0015603 0 ustar 00 <?php class WPML_Sticky_Posts_Lang_Filter { /** @var SitePress */ private $sitepress; /** @var WPML_Post_Translation */ private $post_translation; /** @var array */ private $post_valid_in_all_langs_cache = array(); /** * @param SitePress $sitepress * @param WPML_Post_Translation $post_translation */ public function __construct( SitePress $sitepress, WPML_Post_Translation $post_translation ) { $this->sitepress = $sitepress; $this->post_translation = $post_translation; } /** * @param array $posts * @param string $lang * * @return array */ public function filter_by_language( array $posts, $lang ) { if ( ! $posts ) { return $posts; } $result = array(); foreach ( $posts as $post_id ) { if ( $this->post_translation->get_element_lang_code( $post_id ) === $lang || $this->is_post_type_valid_in_any_language( $this->post_translation->get_type( $post_id ) ) ) { $result[] = $post_id; } } return $result; } /** * @param string $post_type * * @return bool */ private function is_post_type_valid_in_any_language( $post_type ) { if ( ! array_key_exists( $post_type, $this->post_valid_in_all_langs_cache ) ) { $this->post_valid_in_all_langs_cache[ $post_type ] = ! $this->sitepress->is_translated_post_type( $post_type ) || $this->sitepress->is_display_as_translated_post_type( $post_type ); } return $this->post_valid_in_all_langs_cache[ $post_type ]; } } utils/class-wpml-array-search.php 0000755 00000002545 14720342453 0013073 0 ustar 00 <?php class WPML_TM_Array_Search { /** * @var array */ private $where; /** * @var array */ private $data; /** * @param array $data * * @return $this */ public function set_data( $data ) { $this->data = $data; return $this; } /** * @param array $args * * @return $this */ public function set_where( $args ) { $this->where = $args; return $this; } /** * @return array */ public function get_results() { $results = array(); foreach ( $this->where as $key => $clause ) { $operator = isset( $clause['operator'] ) ? strtoupper( $clause['operator'] ) : 'LIKE'; foreach ( $this->data as $data ) { if ( in_array( $data, $results, true ) ) { continue; } $field_value = ''; if ( is_object( $data ) ) { $field_value = $data->{$clause['field']}; } elseif ( is_array( $data ) ) { $field_value = $data[ $clause['field'] ]; } switch ( $operator ) { default: case 'LIKE': if ( false !== strpos( strtolower( $field_value ), strtolower( $clause['value'] ) ) ) { $results[] = $data; } break; case '=': if ( strlen( $field_value ) === strlen( $clause['value'] ) && 0 === strpos( strtolower( $field_value ), strtolower( $clause['value'] ) ) ) { $results[] = $data; } } } } return array_values( $results ); } } utils/wpml-wp-cron-check.php 0000755 00000002006 14720342453 0012037 0 ustar 00 <?php class WPML_WP_Cron_Check { const TRANSIENT_NAME = 'wpml_cron_check'; /** @var WPML_PHP_Functions $php_functions */ private $php_functions; public function __construct( WPML_PHP_Functions $php_functions ) { $this->php_functions = $php_functions; } /** @return bool */ public function verify() { if ( $this->is_doing_cron() ) { return true; } if ( $this->php_functions->constant( 'DISABLE_WP_CRON' ) ) { return false; } $is_on_from_transient = get_transient( self::TRANSIENT_NAME ); if ( false !== $is_on_from_transient ) { return (bool) $is_on_from_transient; } $is_on = 1; $request = wp_remote_get( site_url( 'wp-cron.php' ) ); if ( is_wp_error( $request ) ) { $is_on = 0; } elseif ( $request['response']['code'] !== 200 ) { $is_on = 0; } set_transient( self::TRANSIENT_NAME, $is_on, 12 * HOUR_IN_SECONDS ); return (bool) $is_on; } /** @return bool */ public function is_doing_cron() { return (bool) $this->php_functions->constant( 'DOING_CRON' ); } } url-handling/class-wpml-wp-in-subdir-url-filters-factory.php 0000755 00000001710 14720342453 0020162 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_WP_In_Subdir_URL_Filters_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { public function create() { /** * @var WPML_URL_Converter $wpml_url_converter * @var SitePress $sitepress */ global $wpml_url_converter, $sitepress; $lang_negotiation_type = $sitepress->get_setting( 'language_negotiation_type', false ); if ( WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY === (int) $lang_negotiation_type ) { $request_uri = Sanitize::stringProp( 'REQUEST_URI', $_SERVER ); if ( ! is_string( $request_uri ) ) { return null; } $uri_without_subdir = wpml_strip_subdir_from_url( $request_uri ); if ( trim( $request_uri, '/' ) !== trim( $uri_without_subdir, '/' ) ) { $backtrace = new WPML_Debug_BackTrace( null, 5 ); return new WPML_WP_In_Subdir_URL_Filters( $backtrace, $sitepress, $wpml_url_converter, $uri_without_subdir ); } } return null; } } url-handling/class-wpml-endpoints-support-factory.php 0000755 00000001231 14720342453 0017107 0 ustar 00 <?php class WPML_Endpoints_Support_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader, IWPML_Deferred_Action_Loader { public function get_load_action() { return 'plugins_loaded'; } /** * @return WPML_Endpoints_Support */ public function create() { global $sitepress, $wpml_post_translations; if ( $this->are_st_functions_loaded() ) { return new WPML_Endpoints_Support( $wpml_post_translations, $sitepress->get_current_language(), $sitepress->get_default_language() ); } return null; } /** * @return bool */ private function are_st_functions_loaded() { return function_exists( 'icl_get_string_id' ); } } url-handling/class-wpml-absolute-links-blacklist.php 0000755 00000002337 14720342453 0016637 0 ustar 00 <?php use WPML\FP\Str; use WPML\FP\Lst; use WPML\FP\Fns; use WPML\FP\Wrapper; use function WPML\FP\pipe; class WPML_Absolute_Links_Blacklist { private $blacklist_requests; public function __construct( $blacklist_requests ) { $this->blacklist_requests = $blacklist_requests; if ( ! is_array( $this->blacklist_requests ) ) { $this->blacklist_requests = array(); } } public function is_blacklisted( $request ) { $isBlacklisted = function ( $request ) { return Lst::includes( $request, $this->blacklist_requests ) || $this->is_blacklisted_with_regex( $request ); }; return Wrapper::of( $request ) ->map( Str::split( '/' ) ) ->map( Fns::map( Fns::unary( pipe( 'urlencode', 'strtolower' ) ) ) ) ->map( Lst::join( '/' ) ) ->map( $isBlacklisted ) ->get(); } private function is_blacklisted_with_regex( $request ) { foreach ( $this->blacklist_requests as $blacklist_request ) { if ( $this->is_regex( $blacklist_request ) && preg_match( $blacklist_request, $request ) ) { return true; } } return false; } private function is_regex( $blacklist_request ) { return strpos( $blacklist_request, '/' ) === 0 && strrpos( $blacklist_request, '/' ) === strlen( $blacklist_request ) - 1; } } url-handling/class-wpml-endpoints-support.php 0000755 00000017766 14720342453 0015466 0 ustar 00 <?php class WPML_Endpoints_Support { const STRING_CONTEXT = 'WP Endpoints'; /** * @var WPML_Post_Translation */ private $post_translations; /** * @var string */ private $current_language; /** * @var string */ private $default_language; public function __construct( WPML_Post_Translation $post_translations, $current_language, $default_language ) { $this->post_translations = $post_translations; $this->current_language = $current_language; $this->default_language = $default_language; } public function add_hooks() { add_action( 'init', array( $this, 'add_endpoints_translations' ) ); add_filter( 'option_rewrite_rules', array( $this, 'translate_endpoints_in_rewrite_rules' ), 0, 1 ); // high priority add_filter( 'page_link', array( $this, 'endpoint_permalink_filter' ), 10, 2 ); add_filter( 'wpml_ls_language_url', array( $this, 'add_endpoint_to_current_ls_language_url' ), 10, 2 ); add_filter( 'wpml_get_endpoint_translation', array( $this, 'get_endpoint_translation' ), 10, 3 ); add_filter( 'wpml_register_endpoint_string', array( $this, 'register_endpoint_string' ), 10, 2 ); add_filter( 'wpml_get_endpoint_url', array( $this, 'get_endpoint_url' ), 10, 4 ); add_filter( 'wpml_get_current_endpoint', array( $this, 'get_current_endpoint' ) ); add_filter( 'wpml_get_registered_endpoints', array( $this, 'get_registered_endpoints' ) ); } public function add_endpoints_translations() { $registered_endpoints = $this->get_registered_endpoints(); if ( $registered_endpoints ) { foreach ( $registered_endpoints as $endpoint_key => $endpoint_value ) { $endpoint_translation = $this->get_endpoint_translation( $endpoint_key, $endpoint_value ); if ( $endpoint_value !== urldecode( $endpoint_translation ) ) { add_rewrite_endpoint( $endpoint_translation, EP_ROOT | EP_PAGES, $endpoint_value ); } } } do_action( 'wpml_after_add_endpoints_translations', $this->current_language ); } /** * @param string $key * @param string $endpoint * @param null|string $language * * @return string */ public function get_endpoint_translation( $key, $endpoint, $language = null ) { $this->register_endpoint_string( $key, $endpoint ); $endpoint_translation = apply_filters( 'wpml_translate_single_string', $endpoint, self::STRING_CONTEXT, $key, $language ? $language : $this->current_language ); if ( ! empty( $endpoint_translation ) ) { return implode( '/', array_map( 'rawurlencode', explode( '/', $endpoint_translation ) ) ); } else { return $endpoint; } } /** * @param string $key * @param string $endpoint */ public function register_endpoint_string( $key, $endpoint ) { if ( $key === $endpoint ) { if ( ! $this->is_registered( $endpoint ) ) { icl_register_string( self::STRING_CONTEXT, $key, $endpoint ); wp_cache_delete( self::STRING_CONTEXT, __CLASS__ ); } } } /** * @param string $endpoint * * @return bool */ private function is_registered( $endpoint ) { global $wpdb; $endpoints = wp_cache_get( self::STRING_CONTEXT, __CLASS__ ); if ( false === $endpoints ) { $endpoints = $wpdb->get_col( $wpdb->prepare( "SELECT value FROM {$wpdb->prefix}icl_strings WHERE context = %s", self::STRING_CONTEXT ) ); wp_cache_set( self::STRING_CONTEXT, $endpoints, __CLASS__ ); } return in_array( $endpoint, $endpoints, true ); } /** * @param array $value * * @return array */ public function translate_endpoints_in_rewrite_rules( $value ) { if ( ! empty( $value ) ) { $registered_endpoints = $this->get_registered_endpoints(); if ( $registered_endpoints ) { foreach ( $registered_endpoints as $endpoint_key => $endpoint_value ) { $endpoint_translation = $this->get_endpoint_translation( $endpoint_key, $endpoint_value ); if ( $endpoint_value === urldecode( $endpoint_translation ) ) { continue; } $buff_value = array(); foreach ( $value as $k => $v ) { $k = preg_replace( '/(\/|^)' . $endpoint_value . '(\/)?(\(\/\(\.\*\)\)\?\/\?\$)/', '$1' . $endpoint_translation . '$2$3', $k ); $buff_value[ $k ] = $v; } $value = $buff_value; } } } return $value; } /** * @param string $link * @param int $pid * * @return string */ public function endpoint_permalink_filter( $link, $pid ) { global $post, $wp; if ( isset( $post->ID ) && $post->ID && ! is_admin() ) { $page_lang = $this->post_translations->get_element_lang_code( $post->ID ); if ( ! $page_lang ) { return $link; } if ( $pid === $post->ID ) { $pid_in_page_lang = $pid; } else { $translations = $this->post_translations->get_element_translations( $pid ); $pid_in_page_lang = isset( $translations[ $page_lang ] ) ? $translations[ $page_lang ] : $pid; } $current_lang = apply_filters( 'wpml_current_language', $this->current_language ); if ( ( $current_lang != $page_lang && $pid_in_page_lang == $post->ID ) || apply_filters( 'wpml_endpoint_force_permalink_filter', false, $current_lang, $page_lang ) ) { $endpoints = $this->get_registered_endpoints(); foreach ( $endpoints as $key => $endpoint ) { if ( isset( $wp->query_vars[ $key ] ) ) { list( $link, $endpoint ) = apply_filters( 'wpml_endpoint_permalink_filter', array( $link, $endpoint ), $key ); $link = $this->get_endpoint_url( $this->get_endpoint_translation( $key, $endpoint, $current_lang ), $wp->query_vars[ $key ], $link, $page_lang ); } } } } return $link; } /** * @param string $endpoint * @param string $value * @param string $permalink * @param bool|string $page_lang * * @return string */ public function get_endpoint_url( $endpoint, $value = '', $permalink = '', $page_lang = false ) { $value = apply_filters( 'wpml_endpoint_url_value', $value, $page_lang ); if ( get_option( 'permalink_structure' ) ) { $query_string = ''; if ( strstr( $permalink, '?' ) ) { $query_string = '?' . parse_url( $permalink, PHP_URL_QUERY ); $permalink = current( explode( '?', $permalink ) ); } $url = trailingslashit( $permalink ) . $endpoint . '/' . $value . $query_string; } else { $url = add_query_arg( $endpoint, $value, $permalink ); } return $url; } /** * @param string $url * @param array $data * * @return string */ public function add_endpoint_to_current_ls_language_url( $url, $data ) { global $post; $post_lang = ''; $current_endpoint = array(); if ( isset( $post->ID ) && $post->ID ) { $post_lang = $this->post_translations->get_element_lang_code( $post->ID ); $current_endpoint = $this->get_current_endpoint( $data['code'] ); if ( $post_lang === $data['code'] && $current_endpoint ) { $url = $this->get_endpoint_url( $current_endpoint['key'], $current_endpoint['value'], $url ); } } return apply_filters( 'wpml_current_ls_language_url_endpoint', $url, $post_lang, $data, $current_endpoint ); } /** * @return array */ public function get_registered_endpoints() { global $wp_rewrite; $endpoints = empty( $wp_rewrite->endpoints ) ? [] : $wp_rewrite->endpoints; /** * @param array $endpoints * * @return array * * @deprecated since 4.6, use `wpml_registered_endpoints` instead. */ $endpoints = apply_filters( 'option_wpml_registered_endpoints', array_filter( wp_list_pluck( $endpoints, 2, 1 ) ) ); /** * Filter the endpoints that WPML will handle. * * @param array $endpoints * * @return array * * @since 4.6 */ return apply_filters( 'wpml_registered_endpoints', $endpoints ); } /** * @param string $language * * @return array */ public function get_current_endpoint( $language ) { global $wp; $current_endpoint = array(); foreach ( $this->get_registered_endpoints() as $key => $value ) { if ( isset( $wp->query_vars[ $key ] ) ) { $current_endpoint['key'] = $this->get_endpoint_translation( $key, $value, $language ); $current_endpoint['value'] = $wp->query_vars[ $key ]; break; } } return $current_endpoint; } } url-handling/class-wpml-score-hierarchy.php 0000755 00000006476 14720342453 0015034 0 ustar 00 <?php class WPML_Score_Hierarchy { private $data = array(); private $slugs = array(); /** * WPML_Score_Hierarchy constructor. * * @param object[] $data_set * @param string[] $slugs */ public function __construct( $data_set, $slugs ) { $this->data = $data_set; $this->slugs = $slugs; } /** * Array of best matched post_ids. Better matches have a lower index! * * @return int[] */ public function get_possible_ids_ordered() { $pages_with_name = $this->data; $slugs = $this->slugs; $parent_slugs = array_slice( $slugs, 0, - 1 ); foreach ( $this->data as $key => $page ) { if ( $page->parent_name ) { $slug_pos = array_keys( $slugs, $page->post_name, true ); $par_slug_pos = array_keys( $slugs, $page->parent_name, true ); if ( (bool) $par_slug_pos !== false && (bool) $slug_pos !== false ) { $remove = true; foreach ( $slug_pos as $child_slug_pos ) { if ( in_array( $child_slug_pos - 1, $par_slug_pos ) ) { $remove = false; break; } } if ( $remove === true ) { unset( $pages_with_name[ $key ] ); } } } } $related_ids = array(); $matching_ids = array(); foreach ( $pages_with_name as $key => $page ) { $correct_slug = end( $slugs ); if ( $page->post_name === $correct_slug ) { if ( $this->is_exactly_matching_all_slugs_in_order( $page ) ) { $matching_ids[] = (int) $page->ID; } else { $related_ids[ $page->ID ] = $this->calculate_score( $parent_slugs, $pages_with_name, $page ); } } } arsort( $related_ids ); $related_ids = array_keys( $related_ids ); return array( 'matching_ids' => $matching_ids, 'related_ids' => $related_ids, ); } /** * Get page object by its id. * * @param int $id * * @return false|object */ private function get_page_by_id( $id ) { foreach ( $this->data as $page ) { if ( (int) $page->ID === (int) $id ) { return $page; } } return false; } /** * @param string[] $parent_slugs * @param object[] $pages_with_name * @param object $page * * @return int */ private function calculate_score( $parent_slugs, $pages_with_name, $page ) { $parent_positions = array_keys( $parent_slugs, $page->parent_name, true ); $new_score = (bool) $parent_positions === true ? max( $parent_positions ) + 1 : ( $page->post_parent ? - 1 : 0 ); if ( $page->post_parent ) { foreach ( $pages_with_name as $parent ) { if ( $parent->ID == $page->post_parent ) { $new_score += $this->calculate_score( array_slice( $parent_slugs, 0, count( $parent_slugs ) - 1 ), $pages_with_name, $parent ); break; } } } return $new_score; } /** * @param object $page * * @return bool */ private function is_exactly_matching_all_slugs_in_order( $page ) { return $this->slugs === $this->get_slugs_for_page( $page ); } /** * @param object $current_page * * @return array */ private function get_slugs_for_page( $current_page ) { $slugs = array(); while ( $current_page && $current_page->post_name ) { $slugs[] = $current_page->post_name; $parent_name = $current_page->parent_name; $current_page = $this->get_page_by_id( $current_page->post_parent ); if ( ! $current_page && $parent_name ) { $slugs[] = $parent_name; } } return array_reverse( $slugs ); } } url-handling/class-wpml-allowed-redirect-hosts.php 0000755 00000002050 14720342453 0016311 0 ustar 00 <?php class WPML_Allowed_Redirect_Hosts extends WPML_SP_User { public function __construct( &$sitepress ) { parent::__construct( $sitepress ); } public function get_hosts( $hosts ) { $domains = $this->sitepress->get_setting( 'language_domains' ); $default_language = $this->sitepress->get_default_language(); $default_home = $this->sitepress->convert_url( $this->sitepress->get_wp_api()->get_home_url(), $default_language ); $home_schema = wpml_parse_url( (string) $default_home, PHP_URL_SCHEME ) . '://'; if ( ! isset( $domains[ $default_language ] ) ) { $domains[ $default_language ] = wpml_parse_url( (string) $default_home, PHP_URL_HOST ); } $active_languages = $this->sitepress->get_active_languages(); foreach ( $domains as $code => $url ) { if ( ! empty( $active_languages[ $code ] ) ) { $url = $home_schema . $url; $parts = wpml_parse_url( $url ); if ( isset( $parts['host'] ) && ! in_array( $parts['host'], $hosts ) ) { $hosts[] = $parts['host']; } } } return $hosts; } } url-handling/class-wpml-wp-in-subdir-url-filters.php 0000755 00000004112 14720342453 0016514 0 ustar 00 <?php class WPML_WP_In_Subdir_URL_Filters implements IWPML_Action { /** @var WPML_Debug_BackTrace $backtrace */ private $backtrace; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_URL_Converter $url_converter */ private $url_converter; /** @var string $uri_without_subdir */ private $uri_without_subdir; /** * @param WPML_Debug_BackTrace $backtrace * @param SitePress $sitepress * @param WPML_URL_Converter $url_converter * @param string $uri_without_subdir */ public function __construct( WPML_Debug_BackTrace $backtrace, SitePress $sitepress, WPML_URL_Converter $url_converter, $uri_without_subdir ) { $this->url_converter = $url_converter; $this->backtrace = $backtrace; $this->sitepress = $sitepress; $this->url_converter = $url_converter; $this->uri_without_subdir = $uri_without_subdir; } public function add_hooks() { add_filter( 'home_url', array( $this, 'home_url_filter_on_parse_request' ), PHP_INT_MAX ); } /** * This filter is only applied in `WP::parse_request` in order to get * the proper URI cleanup base in `$home_path_regex` * * @param string $home_url * * @return string */ public function home_url_filter_on_parse_request( $home_url ) { if ( $this->backtrace->is_class_function_in_call_stack( 'WP', 'parse_request' ) ) { if ( $this->request_uri_begins_with_lang() ) { /** * The URL is already filtered with the lang directory. * e.g. http://example.org/subdir/en/ */ return $home_url; } /** * We return the root URL of the WP install. * e.g. http://example.org/subdir/ */ return $this->url_converter->get_abs_home(); } return $home_url; } /** * @return bool */ private function request_uri_begins_with_lang() { $active_lang_codes = array_keys( $this->sitepress->get_active_languages() ); $uri_parts = explode( '/', trim( wpml_parse_url( $this->uri_without_subdir, PHP_URL_PATH ), '/' ) ); return isset( $uri_parts[0] ) && in_array( $uri_parts[0], $active_lang_codes, true ); } } url-handling/class-wpml-home-url-filter-context.php 0000755 00000002501 14720342453 0016423 0 ustar 00 <?php class WPML_Home_Url_Filter_Context { const REST_REQUEST = 'rest-request'; const REWRITE_RULES = 'rewrite-rules'; const PAGINATION = 'pagination'; /** * @var int */ private $language_negotiation_type; /** * @var string */ private $orig_scheme; /** * @var WPML_Debug_BackTrace */ private $debug_backtrace; public function __construct( $language_negotiation_type, $orig_scheme, WPML_Debug_BackTrace $debug_backtrace ) { $this->language_negotiation_type = $language_negotiation_type; $this->orig_scheme = $orig_scheme; $this->debug_backtrace = $debug_backtrace; } /** * @return bool */ public function should_not_filter() { return $this->is_rest_request() || $this->rewriting_rules() || $this->pagination_link(); } /** * @return bool */ private function is_rest_request() { return in_array( $this->orig_scheme, array( 'json', 'rest' ), true ); } /** * @return bool */ private function rewriting_rules() { return $this->debug_backtrace->is_class_function_in_call_stack( 'WP_Rewrite', 'rewrite_rules' ); } /** * @return bool */ private function pagination_link() { return WPML_LANGUAGE_NEGOTIATION_TYPE_PARAMETER === $this->language_negotiation_type && $this->debug_backtrace->is_function_in_call_stack( 'get_pagenum_link' ); } } url-handling/wpml-url-filters.class.php 0000755 00000035166 14720342453 0014214 0 ustar 00 <?php use WPML\FP\Relation; use \WPML\FP\Str; use \WPML\SuperGlobals\Server; /** * Class WPML_URL_Filters */ class WPML_URL_Filters { /** @var \SitePress */ private $sitepress; /** @var \WPML_Post_Translation $post_translation */ private $post_translation; /** @var \WPML_Canonicals */ private $canonicals; /** @var \WPML_URL_Converter $url_converter */ private $url_converter; /** @var \WPML_Debug_BackTrace */ private $debug_backtrace; /** * WPML_URL_Filters constructor. * * @param \WPML_Post_Translation $post_translation * @param string $url_converter * @param \WPML_Canonicals $canonicals * @param \SitePress $sitepress * @param \WPML_Debug_BackTrace $debug_backtrace */ public function __construct( &$post_translation, &$url_converter, WPML_Canonicals $canonicals, &$sitepress, WPML_Debug_BackTrace $debug_backtrace ) { $this->sitepress = &$sitepress; $this->post_translation = &$post_translation; $this->url_converter = &$url_converter; $this->canonicals = $canonicals; $this->debug_backtrace = $debug_backtrace; if ( $this->frontend_uses_root() === true ) { WPML_Root_Page::init(); } $this->add_hooks(); } private function add_hooks() { if ( $this->frontend_uses_root() === true ) { add_filter( 'page_link', array( $this, 'page_link_filter_root' ), 1, 2 ); } else { add_filter( 'page_link', array( $this, 'page_link_filter' ), 1, 2 ); } $this->add_global_hooks(); if ( $this->has_wp_get_canonical_url() ) { add_filter( 'get_canonical_url', array( $this, 'get_canonical_url_filter' ), 1, 2 ); } add_action( 'current_screen', [ $this, 'permalink_options_home_url' ] ); } public function add_global_hooks() { add_filter( 'home_url', [ $this, 'home_url_filter' ], - 10, 4 ); // posts, pages & attachments links filters add_filter( 'post_link', [ $this, 'permalink_filter' ], 1, 2 ); add_filter( 'attachment_link', [ $this, 'permalink_filter' ], 1, 2 ); add_filter( 'post_type_link', [ $this, 'permalink_filter' ], 1, 2 ); add_filter( 'wpml_filter_link', [ $this, 'permalink_filter' ], 1, 2 ); add_filter( 'get_edit_post_link', [ $this, 'get_edit_post_link' ], 1, 3 ); add_filter( 'oembed_request_post_id', [ $this, 'embedded_front_page_id_filter' ], 1, 2 ); add_filter( 'post_embed_url', [ $this, 'fix_post_embedded_url' ], 1, 1 ); } public function remove_global_hooks() { // posts and pages links filters remove_filter( 'oembed_request_post_id', [ $this, 'embedded_front_page_id_filter' ], 1 ); remove_filter( 'post_embed_url', [ $this, 'fix_post_embedded_url' ], 1 ); remove_filter( 'get_edit_post_link', [ $this, 'get_edit_post_link' ], 1 ); remove_filter( 'wpml_filter_link', [ $this, 'permalink_filter' ], 1 ); remove_filter( 'post_type_link', [ $this, 'permalink_filter' ], 1 ); remove_filter( 'attachment_link', [ $this, 'permalink_filter' ], 1 ); remove_filter( 'post_link', [ $this, 'permalink_filter' ], 1 ); remove_filter( 'home_url', [ $this, 'home_url_filter' ], - 10 ); } /** * @param int $post_id * @param string $url * * @return int * * @hook oembed_request_post_id */ public function embedded_front_page_id_filter( $post_id, $url ) { if ( ! $post_id && $this->is_front_page( $url ) ) { $page_on_front = get_option( 'page_on_front' ); if ( $page_on_front && get_post( $page_on_front ) instanceof WP_Post ) { $post_id = (int) $page_on_front; } } return $post_id; } /** * @param string $embedded_url * * @return string */ public function fix_post_embedded_url( $embedded_url ) { $query = wpml_parse_url( $embedded_url, PHP_URL_QUERY ); $embed = user_trailingslashit( 'embed' ); if ( Str::includes( $embed, $query ) ) { $embedded_url = Str::split( '?', $embedded_url )[0] . $embed . '?' . untrailingslashit( Str::replace( $embed, '', $query ) ); } return $embedded_url; } /** * Filters the link to a post's edit screen by appending the language query argument * * @param string $link * @param int $id * @param string $context * * @return string * * @hook get_edit_post_link */ public function get_edit_post_link( $link, $id, $context = 'display' ) { if ( $id && (bool) ( $lang = $this->post_translation->get_element_lang_code( $id ) ) === true ) { $link .= ( 'display' === $context ? '&' : '&' ) . 'lang=' . $lang; if ( ! did_action( 'wpml_pre_status_icon_display' ) ) { do_action( 'wpml_pre_status_icon_display' ); } $link = apply_filters( 'wpml_link_to_translation', $link, $id, $lang, $this->post_translation->get_element_trid( $id ) ); } return $link; } /** * Permalink filter that is used when the site uses a root page * * @param string $link * @param int|WP_Post $pid * * @return string */ public function permalink_filter_root( $link, $pid ) { $pid = is_object( $pid ) ? $pid->ID : $pid; $link = $this->sitepress->get_root_page_utils()->get_root_page_id() != $pid ? $this->permalink_filter( $link, $pid ) : $this->filter_root_permalink( $link ); return $link; } /** * @param string $link * @param int $pid * * @return string|WPML_Notice|WPML_Notice_Render */ public function page_link_filter_root( $link, $pid ) { $pid = is_object( $pid ) ? $pid->ID : $pid; $link = $this->sitepress->get_root_page_utils()->get_root_page_id() != $pid ? $this->page_link_filter( $link, $pid ) : $this->filter_root_permalink( $link ); return $link; } /** * Filters links to the root page, so that they are displayed properly in the front-end. * * @param string $url * * @return string */ public function filter_root_permalink( $url ) { $root_page_utils = $this->sitepress->get_root_page_utils(); if ( $root_page_utils->get_root_page_id() > 0 && $root_page_utils->is_url_root_page( $url ) ) { $url_parts = wpml_parse_url( $url ); $query = isset( $url_parts['query'] ) ? $url_parts['query'] : ''; $path = isset( $url_parts['path'] ) ? $url_parts['path'] : ''; $slugs = array_filter( explode( '/', $path ) ); $last_slug = array_pop( $slugs ); $new_url = $this->url_converter->get_abs_home(); $new_url = is_numeric( $last_slug ) ? trailingslashit( trailingslashit( $new_url ) . $last_slug ) : $new_url; $query = $this->unset_page_query_vars( $query ); $new_url = trailingslashit( $new_url ); $url = (bool) $query === true ? trailingslashit( $new_url ) . '?' . $query : $new_url; } return $url; } /** * @param string $link * @param int|WP_Post $post * * @return string */ public function permalink_filter( $link, $post ) { if ( ! $post ) { return $link; } $post_id = $post; if ( is_object( $post ) ) { $post_id = $post->ID; } /** @var int $post_id */ if ( $post_id < 1 ) { return $link; } $canonical_url = $this->canonicals->permalink_filter( $link, $post_id ); if ( $canonical_url ) { return $canonical_url; } $post_element = new WPML_Post_Element( $post_id, $this->sitepress ); if ( ! is_wp_error( $post_element->get_wp_element_type() ) && ! $this->is_display_as_translated_mode( $post_element ) && $post_element->is_translatable() ) { $link = $this->get_translated_permalink( $link, $post_id, $post_element ); } return $this->url_converter->get_strategy()->fix_trailingslashit( $link ); } /** * @param string $link * @param int|WP_Post $post * * @return string */ public function page_link_filter( $link, $post ) { return $this->permalink_filter( $link, $post ); } private function has_wp_get_canonical_url() { return $this->sitepress->get_wp_api()->function_exists( 'wp_get_canonical_url' ); } /** * @param string|bool $canonical_url * @param WP_Post $post * * @return mixed * @throws \InvalidArgumentException */ public function get_canonical_url_filter( $canonical_url, $post ) { return $this->canonicals->get_canonical_url( $canonical_url, $post, $this->get_request_language() ); } /** * @param WP_Screen $current_screen */ public function permalink_options_home_url( $current_screen ) { if ( Relation::propEq( 'id', 'options-permalink', $current_screen ) ) { add_filter( 'wpml_get_home_url', 'untrailingslashit' ); } } /** * @param string $url * @param string $path * @param string $orig_scheme * @param int $blog_id * * @return string */ public function home_url_filter( $url, $path, $orig_scheme, $blog_id ) { $language_negotiation_type = (int) $this->sitepress->get_setting( 'language_negotiation_type' ); $context = new WPML_Home_Url_Filter_Context( $language_negotiation_type, $orig_scheme, $this->debug_backtrace ); if ( $context->should_not_filter() ) { return $url; } $url_language = $this->get_request_language(); if ( 'relative' === $orig_scheme ) { $home_url = $this->url_converter->get_home_url_relative( $url, $url_language ); } else { $home_url = $this->url_converter->convert_url( $url, $url_language ); } $home_url = apply_filters( 'wpml_get_home_url', $home_url, $url, $path, $orig_scheme, $blog_id ); return $home_url; } public function frontend_uses_root() { /** @var array $urls */ $urls = $this->sitepress->get_setting( 'urls' ); if ( is_admin() ) { $uses_root = isset( $urls['root_page'], $urls['show_on_root'] ) && ! empty( $urls['directory_for_default_language'] ) && ( in_array( $urls['show_on_root'], array( 'page', 'html_file' ) ) ); } else { $uses_root = isset( $urls['root_page'], $urls['show_on_root'] ) && ! empty( $urls['directory_for_default_language'] ) && ( ( $urls['root_page'] > 0 && 'page' === $urls['show_on_root'] ) || ( $urls['root_html_file_path'] && 'html_file' === $urls['show_on_root'] ) ); } return $uses_root; } /** * Finds the correct language a post belongs to by handling the special case of the post edit screen. * * @param int $post_id * * @return bool|mixed|null|String */ private function get_permalink_filter_lang( $post_id ) { if ( isset( $_POST['action'] ) && $_POST['action'] === 'sample-permalink' ) { $code = $this->get_language_from_url(); $code = $code ? $code : ( ! isset( $_SERVER['HTTP_REFERER'] ) ? $this->sitepress->get_default_language() : $this->url_converter->get_language_from_url( $_SERVER['HTTP_REFERER'] ) ); } else { $code = $this->post_translation->get_element_lang_code( $post_id ); } return $code; } private function unset_page_query_vars( $query ) { parse_str( (string) $query, $query_parts ); foreach ( array( 'p', 'page_id', 'page', 'pagename', 'page_name', 'attachement_id' ) as $part ) { if ( isset( $query_parts[ $part ] ) && ! ( $part === 'page_id' && ! empty( $query_parts['preview'] ) ) ) { unset( $query_parts[ $part ] ); } } return http_build_query( $query_parts ); } private function get_language_from_url( $return_default_language_if_missing = false ) { $query_string_argument = ''; $language = ''; if ( $return_default_language_if_missing ) { $language = $this->sitepress->get_current_language(); } if ( array_key_exists( 'wpml_lang', $_GET ) ) { $query_string_argument = 'wpml_lang'; } elseif ( array_key_exists( 'lang', $_GET ) ) { $query_string_argument = 'lang'; } if ( '' !== $query_string_argument ) { $language = filter_var( $_GET[ $query_string_argument ], FILTER_SANITIZE_FULL_SPECIAL_CHARS ); } return $language; } /** * @param string $link * @param int $post * @param WPML_Post_Element $post_element * * @return bool|false|mixed|string */ public function get_translated_permalink( $link, $post, $post_element ) { $code = $this->get_permalink_filter_lang( $post ); $force_translate_permalink = apply_filters( 'wpml_force_translated_permalink', false ); if ( ( ! is_admin() || wp_doing_ajax() || wpml_is_ajax() || $force_translate_permalink ) && ( ! $this->sitepress->get_wp_api()->is_a_REST_request() || $force_translate_permalink ) && $this->should_use_permalink_of_post_translation( $post_element ) ) { $link = get_permalink( $this->get_translated_post_id_for_current_language( $post_element ) ); } else { $link = $this->url_converter->get_strategy()->convert_url_string( $link, $code ); } if ( $this->sitepress->get_wp_api()->is_feed() ) { $link = str_replace( '&lang=', '&lang=', $link ); } return $link; } /** * @param string $link * @param int $post_id * @param \WPML_Post_Element $post_element * * @return bool|mixed|string */ public function get_translated_page_link( $link, $post_id, $post_element ) { $code = $this->get_permalink_filter_lang( $post_id ); if ( ! is_admin() && $this->should_use_permalink_of_post_translation( $post_element ) ) { $link = get_page_link( $this->get_translated_post_id_for_current_language( $post_element ) ); } else { $link = $this->url_converter->get_strategy()->convert_url_string( $link, $code ); } if ( $this->sitepress->get_wp_api()->is_feed() ) { $link = str_replace( '&lang=', '&lang=', $link ); } return $link; } public function get_request_language() { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : ''; $server_name = strpos( $request_uri, '/' ) === 0 ? untrailingslashit( Server::getServerName() ) : trailingslashit( Server::getServerName() ); $url_snippet = $server_name . $request_uri; return $this->url_converter->get_language_from_url( $url_snippet ); } private function should_use_permalink_of_post_translation( WPML_Post_Element $post_element ) { return $this->sitepress->get_setting( 'auto_adjust_ids' ) && $post_element->get_language_code() !== $this->sitepress->get_current_language() && $this->get_translated_post_id_for_current_language( $post_element ); } private function is_display_as_translated_mode( WPML_Post_Element $post_element ) { return $post_element->is_display_as_translated() && $post_element->get_language_code() == $this->sitepress->get_default_language() && ! $this->get_translated_post_id_for_current_language( $post_element ) && ! $this->debug_backtrace->is_class_function_in_call_stack( 'SitePress', 'get_ls_languages' ); } private function get_translated_post_id_for_current_language( WPML_Post_Element $post_element ) { $post_id = $post_element->get_element_id(); $current_language = $this->sitepress->get_current_language(); return $this->post_translation->element_id_in( $post_id, $current_language ); } /** * @param string $url * * @return bool */ private function is_front_page( $url ) { return $this->canonicals->get_general_canonical_url( $url ) === home_url() && 'page' === get_option( 'show_on_front' ); } } url-handling/class-wpml-absolute-to-permalinks.php 0000755 00000010514 14720342453 0016332 0 ustar 00 <?php use WPML\Utils\AutoAdjustIds; class WPML_Absolute_To_Permalinks { private $taxonomies_query; private $lang; /** @var SitePress $sitepress */ private $sitepress; /** @var AutoAdjustIds $auto_adjust_ids */ private $auto_adjust_ids; public function __construct( SitePress $sitepress, AutoAdjustIds $auto_adjust_ids = null ) { $this->sitepress = $sitepress; $this->auto_adjust_ids = $auto_adjust_ids ?: new AutoAdjustIds( $sitepress ); } public function convert_text( $text ) { $this->lang = $this->sitepress->get_current_language(); $active_langs_reg_ex = implode( '|', array_keys( $this->sitepress->get_active_languages() ) ); if ( ! $this->taxonomies_query ) { $this->taxonomies_query = new WPML_WP_Taxonomy_Query( $this->sitepress->get_wp_api() ); } $home = rtrim( $this->sitepress->get_wp_api()->get_option( 'home' ), '/' ); $parts = parse_url( $home ); $abshome = $parts['scheme'] . '://' . $parts['host']; $path = isset( $parts['path'] ) ? ltrim( $parts['path'], '/' ) : ''; $tx_qvs = join( '|', $this->taxonomies_query->get_query_vars() ); $reg_ex = '@<a([^>]+)?href="((' . $abshome . ')?/' . $path . '/?(' . $active_langs_reg_ex . ')?\?(p|page_id|cat_ID|' . $tx_qvs . ')=([0-9a-z-]+))(#?[^"]*)"([^>]+)?>@i'; $text = preg_replace_callback( $reg_ex, [ $this, 'show_permalinks_cb' ], $text ); return $text; } function show_permalinks_cb( $matches ) { $parts = $this->get_found_parts( $matches ); $url = $this->auto_adjust_ids->runWith( function() use ( $parts ) { return $this->get_url( $parts ); } ); if ( $this->sitepress->get_wp_api()->is_wp_error( $url ) || empty( $url ) ) { return $parts->whole; } $fragment = $this->get_fragment( $url, $parts ); if ( 'widget_text' == $this->sitepress->get_wp_api()->current_filter() ) { $url = $this->sitepress->convert_url( $url ); } return '<a' . $parts->pre_href . 'href="' . $url . $fragment . '"' . $parts->trail . '>'; } private function get_found_parts( $matches ) { return (object) array( 'whole' => $matches[0], 'pre_href' => $matches[1], 'content_type' => $matches[5], 'id' => $matches[6], 'fragment' => $matches[7], 'trail' => isset( $matches[8] ) ? $matches[8] : '', ); } private function get_url( $parts ) { $tax = $this->taxonomies_query->find( $parts->content_type ); if ( $parts->content_type == 'cat_ID' ) { $url = $this->sitepress->get_wp_api()->get_category_link( $parts->id ); } elseif ( $tax ) { $url = $this->sitepress->get_wp_api()->get_term_link( $parts->id, $tax ); } else { $url = $this->sitepress->get_wp_api()->get_permalink( $parts->id ); } return $url; } private function get_fragment( $url, $parts ) { $fragment = $parts->fragment; $fragment = $this->remove_query_in_wrong_lang( $fragment ); if ( is_string( $fragment ) && $fragment != '' ) { $fragment = str_replace( '&', '&', $fragment ); $fragment = str_replace( '&', '&', $fragment ); if ( $fragment[0] == '&' ) { if ( strpos( $fragment, '?' ) === false && strpos( $url, '?' ) === false ) { $fragment[0] = '?'; } } if ( strpos( $url, '?' ) ) { $fragment = $this->check_for_duplicate_lang_query( $fragment, $url ); } } return $fragment; } private function remove_query_in_wrong_lang( $fragment ) { if ( is_string( $fragment ) && $fragment != '' ) { $fragment = str_replace( '&', '&', $fragment ); $fragment = str_replace( '&', '&', $fragment ); $start = $fragment[0]; parse_str( substr( $fragment, 1 ), $fragment_query ); if ( isset( $fragment_query['lang'] ) ) { if ( $fragment_query['lang'] != $this->lang ) { unset( $fragment_query['lang'] ); $fragment = build_query( $fragment_query ); if ( strlen( $fragment ) ) { $fragment = $start . $fragment; } } } } return $fragment; } private function check_for_duplicate_lang_query( $fragment, $url ) { $url_parts = explode( '?', $url ); parse_str( $url_parts[1], $url_query ); if ( isset( $url_query['lang'] ) ) { parse_str( substr( $fragment, 1 ), $fragment_query ); if ( isset( $fragment_query['lang'] ) ) { unset( $fragment_query['lang'] ); $fragment = build_query( $fragment_query ); if ( strlen( $fragment ) ) { $fragment = '&' . $fragment; } } } return $fragment; } } url-handling/resolver/wpml-resolve-absolute-url-cached.php 0000755 00000001603 14720342453 0017766 0 ustar 00 <?php class WPML_Resolve_Absolute_Url_Cached implements IWPML_Resolve_Object_Url { /** @var WPML_Absolute_Url_Persisted $url_persisted */ private $url_persisted; /** @var WPML_Resolve_Absolute_Url $resolve_url */ private $resolve_url; public function __construct( WPML_Absolute_Url_Persisted $url_persisted, WPML_Resolve_Absolute_Url $resolve_url ) { $this->url_persisted = $url_persisted; $this->resolve_url = $resolve_url; } /** * @param string $url * @param string $lang * * @return false|string Will return `false` if the URL could not be resolved */ public function resolve_object_url( $url, $lang ) { $resolved_url = $this->url_persisted->get( $url, $lang ); if ( null === $resolved_url ) { $resolved_url = $this->resolve_url->resolve_object_url( $url, $lang ); $this->url_persisted->set( $url, $lang, $resolved_url ); } return $resolved_url; } } url-handling/resolver/wpml-resolve-absolute-url.php 0000755 00000002111 14720342453 0016554 0 ustar 00 <?php class WPML_Resolve_Absolute_Url implements IWPML_Resolve_Object_Url { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Translate_Link_Targets */ private $translate_link_targets; /** @var bool */ private $lock; public function __construct( SitePress $sitepress, WPML_Translate_Link_Targets $translate_link_targets ) { $this->sitepress = $sitepress; $this->translate_link_targets = $translate_link_targets; } /** * @param string $url * @param string $lang * * @return string|false */ public function resolve_object_url( $url, $lang ) { if ( $this->lock ) { return false; } $this->lock = true; $current_lang = $this->sitepress->get_current_language(); $this->sitepress->switch_lang( $lang ); try { $new_url = $this->translate_link_targets->convert_url( $url ); if ( trailingslashit( $new_url ) === trailingslashit( $url ) ) { $new_url = false; } } catch ( Exception $e ) { $new_url = false; } $this->sitepress->switch_lang( $current_lang ); $this->lock = false; return $new_url; } } url-handling/resolver/filters/wpml-absolute-url-persisted-filters.php 0000755 00000001464 14720342453 0022227 0 ustar 00 <?php class WPML_Absolute_Url_Persisted_Filters implements IWPML_Action { /** @var WPML_Absolute_Url_Persisted $url_persisted */ private $url_persisted; public function __construct( WPML_Absolute_Url_Persisted $url_persisted ) { $this->url_persisted = $url_persisted; } public function add_hooks() { add_filter( 'wp_insert_post_data', [ $this, 'reset' ] ); add_action( 'delete_post', [ $this, 'action_reset' ] ); add_filter( 'wp_update_term_data', [ $this, 'reset' ] ); add_action( 'pre_delete_term', [ $this, 'action_reset' ] ); add_filter( 'rewrite_rules_array', [ $this, 'reset' ] ); } /** * @param mixed $data * * @return array */ public function reset( $data = null ) { $this->url_persisted->reset(); return $data; } public function action_reset() { $this->reset(); } } url-handling/resolver/filters/wpml-absolute-url-persisted-filters-factory.php 0000755 00000000536 14720342453 0023673 0 ustar 00 <?php class WPML_Absolute_Url_Persisted_Filters_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { public function create() { $url_persisted = WPML_Absolute_Url_Persisted::get_instance(); if ( $url_persisted->has_urls() ) { return new WPML_Absolute_Url_Persisted_Filters( $url_persisted ); } return null; } } url-handling/resolver/wpml-absolute-url-persisted.php 0000755 00000003752 14720342453 0017113 0 ustar 00 <?php class WPML_Absolute_Url_Persisted { const OPTION_KEY = 'wpml_resolved_url_persist'; private static $instance; /** * @var array */ private $urls; /** * @return WPML_Absolute_Url_Persisted */ public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } protected function __construct() {} private function __clone() {} /** * @throws Exception */ public function __wakeup() { throw new Exception( 'Cannot unserialize singleton' ); } /** * Returns urls array. * * @return array Array with urls. */ private function get_urls() { if ( null === $this->urls ) { $this->urls = get_option( self::OPTION_KEY, array() ); if ( ! is_array( $this->urls ) ) { $this->urls = array(); } } return $this->urls; } /** @return bool */ public function has_urls() { return (bool) $this->get_urls(); } /** * @param string $original_url * @param string $lang * @param string|false $converted_url A `false` value means that the URL could not be resolved */ public function set( $original_url, $lang, $converted_url ) { $this->get_urls(); $this->urls[ $original_url ][ $lang ] = $converted_url; $this->persist_in_shutdown(); } /** * @param string $original_url * @param string $lang * * @return string|false|null If the URL has already been processed but could not be resolved, it will return `false` */ public function get( $original_url, $lang ) { $this->get_urls(); if ( isset( $this->urls[ $original_url ][ $lang ] ) ) { return $this->urls[ $original_url ][ $lang ]; } return null; } public function reset() { $this->urls = []; $this->persist(); $this->urls = null; } public function persist() { update_option( self::OPTION_KEY, $this->urls ); } private function persist_in_shutdown() { if ( ! has_action( 'shutdown', array( $this, 'persist' ) ) ) { add_action( 'shutdown', array( $this, 'persist' ) ); } } } url-handling/resolver/iwpml-resolve-object-url.php 0000755 00000000377 14720342453 0016371 0 ustar 00 <?php interface IWPML_Resolve_Object_Url { /** * @param string $url * @param string $lang * * @return string|false Will return the resolved URL or `false` if it could not be resolved. */ public function resolve_object_url( $url, $lang ); } url-handling/resolver/class-wpml-resolve-object-url-helper-factory.php 0000755 00000002143 14720342453 0022236 0 ustar 00 <?php class WPML_Resolve_Object_Url_Helper_Factory { const CURRENT_URL_RESOLVER = 'current'; const ABSOLUTE_URL_RESOLVER = 'absolute'; /** * @return IWPML_Resolve_Object_Url */ public function create( $type = self::CURRENT_URL_RESOLVER ) { global $sitepress, $wp_query, $wpml_term_translations, $wpml_post_translations; if ( self::CURRENT_URL_RESOLVER === $type ) { return new WPML_Resolve_Object_Url_Helper( $sitepress, $wp_query, $wpml_term_translations, $wpml_post_translations ); } if ( self::ABSOLUTE_URL_RESOLVER === $type ) { $absolute_links = new AbsoluteLinks(); $absolute_to_permalinks = new WPML_Absolute_To_Permalinks( $sitepress ); $translate_links_target = new WPML_Translate_Link_Targets( $absolute_links, $absolute_to_permalinks ); $resolve_url = new WPML_Resolve_Absolute_Url( $sitepress, $translate_links_target ); $url_persisted = WPML_Absolute_Url_Persisted::get_instance(); return new WPML_Resolve_Absolute_Url_Cached( $url_persisted, $resolve_url ); } throw new InvalidArgumentException( 'Unknown Resolve_Object_Url type: ' . $type ); } } url-handling/resolver/class-wpml-resolve-object-url-helper.php 0000755 00000006430 14720342453 0020574 0 ustar 00 <?php class WPML_Resolve_Object_Url_Helper implements IWPML_Resolve_Object_Url { const CACHE_GROUP = 'resolve_object_url'; /** * @var bool */ protected $lock = false; /** * @var SitePress */ private $sitepress; /** * @var WP_Query */ private $wp_query; /** * @var WPML_Term_Translation */ private $wpml_term_translations; /** * @var WPML_Post_Translation */ private $wpml_post_translations; /** * @param SitePress $sitepress * @param WP_Query $wp_query * @param WPML_Term_Translation $wpml_term_translations * @param WPML_Post_Translation $wpml_post_translations */ public function __construct( SitePress &$sitepress = null, WP_Query &$wp_query = null, WPML_Term_Translation $wpml_term_translations = null, WPML_Post_Translation $wpml_post_translations = null ) { $this->sitepress = &$sitepress; $this->wp_query = &$wp_query; $this->wpml_term_translations = $wpml_term_translations; $this->wpml_post_translations = $wpml_post_translations; } /** * Try to parse the URL to find a related post or term * * @param string $url * @param string $lang_code * * @return string|bool */ public function resolve_object_url( $url, $lang_code ) { if ( $this->lock ) { return false; } $this->lock = true; $new_url = false; $translations = $this->cached_retrieve_translations( $url ); if ( $translations && isset( $translations[ $lang_code ]->element_type ) ) { $current_lang = $this->sitepress->get_current_language(); $this->sitepress->switch_lang( $lang_code ); $element = explode( '_', $translations[ $lang_code ]->element_type ); $type = array_shift( $element ); $subtype = implode( '_', $element ); switch ( $type ) { case 'post': $new_url = get_permalink( $translations[ $lang_code ]->element_id ); break; case 'tax': /** @var WP_Term|false $term */ $term = get_term_by( 'term_taxonomy_id', $translations[ $lang_code ]->element_id, $subtype ); $new_url = $term ? get_term_link( $term ) : false; break; } $this->sitepress->switch_lang( $current_lang ); } $this->lock = false; return $new_url; } /** * @param string $url * * @return array<string,\stdClass> */ private function cached_retrieve_translations( $url ) { $cache_key = md5( $url ); $cache_found = false; $cache = new WPML_WP_Cache( self::CACHE_GROUP ); $translations = $cache->get( $cache_key, $cache_found ); if ( ! $cache_found && is_object( $this->wp_query ) ) { $translations = $this->retrieve_translations(); $cache->set( $cache_key, $translations ); } return $translations; } /** * @return array<string,\stdClass> */ private function retrieve_translations() { $this->sitepress->set_wp_query(); // Make sure $sitepress->wp_query is set $_wp_query_back = clone $this->wp_query; $wp_query = $this->sitepress->get_wp_query(); $wp_query = is_object( $wp_query ) ? clone $wp_query : clone $_wp_query_back; $languages_helper = new WPML_Languages( $this->wpml_term_translations, $this->sitepress, $this->wpml_post_translations ); list( $translations) = $languages_helper->get_ls_translations( $wp_query, $_wp_query_back, $this->sitepress->get_wp_query() ); return $translations; } } url-handling/converter/class-wpml-url-converter-cpt.php 0000755 00000002655 14720342453 0017342 0 ustar 00 <?php class WPML_URL_Converter_CPT { /** * @var WPML_Slash_Management */ private $slash_helper; /** * @param WPML_Slash_Management $slash_helper */ public function __construct( WPML_Slash_Management $slash_helper = null ) { if ( ! $slash_helper ) { $slash_helper = new WPML_Slash_Management(); } $this->slash_helper = $slash_helper; } /** * Adjusts the CPT archive slug for possible slug translations from ST. * * @param string $link * @param string $post_type * @param null|string $language_code * * @return string */ public function adjust_cpt_slug_in_url( $link, $post_type, $language_code = null ) { $post_type_object = get_post_type_object( $post_type ); if ( isset( $post_type_object->rewrite ) ) { $slug = trim( $post_type_object->rewrite['slug'], '/' ); } else { $slug = $post_type_object->name; } $translated_slug = apply_filters( 'wpml_get_translated_slug', $slug, $post_type, $language_code ); if ( is_string( $translated_slug ) ) { $link_parts = explode( '?', $link, 2 ); $pattern = '#\/' . preg_quote( $slug, '#' ) . '\/#'; $link_new = trailingslashit( preg_replace( $pattern, '/' . $translated_slug . '/', trailingslashit( $link_parts[0] ), 1 ) ); $link = $this->slash_helper->match_trailing_slash_to_reference( $link_new, $link_parts[0] ); $link = isset( $link_parts[1] ) ? $link . '?' . $link_parts[1] : $link; } return $link; } } url-handling/converter/class-wpml-url-cached-converter.php 0000755 00000002461 14720342453 0017756 0 ustar 00 <?php class WPML_URL_Cached_Converter extends WPML_URL_Converter { /** @var string[] $cache */ private $cache; /** * @param string $url * @param string|bool $lang_code * * @return string */ public function convert_url( $url, $lang_code = false ) { global $sitepress; if ( ! $lang_code ) { $lang_code = $sitepress->get_current_language(); } $negotiation_type = $sitepress->get_setting( 'language_negotiation_type' ); $skip_convert_url_string = $this->get_strategy()->skip_convert_url_string( $url, $lang_code ); $cache_key_args = array( $url, $lang_code, $negotiation_type, $skip_convert_url_string ); $cache_key = md5( (string) wp_json_encode( $cache_key_args ) ); $cache_group = 'convert_url'; $cache_found = false; $cache = new WPML_WP_Cache( $cache_group ); $new_url = $cache->get( $cache_key, $cache_found ); if ( ! $cache_found ) { $new_url = parent::convert_url( $url, $lang_code ); $cache->set( $cache_key, $new_url ); } return $new_url; } /** * @param string $url * * @return string */ public function get_language_from_url( $url ) { if ( isset( $this->cache[ $url ] ) ) { return $this->cache[ $url ]; } $lang = parent::get_language_from_url( $url ); $this->cache[ $url ] = $lang; return $lang; } } url-handling/converter/strategy/class-wpml-url-converter-abstract-strategy.php 0000755 00000005636 14720342453 0024063 0 ustar 00 <?php use WPML\FP\Str; abstract class WPML_URL_Converter_Abstract_Strategy implements IWPML_URL_Converter_Strategy { protected $absolute_home; protected $default_language; protected $active_languages; protected $cache; /** * @var WPML_URL_Converter_Url_Helper */ protected $url_helper; /** * @var WPML_URL_Converter_Lang_Param_Helper */ protected $lang_param; /** * @var WPML_Slash_Management */ protected $slash_helper; /** * @var WP_Rewrite */ protected $wp_rewrite; /** * @param string $default_language * @param array<string> $active_languages * @param WP_Rewrite|null $wp_rewrite * @param WPML_Slash_Management|null $splash_helper */ public function __construct( $default_language, $active_languages, $wp_rewrite = null, $splash_helper = null ) { $this->default_language = $default_language; $this->active_languages = $active_languages; $this->lang_param = new WPML_URL_Converter_Lang_Param_Helper( $active_languages ); $this->slash_helper = $splash_helper ?: new WPML_Slash_Management(); if ( ! $wp_rewrite ) { global $wp_rewrite; } $this->wp_rewrite = $wp_rewrite; } public function validate_language( $language, $url ) { if ( Str::includes( 'wp-login.php', $_SERVER['REQUEST_URI'] ) ) { return $language; } return in_array( $language, $this->active_languages, true ) || 'all' === $language && $this->get_url_helper()->is_url_admin( $url ) ? $language : $this->get_default_language(); } /** * @param WPML_URL_Converter_Url_Helper $url_helper */ public function set_url_helper( WPML_URL_Converter_Url_Helper $url_helper ) { $this->url_helper = $url_helper; } /** * @return WPML_URL_Converter_Url_Helper */ public function get_url_helper() { if ( ! $this->url_helper ) { $this->url_helper = new WPML_URL_Converter_Url_Helper(); } return $this->url_helper; } /** * @param WPML_URL_Converter_Lang_Param_Helper $lang_param */ public function set_lang_param( WPML_URL_Converter_Lang_Param_Helper $lang_param ) { $this->lang_param = $lang_param; } /** * @param WPML_Slash_Management $slash_helper */ public function set_slash_helper( WPML_Slash_Management $slash_helper ) { $this->slash_helper = $slash_helper; } private function get_default_language() { if ( $this->default_language ) { return $this->default_language; } else { return icl_get_setting( 'default_language' ); } } public function fix_trailingslashit( $source_url ) { return $source_url; } public function skip_convert_url_string( $source_url, $lang_code ) { /** * Allows plugins to skip url conversion. * * @since 4.3 * * @param bool $skip * @param string $source_url * @param string $lang_code * @return bool */ return apply_filters( 'wpml_skip_convert_url_string', false, $source_url, $lang_code ); } public function use_wp_login_url_converter() { return false; } } url-handling/converter/strategy/class-wpml-url-converter-subdir-strategy.php 0000755 00000016621 14720342453 0023544 0 ustar 00 <?php use WPML\FP\Str; class WPML_URL_Converter_Subdir_Strategy extends WPML_URL_Converter_Abstract_Strategy { /** @var bool */ private $use_directory_for_default_lang; /** @var array copy of $sitepress->get_settings( 'urls' ) */ private $urls_settings; /** @var string|bool */ private $root_url; /** @var array map of wpml codes to custom codes*/ private $language_codes_map; private $language_codes_reverse_map; /** @var bool */ private $is_rest_request; /** * @param bool $use_directory_for_default_lang * @param string $default_language * @param array $active_languages * @param array $urls_settings */ public function __construct( $use_directory_for_default_lang, $default_language, $active_languages, $urls_settings ) { parent::__construct( $default_language, $active_languages ); $this->use_directory_for_default_lang = (bool) $use_directory_for_default_lang; $this->urls_settings = $urls_settings; $this->language_codes_map = array_combine( $active_languages, $active_languages ); $this->language_codes_map = apply_filters( 'wpml_language_codes_map', $this->language_codes_map ); $this->language_codes_reverse_map = array_flip( $this->language_codes_map ); } public function add_hooks() { add_filter( 'rest_url', [ $this, 'convertRestUrl' ] ); } public function remove_hooks() { remove_filter( 'rest_url', [ $this, 'convertRestUrl' ] ); } /** * @param string $url * * @return string */ public function convertRestUrl( $url ) { /** @var SitePress */ global $sitepress; $matchTrailingSlash = $url[ strlen( $url ) - 1 ] === '/' ? 'trailingslashit' : 'untrailingslashit'; return $matchTrailingSlash( $this->convert_url_string( $url, $sitepress->get_current_language() ) ); } public function get_lang_from_url_string( $url ) { $url_path = $this->get_url_path( wpml_strip_subdir_from_url( $url ) ); $lang = $this->extract_lang_from_url_path( $url_path ); if ( $lang && in_array( $lang, $this->active_languages, true ) ) { return $lang; } return $this->use_directory_for_default_lang ? null : $this->default_language; } public function validate_language( $language, $url ) { if ( ! ( null === $language && $this->use_directory_for_default_lang && ! $this->get_url_helper()->is_url_admin( $url ) ) ) { $language = parent::validate_language( $language, $url ); } return $language; } public function convert_url_string( $source_url, $code ) { if ( $this->is_root_url( $source_url ) || $this->skip_convert_url_string( $source_url, $code ) ) { return $source_url; } // We have no redirect rule for '/all/wp-json' ( only for '/lang/wp-json' ) so lets use the default one in all case. if ( 'all' === $code && in_array( 'wp-json', explode( '/', $source_url ) ) ) { return $source_url; } $source_url = $this->filter_source_url( $source_url ); $absolute_home_url = trailingslashit( preg_replace( '#^(http|https)://#', '', $this->get_url_helper()->get_abs_home() ) ); $absolute_home_url = strpos( $source_url, $absolute_home_url ) === false ? trailingslashit( get_option( 'home' ) ) : $absolute_home_url; $current_language = $this->get_lang_from_url_string( $source_url ); $code = $this->get_language_of_current_dir( $code, '' ); $current_language = $this->get_language_of_current_dir( $current_language, '' ); $code = isset( $this->language_codes_map[ $code ] ) ? $this->language_codes_map[ $code ] : $code; $current_language = isset( $this->language_codes_map[ $current_language ] ) ? $this->language_codes_map[ $current_language ] : $current_language; $source_url = str_replace( [ trailingslashit( $absolute_home_url . $current_language ), '/' . $code . '//', ], [ $code ? ( $absolute_home_url . $code . '/' ) : trailingslashit( $absolute_home_url ), '/' . $code . '/', ], $source_url ); return $this->slash_helper->maybe_user_trailingslashit( $source_url ); } public function convert_admin_url_string( $source_url, $lang ) { return $source_url; // Admin strings should not be converted with language in directories } /** * @param string $url * @param string $language * * @return string */ public function get_home_url_relative( $url, $language ) { $language = $this->get_language_of_current_dir( $language, '' ); $language = isset( $this->language_codes_map[ $language ] ) ? $this->language_codes_map[ $language ] : $language; if ( $language ) { $parts = parse_url( get_option( 'home' ) ); $path = isset( $parts['path'] ) ? $parts['path'] : ''; $url = preg_replace( '@^' . $path . '@', '', $url ); return rtrim( $path, '/' ) . '/' . $language . $url; } else { return $url; } } public function use_wp_login_url_converter() { return true; } /** * Will return true if root URL or child of root URL * * @param string $url * * @return bool */ private function is_root_url( $url ) { $result = false; if ( isset( $this->urls_settings['root_page'], $this->urls_settings['show_on_root'] ) && 'page' === $this->urls_settings['show_on_root'] && ! empty( $this->urls_settings['directory_for_default_language'] ) ) { $root_url = $this->get_root_url(); if ( $root_url ) { $result = strpos( trailingslashit( $url ), $root_url ) === 0; } } return $result; } /** * @return string|false */ private function get_root_url() { if ( null === $this->root_url ) { $root_post = get_post( $this->urls_settings['root_page'] ); if ( $root_post ) { $this->root_url = trailingslashit( $this->get_url_helper()->get_abs_home() ) . $root_post->post_name; $this->root_url = trailingslashit( $this->root_url ); } else { $this->root_url = false; } } return $this->root_url; } /** * @param string $source_url * * @return string */ private function filter_source_url( $source_url ) { if ( false === strpos( $source_url, '?' ) ) { $source_url = trailingslashit( $source_url ); } elseif ( false !== strpos( $source_url, '?' ) && false === strpos( $source_url, '/?' ) ) { $source_url = str_replace( '?', '/?', $source_url ); } return $source_url; } /** * @param string $url * * @return string */ private function get_url_path( $url ) { if ( strpos( $url, 'http://' ) === 0 || strpos( $url, 'https://' ) === 0 ) { $url_path = wpml_parse_url( $url, PHP_URL_PATH ); } else { $pathparts = array_filter( explode( '/', $url ) ); if ( count( $pathparts ) > 1 ) { unset( $pathparts[0] ); $url_path = implode( '/', $pathparts ); } else { $url_path = $url; } } return $url_path; } /** * @param string $url_path * * @return string */ private function extract_lang_from_url_path( $url_path ) { $fragments = ! empty( $url_path ) ? array_filter( explode( '/', $url_path ) ) : ['']; $lang = array_shift( $fragments ); $lang_get_parts = ! empty( $lang ) ? explode( '?', $lang ) : ['']; $lang = $lang_get_parts[0]; return isset( $this->language_codes_reverse_map[ $lang ] ) ? $this->language_codes_reverse_map[ $lang ] : $lang; } /** * @param string $language_code * @param null|string $value_if_default_language * * @return string|null */ private function get_language_of_current_dir( $language_code, $value_if_default_language = null ) { if ( ! $this->use_directory_for_default_lang && $language_code === $this->default_language ) { return $value_if_default_language; } return $language_code; } } url-handling/converter/strategy/class-wpml-url-converter-parameter-strategy.php 0000755 00000005105 14720342453 0024227 0 ustar 00 <?php class WPML_URL_Converter_Parameter_Strategy extends WPML_URL_Converter_Abstract_Strategy { public function add_hooks() { } public function remove_hooks() { } public function get_lang_from_url_string( $url ) { return $this->lang_param->lang_by_param( $url, false ); } public function convert_url_string( $source_url, $lang_code ) { if ( $this->skip_convert_url_string( $source_url, $lang_code ) ) { return $source_url; } if ( ! $lang_code || $lang_code === $this->default_language ) { $lang_code = ''; } $url_parts = wpml_parse_url( $source_url ); if ( ! is_array( $url_parts ) ) { $url_parts = []; } // This is required for logout url, the parameter comes urlencoded in the URI generated by WordPress. // If we don't decode it first, it will be misinterpreted by browsers when combined with regular parameters. if ( isset( $url_parts['query'] ) ) { $url_parts['query'] = str_replace( '&_wpnonce', '&_wpnonce', $url_parts['query'] ); } $query_args = $this->get_query_args( $url_parts ); if ( $lang_code ) { $query_args['lang'] = $lang_code; } else { unset( $query_args['lang'] ); } $url_parts['query'] = http_build_query( $query_args ); $converted_url = http_build_url( $url_parts ); return $this->slash_helper->maybe_user_trailingslashit( $converted_url ); } public function convert_admin_url_string( $source_url, $lang ) { return $this->convert_url_string( $source_url, $lang ); } /** * @param array $url_parts * * @return array */ private function get_query_args( array $url_parts ) { $query = isset( $url_parts['query'] ) ? $url_parts['query'] : ''; $query = str_replace( '?', '&', $query ); parse_str( $query, $query_args ); return $query_args; } /** * @param string $url * @param string $language * * @return string */ public function get_home_url_relative( $url, $language ) { if ( $language === $this->default_language ) { $language = ''; } if ( $language ) { return add_query_arg( 'lang', $language, $url ); } else { return $url; } } /** * @param string $source_url * * @return string */ public function fix_trailingslashit( $source_url ) { $query = wpml_parse_url( $source_url, PHP_URL_QUERY ); if ( ! empty( $query ) ) { $source_url = str_replace( '?' . $query, '', $source_url ); } $source_url = $this->slash_helper->maybe_user_trailingslashit( $source_url ); if ( ! empty( $query ) ) { $source_url .= '?' . untrailingslashit( $query ); } return $source_url; } public function use_wp_login_url_converter() { return true; } } url-handling/converter/strategy/interface-iwpml-url-converter-strategy.php 0000755 00000001072 14720342453 0023254 0 ustar 00 <?php interface IWPML_URL_Converter_Strategy { public function add_hooks(); public function remove_hooks(); public function convert_url_string( $source_url, $lang ); public function convert_admin_url_string( $source_url, $lang ); public function validate_language( $language, $url ); public function get_lang_from_url_string( $url ); public function get_home_url_relative( $url, $lang ); public function fix_trailingslashit( $source_url ); public function skip_convert_url_string( $url, $lang_code ); public function use_wp_login_url_converter(); } url-handling/converter/strategy/class-wpml-url-converter-domain-strategy.php 0000755 00000007304 14720342453 0023521 0 ustar 00 <?php use \WPML\SuperGlobals\Server; class WPML_URL_Converter_Domain_Strategy extends WPML_URL_Converter_Abstract_Strategy { /** @var string[] $domains */ private $domains = array(); /** * @param array $domains * @param string $default_language * @param array $active_languages */ public function __construct( $domains, $default_language, $active_languages ) { parent::__construct( $default_language, $active_languages ); $domains = array_map( 'untrailingslashit', $domains ); $this->domains = array_map( array( $this, 'strip_protocol' ), $domains ); if ( isset( $this->domains[ $default_language ] ) ) { unset( $this->domains[ $default_language ] ); } } public function add_hooks() { add_filter( 'rest_url', [ $this, 'convertRestUrlToCurrentDomain' ], 10, 4 ); } public function remove_hooks() { remove_filter( 'rest_url', [ $this, 'convertRestUrlToCurrentDomain' ] ); } /** * Filter REST url to avoid CORS error in Gutenberg. * https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-7022 * * @param string $url REST URL. * @param string $path REST route. * @param int $blog_id Blog ID. * @param string $scheme Sanitization scheme. * * @return string */ public function convertRestUrlToCurrentDomain( $url, $path, $blog_id, $scheme ) { $url_parts = $this->parse_domain_and_subdir( $url ); $url_parts['host'] = Server::getServerName(); $url = http_build_url( $url_parts ); return $url; } public function get_lang_from_url_string( $url ) { $url = $this->strip_protocol( $url ); if ( strpos( $url, '?' ) ) { $parts = explode( '?', $url ); $url = $parts[0]; } foreach ( $this->domains as $code => $domain ) { if ( $domain && strpos( trailingslashit( $url ), trailingslashit( $domain ) ) === 0 ) { return $code; } } return null; } public function convert_url_string( $source_url, $lang ) { $original_source_url = untrailingslashit( $source_url ); if ( is_admin() && $this->get_url_helper()->is_url_admin( $original_source_url ) ) { return $original_source_url; } return $this->convert_url( $source_url, $lang ); } public function convert_admin_url_string( $source_url, $lang ) { return $this->convert_url( $source_url, $lang ); } private function convert_url( $source_url, $lang ) { if ( $this->skip_convert_url_string( $source_url, $lang ) ) { return $source_url; } $base_url = isset( $this->domains[ $lang ] ) ? $this->domains[ $lang ] : $this->get_url_helper()->get_abs_home(); $base_url_parts = $this->parse_domain_and_subdir( $base_url ); $url_parts = $this->parse_domain_and_subdir( $source_url ); if ( isset( $base_url_parts['host'] ) ) { $url_parts['host'] = $base_url_parts['host']; } $converted_url = http_build_url( $url_parts ); return $this->slash_helper->maybe_user_trailingslashit( $converted_url ); } /** * @param string $base_url * * @return array */ private function parse_domain_and_subdir( $base_url ) { $url_parts = wpml_parse_url( $base_url ); return is_array( $url_parts ) ? $this->slash_helper->parse_missing_host_from_path( $url_parts ) : []; } /** * @param string $url * @param string $language * * @return string */ public function get_home_url_relative( $url, $language ) { return $url; } /** * @param string $url * * @return string */ private function strip_protocol( $url ) { $url_parts = wpml_parse_url( $url ); if ( is_array( $url_parts ) ) { $url_parts = $this->slash_helper->parse_missing_host_from_path( $url_parts ); unset( $url_parts['scheme'] ); return http_build_url( $url_parts ); } else { return preg_replace( '/^https?:\/\//', '', $url ); } } } url-handling/converter/helper/class-wpml-url-converter-url-helper.php 0000755 00000004626 14720342453 0022112 0 ustar 00 <?php class WPML_URL_Converter_Url_Helper { /** * @var wpdb */ private $wpdb; /** * @var WPML_Include_Url */ private $wpml_include_url_filter; /** * @var string */ private $absolute_home; /** * * @param wpdb $wpdb * @param WPML_Include_Url $wpml_include_url_filter */ public function __construct( wpdb $wpdb = null, WPML_Include_Url $wpml_include_url_filter = null ) { if ( ! $wpdb ) { global $wpdb; } if ( ! $wpml_include_url_filter ) { global $wpml_include_url_filter; } $this->wpdb = $wpdb; $this->wpml_include_url_filter = $wpml_include_url_filter; } /** * Returns the unfiltered home_url by directly retrieving it from wp_options. * * @return string */ public function get_abs_home() { if ( ! $this->absolute_home ) { $is_multisite = is_multisite(); if ( ! $is_multisite && defined( 'WP_HOME' ) ) { $this->absolute_home = WP_HOME; } elseif ( $is_multisite && ! is_main_site() ) { $protocol = preg_match( '/^(https)/', get_option( 'home' ) ) === 1 ? 'https://' : 'http://'; $sql = " SELECT CONCAT(b.domain, b.path) FROM {$this->wpdb->blogs} b WHERE blog_id = {$this->wpdb->blogid} LIMIT 1 "; $this->absolute_home = $protocol . $this->wpdb->get_var( $sql ); } else { $this->absolute_home = $this->get_unfiltered_home_option(); } } return apply_filters( 'wpml_url_converter_get_abs_home', $this->absolute_home ); } /** * Checks if a $url points to a WP Admin screen. * * @param string $url * @return bool True if the input $url points to an admin screen. */ public function is_url_admin( $url ) { $url_query_parts = wpml_parse_url( strpos( $url, 'http' ) === false ? 'http://' . $url : $url ); return isset( $url_query_parts['path'] ) && strpos( wpml_strip_subdir_from_url( $url_query_parts['path'] ), '/wp-admin' ) === 0; } /** * Returns the unfiltered home option from the database. * * @uses \WPML_Include_Url::get_unfiltered_home in case the $wpml_include_url_filter global is loaded * * @return string */ public function get_unfiltered_home_option() { if ( $this->wpml_include_url_filter ) { return $this->wpml_include_url_filter->get_unfiltered_home(); } else { $sql = " SELECT option_value FROM {$this->wpdb->options} WHERE option_name = 'home' LIMIT 1 "; return $this->wpdb->get_var( $sql ); } } } url-handling/converter/helper/class-wpml-url-converter-lang-param-helper.php 0000755 00000006213 14720342453 0023321 0 ustar 00 <?php use WPML\FP\Str; class WPML_URL_Converter_Lang_Param_Helper { /** * @var array */ private static $cache = array(); /** * @var array */ private $active_languages; /** * @param array $active_languages */ public function __construct( array $active_languages ) { $this->active_languages = $active_languages; } /** * * @param string $url * @param bool $only_admin If set to true only language parameters on Admin Screen URLs will be recognized. The * function will return null for non-Admin Screens. * * @return null|string Language code */ public function lang_by_param( $url, $only_admin = true ) { if ( isset( self::$cache[ $url ] ) ) { return self::$cache[ $url ]; } $lang = $this->extract_lang_param_from_url( $url, $only_admin ); self::$cache[ $url ] = $lang; return $lang; } /** * @param string $url * @param bool $only_admin * * @return string|null */ private function extract_lang_param_from_url( $url, $only_admin ) { $url = wpml_strip_subdir_from_url( $url ); $url_query_parts = wpml_parse_url( $url ); $url_query = $this->has_query_part( $only_admin, $url_query_parts ) ? untrailingslashit( $url_query_parts['query'] ) : null; $isLoginPageUrl = Str::includes( 'wp-login.php', $_SERVER['REQUEST_URI'] ); $isLoginPage = function( $vars ) use ( $isLoginPageUrl ) { return $isLoginPageUrl && isset( $vars['wp_lang'] ) && is_string( $vars['wp_lang'] ); }; $getWpLang = function( $vars ) { $wp_lang = explode( '_', $vars['wp_lang'] ); return ( count( $wp_lang ) > 0 ) ? strtolower( $wp_lang[0] ) : null; }; if ( null !== $url_query ) { parse_str( $url_query, $vars ); if ( $this->can_retrieve_lang_from_query( $only_admin, $vars ) ) { return $vars['lang']; } else if ( $isLoginPage( $vars ) ) { // Handling case when Language URL format is 'Language name added as a parameter'. return $getWpLang( $vars ); } } // Handling case when Language URL format is 'Different languages in directories'. if ( is_array( $url_query_parts ) && isset( $url_query_parts['query'] ) && is_string( $url_query_parts['query'] ) ) { parse_str( $url_query_parts['query'], $vars ); if ( $isLoginPage( $vars ) ) { return $getWpLang( $vars ); } } return null; } /** * @param bool $only_admin * @param array $url_query_parts * * @return bool */ private function has_query_part( $only_admin, $url_query_parts ) { if ( ! isset( $url_query_parts['query'] ) ) { return false; } if ( false === $only_admin ) { return true; } if ( ! isset( $url_query_parts['path'] ) ) { return false; } if ( false === strpos( $url_query_parts['path'], '/wp-admin' ) ) { return false; } return true; } /** * @param bool $only_admin * @param array $vars * * @return bool */ private function can_retrieve_lang_from_query( $only_admin, $vars ) { if ( ! isset( $vars['lang'] ) ) { return false; } if ( $only_admin && 'all' === $vars['lang'] ) { return true; } if ( in_array( $vars['lang'], $this->active_languages, true ) ) { return true; } return false; } } url-handling/converter/class-wpml-url-converter-factory.php 0000755 00000010242 14720342453 0020212 0 ustar 00 <?php class WPML_URL_Converter_Factory { /** * @var array */ private $settings; /** * @var string */ private $default_lang_code; /** * @var array */ private $active_language_codes; /** * @var WPML_Resolve_Object_Url_Helper_Factory */ private $object_url_helper_factory; /** @var WPML_URL_Converter */ private static $previous_url_converter; const SUBDIR = 1; const DOMAIN = 2; /** * @param array $settings * @param string $default_lang_code * @param array $active_language_codes */ public function __construct( $settings, $default_lang_code, $active_language_codes ) { $this->settings = $settings; $this->default_lang_code = $default_lang_code; $this->active_language_codes = $active_language_codes; } public static function remove_previous_hooks() { if ( self::$previous_url_converter ) { self::$previous_url_converter->get_strategy()->remove_hooks(); } } /** * @return WPML_Resolve_Object_Url_Helper_Factory */ public function get_object_url_helper_factory() { if ( ! $this->object_url_helper_factory ) { $this->object_url_helper_factory = new WPML_Resolve_Object_Url_Helper_Factory(); } return $this->object_url_helper_factory; } /** * @param WPML_Resolve_Object_Url_Helper_Factory $factory */ public function set_object_url_helper_factory( WPML_Resolve_Object_Url_Helper_Factory $factory ) { $this->object_url_helper_factory = $factory; } /** * @param int $url_type * * @return WPML_URL_Converter */ public function create( $url_type ) { switch ( $url_type ) { case self::SUBDIR: $wpml_url_converter = $this->create_subdir_converter(); break; case self::DOMAIN: $wpml_url_converter = $this->create_domain_converter(); break; default: $wpml_url_converter = $this->create_parameter_converter(); } $home_url = new WPML_URL_Converter_Url_Helper(); $wpml_url_converter->set_url_helper( $home_url ); $wpml_url_converter->get_strategy()->add_hooks(); self::$previous_url_converter = $wpml_url_converter; return $wpml_url_converter; } /** * @return WPML_URL_Cached_Converter */ private function create_subdir_converter() { $dir_default = false; if ( ! isset( $this->settings['urls'] ) ) { $this->settings['urls'] = array(); } else { if ( isset( $this->settings['urls']['directory_for_default_language'] ) ) { $dir_default = $this->settings['urls']['directory_for_default_language']; } } $strategy = new WPML_URL_Converter_Subdir_Strategy( $dir_default, $this->default_lang_code, $this->active_language_codes, $this->settings['urls'] ); return new WPML_URL_Cached_Converter( $strategy, $this->get_object_url_helper_factory()->create(), $this->default_lang_code, $this->active_language_codes ); } /** * @return WPML_URL_Cached_Converter */ private function create_domain_converter() { $domains = isset( $this->settings['language_domains'] ) ? $this->settings['language_domains'] : array(); $wpml_wp_api = new WPML_WP_API(); $strategy = new WPML_URL_Converter_Domain_Strategy( $domains, $this->default_lang_code, $this->active_language_codes ); $wpml_url_converter = new WPML_URL_Cached_Converter( $strategy, $this->get_object_url_helper_factory()->create(), $this->default_lang_code, $this->active_language_codes ); $wpml_fix_url_domain = new WPML_Lang_Domain_Filters( $wpml_url_converter, $wpml_wp_api, new WPML_Debug_BackTrace( null, 10 ) ); $wpml_fix_url_domain->add_hooks(); $xdomain_data_parser = new WPML_XDomain_Data_Parser( $this->settings, new WPML_Data_Encryptor() ); $xdomain_data_parser->init_hooks(); return $wpml_url_converter; } /** * @return WPML_URL_Cached_Converter */ private function create_parameter_converter() { $strategy = new WPML_URL_Converter_Parameter_Strategy( $this->default_lang_code, $this->active_language_codes ); $wpml_url_converter = new WPML_URL_Cached_Converter( $strategy, $this->get_object_url_helper_factory()->create(), $this->default_lang_code, $this->active_language_codes ); $filters = new WPML_Lang_Parameter_Filters(); $filters->add_hooks(); return $wpml_url_converter; } } url-handling/converter/filters/class-wpml-lang-domain-filters.php 0000755 00000010367 14720342453 0021252 0 ustar 00 <?php class WPML_Lang_Domain_Filters { private $wpml_url_converter; private $wpml_wp_api; private $debug_backtrace; private $domains = array(); /** * WPML_Lang_Domain_Filters constructor. * * @param \WPML_URL_Converter $wpml_url_converter * @param \WPML_WP_API $wpml_wp_api */ public function __construct( WPML_URL_Converter $wpml_url_converter, WPML_WP_API $wpml_wp_api, WPML_Debug_BackTrace $debug_backtrace ) { $this->wpml_url_converter = $wpml_url_converter; $this->wpml_wp_api = $wpml_wp_api; $this->debug_backtrace = $debug_backtrace; } public function add_hooks() { add_filter( 'upload_dir', array( $this, 'upload_dir_filter_callback' ) ); add_filter( 'stylesheet_uri', array( $this, 'convert_url' ) ); add_filter( 'option_siteurl', array( $this, 'siteurl_callback' ) ); add_filter( 'content_url', array( $this, 'siteurl_callback' ) ); add_filter( 'plugins_url', array( $this, 'siteurl_callback' ) ); add_filter( 'login_url', array( $this, 'convert_url' ) ); add_filter( 'logout_url', array( $this, 'convert_logout_url' ) ); add_filter( 'admin_url', array( $this, 'admin_url_filter' ), 10, 2 ); add_filter( 'login_redirect', array( $this, 'convert_url' ), 1, 1 ); } /** * @param string $url * * @return string */ public function convert_url( $url ) { return $this->wpml_url_converter->convert_url( $url ); } /** * @param array $upload_dir * * @return array */ public function upload_dir_filter_callback( $upload_dir ) { $convertWithMatchingTrailingSlash = function ( $url ) { $hasTrailingSlash = '/' === substr( $url, -1 ); $newUrl = $this->wpml_url_converter->convert_url( $url ); return $hasTrailingSlash ? trailingslashit( $newUrl ) : untrailingslashit( $newUrl ); }; $upload_dir['url'] = $convertWithMatchingTrailingSlash( $upload_dir['url'] ); $upload_dir['baseurl'] = $convertWithMatchingTrailingSlash( $upload_dir['baseurl'] ); return $upload_dir; } /** * @param string $url * * @return string */ public function siteurl_callback( $url ) { $getting_network_site_url = $this->debug_backtrace->is_function_in_call_stack( 'get_admin_url' ) && is_multisite(); if ( ! $this->debug_backtrace->is_function_in_call_stack( 'get_home_path', false ) && ! $getting_network_site_url ) { $parsed_url = wpml_parse_url( $url ); $host = is_array( $parsed_url ) && isset( $parsed_url['host'] ); if ( $host && isset( $_SERVER['HTTP_HOST'] ) && $_SERVER['HTTP_HOST'] ) { $domain_from_global = $this->get_host_from_HTTP_HOST(); if ( $domain_from_global ) { $url = str_replace( $parsed_url['host'], $domain_from_global, $url ); } } } return $url; } /** * @return string */ private function get_host_from_HTTP_HOST() { $host = $_SERVER['HTTP_HOST']; if ( false !== strpos( $_SERVER['HTTP_HOST'], ':' ) ) { $host = explode( ':', $_SERVER['HTTP_HOST'] ); $host = $host[0]; } return $this->is_host_valid( $host ) ? $host : null; } /** * @param string $host * * @return bool */ private function is_host_valid( $host ) { $valid = false; foreach ( $this->get_domains() as $domain ) { if ( $domain === $host ) { $valid = true; break; } } return $valid; } /** * @return array */ private function get_domains() { if ( ! $this->domains ) { $this->domains = wpml_get_setting( 'language_domains' ); $home_parsed = wp_parse_url( $this->wpml_url_converter->get_abs_home() ); $this->domains[] = $home_parsed['host']; } return $this->domains; } /** * @param string $url * @param string $path * * @return string */ public function admin_url_filter( $url, $path ) { if ( ( strpos( $url, 'http://' ) === 0 || strpos( $url, 'https://' ) === 0 ) && 'admin-ajax.php' === $path && $this->wpml_wp_api->is_front_end() ) { global $sitepress; $url = $this->wpml_url_converter->convert_url( $url, $sitepress->get_current_language() ); } return $url; } /** * Convert logout url only for front-end. * * @param string $logout_url * * @return string */ public function convert_logout_url( $logout_url ) { if ( $this->wpml_wp_api->is_front_end() ) { $logout_url = $this->wpml_url_converter->convert_url( $logout_url ); } return $logout_url; } } url-handling/converter/filters/class-wpml-lang-parameter-filters.php 0000755 00000003436 14720342453 0021762 0 ustar 00 <?php class WPML_Lang_Parameter_Filters { public function add_hooks() { add_filter( 'request', array( $this, 'request_filter' ) ); add_filter( 'get_pagenum_link', array( $this, 'paginated_url_filter' ) ); add_filter( 'wp_link_pages_link', array( $this, 'paginated_link_filter' ) ); } public function request_filter( $request ) { // This is required so that home page detection works for other languages. if ( ! defined( 'WP_ADMIN' ) && isset( $request['lang'] ) ) { unset( $request['lang'] ); } return $request; } /** * Filters the pagination links on taxonomy archives to properly have the language parameter after the URI. * * @param string $url * * @return string */ public function paginated_url_filter( $url ) { $url = urldecode( $url ); $parts = explode( '?', $url ); $last_part = count( $parts ) > 2 ? array_pop( $parts ) : ''; $url = join( '?', $parts ); $url = preg_replace( '#(.+?)(/\?|\?)(.*?)(/.+?$)$#', '$1$4$5?$3', $url ); $url = preg_replace( '#(\?.+)(%2F|\/)$#', '$1', $url ); if ( '' !== $last_part && strpos( $url, '?' . $last_part ) === false ) { $url .= '&' . $last_part; } $parts = explode( '?', $url ); if ( isset( $parts[1] ) ) { // Maybe remove duplicated lang param $params = array(); parse_str( $parts[1], $params ); $url = $parts[0] . '?' . build_query( $params ); } return $url; } /** * Filters the pagination links on paginated posts and pages, acting on the links html * output containing the anchor tag the link is a property of. * * @param string $link_html * * @return string * * @hook wp_link_pages_link */ public function paginated_link_filter( $link_html ) { return preg_replace( '#"([^"].+?)(/\?|\?)([^/]+)(/[^"]+)"#', '"$1$4?$3"', $link_html ); } } url-handling/converter/filters/class-wpml-tax-permalink-filters-factory.php 0000755 00000000621 14720342453 0023275 0 ustar 00 <?php class WPML_Tax_Permalink_Filters_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { public function create() { global $wpml_url_converter, $sitepress; return new WPML_Tax_Permalink_Filters( $wpml_url_converter, new WPML_WP_Cache_Factory(), new WPML_Translation_Element_Factory( $sitepress ), WPML_Get_LS_Languages_Status::get_instance() ); } } url-handling/converter/filters/class-wpml-tax-permlink-filters.php 0000755 00000005141 14720342453 0021471 0 ustar 00 <?php class WPML_Tax_Permalink_Filters implements IWPML_Action { /** @var WPML_Translation_Element_Factory */ private $term_element_factory; /** @var WPML_URL_Converter */ private $url_converter; /** @var WPML_WP_Cache_Factory $cache_factory */ private $cache_factory; /** @var WPML_Get_LS_Languages_Status */ private $ls_languages_status; public function __construct( WPML_URL_Converter $url_converter, WPML_WP_Cache_Factory $cache_factory, WPML_Translation_Element_Factory $term_element_factory, WPML_Get_LS_Languages_Status $ls_language_status ) { $this->term_element_factory = $term_element_factory; $this->url_converter = $url_converter; $this->cache_factory = $cache_factory; $this->ls_languages_status = $ls_language_status; } public function add_hooks() { add_filter( 'term_link', array( $this, 'cached_filter_tax_permalink' ), 1, 3 ); } /** * @param string $permalink * @param int|\WP_Term|\stdClass $tag * @param string $taxonomy * * @return string */ public function cached_filter_tax_permalink( $permalink, $tag, $taxonomy ) { $tag = is_object( $tag ) ? $tag : get_term( $tag, $taxonomy ); $tag_id = $tag ? $tag->term_id : 0; $cache = $this->cache_factory->create_cache_item( 'icl_tax_permalink_filter', array( $tag_id, $taxonomy, $this->is_link_for_language_switcher() ) ); if ( $cache->exists() ) { return $cache->get(); } $permalink = $this->filter_tax_permalink( $permalink, $tag_id ); $cache->set( $permalink ); return $permalink; } /** * Filters the permalink pointing at a taxonomy archive to correctly reflect the language of its underlying term * * @param string $permalink url pointing at a term's archive * @param int $tag_id * * @return string */ private function filter_tax_permalink( $permalink, $tag_id ) { if ( $tag_id ) { $term_element = $this->term_element_factory->create( $tag_id, 'term' ); if ( ! $this->is_display_as_translated_and_in_default_lang( $term_element ) || $this->is_link_for_language_switcher() ) { $term_language = $term_element->get_language_code(); if ( (bool) $term_language ) { $permalink = $this->url_converter->convert_url( $permalink, $term_language ); } } } return $permalink; } private function is_display_as_translated_and_in_default_lang( WPML_Translation_Element $element ) { return $element->is_display_as_translated() && $element->is_in_default_language(); } private function is_link_for_language_switcher() { return $this->ls_languages_status->is_getting_ls_languages(); } } url-handling/converter/class-wpml-rewrite-rules-filter.php 0000755 00000003430 14720342453 0020033 0 ustar 00 <?php class WPML_Rewrite_Rules_Filter { /** * @var array */ private $active_languages; /** * @var WPML_URL_Filters */ private $wpml_url_filters; /** * @param array $active_languages * @param WPML_URL_Filters $wpml_url_filters */ public function __construct( $active_languages, $wpml_url_filters = null ) { $this->active_languages = $active_languages; if ( ! $wpml_url_filters ) { global $wpml_url_filters; } $this->wpml_url_filters = $wpml_url_filters; } /** * @param string $htaccess_string Content of the .htaccess file * * @return string .htaccess file contents with adjusted RewriteBase */ public function rid_of_language_param( $htaccess_string ) { if ( $this->wpml_url_filters->frontend_uses_root() || $this->is_permalink_page() || $this->is_shop_page() ) { foreach ( $this->active_languages as $lang_code ) { foreach ( array( '', 'index.php' ) as $base ) { $htaccess_string = str_replace( '/' . $lang_code . '/' . $base, '/' . $base, $htaccess_string ); } } } return $htaccess_string; } /** * Check if it is permalink page in admin. * * @return bool */ private function is_permalink_page() { return $this->is_admin_screen( 'options-permalink' ); } /** * Check if it is WooCommerce shop page in admin. * * @return bool */ private function is_shop_page() { return $this->is_admin_screen( 'page' ) && get_option( 'woocommerce_shop_page_id' ) === intval( $_GET['post'] ); } /** * Check if it as a certain screen on admin page. * * @param string $screen_id * * @return bool */ private function is_admin_screen( $screen_id ) { return is_admin() && function_exists( 'get_current_screen' ) && get_current_screen() && get_current_screen()->id === $screen_id; } } url-handling/converter/class-wpml-url-converter.php 0000755 00000014154 14720342453 0016553 0 ustar 00 <?php /** * Class WPML_URL_Converter * * @package wpml-core * @subpackage url-handling */ use WPML\SuperGlobals\Server; use WPML\UrlHandling\WPLoginUrlConverter; class WPML_URL_Converter { /** * @var IWPML_URL_Converter_Strategy */ private $strategy; /** * @var string */ protected $default_language; /** * @var string[] */ protected $active_languages; /** * @var WPML_URL_Converter_Url_Helper */ protected $home_url_helper; /** * @var WPML_URL_Converter_Lang_Param_Helper */ protected $lang_param; /** * @var WPML_Slash_Management */ protected $slash_helper; /** * @var WPML_Resolve_Object_Url_Helper */ protected $object_url_helper; /** * @param IWPML_URL_Converter_Strategy $strategy * @param IWPML_Resolve_Object_Url $object_url_helper * @param string $default_language * @param array<string> $active_languages */ public function __construct( IWPML_URL_Converter_Strategy $strategy, IWPML_Resolve_Object_Url $object_url_helper, $default_language, $active_languages ) { $this->strategy = $strategy; $this->object_url_helper = $object_url_helper; $this->default_language = $default_language; $this->active_languages = $active_languages; $this->lang_param = new WPML_URL_Converter_Lang_Param_Helper( $active_languages ); $this->slash_helper = new WPML_Slash_Management(); } /** * @return IWPML_URL_Converter_Strategy */ public function get_strategy() { return $this->strategy; } /** * @param WPML_URL_Converter_Url_Helper $url_helper */ public function set_url_helper( WPML_URL_Converter_Url_Helper $url_helper ) { $this->home_url_helper = $url_helper; if ( $this->strategy instanceof WPML_URL_Converter_Abstract_Strategy ) { $this->strategy->set_url_helper( $url_helper ); } } /** * @return WPML_URL_Converter_Url_Helper */ public function get_url_helper() { if ( ! $this->home_url_helper ) { $this->home_url_helper = new WPML_URL_Converter_Url_Helper(); } return $this->home_url_helper; } public function get_abs_home() { return $this->get_url_helper()->get_abs_home(); } /** * @param WPML_URL_Converter_Lang_Param_Helper $lang_param_helper */ public function set_lang_param_helper( WPML_URL_Converter_Lang_Param_Helper $lang_param_helper ) { $this->lang_param = $lang_param_helper; } /** * @param WPML_Slash_Management $slash_helper */ public function set_slash_helper( WPML_Slash_Management $slash_helper ) { $this->slash_helper = $slash_helper; } public function get_default_site_url() { return $this->get_url_helper()->get_unfiltered_home_option(); } /** * Scope of this function: * 1. Convert the home URL in the specified language depending on language negotiation: * 1. Add a language directory * 2. Change the domain * 3. Add a language parameter * 2. If the requested URL is equal to the current URL, the URI will be adapted * with potential slug translations for: * - single post slugs * - taxonomy term slug * * WARNING: The URI slugs won't be translated for arbitrary URL (not the current one) * * @param string $url * @param bool $lang_code * * @return bool|mixed|string */ public function convert_url( $url, $lang_code = false ) { if ( ! $url ) { return $url; } global $sitepress; $new_url = false; if ( ! $lang_code ) { $lang_code = $sitepress->get_current_language(); } $language_from_url = $this->get_language_from_url( $url ); if ( $language_from_url === $lang_code || 'all' === $lang_code ) { $new_url = $url; } else { if ( $this->can_resolve_object_url( $url ) ) { $new_url = $this->object_url_helper->resolve_object_url( $url, $lang_code ); } if ( false === $new_url ) { $new_url = $this->strategy->convert_url_string( $url, $lang_code ); } } return $new_url; } /** * Takes a URL and returns the language of the document it points at * * @param string $url * @return string */ public function get_language_from_url( $url ) { $http_referer_factory = new WPML_URL_HTTP_Referer_Factory(); $http_referer = $http_referer_factory->create(); $url = $http_referer->get_url( $url ); $language = $this->lang_param->lang_by_param( $url ) ?: $this->get_strategy()->get_lang_from_url_string( $url ); /** * Filters language code fetched from the current URL and allows to rewrite * the language to set on front-end * * @param string $language language fetched from the current URL * @param string $url current URL. */ $language = apply_filters( 'wpml_get_language_from_url', $language, $url ); return $this->get_strategy()->validate_language( $language, $url ); } /** * @param string $url * @param string $language * * @return string */ public function get_home_url_relative( $url, $language ) { return $this->get_strategy()->get_home_url_relative( $url, $language ); } /** * @param SitePress $sitepress * * @return WPLoginUrlConverter|null */ public function get_wp_login_url_converter( $sitepress ) { return $this->strategy->use_wp_login_url_converter() ? new WPLoginUrlConverter( $sitepress, $this ) : null; } /** * @param string $url * * @return bool */ private function can_resolve_object_url( $url ) { $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : ''; $server_name = strpos( $request_uri, '/' ) === 0 ? untrailingslashit( Server::getServerName() ) : trailingslashit( Server::getServerName() ); $request_url = stripos( get_option( 'siteurl' ), 'https://' ) === 0 ? 'https://' . $server_name . $request_uri : 'http://' . $server_name . $request_uri; $is_request_url = trailingslashit( $request_url ) === trailingslashit( $url ); $is_home_url = trailingslashit( $this->get_url_helper()->get_abs_home() ) === trailingslashit( $url ); $is_home_url_filter = current_filter() === 'home_url'; return $is_request_url && ! $is_home_url && ! $is_home_url_filter; } /** @return WPML_URL_Converter */ public static function getGlobalInstance() { global $wpml_url_converter; return $wpml_url_converter; } } url-handling/class-wpml-include-url.php 0000755 00000004103 14720342453 0014151 0 ustar 00 <?php class WPML_Include_Url extends WPML_WPDB_User { private $unfiltered_home_url; private $requested_host; public function __construct( &$wpdb, $requested_host ) { parent::__construct( $wpdb ); add_filter( 'style_loader_src', array( $this, 'filter_include_url' ) ); add_filter( 'script_loader_src', array( $this, 'filter_include_url' ) ); add_filter( 'the_password_form', array( $this, 'wpml_password_form_filter' ) ); $this->requested_host = $requested_host; } public function filter_include_url( $result ) { $domains = wpml_get_setting_filter( array(), 'language_domains' ); $domains = preg_replace( '#^(http(?:s?))://#', '', array_map( 'untrailingslashit', $domains ) ); if ( (bool) $domains === true ) { $php_host_in_domain = wpml_parse_url( $result, PHP_URL_HOST ); $domains[] = wpml_parse_url( $this->get_unfiltered_home(), PHP_URL_HOST ); foreach ( $domains as $dom ) { if ( strpos( trailingslashit( $php_host_in_domain ), trailingslashit( $dom ) ) === 0 ) { $http_host_parts = explode( ':', $this->requested_host ); unset( $http_host_parts[1] ); $http_host_without_port = implode( $http_host_parts ); $result = str_replace( $php_host_in_domain, $http_host_without_port, $result ); break; } } } return $result; } public function wpml_password_form_filter( $form ) { if ( preg_match( '/action="(.*?)"/', $form, $matches ) ) { $new_url = $this->filter_include_url( $matches[1] ); $form_action = str_replace( $matches[1], $new_url, $matches[0] ); $form = str_replace( $matches[0], $form_action, $form ); } return $form; } /** * Returns the value of the unfiltered home option directly from the wp_options table. * * @return string */ public function get_unfiltered_home() { $this->unfiltered_home_url = $this->unfiltered_home_url ? $this->unfiltered_home_url : $this->wpdb->get_var( " SELECT option_value FROM {$this->wpdb->options} WHERE option_name = 'home' LIMIT 1" ); return $this->unfiltered_home_url; } } url-handling/class-wpml-url-http-referer-factory.php 0000755 00000000301 14720342453 0016576 0 ustar 00 <?php class WPML_URL_HTTP_Referer_Factory { /** * @return WPML_URL_HTTP_Referer */ public function create() { return new WPML_URL_HTTP_Referer( new WPML_Rest( new WP_Http() ) ); } } url-handling/wpml-wp-login-url-converter.php 0000755 00000017350 14720342453 0015174 0 ustar 00 <?php namespace WPML\UrlHandling; use WPML\API\Sanitize; use WPML\Element\API\Languages; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Str; use WPML\LIB\WP\Option; use WPML\LIB\WP\User; class WPLoginUrlConverter implements \IWPML_Action { const PRIORITY_AFTER_URL_FILTERS = 100; const SETTINGS_KEY = 'wpml_login_page_translation'; private $rewrite_rule_not_found; /** @var \WPML_URL_Converter $url_converter */ private $url_converter; /** @var \SitePress $sitepress */ private $sitepress; /** * @param \WPML_URL_Converter $url_converter * @param \SitePress $sitepress */ public function __construct( $sitepress, $url_converter ) { $this->rewrite_rule_not_found = false; $this->url_converter = $url_converter; $this->sitepress = $sitepress; } public function add_hooks() { add_filter( 'site_url', [ $this, 'site_url' ], self::PRIORITY_AFTER_URL_FILTERS, 3 ); add_filter( 'login_url', [ $this, 'convert_url' ] ); add_filter( 'register_url', [ $this, 'convert_url' ] ); add_filter( 'lostpassword_url', [ $this, 'convert_url' ] ); add_filter( 'request', [ $this, 'on_request' ] ); add_filter( 'wpml_get_language_from_url', [ $this, 'wpml_login_page_language_from_url', ], self::PRIORITY_AFTER_URL_FILTERS, 2 ); add_filter( 'registration_redirect', [ $this, 'filter_redirect_with_lang' ] ); add_filter( 'lostpassword_redirect', [ $this, 'filter_redirect_with_lang' ] ); if ( self::isEnabled() ) { add_filter( 'logout_url', [ $this, 'convert_user_logout_url' ] ); add_filter( 'logout_redirect', [ $this, 'convert_default_redirect_url' ], 2, 2 ); } add_action( 'generate_rewrite_rules', [ $this, 'generate_rewrite_rules' ] ); add_action( 'login_init', [ $this, 'redirect_to_login_url_with_lang' ] ); if ( is_multisite() ) { add_filter( 'network_site_url', [ $this, 'site_url' ], self::PRIORITY_AFTER_URL_FILTERS, 3 ); add_filter( 'wp_signup_location', [ $this, 'convert_url' ] ); add_action( 'signup_hidden_fields', [ $this, 'add_signup_language_field' ] ); } } /** * Converts the logout URL to be translated. * * @param string $url * @return string */ public function convert_user_logout_url( $url ) { $current_user_id = User::getCurrentId(); if ( $current_user_id ) { $user_locale = User::getMetaSingle( $current_user_id, 'locale' ); if ( ! empty( $user_locale ) ) { $language_code = $this->sitepress->get_language_code_from_locale( $user_locale ); return $this->url_converter->convert_url( $url, $language_code ); } } return $url; } public function add_signup_language_field() { echo "<input type='hidden' name='lang' value='" . $this->sitepress->get_current_language() . "' />"; } public function redirect_to_login_url_with_lang() { $sitePath = Obj::propOr( '', 'path', parse_url( site_url() ) ); /** @var string $requestUriWithoutPath */ $requestUriWithoutPath = Str::trimPrefix( (string) $sitePath, (string) $_SERVER['REQUEST_URI'] ); $converted_url = site_url( $requestUriWithoutPath, 'login' ); if ( ! is_multisite() && $converted_url != site_url( $requestUriWithoutPath ) && wp_redirect( $converted_url, 302, 'WPML' ) ) { exit; } } public function generate_rewrite_rules() { global $wp_rewrite; $language_rules = wpml_collect( Languages::getActive() )->mapWithKeys( function ( $lang ) { return [ $lang['code'] . '/wp-login.php' => 'wp-login.php' ]; } ); $wp_rewrite->non_wp_rules = array_merge( $language_rules->toArray(), $wp_rewrite->non_wp_rules ); } /** * Converts the redirected string if it's the default one. * * @param string $redirect_to * @param string $requested_redirect_to * @return string */ public function convert_default_redirect_url( $redirect_to, $requested_redirect_to ) { if ( '' === $requested_redirect_to ) { return $this->convert_url( $redirect_to ); } return $redirect_to; } public function filter_redirect_with_lang( $redirect_to ) { if ( ! $redirect_to && Obj::prop( 'lang', $_GET ) ) { $redirect_to = site_url( 'wp-login.php?checkemail=confirm', 'login' ); } return $redirect_to; } public function wpml_login_page_language_from_url( $language, $url ) { if ( $this->is_wp_login_url( $url ) ) { if ( isset( $_GET['lang'] ) && $_GET['lang'] != $language ) { return Sanitize::stringProp( 'lang', $_GET ); } if ( is_multisite() && isset( $_POST['lang'] ) && $_POST['lang'] != $language ) { return Sanitize::stringProp( 'lang', $_POST ); } } return $language; } public function site_url( $url, $path, $scheme ) { if ( $scheme === 'login_post' || $scheme === 'login' || $this->should_convert_url_for_multisite( $url ) ) { $url = $this->convert_url( $url ); } return $url; } public function convert_url( $url ) { $lang_param = $this->get_language_param_for_convert_url(); if ( $lang_param ) { return add_query_arg( 'lang', $lang_param, $url ); } return $this->url_converter->convert_url( $url ); } public function on_request( $query_vars ) { if ( Relation::propEq( 'name', 'wp-login.php', $query_vars ) ) { $this->rewrite_rule_not_found = true; } else { $this->rewrite_rule_not_found = false; } if ( $this->rewrite_rule_not_found && $this->is_wp_login_action() ) { $current_url = site_url( $_SERVER['REQUEST_URI'], 'login' ); $redirect_to = $this->remove_language_directory_from_url( $current_url ); if ( $redirect_to !== $current_url && wp_redirect( $redirect_to, 302, 'WPML' ) ) { exit; } } return $query_vars; } /** * @param bool $validateOrRollback - If true, it will be validated that the translated Login URL is accessible or rollback. * @return void */ public static function enable( $validateOrRollback = false ) { self::saveState( true, $validateOrRollback ); } public static function disable() { self::saveState( false ); } /** * @return bool */ public static function isEnabled() { return Option::getOr( self::SETTINGS_KEY, false ); } /** * @param bool $state * @param bool $validate - if true, will validate the change or undo it. * */ public static function saveState( $state, $validate = false ) { Option::update( self::SETTINGS_KEY, $state ); WPLoginUrlConverterRules::markRulesForUpdating( $validate ); } private function should_convert_url_for_multisite( $url ) { return is_multisite() && Str::includes( 'wp-activate.php', $url ); } private function is_wp_login_url( $url ) { return Str::includes( 'wp-login.php', $url ) || Str::includes( 'wp-signup.php', $url ) || Str::includes( 'wp-activate.php', $url ); } private function is_wp_login_action() { $actions = wpml_collect( [ 'confirm_admin_email', 'postpass', 'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register', 'login', 'confirmaction', ] ); return $actions->contains( Obj::prop( 'action', $_GET ) ); } private function get_language_param_for_convert_url() { if ( isset( $_GET['lang'] ) ) { return Sanitize::stringProp( 'lang', $_GET ); } if ( is_multisite() && isset( $_POST['lang'] ) ) { return Sanitize::stringProp( 'lang', $_POST ); } if ( $this->rewrite_rule_not_found || $this->is_subdomain_multisite() ) { return $this->sitepress->get_current_language(); } return null; } private function is_subdomain_multisite() { return is_multisite() && defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL; } private function remove_language_directory_from_url( $url ) { $lang = $this->get_language_param_for_convert_url(); $url_parts = wpml_parse_url( $url ); if ( $lang && Str::includes( '/' . $lang . '/', $url_parts['path'] ) ) { $url = Str::replace( '/' . $lang . '/', '/', $url ); } return $url; } } url-handling/class-wpml-root-page.php 0000755 00000022522 14720342453 0013630 0 ustar 00 <?php class WPML_Root_Page { public static function init() { if ( self::uses_html_root() || self::get_root_id() > 0 || strpos( (string) filter_var( $_SERVER['REQUEST_URI'] ), 'wpml_root_page=1' ) !== false || (bool) filter_input( INPUT_POST, '_wpml_root_page' ) === true ) { global $wpml_root_page_actions; $wpml_root_page_actions = wpml_get_root_page_actions_obj(); add_action( 'init', array( $wpml_root_page_actions, 'wpml_home_url_init' ), 0 ); add_filter( 'wp_page_menu_args', array( $wpml_root_page_actions, 'wpml_home_url_exclude_root_page_from_menus' ) ); add_filter( 'wp_get_nav_menu_items', array( $wpml_root_page_actions, 'exclude_root_page_menu_item' ), 10, 1 ); add_filter( 'wp_list_pages_excludes', array( $wpml_root_page_actions, 'wpml_home_url_exclude_root_page' ) ); add_filter( 'page_attributes_dropdown_pages_args', array( $wpml_root_page_actions, 'wpml_home_url_exclude_root_page2' ) ); add_filter( 'get_pages', array( $wpml_root_page_actions, 'wpml_home_url_get_pages' ) ); add_action( 'save_post', array( $wpml_root_page_actions, 'wpml_home_url_save_post_actions' ), 0, 2 ); if ( self::get_root_id() > 0 ) { add_filter( 'template_include', array( 'WPML_Root_Page', 'wpml_home_url_template_include' ) ); add_filter( 'the_preview', array( 'WPML_Root_Page', 'front_page_id_filter' ) ); } $root_page_actions = wpml_get_root_page_actions_obj(); add_action( 'icl_set_element_language', array( $root_page_actions, 'delete_root_page_lang' ), 10, 0 ); } } /** * Checks if the value in $_SERVER['REQUEST_URI] points towards the root page. * Therefore this can be used to check if the current request points towards the root page. * * @return bool */ public static function is_current_request_root() { return self::is_root_page( $_SERVER['REQUEST_URI'] ); } /** * @param string $requested_url * Checks if a requested url points towards the root page. * * @return bool */ public static function is_root_page( $requested_url ) { $cached_val = wp_cache_get( md5( $requested_url ) ); if ( $cached_val !== false ) { return (bool) $cached_val; } $request_parts = self::get_slugs_and_get_query( $requested_url ); $slugs = $request_parts['slugs']; $gets = $request_parts['querystring']; $target_of_gets = self::get_query_target_from_query_string( $gets ); if ( $target_of_gets == WPML_QUERY_IS_ROOT ) { $result = true; } elseif ( $target_of_gets == WPML_QUERY_IS_OTHER_THAN_ROOT || $target_of_gets == WPML_QUERY_IS_NOT_FOR_POST && self::query_points_to_archive( $gets ) ) { $result = false; } else { $result = self::slugs_point_to_root( $slugs ); } wp_cache_add( md5( $requested_url ), ( $result === true ? 1 : 0 ) ); return $result; } public static function uses_html_root() { $urls = icl_get_setting( 'urls' ); return isset( $urls['root_page'] ) && isset( $urls['show_on_root'] ) && $urls['directory_for_default_language'] && 'html_file' === $urls['show_on_root']; } /** * Returns the id of the root page or false if it isn't set. * * @return bool|int */ public static function get_root_id() { $root_actions = wpml_get_root_page_actions_obj(); return $root_actions->get_root_page_id(); } /** * Returns the slug of the root page or false if non exists. * * @return bool|string */ private static function get_root_slug() { $root_id = self::get_root_id(); $root_slug = false; if ( $root_id ) { $root_page_object = get_post( (int) $root_id ); if ( $root_page_object && isset( $root_page_object->post_name ) ) { $root_slug = $root_page_object->post_name; } } return $root_slug; } /** * @param string $requested_url * Takes a request_url in the format of $_SERVER['REQUEST_URI'] * and returns an associative array containing its slugs ans query string. * * @return array */ private static function get_slugs_and_get_query( $requested_url ) { $result = array(); $request_path = wpml_parse_url( $requested_url, PHP_URL_PATH ); $request_path = wpml_strip_subdir_from_url( $request_path ); $slugs = self::get_slugs_array( $request_path ); $result['slugs'] = $slugs; $query_string = wpml_parse_url( $requested_url, PHP_URL_QUERY ); $result['querystring'] = ! $query_string ? '' : $query_string; return $result; } /** * @param string $path * Turns a query string into an array of its slugs. * The array is filtered so to not contain empty values and * consecutively and numerically indexed starting at 0. * * @return array */ private static function get_slugs_array( $path ) { $slugs = explode( '/', $path ); $slugs = array_filter( $slugs ); $slugs = array_values( $slugs ); return $slugs; } /** * @param array $slugs * Checks if a given set of slugs points towards the root page or not. * The result of this can always be overridden by GET parameters and is not a certain * check as to being on the root page or not. * * @return bool */ private static function slugs_point_to_root( $slugs ) { $result = true; if ( ! empty( $slugs ) ) { $root_slug = self::get_root_slug(); $last_slug = array_pop( $slugs ); $second_slug = array_pop( $slugs ); $third_slug = array_pop( $slugs ); if ( ( $root_slug != $last_slug && ! is_numeric( $last_slug ) ) || ( is_numeric( $last_slug ) && $second_slug != null && $root_slug !== $second_slug && ( ( 'page' !== $second_slug ) || ( 'page' === $second_slug && ( $third_slug && $third_slug != $root_slug ) ) ) ) ) { $result = false; } } return $result; } /** * Turns a given query string into an associative array of its parameters. * * @param string $query_string * @return array<string,string> */ private static function get_query_array_from_string( $query_string ) { $all_query_params = array(); parse_str( $query_string, $all_query_params ); return $all_query_params; } /** * @param string $query_string * Checks if the WP_Query functionality can decisively recognize if a querystring points * towards an archive. * * @return bool */ private static function query_points_to_archive( $query_string ) { $root_page_actions = wpml_get_root_page_actions_obj(); remove_action( 'parse_query', array( $root_page_actions, 'wpml_home_url_parse_query' ) ); $query_string = str_replace( '?', '', $query_string ); $query = new WP_Query( $query_string ); $is_archive = $query->is_archive(); add_action( 'parse_query', array( $root_page_actions, 'wpml_home_url_parse_query' ) ); return $is_archive; } /** * @param string $query_string * Checks if a given query string decisively points towards or away from the root page. * * @return int */ private static function get_query_target_from_query_string( $query_string ) { $params_array = self::get_query_array_from_string( $query_string ); return self::get_query_target_from_params_array( $params_array ); } /** * @param array $query_params * Checks if a set of query parameters decisively points towards or away from the root page. * * @return int */ private static function get_query_target_from_params_array( $query_params ) { if ( ! isset( $query_params['p'] ) && ! isset( $query_params['page_id'] ) && ! isset( $query_params['name'] ) && ! isset( $query_params['pagename'] ) && ! isset( $query_params['page_name'] ) && ! isset( $query_params['attachment_id'] ) ) { $result = WPML_QUERY_IS_NOT_FOR_POST; } else { $root_id = self::get_root_id(); $root_slug = self::get_root_slug(); if ( ( isset( $query_params['p'] ) && $query_params['p'] != $root_id ) || ( isset( $query_params['page_id'] ) && $query_params['page_id'] != $root_id ) || ( isset( $query_params['name'] ) && $query_params['name'] != $root_slug ) || ( isset( $query_params['pagename'] ) && $query_params['pagename'] != $root_slug ) || ( isset( $query_params['preview_id'] ) && $query_params['preview_id'] != $root_id ) || ( isset( $query_params['attachment_id'] ) && $query_params['attachment_id'] != $root_id ) ) { $result = WPML_QUERY_IS_OTHER_THAN_ROOT; } else { $result = WPML_QUERY_IS_ROOT; } } return $result; } /** * @param false|WP_Post $post * Filters the postID used by the preview for the case of the root page preview. * * @return null|WP_Post */ public static function front_page_id_filter( $post ) { $preview_id = isset( $_GET['preview_id'] ) ? $_GET['preview_id'] : - 1; if ( $preview_id == self::get_root_id() ) { $post = get_post( $preview_id ); } return $post; } /** * Filters the template that is used for the root page * * @param string $template * * @return string */ public static function wpml_home_url_template_include( $template ) { return self::is_current_request_root() ? self::get_root_page_template() : $template; } /** * @return string */ public static function get_root_page_template() { $page_template = get_page_template(); if ( $page_template ) { return $page_template; } $singular_template = get_singular_template(); return $singular_template ?: get_index_template(); } } url-handling/class-wpml-xdomain-data-parser.php 0000755 00000007033 14720342453 0015573 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_XDomain_Data_Parser { const SCRIPT_HANDLER = 'wpml-xdomain-data'; /** * @var array $settings */ private $settings; private $encryptor; /** * WPML_XDomain_Data_Parser constructor. * * @param array<string,mixed> $settings * @param \WPML_Data_Encryptor $encryptor */ public function __construct( &$settings, $encryptor ) { $this->settings = &$settings; $this->encryptor = $encryptor; } public function init_hooks() { if ( ! isset( $this->settings['xdomain_data'] ) || $this->settings['xdomain_data'] != WPML_XDOMAIN_DATA_OFF ) { add_action( 'init', array( $this, 'init' ) ); add_filter( 'wpml_get_cross_domain_language_data', array( $this, 'get_xdomain_data' ) ); add_filter( 'wpml_get_cross_domain_language_data', array( $this, 'get_xdomain_data' ) ); } } public function init() { add_action( 'wp_ajax_switching_language', array( $this, 'send_xdomain_language_data' ) ); add_action( 'wp_ajax_nopriv_switching_language', array( $this, 'send_xdomain_language_data' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts_action' ), 100 ); } public function register_scripts_action() { if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { $ls_parameters = WPML_Language_Switcher::parameters(); $js_xdomain_data = array( 'css_selector' => $ls_parameters['css_prefix'] . 'item', 'ajax_url' => admin_url( 'admin-ajax.php' ), 'current_lang' => apply_filters( 'wpml_current_language', '' ), '_nonce' => wp_create_nonce( 'wp_ajax_switching_language' ), ); wp_enqueue_script( self::SCRIPT_HANDLER, ICL_PLUGIN_URL . '/res/js/xdomain-data.js', array(), ICL_SITEPRESS_VERSION ); wp_script_add_data( self::SCRIPT_HANDLER, 'strategy', 'defer' ); wp_localize_script( self::SCRIPT_HANDLER, 'wpml_xdomain_data', $js_xdomain_data ); } } public function set_up_xdomain_language_data() { $ret = array(); $data = apply_filters( 'WPML_cross_domain_language_data', array() ); $data = apply_filters( 'wpml_cross_domain_language_data', $data ); if ( ! empty( $data ) ) { $encoded_data = json_encode( $data ); $encoded_data = $this->encryptor->encrypt( $encoded_data ); $base64_encoded_data = base64_encode( $encoded_data ); $ret['xdomain_data'] = urlencode( $base64_encoded_data ); $ret['method'] = WPML_XDOMAIN_DATA_POST == $this->settings['xdomain_data'] ? 'post' : 'get'; } return $ret; } public function send_xdomain_language_data() { $nonce = isset( $_POST['_nonce'] ) ? sanitize_text_field( $_POST['_nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wp_ajax_switching_language' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ), 400 ); return; } $data = $this->set_up_xdomain_language_data(); wp_send_json_success( $data ); } public function get_xdomain_data() { $xdomain_data = array(); if ( isset( $_GET['xdomain_data'] ) || isset( $_POST['xdomain_data'] ) ) { $xdomain_data_request = false; if ( WPML_XDOMAIN_DATA_GET == $this->settings['xdomain_data'] ) { $xdomain_data_request = Sanitize::stringProp( 'xdomain_data', $_GET ); } elseif ( WPML_XDOMAIN_DATA_POST == $this->settings['xdomain_data'] ) { $xdomain_data_request = urldecode( (string) Sanitize::stringProp( 'xdomain_data', $_POST ) ); } if ( $xdomain_data_request ) { $data = base64_decode( $xdomain_data_request ); $data = $this->encryptor->decrypt( $data ); $xdomain_data = (array) json_decode( $data, true ); } } return $xdomain_data; } } url-handling/class-wpml-url-http-referer.php 0000755 00000004517 14720342453 0015146 0 ustar 00 <?php class WPML_URL_HTTP_Referer { private $rest; public function __construct( WPML_Rest $rest ) { $this->rest = $rest; } /** * @param string $backup_url * * @return string */ public function get_url( $backup_url ) { if ( ! isset( $_SERVER['HTTP_REFERER'] ) ) { return $backup_url; } if ( WPML_Ajax::is_admin_ajax_request_called_from_frontend( $backup_url ) || $this->is_rest_request_called_from_post_edit_page() ) { return $_SERVER['HTTP_REFERER']; } return $backup_url; } /** * @return bool|int */ public function get_trid() { $referer_data = $request_uri_data = array(); if ( array_key_exists( 'HTTP_REFERER', $_SERVER ) ) { $query = (string) wpml_parse_url( $_SERVER['HTTP_REFERER'], PHP_URL_QUERY ); parse_str( $query, $referer_data ); } if ( array_key_exists( 'REQUEST_URI', $_SERVER ) ) { $request_uri = (string) wpml_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY ); parse_str( $request_uri, $request_uri_data ); } /** * trid from `HTTP_REFERER` should be return only if `REQUEST_URI` also has trid set. * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmltm-1351 * * Or when it is a rest request called in the post edit page (Gutenberg) * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-5265 */ return array_key_exists( 'trid', $referer_data ) && array_key_exists( 'trid', $request_uri_data ) || ( array_key_exists( 'trid', $referer_data ) && $this->is_rest_request_called_from_post_edit_page() ) ? (int) $referer_data['trid'] : false; } /** * We need this in order to detect the language when adding * translation from inside of a Gutenberg page while * they don't provide a JS API which allows us to do it * * @link https://github.com/WordPress/gutenberg/issues/5958 * @link https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlcore-5265 * * @return bool */ public function is_rest_request_called_from_post_edit_page() { return $this->rest->is_rest_request() && self::is_post_edit_page(); } public static function is_post_edit_page() { return isset( $_SERVER['HTTP_REFERER'] ) && ( strpos( $_SERVER['HTTP_REFERER'], 'wp-admin/post.php' ) || strpos( $_SERVER['HTTP_REFERER'], 'wp-admin/post-new.php' ) || strpos( $_SERVER['HTTP_REFERER'], 'wp-admin/edit.php' ) ); } } url-handling/class-wpml-language-domains.php 0000755 00000000672 14720342453 0015150 0 ustar 00 <?php class WPML_Language_Domains { private $domains; public function __construct( SitePress $sitepress, WPML_URL_Converter_Url_Helper $converter_url_helper ) { $this->domains = $sitepress->get_setting( 'language_domains' ); $this->domains[ $sitepress->get_default_language() ] = wpml_parse_url( $converter_url_helper->get_abs_home(), PHP_URL_HOST ); } public function get( $lang ) { return $this->domains[ $lang ]; } } url-handling/WPLoginUrlConverterRules.php 0000755 00000005362 14720342453 0014563 0 ustar 00 <?php namespace WPML\UrlHandling; use WPML\LIB\WP\Option; use function WPML\Container\make; class WPLoginUrlConverterRules implements \IWPML_Action { const MARKED_FOR_UPDATE_AND_VALIDATE_OR_ROLLBACK = 3; const MARKED_FOR_UPDATE = true; const UNMARKED = false; const SKIP_SAVING_LANG_IN_COOKIES_KEY = 'skip_saving_language_cookie'; const UPDATE_RULES_KEY = 'wpml_login_page_translation_update_rules'; public function add_hooks() { /** * Filter hook that checks existence of skip_saving_language_cookie key in $_GET and whether its value is 'true'. * According to that we make decision to save language code in cookies or not */ add_filter( 'wpml_should_skip_saving_language_in_cookies', function ( $forceSkipSavingLangInCookies ) { if ( $forceSkipSavingLangInCookies ) { return true; } return isset( $_GET[ WPLoginUrlConverterRules::SKIP_SAVING_LANG_IN_COOKIES_KEY ] ) && $_GET[ WPLoginUrlConverterRules::SKIP_SAVING_LANG_IN_COOKIES_KEY ] === 'true'; } ); if ( Option::getOr( self::UPDATE_RULES_KEY, self::UNMARKED ) ) { add_filter( 'init', [ self::class, 'update' ] ); } } public static function markRulesForUpdating( $verify = false ) { Option::update( self::UPDATE_RULES_KEY, $verify ? self::MARKED_FOR_UPDATE_AND_VALIDATE_OR_ROLLBACK : self::MARKED_FOR_UPDATE ); } public static function update() { global $wp_rewrite; if ( ! function_exists( 'save_mod_rewrite_rules' ) ) { require_once ABSPATH . 'wp-admin/includes/misc.php'; } $wp_rewrite->rewrite_rules(); save_mod_rewrite_rules(); $wp_rewrite->flush_rules( false ); $needsValidation = self::MARKED_FOR_UPDATE_AND_VALIDATE_OR_ROLLBACK === (int) Option::get( self::UPDATE_RULES_KEY ); Option::update( self::UPDATE_RULES_KEY, self::UNMARKED ); if ( $needsValidation ) { static::validateOrDisable(); } } /** * Validates that the Translated Login URL is accessible. * Used to validate the setting when enabled by default. */ public static function validateOrDisable() { $translationLangs = \WPML\Setup\Option::getTranslationLangs(); if ( empty( $translationLangs ) ) { return; } /** @var \WPML_URL_Converter $urlConverter */ $urlConverter = make( \WPML_URL_Converter::class ); $newUrl = $urlConverter->convert_url( wp_login_url(), $translationLangs[0] ); /** * We pass SKIP_SAVING_LANG_IN_COOKIES_KEY to avoid saving the language code in $sitepress and cookies in this case * @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1544 */ $loginResponseCode = wp_remote_retrieve_response_code( wp_remote_get( $newUrl, [ 'body' => [ self::SKIP_SAVING_LANG_IN_COOKIES_KEY => 'true' ] ] ) ); if ( 200 !== $loginResponseCode ) { WPLoginUrlConverter::disable(); } } } url-handling/wpml-wp-login-url-converter-factory.php 0000755 00000000763 14720342453 0016641 0 ustar 00 <?php namespace WPML\UrlHandling; class WPLoginUrlConverterFactory implements \IWPML_Frontend_Action_Loader, \IWPML_Backend_Action_Loader { /** * @return array */ public function create() { /** @var \WPML_URL_Converter $wpml_url_converter */ global $wpml_url_converter, $sitepress; $rules = new WPLoginUrlConverterRules(); return WPLoginUrlConverter::isEnabled() ? array_filter( [ $wpml_url_converter->get_wp_login_url_converter( $sitepress ), $rules ] ) : [ $rules ]; } } jobs-deadline/wpml-tm-jobs-deadline-cron-hooks.php 0000755 00000003404 14720342453 0016140 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Cron_Hooks implements IWPML_Action { const CHECK_OVERDUE_JOBS_EVENT = 'wpml-tm-check-overdue-jobs-event'; /** @var WPML_TM_Overdue_Jobs_Report_Factory $overdue_jobs_report_factory */ private $overdue_jobs_report_factory; /** @var TranslationManagement $notification_settings */ private $translation_management; public function __construct( WPML_TM_Overdue_Jobs_Report_Factory $overdue_jobs_report_factory, TranslationManagement $translation_management ) { $this->overdue_jobs_report_factory = $overdue_jobs_report_factory; $this->translation_management = $translation_management; } public function add_hooks() { if ( is_admin() ) { add_action( 'init', array( $this, 'schedule_event' ), $this->get_init_priority() ); } add_action( self::CHECK_OVERDUE_JOBS_EVENT, array( $this, 'send_overdue_email_report' ) ); } public function schedule_event() { if ( $this->is_notification_enabled() ) { if ( ! wp_next_scheduled( self::CHECK_OVERDUE_JOBS_EVENT ) ) { wp_schedule_event( time(), 'daily', self::CHECK_OVERDUE_JOBS_EVENT ); } } else { $timestamp = wp_next_scheduled( self::CHECK_OVERDUE_JOBS_EVENT ); if ( $timestamp ) { wp_unschedule_event( $timestamp, self::CHECK_OVERDUE_JOBS_EVENT ); } } } /** @return int */ private function get_init_priority() { return $this->translation_management->get_init_priority() + 1; } /** @return bool */ private function is_notification_enabled() { $settings = $this->translation_management->get_settings(); return ICL_TM_NOTIFICATION_NONE !== (int) $settings['notification']['overdue']; } public function send_overdue_email_report() { $overdue_jobs_report = $this->overdue_jobs_report_factory->create(); $overdue_jobs_report->send(); } } jobs-deadline/wpml-tm-jobs-deadline-estimate-ajax-action-factory.php 0000755 00000001056 14720342453 0021533 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Estimate_AJAX_Action_Factory extends WPML_AJAX_Base_Factory { /** @return null|WPML_TM_Jobs_Deadline_Estimate_AJAX_Action */ public function create() { $hooks = null; if ( $this->is_valid_action( 'wpml-tm-jobs-deadline-estimate-ajax-action' ) ) { $deadline_estimate_factory = new WPML_TM_Jobs_Deadline_Estimate_Factory(); $hooks = new WPML_TM_Jobs_Deadline_Estimate_AJAX_Action( $deadline_estimate_factory->create(), TranslationProxy_Basket::get_basket(), $_POST ); } return $hooks; } } jobs-deadline/wpml-tm-jobs-deadline-cron-hooks-factory.php 0000755 00000000473 14720342453 0017610 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Cron_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { public function create() { global $iclTranslationManagement; return new WPML_TM_Jobs_Deadline_Cron_Hooks( new WPML_TM_Overdue_Jobs_Report_Factory(), $iclTranslationManagement ); } } jobs-deadline/wpml-tm-jobs-deadline-estimate-factory.php 0000755 00000001343 14720342453 0017336 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Estimate_Factory { public function create() { $word_count_records_factory = new WPML_TM_Word_Count_Records_Factory(); $word_count_records = $word_count_records_factory->create(); $single_process_factory = new WPML_TM_Word_Count_Single_Process_Factory(); $single_process = $single_process_factory->create(); $st_package_factory = class_exists( 'WPML_ST_Package_Factory' ) ? new WPML_ST_Package_Factory() : null; $translatable_element_provider = new WPML_TM_Translatable_Element_Provider( $word_count_records, $single_process, $st_package_factory ); return new WPML_TM_Jobs_Deadline_Estimate( $translatable_element_provider, wpml_tm_get_jobs_repository() ); } } jobs-deadline/wpml-tm-jobs-deadline-estimate.php 0000755 00000006760 14720342453 0015701 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Estimate { const LATENCY_DAYS = 1; const WORDS_PER_DAY = 1200; /** @var WPML_TM_Translatable_Element_Provider */ private $translatable_element_provider; /** @var WPML_TM_Jobs_Repository */ private $jobs_repository; public function __construct( WPML_TM_Translatable_Element_Provider $translatable_element_provider, WPML_TM_Jobs_Repository $jobs_repository ) { $this->translatable_element_provider = $translatable_element_provider; $this->jobs_repository = $jobs_repository; } /** * @param array $basket * @param array $translator_options * * @return string */ public function get( array $basket, array $translator_options ) { $pending_jobs = $this->get_pending_jobs_for_translator( $translator_options ); $words_per_langs = $this->get_pending_words_per_langs( $pending_jobs ); $words_per_langs = $this->add_basket_words_per_langs( $basket, $words_per_langs ); $max_words_in_lang = 0; if ( $words_per_langs ) { $max_words_in_lang = max( $words_per_langs ); } $estimated_days = ceil( $max_words_in_lang / self::WORDS_PER_DAY ); $estimated_days += self::LATENCY_DAYS; $date = new DateTime(); $date->modify( '+' . $estimated_days . ' day' ); return $date->format( 'Y-m-d' ); } /** * @param array $translator_options * * @return WPML_TM_Jobs_Collection */ private function get_pending_jobs_for_translator( array $translator_options ) { $translator_id = $translator_options['translator_id']; $params = new WPML_TM_Jobs_Search_Params(); $params->set_status( array( ICL_TM_IN_PROGRESS, ICL_TM_WAITING_FOR_TRANSLATOR ) ); if ( false !== strpos( $translator_id, 'ts-' ) ) { $params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_REMOTE ); } else { $params->set_scope( WPML_TM_Jobs_Search_Params::SCOPE_LOCAL ); $params->set_translated_by( $translator_id ); } if ( array_key_exists( 'to', $translator_options ) ) { $params->set_target_language( $translator_options['to'] ); } return $this->jobs_repository->get( $params ); } /** * @param WPML_TM_Jobs_Collection $pending_jobs * * @return int[] */ private function get_pending_words_per_langs( WPML_TM_Jobs_Collection $pending_jobs ) { $words_per_langs = array(); foreach ( $pending_jobs as $pending_job ) { if ( ! isset( $words_per_langs[ $pending_job->get_target_language() ] ) ) { $words_per_langs[ $pending_job->get_target_language() ] = 0; } $translatable_element = $this->translatable_element_provider->get_from_job( $pending_job ); if ( $translatable_element ) { $words_per_langs[ $pending_job->get_target_language() ] += $translatable_element->get_words_count(); } } return $words_per_langs; } /** * @param array $basket * @param int[] $words_per_langs * * @return int[] */ private function add_basket_words_per_langs( array $basket, array $words_per_langs ) { $element_types = array( 'post', 'string', 'package' ); foreach ( $element_types as $element_type ) { if ( isset( $basket[ $element_type ] ) ) { foreach ( $basket[ $element_type ] as $element_id => $data ) { foreach ( $data['to_langs'] as $to_lang => $v ) { if ( ! isset( $words_per_langs[ $to_lang ] ) ) { $words_per_langs[ $to_lang ] = 0; } $translatable_element = $this->translatable_element_provider->get_from_type( $element_type, $element_id ); $words_per_langs[ $to_lang ] += $translatable_element->get_words_count(); } } } } return $words_per_langs; } } jobs-deadline/wpml-tm-jobs-deadline-estimate-ajax-action.php 0000755 00000003673 14720342453 0020075 0 ustar 00 <?php class WPML_TM_Jobs_Deadline_Estimate_AJAX_Action implements IWPML_Action { /** @var WPML_TM_Jobs_Deadline_Estimate $deadline_estimate */ private $deadline_estimate; /** @var array $translation_basket */ private $translation_basket; /** @var array $post_data */ private $post_data; public function __construct( WPML_TM_Jobs_Deadline_Estimate $deadline_estimate, array $translation_basket, array $post_data ) { $this->deadline_estimate = $deadline_estimate; $this->translation_basket = $translation_basket; $this->post_data = $post_data; } public function add_hooks() { add_action( 'wp_ajax_' . 'wpml-tm-jobs-deadline-estimate-ajax-action', array( $this, 'refresh' ) ); } public function refresh() { if ( isset( $this->post_data['translators'] ) ) { $deadline_per_lang = array(); foreach ( $this->post_data['translators'] as $lang_to => $translator_data ) { list( $translator_id, $service ) = $this->parse_translator_data( $translator_data ); $translator_args = array( 'translator_id' => $translator_id, 'service' => $service, 'to' => $lang_to, ); $deadline_per_lang[ $lang_to ] = $this->deadline_estimate->get( $this->translation_basket, $translator_args ); } $response_data = array( 'deadline' => max( $deadline_per_lang ), ); wp_send_json_success( $response_data ); } } /** * The translator data for a remote service will be like "ts-7" with 7 the ID of the remote service * For a local translator, the translation data will be the ID * * @param string $translator_data * * @return array */ private function parse_translator_data( $translator_data ) { $translator_id = $translator_data; $service = 'local'; if ( false !== strpos( $translator_data, 'ts-' ) ) { $translator_id = 0; $service = (int) preg_replace( '/^ts-/', '', $translator_data ); } return array( $translator_id, $service ); } } class-wpml-tm-translators-dropdown.php 0000755 00000012445 14720342453 0014176 0 ustar 00 <?php /** * Class WPML_TM_Translators_Dropdown */ class WPML_TM_Translators_Dropdown { /** * @var WPML_TM_Blog_Translators $blog_translators */ private $blog_translators; /** * @param WPML_TM_Blog_Translators $blog_translators */ public function __construct( $blog_translators ) { $this->blog_translators = $blog_translators; } /** * @param array $args * * @return string */ public function render( $args = array() ) { $dropdown = ''; /** @var $from string|false */ /** @var $to string|false */ /** @var $classes string|false */ /** @var $id string|false */ /** @var $name string|false */ /** @var $selected bool */ /** @var $echo bool */ /** @var $add_label bool */ /** @var $services array */ /** @var $disabled bool */ /** @var $default_name bool|string */ /** @var $local_only bool */ // set default value for variables $from = false; $to = false; $id = 'translator_id'; $name = 'translator_id'; $selected = 0; $echo = true; $add_label = false; $services = array( 'local' ); $disabled = false; $default_name = false; $local_only = false; extract( $args, EXTR_OVERWRITE ); $translators = array(); $id .= $from ? '_' . $from . ( $to ? '_' . $to : '' ) : ''; try { $translation_service = TranslationProxy::get_current_service(); $translation_service_id = TranslationProxy::get_current_service_id(); $translation_service_name = TranslationProxy::get_current_service_name(); $is_service_authenticated = TranslationProxy::is_service_authenticated(); // if translation service does not support translators choice, always shows first available if ( isset( $translation_service->id ) && ! TranslationProxy::translator_selection_available() && $is_service_authenticated ) { $translators[] = (object) array( 'ID' => TranslationProxy_Service::get_wpml_translator_id( $translation_service->id ), 'display_name' => __( 'First available', 'wpml-translation-management' ), 'service' => $translation_service_name, ); } elseif ( in_array( $translation_service_id, $services ) && $is_service_authenticated ) { $lang_status = TranslationProxy_Translator::get_language_pairs(); if ( empty( $lang_status ) ) { $lang_status = array(); } foreach ( (array) $lang_status as $language_pair ) { if ( $from && $from != $language_pair['from'] ) { continue; } if ( $to && $to != $language_pair['to'] ) { continue; } if ( ! empty( $language_pair['translators'] ) ) { if ( 1 < count( $language_pair['translators'] ) ) { $translators[] = (object) array( 'ID' => TranslationProxy_Service::get_wpml_translator_id( $translation_service->id ), 'display_name' => __( 'First available', 'wpml-translation-management' ), 'service' => $translation_service_name, ); } foreach ( $language_pair['translators'] as $tr ) { if ( ! isset( $_icl_translators[ $tr['id'] ] ) ) { $translators[] = $_icl_translators[ $tr['id'] ] = (object) array( 'ID' => TranslationProxy_Service::get_wpml_translator_id( $translation_service->id, $tr['id'] ), 'display_name' => $tr['nickname'], 'service' => $translation_service_name, ); } } } } } if ( in_array( 'local', $services ) ) { $translators[] = (object) array( 'ID' => 0, 'display_name' => __( 'First available', 'wpml-translation-management' ), ); $translators = array_merge( $translators, $this->blog_translators->get_blog_translators( array( 'from' => $from, 'to' => $to, ) ) ); } $translators = apply_filters( 'wpml_tm_translators_list', $translators ); $dropdown .= '<select id="' . esc_attr( $id ) . '" class="js-wpml-translator-dropdown" data-lang-to="' . esc_attr( (string) $to ) . '" name="' . esc_attr( $name ) . '" ' . ( $disabled ? 'disabled="disabled"' : '' ) . '>'; if ( $default_name ) { $dropdown_selected = selected( $selected, false, false ); $dropdown .= '<option value="" ' . $dropdown_selected . '>'; $dropdown .= esc_html( $default_name ); $dropdown .= '</option>'; } foreach ( $translators as $t ) { if ( $local_only && isset( $t->service ) ) { continue; } $current_translator = $t->ID; $dropdown_selected = selected( $selected, $current_translator, false ); $dropdown .= '<option value="' . $current_translator . '" ' . $dropdown_selected . '>'; $dropdown .= isset( $t->service ) ? $t->service : esc_html( $t->display_name ); $dropdown .= '</option>'; } $dropdown .= '</select>'; } catch ( WPMLTranslationProxyApiException $ex ) { $dropdown .= esc_html__( 'Translation Proxy error', 'wpml-translation-management' ) . ': ' . $ex->getMessage(); } catch ( Exception $ex ) { $dropdown .= esc_html__( 'Error', 'wpml-translation-management' ) . ': ' . $ex->getMessage(); } if ( $add_label ) { $dropdown = '<label for="' . esc_attr( $id ) . '">' . esc_html__( 'Translation jobs for:', 'wpml-translation-management' ) . '</label> ' . $dropdown; } if ( $echo ) { echo $dropdown; } return $dropdown; } } ICL-20-migration/ui/class-wpml-tm-icl20-migration-locks.php 0000755 00000000544 14720342453 0017230 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Locks { private $progress; public function __construct( WPML_TM_ICL20_Migration_Progress $progress ) { $this->progress = $progress; } public function add_hooks() { if ( ! $this->progress->is_migration_done() ) { add_filter( 'wpml_tm_lock_ui', '__return_true' ); } } } ICL-20-migration/ui/class-wpml-tm-icl20-migration-support.php 0000755 00000006522 14720342453 0017633 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Support { const PREFIX = 'icl20-migration-reset-'; const TIMESTAMP_FORMAT = 'Y-m-d H:i:s'; private $can_rollback; private $progress; private $template_service; function __construct( IWPML_Template_Service $template_service, WPML_TM_ICL20_Migration_Progress $progress, $can_rollback = false ) { $this->template_service = $template_service; $this->progress = $progress; $this->can_rollback = $can_rollback; } function add_hooks() { add_action( 'wpml_support_page_after', array( $this, 'show' ) ); } public function parse_request() { $nonce_arg = self::PREFIX . 'nonce'; $action_arg = self::PREFIX . 'action'; if ( array_key_exists( $nonce_arg, $_GET ) && array_key_exists( $action_arg, $_GET ) ) { $nonce = filter_var( $_GET[ $nonce_arg ] ); $action = filter_var( $_GET[ $action_arg ] ); if ( $nonce && $action && wp_verify_nonce( $nonce, $action ) ) { if ( self::PREFIX . 'reset' === $action ) { update_option( WPML_TM_ICL20_Migration_Progress::OPTION_KEY_MIGRATION_LOCKED, false, false ); } if ( self::PREFIX . 'rollback' === $action && $this->can_rollback ) { do_action( 'wpml_tm_icl20_migration_rollback' ); } } $redirect_to = remove_query_arg( array( $nonce_arg, $action_arg ) ); wp_safe_redirect( $redirect_to, 302, 'WPML' ); } } public function show() { $model = $this->get_model(); echo $this->template_service->show( $model, 'main.twig' ); } private function get_model() { $reset_target_url = add_query_arg( array( self::PREFIX . 'nonce' => wp_create_nonce( self::PREFIX . 'reset' ), self::PREFIX . 'action' => self::PREFIX . 'reset', ) ); $reset_target_url .= '#icl20-migration'; $model = array( 'title' => __( 'Migration to ICL 2.0', 'wpml-translation-management' ), 'data' => array( __( 'Number of attempts made', 'wpml-translation-management' ) => $this->progress->get_current_attempts_count(), __( 'Last attempt was on', 'wpml-translation-management' ) => date( self::TIMESTAMP_FORMAT, $this->progress->get_last_attempt_timestamp() ), __( 'Last error', 'wpml-translation-management' ) => $this->progress->get_last_migration_error(), ), 'buttons' => array( 'reset' => array( 'type' => 'primary', 'label' => __( 'Try again', 'wpml-translation-management' ), 'url' => $reset_target_url, ), ), ); if ( $this->can_rollback ) { $rollback_target_url = add_query_arg( array( self::PREFIX . 'nonce' => wp_create_nonce( self::PREFIX . 'rollback' ), self::PREFIX . 'action' => self::PREFIX . 'rollback', ) ); $model['buttons']['rollback'] = array( 'type' => 'secondary', 'label' => __( 'Rollback migration (use at your own risk!)', 'wpml-translation-management' ), 'url' => $rollback_target_url, ); } return $model; } } ICL-20-migration/ui/class-wpml-tm-icl20-migration-notices.php 0000755 00000020255 14720342453 0017562 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Notices { const NOTICE_GROUP = 'icl-20-migration'; const NOTICE_MIGRATION_REQUIRED_ID = 'icl-20-migration'; const NOTICE_MIGRATION_COMPLETED_ID = 'icl-20-migration-completed'; /** * @var WPML_Notices */ private $notices; /** * @var WPML_TM_ICL20_Migration_Progress */ private $progress; /** * WPML_TM_ICL20_Migration_Notices constructor. * * @param WPML_TM_ICL20_Migration_Progress $progress * @param WPML_Notices $notices */ public function __construct( WPML_TM_ICL20_Migration_Progress $progress, WPML_Notices $notices ) { $this->progress = $progress; $this->notices = $notices; } /** * @param bool $requires_migration */ public function run( $requires_migration = false ) { $this->clear_migration_required(); $ever_started = $this->progress->has_migration_ever_started(); if ( $ever_started && ! $this->progress->requires_migration() ) { $this->build_migration_completed(); } else { if ( ! $ever_started && $requires_migration && ! $this->progress->is_migration_incomplete() ) { $this->build_migration_required(); add_action( 'wpml-notices-scripts-enqueued', array( $this, 'admin_enqueue_scripts' ) ); return; } if ( $ever_started && ! $this->progress->is_migration_done() ) { $this->build_migration_failed(); return; } } } /** * Clear all notices created before and during the migration */ public function clear_migration_required() { $this->notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_MIGRATION_REQUIRED_ID ); } /** * Builds the notice shown if the migration fails */ private function build_migration_failed() { $message = array(); $actions = array(); $title = '<h3>' . esc_html__( 'ICanLocalize migration could not complete', 'wpml-translation-management' ) . '</h3>'; $message[] = esc_html__( 'WPML needs to update your connection to ICanLocalize, but could not complete the change.', 'wpml-translation-management' ); $locked = $this->progress->are_next_automatic_attempts_locked(); if ( $locked ) { $message[] = esc_html__( 'Please contact WPML support and give them the following debug information:', 'wpml-translation-management' ); $error = '<pre>'; foreach ( $this->progress->get_steps() as $step ) { $completed_step = $this->progress->get_completed_step( $step ); if ( ! $completed_step || WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $completed_step ) { $error .= esc_html( sprintf( __( 'Failed step: %s', 'wpml-translation-management' ), $step ) ) . PHP_EOL; break; } } $error .= esc_html( $this->progress->get_last_migration_error() ); $error .= '</pre>'; $message[] = $error; } else { $message[] = esc_html__( 'Please wait a few minutes and try again to see if there’s a temporary problem.', 'wpml-translation-management' ); } $text = $title . '<p>' . implode( '</p><p>', $message ) . '</p>'; $button_label = __( 'Try again', 'wpml-translation-management' ); $button_url = add_query_arg( array( WPML_TM_ICL20_Migration_Support::PREFIX . 'nonce' => wp_create_nonce( WPML_TM_ICL20_Migration_Support::PREFIX . 'reset' ), WPML_TM_ICL20_Migration_Support::PREFIX . 'action' => WPML_TM_ICL20_Migration_Support::PREFIX . 'reset', ) ) . '#icl20-migration'; $retry_action = $this->notices->get_new_notice_action( $button_label, $button_url, false, false, true ); $actions[] = $retry_action; if ( $locked ) { $wpml_support_action = $this->notices->get_new_notice_action( __( 'WPML Support', 'wpml-translation-management' ), 'https://wpml.org/forums/' ); $wpml_support_action->set_link_target( '_blank' ); $actions[] = $wpml_support_action; } else { $support_url = add_query_arg( array( 'page' => WPML_PLUGIN_FOLDER . '/menu/support.php', ), admin_url( 'admin.php' ) ) . '#icl20-migration'; $action = $this->notices->get_new_notice_action( __( 'Error details', 'wpml-translation-management' ), $support_url ); $actions[] = $action; } $this->create_notice( $text, $actions ); } /** * Builds the notice shown when the migration is required */ private function build_migration_required() { $message = array(); $actions = array(); $link_pattern = '<a href="%1$s" target="_blank">%2$s</a>'; $ask_us_link = sprintf( $link_pattern, 'https://wpml.org/forums/topic/im-not-sure-if-i-need-to-run-icanlocalize-migration/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmltm', esc_html__( 'Ask us', 'wpml-translation-management' ) ); $button_label = esc_html__( 'Start the update', 'wpml-translation-management' ); $title = '<h3>WPML needs to update your ICanLocalize account settings</h3>'; $message[] = esc_html__( 'WPML 3.9 changes the way it works with ICanLocalize. This requires WPML to move to a new interface with ICanLocalize.', 'wpml-translation-management' ); $message[] = esc_html__( 'If you are in a production site, you have to run this update before you can send more content for translation and receive completed translations.', 'wpml-translation-management' ); $message[] = esc_html__( "If this is not your production site (it's a staging or testing site), please do not run the update.", 'wpml-translation-management' ); $message[] = esc_html__( 'Running this update on a non-production site will make it impossible to correctly run it on the production site.', 'wpml-translation-management' ); $message[] = ''; $message[] = sprintf( esc_html__( 'Not sure? %s.', 'wpml-translation-management' ), $ask_us_link ); $message[] = ''; $message[] = $this->get_user_confirmation_input(); $text = $title . '<p>' . implode( '</p><p>', $message ) . '</p>'; $actions[] = $this->notices->get_new_notice_action( $button_label, '#', false, false, true ); $this->create_notice( $text, $actions ); } /** * @param $text * @param array $actions */ private function create_notice( $text, array $actions = array() ) { $notice = $this->notices->create_notice( self::NOTICE_MIGRATION_REQUIRED_ID, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( array( 'error' ) ); $notice->set_exclude_from_pages( array( 'sitepress-multilingual-cms/menu/support.php', ) ); foreach ( $actions as $action ) { $notice->add_action( $action ); } $this->notices->add_notice( $notice ); } /** * Builds the notice shown when the migration completes */ private function build_migration_completed() { $message = array(); $message[] = esc_html__( 'WPML updated your connection to ICanLocalize. You can continue sending content to translation.', 'wpml-translation-management' ); $text = '<p>' . implode( '</p><p>', $message ) . '</p>'; $notice = $this->notices->create_notice( self::NOTICE_MIGRATION_COMPLETED_ID, $text, self::NOTICE_GROUP ); $notice->set_css_class_types( array( 'warning' ) ); $notice->set_exclude_from_pages( array( 'sitepress-multilingual-cms/menu/support.php', ) ); $notice->set_dismissible( true ); $this->notices->add_notice( $notice ); } /** * Required by `\WPML_TM_ICL20_Migration_Notices::build_migration_required` */ public function admin_enqueue_scripts() { wp_enqueue_script( 'wpml-icl20-migrate-confirm', WPML_TM_URL . '/classes/ICL-20-migration/res/migration-required.js' ); } /** * @return string */ private function get_user_confirmation_input() { $id = 'wpml-icl20-migrate-confirm'; $label = esc_html__( 'This is indeed my production site', 'wpml-translation-management' ); $data_action = esc_attr( WPML_TM_ICL20_Migration_Support::PREFIX . 'user_confirm' ); $data_nonce = esc_attr( wp_create_nonce( WPML_TM_ICL20_Migration_Support::PREFIX . 'user_confirm' ) ); $html_pattern = '<input type="checkbox" value="1" id="%1$s" data-action="%2$s" data-nonce="%3$s"><label for="%1$s">%4$s</label>'; return sprintf( $html_pattern, $id, $data_action, $data_nonce, $label ); } } ICL-20-migration/class-wpml-tm-icl20-migration-status.php 0000755 00000006736 14720342453 0017034 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Status { const ICL_20_TS_ID = 67; const ICL_LEGACY_TS_ID = 4; const ICL_LEGACY_TS_SUID = '6ab1000a33e2cc9ecbcf6abc57254be8'; const ICL_20_TS_SUID = 'dd17d48516ca4bce0b83043583fabd2e'; private $installer_settings = array(); private $service; public function __construct( $service ) { $this->service = $service; } public function has_active_legacy_icl() { return $this->has_active_service() && $this->get_ICL_LEGACY_TS_ID() === $this->service->id; } public function has_active_icl_20() { return $this->has_active_service() && $this->get_ICL_20_TS_ID() === $this->service->id; } public function has_active_service() { return (bool) $this->service; } public function get_ICL_LEGACY_TS_ID() { if ( defined( 'WPML_TP_ICL_LEGACY_TS_ID' ) ) { return WPML_TP_ICL_LEGACY_TS_ID; } return self::ICL_LEGACY_TS_ID; } public function get_ICL_20_TS_ID() { if ( defined( 'WPML_TP_ICL_20_TS_ID' ) ) { return WPML_TP_ICL_20_TS_ID; } return self::ICL_20_TS_ID; } public function get_ICL_LEGACY_TS_SUID() { if ( defined( 'WPML_TP_ICL_LEGACY_TS_SUID' ) ) { return WPML_TP_ICL_LEGACY_TS_SUID; } return self::ICL_LEGACY_TS_SUID; } public function get_ICL_20_TS_SUID() { if ( defined( 'WPML_TP_ICL_20_TS_SUID' ) ) { return WPML_TP_ICL_20_TS_SUID; } return self::ICL_20_TS_SUID; } public function is_preferred_service_legacy_ICL() { return $this->has_preferred_service() && self::ICL_LEGACY_TS_SUID === $this->get_preferred_service(); } public function set_preferred_service_to_ICL20() { if ( $this->get_preferred_service() ) { $this->installer_settings['repositories']['wpml']['ts_info']['preferred'] = $this->get_ICL_20_TS_SUID(); $this->update_installer_settings(); } } private function has_preferred_service() { return ! in_array( $this->get_preferred_service(), array( 'clear', false ), true ); } private function get_preferred_service() { $installer_settings = $this->get_installer_settings(); if ( isset( $installer_settings['repositories']['wpml']['ts_info']['preferred'] ) && 'clear' !== $installer_settings['repositories']['wpml']['ts_info']['preferred'] ) { return $installer_settings['repositories']['wpml']['ts_info']['preferred']; } return false; } /** * @return array */ private function get_installer_settings() { if ( ! $this->installer_settings ) { $raw_settings = get_option( 'wp_installer_settings', null ); if ( $raw_settings ) { if ( is_array( $raw_settings ) ) { // backward compatibility 1.1 $this->installer_settings = $raw_settings; } else { $has_gz_support = function_exists( 'gzuncompress' ) && function_exists( 'gzcompress' ); $raw_settings = base64_decode( $raw_settings ); if ( $has_gz_support ) { $raw_settings = gzuncompress( $raw_settings ); } if ( $raw_settings ) { /** @noinspection UnserializeExploitsInspection */ $this->installer_settings = unserialize( $raw_settings ); } } } } return $this->installer_settings; } private function update_installer_settings() { $has_gz_support = function_exists( 'gzuncompress' ) && function_exists( 'gzcompress' ); $raw_settings = serialize( $this->installer_settings ); if ( $has_gz_support ) { $raw_settings = gzcompress( $raw_settings ); } if ( $raw_settings ) { $raw_settings = base64_encode( $raw_settings ); update_option( 'wp_installer_settings', $raw_settings ); } } } ICL-20-migration/res/migration-required.js 0000755 00000002064 14720342453 0014330 0 ustar 00 /*globals jQuery, ajaxurl */ jQuery(function () { 'use strict'; var notice = jQuery('[data-id="icl-20-migration"][data-group="icl-20-migration"]'); var confirm; var startButton; var updateButton = function () { if (confirm.prop('checked')) { startButton.removeClass('disabled'); startButton.on('click', userConfirms); } else { startButton.addClass('disabled'); startButton.off('click'); } }; var userConfirms = function (e) { e.preventDefault(); e.stopPropagation(); if (confirm.prop('checked')) { jQuery.ajax(ajaxurl, { method: 'POST', data: { action: confirm.data('action'), nonce: confirm.data('nonce') }, complete: function () { location.reload(); } }); } }; if (notice.length) { confirm = notice.find('#wpml-icl20-migrate-confirm'); startButton = notice.find('.notice-action.button-secondary.notice-action-button-secondary'); if (confirm.length && startButton.length) { updateButton(); confirm.on('click', updateButton); } } }); ICL-20-migration/class-wpml-tm-icl20-migration-exception.php 0000755 00000000175 14720342453 0017476 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20MigrationException extends WPMLTranslationProxyApiException { } ICL-20-migration/class-wpml-tm-icl20-migration-loader.php 0000755 00000005510 14720342453 0016744 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Loader { /** @var WPML_TM_ICL20_Migration_Factory */ private $factory; /** @var WPML_TM_ICL20_Migration_Progress */ private $progress; /** @var WPML_TM_ICL20_Migration_Status */ private $status; /** @var WPML_WP_API */ private $wp_api; /** * WPML_TM_ICL20_Migration_Loader constructor. * * @param WPML_WP_API $wp_api * @param WPML_TM_ICL20_Migration_Factory $factory */ public function __construct( WPML_WP_API $wp_api, WPML_TM_ICL20_Migration_Factory $factory ) { $this->wp_api = $wp_api; $this->factory = $factory; } /** * This is the main method which deals with the whole logic for handling the migration */ public function run() { $this->status = $this->factory->create_status(); $this->progress = $this->factory->create_progress(); $requires_migration = $this->requires_migration(); $notices = $this->factory->create_notices(); $notices->clear_migration_required(); if ( $this->status->has_active_service() ) { $notices->run( $requires_migration ); } if ( $requires_migration ) { if ( ! $this->progress->get_user_confirmed() ) { add_action( 'wp_ajax_' . WPML_TM_ICL20_Migration_Support::PREFIX . 'user_confirm', array( $this->factory->create_ajax(), 'user_confirmation' ) ); return; } if ( $this->is_back_end() ) { $this->maybe_fix_preferred_service(); $migration = $this->factory->create_migration(); if ( $this->factory->can_rollback() ) { add_action( 'wpml_tm_icl20_migration_rollback', array( $migration, 'migrate_project_rollback' ) ); } $support = $this->factory->create_ui_support(); $support->add_hooks(); $support->parse_request(); if ( ! $this->progress->are_next_automatic_attempts_locked() ) { $notices->run( ! $migration->run() ); } if ( ! $this->progress->is_migration_done() ) { $locks = $this->factory->create_locks(); $locks->add_hooks(); } } } } /** @return bool */ private function is_back_end() { return $this->wp_api->is_admin() && ! $this->wp_api->is_ajax() && ! $this->wp_api->is_cron_job() && ! $this->wp_api->is_heartbeat(); } /** @return bool */ private function requires_migration() { if ( $this->status->has_active_legacy_icl() ) { return true; } if ( $this->status->has_active_icl_20() ) { if ( $this->progress->is_migration_incomplete() ) { return true; } $this->progress->set_migration_done(); } return false; } /** * If the website is set to use a preferred translation service which is the legacy ICL, it will replace it with * ICL2.0 */ private function maybe_fix_preferred_service() { if ( $this->status->is_preferred_service_legacy_ICL() ) { $this->status->set_preferred_service_to_ICL20(); } } } ICL-20-migration/class-wpml-tm-icl20-migration-progress.php 0000755 00000015567 14720342453 0017357 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Progress { const MAX_AUTOMATIC_ATTEMPTS = 5; const OPTION_KEY_USER_CONFIRMED = '_wpml_icl20_migration_user_confirmed'; const OPTION_KEY_MIGRATION_ATTEMPTS = '_wpml_icl20_migration_attempts'; const OPTION_KEY_MIGRATION_LAST_ATTEMPT = '_wpml_icl20_migration_last_attempt'; const OPTION_KEY_MIGRATION_LAST_ERROR = '_wpml_icl20_migration_last_error'; const OPTION_KEY_MIGRATION_LOCAL_PROJECT_INDEX = '_wpml_icl20_migration_local_project_index'; const OPTION_KEY_MIGRATION_LOCKED = '_wpml_icl20_migration_locked'; const OPTION_KEY_MIGRATION_REQUIRED = '_wpml_icl20_migration_required'; const OPTION_KEY_MIGRATION_STEPS = '_wpml_icl20_migration_step_%s'; const STEP_ICL_ACK = 'icl_ack'; const STEP_MIGRATE_JOBS_DOCUMENTS = 'migrate_jobs_doc'; const STEP_MIGRATE_JOBS_STRINGS = 'migrate_jobs_strings'; const STEP_MIGRATE_LOCAL_PROJECT = 'migrate_local_project'; const STEP_MIGRATE_LOCAL_SERVICE = 'migrate_local_service'; const STEP_MIGRATE_REMOTE_PROJECT = 'migrate_remote_project'; const STEP_TOKEN = 'token'; const STEP_FAILED = 'failed'; const STEP_DONE = 'done'; const VALUE_YES = 'yes'; const VALUE_NO = 'no'; /** * @var array */ private $steps; /** * WPML_TM_ICL20_Migration_Progress constructor. */ public function __construct() { $this->steps = array( self::STEP_TOKEN, self::STEP_MIGRATE_REMOTE_PROJECT, self::STEP_ICL_ACK, self::STEP_MIGRATE_LOCAL_SERVICE, self::STEP_MIGRATE_LOCAL_PROJECT, self::STEP_MIGRATE_JOBS_DOCUMENTS, self::STEP_MIGRATE_JOBS_STRINGS, ); } /** * @param string $step * * @return string|null */ public function get_completed_step( $step ) { return get_option( sprintf( self::OPTION_KEY_MIGRATION_STEPS, $step ), self::STEP_FAILED ); } /** * @return int|null */ public function get_last_attempt_timestamp() { return get_option( self::OPTION_KEY_MIGRATION_LAST_ATTEMPT, null ); } /** * @return string|null */ public function get_last_migration_error() { return get_option( self::OPTION_KEY_MIGRATION_LAST_ERROR, null ); } /** * @return string|null */ public function get_project_to_migrate() { return get_option( self::OPTION_KEY_MIGRATION_LOCAL_PROJECT_INDEX, null ); } /** * @return array */ public function get_steps() { return $this->steps; } /** * @param string $message */ public function log_failed_attempt( $message ) { $this->update_last_error( $message ); } /** * @param string $message */ private function update_last_error( $message ) { update_option( self::OPTION_KEY_MIGRATION_LAST_ERROR, $message, false ); } /** * @return bool */ public function is_migration_incomplete() { return $this->get_current_attempts_count() > 0; } /** * @param string $step * @param string|bool $value */ public function set_completed_step( $step, $value ) { $step_value = $value; if ( is_bool( $value ) ) { $step_value = $value ? self::STEP_DONE : self::STEP_FAILED; } update_option( sprintf( self::OPTION_KEY_MIGRATION_STEPS, $step ), $step_value, false ); } /** * @return bool */ public function has_migration_ever_started() { $this->requires_migration(); $notoptions = wp_cache_get( 'notoptions', 'options' ); if ( isset( $notoptions[ self::OPTION_KEY_MIGRATION_REQUIRED ] ) ) { return false; } return true; } /** * It will mark the migration as done */ public function set_migration_done() { update_option( self::OPTION_KEY_MIGRATION_REQUIRED, self::VALUE_NO, false ); $this->clear_temporary_options(); } /** * It will remove all the temporary options used to store the status of the migration */ public function clear_temporary_options() { $options = array( self::OPTION_KEY_MIGRATION_LOCKED, self::OPTION_KEY_MIGRATION_LAST_ERROR, self::OPTION_KEY_MIGRATION_ATTEMPTS, self::OPTION_KEY_MIGRATION_LAST_ATTEMPT, self::OPTION_KEY_MIGRATION_LOCAL_PROJECT_INDEX, ); foreach ( $options as $option ) { delete_option( $option ); } foreach ( $this->steps as $step ) { delete_option( sprintf( self::OPTION_KEY_MIGRATION_STEPS, $step ) ); } } /** * @return bool */ public function is_migration_done() { $result = true; if ( $this->requires_migration() ) { foreach ( $this->steps as $step ) { if ( self::STEP_DONE !== get_option( sprintf( self::OPTION_KEY_MIGRATION_STEPS, $step ), self::STEP_FAILED ) ) { $result = false; break; } } } return $result; } /** * @return bool */ public function requires_migration() { return self::VALUE_YES === get_option( self::OPTION_KEY_MIGRATION_REQUIRED, self::VALUE_NO ); } /** * Sets the migration as started so to know, in the next attempts, if the migration was partial or never started. */ public function set_migration_started() { update_option( self::OPTION_KEY_MIGRATION_REQUIRED, self::VALUE_YES, false ); $this->increase_attempts_count(); } /** * It will increases on every migration attempt */ private function increase_attempts_count() { update_option( self::OPTION_KEY_MIGRATION_ATTEMPTS, $this->get_current_attempts_count() + 1, false ); update_option( self::OPTION_KEY_MIGRATION_LAST_ATTEMPT, time(), false ); if ( $this->has_too_many_automatic_attempts() ) { $this->block_next_automatic_attempts(); } } /** * @return int */ public function get_current_attempts_count() { return (int) get_option( self::OPTION_KEY_MIGRATION_ATTEMPTS, 0 ); } /** * @return bool */ public function has_too_many_automatic_attempts() { return $this->get_current_attempts_count() >= self::MAX_AUTOMATIC_ATTEMPTS || $this->are_next_automatic_attempts_locked(); } /** * Used when too many attempts are made */ private function block_next_automatic_attempts() { update_option( self::OPTION_KEY_MIGRATION_LOCKED, self::VALUE_YES, false ); } /** * @return bool */ public function are_next_automatic_attempts_locked() { return self::VALUE_YES === get_option( self::OPTION_KEY_MIGRATION_LOCKED, self::VALUE_NO ); } /** * @param string $old_index */ public function set_project_to_migrate( $old_index ) { update_option( self::OPTION_KEY_MIGRATION_LOCAL_PROJECT_INDEX, $old_index, false ); } /** * @return bool */ public function get_user_confirmed() { return self::VALUE_YES === get_option( self::OPTION_KEY_USER_CONFIRMED, self::VALUE_NO ); } /** * User as an opt-in action from the user before starting the migration */ public function set_user_confirmed() { update_option( self::OPTION_KEY_USER_CONFIRMED, self::VALUE_YES, false ); } } ICL-20-migration/class-wpml-tm-icl20-migrate-local.php 0000755 00000012105 14720342453 0016225 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migrate_Local { const JOBS_TYPES_DOCUMENTS = 'icl_translation_status'; const JOBS_TYPES_STRINGS = 'icl_string_translations'; private $progress; private $sitepress; private $status; /** @var WPML_TP_Services */ private $tp_services; /** * WPML_TM_ICL20 constructor. * * @param WPML_TP_Services $tp_services * @param WPML_TM_ICL20_Migration_Status $status * @param WPML_TM_ICL20_Migration_Progress $progress * @param SitePress $sitepress * * @internal param SitePress $sitepress */ public function __construct( WPML_TP_Services $tp_services, WPML_TM_ICL20_Migration_Status $status, WPML_TM_ICL20_Migration_Progress $progress, SitePress $sitepress ) { $this->tp_services = $tp_services; $this->status = $status; $this->progress = $progress; $this->sitepress = $sitepress; } public function migrate_jobs( $table ) { $result = false; $current_service = $this->tp_services->get_current_service(); if ( $this->status->get_ICL_20_TS_ID() === $current_service->id ) { $step = null; if ( self::JOBS_TYPES_DOCUMENTS === $table ) { $step = WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_JOBS_DOCUMENTS; } if ( self::JOBS_TYPES_STRINGS === $table ) { $step = WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_JOBS_STRINGS; } if ( null !== $step ) { $update = $this->update_table( $table ); $result = false !== $update; $this->progress->set_completed_step( $step, $result ); } else { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . 'Wrong "' . $table . '"' ); } } return $result; } /** * @param $table * * @return false|int */ private function update_table( $table ) { $wpdb = $this->sitepress->get_wpdb(); $update = $wpdb->update( $wpdb->prefix . $table, array( 'translator_id' => 0, 'translation_service' => $this->status->get_ICL_20_TS_ID(), ), array( 'translation_service' => $this->status->get_ICL_LEGACY_TS_ID(), ), array( '%d', '%d' ), array( '%d' ) ); if ( false === $update ) { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $wpdb->last_error ); } return $update; } public function migrate_project() { $old_index = $this->progress->get_project_to_migrate(); // icl_translation_projects $migrated = false; if ( $old_index ) { $current_service = $this->tp_services->get_current_service(); if ( $current_service && null !== $current_service->id ) { $new_index = md5( $current_service->id . serialize( $current_service->custom_fields_data ) ); $migrated = $this->update_project_index( $old_index, $new_index ); } } $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_LOCAL_PROJECT, $migrated ); return $migrated; } private function update_project_index( $old_service_index, $new_service_index ) { $updated = false; $projects = $this->sitepress->get_setting( 'icl_translation_projects', null ); $old_index_exists = array_key_exists( $old_service_index, $projects ); $new_index_does_not_exists = ! array_key_exists( $new_service_index, $projects ); if ( $projects && $old_index_exists && $new_index_does_not_exists ) { $project = $projects[ $old_service_index ]; $projects[ $new_service_index ] = $project; unset( $projects[ $old_service_index ] ); $this->sitepress->set_setting( 'icl_translation_projects', $projects, true ); $updated = true; } if ( false === $updated ) { $error = ''; if ( ! $old_index_exists ) { $error = 'The old project does not exists'; } if ( ! $new_index_does_not_exists ) { $error = 'The new project index already exists'; } $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $error ); } return $updated; } public function migrate_service( $new_token ) { $current_service = $this->tp_services->get_current_service(); $migrated = false; if ( $current_service ) { $old_index = md5( $current_service->id . serialize( $current_service->custom_fields_data ) ); $this->progress->set_project_to_migrate( $old_index ); $icl20_service_id = $this->status->get_ICL_20_TS_ID(); $this->tp_services->select_service( $icl20_service_id, array( 'api_token' => $new_token, ) ); $active_service = $this->tp_services->get_current_service(); $migrated = $active_service->id === $icl20_service_id; } $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_LOCAL_SERVICE, $migrated ); return $migrated; } public function rollback_service() { $current_service = $this->tp_services->get_current_service(); $rolled_back = false; if ( $current_service ) { $this->tp_services->select_service( $this->status->get_ICL_LEGACY_TS_ID() ); $active_service = $this->tp_services->get_current_service(); $rolled_back = $active_service->id === $this->status->get_ICL_LEGACY_TS_ID(); } $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_LOCAL_SERVICE, false ); return $rolled_back; } } ICL-20-migration/class-wpml-tm-icl20-migrate-remote.php 0000755 00000006556 14720342453 0016443 0 ustar 00 <?php class WPML_TM_ICL20_Migrate_Remote { private $container; private $progress; /** * WPML_TM_ICL20 constructor. * * @param WPML_TM_ICL20_Migration_Progress $progress * @param WPML_TM_ICL20_Migration_Container $container */ public function __construct( WPML_TM_ICL20_Migration_Progress $progress, WPML_TM_ICL20_Migration_Container $container ) { $this->progress = $progress; $this->container = $container; } /** * @param string $ts_accesskey * @param int $ts_id * * @return bool * * Note: `ts_id` (aka `website_id`) = `website_id` * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/icldev-2322 */ public function acknowledge_icl( $ts_id, $ts_accesskey ) { $result = false; try { $result = $this->container->get_acknowledge()->acknowledge_icl( $ts_id, $ts_accesskey ); } catch ( WPML_TM_ICL20MigrationException $ex ) { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $ex->getCode() . ': ' . $ex->getMessage() ); } if ( $result ) { $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_ICL_ACK, true ); return $result; } $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_ICL_ACK, false ); return false; } /** * @param string $ts_accesskey * @param int $ts_id * * @return string|null * * Note: `ts_id` (aka `website_id`) = `website_id` * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/icldev-2285 */ public function get_token( $ts_id, $ts_accesskey ) { $token = null; try { $token = $this->container->get_token()->get_token( $ts_id, $ts_accesskey ); } catch ( WPML_TM_ICL20MigrationException $ex ) { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $ex->getCode() . ': ' . $ex->getMessage() ); } if ( null !== $token ) { $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_TOKEN, $token ); return $token; } $this->progress->set_completed_step( 'token', false ); return null; } /** * @param int $project_id * @param string $access_key * @param string $new_token * * @return bool|null * @link https://onthegosystems.myjetbrains.com/youtrack/issue/tsapi-887 * */ public function migrate_project( $project_id, $access_key, $new_token ) { $migrate = null; try { $migrate = $this->container->get_project()->migrate( $project_id, $access_key, $new_token ); } catch ( WPML_TM_ICL20MigrationException $ex ) { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $ex->getCode() . ': ' . $ex->getMessage() ); } if ( $migrate ) { $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_REMOTE_PROJECT, true ); return $migrate; } $this->progress->set_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_REMOTE_PROJECT, false ); return false; } /** * @param int $project_id * @param string $access_key * * @return bool * @link https://onthegosystems.myjetbrains.com/youtrack/issue/tsapi-887 * */ public function migrate_project_rollback( $project_id, $access_key ) { $result = false; try { $result = $this->container->get_project()->rollback_migration( $project_id, $access_key ); } catch ( WPML_TM_ICL20MigrationException $ex ) { $this->progress->log_failed_attempt( __METHOD__ . ' - ' . $ex->getCode() . ': ' . $ex->getMessage() ); } return $result; } } ICL-20-migration/remote/class-wpml-tm-icl20-project-migration.php 0000755 00000005114 14720342453 0020437 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Project { private $end_point; private $http; /** * WPML_TM_ICL20 constructor. * * @param WP_Http $http * @param string $end_point */ public function __construct( WP_Http $http, $end_point ) { $this->http = $http; $this->end_point = $end_point; } /** * @param int $project_id * @param string $access_key * @param string $new_token * * @return bool|null * @throws \WPML_TM_ICL20MigrationException * @link https://onthegosystems.myjetbrains.com/youtrack/issue/tsapi-887 * */ public function migrate( $project_id, $access_key, $new_token ) { $url = $this->end_point . '/projects/' . $project_id . '/migrate_service.json'; $args = array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'accesskey' => $access_key, 'custom_fields' => array( 'api_token' => $new_token, ) ) ) ); $response = $this->http->post( $url, $args ); $code = (int) $response['response']['code']; if ( $code !== 200 ) { $message = $response['response']['message']; if ( isset( $response['body'] ) ) { $body = json_decode( $response['body'], true ); if ( isset( $body['status']['message'] ) ) { $message .= PHP_EOL . $body['status']['message']; } } throw new WPML_TM_ICL20MigrationException( $message, $code ); } return true; } /** * @param int $project_id * @param string $access_key * * @return bool * @throws WPML_TM_ICL20MigrationException */ public function rollback_migration( $project_id, $access_key ) { $url = $this->end_point . '/projects/' . $project_id . '/rollback_migration.json'; $args = array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'accesskey' => $access_key ) ) ); $response = $this->http->post( $url, $args ); $code = (int) $response['response']['code']; if ( $code !== 200 ) { $message = $response['response']['message']; if ( isset( $response['body'] ) ) { $body = json_decode( $response['body'], true ); if ( isset( $body['status']['message'] ) ) { $message .= PHP_EOL . $body['status']['message']; } } throw new WPML_TM_ICL20MigrationException( $message, $code ); } return true; } } ICL-20-migration/remote/class-wpml-tm-icl20-acknowledge.php 0000755 00000002707 14720342453 0017272 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Acknowledge { private $end_point; private $http; /** * WPML_TM_ICL20 constructor. * * @param WP_Http $http * @param string $end_point */ public function __construct( WP_Http $http, $end_point ) { $this->http = $http; $this->end_point = $end_point; } /** * @param string $ts_accesskey * @param int $ts_id * * @return bool * @throws \WPML_TM_ICL20MigrationException * * Note: `ts_id` (aka `website_id`) = `website_id` * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/icldev-2322 */ public function acknowledge_icl( $ts_id, $ts_accesskey ) { $url = $this->end_point . '/wpml/websites/' . $ts_id . '/migrated'; $url = add_query_arg( array( 'accesskey' => $ts_accesskey, ), $url ); $response = $this->http->post( $url, array( 'method' => 'POST', 'headers' => array( 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), ) ); $code = (int) $response['response']['code']; if ( $code !== 200 ) { throw new WPML_TM_ICL20MigrationException( $response['response']['message'], $code ); } return true; } } ICL-20-migration/remote/class-wpml-tm-icl20-migration-container.php 0000755 00000001271 14720342453 0020753 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Container { private $acknowledge; private $project; private $token; public function __construct( WPML_TM_ICL20_Token $token, WPML_TM_ICL20_Project $project, WPML_TM_ICL20_Acknowledge $ack ) { $this->token = $token; $this->project = $project; $this->acknowledge = $ack; } /** * @return WPML_TM_ICL20_Acknowledge */ public function get_acknowledge() { return $this->acknowledge; } /** * @return WPML_TM_ICL20_Project */ public function get_project() { return $this->project; } /** * @return WPML_TM_ICL20_Token */ public function get_token() { return $this->token; } } ICL-20-migration/remote/class-wpml-tm-icl20-token.php 0000755 00000003520 14720342453 0016121 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Token { private $end_point; private $http; /** * WPML_TM_ICL20 constructor. * * @param WP_Http $http * @param string $end_point */ public function __construct( WP_Http $http, $end_point ) { $this->http = $http; $this->end_point = $end_point; } /** * @param string $ts_accesskey * @param int $ts_id * * @return string|null * @throws \WPML_TM_ICL20MigrationException * * Note: `ts_id` (aka `website_id`) = `website_id` * * @link https://onthegosystems.myjetbrains.com/youtrack/issue/icldev-2285 */ public function get_token( $ts_id, $ts_accesskey ) { if ( ! $ts_id ) { throw new WPML_TM_ICL20MigrationException( 'Missing ICL Website ID ($ts_id)' ); } if ( ! $ts_accesskey ) { throw new WPML_TM_ICL20MigrationException( 'Missing ICL Access KEY ($ts_accesskey)' ); } $url = $this->end_point . '/wpml/websites/' . $ts_id . '/token'; $url = add_query_arg( array( 'accesskey' => $ts_accesskey, ), $url ); $response = $this->http->get( $url, array( 'method' => 'GET', 'headers' => array( 'Accept: application/json' ), ) ); $code = (int) $response['response']['code']; if ( 200 !== $code ) { throw new WPML_TM_ICL20MigrationException( $response['response']['message'], $code ); } if ( isset( $response['body'] ) ) { $response_data = json_decode( $response['body'], true ); if ( array_key_exists( 'api_token', $response_data ) && '' !== trim( $response_data['api_token'] ) ) { return $response_data['api_token']; } } return null; } } ICL-20-migration/class-wpml-tm-icl20-migration-ajax.php 0000755 00000001174 14720342453 0016423 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_AJAX extends WPML_TM_AJAX { /** @var WPML_TM_ICL20_Migration_Progress */ private $progress; /** * WPML_TM_ICL20_Migration_AJAX constructor. * * @param WPML_TM_ICL20_Migration_Progress $progress */ public function __construct( WPML_TM_ICL20_Migration_Progress $progress ) { $this->progress = $progress; } /** * AJAX callback used to set the user confirmation for starting the migration */ public function user_confirmation() { if ( $this->is_valid_request() ) { $this->progress->set_user_confirmed(); wp_send_json_success(); } } } ICL-20-migration/class-wpml-tm-icl20-migrate.php 0000755 00000010740 14720342453 0015140 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migrate { private $local_migration; private $progress; private $remote_migration; private $status; /** @var WPML_TP_Services */ private $tp_services; public function __construct( WPML_TM_ICL20_Migration_Progress $progress, WPML_TM_ICL20_Migration_Status $status, WPML_TM_ICL20_Migrate_Remote $remote_migration, WPML_TM_ICL20_Migrate_Local $local_migration, WPML_TP_Services $tp_services ) { $this->progress = $progress; $this->status = $status; $this->remote_migration = $remote_migration; $this->local_migration = $local_migration; $this->tp_services = $tp_services; } public function migrate_project_rollback() { if ( ! $this->status->has_active_legacy_icl() ) { return false; } $project = $this->tp_services->get_current_project(); $token = $this->get_token( $project ); if ( $token ) { return $this->remote_migration->migrate_project_rollback( $project->id, $project->access_key ); } return false; } public function run() { $this->progress->set_migration_started(); $project = $this->tp_services->get_current_project(); $token = $project ? $this->get_token( $project ) : null; if ( (bool) $token && $this->migrate_project( $project, $token ) && $this->acknowledge_icl( $project ) && $this->migrate_local_service( $token ) && $this->migrate_local_project() && $this->migrate_local_jobs( WPML_TM_ICL20_Migrate_Local::JOBS_TYPES_DOCUMENTS, WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_JOBS_DOCUMENTS ) && $this->migrate_local_jobs( WPML_TM_ICL20_Migrate_Local::JOBS_TYPES_STRINGS, WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_JOBS_STRINGS ) ) { $this->progress->set_migration_done(); return true; } return false; } /** * @param $project * * @return string */ private function get_token( $project ) { $token = $this->progress->get_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_TOKEN ); if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $token ) { $token = $this->remote_migration->get_token( $project->ts_id, $project->ts_access_key ); } return $token; } /** * @param $project * @param $token * * @return bool */ private function migrate_project( $project, $token ) { $project_migrated = $this->progress->get_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_REMOTE_PROJECT ); if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $project_migrated ) { $project_migrated = $this->remote_migration->migrate_project( $project->id, $project->access_key, $token ); } return (bool) $project_migrated; } /** * @param $project * * @return bool */ private function acknowledge_icl( $project ) { $icl_acknowledged = $this->progress->get_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_ICL_ACK ); if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $icl_acknowledged ) { $icl_acknowledged = $this->remote_migration->acknowledge_icl( $project->ts_id, $project->ts_access_key ); } return (bool) $icl_acknowledged; } /** * @param $token * * @return bool */ private function migrate_local_service( $token ) { $service_migrated = $this->progress->get_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_LOCAL_SERVICE ); if ( WPML_TM_ICL20_Migration_Progress::STEP_DONE === $service_migrated ) { $current_service = $this->tp_services->get_current_service(); $service_migrated = $current_service && $this->status->get_ICL_20_TS_ID() === $current_service->id; } if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $service_migrated ) { $service_migrated = $this->local_migration->migrate_service( $token ); } return (bool) $service_migrated; } /** * @return bool */ private function migrate_local_project() { $project_migrated = $this->progress->get_completed_step( WPML_TM_ICL20_Migration_Progress::STEP_MIGRATE_LOCAL_PROJECT ); if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $project_migrated ) { $project_migrated = $this->local_migration->migrate_project(); } return (bool) $project_migrated; } /** * @param string $table * @param string $step * * @return bool */ private function migrate_local_jobs( $table, $step ) { $job_migrated = $this->progress->get_completed_step( $step ); if ( WPML_TM_ICL20_Migration_Progress::STEP_FAILED === $job_migrated ) { $job_migrated = $this->local_migration->migrate_jobs( $table ); } return (bool) $job_migrated; } } ICL-20-migration/class-wpml-tm-icl20-migration-factory.php 0000755 00000007146 14720342453 0017154 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_TM_ICL20_Migration_Factory { /** * @var WPML_TM_ICL20_Migration_Notices */ private $notices; /** * @var WPML_TM_ICL20_Migration_Progress */ private $progress; /** * @var WPML_TM_ICL20_Migration_Status */ private $status; /** * @var WPML_TP_Services */ private $tp_services; /** * @var WP_Http */ private $wp_http; /** * @return WPML_TM_ICL20_Migration_AJAX */ public function create_ajax() { return new WPML_TM_ICL20_Migration_AJAX( $this->create_progress() ); } /** * @return WPML_TM_ICL20_Migration_Locks */ public function create_locks() { return new WPML_TM_ICL20_Migration_Locks( $this->create_progress() ); } /** * @return WPML_TM_ICL20_Migration_Progress */ public function create_progress() { if ( null === $this->progress ) { $this->progress = new WPML_TM_ICL20_Migration_Progress(); } return $this->progress; } /** * @return WPML_TM_ICL20_Migrate */ public function create_migration() { return new WPML_TM_ICL20_Migrate( $this->create_progress(), $this->create_status(), $this->get_remote_migration(), $this->get_local_migration(), $this->get_tp_services() ); } /** * @return WPML_TM_ICL20_Migration_Status */ public function create_status() { if ( null === $this->status ) { $current_service = $this->get_tp_services()->get_current_service(); $this->status = new WPML_TM_ICL20_Migration_Status( $current_service ); } return $this->status; } /** * @return WPML_TM_ICL20_Migrate_Remote */ private function get_remote_migration() { $http = $this->get_wp_http(); $token = new WPML_TM_ICL20_Token( $http, ICL_API_ENDPOINT ); $project = new WPML_TM_ICL20_Project( $http, OTG_TRANSLATION_PROXY_URL ); $ack = new WPML_TM_ICL20_Acknowledge( $http, ICL_API_ENDPOINT ); $container = new WPML_TM_ICL20_Migration_Container( $token, $project, $ack ); return new WPML_TM_ICL20_Migrate_Remote( $this->create_progress(), $container ); } /** * @return WPML_TM_ICL20_Migrate_Local */ private function get_local_migration() { return new WPML_TM_ICL20_Migrate_Local( $this->get_tp_services(), $this->create_status(), $this->create_progress(), $this->get_sitepress() ); } /** * @return WPML_TP_Services */ private function get_tp_services() { if ( null === $this->tp_services ) { $this->tp_services = new WPML_TP_Services(); } return $this->tp_services; } /** * @return WP_Http */ private function get_wp_http() { if ( null === $this->wp_http ) { $this->wp_http = new WP_Http(); } return $this->wp_http; } /** * @return SitePress */ private function get_sitepress() { global $sitepress; return $sitepress; } /** * @return WPML_TM_ICL20_Migration_Notices */ public function create_notices() { if ( null === $this->notices ) { $this->notices = new WPML_TM_ICL20_Migration_Notices( $this->create_progress(), wpml_get_admin_notices() ); } return $this->notices; } /** * @return WPML_TM_ICL20_Migration_Support */ public function create_ui_support() { $template_paths = array( WPML_TM_PATH . '/templates/support/icl20/migration/', ); $template_loader = new WPML_Twig_Template_Loader( $template_paths ); $template_service = $template_loader->get_template(); return new WPML_TM_ICL20_Migration_Support( $template_service, $this->create_progress(), $this->can_rollback() ); } /** * @return bool */ public function can_rollback() { return defined( 'WP_DEBUG' ) && defined( 'WPML_TP_ICL_20_ENABLE_ROLLBACK' ) && WP_DEBUG && WPML_TP_ICL_20_ENABLE_ROLLBACK; } } translation-batch/class-wpml-tm-translation-batch.php 0000755 00000007431 14720342453 0017021 0 ustar 00 <?php use WPML\FP\Lst; class WPML_TM_Translation_Batch { const HANDLE_EXISTING_LEAVE = 'leave'; const HANDLE_EXISTING_OVERRIDE = 'override'; /** @var WPML_TM_Translation_Batch_Element[] */ private $elements; /** @var string */ private $basket_name; /** @var array */ private $translators; /** @var DateTime */ private $deadline; /** @var "auto"|"manual"|null */ private $translationMode = null; /** @var string */ private $howToHandleExisting = self::HANDLE_EXISTING_LEAVE; /** * @param WPML_TM_Translation_Batch_Element[] $elements * @param string $basket_name * @param array $translators * @param DateTime $deadline * * @throws InvalidArgumentException */ public function __construct( array $elements, $basket_name, array $translators, DateTime $deadline = null ) { if ( empty( $elements ) ) { throw new InvalidArgumentException( 'Batch elements cannot be empty' ); } if ( empty( $basket_name ) ) { throw new InvalidArgumentException( 'Basket name cannot be empty' ); } if ( empty( $translators ) ) { throw new InvalidArgumentException( 'Translators array cannot be empty' ); } $this->elements = $elements; $this->basket_name = (string) $basket_name; $this->translators = $translators; $this->deadline = $deadline; } /** * @return WPML_TM_Translation_Batch_Element[] */ public function get_elements() { return $this->elements; } public function add_element( WPML_TM_Translation_Batch_Element $element ) { $this->elements[] = $element; } /** * @param string $type * * @return WPML_TM_Translation_Batch_Element[] */ public function get_elements_by_type( $type ) { $result = array(); foreach ( $this->get_elements() as $element ) { if ( $element->get_element_type() === $type ) { $result[] = $element; } } return $result; } /** * @return string */ public function get_basket_name() { return $this->basket_name; } /** * @return array */ public function get_translators() { return $this->translators; } public function get_translator( $lang ) { return $this->translators[ $lang ]; } /** * @return DateTime */ public function get_deadline() { return $this->deadline; } /** * @return array */ public function get_target_languages() { $result = array(); foreach ( $this->get_elements() as $element ) { $result[] = array_keys( $element->get_target_langs() ); } return array_values( array_unique( call_user_func_array( 'array_merge', $result ) ) ); } /** * @return array */ public function get_remote_target_languages() { return array_values( array_filter( $this->get_target_languages(), array( $this, 'is_remote_target_language', ) ) ); } private function is_remote_target_language( $lang ) { return isset( $this->translators[ $lang ] ) && ! is_numeric( $this->translators[ $lang ] ); } /** * @return array */ public function get_batch_options() { return array( 'basket_name' => $this->get_basket_name(), 'deadline_date' => $this->get_deadline() ? $this->get_deadline()->format( 'Y-m-d' ) : '', ); } /** * @return "auto"|"manual"|null */ public function getTranslationMode() { return $this->translationMode; } /** * @param "auto"|"manual"|null $translationMode */ public function setTranslationMode( $translationMode ) { $this->translationMode = Lst::includes( $translationMode, [ 'auto', 'manual' ] ) ? $translationMode : null; } /** * @return string */ public function getHowToHandleExisting() { return $this->howToHandleExisting; } /** * @param string $howToHandleExisting */ public function setHowToHandleExisting( $howToHandleExisting ) { $this->howToHandleExisting = $howToHandleExisting; } } translation-batch/class-wpml-tm-translation-batch-factory.php 0000755 00000005620 14720342453 0020464 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_TM_Translation_Batch_Factory { /** @var WPML_Translation_Basket $basket */ private $basket; /** * @param WPML_Translation_Basket $basket */ public function __construct( WPML_Translation_Basket $basket ) { $this->basket = $basket; } /** * @param array $batch_data * * @return WPML_TM_Translation_Batch */ public function create( array $batch_data ) { $translators = isset( $batch_data['translators'] ) ? $batch_data['translators'] : array(); $basket_name = Sanitize::stringProp( 'basket_name', $batch_data ); $elements = apply_filters( 'wpml_tm_batch_factory_elements', $this->get_elements( $batch_data, array_keys( $translators ) ), $basket_name ); $deadline_date = null; if ( isset( $batch_data['deadline_date'] ) && $this->validate_deadline( $batch_data['deadline_date'] ) ) { $deadline_date = new DateTime( $batch_data['deadline_date'] ); } return new WPML_TM_Translation_Batch( $elements, $basket_name ?: '', $translators, $deadline_date ); } private function get_elements( array $batch_data, array $translators_target_languages ) { if ( ! isset( $batch_data['batch'] ) || empty( $batch_data['batch'] ) ) { return array(); } $basket = $this->basket->get_basket(); if ( ! isset( $basket['post'] ) ) { $basket['post'] = array(); } if ( ! isset( $basket['string'] ) ) { $basket['string'] = array(); } $basket_items_types = array_keys( $this->basket->get_item_types() ); $result = array(); foreach ( $batch_data['batch'] as $item ) { $element_id = $item['post_id']; $element_type = isset( $item['type'] ) && is_string( $item['type'] ) ? $item['type'] : ''; if ( ! in_array( $element_type, $basket_items_types, true ) ) { continue; } if ( ! isset( $basket[ $element_type ][ $element_id ] ) ) { continue; } $basket_item = $basket[ $element_type ][ $element_id ]; $target_languages = array_intersect_key( $basket_item['to_langs'], array_combine( $translators_target_languages, $translators_target_languages ) ); if ( empty( $target_languages ) ) { throw new InvalidArgumentException( 'Element\'s target languages do not match to batch list' ); } $media_to_translations = isset( $basket_item['media-translation'] ) ? $basket_item['media-translation'] : array(); $result[] = new WPML_TM_Translation_Batch_Element( $element_id, $element_type, $basket[ $element_type ][ $element_id ]['from_lang'], $target_languages, $media_to_translations ); } return $result; } /** * The expected format is "2017-09-28" * * @param string $date * * @return bool */ private function validate_deadline( $date ) { $date_parts = explode( '-', $date ); return is_array( $date_parts ) && count( $date_parts ) === 3 && checkdate( (int) $date_parts[1], (int) $date_parts[2], (int) $date_parts[0] ); } } translation-batch/class-wpml-tm-translation-batch-element.php 0000755 00000004427 14720342453 0020452 0 ustar 00 <?php class WPML_TM_Translation_Batch_Element { /** @var int */ private $element_id; /** @var string */ private $element_type; /** @var string */ private $source_lang; /** @var array */ private $target_langs; /** @var $media_to_translations */ private $media_to_translations; /** * @param int $element_id * @param string $element_type * @param string $source_lang * @param array $target_languages * @param array $media_to_translations */ public function __construct( $element_id, $element_type, $source_lang, array $target_languages, array $media_to_translations = array() ) { if ( ! $element_id ) { throw new InvalidArgumentException( 'Element id has to be defined' ); } if ( empty( $element_type ) ) { throw new InvalidArgumentException( 'Element type has to be defined' ); } if ( ! is_string( $source_lang ) || empty( $source_lang ) ) { throw new InvalidArgumentException( 'Source lang has to be not empty string' ); } if ( empty( $target_languages ) ) { throw new InvalidArgumentException( 'Target languages array cannot be empty' ); } $possible_actions = array( TranslationManagement::TRANSLATE_ELEMENT_ACTION, TranslationManagement::DUPLICATE_ELEMENT_ACTION, ); foreach ( $target_languages as $lang => $action ) { if ( ! is_string( $lang ) || ! in_array( $action, $possible_actions, true ) ) { throw new InvalidArgumentException( 'Target languages must be an associative array with the language code as a key and the action as a numeric value.' ); } } $this->element_id = $element_id; $this->element_type = $element_type; $this->source_lang = $source_lang; $this->target_langs = $target_languages; $this->media_to_translations = $media_to_translations; } /** * @return int */ public function get_element_id() { return $this->element_id; } /** * @return string */ public function get_element_type() { return $this->element_type; } /** * @return string */ public function get_source_lang() { return $this->source_lang; } /** * @return string[] */ public function get_target_langs() { return $this->target_langs; } /** * @return mixed */ public function get_media_to_translations() { return $this->media_to_translations; } } ui-elements/class-wpml-ui-screen-options-factory.php 0000755 00000001537 14720342453 0016631 0 ustar 00 <?php /** * @package wpml-core */ class WPML_UI_Screen_Options_Factory { /** @var SitePress $sitepress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param string $option_name * @param int $default_per_page * * @return WPML_UI_Screen_Options_Pagination */ public function create_pagination( $option_name, $default_per_page ) { $pagination = new WPML_UI_Screen_Options_Pagination( $option_name, $default_per_page ); $pagination->init_hooks(); return $pagination; } public function create_help_tab( $id, $title, $content ) { $help_tab = new WPML_UI_Help_Tab( $this->sitepress->get_wp_api(), $id, $title, $content ); $help_tab->init_hooks(); return $help_tab; } public function create_admin_table_sort() { return new WPML_Admin_Table_Sort(); } } ui-elements/class-wpml-ui-help-tab.php 0000755 00000001273 14720342453 0013705 0 ustar 00 <?php /** * @package wpml-core */ class WPML_UI_Help_Tab { private $wp_api; private $id; private $title; private $content; public function __construct( WPML_WP_API $wp_api, $id, $title, $content ) { $this->wp_api = $wp_api; $this->id = $id; $this->title = $title; $this->content = $content; } public function init_hooks() { $this->wp_api->add_action( 'admin_head', array( $this, 'add_help_tab' ) ); } public function add_help_tab() { $screen = $this->wp_api->get_current_screen(); if ( null !== $screen ) { $screen->add_help_tab( array( 'id' => $this->id, 'title' => $this->title, 'content' => $this->content, ) ); } } } ui-elements/class-wpml-ui-pagination.php 0000755 00000000545 14720342453 0014343 0 ustar 00 <?php /** * @package wpml-core */ class WPML_UI_Pagination extends WP_List_Table { public function __construct( $total, $number_per_page ) { parent::__construct(); $this->set_pagination_args( array( 'total_items' => $total, 'per_page' => $number_per_page, ) ); } public function show() { $this->pagination( 'bottom' ); } } ui-elements/class-wpml-ui-unlock-button.php 0000755 00000001260 14720342453 0015011 0 ustar 00 <?php class WPML_UI_Unlock_Button { public function render( $disabled, $unlocked, $radio_name, $unlocked_name ) { if ( $disabled && ! $unlocked ) { ?> <button type="button" class="button-secondary wpml-button-lock js-wpml-sync-lock" title="<?php esc_html_e( 'This setting is controlled by a wpml-config.xml file. Click here to unlock and override this setting.', 'sitepress' ); ?>" data-radio-name="<?php echo $radio_name; ?>" data-unlocked-name="<?php echo $unlocked_name; ?>"> <i class="otgs-ico-lock"></i> </button> <?php } ?> <input type="hidden" name="<?php echo $unlocked_name; ?>" value="<?php echo $unlocked ? '1' : '0'; ?>"> <?php } } ui-elements/class-wpml-ui-screen-options-pagination.php 0000755 00000002450 14720342453 0017306 0 ustar 00 <?php /** * @package wpml-core */ class WPML_UI_Screen_Options_Pagination { /** * @var string $option_name */ private $option_name; /** * @var int $default_per_page */ private $default_per_page; /** * WPML_UI_Screen_Options_Pagination constructor. * * @param string $option_name * @param int $default_per_page */ public function __construct( $option_name, $default_per_page ) { $this->option_name = $option_name; $this->default_per_page = $default_per_page; } public function init_hooks() { add_action( 'admin_head', array( $this, 'add_screen_options' ) ); add_filter( 'set-screen-option', array( $this, 'set_screen_options_filter' ), 10, 3 ); add_filter( 'set_screen_option_' . $this->option_name, [ $this, 'set_screen_options_filter' ], 10, 3 ); } public function add_screen_options() { add_screen_option( 'per_page', array( 'default' => $this->default_per_page, 'option' => $this->option_name, ) ); } public function set_screen_options_filter( $value, $option, $set_value ) { return $option === $this->option_name ? $set_value : $value; } public function get_items_per_page() { $page_size = (int) get_user_option( $this->option_name ); if ( ! $page_size ) { $page_size = $this->default_per_page; } return $page_size; } } database-queries/translated-posts.php 0000755 00000001353 14720342453 0014014 0 ustar 00 <?php namespace WPML\DatabaseQueries; class TranslatedPosts { /** * Returns array of translated content IDs that are related to defined secondary languages. * * @param array $langs Array of secondary languages codes to get IDs of translated content for them. * * @return array */ public static function getIdsForLangs( $langs ) { global $wpdb; $languagesIn = wpml_prepare_in( $langs ); $contentIdsQuery = " SELECT posts.ID FROM {$wpdb->posts} posts INNER JOIN {$wpdb->prefix}icl_translations translations ON translations.element_id = posts.ID AND translations.element_type = CONCAT('post_', posts.post_type) WHERE translations.language_code IN ({$languagesIn}) "; return $wpdb->get_col( $contentIdsQuery ); } } class-wpml-mo-file-search.php 0000755 00000007651 14720342453 0012150 0 ustar 00 <?php class WPML_MO_File_Search { /** * @var SitePress */ private $sitepress; /** * @var array */ private $settings; /** * @var WP_Filesystem_Direct */ private $filesystem; /** * @var array */ private $locales; /** * @param SitePress $sitepress */ public function __construct( SitePress $sitepress, WP_Filesystem_Direct $filesystem = null ) { $this->sitepress = $sitepress; if ( ! $filesystem ) { $filesystem = $sitepress->get_wp_api()->get_wp_filesystem_direct(); } $this->filesystem = $filesystem; $this->settings = $this->sitepress->get_settings(); $this->locales = $this->sitepress->get_locale_file_names(); } /** * @param array $active_languages * * @return bool */ public function has_mo_file_for_any_language( $active_languages ) { foreach ( $active_languages as $lang ) { if ( $this->can_find_mo_file( $lang['code'] ) ) { return true; } } return false; } public function reload_theme_dirs() { $dirs = $this->find_theme_mo_dirs(); $this->save_mo_dirs( $dirs ); $this->settings['theme_language_folders'] = $dirs; } /** * @param string $lang_code * * @return bool */ public function can_find_mo_file( $lang_code ) { if ( ! isset( $this->locales[ $lang_code ] ) ) { return false; } $file_names = $this->locales[ $lang_code ]; if ( isset( $this->settings['theme_language_folders']['parent'] ) ) { $files[] = $this->settings['theme_language_folders']['parent'] . '/' . $file_names . '.mo'; } if ( isset( $this->settings['theme_language_folders']['child'] ) ) { $files[] = $this->settings['theme_language_folders']['child'] . '/' . $file_names . '.mo'; } $files[] = $this->get_template_path() . '/' . $file_names . '.mo'; foreach ( $files as $file ) { if ( $this->filesystem->is_readable( $file ) ) { return true; } } return false; } /** * @return string */ protected function get_template_path() { return TEMPLATEPATH; } /** * @return array */ public function find_theme_mo_dirs() { $parent_theme = get_template_directory(); $child_theme = get_stylesheet_directory(); $languages_folders = null; if ( $found_folder = $this->determine_mo_folder( $parent_theme ) ) { $languages_folders['parent'] = $found_folder; } if ( $parent_theme != $child_theme && $found_folder = $this->determine_mo_folder( $child_theme ) ) { $languages_folders['child'] = $found_folder; } return $languages_folders; } /** * @param string $folder * @param int $rec * * @return bool */ public function determine_mo_folder( $folder, $rec = 0 ) { $lfn = $this->sitepress->get_locale_file_names(); $files = $this->filesystem->dirlist( $folder, false, false ); foreach ( $files as $file => $data ) { if ( 0 === strpos( $file, '.' ) ) { continue; } if ( $this->filesystem->is_file( $folder . '/' . $file ) && preg_match( '#\.mo$#i', $file ) && in_array( preg_replace( '#\.mo$#i', '', $file ), $lfn ) ) { return $folder; } elseif ( $this->filesystem->is_dir( $folder . '/' . $file ) && $rec < 5 ) { if ( $f = $this->determine_mo_folder( $folder . '/' . $file, $rec + 1 ) ) { return $f; }; } } return false; } /** * @return array */ public function get_dir_names() { $dirs = array(); if ( isset( $this->settings['theme_language_folders']['parent'] ) ) { $dirs[] = $this->settings['theme_language_folders']['parent']; } if ( isset( $this->settings['theme_language_folders']['child'] ) ) { $dirs[] = $this->settings['theme_language_folders']['child']; } if ( empty( $dirs ) ) { $template = get_option( 'template' ); $dirs[] = get_theme_root( $template ) . '/' . $template; } return $dirs; } /** * @param array $dirs */ public function save_mo_dirs( $dirs ) { $sitepress_settings = $this->sitepress->get_settings(); $sitepress_settings['theme_language_folders'] = $dirs; $this->sitepress->save_settings( $sitepress_settings ); } } theme-plugin-localization/class-wpml-theme-plugin-localization-ui-hooks.php 0000755 00000002630 14720342453 0023251 0 ustar 00 <?php class WPML_Theme_Plugin_Localization_UI_Hooks { /** @var WPML_Theme_Plugin_Localization_UI */ private $localization_ui; /** @var WPML_Theme_Plugin_Localization_Options_UI */ private $options_ui; /** * WPML_Theme_Plugin_Localization_UI_Hooks constructor. * * @param WPML_Theme_Plugin_Localization_UI $localization_ui * @param WPML_Theme_Plugin_Localization_Options_UI $options_ui */ public function __construct( WPML_Theme_Plugin_Localization_UI $localization_ui, WPML_Theme_Plugin_Localization_Options_UI $options_ui ) { $this->localization_ui = $localization_ui; $this->options_ui = $options_ui; } public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_styles' ) ); add_action( 'wpml_custom_localization_type', array( $this, 'render_options_ui' ), 1 ); } public function enqueue_styles() { wp_enqueue_style( 'wpml-theme-plugin-localization', ICL_PLUGIN_URL . '/res/css/theme-plugin-localization.css', array( 'wpml-tooltip' ), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'wpml-theme-plugin-localization', ICL_PLUGIN_URL . '/res/js/theme-plugin-localization.js', array( 'jquery' ), ICL_SITEPRESS_VERSION ); wp_enqueue_script( OTGS_Assets_Handles::POPOVER_TOOLTIP ); wp_enqueue_style( OTGS_Assets_Handles::POPOVER_TOOLTIP ); } public function render_options_ui() { echo $this->localization_ui->render( $this->options_ui ); } } theme-plugin-localization/class-wpml-themes-plugins-localization-options-ajax.php 0000755 00000002205 14720342453 0024473 0 ustar 00 <?php use WPML\FP\Obj; class WPML_Theme_Plugin_Localization_Options_Ajax implements IWPML_AJAX_Action, IWPML_DIC_Action { const NONCE_LOCALIZATION_OPTIONS = 'wpml-localization-options-nonce'; /** @var WPML_Save_Themes_Plugins_Localization_Options */ private $save_localization_options; /** * WPML_Themes_Plugins_Localization_Options_Ajax constructor. * * @param WPML_Save_Themes_Plugins_Localization_Options $save_localization_options */ public function __construct( WPML_Save_Themes_Plugins_Localization_Options $save_localization_options ) { $this->save_localization_options = $save_localization_options; } public function add_hooks() { add_action( 'wp_ajax_wpml_update_localization_options', array( $this, 'update_localization_options' ) ); } public function update_localization_options() { if ( ! $this->is_valid_request() ) { wp_send_json_error(); } else { $this->save_localization_options->save_settings( $_POST ); wp_send_json_success(); } } /** @return bool */ private function is_valid_request() { return wp_verify_nonce( Obj::propOr( '', 'nonce', $_POST ), self::NONCE_LOCALIZATION_OPTIONS ); } } theme-plugin-localization/class-wpml-save-themes-plugins-localization-options.php 0000755 00000003617 14720342453 0024516 0 ustar 00 <?php class WPML_Save_Themes_Plugins_Localization_Options { /** @var SitePress */ private $sitepress; /** * WPML_Save_Themes_Plugins_Localization_Options constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** @param array $settings */ public function save_settings( $settings ) { foreach ( $this->get_settings() as $key => $setting ) { if ( array_key_exists( $key, $settings ) ) { $value = filter_var( $settings[ $key ], $setting['filter'] ); if ( 'setting' === $setting['type'] ) { if ( $setting['st_setting'] ) { $st_settings = $this->sitepress->get_setting( 'st' ); $st_settings[ $setting['settings_var'] ] = $value; $this->sitepress->set_setting( 'st', $st_settings ); } else { $this->sitepress->set_setting( $setting['settings_var'], $value ); } } elseif ( 'option' === $setting['type'] ) { update_option( $setting['settings_var'], $value ); } } } $this->sitepress->save_settings(); do_action( 'theme_plugin_localization_settings_saved', $settings ); } /** @return array */ private function get_settings() { $settings = array(); $settings['theme_localization_load_textdomain'] = array( 'settings_var' => 'theme_localization_load_textdomain', 'filter' => FILTER_SANITIZE_NUMBER_INT, 'type' => 'setting', 'st_setting' => false, ); $settings['gettext_theme_domain_name'] = array( 'settings_var' => 'gettext_theme_domain_name', 'filter' => FILTER_SANITIZE_FULL_SPECIAL_CHARS, 'type' => 'setting', 'st_setting' => false, ); /** * @param array $settings array of settings rendered in theme/plugin localization screen */ return apply_filters( 'wpml_localization_options_settings', $settings ); } } theme-plugin-localization/view/class-wpml-theme-plugin-localization-ui.php 0000755 00000001432 14720342453 0023101 0 ustar 00 <?php class WPML_Theme_Plugin_Localization_UI { const TEMPLATE_PATH = '/templates/theme-plugin-localization/'; /** * @return IWPML_Template_Service */ private function get_template_service() { $paths = array(); $paths[] = WPML_PLUGIN_PATH . self::TEMPLATE_PATH; if ( defined( 'WPML_ST_PATH' ) ) { $paths[] = WPML_ST_PATH . self::TEMPLATE_PATH; } $template_loader = new WPML_Twig_Template_Loader( $paths ); return $template_loader->get_template(); } /** * @param IWPML_Theme_Plugin_Localization_UI_Strategy $localization_strategy */ public function render( IWPML_Theme_Plugin_Localization_UI_Strategy $localization_strategy ) { return $this->get_template_service()->show( $localization_strategy->get_model(), $localization_strategy->get_template() ); } } theme-plugin-localization/strategy/class-wpml-theme-plugin-localization-options-ui.php 0000755 00000003175 14720342453 0025470 0 ustar 00 <?php class WPML_Theme_Plugin_Localization_Options_UI implements IWPML_Theme_Plugin_Localization_UI_Strategy { /** @var SitePress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** @return array */ public function get_model() { $model = array( 'nonce_field' => WPML_Theme_Plugin_Localization_Options_Ajax::NONCE_LOCALIZATION_OPTIONS, 'nonce_value' => wp_create_nonce( WPML_Theme_Plugin_Localization_Options_Ajax::NONCE_LOCALIZATION_OPTIONS ), 'section_label' => __( 'Localization options', 'sitepress' ), 'top_options' => array( array( 'template' => 'automatic-load-check.twig', 'model' => array( 'theme_localization_load_textdomain' => array( 'value' => 1, 'label' => __( "Automatically load the theme's .mo file using 'load_textdomain'", 'sitepress' ), 'checked' => checked( $this->sitepress->get_setting( 'theme_localization_load_textdomain' ), true, false ), ), 'gettext_theme_domain_name' => array( 'value' => $this->sitepress->get_setting( 'gettext_theme_domain_name', '' ), 'label' => __( 'Enter textdomain:', 'sitepress' ), ), ), ), ), 'button_label' => __( 'Save', 'sitepress' ), 'scanning_progress_msg' => __( "Scanning now, please don't close this page.", 'sitepress' ), 'scanning_results_title' => __( 'Scanning Results', 'sitepress' ), ); return apply_filters( 'wpml_localization_options_ui_model', $model ); } /** @return string */ public function get_template() { return 'options.twig'; } } theme-plugin-localization/strategy/interface-wpml-theme-plugin-localization-ui-strategy.php 0000755 00000000201 14720342453 0026455 0 ustar 00 <?php interface IWPML_Theme_Plugin_Localization_UI_Strategy { public function get_model(); public function get_template(); } theme-plugin-localization/factory/class-wpml-theme-plugin-localization-ui-hooks-factory.php 0000755 00000001043 14720342453 0026362 0 ustar 00 <?php use WPML\FP\Relation; use function WPML\Container\make; class WPML_Themes_Plugin_Localization_UI_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Deferred_Action_Loader { /** @return WPML_Theme_Plugin_Localization_UI_Hooks */ public function create() { return Relation::propEq( 'id', WPML_PLUGIN_FOLDER . '/menu/theme-localization', get_current_screen() ) ? make( WPML_Theme_Plugin_Localization_UI_Hooks::class ) : null; } /** @return string */ public function get_load_action() { return 'current_screen'; } } logging/interface-wpml-log.php 0000755 00000000412 14720342453 0012403 0 ustar 00 <?php /** * @author OnTheGo Systems */ interface WPML_Log { public function insert( $timestamp, array $entry ); public function get( $page_size = 0, $page = 0 ); public function save( array $data ); public function clear(); public function is_empty(); } menu-elements/class-wpml-lang-domains-box.php 0000755 00000014650 14720342453 0015267 0 ustar 00 <?php /** * Class WPML_Lang_Domains_Box * * Displays the table holding the language domains on languages.php */ class WPML_Lang_Domains_Box extends WPML_SP_User { public function render() { $active_languages = $this->sitepress->get_active_languages(); $default_language = $this->sitepress->get_default_language(); $language_domains = $this->sitepress->get_setting( 'language_domains', array() ); $default_home = (string) $this->sitepress->convert_url( $this->sitepress->get_wp_api()->get_home_url(), $default_language ); $home_schema = wpml_parse_url( $default_home, PHP_URL_SCHEME ) . '://'; $home_path = wpml_parse_url( $default_home, PHP_URL_PATH ); $is_per_domain = WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN === (int) $this->sitepress->get_setting( 'language_negotiation_type' ); $is_sso_enabled = (bool) $this->sitepress->get_setting( 'language_per_domain_sso_enabled', ! $is_per_domain ); ob_start(); ?> <table class="language_domains sub-section"> <?php foreach ( $active_languages as $code => $lang ) { $text_box_id = esc_attr( 'language_domain_' . $code ); ?> <tr> <td> <label for="<?php echo $text_box_id; ?>"> <?php echo esc_html( $lang['display_name'] ); ?> </label> </td> <?php if ( $code === $default_language ) { ?> <td id="icl_ln_home"> <code> <?php echo esc_url( $default_home ); ?> </code> </td> <td> </td> <?php } else { ?> <td style="white-space: nowrap"> <code><?php echo esc_html( $home_schema ); ?></code> <input type="text" id="<?php echo $text_box_id; ?>" name="language_domains[<?php echo esc_attr( $code ); ?>]" value="<?php echo $this->get_language_domain( $code, $default_home, $language_domains ); ?>" data-language="<?php echo esc_attr( $code ); ?>" size="30"/> <?php if ( isset( $home_path[1] ) && is_string( $home_path[1] ) ) { ?> <code><?php echo esc_html( $home_path ); ?></code> <?php } ?> </td> <td> <p style="white-space: nowrap"><input class="validate_language_domain" type="checkbox" id="validate_language_domains_<?php echo esc_attr( $code ); ?>" name="validate_language_domains[]" value="<?php echo esc_attr( $code ); ?>" checked="checked"/> <label for="validate_language_domains_<?php echo esc_attr( $code ); ?>"> <?php esc_html_e( 'Validate on save', 'sitepress' ); ?> </label> </p> <p style="white-space: nowrap"> <span class="spinner spinner-<?php echo esc_attr( $code ); ?>"></span> <span id="ajx_ld_<?php echo esc_attr( $code ); ?>"></span> </p> </td> <?php } ?> </tr> <?php } ?> <tr> <td colspan="2"> <label for="sso_enabled"> <input type="checkbox" id="sso_enabled" name="sso_enabled" value="1" <?php checked( $is_sso_enabled, true, true ); ?>> <?php esc_html_e( 'Auto sign-in and sign-out users from all domains', 'sitepress' ); ?> </label> <span id="sso_information"><i class="otgs-ico-help"></i></span> <div id="sso_enabled_notice" style="display: none;"> <?php esc_html_e( 'Please log-out and login again in order to be able to access the admin features in all language domains.', 'sitepress' ); ?> </div> </td> </tr> </table> <div id="language_per_domain_sso_description" style="display:none;"> <p> <?php /* translators: this is the first of two sentences explaining the "Auto sign-in and sign-out users from all domains" feature */ echo esc_html_x( 'This feature allows the theme and plugins to work correctly when on sites that use languages in domains.', 'Tooltip: Auto sign-in and sign-out users from all domains', 'sitepress' ); ?> <br> <?php /* translators: this is the second of two sentences explaining the "Auto sign-in and sign-out users from all domains" feature */ echo esc_html_x( "It requires a call to each of the site's language domains on both log-in and log-out, so there's a small performance penalty to using it.", 'Tooltip: Auto sign-in and sign-out users from all domains', 'sitepress' ); ?> </p> </div> <?php return ob_get_clean(); } /** * @param string $code * @param string $default_home * @param string[] $language_domains * * @return string */ private function get_language_domain( $code, $default_home, $language_domains ) { $home_schema = wpml_parse_url( $default_home, PHP_URL_SCHEME ) . '://'; $home_path = wpml_parse_url( $default_home, PHP_URL_PATH ); if ( isset( $language_domains[ $code ] ) ) { $pattern = [ '#^' . $home_schema . '#', '#' . $home_path . '$#', ]; $language_domain_raw = preg_replace( $pattern, '', $language_domains[ $code ] ); } else { $language_domain_raw = $this->render_suggested_url( $default_home, $code ); } return filter_var( $language_domain_raw, FILTER_SANITIZE_URL ); } private function render_suggested_url( $home, $lang ) { $url_parts = parse_url( $home ); $exp = explode( '.', $url_parts['host'] ); $suggested_url = $lang . '.'; array_shift( $exp ); $suggested_url .= count( $exp ) < 2 ? $url_parts['host'] : implode( '.', $exp ); return $suggested_url; } } menu-elements/class-wpml-admin-scripts-setup.php 0000755 00000047376 14720342453 0016056 0 ustar 00 <?php class WPML_Admin_Scripts_Setup extends WPML_Full_Translation_API { const PRIORITY_ENQUEUE_SCRIPTS = 10; /** @var string $page */ private $page; /** * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_Post_Translation $post_translation * @param WPML_Terms_Translations $term_translation * @param string $page */ public function __construct( &$wpdb, &$sitepress, &$post_translation, &$term_translation, $page ) { parent::__construct( $sitepress, $wpdb, $post_translation, $term_translation ); $this->page = $page; } public function add_admin_hooks() { add_action( 'admin_print_scripts', array( $this, 'wpml_js_scripts_setup' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'wpml_css_setup' ), self::PRIORITY_ENQUEUE_SCRIPTS ); } public function register_styles() { wp_register_style( 'otgs-dialogs', ICL_PLUGIN_URL . '/res/css/otgs-dialogs.css', array( 'wp-jquery-ui-dialog' ), ICL_SITEPRESS_VERSION ); wp_register_style( 'wpml-dialog', ICL_PLUGIN_URL . '/res/css/dialog.css', array( 'otgs-dialogs' ), ICL_SITEPRESS_VERSION ); wp_register_style( 'wpml-wizard', ICL_PLUGIN_URL . '/res/css/wpml-wizard.css', [], ICL_SITEPRESS_VERSION ); } private function print_js_globals() { $icl_ajax_url = wpml_get_admin_url( array( 'path' => 'admin.php', 'query' => array( 'page' => WPML_PLUGIN_FOLDER . '/menu/languages.php' ), ) ); ?> <script type="text/javascript"> // <![CDATA[ var icl_ajx_url = '<?php echo esc_url( $icl_ajax_url ); ?>', icl_ajx_saved = '<?php echo icl_js_escape( __( 'Data saved', 'sitepress' ) ); ?>', icl_ajx_error = '<?php echo icl_js_escape( __( 'Error: data not saved', 'sitepress' ) ); ?>', icl_default_mark = '<?php echo icl_js_escape( __( 'default', 'sitepress' ) ); ?>', icl_this_lang = '<?php echo esc_js( $this->sitepress->get_current_language() ); ?>', icl_ajxloaderimg_src = '<?php echo esc_url( ICL_PLUGIN_URL ); ?>/res/img/ajax-loader.gif', icl_cat_adder_msg = '<?php echo icl_js_escape( sprintf( __( 'To add categories that already exist in other languages go to the <a%s>category management page</a>', 'sitepress' ), ' href="' . admin_url( 'edit-tags.php?taxonomy=category' ) . '"' ) ); ?>'; // ]]> <?php if ( ! $this->sitepress->get_setting( 'ajx_health_checked' ) && ! (bool) get_option( '_wpml_inactive' ) ) { $error = __( "WPML can't run normally. There is an installation or server configuration problem. %1\$sShow details%2\$s", 'sitepress' ); $error_message = sprintf( $error, '<a href="#" onclick="jQuery(this).parent().next().slideToggle()">', '</a>' ); ?> addLoadEvent(function () { jQuery.ajax({ type : "POST", url : icl_ajx_url, data : "icl_ajx_action=health_check", error: function (msg) { var icl_initial_language = jQuery('#icl_initial_language'); if (icl_initial_language.length) { icl_initial_language.find('input').attr('disabled', 'disabled'); } jQuery('.wrap').prepend('<div class="error"><p><?php echo icl_js_escape( $error_message );?></p><p style="display:none"><?php echo icl_js_escape( __( 'AJAX Error:', 'sitepress' ) ); ?> ' + msg.statusText + ' [' + msg.status + ']<br />URL:' + icl_ajx_url + '</p></div>'); } }); }); <?php } ?> </script> <?php } public function wpml_js_scripts_setup() { // TODO: [WPML 3.3] move javascript to external resource (use wp_localize_script() to pass arguments) global $pagenow, $sitepress; $default_language = $this->sitepress->get_default_language(); $current_language = $this->sitepress->get_current_language(); $page_basename = $this->page; $this->print_js_globals(); $wpml_script_setup_args['default_language'] = $default_language; $wpml_script_setup_args['current_language'] = $current_language; do_action( 'wpml_scripts_setup', $wpml_script_setup_args ); if ( 'options-reading.php' === $pagenow ) { $this->print_reading_options_js(); } elseif ( in_array( $pagenow, array( 'categories.php', 'edit-tags.php', 'edit.php', 'term.php', ), true ) && $current_language !== $default_language ) { $this->correct_status_links_js( $current_language ); } if ( 'edit-tags.php' === $pagenow || 'term.php' === $pagenow ) { $post_type = isset( $_GET['post_type'] ) ? '&post_type=' . esc_html( $_GET['post_type'] ) : ''; $admin_url = admin_url( 'edit-tags.php' ); $admin_url = add_query_arg( 'taxonomy', esc_js( $_GET['taxonomy'] ), $admin_url ); $admin_url = add_query_arg( 'lang', $current_language, $admin_url ); $admin_url = add_query_arg( 'message', 3, $admin_url ); if ( $post_type ) { $admin_url = add_query_arg( 'post_type', $post_type, $admin_url ); } ?> <script type="text/javascript"> addLoadEvent(function () { var edit_tag = jQuery('#edittag'); if (edit_tag.find('[name="_wp_original_http_referer"]').length && edit_tag.find('[name="_wp_http_referer"]').length) { edit_tag.find('[name="_wp_original_http_referer"]').val('<?php echo esc_js( $admin_url ); ?>'); } }); </script> <?php } $trid = filter_input( INPUT_GET, 'trid', FILTER_SANITIZE_NUMBER_INT ); $source_lang = null !== $trid ? filter_input( INPUT_GET, 'source_lang', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : null; if ( 'post-new.php' === $pagenow ) { if ( $trid ) { $translations = $this->post_translations->get_element_translations( false, $trid ); $sticky_posts = wpml_sticky_post_sync()->get_unfiltered_sticky_posts_option(); $is_sticky = false; foreach ( $translations as $t ) { if ( in_array( $t, $sticky_posts ) ) { $is_sticky = true; break; } } if ( $this->sitepress->get_setting( 'sync_ping_status' ) || $this->sitepress->get_setting( 'sync_comment_status' ) ) { $this->print_ping_and_comment_sync_js( $trid, $source_lang ); } if ( $this->sitepress->get_setting( 'sync_private_flag' ) && 'private' === $this->post_translations->get_original_post_status( $trid, $source_lang ) ) { ?> <script type="text/javascript">addLoadEvent(function () { jQuery('#visibility-radio-private').prop('checked', true); jQuery('#post-visibility-display').html('<?php echo icl_js_escape( __( 'Private', 'sitepress' ) ); ?>'); }); </script> <?php } if ( $this->sitepress->get_setting( 'sync_post_taxonomies' ) ) { $this->print_tax_sync_js(); } $custom_field_note = new WPML_Sync_Custom_Field_Note( $this->sitepress ); $custom_field_note->print_sync_copy_custom_field_note( $source_lang, $translations ); } ?> <?php if ( ! empty( $is_sticky ) && $this->sitepress->get_setting( 'sync_sticky_flag' ) ) : ?> <script type="text/javascript"> addLoadEvent( function () { var block_editor = wpml_get_block_editor(); if(block_editor){ document.addEventListener('DOMContentLoaded', function () { block_editor.then(function () { setTimeout(function () { wp.data.dispatch('core/editor').editPost({sticky: true}); }, 100); }); }); } else { jQuery('#sticky').prop('checked', true); var post_visibility_display = jQuery('#post-visibility-display'); post_visibility_display.html(post_visibility_display.html() + ', <?php echo icl_js_escape( __( 'Sticky', 'sitepress' ) ); ?>'); } } ); </script> <?php endif; ?> <?php } if ( ( 'page-new.php' === $pagenow || ( 'post-new.php' === $pagenow && isset( $_GET['post_type'] ) ) ) && ( $trid && ( $this->sitepress->get_setting( 'sync_page_template' ) || $this->sitepress->get_setting( 'sync_page_ordering' ) ) ) ) { $this->print_mo_sync_js( $trid, $source_lang ); } if ( $this->sitepress->is_post_edit_screen() && $this->sitepress->get_setting( 'sync_post_date' ) ) { $this->print_sync_date_js(); } if ( 'post-new.php' === $pagenow && isset( $_GET['trid'] ) && $sitepress->get_setting( 'sync_post_format' ) && function_exists( 'get_post_format' ) ) { $format = $this->post_translations->get_original_post_format( $trid, $source_lang ); ?> <script type="text/javascript"> addLoadEvent(function () { jQuery('#post-format-' + '<?php echo $format; ?>').prop('checked', true); }); </script> <?php } wp_enqueue_script( 'theme-preview' ); if ( 'languages' === $page_basename || 'string-translation' === $page_basename ) { wp_enqueue_script( 'wp-color-picker' ); wp_register_style( 'wpml-color-picker', ICL_PLUGIN_URL . '/res/css/colorpicker.css', array( 'wp-color-picker' ), ICL_SITEPRESS_VERSION ); wp_enqueue_style( 'wpml-color-picker' ); wp_enqueue_script( 'jquery-ui-sortable' ); } } /** * Prints JavaScript to display correct links on the posts by status break down and also fixes links * to category and tag pages * * @param string $current_language */ private function correct_status_links_js( $current_language ) { ?> <script type="text/javascript"> addLoadEvent( function () { jQuery(document).ready( function () { jQuery('.subsubsub>li a').each( function () { var h = jQuery(this).attr('href'); var urlg = -1 === h.indexOf('?') ? '?' : '&'; jQuery(this).attr('href', h + urlg + 'lang=<?php echo esc_js( $current_language ); ?>'); } ); jQuery('.column-categories a, .column-tags a, .column-posts a').each( function () { jQuery(this).attr('href', jQuery(this).attr('href') + '&lang=<?php echo esc_js( $current_language ); ?>'); } ); } ); } ); </script> <?php } /** * Prints the JavaScript for synchronizing page order or page template on the post edit screen. * * @param int $trid * @param string $source_lang */ private function print_mo_sync_js( $trid, $source_lang ) { $menu_order = $this->sitepress->get_setting( 'sync_page_ordering' ) ? $this->post_translations->get_original_menu_order( $trid, $source_lang ) : null; $page_template = $this->sitepress->get_setting( 'sync_page_template' ) ? get_post_meta( $this->post_translations->get_element_id( $source_lang, $trid ), '_wp_page_template', true ) : null; if ( $menu_order || $page_template ) { ?> <script type="text/javascript">addLoadEvent(function () { <?php if ( $menu_order ) { ?> jQuery('#menu_order').val(<?php echo esc_js( $menu_order ); ?>); <?php } if ( $page_template && 'default' !== $page_template ) { ?> jQuery('#page_template').val('<?php echo esc_js( $page_template ); ?>'); <?php } ?> });</script> <?php } } /** * Prints the JavaScript for synchronizing ping and comment status for a post translation on the post edit screen. * * @param int $trid * @param string $source_lang */ private function print_ping_and_comment_sync_js( $trid, $source_lang ) { ?> <script type="text/javascript">addLoadEvent(function () { var comment_status = jQuery('#comment_status'); var ping_status = jQuery('#ping_status'); <?php if ( $this->sitepress->get_setting( 'sync_comment_status' ) ) : ?> <?php if ( $this->post_translations->get_original_comment_status( $trid, $source_lang ) === 'open' ) : ?> comment_status.prop('checked', true); <?php else : ?> comment_status.prop('checked', false); <?php endif; ?> <?php endif; ?> <?php if ( $this->sitepress->get_setting( 'sync_ping_status' ) ) : ?> <?php if ( $this->post_translations->get_original_ping_status( $trid, $source_lang ) === 'open' ) : ?> ping_status.prop('checked', true); <?php else : ?> ping_status.prop('checked', false); <?php endif; ?> <?php endif; ?> });</script> <?php } /** * Prints the JavaScript for disabling editing the post_date on the post edit screen, * when the synchronize post_date for translations setting is activated. */ private function print_sync_date_js() { $post_id = $this->get_current_req_post_id(); if ( $post_id !== null ) { $original_id = $this->post_translations->get_original_element( $post_id ); if ( $original_id && (int) $original_id !== (int) $post_id ) { $original_date = get_post_field( 'post_date', $original_id ); $exp = explode( ' ', $original_date ); list( $aa, $mm, $jj ) = explode( '-', $exp[0] ); list( $hh, $mn, $ss ) = explode( ':', $exp[1] ); ?> <script type="text/javascript"> addLoadEvent( function () { jQuery('#aa').val('<?php echo esc_js( $aa ); ?>').attr('readonly', 'readonly'); jQuery('#mm').val('<?php echo esc_js( $mm ); ?>').attr('disabled', 'disabled').attr('id', 'mm-disabled').attr('name', 'mm-disabled'); // create a hidden element for month because we wont get anything returned from the disabled month dropdown. jQuery('<input type="hidden" id="mm" name="mm" value="<?php echo $mm; ?>" />').insertAfter('#mm-disabled'); jQuery('#jj').val('<?php echo esc_js( $jj ); ?>').attr('readonly', 'readonly'); jQuery('#hh').val('<?php echo esc_js( $hh ); ?>').attr('readonly', 'readonly'); jQuery('#mn').val('<?php echo esc_js( $mn ); ?>').attr('readonly', 'readonly'); jQuery('#ss').val('<?php echo esc_js( $ss ); ?>').attr('readonly', 'readonly'); var timestamp = jQuery('#timestamp'); timestamp.find('b').append(( '<span> <?php esc_html_e( 'Copied From the Original', 'sitepress' ); ?></span>')); timestamp.next().html('<span style="margin-left:1em;"><?php esc_html_e( 'Edit', 'sitepress' ); ?></span>'); }); </script> <?php } } } private function print_tax_sync_js() { $post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; $source_lang = isset( $_GET['source_lang'] ) ? filter_input( INPUT_GET, 'source_lang', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) : $this->sitepress->get_default_language(); $trid = filter_var( $_GET['trid'], FILTER_SANITIZE_NUMBER_INT ); $translations = $this->sitepress->get_element_translations( $trid, 'post_' . $post_type ); if ( ! isset( $translations[ $source_lang ] ) ) { return; } $current_lang = $this->sitepress->get_current_language(); $translatable_taxs = $this->sitepress->get_translatable_taxonomies( true, $post_type ); $all_taxs = get_object_taxonomies( $post_type ); $js = array(); $this->sitepress->switch_lang( $source_lang ); foreach ( $all_taxs as $tax ) { $tax_detail = get_taxonomy( $tax ); $terms = get_the_terms( $translations[ $source_lang ]->element_id, $tax ); $term_names = array(); if ( $terms ) { foreach ( $terms as $term ) { if ( $tax_detail->hierarchical ) { $term_id = in_array( $tax, $translatable_taxs ) ? $this->term_translations->term_id_in( $term->term_id, $current_lang, false ) : $term->term_id; $js[] = "jQuery('#in-" . $tax . '-' . $term_id . "').prop('checked', true);"; } else { if ( in_array( $tax, $translatable_taxs ) ) { $term_id = $this->term_translations->term_id_in( $term->term_id, $current_lang, false ); if ( $term_id ) { $term = get_term( $term_id, $tax ); $term_names[] = esc_js( $term->name ); } } else { $term_names[] = esc_js( $term->name ); } } } } if ( $term_names ) { $js[] = "jQuery('#" . esc_js( $tax ) . ".taghint').css('visibility','hidden');"; $js[] = "jQuery('#new-tag-" . esc_js( $tax ) . "').val('" . esc_js( join( ', ', $term_names ) ) . "');"; } } $this->sitepress->switch_lang( null ); if ( $js ) { ?> <script type="text/javascript"> // <![CDATA[ addLoadEvent(function(){ <?php echo join( PHP_EOL, $js ); ?> jQuery().ready(function() { jQuery(".tagadd").click(); jQuery('html, body').prop({scrollTop:0}); jQuery('#title').focus(); }); }); // ]]> </script> <?php } } private function get_current_req_post_id() { return isset( $_GET['post'] ) ? filter_var( $_GET['post'], FILTER_SANITIZE_NUMBER_INT ) : null; } private function print_reading_options_js() { list( $warn_home, $warn_posts ) = $this->verify_home_and_blog_pages_translations(); if ( $warn_home || $warn_posts ) { ?> <script type="text/javascript"> addLoadEvent(function () { jQuery('input[name="show_on_front"]').parent().parent().parent().parent().append('<?php echo str_replace( "'", "\\'", $warn_home . $warn_posts ); ?>'); }); </script> <?php } } function wpml_css_setup() { if ( isset( $_GET['page'] ) ) { $page = basename( $_GET['page'] ); $page_basename = str_replace( '.php', '', $page ); $page_basename = preg_replace( '/[^\w-]/', '', $page_basename ); } wp_enqueue_style( 'sitepress-style', ICL_PLUGIN_URL . '/res/css/style.css', array(), ICL_SITEPRESS_VERSION ); if ( isset( $page_basename ) && file_exists( WPML_PLUGIN_PATH . '/res/css/' . $page_basename . '.css' ) ) { wp_enqueue_style( 'sitepress-' . $page_basename, ICL_PLUGIN_URL . '/res/css/' . $page_basename . '.css', array(), ICL_SITEPRESS_VERSION ); } wp_enqueue_style( 'wpml-dialog' ); wp_enqueue_style( 'otgs-icons' ); wp_enqueue_style( 'wpml-wizard' ); wp_enqueue_style( 'thickbox' ); } private function verify_home_and_blog_pages_translations() { $warn_home = $warn_posts = ''; $page_on_front = get_option( 'page_on_front' ); if ( 'page' === get_option( 'show_on_front' ) && $page_on_front ) { $warn_home = $this->missing_page_warning( $page_on_front, __( 'Your home page does not exist or its translation is not published in %s.', 'sitepress' ) ); } $page_for_posts = get_option( 'page_for_posts' ); if ( $page_for_posts ) { $warn_posts = $this->missing_page_warning( $page_for_posts, __( 'Your blog page does not exist or its translation is not published in %s.', 'sitepress' ), 'margin-top:4px;' ); } return array( $warn_home, $warn_posts ); } /** * @param int $original_page_id * @param string $label * @param string $additional_css * * @return string */ private function missing_page_warning( $original_page_id, $label, $additional_css = '' ) { $warn_posts = ''; if ( $original_page_id ) { $page_posts_translations = $this->post_translations->get_element_translations( $original_page_id ); $missing_posts = array(); $active_languages = $this->sitepress->get_active_languages(); foreach ( $active_languages as $lang ) { if ( ! isset( $page_posts_translations[ $lang['code'] ] ) || get_post_status( $page_posts_translations[ $lang['code'] ] ) !== 'publish' ) { $missing_posts[] = $lang['display_name']; } } if ( ! empty( $missing_posts ) ) { $warn_posts = '<div class="icl_form_errors" style="font-weight:bold;' . $additional_css . '">'; $warn_posts .= sprintf( $label, join( ', ', $missing_posts ) ); $warn_posts .= '<br />'; $warn_posts .= '<a href="' . get_edit_post_link( $original_page_id ) . '">' . __( 'Edit this page to add translations', 'sitepress' ) . '</a>'; $warn_posts .= '</div>'; } } return $warn_posts; } } menu-elements/class-wpml-tm-post-link-factory.php 0000755 00000002301 14720342453 0016121 0 ustar 00 <?php /** * Class WPML_TM_Post_Link_Factory * * Creates post links for the TM dashboard and the translation queue */ class WPML_TM_Post_Link_Factory { /** @var SitePress $sitepress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * Link to the front end, link text is the post title * * @param int $post_id * * @return string */ public function view_link( $post_id ) { return (string) ( new WPML_TM_Post_View_Link_Title( $this->sitepress, (int) $post_id ) ); } /** * Link to the front end, link text is given by the anchor * * @param int $post_id * @param string $anchor * * @return string */ public function view_link_anchor( $post_id, $anchor, $target = '' ) { return (string) ( new WPML_TM_Post_View_Link_Anchor( $this->sitepress, (int) $post_id, $anchor, $target ) ); } /** * Link to the backend, link text is given by the anchor * * @param int $post_id * @param string $anchor * * @return string */ public function edit_link_anchor( $post_id, $anchor ) { return (string) ( new WPML_TM_Post_Edit_Link_Anchor( $this->sitepress, (int) $post_id, $anchor ) ); } } menu-elements/class-wpml-tm-post-edit-link-anchor.php 0000755 00000000524 14720342453 0016654 0 ustar 00 <?php /** * Class WPML_TM_Post_Edit_Link_Anchor * * Creates post links with a given anchor text, pointing at the back-end * post edit view */ class WPML_TM_Post_Edit_Link_Anchor extends WPML_TM_Post_Link_Anchor { protected function link_target() { return $this->sitepress->get_wp_api()->get_edit_post_link( $this->post_id ); } } menu-elements/class-wpml-tm-post-view-link-anchor.php 0000755 00000000513 14720342453 0016677 0 ustar 00 <?php /** * Class WPML_TM_Post_View_Link_Anchor * * Creates post links with a given anchor text, pointing at the front-end * post view */ class WPML_TM_Post_View_Link_Anchor extends WPML_TM_Post_Link_Anchor { protected function link_target() { return $this->sitepress->get_wp_api()->get_permalink( $this->post_id ); } } menu-elements/class-wpml-support-page.php 0000755 00000005106 14720342453 0014552 0 ustar 00 <?php class WPML_Support_Page { /** * @var \WPML_WP_API */ private $wpml_wp_api; /** * WPML_Support_Page constructor. * * @param WPML_WP_API $wpml_wp_api */ public function __construct( &$wpml_wp_api ) { $this->wpml_wp_api = &$wpml_wp_api; $this->init_hooks(); } public function display_compatibility_issues() { $message = $this->get_message(); $this->render_message( $message ); } /** * @return string */ private function get_message() { $message = ''; if ( ! $this->wpml_wp_api->extension_loaded( 'libxml' ) ) { $message .= $this->missing_extension_message(); if ( $this->wpml_wp_api->version_compare_naked( $this->wpml_wp_api->phpversion(), '7.0.0', '>=' ) ) { $message .= $this->missing_extension_message_for_php7(); } $message .= $this->contact_the_admin(); return $message; } return $message; } private function init_hooks() { add_action( 'wpml_support_page_after', array( $this, 'display_compatibility_issues' ) ); } /** * @return string */ private function missing_extension_message() { return '<p class="missing-extension-message">' . esc_html__( 'It looks like the %1$s extension, which is required by WPML, is not installed. Please refer to this link to know how to install this extension: %2$s.', 'sitepress' ) . '</p>'; } /** * @return string */ private function missing_extension_message_for_php7() { return '<p class="missing-extension-message-for-php7">' . esc_html__( 'You are using PHP 7: in some cases, the extension might have been removed during a system update. In this case, please see %3$s.', 'sitepress' ) . '</p>'; } /** * @return string */ private function contact_the_admin() { return '<p class="contact-the-admin">' . esc_html__( 'You may need to contact your server administrator or your hosting company to install this extension.', 'sitepress' ) . '</p>'; } /** * @param string $message */ private function render_message( $message ) { if ( $message ) { $libxml_text = '<strong>libxml</strong>'; $libxml_link = '<a href="http://php.net/manual/en/book.libxml.php" target="_blank">http://php.net/manual/en/book.libxml.php</a>'; $libxml_php7_link = '<a href="https://wpml.org/errata/php-7-possible-issues-simplexml/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore" target="_blank">PHP 7: possible issues with simplexml</a>'; echo '<div class="icl-admin-message icl-admin-message-icl-admin-message-warning icl-admin-message-warning error">'; echo sprintf( $message, $libxml_text, $libxml_link, $libxml_php7_link ); echo '</div>'; } } } menu-elements/class-wpml-taxonomy-translation.php 0000755 00000002100 14720342453 0016325 0 ustar 00 <?php /** * class WPML_Taxonomy_Translation * * Used by WCML so be careful about modifications to the contructor */ class WPML_Taxonomy_Translation { private $ui = null; /** * WPML_Taxonomy_Translation constructor. * * @param string $taxonomy if given renders a specific taxonomy, * otherwise renders a placeholder * @param bool[] $args array with possible indices: * 'taxonomy_selector' => bool .. whether or not to show the taxonomy selector * @param WPML_UI_Screen_Options_Factory $screen_options_factory */ public function __construct( $taxonomy = '', $args = array(), $screen_options_factory = null ) { global $sitepress; $this->ui = new WPML_Taxonomy_Translation_UI( $sitepress, $taxonomy, $args, $screen_options_factory ); } /** * Echos the HTML that serves as an entry point for the taxonomy translation * screen and enqueues necessary js. */ public function render() { $this->ui->render(); } } menu-elements/class-wpml-custom-columns-factory.php 0000755 00000000463 14720342453 0016562 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Custom_Columns_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { private $hooks; /** * @return WPML_Custom_Columns */ public function create() { global $sitepress; return new WPML_Custom_Columns( $sitepress ); } } menu-elements/class-wpml-custom-types-translation-ui.php 0000755 00000007214 14720342453 0017551 0 ustar 00 <?php class WPML_Custom_Types_Translation_UI { /** @var array */ private $translation_option_class_names; /** @var WPML_Translation_Modes $translation_modes */ private $translation_modes; /** @var WPML_UI_Unlock_Button $unlock_button_ui */ private $unlock_button_ui; public function __construct( WPML_Translation_Modes $translation_modes, WPML_UI_Unlock_Button $unlock_button_ui ) { $this->translation_modes = $translation_modes; $this->unlock_button_ui = $unlock_button_ui; $this->translation_option_class_names = array( WPML_CONTENT_TYPE_TRANSLATE => 'translate', WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED => 'display-as-translated', WPML_CONTENT_TYPE_DONT_TRANSLATE => 'dont-translate', ); } public function render_custom_types_header_ui( $type_label ) { ?> <header class="wpml-flex-table-header wpml-flex-table-sticky"> <div class="wpml-flex-table-row"> <div class="wpml-flex-table-cell name"> <?php echo $type_label; ?> </div> <?php $this->renderModeLabels(); ?> </div> </header> <?php } public function render_row( $content_label, $name, $content_slug, $disabled, $current_translation_mode, $unlocked, $content_label_singular = false ) { $radio_name = esc_attr( $name . '[' . $content_slug . ']' ); $unlocked_name = esc_attr( $name . '_unlocked[' . $content_slug . ']' ); ?> <div class="wpml-flex-table-cell name"> <?php $this->unlock_button_ui->render( $disabled, $unlocked, $radio_name, $unlocked_name ); echo $content_label; ?> (<i><?php echo esc_html( $content_slug ); ?></i>) </div> <?php foreach ( $this->translation_modes->get_options() as $value => $label ) { $disabled_state_for_mode = self::get_disabled_state_for_mode( $unlocked, $disabled, $value, $content_slug ) ?> <div class="wpml-flex-table-cell text-center <?php echo $this->translation_option_class_names[ $value ]; ?>" data-header="<?php echo esc_attr( $label ); ?>"> <input type="radio" name="<?php echo $radio_name; ?>" class="js-custom-post-mode" value="<?php echo esc_attr( $value ); ?>" <?php echo $disabled_state_for_mode['html_attribute']; ?> data-slug="<?php esc_attr_e( $content_slug ) ?>" data-name="<?php esc_attr_e( $content_label ) ?>" data-singular-name="<?php esc_attr_e( $content_label_singular ?: $content_label ) ?>" <?php checked( $value, $current_translation_mode ); ?> /> <?php if ( $disabled_state_for_mode['reason_message'] ) { ?> <p class="otgs-notice warning js-disabled-externally"><?php echo wp_kses_post( $disabled_state_for_mode['reason_message'] ); ?></p> <?php } ?> </div> <?php } } /** * @param bool $unlocked * @param bool $disabled * @param int $mode * @param string $content_slug * * @return array */ public static function get_disabled_state_for_mode( $unlocked, $disabled, $mode, $content_slug ) { $disabled_state_for_mode = array( 'state' => ! $unlocked && $disabled, 'reason_message' => '', ); $disabled_state_for_mode = apply_filters( 'wpml_disable_translation_mode_radio', $disabled_state_for_mode, $mode, $content_slug ); $disabled_state_for_mode['html_attribute'] = $disabled_state_for_mode['state'] ? 'disabled="disabled"' : ''; return $disabled_state_for_mode; } public function renderModeLabels() { foreach ( $this->translation_modes->get_options() as $value => $label ) { ?> <div class="wpml-flex-table-cell text-center <?php echo $this->translation_option_class_names[ $value ]; ?>"> <?php echo wp_kses_post( $label ); ?> </div> <?php } } } menu-elements/class-wpml-tm-post-link.php 0000755 00000000574 14720342453 0014466 0 ustar 00 <?php abstract class WPML_TM_Post_Link { /** @var SitePress $sitepress */ protected $sitepress; /** @var int $post */ protected $post_id; /** * WPML_TM_Post_Link constructor. * * @param SitePress $sitepress * @param int $post_id */ public function __construct( $sitepress, $post_id ) { $this->sitepress = $sitepress; $this->post_id = $post_id; } } menu-elements/class-wpml-custom-columns.php 0000755 00000013623 14720342453 0015117 0 ustar 00 <?php use WPML\Element\API\TranslationsRepository; /** * Class WPML_Custom_Columns */ class WPML_Custom_Columns implements IWPML_Action { const COLUMN_KEY = 'icl_translations'; const CUSTOM_COLUMNS_PRIORITY = 1010; /** * @param SitePress $sitepress */ private $sitepress; /** * @var WPML_Post_Status_Display */ public $post_status_display; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @param array $columns * * @return array */ public function add_posts_management_column( $columns ) { $new_columns = $columns; if ( 'trash' === get_query_var( 'post_status' ) ) { return $columns; } $flags_column = $this->get_flags_column(); if ( $flags_column ) { $new_columns = []; foreach ( $columns as $column_key => $column_content ) { $new_columns[ $column_key ] = $column_content; if ( ( 'title' === $column_key || 'name' === $column_key ) && ! isset( $new_columns[ self::COLUMN_KEY ] ) ) { $new_columns[ self::COLUMN_KEY ] = $flags_column; } } } return $new_columns; } public function get_flags_column() { $active_languages = $this->get_filtered_active_languages(); if ( count( $active_languages ) <= 1 ) { return ''; } $current_language = $this->sitepress->get_current_language(); unset( $active_languages[ $current_language ] ); if ( ! count( $active_languages ) ) { return ''; } $flags_column = '<span class="screen-reader-text">' . esc_html__( 'Languages', 'sitepress' ) . '</span>'; foreach ( $active_languages as $language_data ) { $flags_column .= $this->get_flag_img( $language_data ); } return $flags_column; } private function get_flag_img( $language_data ) { $url = $this->sitepress->get_flag_url( $language_data['code'] ); if ( $url !== '' ) { return '<img src="' . esc_url( $url ) . '" width="18" height="12" alt="' . esc_attr( $language_data['display_name'] ) . '" title="' . esc_attr( $language_data['display_name'] ) . '" style="margin:2px" />'; } else { return $language_data['code']; } } /** * Add posts management column. * * @param string $column_name * @param int|null $post_id */ public function add_content_for_posts_management_column( $column_name, $post_id = null ) { global $post; if ( ! $post_id ) { $post_id = $post->ID; } if ( self::COLUMN_KEY !== $column_name ) { return; } $active_languages = $this->get_filtered_active_languages(); if ( null === $this->post_status_display ) { $this->post_status_display = new WPML_Post_Status_Display( $active_languages ); } unset( $active_languages[ $this->sitepress->get_current_language() ] ); foreach ( $active_languages as $language_data ) { $icon_html = $this->post_status_display->get_status_html( $post_id, $language_data['code'] ); echo $icon_html; } } /** * Check translation management column screen option. * * @param string $post_type Current post type. * * @return bool */ public function show_management_column_content( $post_type ) { $user = get_current_user_id(); $hidden_columns = get_user_meta( $user, 'manageedit-' . $post_type . 'columnshidden', true ); if ( '' === $hidden_columns ) { $is_visible = (bool) apply_filters( 'wpml_hide_management_column', true, $post_type ); if ( false === $is_visible ) { update_user_meta( $user, 'manageedit-' . $post_type . 'columnshidden', array( self::COLUMN_KEY ) ); } return $is_visible; } return ! is_array( $hidden_columns ) || ! in_array( self::COLUMN_KEY, $hidden_columns, true ); } /** * Get list of active languages. * * @return array */ private function get_filtered_active_languages() { $active_languages = $this->sitepress->get_active_languages(); return apply_filters( 'wpml_active_languages_access', $active_languages, array( 'action' => 'edit' ) ); } public function add_hooks() { add_action( 'admin_init', array( $this, 'add_custom_columns_hooks', ), self::CUSTOM_COLUMNS_PRIORITY ); // accommodate Types init@999 } /** * Add custom columns hooks. */ public function add_custom_columns_hooks() { $post_type = isset( $_REQUEST['post_type'] ) ? $_REQUEST['post_type'] : 'post'; if ( $post_type && $this->has_custom_columns() && array_key_exists( $post_type, $this->sitepress->get_translatable_documents() ) ) { add_filter( 'manage_' . $post_type . '_posts_columns', array( $this, 'add_posts_management_column', ) ); $show_management_column_content = $this->show_management_column_content( $post_type ); if ( $show_management_column_content ) { if ( is_post_type_hierarchical( $post_type ) ) { add_action( 'manage_pages_custom_column', array( $this, 'add_content_for_posts_management_column', ) ); } add_action( 'manage_posts_custom_column', array( $this, 'add_content_for_posts_management_column', ) ); add_action( 'manage_posts_custom_column', [ $this, 'preloadTranslationData' ], 1, 0 ); add_action( 'manage_pages_custom_column', [ $this, 'preloadTranslationData' ], 1, 0 ); } } } public function preloadTranslationData() { static $loaded = false; if ( $loaded ) { return; } global $wp_query; $posts = $wp_query->posts; if ( is_array( $posts ) ) { TranslationsRepository::preloadForPosts( $posts ); $loaded = true; } } /** * Check if we need to add custom columns on page. * * @return bool */ private function has_custom_columns() { global $pagenow; if ( 'edit.php' === $pagenow || 'edit-pages.php' === $pagenow || ( 'admin-ajax.php' === $pagenow && ( ( array_key_exists( 'action', $_POST ) && 'inline-save' === filter_var( $_POST['action'] ) ) || ( array_key_exists( 'action', $_GET ) && 'fetch-list' === filter_var( $_GET['action'] ) ) ) ) ) { return true; } return false; } } menu-elements/class-wpml-user-options-menu.php 0000755 00000014435 14720342453 0015542 0 ustar 00 <?php /** * Class WPML_User_Options_Menu * Renders the WPML UI elements on the WordPress user profile edit screen */ class WPML_User_Options_Menu { /** @var WP_User */ private $current_user; /** @var SitePress */ private $sitepress; /** * @var string */ private $user_language; /** * @var string */ private $user_admin_def_lang; /** * @var string */ private $admin_default_language; /** * @var string */ private $admin_language; /** * @var mixed[] */ private $all_languages; /** * WPML_User_Options_Menu constructor. * * @param SitePress $sitepress * @param WP_User $current_user */ public function __construct( SitePress $sitepress, WP_User $current_user ) { $this->sitepress = $sitepress; $this->current_user = $current_user; $this->user_language = $this->sitepress->get_wp_api()->get_user_meta( $this->current_user->ID, 'icl_admin_language', true ); $this->user_admin_def_lang = $this->sitepress->get_setting( 'admin_default_language' ); $this->user_admin_def_lang = $this->user_admin_def_lang === '_default_' ? $this->sitepress->get_default_language() : $this->user_admin_def_lang; $lang = $this->sitepress->get_language_details( $this->user_admin_def_lang ); $this->admin_default_language = is_array( $lang ) && isset( $lang['display_name'] ) ? $lang['display_name'] : $this->user_admin_def_lang; $this->admin_language = $this->sitepress->get_admin_language(); $user_language_for_all_languages = $this->user_admin_def_lang; if ( $this->user_language ) { $user_language_for_all_languages = $this->user_language; } $this->all_languages = $this->sitepress->get_languages( $user_language_for_all_languages ); } /** * @return string the html for the user profile edit screen element WPML * adds to it */ public function render() { $wp_api = $this->sitepress->get_wp_api(); $hide_wpml_languages = $wp_api->version_compare_naked( get_bloginfo( 'version' ), '4.7', '>=' ) ? 'style="display: none"' : ''; ob_start(); $admin_default_language_selected = $this->user_language === $this->user_admin_def_lang; ?> <tr class="user-language-wrap"> <th colspan="2"><h3><a name="wpml"></a><?php esc_html_e( 'WPML language settings', 'sitepress' ); ?></h3></th> </tr> <tr class="user-language-wrap" <?php echo $hide_wpml_languages; ?>> <th><label for="icl_user_admin_language"><?php esc_html_e( 'Select your language:', 'sitepress' ); ?></label></th> <td> <select id="icl_user_admin_language" name="icl_user_admin_language"> <option value=""<?php selected( true, $admin_default_language_selected ); ?>> <?php echo esc_html( sprintf( __( 'Default admin language (currently %s)', 'sitepress' ), $this->admin_default_language ) ); ?> </option> <?php foreach ( array( true, false ) as $active ) { foreach ( (array) $this->all_languages as $lang_code => $al ) { if ( (bool) $al['active'] === $active ) { $current_language_selected = $this->user_language === $lang_code; $language_name = $al['display_name']; if ( $this->admin_language !== $lang_code ) { $language_name .= ' (' . $al['native_name'] . ')'; } ?> <option value="<?php echo esc_attr( $lang_code ); ?>"<?php selected( true, $current_language_selected ); ?>> <?php echo esc_html( $language_name ); ?> </option> <?php } } } $use_admin_language_for_edit = $wp_api->get_user_meta( $this->current_user->ID, 'icl_admin_language_for_edit', true ) ?> </select> <span class="description"> <?php esc_html_e( 'this will be your admin language and will also be used for translating comments.', 'sitepress' ); ?> </span> </td> </tr> <?php $this->get_hidden_languages_options( $use_admin_language_for_edit ); do_action( 'wpml_user_profile_options', $this->current_user->ID ); return ob_get_clean(); } /** * @param bool $use_admin_language_for_edit */ private function get_hidden_languages_options( $use_admin_language_for_edit ) { /** * Filters a condition if current user can see hidden languages options in profile settings * * @params bool $show_hidden_languages_options */ $show_hidden_languages_options = apply_filters( 'wpml_show_hidden_languages_options', current_user_can( 'manage_options' ) ); if ( $show_hidden_languages_options ) { $hidden_languages = $this->sitepress->get_setting( 'hidden_languages' ); $display_hidden_languages = get_user_meta( $this->current_user->ID, 'icl_show_hidden_languages', true ); ?> <tr class="user-language-wrap"> <th><?php esc_html_e( 'Editing language:', 'sitepress' ); ?></th> <td> <input type="checkbox" name="icl_admin_language_for_edit" id="icl_admin_language_for_edit" value="1" <?php checked( true, $use_admin_language_for_edit ); ?> /> <label for="icl_admin_language_for_edit"><?php esc_html_e( 'Set admin language as editing language.', 'sitepress' ); ?></label> </td> </tr> <tr class="user-language-wrap"> <th><?php esc_html_e( 'Hidden languages:', 'sitepress' ); ?></th> <td> <p> <?php if ( ! empty( $hidden_languages ) ) { if ( 1 === count( $hidden_languages ) ) { echo esc_html( sprintf( __( '%s is currently hidden to visitors.', 'sitepress' ), $this->all_languages[ end( $hidden_languages ) ]['display_name'] ) ); } else { $hidden_languages_array = array(); foreach ( (array) $hidden_languages as $l ) { $hidden_languages_array[] = $this->all_languages[ $l ]['display_name']; } $hidden_languages = implode( ', ', $hidden_languages_array ); echo esc_html( sprintf( __( '%s are currently hidden to visitors.', 'sitepress' ), $hidden_languages ) ); } } else { esc_html_e( 'All languages are currently displayed. Choose what to do when site languages are hidden.', 'sitepress' ); } ?> </p> <p> <input id="icl_show_hidden_languages" name="icl_show_hidden_languages" type="checkbox" value="1" <?php checked( true, $display_hidden_languages ); ?> /> <label for="icl_show_hidden_languages"><?php esc_html_e( 'Display hidden languages', 'sitepress' ); ?></label> </p> </td> </tr> <?php } } } menu-elements/class-wpml-inactive-content-render.php 0000755 00000002034 14720342453 0016650 0 ustar 00 <?php class WPML_Inactive_Content_Render extends WPML_Twig_Template_Loader { const TEMPLATE = 'inactive-content.twig'; /** @var WPML_Inactive_Content $inactive_content */ private $inactive_content; public function __construct( WPML_Inactive_Content $inactive_content, array $paths ) { $this->inactive_content = $inactive_content; parent::__construct( $paths ); } public function render() { $model = array( 'content' => $this->inactive_content, 'strings' => [ 'title' => __( "You deactivated the following language(s) from your site, but there are still some existing translations saved in your database.", 'sitepress' ), 'more_info' => __( "If you don't plan to activate this language again, you can delete the associated content from your database.", "sitepress" ), 'language' => __( 'Language', 'sitepress' ), 'delete_translated_content' => __( 'Delete content', 'sitepress' ) ], ); return $this->get_template()->show( $model, self::TEMPLATE ); } } menu-elements/class-wpml-taxonomy-translation-screen-data.php 0000755 00000016220 14720342453 0020521 0 ustar 00 <?php class WPML_Taxonomy_Translation_Screen_Data extends WPML_WPDB_And_SP_User { const WPML_TAXONOMY_TRANSLATION_MAX_TERMS_RESULTS_SET = 1000; /** @var string $taxonomy */ private $taxonomy; /** * WPML_Taxonomy_Translation_Screen_Data constructor. * * @param SitePress $sitepress * @param string $taxonomy */ public function __construct( &$sitepress, $taxonomy ) { $wpdb = $sitepress->wpdb(); parent::__construct( $wpdb, $sitepress ); $this->taxonomy = $taxonomy; } /** * The returned array from this function is indexed as follows. * It holds an array of all terms to be displayed under [terms] * and the count of all terms matching the filter under [count]. * * The array under [terms] itself is index as such: * [trid][lang] * * It holds in itself the terms objects of the to be displayed terms. * These are ordered by their names alphabetically. * Also their objects are amended by the index $term->translation_of holding the term_taxonomy_id of their original element * and their level under $term->level in case of hierarchical terms. * * Also the index [trid][source_lang] holds the source language of the term group. * * @return array */ public function terms() { $terms_data = array( 'truncated' => 0, 'terms' => array(), ); $attributes_to_select = array(); $icl_translations_table_name = $this->wpdb->prefix . 'icl_translations'; $attributes_to_select[ $this->wpdb->terms ] = array( 'alias' => 't', 'vars' => array( 'name', 'slug', 'term_id' ), ); $attributes_to_select[ $this->wpdb->term_taxonomy ] = array( 'alias' => 'tt', 'vars' => array( 'term_taxonomy_id', 'parent', 'description', ), ); $attributes_to_select[ $icl_translations_table_name ] = array( 'alias' => 'i', 'vars' => array( 'language_code', 'trid', 'source_language_code', ), ); $query_limit_cap = defined( 'WPML_TAXONOMY_TRANSLATION_MAX_TERMS_RESULTS_SET' ) ? WPML_TAXONOMY_TRANSLATION_MAX_TERMS_RESULTS_SET : self::WPML_TAXONOMY_TRANSLATION_MAX_TERMS_RESULTS_SET; $join_statements = array(); $as = $this->alias_statements( $attributes_to_select ); $join_statements[] = "{$as['t']} JOIN {$as['tt']} ON tt.term_id = t.term_id"; $join_statements[] = "{$as['i']} ON i.element_id = tt.term_taxonomy_id"; $from_clause = $this->build_from_clause( join( ' JOIN ', $join_statements ), $attributes_to_select, $query_limit_cap ); $select_clause = $this->build_select_vars( $attributes_to_select ); $where_clause = $this->build_where_clause( $attributes_to_select ); $full_statement = "SELECT SQL_CALC_FOUND_ROWS {$select_clause} FROM {$from_clause} WHERE {$where_clause}"; $all_terms = $this->wpdb->get_results( $full_statement ); $real_terms_count = (int) $this->wpdb->get_var( 'SELECT FOUND_ROWS()' ); if ( $real_terms_count > $query_limit_cap ) { $terms_data['truncated'] = 1; } if ( ! is_array( $all_terms ) ) { return $terms_data; } if ( function_exists( 'get_term_meta' ) ) { $all_terms = $this->add_metadata( $all_terms ); } if ( $all_terms ) { $terms_data['terms'] = $this->order_terms_list( $this->index_terms_array( $all_terms ) ); } return $terms_data; } /** * @param array $terms * Turn a numerical array of terms objects into an associative once, * holding the same terms, but indexed by their term_id. * * @return array */ private function index_terms_array( $terms ) { $terms_indexed = array(); foreach ( $terms as $term ) { $terms_indexed[ $term->term_id ] = $term; } return $terms_indexed; } /** * @param array $trid_group * @param array $terms * Transforms the term arrays generated by the Translation Tree class and turns them into * standard WordPress terms objects. * * @return mixed */ private function set_language_information( $trid_group, $terms ) { foreach ( $trid_group['elements'] as $lang => $term ) { $term_object = $terms[ $term['term_id'] ]; $term_object->level = $term['level']; $trid_group[ $lang ] = $term_object; } unset( $trid_group['elements'] ); return $trid_group; } /** * Orders a list of terms alphabetically and hierarchy-wise * * @param array $terms * * @return array */ private function order_terms_list( $terms ) { $terms_tree = new WPML_Translation_Tree( $this->sitepress, $this->taxonomy, $terms ); $ordered_terms = $terms_tree->get_alphabetically_ordered_list(); foreach ( $ordered_terms as $key => $trid_group ) { $ordered_terms[ $key ] = self::set_language_information( $trid_group, $terms ); } return $ordered_terms; } /** * @param array $selects * Generates a list of to be selected variables in an sql query. * * @return string */ private function build_select_vars( $selects ) { $output = ''; if ( is_array( $selects ) ) { $coarse_selects = array(); foreach ( $selects as $select ) { $vars = $select['vars']; $table = $select['alias']; foreach ( $vars as $key => $var ) { $vars[ $key ] = $table . '.' . $var; } $coarse_selects[] = join( ', ', $vars ); } $output = join( ', ', $coarse_selects ); } return $output; } /** * @param array $selects * Returns an array of alias statements to be used in SQL queries with joins. * * @return array */ private function alias_statements( $selects ) { $output = array(); foreach ( $selects as $key => $select ) { $output[ $select['alias'] ] = $key . ' AS ' . $select['alias']; } return $output; } private function build_where_clause( $selects ) { $where_clauses[] = $selects[ $this->wpdb->term_taxonomy ]['alias'] . $this->wpdb->prepare( '.taxonomy = %s ', $this->taxonomy ); $where_clauses[] = $selects[ $this->wpdb->prefix . 'icl_translations' ]['alias'] . $this->wpdb->prepare( '.element_type = %s ', 'tax_' . $this->taxonomy ); $where_clause = join( ' AND ', $where_clauses ); return $where_clause; } /** * @param string $from * @param array $selects * @param int $limit * * @return string */ private function build_from_clause( $from, $selects, $limit ) { return $from . sprintf( " INNER JOIN ( SELECT trid FROM %s WHERE element_type = '%s' AND source_language_code IS NULL LIMIT %d ) lm on lm.trid = %s.trid", $this->wpdb->prefix . 'icl_translations', 'tax_' . $this->taxonomy, $limit, $selects[ $this->wpdb->prefix . 'icl_translations' ]['alias'] ); } /** * @param array $all_terms * * @return array */ private function add_metadata( $all_terms ) { $setting_factory = $this->sitepress->core_tm()->settings_factory(); foreach ( $all_terms as $term ) { $meta_data = get_term_meta( $term->term_id ); foreach ( $meta_data as $meta_key => $meta_data ) { if ( in_array( $setting_factory->term_meta_setting( $meta_key )->status(), array( WPML_TRANSLATE_CUSTOM_FIELD, WPML_COPY_ONCE_CUSTOM_FIELD ), true ) ) { $term->meta_data[ $meta_key ] = $meta_data; } } } return $all_terms; } } menu-elements/PostLinkUrl.php 0000755 00000001204 14720342453 0012265 0 ustar 00 <?php namespace WPML\TM\Menu; use SitePress; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\User; use WPML\LIB\WP\Post; use function WPML\FP\pipe; class PostLinkUrl { /** * @param int $postId * * @return string */ public function viewLinkUrl( $postId ) { return Maybe::of( $postId ) ->map( Post::get() ) ->reject( pipe( Obj::prop( 'post_status' ), Lst::includes( Fns::__, [ 'private', 'trash', ] ) ) ) ->map( 'get_permalink' ) ->getOrElse( '' ); } } menu-elements/class-wpml-translation-tree.php 0000755 00000016737 14720342453 0015433 0 ustar 00 <?php /** * Class WPML_Translation_Tree * * @package wpml-core * @subpackage taxonomy-term-translation */ class WPML_Translation_Tree extends WPML_SP_User { private $taxonomy; private $tree = array(); private $trid_levels; private $language_order; private $term_ids; /** * @param SitePress $sitepress * @param string $element_type * @param object[] $terms */ public function __construct( &$sitepress, $element_type, $terms ) { if ( ! $terms ) { throw new InvalidArgumentException( 'No terms to order given given' ); } parent::__construct( $sitepress ); $this->taxonomy = $element_type; $this->language_order = $this->get_language_order( $terms ); $this->tree = $this->get_tree_from_terms_array( $terms ); } /** * Returns all terms in the translation tree, ordered by hierarchy and as well as alphabetically within a level and/or parent term relationship. * * @return array */ public function get_alphabetically_ordered_list() { $root_list = $this->sort_trids_alphabetically( $this->tree ); $ordered_list_flattened = array(); foreach ( $root_list as $root_trid_group ) { $ordered_list_flattened = $this->get_children_recursively( $root_trid_group, $ordered_list_flattened ); } return $ordered_list_flattened; } /** * @param array $terms * * Generates a tree representation of an array of terms objects * * @return array|bool */ private function get_tree_from_terms_array( $terms ) { $trids = $this->generate_trid_groups( $terms ); $trid_tree = $this->parse_tree( $trids, false, 0 ); return $trid_tree; } /** * Groups an array of terms objects by their trid and language_code * * @param array<\stdClass> $terms * * @return array<int,array> */ private function generate_trid_groups( $terms ) { $trids = array(); foreach ( $terms as $term ) { $trids [ $term->trid ] [ $term->language_code ] = array( 'ttid' => $term->term_taxonomy_id, 'parent' => $term->parent, 'term_id' => $term->term_id, ); if ( isset( $term->name ) ) { $trids [ $term->trid ] [ $term->language_code ]['name'] = $this->get_unique_term_name( $term->name ); } $this->term_ids[] = $term->term_id; } return $trids; } /** * @param string $name * * @return string */ private function get_unique_term_name( $name ) { return uniqid( $name . '-' ); } /** * @param array<int, array> $trids * @param array|bool|false $root_trid_group * @param int $level current depth in the tree * Recursively turns an array of unordered trid objects into a tree. * * @return array|bool */ private function parse_tree( $trids, $root_trid_group, $level ) { $return = array(); foreach ( $trids as $trid => $trid_group ) { if ( $this->is_root( $trid_group, $root_trid_group ) ) { unset( $trids[ $trid ] ); if ( ! isset( $this->trid_levels[ $trid ] ) ) { $this->trid_levels[ $trid ] = 0; } $this->trid_levels[ $trid ] = max( array( $level, $this->trid_levels[ $trid ] ) ); $return [ $trid ] = array( 'trid' => $trid, 'elements' => $trid_group, 'children' => $this->parse_tree( $trids, $trid_group, $level + 1 ), ); } } return empty( $return ) ? false : $return; } /** * @param array|bool $parent * @param array $children * Checks if one trid is the root of another. This is the case if at least one parent child * relationship between both trids exists. * * @return bool */ private function is_root( $children, $parent ) { $root = ! (bool) $parent; foreach ( $this->language_order as $c_lang ) { if ( ! isset( $children[ $c_lang ] ) ) { continue; } $child_in_lang = $children[ $c_lang ]; if ( $parent === false ) { $root = ! ( $child_in_lang['parent'] != 0 || in_array( $child_in_lang['parent'], $this->term_ids ) ); break; } else { foreach ( (array) $parent as $p_lang => $parent_in_lang ) { if ( $c_lang == $p_lang && $child_in_lang['parent'] == $parent_in_lang['term_id'] ) { $root = true; break; } } } } return $root; } private function sort_trids_alphabetically( $trid_groups ) { $terms_in_trids = array(); $ordered_trids = array(); foreach ( $trid_groups as $trid_group ) { foreach ( $trid_group['elements'] as $lang => $term ) { if ( ! isset( $terms_in_trids[ $lang ] ) ) { $terms_in_trids[ $lang ] = array(); } $trid = $trid_group['trid']; $term['trid'] = $trid; $terms_in_trids[ $lang ][ $trid ][] = $term; } } $sorted = array(); foreach ( $this->language_order as $lang ) { if ( isset( $terms_in_trids[ $lang ] ) ) { $terms_in_lang_and_trids = $terms_in_trids[ $lang ]; $term_names = array(); $term_names_numerical = array(); foreach ( $terms_in_lang_and_trids as $trid => $terms_in_lang_and_trid ) { if ( in_array( $trid, $sorted ) ) { continue; } $term_in_lang_and_trid = array_pop( $terms_in_lang_and_trid ); $term_name = $term_in_lang_and_trid['name']; $term_names [ $term_name ] = $trid; $term_names_numerical [] = $term_name; } natsort( $term_names_numerical ); foreach ( $term_names_numerical as $name ) { $ordered_trids [] = $trid_groups[ $term_names[ $name ] ]; $sorted[] = $term_names[ $name ]; } } } return $ordered_trids; } /** * @param array $trid_group * @param array $existing_list * * Reads in a trid array and appends it and its children to the input array. * This is done in the order parent->alphabetically ordered children -> ( alphabetically ordered children's children) ... * * @return array */ private function get_children_recursively( $trid_group, $existing_list = array() ) { $children = $trid_group['children']; unset( $trid_group['children'] ); $existing_list [] = $this->add_level_information_to_terms( $trid_group ); if ( is_array( $children ) ) { $children = $this->sort_trids_alphabetically( $children ); foreach ( $children as $child ) { $existing_list = $this->get_children_recursively( $child, $existing_list ); } } return $existing_list; } /** * Adds the hierarchical depth as a variable to all terms. * 0 means, that the term has no parent. * * @param array $tridgroup * * @return array */ private function add_level_information_to_terms( $tridgroup ) { foreach ( $tridgroup['elements'] as $lang => &$term ) { $level = 0; $term_id = $term['term_id']; while ( ( $term_id = wp_get_term_taxonomy_parent_id( $term_id, $this->taxonomy ) ) ) { $level ++; } $term['level'] = $level; } return $tridgroup; } /** * Counts the number of terms per language and returns an array of language codes, * that is ordered by the number of terms in every language. * * @param array $terms * * @return array */ private function get_language_order( $terms ) { $langs = array(); $default_lang = $this->sitepress->get_default_language(); foreach ( $terms as $term ) { $term_lang = $term->language_code; if ( $term_lang === $default_lang ) { continue; } if ( isset( $langs[ $term_lang ] ) ) { $langs[ $term_lang ] += 1; } else { $langs[ $term_lang ] = 1; } } natsort( $langs ); $return = array_keys( $langs ); $return [] = $default_lang; $return = array_reverse( $return ); return $return; } } menu-elements/post-edit-screen/wpml-post-edit-screen.class.php 0000755 00000004321 14720342453 0020475 0 ustar 00 <?php class WPML_Post_Edit_Screen { /** @var SitePress $sitepress */ private $sitepress; /** * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; if ( $this->sitepress->get_setting( 'language_negotiation_type' ) == 2 ) { add_filter( 'preview_post_link', array( $this, 'preview_post_link_filter' ), 10, 1 ); add_filter( 'preview_page_link ', array( $this, 'preview_post_link_filter' ), 10, 1 ); } add_action( 'wpml_scripts_setup', array( $this, 'scripts_setup' ), 10, 1 ); add_filter( 'get_sample_permalink', array( $this, 'get_sample_permalink_filter' ) ); } /** * Enqueues scripts and styles for the post edit screen. */ function scripts_setup() { wp_enqueue_style( 'wp-jquery-ui-dialog' ); wp_enqueue_style( 'sitepress-post-edit', ICL_PLUGIN_URL . '/res/css/post-edit.css', array(), ICL_SITEPRESS_VERSION ); wp_enqueue_script( 'sitepress-post-edit', ICL_PLUGIN_URL . '/res/js/post-edit.js', array( 'jquery-ui-dialog', 'jquery-ui-autocomplete' ), ICL_SITEPRESS_VERSION ); } /** * Filters the preview links on the post edit screen so that they always point to the currently used language * domain. This ensures that the user can actually see the preview, as he might not have the login cookie set for * the actual language domain of the post. * * @param string $link * * @return mixed */ public function preview_post_link_filter( $link ) { if ( ! $this->sitepress->get_setting( 'language_per_domain_sso_enabled' ) ) { $original_host = filter_var( $_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL ); if ( $original_host ) { $domain = wpml_parse_url( $link, PHP_URL_HOST ); $link = str_replace( '//' . $domain . '/', '//' . $original_host . '/', $link ); } } return $link; } /** * @param array $permalink Array containing the sample permalink with placeholder for the post name, and the post name. * * @return array */ public function get_sample_permalink_filter( array $permalink ) { $permalink[0] = $this->sitepress->convert_url( $permalink[0] ); return $permalink; } } menu-elements/class-wpml-tm-post-link-anchor.php 0000755 00000002133 14720342453 0015727 0 ustar 00 <?php abstract class WPML_TM_Post_Link_Anchor extends WPML_TM_Post_Link { /** @var string $anchor */ private $anchor; /** @var string $target */ private $target; /** * WPML_TM_Post_Link_Anchor constructor. * * @param SitePress $sitepress * @param int $post_id * @param string $anchor * @param string $target */ public function __construct( SitePress $sitepress, $post_id, $anchor, $target = '' ) { parent::__construct( $sitepress, $post_id ); $this->anchor = $anchor; $this->target = $target; } public function __toString() { $post = $this->sitepress->get_wp_api()->get_post( $this->post_id ); return ! $post || ( in_array( $post->post_status, array( 'draft', 'private', 'trash' ), true ) && $post->post_author != $this->sitepress->get_wp_api() ->get_current_user_id() ) ? '' : sprintf( '<a href="%s"%s>%s</a>', esc_url( $this->link_target() ), $this->target ? ' target="' . $this->target . '"' : '', esc_html( $this->anchor ) ); } protected abstract function link_target(); } menu-elements/PostTypesUI.php 0000755 00000003541 14720342453 0012255 0 ustar 00 <?php namespace WPML\Settings; use WPML\Settings\PostType\Automatic; use WPML\Setup\Option; class PostTypesUI extends \WPML_Custom_Types_Translation_UI { public function renderModeLabels() { parent::renderModeLabels(); $style_column = Option::shouldTranslateEverything() ? '' : 'display: none'; ?> <div class="wpml-flex-table-cell text-center translate-automatically" style="<?php echo esc_attr( $style_column ); ?>"> <?php esc_html_e( 'Translate', 'sitepress' ); ?> <br/><?php esc_html_e( 'automatically', 'sitepress' ); ?> </div> <?php } public function render_row( $content_label, $name, $content_slug, $disabled, $current_translation_mode, $unlocked, $content_label_singular = false ) { parent::render_row( ...func_get_args() ); $style_column = Option::shouldTranslateEverything() ? '' : 'display: none'; $isAutomatic = Automatic::isAutomatic( $content_slug ); $name = 'automatic_post_type[' . $content_slug . ']'; $style = $current_translation_mode !== WPML_CONTENT_TYPE_TRANSLATE ? 'display:none' : ''; ?> <div class="wpml-flex-table-cell text-center translate-automatically" style="<?php echo esc_attr( $style_column ); ?>"> <div class="otgs-toggle-group" style="<?php echo esc_attr( $style ); ?>"> <input type="checkbox" class="otgs-switcher-input" name="<?php echo esc_attr( $name ); ?>" data-was-automatic-before-user-changes="<?php echo $isAutomatic ? '1' : '0'; ?>" <?php echo $isAutomatic ? 'checked' : ''; ?> <?php echo ( $disabled && ! $unlocked ) ? 'disabled' : ''; ?> id="<?php echo esc_attr( $name ); ?>" > <label for="<?php echo esc_attr( $name ); ?>" class="otgs-switcher" data-on="<?php esc_attr_e( 'YES', 'sitepress' ); ?>" data-off="<?php esc_attr_e( 'NO', 'sitepress' ); ?>" > </label> </div> </div> <?php } } menu-elements/class-wpml-taxonomy-translation-ui.php 0000755 00000011416 14720342453 0016752 0 ustar 00 <?php /** * Class WPML_Taxonomy_Translation_UI */ class WPML_Taxonomy_Translation_UI { private $sitepress; private $taxonomy; private $tax_selector; private $screen_options; /** * WPML_Taxonomy_Translation constructor. * * @param SitePress $sitepress * @param string $taxonomy if given renders a specific taxonomy, * otherwise renders a placeholder * @param bool[] $args array with possible indices: * 'taxonomy_selector' => bool .. whether or not to show the taxonomy selector * @param WPML_UI_Screen_Options_Factory $screen_options_factory */ public function __construct( SitePress $sitepress, $taxonomy = '', array $args = array(), WPML_UI_Screen_Options_Factory $screen_options_factory = null ) { $this->sitepress = $sitepress; $this->tax_selector = isset( $args['taxonomy_selector'] ) ? $args['taxonomy_selector'] : true; $this->taxonomy = $taxonomy ? $taxonomy : false; if ( $screen_options_factory ) { $help_title = esc_html__( 'Taxonomy Translation', 'sitepress' ); $help_text = $this->get_help_text(); $this->screen_options = $screen_options_factory->create_pagination( 'taxonomy_translation_per_page', ICL_TM_DOCS_PER_PAGE ); $screen_options_factory->create_help_tab( 'taxonomy_translation_help_tab', $help_title, $help_text ); } } /** * Echos the HTML that serves as an entry point for the taxonomy translation * screen and enqueues necessary js. */ public function render() { WPML_Taxonomy_Translation_Table_Display::enqueue_taxonomy_table_resources( $this->sitepress ); $output = '<div class="wrap">'; if ( $this->taxonomy ) { $output .= '<input type="hidden" id="tax-preselected" value="' . $this->taxonomy . '">'; } if ( ! $this->tax_selector ) { $output .= '<input type="hidden" id="tax-selector-hidden" value="1"/>'; } if ( $this->tax_selector ) { $output .= '<h1>' . esc_html__( 'Taxonomy Translation', 'sitepress' ) . '</h1>'; $output .= '<br/>'; } $output .= '<div id="wpml_tt_taxonomy_translation_wrap" data-items_per_page="' . $this->get_items_per_page() . '">'; $output .= '<div class="loading-content"><span class="spinner" style="visibility: visible"></span></div>'; $output .= '</div>'; do_action( 'icl_menu_footer' ); echo $output . '</div>'; } /** * @return int */ private function get_items_per_page() { $items_per_page = 10; if ( $this->screen_options ) { $items_per_page = $this->screen_options->get_items_per_page(); } return $items_per_page; } /** * @return string */ private function get_help_text() { /* translators: this is the title of a documentation page used to terminate the sentence "is not possible to ..." */ $translate_base_taxonomy_slug_link_title = esc_html__( 'translate the base taxonomy slugs with WPML', 'sitepress' ); $translate_base_taxonomy_slug_link = '<a href="https://wpml.org/faq/translate-taxonomy-slugs-wpml/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore" target="_blank">' . $translate_base_taxonomy_slug_link_title . '</a>'; /* translators: this is the title of a documentation page used to terminate the sentence "To learn more, please visit our documentation page about..." */ $translate_taxonomies_link_title = esc_html__( 'translating post categories and custom taxonomies', 'sitepress' ); $translate_taxonomies_link = '<a href="https://wpml.org/documentation/getting-started-guide/translating-post-categories-and-custom-taxonomies/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore" target="_blank">' . $translate_taxonomies_link_title . '</a>'; $help_sentences = array(); $help_sentences[] = esc_html__( "WPML allows you to easily translate your site's taxonomies. Only taxonomies marked as translatable will be available for translation. Select the taxonomy in the dropdown menu and then use the list of taxonomy terms that appears to translate them.", 'sitepress' ); /* translators: the sentence is completed with "translate the base taxonomy slugs with WPML" */ $help_sentences[] = sprintf( esc_html__( 'Please note that currently, you can translate the slugs of taxonomy terms but it is not possible to %s.', 'sitepress' ), $translate_base_taxonomy_slug_link ); /* translators: the sentence is completed with "translating post categories and custom taxonomies" */ $help_sentences[] = sprintf( esc_html__( 'To learn more, please visit our documentation page about %s.', 'sitepress' ), $translate_taxonomies_link ); return '<p>' . implode( '</p><p>', $help_sentences ) . '</p>'; } } menu-elements/class-wpml-tm-post-edit-custom-field-settings-menu.php 0000755 00000006720 14720342453 0021646 0 ustar 00 <?php /** * Class WPML_TM_Post_Edit_Custom_Field_Settings_Menu */ class WPML_TM_Post_Edit_Custom_Field_Settings_Menu { /** @var WPML_Custom_Field_Setting_Factory $setting_factory */ private $setting_factory; /** @var WP_Post $post */ private $post; private $rendered = false; /** * WPML_TM_Post_Edit_Custom_Field_Settings_Menu constructor. * * @param WPML_Custom_Field_Setting_Factory $settings_factory * @param WP_Post $post */ public function __construct( &$settings_factory, $post ) { $this->setting_factory = &$settings_factory; $this->post = $post; } /** * @return string */ public function render() { $custom_keys = get_post_custom_keys( $this->post->ID ); $custom_keys = $this->setting_factory->filter_custom_field_keys( $custom_keys ); ob_start(); if ( 0 !== count( $custom_keys ) ) { ?> <table class="widefat"> <thead> <tr> <th colspan="2"><?php esc_html_e( 'Custom fields', 'sitepress' ); ?></th> </tr> </thead> <tbody> <?php foreach ( $custom_keys as $cfield ) { $field_setting = $this->setting_factory->post_meta_setting( $cfield ); if ( $field_setting->excluded() ) { continue; } $this->rendered = true; $radio_disabled = $field_setting->get_html_disabled(); $status = (int) $field_setting->status(); $states = array( array( 'value' => WPML_IGNORE_CUSTOM_FIELD, 'text' => __( "Don't translate", 'sitepress' ) ), array( 'value' => WPML_COPY_CUSTOM_FIELD, 'text' => __( "Copy", 'sitepress' ) ), array( 'value' => WPML_COPY_ONCE_CUSTOM_FIELD, 'text' => __( "Copy once", 'sitepress' ) ), array( 'value' => WPML_TRANSLATE_CUSTOM_FIELD, 'text' => __( "Translate", 'sitepress' ) ), ); ?> <tr> <td id="icl_mcs_cf_<?php echo esc_attr( base64_encode( $cfield ) ); ?>"> <div class="icl_mcs_cf_name"> <?php echo esc_html( $cfield ); ?> </div> <?php /** * Filter for custom field description in multilingual content setup metabox on post edit screen * * @param string $custom_field_description custom field description * @param string $custom_field_name custom field name * @param int $post_id current post ID */ $cfield_description = apply_filters( 'wpml_post_edit_settings_custom_field_description', "", $cfield, $this->post->ID ); if ( !empty( $cfield_description ) ) { printf( '<div class="icl_mcs_cf_description">%s</div>', esc_html( $cfield_description ) ); } ?> </td> <td align="right"> <?php foreach ( $states as $state ) { $checked = $status == $state['value'] ? ' checked="checked"' : ''; ?> <label> <input class="icl_mcs_cfs" name="icl_mcs_cf_<?php echo esc_attr( base64_encode( $cfield ) ); ?>" type="radio" value="<?php echo esc_attr( (string) $state['value'] ); ?>" <?php echo esc_attr( $radio_disabled . $checked ); ?> /> <?php echo esc_html( $state['text'] ) ?> </label> <?php } ?> </td> </tr> <?php } ?> </tbody> </table> <br/> <?php } return ob_get_clean(); } /** * @return bool true if there were actual custom fields to display options for */ public function is_rendered() { return $this->rendered; } } menu-elements/class-wpml-tm-post-view-link-title.php 0000755 00000000610 14720342453 0016544 0 ustar 00 <?php /** * Class WPML_TM_Post_View_Link_Title * * Creates post links with the post title as anchor text, pointing at the front-end * post view */ class WPML_TM_Post_View_Link_Title extends WPML_TM_Post_View_Link_Anchor { public function __construct( &$sitepress, $post_id ) { parent::__construct( $sitepress, $post_id, $sitepress->get_wp_api()->get_the_title( $post_id ) ); } } troubleshoot/class-wpml-table-collate-fix.php 0000755 00000005461 14720342453 0015377 0 ustar 00 <?php class WPML_Table_Collate_Fix implements IWPML_AJAX_Action, IWPML_Backend_Action, IWPML_DIC_Action { const AJAX_ACTION = 'fix_tables_collation'; /** * @var wpdb */ private $wpdb; /** @var WPML_Upgrade_Schema $schema */ private $schema; public function __construct( wpdb $wpdb, WPML_Upgrade_Schema $schema ) { $this->wpdb = $wpdb; $this->schema = $schema; } public function add_hooks() { add_action( 'wpml_troubleshooting_after_fix_element_type_collation', array( $this, 'render_troubleshooting_button', ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_ajax_' . self::AJAX_ACTION, array( $this, 'fix_collate_ajax' ) ); add_action( 'upgrader_process_complete', array( $this, 'fix_collate' ), PHP_INT_MAX ); } public function fix_collate_ajax() { if ( isset( $_POST['nonce'] ) && wp_verify_nonce( $_POST['nonce'], self::AJAX_ACTION ) ) { $this->fix_collate(); wp_send_json_success(); } } public function render_troubleshooting_button() { ?> <p> <input id="wpml_fix_tables_collation" type="button" class="button-secondary" value="<?php _e( 'Fix WPML tables collation', 'sitepress' ); ?>"/><br/> <?php wp_nonce_field( self::AJAX_ACTION, 'wpml-fix-tables-collation-nonce' ); ?> <small style="margin-left:10px;"><?php esc_attr_e( 'Fixes the collation of WPML tables in order to match the collation of default WP tables.', 'sitepress' ); ?></small> </p> <?php } public function enqueue_scripts( $hook ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $hook ) { wp_enqueue_script( 'wpml-fix-tables-collation', ICL_PLUGIN_URL . '/res/js/fix-tables-collation.js', array( 'jquery' ), ICL_SITEPRESS_VERSION ); } } public function fix_collate() { if ( did_action( 'upgrader_process_complete' ) > 1 ) { return; } $wp_default_table_data = $this->wpdb->get_row( $this->wpdb->prepare( 'SHOW TABLE status LIKE %s', $this->wpdb->posts ) ); if ( isset( $wp_default_table_data->Collation ) ) { $charset = $this->schema->get_default_charset(); foreach ( $this->get_all_wpml_tables() as $table ) { $table = reset( $table ); $table_data = $this->wpdb->get_row( $this->wpdb->prepare( 'SHOW TABLE status LIKE %s', $table ) ); if ( isset( $table_data->Collation ) && $table_data->Collation !== $wp_default_table_data->Collation ) { $this->wpdb->query( $this->wpdb->prepare( 'ALTER TABLE ' . $table . ' CONVERT TO CHARACTER SET %s COLLATE %s', $charset, $wp_default_table_data->Collation ) ); } } } } /** * @return array */ private function get_all_wpml_tables() { return $this->wpdb->get_results( $this->wpdb->prepare( 'SHOW TABLES LIKE %s', $this->wpdb->prefix . 'icl_%' ), ARRAY_A ); } } troubleshoot/class-wpml-debug-information.php 0000755 00000011201 14720342453 0015501 0 ustar 00 <?php class WPML_Debug_Information { /** @var wpdb $wpdb */ public $wpdb; /** @var SitePress $sitepress */ protected $sitepress; /** * @param wpdb $wpdb * @param SitePress $sitepress */ public function __construct( $wpdb, $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } public function run() { $info = array( 'core', 'plugins', 'theme', 'extra-debug' ); $output = array(); foreach ( $info as $type ) { switch ( $type ) { case 'core': $output['core'] = $this->get_core_info(); break; case 'plugins': $output['plugins'] = $this->get_plugins_info(); break; case 'theme': $output['theme'] = $this->get_theme_info(); break; case 'extra-debug': $output['extra-debug'] = apply_filters( 'icl_get_extra_debug_info', array() ); break; } } return $output; } function get_core_info() { $core = array( 'Wordpress' => array( 'Multisite' => is_multisite() ? 'Yes' : 'No', 'SiteURL' => site_url(), 'HomeURL' => home_url(), 'Version' => get_bloginfo( 'version' ), 'PermalinkStructure' => get_option( 'permalink_structure' ), 'PostTypes' => implode( ', ', get_post_types( '', 'names' ) ), 'PostStatus' => implode( ', ', get_post_stati() ), 'RestEnabled' => wpml_is_rest_enabled() ? 'Yes' : 'No', ), 'Server' => array( 'jQueryVersion' => wp_script_is( 'jquery', 'registered' ) ? $GLOBALS['wp_scripts']->registered['jquery']->ver : __( 'n/a', 'bbpress' ), 'PHPVersion' => $this->sitepress->get_wp_api()->phpversion(), 'MySQLVersion' => $this->wpdb->db_version(), 'ServerSoftware' => $_SERVER['SERVER_SOFTWARE'], ), 'PHP' => array( 'MemoryLimit' => ini_get( 'memory_limit' ), 'WP Memory Limit' => WP_MEMORY_LIMIT, 'UploadMax' => ini_get( 'upload_max_filesize' ), 'PostMax' => ini_get( 'post_max_size' ), 'TimeLimit' => ini_get( 'max_execution_time' ), 'MaxInputVars' => ini_get( 'max_input_vars' ), 'MBString' => $this->sitepress->get_wp_api()->extension_loaded( 'mbstring' ), 'libxml' => $this->sitepress->get_wp_api()->extension_loaded( 'libxml' ), ), ); return $core; } function get_plugins_info() { $plugins = $this->sitepress->get_wp_api()->get_plugins(); $active_plugins = $this->sitepress->get_wp_api()->get_option( 'active_plugins' ); $active_plugins_info = array(); foreach ( $active_plugins as $plugin ) { if ( isset( $plugins[ $plugin ] ) ) { unset( $plugins[ $plugin ]['Description'] ); $active_plugins_info[ $plugin ] = $plugins[ $plugin ]; } } $mu_plugins = get_mu_plugins(); $dropins = get_dropins(); $output = array( 'active_plugins' => $active_plugins_info, 'mu_plugins' => $mu_plugins, 'dropins' => $dropins, ); return $output; } function get_theme_info() { if ( $this->sitepress->get_wp_api()->get_bloginfo( 'version' ) < '3.4' ) { /** @var \WP_Theme $current_theme */ $current_theme = get_theme_data( get_stylesheet_directory() . '/style.css' ); $theme = $current_theme; unset( $theme['Description'] ); unset( $theme['Status'] ); unset( $theme['Tags'] ); } else { $theme = array( 'Name' => $this->sitepress->get_wp_api()->get_theme_name(), 'ThemeURI' => $this->sitepress->get_wp_api()->get_theme_URI(), 'Author' => $this->sitepress->get_wp_api()->get_theme_author(), 'AuthorURI' => $this->sitepress->get_wp_api()->get_theme_authorURI(), 'Template' => $this->sitepress->get_wp_api()->get_theme_template(), 'Version' => $this->sitepress->get_wp_api()->get_theme_version(), 'TextDomain' => $this->sitepress->get_wp_api()->get_theme_textdomain(), 'DomainPath' => $this->sitepress->get_wp_api()->get_theme_domainpath(), 'ParentName' => $this->sitepress->get_wp_api()->get_theme_parent_name(), ); } return $theme; } function do_json_encode( $data ) { $json_options = 0; if ( defined( 'JSON_HEX_TAG' ) ) { $json_options += JSON_HEX_TAG; } if ( defined( 'JSON_HEX_APOS' ) ) { $json_options += JSON_HEX_APOS; } if ( defined( 'JSON_HEX_QUOT' ) ) { $json_options += JSON_HEX_QUOT; } if ( defined( 'JSON_HEX_AMP' ) ) { $json_options += JSON_HEX_AMP; } if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) { $json_options += JSON_UNESCAPED_UNICODE; } if ( version_compare( $this->sitepress->get_wp_api()->phpversion(), '5.3.0', '<' ) ) { $json_data = wp_json_encode( $data ); } else { $json_data = wp_json_encode( $data, $json_options ); } return $json_data; } } troubleshoot/Endpoints/ATESecondaryDomains/EnableSecondaryDomain.php 0000755 00000001564 14720342453 0021750 0 ustar 00 <?php namespace WPML\TM\Troubleshooting\Endpoints\ATESecondaryDomains; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Lst; use WPML\TM\ATE\API\FingerprintGenerator; use WPML\TM\ATE\ClonedSites\Lock; use WPML\TM\ATE\ClonedSites\SecondaryDomains; use function WPML\Container\make; class EnableSecondaryDomain implements IHandler { /** @var Lock */ private $lock; /** @var SecondaryDomains */ private $secondsDomains; public function __construct( Lock $lock, SecondaryDomains $secondsDomains ) { $this->lock = $lock; $this->secondsDomains = $secondsDomains; } public function run( Collection $data ) { $lockData = $this->lock->getLockData(); $this->secondsDomains->add( $lockData['urlUsedToMakeRequest'], $lockData['urlCurrentlyRegisteredInAMS'] ); $this->lock->unlock(); return Either::of( true ); } } troubleshoot/class-wpml-troubleshoot-action.php 0000755 00000001250 14720342453 0016077 0 ustar 00 <?php /** * Class WPML_Troubleshoot_Action * * @author onTheGoSystems */ class WPML_Troubleshoot_Action { const SYNC_POSTS_TAXONOMIES_SLUG = 'synchronize_posts_taxonomies'; /** * @return bool */ public function is_valid_request() { $response = false; if ( array_key_exists( 'nonce', $_POST ) && array_key_exists( 'debug_action', $_POST ) && self::SYNC_POSTS_TAXONOMIES_SLUG === $_POST['debug_action'] ) { $response = wp_verify_nonce( $_POST['nonce'], $_POST['debug_action'] ); if ( ! $response ) { wp_send_json_error( array( 'message' => esc_html__( 'Invalid nonce.', 'sitepress' ) ) ); return $response; } } return $response; } } troubleshoot/AssignTranslationStatusToDuplicates.php 0000755 00000003435 14720342453 0017212 0 ustar 00 <?php namespace WPML\Troubleshooting; class AssignTranslationStatusToDuplicates { public static function run() { global $sitepress, $iclTranslationManagement; $active_language_codes = array_keys( $sitepress->get_active_languages() ); $duplicated_posts = self::get_duplicates(); $updated_items = 0; foreach ( $duplicated_posts as $original_post_id ) { $element_type = 'post_' . get_post_type( $original_post_id ); $trid = $sitepress->get_element_trid( $original_post_id, $element_type ); $element_language_details = $sitepress->get_element_translations( $trid, $element_type ); $item_updated = false; foreach ( $active_language_codes as $code ) { if ( ! isset( $element_language_details[ $code ] ) ) { continue; } /** @var \stdClass $element_translation */ $element_translation = $element_language_details[ $code ]; if ( ! isset( $element_translation->element_id ) || $element_translation->original ) { continue; } $translation = $iclTranslationManagement->get_element_translation( $element_translation->element_id, $code, $element_type ); if ( ! $translation ) { $status_helper = wpml_get_post_status_helper(); $status_helper->set_status( $element_translation->element_id, ICL_TM_DUPLICATE ); $item_updated = true; } } if ( $item_updated ) { $updated_items ++; } if ( $updated_items >= 20 ) { break; } } return $updated_items; } /** * @return array */ private static function get_duplicates() { global $wpdb; $duplicated_posts_sql = "SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key='_icl_lang_duplicate_of' AND meta_value<>'' GROUP BY meta_value;"; return $wpdb->get_col( $duplicated_posts_sql ); } } troubleshoot/class-wpml-troubleshoot-sync-posts-taxonomies.php 0000755 00000005043 14720342453 0021134 0 ustar 00 <?php use WPML\API\Sanitize; /** * Class WPML_Troubleshoot_Sync_Posts_Taxonomies */ class WPML_Troubleshoot_Sync_Posts_Taxonomies { const BATCH_SIZE = 5; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Term_Translation_Utils $term_translation_utils */ private $term_translation_utils; public function __construct( SitePress $sitePress, WPML_Term_Translation_Utils $term_translation_utils ) { $this->sitepress = $sitePress; $this->term_translation_utils = $term_translation_utils; } public function run() { if ( ! array_key_exists( 'post_type', $_POST ) || ! array_key_exists( 'batch_number', $_POST ) ) { wp_send_json_error( array( 'message' => esc_html__( 'Some parameters are missing for this request.', 'sitepress' ) ) ); return; } $post_type = Sanitize::stringProp( 'post_type', $_POST ); $batch_number = (int) filter_var( $_POST['batch_number'], FILTER_SANITIZE_NUMBER_INT ); $posts = $this->get_posts_batch( (string) $post_type, $batch_number ); $this->synchronize_batch( $posts ); $new_batch_number = $batch_number + 1; $response_data = array( 'post_type' => $post_type, 'batch_number' => $new_batch_number, 'message' => sprintf( esc_html__( 'Running now batch #%d', 'sitepress' ), $new_batch_number ), ); if ( count( $posts ) < self::BATCH_SIZE ) { $total_posts_processed = ( $batch_number * self::BATCH_SIZE ) + count( $posts ); $response_data['completed'] = true; $response_data['message'] = sprintf( esc_html__( 'Completed: %1$d posts were processed for "%2$s".', 'sitepress' ), $total_posts_processed, $post_type ); } wp_send_json_success( $response_data ); } /** * @param string $type * @param int $batch_number * * @return array */ private function get_posts_batch( $type, $batch_number ) { $this->sitepress->switch_lang( $this->sitepress->get_default_language() ); $args = array( 'post_type' => $type, 'offset' => $batch_number * self::BATCH_SIZE, 'order_by' => 'ID', 'order' => 'ASC', 'posts_per_page' => self::BATCH_SIZE, 'suppress_filters' => false, ); $posts = get_posts( $args ); $this->sitepress->switch_lang(); return $posts; } /** * @param array $posts */ private function synchronize_batch( $posts ) { $active_languages = $this->sitepress->get_active_languages(); foreach ( $active_languages as $language_code => $active_language ) { foreach ( $posts as $post ) { $this->term_translation_utils->sync_terms( $post->ID, $language_code ); } } } } troubleshoot/class-wpml-fix-type-assignments.php 0000755 00000017203 14720342453 0016176 0 ustar 00 <?php class WPML_Fix_Type_Assignments extends WPML_WPDB_And_SP_User { /** * WPML_Fix_Type_Assignments constructor. * * @param SitePress $sitepress */ public function __construct( $sitepress ) { $wpdb = $sitepress->wpdb(); parent::__construct( $wpdb, $sitepress ); } /** * Runs various database repair and cleanup actions on icl_translations. * * @return int Number of rows in icl_translations that were fixed */ public function run() { $rows_fixed = $this->fix_broken_duplicate_rows(); $rows_fixed += $this->fix_missing_original(); $rows_fixed += $this->fix_wrong_source_language(); $rows_fixed += $this->fix_broken_type_assignments(); $rows_fixed += $this->fix_broken_taxonomy_assignments(); $rows_fixed += $this->fix_broken_post_assignments(); $rows_fixed += $this->fix_mismatched_types(); icl_cache_clear(); wp_cache_init(); return $rows_fixed; } /** * Deletes rows from icl_translations that are duplicated in terms of their * element id and within their meta type ( post,taxonomy,package ...), * with the duplicate actually being of the correct type. * * @return int number of rows fixed */ private function fix_broken_duplicate_rows() { $rows_fixed = $this->wpdb->query( " DELETE t FROM {$this->wpdb->prefix}icl_translations i JOIN {$this->wpdb->prefix}icl_translations t ON i.element_id = t.element_id AND SUBSTRING_INDEX(i.element_type, '_', 1) = SUBSTRING_INDEX(t.element_type, '_', 1) AND i.element_type != t.element_type AND i.translation_id != t.translation_id JOIN (SELECT CONCAT('post_', p.post_type) AS element_type, p.ID AS element_id FROM {$this->wpdb->posts} p UNION ALL SELECT CONCAT('tax_', tt.taxonomy) AS element_type, tt.term_taxonomy_id AS element_id FROM {$this->wpdb->term_taxonomy} tt) AS data ON data.element_id = i.element_id AND data.element_type = i.element_type" ); if ( 0 < $rows_fixed ) { do_action( 'wpml_translation_update', array( 'type' => 'delete', 'rows_affected' => $rows_fixed, ) ); } return $rows_fixed; } /** * Fixes all taxonomy term rows in icl_translations, which have a corrupted * element_type set, different from the one actually set in the term_taxonomy * table. * * @return int number of rows fixed */ private function fix_broken_taxonomy_assignments() { $rows_fixed = $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translations t JOIN {$this->wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = t.element_id AND t.element_type LIKE 'tax%' AND t.element_type <> CONCAT('tax_', tt.taxonomy) SET t.element_type = CONCAT('tax_', tt.taxonomy)" ); if ( 0 < $rows_fixed ) { do_action( 'wpml_translation_update', array( 'context' => 'tax', 'type' => 'element_type_update', 'rows_affected' => $rows_fixed, ) ); } return $rows_fixed; } /** * Fixes all post rows in icl_translations, which have a corrupted * element_type set, different from the one actually set in the wp_posts * table. * * @return int number of rows fixed */ private function fix_broken_post_assignments() { $rows_fixed = $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translations t JOIN {$this->wpdb->posts} p ON p.ID = t.element_id AND t.element_type LIKE 'post%' AND t.element_type <> CONCAT('post_', p.post_type) SET t.element_type = CONCAT('post_', p.post_type)" ); if ( 0 < $rows_fixed ) { do_action( 'wpml_translation_update', array( 'context' => 'tax', 'type' => 'element_type_update', 'rows_affected' => $rows_fixed, ) ); } return $rows_fixed; } /** * Fixes all instances of a different element_type having been set for * an original element and it's translation, by setting the original's type * on the corrupted translation rows. * * This needs to be run before fix_broken_post_assignments. If it is run after * then the element_type will be set to element_type of the source_language_code * which might not be the same as the post_type which is set in fix_broken_post_assignments. * * @return int number of rows fixed */ private function fix_broken_type_assignments() { $rows_fixed = $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translations t JOIN {$this->wpdb->prefix}icl_translations c ON c.trid = t.trid AND c.language_code != t.language_code SET t.element_type = c.element_type WHERE c.source_language_code IS NULL AND t.source_language_code IS NOT NULL" ); if ( 0 < $rows_fixed ) { do_action( 'wpml_translation_update', array( 'type' => 'element_type_update', 'rows_affected' => $rows_fixed, ) ); } return $rows_fixed; } /** * Fixes all rows that have an empty string instead of NULL or a source language * equal to its actual language set by setting the source language to NULL. * * @return int number of rows fixed */ private function fix_wrong_source_language() { return $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translations SET source_language_code = NULL WHERE source_language_code = '' OR source_language_code = language_code" ); } /** * Fixes instances of the source element of a trid being missing, by assigning * the oldest element ( determined by the lowest element_id ) as the original * element in a trid. * * @return int number of rows fixed */ private function fix_missing_original() { $broken_elements = $this->wpdb->get_results( " SELECT MIN(iclt.element_id) AS element_id, iclt.trid FROM {$this->wpdb->prefix}icl_translations iclt LEFT JOIN {$this->wpdb->prefix}icl_translations iclo ON iclt.trid = iclo.trid AND iclo.source_language_code IS NULL WHERE iclo.translation_id IS NULL GROUP BY iclt.trid" ); $rows_affected = 0; foreach ( $broken_elements as $element ) { $rows_affected_per_element = $this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->prefix}icl_translations SET source_language_code = NULL WHERE trid = %d AND element_id = %d", $element->trid, $element->element_id ) ); if ( 0 < $rows_affected_per_element ) { do_action( 'wpml_translation_update', array( 'trid' => $element->trid ) ); } $rows_affected += $rows_affected_per_element; } return $rows_affected; } /** * Deletes the row for a translated element from icl_translations, where the translated element_type * is not the same as the original element_type in a trid. This is the final fix to run. * The previous fix should have associated the element_type with the matching type from the posts table. * If the translated type does not match the original type, then it needs to be deleted. * * @return int number of rows fixed */ private function fix_mismatched_types() { $rows_affected = $this->wpdb->query( "DELETE t FROM {$this->wpdb->prefix}icl_translations t JOIN {$this->wpdb->prefix}icl_translations o ON o.trid = t.trid AND o.language_code != t.language_code AND o.source_language_code IS NULL AND t.source_language_code IS NOT NULL AND o.element_type <> t.element_type" ); if ( 0 < $rows_affected ) { do_action( 'wpml_translation_update', array( 'type' => 'delete', 'rows_affected' => $rows_affected, ) ); } return $rows_affected; } } troubleshoot/Loader.php 0000755 00000002655 14720342453 0011233 0 ustar 00 <?php namespace WPML\TM\Troubleshooting; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\Nonce; use WPML\Core\WP\App\Resources; use WPML\Media\Option; use WPML\TM\ATE\AutoTranslate\Endpoint\CancelJobs; use WPML\TM\ATE\ClonedSites\Lock; use WPML\TM\ATE\ClonedSites\SecondaryDomains; use WPML\TM\ATE\Hooks\JobActionsFactory; use WPML\LanguageSwitcher\LsTemplateDomainUpdater; use WPML\TM\Troubleshooting\Endpoints\ATESecondaryDomains\EnableSecondaryDomain; class Loader implements \IWPML_Backend_Action { public function add_hooks() { add_action( 'after_setup_complete_troubleshooting_functions', [ $this, 'render' ], 7 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueueScripts' ] ); } public function render() { echo '<div id="wpml-troubleshooting-container" style="margin: 5px 0;"></div>'; } public function enqueueScripts( $hook ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $hook ) { $enqueue = Resources::enqueueApp( 'troubleshooting' ); $enqueue( [ 'name' => 'troubleshooting', 'data' => [ 'refreshLicense' => [ 'nonce' => Nonce::create( 'update_site_key_wpml' ), ], 'isATELocked' => Lock::isLocked(), 'endpoints' => [ 'cancelJobs' => CancelJobs::class, 'lsTemplatesUpdateDomain' => LsTemplateDomainUpdater::class, 'enableSecondaryDomain' => EnableSecondaryDomain::class, ], ], ] ); } } } setup/Initializer.php 0000755 00000017453 14720342453 0010721 0 ustar 00 <?php namespace WPML\Setup; use WPML\Ajax\Endpoint\Upload; use WPML\Core\LanguageNegotiation; use WPML\Core\WP\App\Resources; use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Wrapper; use WPML\LIB\WP\Option as WPOption; use WPML\LIB\WP\User; use WPML\Setup\Endpoint\CheckTMAllowed; use WPML\Setup\Endpoint\CurrentStep; use WPML\TM\ATE\TranslateEverything\Pause\View as PauseTranslateEverything; use WPML\TM\ATE\TranslateEverything\TranslatableData\View as TranslatableData; use WPML\TM\ATE\TranslateEverything\TranslatableData\DataPreSetup; use WPML\TM\Menu\TranslationMethod\TranslationMethodSettings; use WPML\TranslationMode\Endpoint\SetTranslateEverything; use WPML\TranslationRoles\UI\Initializer as TranslationRolesInitializer; use WPML\UIPage; use function WPML\Container\make; class Initializer { public static function loadJS() { Wrapper::of( self::getData() )->map( Resources::enqueueApp( 'setup' ) ); } public static function getData() { $currentStep = Option::getCurrentStep(); if ( CurrentStep::STEP_HIGH_COSTS_WARNING === $currentStep ) { // The user stopped the wizard on the high costs warning step. // In this case we need to start the wizard one step before. $currentStep = CurrentStep::STEP_TRANSLATION_SETTINGS; } $siteUrl = self::getSiteUrl(); $defaultLang = self::getDefaultLang(); $originalLang = Option::getOriginalLang(); if ( ! $originalLang ) { $originalLang = $defaultLang; Option::setOriginalLang( $originalLang ); } $userLang = Languages::getUserLanguageCode()->getOrElse( $defaultLang ); if ( defined( 'OTGS_INSTALLER_SITE_KEY_WPML' ) ) { self::savePredefinedSiteKey( OTGS_INSTALLER_SITE_KEY_WPML ); } return [ 'name' => 'wpml_wizard', 'data' => [ 'currentStep' => $currentStep, 'endpoints' => Lst::concat( [ 'setOriginalLanguage' => Endpoint\SetOriginalLanguage::class, 'setSupport' => Endpoint\SetSupport::class, 'setSecondaryLanguages' => Endpoint\SetSecondaryLanguages::class, 'currentStep' => Endpoint\CurrentStep::class, 'addressStep' => Endpoint\AddressStep::class, 'licenseStep' => Endpoint\LicenseStep::class, 'translationStep' => Endpoint\TranslationStep::class, 'setTranslateEverything' => SetTranslateEverything::class, 'pauseTranslateEverything' => PauseTranslateEverything::class, 'recommendedPlugins' => Endpoint\RecommendedPlugins::class, 'finishStep' => Endpoint\FinishStep::class, 'addLanguages' => Endpoint\AddLanguages::class, 'upload' => Upload::class, 'checkTMAllowed' => CheckTMAllowed::class, 'translatableData' => TranslatableData::class, ], TranslationRolesInitializer::getEndPoints() ), 'languages' => [ 'list' => Obj::values( Languages::withFlags( Languages::getAll( $userLang ) ) ), 'secondaries' => Fns::map( Languages::getLanguageDetails(), Option::getTranslationLangs() ), 'original' => Languages::getLanguageDetails( $originalLang ), 'customFlagsDir' => self::getCustomFlagsDir(), 'predefinedFlagsDir' => \WPML_Flags::get_wpml_flags_url(), 'flagsByLocalesFileUrl' => \WPML_Flags::get_wpml_flags_by_locales_url(), ], 'siteAddUrl' => 'https://wpml.org/account/sites/?add=' . urlencode( $siteUrl ) . '&wpml_version=' . self::getWPMLVersion(), 'siteKey' => self::getSiteKey(), 'usePredefinedSiteKey' => self::isPredefinedSiteKeySaved(), 'supportValue' => \OTGS_Installer_WP_Share_Local_Components_Setting::get_setting( 'wpml' ), 'address' => [ 'siteUrl' => $siteUrl, 'mode' => self::getLanguageNegotiationMode(), 'domains' => LanguageNegotiation::getDomains() ?: [], 'gotUrlRewrite' => got_url_rewrite(), ], 'isTMAllowed' => Option::isTMAllowed() === true, 'isTMDisabled' => Option::isTMAllowed() === false, 'ateBaseUrl' => self::getATEBaseUrl(), 'whenFinishedUrlLanguages' => admin_url( UIPage::getLanguages() ), 'whenFinishedUrlTM' => admin_url( UIPage::getTM() ), 'ateSignUpUrl' => admin_url( UIPage::getTMATE() ), 'languagesMenuUrl' => admin_url( UIPage::getLanguages() ), 'adminUserName' => User::getCurrent()->display_name, 'translation' => Lst::concat( TranslationMethodSettings::getModeSettingsData(), TranslationRolesInitializer::getTranslationData( null, false ) ), 'license' => [ 'actions' => [ 'registerSiteKey' => Endpoint\LicenseStep::ACTION_REGISTER_SITE_KEY, 'getSiteType' => Endpoint\LicenseStep::ACTION_GET_SITE_TYPE, ], 'siteType' => [ 'production' => \OTGS_Installer_Subscription::SITE_KEY_TYPE_PRODUCTION, 'development' => \OTGS_Installer_Subscription::SITE_KEY_TYPE_DEVELOPMENT, ], ], 'translatableData' => [ 'actions' => [ 'listTranslatables' => TranslatableData::ACTION_LIST_TRANSLATABLES, 'fetchData' => TranslatableData::ACTION_FETCH_DATA, ], 'types' => [ 'postTypes' => DataPreSetup::KEY_POST_TYPES, 'taxonomies' => DataPreSetup::KEY_TAXONOMIES, ], ], ], ]; } /** * @return bool */ private static function isPredefinedSiteKeySaved() { return function_exists( 'OTGS_Installer' ) && defined( 'OTGS_INSTALLER_SITE_KEY_WPML' ) && OTGS_INSTALLER_SITE_KEY_WPML && OTGS_Installer()->get_site_key( 'wpml' ) === OTGS_INSTALLER_SITE_KEY_WPML; } /** * @param string $siteKey */ private static function savePredefinedSiteKey( $siteKey ) { if ( function_exists( 'OTGS_Installer' ) ) { $args = [ 'repository_id' => 'wpml', 'nonce' => wp_create_nonce( 'save_site_key_wpml' ), 'site_key' => $siteKey, 'return' => 1, ]; $result = OTGS_Installer()->save_site_key( $args ); if ( empty( $result['error'] ) ) { icl_set_setting( 'site_key', $siteKey, true ); } } } /** * @return string */ private static function getLanguageNegotiationMode() { if ( Option::getCurrentStep() === 'address' && ! got_url_rewrite() && LanguageNegotiation::getMode() === WPML_LANGUAGE_NEGOTIATION_TYPE_DIRECTORY ) { return LanguageNegotiation::getModeAsString( WPML_LANGUAGE_NEGOTIATION_TYPE_PARAMETER ); } return LanguageNegotiation::getModeAsString(); } /** * @return string */ private static function getDefaultLang() { $getLangFromConstant = function () { global $sitepress; return Maybe::fromNullable( $sitepress->get_wp_api()->constant( 'WP_LANG' ) ) ->map( Languages::localeToCode() ) ->getOrElse( 'en' ); }; return Maybe::fromNullable( WPOption::getOr( 'WPLANG', null ) ) ->map( Languages::localeToCode() ) ->getOrElse( $getLangFromConstant ); } private static function getCustomFlagsDir() { return sprintf( '%s/flags/', Obj::propOr( '', 'baseurl', wp_upload_dir() ) ); } private static function getATEBaseUrl() { return make( \WPML_TM_ATE_AMS_Endpoints::class )->get_base_url( \WPML_TM_ATE_AMS_Endpoints::SERVICE_ATE ); } /** * @return string */ private static function getWPMLVersion() { return Obj::prop( 'Version', get_plugin_data( WPML_PLUGIN_PATH . '/' . WPML_PLUGIN_FILE ) ); } /** * @return string */ private static function getSiteKey() { $siteKey = wpml_get_setting( 'site_key', (string) OTGS_Installer()->get_site_key( 'wpml' ) ); return is_string( $siteKey ) && strlen( $siteKey ) === 10 ? $siteKey : ''; } private static function getSiteUrl() { return OTGS_Installer()->get_installer_site_url('wpml'); } } setup/DisableNotices.php 0000755 00000001034 14720342453 0011312 0 ustar 00 <?php namespace WPML\Setup; use WPML\FP\Lst; use WPML\FP\Obj; class DisableNotices implements \IWPML_DIC_Action, \IWPML_Backend_Action { public function add_hooks() { add_action( 'wp_before_admin_bar_render', function () { $iSetupPage = Lst::includes( Obj::propOr( '', 'page', $_GET ), [ 'sitepress-multilingual-cms/menu/setup.php', 'sitepress-multilingual-cms/menu/languages.php' ] ); if ( $iSetupPage ) { remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); } } ); } } setup/endpoints/RecommendedPlugins.php 0000755 00000000463 14720342453 0014216 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Obj; class RecommendedPlugins implements IHandler { public function run( Collection $data ) { return Either::of( OTGS_Installer()->get_recommendations( 'wpml' ) ); } } setup/endpoints/TranslationStep.php 0000755 00000000500 14720342453 0013554 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; use WPML\Setup\Option; class TranslationStep implements IHandler { public function run( Collection $data ) { Option::setTranslationMode( $data->get('whoMode') ); return Right::of( true ); } } setup/endpoints/CheckTMAllowed.php 0000755 00000000710 14720342453 0013213 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\Plugins; use WPML\Setup\Option; class CheckTMAllowed implements IHandler { public function run( Collection $data ) { $isTMAllowed = Option::isTMAllowed(); if ( $isTMAllowed === null ) { $isTMAllowed = Plugins::isTMAllowed(); Option::setTMAllowed( $isTMAllowed ); } return Either::right( $isTMAllowed ); } } setup/endpoints/SetOriginalLanguage.php 0000755 00000000763 14720342453 0014321 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Str; use WPML\Setup\Option; class SetOriginalLanguage implements IHandler { public function run( Collection $data ) { return Either::of( $data ) ->map( Obj::prop( 'languageCode' ) ) ->map( Fns::tap( [ Option::class, 'setOriginalLang' ] ) ) ->map( Str::concat( 'success: ' ) ); } } setup/endpoints/TranslationServices.php 0000755 00000004613 14720342453 0014435 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Relation; use WPML\LIB\WP\Http; use WPML\TM\Geolocalization; use WPML\TM\Menu\TranslationServices\ActiveServiceRepository; use WPML\TM\Menu\TranslationServices\ServiceMapper; use WPML\TM\Menu\TranslationServices\ServicesRetriever; use function WPML\Container\make; use function WPML\FP\partialRight; class TranslationServices implements IHandler { public function run( Collection $data ) { $tpApi = make( \WPML_TP_Client_Factory::class )->create()->services(); $serviceMapperFunction = partialRight( [ ServiceMapper::class, 'map' ], [ ActiveServiceRepository::class, 'getId' ] ); $services = ServicesRetriever::get( $tpApi, Geolocalization::getCountryByIp( Http::post() ), $serviceMapperFunction ); $preferredServiceSUID = \TranslationProxy::get_tp_default_suid(); $preferredService = false; if ( $preferredServiceSUID ) { $services = self::filterByPreferred( $services, $preferredServiceSUID ); $preferredServiceData = \TranslationProxy_Service::get_service_by_suid( $preferredServiceSUID ); $preferredService = new \WPML_TP_Service( $preferredServiceData ); $preferredService->set_custom_fields_data(); $preferredService = $serviceMapperFunction( $preferredService ); } return Either::of( [ 'services' => $services, 'preferredService' => $preferredService, 'logoPlaceholder' => WPML_TM_URL . '/res/img/lsp-logo-placeholder.png', ] ); } /** * @param array $services * @param string $preferredServiceSUID * @return array */ private static function filterByPreferred( $services, $preferredServiceSUID ) { $preferredService = \TranslationProxy_Service::get_service_by_suid( $preferredServiceSUID ); if ( $preferredService ) { foreach ( $services as $key => $serviceGroup ) { $services[ $key ] = self::filterServices( $serviceGroup, $preferredService->id ); if ( empty( $services[ $key ]['services'] ) ) { unset( $services[ $key ] ); } } } return array_values( $services ); } /** * @param array $serviceGroup * @param int $serviceId * @return array */ public static function filterServices( $serviceGroup, $serviceId ) { $serviceGroup['services'] = Fns::filter( Relation::propEq( 'id', $serviceId ), $serviceGroup['services'] ); return $serviceGroup; } } setup/endpoints/AddLanguages.php 0000755 00000003047 14720342453 0012752 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\Element\API\Languages; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Str; use WPML\Element\API\Entity\LanguageMapping; use WPML\Setup\Option; class AddLanguages implements IHandler { public function run( Collection $data ) { $languages = $data->get( 'languages' ); $create = function ( $language ) { $id = Languages::add( $language['code'], $language['name'], $language['locale'], 0, 0, (int) $language['encode_url'], $language['hreflang'], Obj::prop('country', $language) ); if ( $id ) { $flag = Obj::prop( 'flag', $language ); if ( $flag ) { Languages::setFlag( $language['code'], Obj::propOr( '', 'name', $flag ), (bool) Obj::propOr( false, 'fromTemplate', $flag ) ); } /** @phpstan-ignore-next-line */ $this->saveMapping( $language, $id ); } return [ $language['code'], $id ]; }; $result = Either::right( Fns::map( $create, $languages ) ); icl_cache_clear( false ); return $result; } /** * @param array $language * @param int $id */ private function saveMapping( $language, $id ) { $languageMapping = Obj::prop( 'mapping', $language ); if ( $id && $languageMapping ) { $languageMapping = Str::split( '_', $languageMapping ); Option::addLanguageMapping( new LanguageMapping( $language['code'], $language['name'], $languageMapping[0], Obj::prop( 1, $languageMapping ) ) ); } } } setup/endpoints/AddressStep.php 0000755 00000005356 14720342453 0012661 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\Core\LanguageNegotiation; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Right; use WPML\FP\Str; use function WPML\Container\make; use function WPML\FP\chain; use function WPML\FP\pipe; use function WPML\FP\System\sanitizeString; class AddressStep implements IHandler { public function run( Collection $data ) { $isDomainMode = Relation::propEq( 'mode', LanguageNegotiation::DOMAIN_STRING ); $isDirectoryMode = Relation::propEq( 'mode', LanguageNegotiation::DIRECTORY_STRING ); $otherWise = Fns::always( true ); $handleModes = Logic::cond( [ [ $isDomainMode, $this->handleDomains() ], [ $isDirectoryMode, $this->validateSubdirectoryUrls() ], [ $otherWise, Fns::identity() ] ] ); return Right::of( $data ) ->chain( $handleModes ) ->map( Obj::prop( 'mode' ) ) ->map( sanitizeString() ) ->map( LanguageNegotiation::saveMode() ) ->map( Fns::always( 'ok' ) ); } /** * @return callable(Collection) : Either Left(unavailable) | Right(data) */ private function validateDomains() { return function ( $data ) { return $this->validate( wpml_collect( $data->get( 'domains' ) ), $data ); }; } /** * @return callable(Collection) : Either Left(unavailable) | Right(data) */ private function handleDomains() { $saveDomains = Fns::tap( pipe( Obj::prop( 'domains' ), LanguageNegotiation::saveDomains() ) ); return pipe( $this->validateDomains(), chain( $saveDomains ) ); } /** * @return callable(Collection) : Either Left(unavailable) | Right(data) */ private function validateSubdirectoryUrls() { return function ( Collection $data ) { return $this->validate( \wpml_collect( $data->get( 'domains' ) )->map( Fns::nthArg( 1 ) ), $data ); }; } /** * @param Collection $domains * @param Collection $data * * @return callable|\WPML\FP\Left|Right */ private function validate( Collection $domains, Collection $data ) { $unavailableUrls = $domains->reject( $this->getValidator( $data ) )->keys()->toArray(); return $unavailableUrls ? Either::left( $unavailableUrls ) : Either::of( $data ); } /** * @param Collection $data * * @return callable(string) : bool */ private function getValidator( Collection $data ) { $validator = Relation::propEq( 'mode', LanguageNegotiation::DIRECTORY_STRING, $data ) ? [ make( \WPML_Lang_URL_Validator::class ), 'validate_langs_in_dirs' ] : [ make( \WPML_Language_Domain_Validation::class ), 'is_valid' ]; return Obj::propOr( false, 'ignoreInvalidUrls', $data ) ? Fns::always( true ) : Fns::unary( $validator ); } } setup/endpoints/SetSecondaryLanguages.php 0000755 00000001055 14720342453 0014662 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; use WPML\Setup\Option; class SetSecondaryLanguages implements IHandler { public function run( Collection $data ) { return Either::of( $data ) ->map( Obj::prop( 'languages' ) ) ->map( Fns::tap( [ Option::class, 'setTranslationLangs' ] ) ) ->map( Lst::join( ', ' ) ) ->map( Str::concat('success: ') ); } } setup/endpoints/CurrentStep.php 0000755 00000002025 14720342453 0012704 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\Setup\Option; class CurrentStep implements IHandler { const STEP_TRANSLATION_SETTINGS = 'translationSettings'; const STEP_HIGH_COSTS_WARNING = 'highCostsWarning'; const STEPS = [ 'languages', 'address', 'license', 'translation', self::STEP_TRANSLATION_SETTINGS, self::STEP_HIGH_COSTS_WARNING, 'pauseTranslateEverything', 'support', 'plugins', 'finished' ]; public function run( Collection $data ) { $isValid = Logic::allPass( [ Lst::includes( Fns::__, self::STEPS ), Logic::ifElse( Relation::equals( 'languages' ), Fns::identity(), Fns::always( ! empty( Option::getTranslationLangs() ) ) ), ] ); return Either::fromNullable( Obj::prop( 'currentStep', $data ) ) ->filter( $isValid ) ->map( [ Option::class, 'saveCurrentStep' ] ); } } setup/endpoints/LicenseStep.php 0000755 00000003772 14720342453 0012656 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use OTGS_Installer_Subscription; use WPML\Ajax\IHandler; use WPML\API\Sanitize; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Right; use WPML\FP\Left; use WPML\Plugins; class LicenseStep implements IHandler { const ACTION_REGISTER_SITE_KEY = 'register-site-key'; const ACTION_GET_SITE_TYPE = 'get-site-type'; public function run( Collection $data ) { $action = $data->get( 'action' ); switch ( $action ) { case self::ACTION_REGISTER_SITE_KEY: return $this->register_site_key( $data ); case self::ACTION_GET_SITE_TYPE: return $this->get_site_type(); default: } return $this->unexpectedError(); } private function register_site_key( Collection $data ) { $site_key = Sanitize::string( $data->get( 'siteKey' ) ); icl_set_setting( 'site_key', null, true ); if ( function_exists( 'OTGS_Installer' ) ) { $args = [ 'repository_id' => 'wpml', 'nonce' => wp_create_nonce( 'save_site_key_wpml' ), 'site_key' => $site_key, 'return' => 1, ]; $r = OTGS_Installer()->save_site_key( $args ); if ( ! empty( $r['error'] ) ) { return Either::left( [ 'msg' => strip_tags( $r['error'] ) ] ); } else { icl_set_setting( 'site_key', $site_key, true ); $isTMAllowed = Plugins::updateTMAllowedOption(); return Right::of( [ 'isTMAllowed' => $isTMAllowed, 'msg' => __( 'Thank you for registering WPML on this site. You will receive automatic updates when new versions are available.', 'sitepress' ), ] ); } } return Either::left( false ); } private function get_site_type() { $site_type = OTGS_Installer()->repository_has_development_site_key( 'wpml' ) ? OTGS_Installer_Subscription::SITE_KEY_TYPE_DEVELOPMENT : OTGS_Installer_Subscription::SITE_KEY_TYPE_PRODUCTION; return Right::of( $site_type ); } private function unexpectedError() { return Left::of( __( 'Server error. Please refresh and try again.', 'sitepress' ) ); } } setup/endpoints/SetSupport.php 0000755 00000001514 14720342453 0012560 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; class SetSupport implements IHandler { const EVENT_SEND_COMPONENTS_AFTER_REGISTRATION = 'otgs_send_components_data_on_product_registration'; public function run( Collection $data ) { $settingStorage = new \OTGS_Installer_WP_Share_Local_Components_Setting(); $settingStorage->save( [ $data->get( 'repo' ) => $data->get( 'agree' ) ] ); if ( $settingStorage->is_repo_allowed( $data->get( 'repo' ) ) ) { $this->schedule_send_components_data(); } return Right::of( true ); } private function schedule_send_components_data() { if ( ! wp_next_scheduled( self::EVENT_SEND_COMPONENTS_AFTER_REGISTRATION ) ) { wp_schedule_single_event( time() + 60, self::EVENT_SEND_COMPONENTS_AFTER_REGISTRATION ); } } } setup/endpoints/FinishStep.php 0000755 00000005504 14720342453 0012507 0 ustar 00 <?php namespace WPML\Setup\Endpoint; use WPML\AdminLanguageSwitcher\AdminLanguageSwitcher; use WPML\Ajax\IHandler; use WPML\API\Settings; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\LIB\WP\User; use WPML\TM\ATE\AutoTranslate\Endpoint\EnableATE; use WPML\UrlHandling\WPLoginUrlConverter; use function WPML\Container\make; use WPML\FP\Lst; use WPML\FP\Right; use WPML\Setup\Option; use WPML\TM\Menu\TranslationServices\Endpoints\Deactivate; use WPML\TranslationMode\Endpoint\SetTranslateEverything; class FinishStep implements IHandler { public function run( Collection $data ) { // Prepare media setup which will run right after finishing WPML setup. \WPML\Media\Option::prepareSetup(); $wpmlInstallation = wpml_get_setup_instance(); $originalLanguage = Option::getOriginalLang(); $wpmlInstallation->finish_step1( $originalLanguage ); $wpmlInstallation->finish_step2( Lst::append( $originalLanguage, Option::getTranslationLangs() ) ); $wpmlInstallation->finish_installation(); self::enableFooterLanguageSwitcher(); if ( Option::isPausedTranslateEverything() ) { // Resave translate everything settings as now languages // are activated, which happened on 'finish_step2'. make( SetTranslateEverything::class )->run( wpml_collect( [ 'onlyNew' => true ] ) ); } $translationMode = Option::getTranslationMode(); if ( ! Lst::includes( 'users', $translationMode ) ) { make( \WPML_Translator_Records::class )->delete_all(); } if ( ! Lst::includes( 'manager', $translationMode ) ) { make( \WPML_Translation_Manager_Records::class )->delete_all(); } if ( Lst::includes( 'myself', $translationMode ) ) { self::setCurrentUserToTranslateAllLangs(); } if ( Option::isTMAllowed( ) ) { Option::setTranslateEverythingDefault(); if ( ! Lst::includes( 'service', $translationMode ) ) { make( Deactivate::class )->run( wpml_collect( [] ) ); } make( EnableATE::class )->run( wpml_collect( [] ) ); } else { Option::setTranslateEverything( false ); } WPLoginUrlConverter::enable( true ); AdminLanguageSwitcher::enable(); return Right::of( true ); } private static function enableFooterLanguageSwitcher() { \WPML_Config::load_config_run(); /** @var \WPML_LS_Settings $lsSettings */ $lsSettings = make( \WPML_LS_Dependencies_Factory::class )->settings(); $settings = $lsSettings->get_settings(); $settings['statics']['footer']->set( 'show', true ); $lsSettings->save_settings( $settings ); } private static function setCurrentUserToTranslateAllLangs() { $currentUser = User::getCurrent(); $currentUser->add_cap( \WPML\LIB\WP\User::CAP_TRANSLATE ); User::updateMeta( $currentUser->ID, \WPML_TM_Wizard_Options::ONLY_I_USER_META, true ); make( \WPML_Language_Pair_Records::class )->store( $currentUser->ID, \WPML_All_Language_Pairs::get() ); } } post-edit-screen/TranslationEditorPostSettings.php 0000755 00000003540 14720342453 0016507 0 ustar 00 <?php namespace WPML\TM\PostEditScreen; use WPML\Element\API\PostTranslations; use WPML\Element\API\Translations; use WPML\FP\Fns; use WPML\LIB\WP\Hooks; use WPML\TM\PostEditScreen\Endpoints\SetEditorMode; use WPML\Core\WP\App\Resources; use function WPML\FP\spreadArgs; class TranslationEditorPostSettings { private $sitepress; public function __construct( $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { Hooks::onAction( 'admin_enqueue_scripts' ) ->then( [ $this, 'localize' ] ) ->then( Resources::enqueueApp( 'postEditTranslationEditor' ) ); $render = Fns::once( spreadArgs( [ $this, 'render' ] ) ); Hooks::onAction( 'wpml_before_post_edit_translations_table' ) ->then( $render ); Hooks::onAction( 'wpml_before_post_edit_translations_summary' ) ->then( $render ); } public static function localize() { return [ 'name' => 'wpml_translation_post_editor', 'data' => [ 'endpoints' => [ 'setEditorMode' => SetEditorMode::class, ], ], ]; } public function render( $post ) { global $wp_post_types; if ( ! Translations::isOriginal( $post->ID, PostTranslations::get( $post->ID ) ) ) { return; } list( $useTmEditor, $isWpmlEditorBlocked, $reason ) = \WPML_TM_Post_Edit_TM_Editor_Mode::get_editor_settings( $this->sitepress, $post->ID ); $enabledEditor = $useTmEditor && ! $isWpmlEditorBlocked ? 'wpml' : 'native'; $postTypeLabels = $wp_post_types[ $post->post_type ]->labels; echo '<div id="translation-editor-post-settings" data-post-id="' . $post->ID . '" data-post-type="' . $post->post_type . '" data-enabled-editor="' . $enabledEditor . '" data-is-wpml-blocked="' . $isWpmlEditorBlocked . '" data-wpml-blocked-reason="' . $reason . '" data-type-singular="' . $postTypeLabels->singular_name . '" data-type-plural="' . $postTypeLabels->name . '"></div>'; } } post-edit-screen/class-wpml-tm-post-edit-tm-editor-select-factory.php 0000755 00000000716 14720342453 0021707 0 ustar 00 <?php use WPML\TM\PostEditScreen\TranslationEditorPostSettings; class WPML_TM_Post_Edit_TM_Editor_Select_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { public function create() { global $sitepress; if ( $sitepress->is_post_edit_screen() || wpml_is_ajax() || apply_filters( 'wpml_enable_language_meta_box', false ) ) { return new TranslationEditorPostSettings( $sitepress ); } else { return null; } } } post-edit-screen/endpoints/SetEditorMode.php 0000755 00000003172 14720342453 0015166 0 ustar 00 <?php namespace WPML\TM\PostEditScreen\Endpoints; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; use WPML_TM_Post_Edit_TM_Editor_Mode; class SetEditorMode implements IHandler { /** @var \SitePress $sitepress */ private $sitepress; public function __construct( \SitePress $sitepress ) { $this->sitepress = $sitepress; } public function run( Collection $data ) { $tmSettings = $this->sitepress->get_setting( 'translation-management' ); $useNativeEditor = $data->get( 'enabledEditor' ) !== 'wpml'; $postId = $data->get( 'postId' ); $editorModeFor = $data->get( 'editorModeFor' ); switch ( $editorModeFor ) { case 'global': $tmSettings[ WPML_TM_Post_Edit_TM_Editor_Mode::TM_KEY_GLOBAL_USE_NATIVE ] = $useNativeEditor; $tmSettings[ WPML_TM_Post_Edit_TM_Editor_Mode::TM_KEY_FOR_POST_TYPE_USE_NATIVE ] = []; $this->sitepress->set_setting( 'translation-management', $tmSettings, true ); WPML_TM_Post_Edit_TM_Editor_Mode::delete_all_posts_option(); break; case 'all_posts_of_type': $post_type = get_post_type( $postId ); if ( $post_type ) { $tmSettings[ WPML_TM_Post_Edit_TM_Editor_Mode::TM_KEY_FOR_POST_TYPE_USE_NATIVE ][ $post_type ] = $useNativeEditor; $this->sitepress->set_setting( 'translation-management', $tmSettings, true ); WPML_TM_Post_Edit_TM_Editor_Mode::delete_all_posts_option( $post_type ); } break; case 'this_post': update_post_meta( $postId, WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, $useNativeEditor ? 'yes' : 'no' ); break; } return Right::of( true ); } } post-edit-screen/class-wpml-tm-post-edit-tm-editor-mode.php 0000755 00000011115 14720342453 0017702 0 ustar 00 <?php use WPML\FP\Obj; use WPML\Collect\Support\Collection; class WPML_TM_Post_Edit_TM_Editor_Mode { const POST_META_KEY_USE_NATIVE = '_wpml_post_translation_editor_native'; const TM_KEY_FOR_POST_TYPE_USE_NATIVE = 'post_translation_editor_native_for_post_type'; const TM_KEY_GLOBAL_USE_NATIVE = 'post_translation_editor_native'; /** * Check post meta first * Then check setting for post type * Then finally check global setting * * @param SitePress $sitepress * @param $post * * @return bool */ public static function is_using_tm_editor( SitePress $sitepress, $post_id ) { $post_id = self::get_source_id( $sitepress, $post_id, 'post_' . get_post_type( $post_id ) ); $post_meta = get_post_meta( $post_id, self::POST_META_KEY_USE_NATIVE, true ); if ( 'no' === $post_meta ) { return true; } elseif ( 'yes' === $post_meta ) { return false; } $tm_settings = self::init_settings( $sitepress ); $post_type = get_post_type( $post_id ); if ( isset( $tm_settings[ self::TM_KEY_FOR_POST_TYPE_USE_NATIVE ][ $post_type ] ) ) { return ! $tm_settings[ self::TM_KEY_FOR_POST_TYPE_USE_NATIVE ][ $post_type ]; } return ! $tm_settings[ self::TM_KEY_GLOBAL_USE_NATIVE ]; } /** * @param SitePress $sitepress * @param int $postId * * @return array */ public static function get_editor_settings( SitePress $sitepress, $postId ) { $useTmEditor = \WPML_TM_Post_Edit_TM_Editor_Mode::is_using_tm_editor( $sitepress, $postId ); $useTmEditor = apply_filters( 'wpml_use_tm_editor', $useTmEditor, $postId ); $result = self::get_blocked_posts( [ $postId ] ); if ( isset($result[$postId]) ) { $isWpmlEditorBlocked = true; $reason = $result[$postId]; } else { $isWpmlEditorBlocked = false; $reason = ''; } return [ $useTmEditor, $isWpmlEditorBlocked, $reason, ]; } /** * @param array $postIds list of post ids that should be checked is blocked. * * @return array list of post ids that are blocked and the reason why they are blocked. */ public static function get_blocked_posts( $postIds ) { /** * Returns the editor settings for the posts - is the WPML editor blocked, and if so, why. * * Filter returns an array of: the reason why its blocked indexed by the post ID. * * @since 4.6.0 * * @param array $defaultParams The default parameters that should be returned * @param array $postIds An array of post IDs */ return apply_filters( 'wpml_tm_editor_exclude_posts', [], $postIds ); } /** * @param SitePress $sitepress * @param int $post_id * @param string $wpml_post_type * * @return int */ private static function get_source_id( SitePress $sitepress, $post_id, $wpml_post_type ) { $source_id = $post_id; $trid = $sitepress->get_element_trid( $post_id, $wpml_post_type ); $translations = $sitepress->get_element_translations( $trid, $wpml_post_type ); if ( ! $translations ) { return (int) $post_id; } foreach ( $translations as $translation ) { if ( $translation->original ) { $source_id = $translation->element_id; break; } } return (int) $source_id; } /** * @param SitePress $sitepress * * @return array */ private static function init_settings( SitePress $sitepress ) { $tm_settings = $sitepress->get_setting( 'translation-management' ); /** * Until a user explicitly change the settings through * the switcher ( @see WPML_TM_Post_Edit_TM_Editor_Select::save_mode ), * we'll set it by default at run time: * - Native editor set to true if using the manual method * - Native editor set to false otherwise */ if ( ! isset( $tm_settings['post_translation_editor_native'] ) ) { if ( ( (string) ICL_TM_TMETHOD_MANUAL === (string) $tm_settings['doc_translation_method'] ) ) { $tm_settings['post_translation_editor_native'] = true; } else { $tm_settings['post_translation_editor_native'] = false; } if ( ! isset( $tm_settings['post_translation_editor_native_for_post_type'] ) ) { $tm_settings['post_translation_editor_native_for_post_type'] = []; } } return $tm_settings; } /** * @param null|string $post_type */ public static function delete_all_posts_option( $post_type = null ) { global $wpdb; if ( $post_type ) { $wpdb->query( $wpdb->prepare( "DELETE postmeta FROM {$wpdb->postmeta} AS postmeta INNER JOIN {$wpdb->posts} AS posts ON posts.ID = postmeta.post_id WHERE posts.post_type = %s AND postmeta.meta_key = %s", $post_type, self::POST_META_KEY_USE_NATIVE ) ); } else { delete_post_meta_by_key( self::POST_META_KEY_USE_NATIVE ); } } } media/duplication/Hooks.php 0000755 00000001742 14720342453 0011745 0 ustar 00 <?php namespace WPML\Media\Duplication; use WPML\Element\API\IfOriginalPost; use WPML\FP\Fns; use WPML\FP\Relation; use WPML\LIB\WP\Post; use WPML\LIB\WP\Hooks as WPHooks; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class Hooks { public static function add() { WPHooks::onAction( 'update_postmeta', 10, 4 ) ->then( spreadArgs( Fns::withoutRecursion( Fns::noop(), [ self::class, 'syncAttachedFile' ] ) ) ); } public static function syncAttachedFile( $meta_id, $object_id, $meta_key, $meta_value ) { if ( $meta_key === '_wp_attached_file' ) { $prevValue = Post::getMetaSingle( $object_id, $meta_key ); // $isMetaSameAsPrevious :: id → bool $isMetaSameAsPrevious = pipe( Post::getMetaSingle( Fns::__, $meta_key ), Relation::equals( $prevValue ) ); IfOriginalPost::getTranslationIds( $object_id ) ->filter( Fns::unary( $isMetaSameAsPrevious ) ) ->each( Fns::unary( Post::updateMeta( Fns::__, $meta_key, $meta_value ) ) ); } } } media/duplication/class-wpml-media-attachments-duplication-factory.php 0000755 00000000424 14720342453 0022304 0 ustar 00 <?php class WPML_Media_Attachments_Duplication_Factory extends \WPML\Media\Duplication\AbstractFactory { public function create() { if ( self::shouldActivateHooks() ) { return \WPML\Container\make( WPML_Media_Attachments_Duplication::class ); } return null; } } media/duplication/AbstractFactory.php 0000755 00000000473 14720342453 0013755 0 ustar 00 <?php namespace WPML\Media\Duplication; abstract class AbstractFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { /** * @return bool */ protected static function shouldActivateHooks() { return \WPML_Element_Sync_Settings_Factory::createPost()->is_sync( 'attachment' ); } } media/duplication/class-wpml-media-attachments-duplication.php 0000755 00000151447 14720342453 0020653 0 ustar 00 <?php use WPML\FP\Obj; use WPML\LIB\WP\Nonce; use WPML\LIB\WP\User; use WPML\Media\Option; class WPML_Media_Attachments_Duplication { const WPML_MEDIA_PROCESSED_META_KEY = 'wpml_media_processed'; /** @var WPML_Model_Attachments */ private $attachments_model; /** @var SitePress */ private $sitepress; private $wpdb; private $language_resolution; private $original_thumbnail_ids = array(); /** * WPML_Media_Attachments_Duplication constructor. * * @param SitePress $sitepress * @param WPML_Model_Attachments $attachments_model * * @internal param WPML_WP_API $wpml_wp_api */ public function __construct( SitePress $sitepress, WPML_Model_Attachments $attachments_model, wpdb $wpdb, WPML_Language_Resolution $language_resolution ) { $this->sitepress = $sitepress; $this->attachments_model = $attachments_model; $this->wpdb = $wpdb; $this->language_resolution = $language_resolution; } public function add_hooks() { // do not run this when user is importing posts in Tools > Import if ( ! isset( $_GET['import'] ) || $_GET['import'] !== 'wordpress' ) { add_action( 'add_attachment', array( $this, 'save_attachment_actions' ) ); add_action( 'add_attachment', array( $this, 'save_translated_attachments' ) ); add_filter( 'wp_generate_attachment_metadata', array( $this, 'wp_generate_attachment_metadata' ), 10, 2 ); } $active_languages = $this->language_resolution->get_active_language_codes(); if ( $this->is_admin_or_xmlrpc() && ! $this->is_uploading_plugin_or_theme() && 1 < count( $active_languages ) ) { add_action( 'edit_attachment', array( $this, 'save_attachment_actions' ) ); add_action( 'icl_make_duplicate', array( $this, 'make_duplicate' ), 10, 4 ); } $this->add_postmeta_hooks(); add_action( 'save_post', array( $this, 'save_post_actions' ), 100, 2 ); add_action( 'wpml_pro_translation_completed', array( $this, 'sync_on_translation_complete' ), 10, 3 ); add_action( 'wp_ajax_wpml_media_set_initial_language', array( $this, 'batch_set_initial_language' ) ); add_action( 'wp_ajax_wpml_media_translate_media', array( $this, 'ajax_batch_translate_media' ), 10, 0 ); add_action( 'wp_ajax_wpml_media_duplicate_media', array( $this, 'ajax_batch_duplicate_media' ), 10, 0 ); add_action( 'wp_ajax_wpml_media_duplicate_featured_images', array( $this, 'ajax_batch_duplicate_featured_images' ), 10, 0 ); add_action( 'wp_ajax_wpml_media_mark_processed', array( $this, 'ajax_batch_mark_processed' ), 10, 0 ); add_action( 'wp_ajax_wpml_media_scan_prepare', array( $this, 'ajax_batch_scan_prepare' ), 10, 0 ); add_action( 'wp_ajax_wpml_media_set_content_prepare', array( $this, 'set_content_defaults_prepare' ) ); add_action( 'wpml_loaded', array( $this, 'add_settings_hooks' ) ); } public function add_settings_hooks() { if ( User::getCurrent() && ( User::canManageTranslations() || User::hasCap( 'wpml_manage_media_translation' ) ) ) { add_action('wp_ajax_wpml_media_set_content_defaults', array($this, 'wpml_media_set_content_defaults') ); } } private function add_postmeta_hooks() { add_action( 'update_postmeta', [ $this, 'record_original_thumbnail_ids_and_sync' ], 10, 4 ); add_action( 'delete_post_meta', [ $this, 'record_original_thumbnail_ids_and_sync' ], 10, 4 ); } private function withPostMetaFiltersDisabled( callable $callback ) { $filter = [ $this, 'record_original_thumbnail_ids_and_sync' ]; $shouldRestoreFilters = remove_action( 'update_postmeta', $filter, 10 ) && remove_action( 'delete_post_meta', $filter, 10 ); $callback(); if ( $shouldRestoreFilters ) { $this->add_postmeta_hooks(); } } private function is_admin_or_xmlrpc() { $is_admin = is_admin(); $is_xmlrpc = defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST; return $is_admin || $is_xmlrpc; } public function save_attachment_actions( $post_id ) { if ( $this->is_uploading_media_on_wpml_media_screen() ) { return; } if ( $this->is_uploading_plugin_or_theme() && get_post_type( $post_id ) == 'attachment' ) { return; } $media_language = $this->sitepress->get_language_for_element( $post_id, 'post_attachment' ); $trid = false; if ( ! empty( $media_language ) ) { $trid = $this->sitepress->get_element_trid( $post_id, 'post_attachment' ); } if ( empty( $media_language ) ) { $parent_post_sql = "SELECT p2.ID, p2.post_type FROM {$this->wpdb->posts} p1 JOIN {$this->wpdb->posts} p2 ON p1.post_parent = p2.ID WHERE p1.ID=%d"; $parent_post_prepared = $this->wpdb->prepare( $parent_post_sql, array( $post_id ) ); /** @var \stdClass $parent_post */ $parent_post = $this->wpdb->get_row( $parent_post_prepared ); if ( $parent_post ) { $media_language = $this->sitepress->get_language_for_element( $parent_post->ID, 'post_' . $parent_post->post_type ); } if ( empty( $media_language ) ) { $media_language = $this->sitepress->get_admin_language_cookie(); } if ( empty( $media_language ) ) { $media_language = $this->sitepress->get_default_language(); } } if ( ! empty( $media_language ) ) { $this->sitepress->set_element_language_details( $post_id, 'post_attachment', $trid, $media_language ); $this->save_translated_attachments( $post_id ); $this->update_attachment_metadata( $post_id ); } } private function is_uploading_media_on_wpml_media_screen() { return isset( $_POST['action'] ) && 'wpml_media_save_translation' === $_POST['action']; } public function wp_generate_attachment_metadata( $metadata, $attachment_id ) { if ( $this->is_uploading_media_on_wpml_media_screen() ) { return $metadata; } $this->synchronize_attachment_metadata( $metadata, $attachment_id ); return $metadata; } private function update_attachment_metadata( $source_attachment_id ) { $original_element_id = $this->sitepress->get_original_element_id( $source_attachment_id, 'post_attachment', false, false, true ); if ( $original_element_id ) { $metadata = wp_get_attachment_metadata( $original_element_id ); $this->synchronize_attachment_metadata( $metadata, $original_element_id ); } } private function synchronize_attachment_metadata( $metadata, $attachment_id ) { // Update _wp_attachment_metadata to all translations (excluding the current one) $trid = $this->sitepress->get_element_trid( $attachment_id, 'post_attachment' ); if ( $trid ) { $translations = $this->sitepress->get_element_translations( $trid, 'post_attachment', true, true, true ); foreach ( $translations as $translation ) { if ( $translation->element_id != $attachment_id ) { $this->update_attachment_texts( $translation ); /** * Action to allow synchronise additional attachment data with translation. * * @param int $attachment_id The ID of original attachment. * @param object $translation The translated attachment. */ do_action( 'wpml_after_update_attachment_texts', $attachment_id, $translation ); $attachment_meta_data = get_post_meta( $translation->element_id, '_wp_attachment_metadata' ); if ( isset( $attachment_meta_data[0]['file'] ) ) { continue; } update_post_meta( $translation->element_id, '_wp_attachment_metadata', $metadata ); $mime_type = get_post_mime_type( $attachment_id ); if ( $mime_type ) { $this->wpdb->update( $this->wpdb->posts, array( 'post_mime_type' => $mime_type ), array( 'ID' => $translation->element_id ) ); } } } } } private function update_attachment_texts( $translation ) { if ( ! isset( $_POST['changes'] ) ) { return; } $changes = array( 'ID' => $translation->element_id ); foreach ( $_POST['changes'] as $key => $value ) { switch ( $key ) { case 'caption': $post = get_post( $translation->element_id ); if ( ! $post->post_excerpt ) { $changes['post_excerpt'] = $value; } break; case 'description': $translated_attachment = get_post( $translation->element_id ); if ( ! $translated_attachment->post_content ) { $changes['post_content'] = $value; } break; case 'alt': if ( ! get_post_meta( $translation->element_id, '_wp_attachment_image_alt', true ) ) { update_post_meta( $translation->element_id, '_wp_attachment_image_alt', $value ); } break; } } remove_action( 'edit_attachment', array( $this, 'save_attachment_actions' ) ); wp_update_post( $changes ); add_action( 'edit_attachment', array( $this, 'save_attachment_actions' ) ); } public function save_translated_attachments( $post_id ) { if ( $this->is_uploading_plugin_or_theme() && get_post_type( $post_id ) == 'attachment' ) { return; } $language_details = $this->sitepress->get_element_language_details( $post_id, 'post_attachment' ); if ( isset( $language_details->language_code ) ) { $this->translate_attachments( $post_id, $language_details->language_code ); } } private function translate_attachments( $attachment_id, $source_language, $override_always_translate_media = false ) { if ( ! $source_language ) { return; } if ( $override_always_translate_media || Obj::prop( 'always_translate_media', Option::getNewContentSettings() ) ) { /** @var SitePress $sitepress */ global $sitepress; $original_attachment_id = false; $trid = $sitepress->get_element_trid( $attachment_id, 'post_attachment' ); if ( $trid ) { $translations = $sitepress->get_element_translations( $trid, 'post_attachment', true, true ); $translated_languages = []; $default_language = $sitepress->get_default_language(); $default_language_attachment_id = false; foreach ( $translations as $translation ) { // Get the default language attachment ID if ( $translation->original ) { $original_attachment_id = $translation->element_id; } if ( $translation->language_code == $default_language ) { $default_language_attachment_id = $translation->element_id; } // Store already translated versions $translated_languages[] = $translation->language_code; } // Original attachment is missing if ( ! $original_attachment_id ) { $attachment = get_post( $attachment_id ); if ( ! $default_language_attachment_id ) { $this->create_duplicate_attachment( $attachment_id, $attachment->post_parent, $default_language ); } else { $sitepress->set_element_language_details( $default_language_attachment_id, 'post_attachment', $trid, $default_language, null ); } // Start over $this->translate_attachments( $attachment->ID, $source_language ); } else { // Original attachment is present $original = get_post( $original_attachment_id ); $codes = array_keys( $sitepress->get_active_languages() ); foreach ( $codes as $code ) { // If translation is not present, create it if ( ! in_array( $code, $translated_languages ) ) { $this->create_duplicate_attachment( $attachment_id, $original->post_parent, $code ); } } } } } } private function is_uploading_plugin_or_theme() { global $action; return isset( $action ) && ( $action == 'upload-plugin' || $action == 'upload-theme' ); } public function make_duplicate( $master_post_id, $target_lang, $post_array, $target_post_id ) { $translated_attachment_id = false; // Get Master Post attachments $master_post_attachment_ids_prepared = $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_parent = %d AND post_type = %s", array( $master_post_id, 'attachment', ) ); $master_post_attachment_ids = $this->wpdb->get_col( $master_post_attachment_ids_prepared ); if ( $master_post_attachment_ids ) { foreach ( $master_post_attachment_ids as $master_post_attachment_id ) { $attachment_trid = $this->sitepress->get_element_trid( $master_post_attachment_id, 'post_attachment' ); if ( $attachment_trid ) { // Get attachment translation $attachment_translations = $this->sitepress->get_element_translations( $attachment_trid, 'post_attachment' ); foreach ( $attachment_translations as $attachment_translation ) { if ( $attachment_translation->language_code == $target_lang ) { $translated_attachment_id = $attachment_translation->element_id; break; } } if ( ! $translated_attachment_id ) { $translated_attachment_id = $this->create_duplicate_attachment( $master_post_attachment_id, wp_get_post_parent_id( $master_post_id ), $target_lang ); } if ( $translated_attachment_id ) { // Set the parent post, if not already set $translated_attachment = get_post( $translated_attachment_id ); if ( $translated_attachment && ! $translated_attachment->post_parent ) { $prepared_query = $this->wpdb->prepare( "UPDATE {$this->wpdb->posts} SET post_parent=%d WHERE ID=%d", array( $target_post_id, $translated_attachment_id, ) ); $this->wpdb->query( $prepared_query ); } } } } } // Duplicate the featured image. $thumbnail_id = get_post_meta( $master_post_id, '_thumbnail_id', true ); if ( $thumbnail_id ) { $thumbnail_trid = $this->sitepress->get_element_trid( $thumbnail_id, 'post_attachment' ); if ( $thumbnail_trid ) { // translation doesn't have a featured image $t_thumbnail_id = icl_object_id( $thumbnail_id, 'attachment', false, $target_lang ); if ( $t_thumbnail_id == null ) { $dup_att_id = $this->create_duplicate_attachment( $thumbnail_id, $target_post_id, $target_lang ); $t_thumbnail_id = $dup_att_id; } if ( $t_thumbnail_id != null ) { update_post_meta( $target_post_id, '_thumbnail_id', $t_thumbnail_id ); } } } return $translated_attachment_id; } /** * @param int $attachment_id * @param int|false|null $parent_id * @param string $target_language * * @return int|null */ public function create_duplicate_attachment( $attachment_id, $parent_id, $target_language ) { try { $attachment_post = get_post( $attachment_id ); if ( ! $attachment_post ) { throw new WPML_Media_Exception( sprintf( 'Post with id %d does not exist', $attachment_id ) ); } $trid = $this->sitepress->get_element_trid( $attachment_id, WPML_Model_Attachments::ATTACHMENT_TYPE ); if ( ! $trid ) { throw new WPML_Media_Exception( sprintf( 'Attachment with id %s does not contain language information', $attachment_id ) ); } $duplicated_attachment = $this->attachments_model->find_duplicated_attachment( $trid, $target_language ); $duplicated_attachment_id = null; if ( null !== $duplicated_attachment ) { $duplicated_attachment_id = $duplicated_attachment->ID; } $translated_parent_id = $this->attachments_model->fetch_translated_parent_id( $duplicated_attachment, $parent_id, $target_language ); if ( null !== $duplicated_attachment ) { if ( (int) $duplicated_attachment->post_parent !== (int) $translated_parent_id ) { $this->attachments_model->update_parent_id_in_existing_attachment( $translated_parent_id, $duplicated_attachment ); } } else { $duplicated_attachment_id = $this->attachments_model->duplicate_attachment( $attachment_id, $target_language, $translated_parent_id, $trid ); } $this->attachments_model->duplicate_post_meta_data( $attachment_id, $duplicated_attachment_id ); /** * Fires when attachment is duplicated * * @since 4.1.0 * * @param int $attachment_id The ID of the source/original attachment. * @param int $duplicated_attachment_id The ID of the duplicated attachment. */ do_action( 'wpml_after_duplicate_attachment', $attachment_id, $duplicated_attachment_id ); return $duplicated_attachment_id; } catch ( WPML_Media_Exception $e ) { return null; } } public function sync_on_translation_complete( $new_post_id, $fields, $job ) { $new_post = get_post( $new_post_id ); $this->save_post_actions( $new_post_id, $new_post ); } public function record_original_thumbnail_ids_and_sync( $meta_id, $object_id, $meta_key, $meta_value ) { if ( '_thumbnail_id' === $meta_key ) { $original_thumbnail_id = get_post_meta( $object_id, $meta_key, true ); if ( $original_thumbnail_id !== $meta_value ) { $this->original_thumbnail_ids[ $object_id ] = $original_thumbnail_id; $this->sync_post_thumbnail( $object_id, $meta_value ? $meta_value : false ); } } } /** * @param int $pidd * @param WP_Post $post */ function save_post_actions( $pidd, $post ) { if ( ! $post ) { return; } if ( $post->post_type !== 'attachment' && $post->post_status !== 'auto-draft' ) { $this->sync_attachments( $pidd, $post ); } if ( $post->post_type === 'attachment' ) { $metadata = wp_get_attachment_metadata( $post->ID ); $attachment_id = $pidd; if ( $metadata ) { $this->synchronize_attachment_metadata( $metadata, $attachment_id ); } } } /** * @param int $pidd * @param WP_Post $post */ function sync_attachments( $pidd, $post ) { if ( $post->post_type == 'attachment' || $post->post_status == 'auto-draft' ) { return; } $posts_prepared = $this->wpdb->prepare( "SELECT post_type, post_status FROM {$this->wpdb->posts} WHERE ID = %d", array( $pidd ) ); list( $post_type, $post_status ) = $this->wpdb->get_row( $posts_prepared, ARRAY_N ); // checking - if translation and not saved before if ( isset( $_GET['trid'] ) && ! empty( $_GET['trid'] ) && $post_status == 'auto-draft' ) { // get source language if ( isset( $_GET['source_lang'] ) && ! empty( $_GET['source_lang'] ) ) { $src_lang = $_GET['source_lang']; } else { $src_lang = $this->sitepress->get_default_language(); } // get source id $src_id_prepared = $this->wpdb->prepare( "SELECT element_id FROM {$this->wpdb->prefix}icl_translations WHERE trid=%d AND language_code=%s", array( $_GET['trid'], $src_lang ) ); $src_id = $this->wpdb->get_var( $src_id_prepared ); // delete exist auto-draft post media $results_prepared = $this->wpdb->prepare( "SELECT p.id FROM {$this->wpdb->posts} AS p LEFT JOIN {$this->wpdb->posts} AS p1 ON p.post_parent = p1.id WHERE p1.post_status = %s", array( 'auto-draft' ) ); $results = $this->wpdb->get_results( $results_prepared, ARRAY_A ); $attachments = array(); if ( ! empty( $results ) ) { foreach ( $results as $result ) { $attachments[] = $result['id']; } if ( ! empty( $attachments ) ) { $in_attachments = wpml_prepare_in( $attachments, '%d' ); $delete_prepared = "DELETE FROM {$this->wpdb->posts} WHERE id IN (" . $in_attachments . ')'; $this->wpdb->query( $delete_prepared ); $delete_prepared = "DELETE FROM {$this->wpdb->postmeta} WHERE post_id IN (" . $in_attachments . ')'; $this->wpdb->query( $delete_prepared ); } } // checking - if set duplicate media if ( $src_id && Option::shouldDuplicateMedia( (int) $src_id ) ) { // duplicate media before first save $this->duplicate_post_attachments( $pidd, $_GET['trid'], $src_lang, $this->sitepress->get_language_for_element( $pidd, 'post_' . $post_type ) ); } } // exceptions if ( ! $this->sitepress->is_translated_post_type( $post_type ) || isset( $_POST['autosave'] ) || ( isset( $_POST['post_ID'] ) && $_POST['post_ID'] != $pidd ) || ( isset( $_POST['post_type'] ) && $_POST['post_type'] === 'revision' ) || $post_type === 'revision' || get_post_meta( $pidd, '_wp_trash_meta_status', true ) || ( isset( $_GET['action'] ) && $_GET['action'] === 'restore' ) || $post_status === 'auto-draft' ) { return; } if ( isset( $_POST['icl_trid'] ) ) { $icl_trid = $_POST['icl_trid']; } else { // get trid from database. $icl_trid_prepared = $this->wpdb->prepare( "SELECT trid FROM {$this->wpdb->prefix}icl_translations WHERE element_id=%d AND element_type = %s", array( $pidd, 'post_' . $post_type ) ); $icl_trid = $this->wpdb->get_var( $icl_trid_prepared ); } if ( $icl_trid ) { $language_details = $this->sitepress->get_element_language_details( $pidd, 'post_' . $post_type ); // In some cases the sitepress cache doesn't get updated (e.g. when posts are created with wp_insert_post() // Only in this case, the sitepress cache will be cleared so we can read the element language details if ( ! $language_details ) { $this->sitepress->get_translations_cache()->clear(); $language_details = $this->sitepress->get_element_language_details( $pidd, 'post_' . $post_type ); } if ( $language_details ) { $this->duplicate_post_attachments( $pidd, $icl_trid, $language_details->source_language_code, $language_details->language_code ); } } } /** * @param int $post_id * @param int|null $request_post_thumbnail_id */ public function sync_post_thumbnail( $post_id, $request_post_thumbnail_id = null ) { if ( $post_id && Option::shouldDuplicateFeatured( $post_id ) ) { if ( null === $request_post_thumbnail_id ) { $request_post_thumbnail_id = filter_input( INPUT_POST, 'thumbnail_id', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $thumbnail_id = $request_post_thumbnail_id ? $request_post_thumbnail_id : get_post_meta( $post_id, '_thumbnail_id', true ); } else { $thumbnail_id = $request_post_thumbnail_id; } $trid = $this->sitepress->get_element_trid( $post_id, 'post_' . get_post_type( $post_id ) ); $translations = $this->sitepress->get_element_translations( $trid, 'post_' . get_post_type( $post_id ) ); // Check if it is original. $is_original = false; foreach ( $translations as $translation ) { if ( 1 === (int) $translation->original && (int) $translation->element_id === $post_id ) { $is_original = true; } } if ( $is_original ) { foreach ( $translations as $translation ) { if ( ! $translation->original && $translation->element_id ) { if ( $this->are_post_thumbnails_still_in_sync( $post_id, $thumbnail_id, $translation ) ) { if ( ! $thumbnail_id || - 1 === (int) $thumbnail_id ) { $this->withPostMetaFiltersDisabled( function () use ( $translation ) { delete_post_meta( $translation->element_id, '_thumbnail_id' ); } ); } else { $translated_thumbnail_id = wpml_object_id_filter( $thumbnail_id, 'attachment', false, $translation->language_code ); $id = get_post_meta( $translation->element_id, '_thumbnail_id', true ); if ( (int) $id !== $translated_thumbnail_id ) { $this->withPostMetaFiltersDisabled( function () use ( $translation, $translated_thumbnail_id ) { update_post_meta( $translation->element_id, '_thumbnail_id', $translated_thumbnail_id ); } ); } } } } } } } } protected function are_post_thumbnails_still_in_sync( $source_id, $source_thumbnail_id, $translation ) { $translation_thumbnail_id = get_post_meta( $translation->element_id, '_thumbnail_id', true ); if ( isset( $this->original_thumbnail_ids[ $source_id ] ) ) { if ( $this->original_thumbnail_ids[ $source_id ] === $translation_thumbnail_id ) { return true; } return $this->are_translations_of_each_other( $this->original_thumbnail_ids[ $source_id ], $translation_thumbnail_id ); } else { return $this->are_translations_of_each_other( $source_thumbnail_id, $translation_thumbnail_id ); } } private function are_translations_of_each_other( $post_id_1, $post_id_2 ) { return $this->sitepress->get_element_trid( $post_id_1, 'post_' . get_post_type( $post_id_1 ) ) === $this->sitepress->get_element_trid( $post_id_2, 'post_' . get_post_type( $post_id_2 ) ); } function duplicate_post_attachments( $pidd, $icl_trid, $source_lang = null, $lang = null ) { $wpdb = $this->wpdb; $pidd = ( is_numeric( $pidd ) ) ? (int) $pidd : null; $request_post_icl_ajx_action = filter_input( INPUT_POST, 'icl_ajx_action', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $request_post_icl_post_language = filter_input( INPUT_POST, 'icl_post_language', FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); $request_post_post_id = filter_input( INPUT_POST, 'post_id', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); if ( $icl_trid == '' ) { return; } if ( ! $source_lang ) { $source_lang_prepared = $this->wpdb->prepare( "SELECT source_language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND trid=%d", array( $pidd, $icl_trid ) ); $source_lang = $this->wpdb->get_var( $source_lang_prepared ); } // exception for making duplicates. language info not set when this runs and creating the duplicated posts 1/3 if ( $request_post_icl_ajx_action == 'make_duplicates' && $request_post_icl_post_language ) { $source_lang_prepared = $this->wpdb->prepare( "SELECT language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND trid = %d", array( $request_post_post_id, $icl_trid ) ); $source_lang = $this->wpdb->get_var( $source_lang_prepared ); $lang = $request_post_icl_post_language; } if ( $source_lang == null || $source_lang == '' ) { // This is the original see if we should copy to translations if ( Option::shouldDuplicateMedia( $pidd ) || Option::shouldDuplicateFeatured( $pidd ) ) { $translations = $wpdb->get_col( $wpdb->prepare( 'SELECT element_id FROM ' . $wpdb->prefix . 'icl_translations WHERE trid = %d', array( $icl_trid ) ) ); $translations = array_map( 'intval', $translations ); $source_attachments = $wpdb->get_col( $wpdb->prepare( 'SELECT ID FROM ' . $wpdb->posts . ' WHERE post_parent = %d AND post_type = %s', array( $pidd, 'attachment' ) ) ); $source_attachments = array_map( 'intval', $source_attachments ); $all_element_ids = []; $attachments_by_element_id = []; foreach ( $translations as $element_id ) { if ( $element_id && $element_id !== $pidd ) { $all_element_ids[] = $element_id; $attachments_by_element_id[ $element_id ] = []; } } $all_attachments = []; if ( count( $all_element_ids ) > 0 ) { $all_attachments = $wpdb->get_results( $wpdb->prepare( // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared 'SELECT ID, post_parent AS element_id FROM ' . $wpdb->posts . ' WHERE post_parent IN (' . wpml_prepare_in( $all_element_ids ) . ') AND post_type = %s', array( 'attachment' ) ), ARRAY_A ); } foreach ( $all_attachments as $attachment ) { $attachments_by_element_id[ (int) $attachment['element_id'] ][] = (int) $attachment['ID']; } foreach ( $translations as $element_id ) { if ( $element_id && $element_id !== $pidd ) { $lang_prepared = $this->wpdb->prepare( "SELECT language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND trid = %d", array( $element_id, $icl_trid ) ); $lang = $this->wpdb->get_var( $lang_prepared ); if ( Option::shouldDuplicateFeatured( $element_id ) ) { $attachments = $attachments_by_element_id[ $element_id ]; $has_missing_translation_attachment_id = false; foreach ( $attachments as $attachment_id ) { if ( ! icl_object_id( $attachment_id, 'attachment', false, $lang ) ) { $has_missing_translation_attachment_id = true; break; } } $source_attachment_ids = $has_missing_translation_attachment_id ? $source_attachments : []; foreach ( $source_attachment_ids as $source_attachment_id ) { $this->create_duplicate_attachment_not_static( $source_attachment_id, $element_id, $lang ); } } $translation_thumbnail_id = get_post_meta( $element_id, '_thumbnail_id', true ); if ( Option::shouldDuplicateFeatured( $element_id ) && empty( $translation_thumbnail_id ) ) { $thumbnail_id = get_post_meta( $pidd, '_thumbnail_id', true ); if ( $thumbnail_id ) { $t_thumbnail_id = icl_object_id( $thumbnail_id, 'attachment', false, $lang ); if ( $t_thumbnail_id == null ) { $dup_att_id = $this->create_duplicate_attachment_not_static( $thumbnail_id, $element_id, $lang ); $t_thumbnail_id = $dup_att_id; } if ( $t_thumbnail_id != null ) { update_post_meta( $element_id, '_thumbnail_id', $t_thumbnail_id ); } } } } } } } else { // This is a translation. // exception for making duplicates. language info not set when this runs and creating the duplicated posts 2/3 if ( $request_post_icl_ajx_action === 'make_duplicates' ) { $source_id = $request_post_post_id; } else { $source_id_prepared = $this->wpdb->prepare( "SELECT element_id FROM {$this->wpdb->prefix}icl_translations WHERE language_code = %s AND trid = %d", array( $source_lang, $icl_trid ) ); $source_id = $this->wpdb->get_var( $source_id_prepared ); } if ( ! $lang ) { $lang_prepared = $this->wpdb->prepare( "SELECT language_code FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND trid = %d", array( $pidd, $icl_trid ) ); $lang = $this->wpdb->get_var( $lang_prepared ); } // exception for making duplicates. language info not set when this runs and creating the duplicated posts 3/3 if ( $request_post_icl_ajx_action === 'make_duplicates' ) { $duplicate = Option::shouldDuplicateMedia( $source_id ); } else { $duplicate = Option::shouldDuplicateMedia( $pidd, false ); if ( $duplicate === null ) { // check the original state $duplicate = Option::shouldDuplicateMedia( $source_id ); } } if ( $duplicate ) { $source_attachments_prepared = $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_parent = %d AND post_type = %s", array( $source_id, 'attachment' ) ); $source_attachments = $this->wpdb->get_col( $source_attachments_prepared ); foreach ( $source_attachments as $source_attachment_id ) { $translation_attachment_id = icl_object_id( $source_attachment_id, 'attachment', false, $lang ); if ( ! $translation_attachment_id ) { self::create_duplicate_attachment( $source_attachment_id, $pidd, $lang ); } else { $translated_attachment = get_post( $translation_attachment_id ); if ( $translated_attachment && ! $translated_attachment->post_parent ) { $translated_attachment->post_parent = $pidd; /** @phpstan-ignore-next-line (WP doc issue) */ wp_update_post( $translated_attachment ); } } } } $featured = Option::shouldDuplicateFeatured( $pidd, false ); if ( $featured === null ) { // check the original state $featured = Option::shouldDuplicateFeatured( $source_id ); } $translation_thumbnail_id = get_post_meta( $pidd, '_thumbnail_id', true ); if ( $featured && empty( $translation_thumbnail_id ) ) { $thumbnail_id = get_post_meta( $source_id, '_thumbnail_id', true ); if ( $thumbnail_id ) { $t_thumbnail_id = icl_object_id( $thumbnail_id, 'attachment', false, $lang ); if ( $t_thumbnail_id == null ) { $dup_att_id = self::create_duplicate_attachment( $thumbnail_id, $pidd, $lang ); $t_thumbnail_id = $dup_att_id; } if ( $t_thumbnail_id != null ) { update_post_meta( $pidd, '_thumbnail_id', $t_thumbnail_id ); } } } } } /** * @param int $source_attachment_id * @param int $pidd * @param string $lang * * @return int|null|WP_Error */ public function create_duplicate_attachment_not_static( $source_attachment_id, $pidd, $lang ) { return self::create_duplicate_attachment( $source_attachment_id, $pidd, $lang ); } private function duplicate_featured_images( $limit = 0, $offset = 0 ) { global $wpdb; list( $thumbnails, $processed ) = $this->get_post_thumbnail_map( $limit, $offset ); if ( sizeof( $thumbnails ) ) { // Posts IDs with found featured images $post_ids = wpml_prepare_in( array_keys( $thumbnails ), '%d' ); $posts_prepared = "SELECT ID, post_type FROM {$wpdb->posts} WHERE ID IN ({$post_ids})"; $posts = $wpdb->get_results( $posts_prepared ); foreach ( $posts as $post ) { $this->duplicate_featured_image_in_post( $post, $thumbnails ); } } return $processed; } /** * @param int $limit * @param int $offset Offset to use for getting thumbnails. Default: 0. * * @return array */ public function get_post_thumbnail_map( $limit = 0, $offset = 0 ) { global $wpdb; $featured_images_sql = "SELECT * FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id' ORDER BY `meta_id`"; if ( $limit > 0 ) { $featured_images_sql .= $wpdb->prepare( ' LIMIT %d, %d', $offset, $limit ); } $featured_images = $wpdb->get_results( $featured_images_sql ); $processed = count( $featured_images ); $thumbnails = array(); foreach ( $featured_images as $featured ) { $thumbnails[ $featured->post_id ] = $featured->meta_value; } return array( $thumbnails, $processed ); } /** * @param \stdClass $post contains properties `ID` and `post_type` * @param array $thumbnails a map of post ID => thumbnail ID */ public function duplicate_featured_image_in_post( $post, $thumbnails = array() ) { global $wpdb, $sitepress; $row_prepared = $wpdb->prepare( "SELECT trid, source_language_code FROM {$wpdb->prefix}icl_translations WHERE element_id=%d AND element_type = %s", array( $post->ID, 'post_' . $post->post_type ) ); $row = $wpdb->get_row( $row_prepared ); if ( $row && $row->trid && ( $row->source_language_code == null || $row->source_language_code == '' ) ) { $translations = $sitepress->get_element_translations( $row->trid, 'post_' . $post->post_type ); foreach ( $translations as $translation ) { if ( $translation->element_id != $post->ID ) { $translation_thumbnail_id = get_post_meta( $translation->element_id, '_thumbnail_id', true ); if ( empty( $translation_thumbnail_id ) ) { if ( ! in_array( $translation->element_id, array_keys( $thumbnails ) ) ) { // translation doesn't have a featured image $t_thumbnail_id = icl_object_id( $thumbnails[ $post->ID ], 'attachment', false, $translation->language_code ); if ( $t_thumbnail_id == null ) { $dup_att_id = self::create_duplicate_attachment( $thumbnails[ $post->ID ], $translation->element_id, $translation->language_code ); $t_thumbnail_id = $dup_att_id; } if ( $t_thumbnail_id != null ) { update_post_meta( $translation->element_id, '_thumbnail_id', $t_thumbnail_id ); } } elseif ( $thumbnails[ $post->ID ] ) { update_post_meta( $translation->element_id, '_thumbnail_id', $thumbnails[ $post->ID ] ); } } } } } } public function ajax_batch_duplicate_featured_images() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_duplicate_featured_images' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $featured_images_left = array_key_exists( 'featured_images_left', $_POST ) && is_numeric( $_POST['featured_images_left'] ) ? (int) $_POST['featured_images_left'] : null; return $this->batch_duplicate_featured_images( true, $featured_images_left ); } public function batch_duplicate_featured_images( $outputResult = true, $featured_images_left = null ) { // Use $featured_images_left if it's a number otherwise proceed with null. $featured_images_left = is_numeric( $featured_images_left ) ? (int) $featured_images_left : null; if ( null === $featured_images_left ) { $featured_images_left = $this->get_featured_images_total_number(); } // Use 10 as limit or what's left if there are less than 10 images left to proceed. $limit = $featured_images_left < 10 ? $featured_images_left : 10; // Duplicate batch of feature images. $processed = $this->duplicate_featured_images( $limit, $featured_images_left - $limit ); // Response result. $response = array( 'left' => max( $featured_images_left - $processed, 0 ) ); if ( $response['left'] ) { $response['message'] = sprintf( __( 'Duplicating featured images. %d left', 'sitepress' ), $response['left'] ); } else { $response['message'] = sprintf( __( 'Duplicating featured images: done!', 'sitepress' ), $response['left'] ); } if ( $outputResult ) { wp_send_json( $response ); } return $response['left']; } /** * Returns the total number of Featured Images. * * @return int */ private function get_featured_images_total_number() { $wpdb = $this->wpdb; // Makes Codesniffer interpret the following correctly. return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id'" ); } public function ajax_batch_duplicate_media() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_duplicate_media' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } return $this->batch_duplicate_media(); } public function batch_duplicate_media( $outputResult = true ) { $limit = 10; $response = array(); $attachments_prepared = $this->wpdb->prepare( " SELECT SQL_CALC_FOUND_ROWS p1.ID, p1.post_parent FROM {$this->wpdb->posts} p1 WHERE post_type = %s AND ID NOT IN (SELECT post_id FROM {$this->wpdb->postmeta} WHERE meta_key = %s) ORDER BY p1.ID ASC LIMIT %d", array( 'attachment', 'wpml_media_processed', $limit ) ); $attachments = $this->wpdb->get_results( $attachments_prepared ); $found = (int) $this->wpdb->get_var( 'SELECT FOUND_ROWS()' ); if ( $attachments ) { foreach ( $attachments as $attachment ) { $this->create_duplicated_media( $attachment ); } } $response['left'] = max( $found - $limit, 0 ); if ( $response['left'] ) { $response['message'] = sprintf( __( 'Duplicating media. %d left', 'sitepress' ), $response['left'] ); } else { $response['message'] = sprintf( __( 'Duplicating media: done!', 'sitepress' ), $response['left'] ); } if ( $outputResult ) { wp_send_json( $response ); } return $response['left']; } private function get_batch_translate_limit( $activeLanguagesCount ) { global $sitepress; $limit = $sitepress->get_wp_api()->constant( 'WPML_MEDIA_BATCH_LIMIT' ); $limit = $limit ?: ceil( 100 / max( $activeLanguagesCount - 1, 1 ) ); return max( $limit, 1 ); } public function ajax_batch_translate_media() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_translate_media' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } return $this->batch_translate_media(); } public function batch_translate_media( $outputResult = true ) { $response = []; $activeLanguagesCount = count( $this->sitepress->get_active_languages() ); $limit = $this->get_batch_translate_limit( $activeLanguagesCount ); $sql = " SELECT SQL_CALC_FOUND_ROWS p1.ID, p1.post_parent FROM {$this->wpdb->prefix}icl_translations t INNER JOIN {$this->wpdb->posts} p1 ON t.element_id = p1.ID LEFT JOIN {$this->wpdb->prefix}icl_translations tt ON t.trid = tt.trid WHERE t.element_type = 'post_attachment' AND t.source_language_code IS null GROUP BY p1.ID, p1.post_parent HAVING count(tt.language_code) < %d LIMIT %d "; $sql_prepared = $this->wpdb->prepare( $sql, [ $activeLanguagesCount, $limit ] ); $attachments = $this->wpdb->get_results( $sql_prepared ); $found = $this->wpdb->get_var( 'SELECT FOUND_ROWS()' ); if ( $attachments ) { foreach ( $attachments as $attachment ) { $lang = $this->sitepress->get_element_language_details( $attachment->ID, 'post_attachment' ); $this->translate_attachments( $attachment->ID, ( is_object( $lang ) && property_exists( $lang, 'language_code' ) ) ? $lang->language_code : null, true ); } } $response['left'] = max( $found - $limit, 0 ); if ( $response['left'] ) { $response['message'] = sprintf( esc_html__( 'Translating media. %d left', 'sitepress' ), $response['left'] ); } else { $response['message'] = __( 'Translating media: done!', 'sitepress' ); } if ( $outputResult ) { wp_send_json( $response ); } return $response['left']; } public function batch_set_initial_language() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_set_initial_language' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $default_language = $this->sitepress->get_default_language(); $limit = 10; $response = array(); $attachments_prepared = $this->wpdb->prepare( " SELECT SQL_CALC_FOUND_ROWS ID FROM {$this->wpdb->posts} WHERE post_type = %s AND ID NOT IN (SELECT element_id FROM {$this->wpdb->prefix}icl_translations WHERE element_type=%s) LIMIT %d", array( 'attachment', 'post_attachment', $limit, ) ); $attachments = $this->wpdb->get_col( $attachments_prepared ); $found = (int) $this->wpdb->get_var( 'SELECT FOUND_ROWS()' ); foreach ( $attachments as $attachment_id ) { $this->sitepress->set_element_language_details( $attachment_id, 'post_attachment', false, $default_language ); } $response['left'] = max( $found - $limit, 0 ); if ( $response['left'] ) { $response['message'] = sprintf( __( 'Setting language to media. %d left', 'sitepress' ), $response['left'] ); } else { $response['message'] = sprintf( __( 'Setting language to media: done!', 'sitepress' ), $response['left'] ); } echo wp_json_encode( $response ); exit; } public function ajax_batch_scan_prepare() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_scan_prepare' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $this->batch_scan_prepare(); } public function batch_scan_prepare( $outputResult = true ) { $response = array(); $this->wpdb->delete( $this->wpdb->postmeta, array( 'meta_key' => 'wpml_media_processed' ) ); $response['message'] = __( 'Started...', 'sitepress' ); if ( $outputResult ) { wp_send_json( $response ); } } public function ajax_batch_mark_processed() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_mark_processed' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $this->batch_mark_processed(); } public function batch_mark_processed( $outputResult = true ) { $response = []; $wpmlMediaProcessedMetaValue = 1; $limit = 300; /** * Query to get count of attachments from wp_posts table to decide how many rounds we should loop according to $limit */ $attachmentsCountQuery = "SELECT COUNT(ID) from {$this->wpdb->posts} where post_type = %s"; $attachmentsCountQueryPrepared = $this->wpdb->prepare( $attachmentsCountQuery, 'attachment' ); /** * Retrieving count of attachments */ $attachmentsCount = $this->wpdb->get_var( $attachmentsCountQueryPrepared ); /** * Query to get limited number of attachments with metadata up to $limit * * We join with the wp_postmeta table to also retrieve any related data of attachments in this table, * we only need the related data when the wp_postmeta.metavalue is null or != 1 because if it equals 1 then it doesn't need to be processed again */ $limitedAttachmentsWithMetaDataQuery = "SELECT posts.ID, post_meta.post_id, post_meta.meta_key, post_meta.meta_value FROM {$this->wpdb->posts} AS posts LEFT JOIN {$this->wpdb->postmeta} AS post_meta ON posts.ID = post_meta.post_id AND post_meta.meta_key = %s WHERE posts.post_type = %s AND (post_meta.meta_value IS NULL OR post_meta.meta_value != %d) LIMIT %d"; $limitedAttachmentsWithMetaDataQueryPrepared = $this->wpdb->prepare( $limitedAttachmentsWithMetaDataQuery, [ self::WPML_MEDIA_PROCESSED_META_KEY, 'attachment', 1, $limit, ] ); /** * Calculating loop rounds for processing attachments */ $attachmentsProcessingLoopRounds = $attachmentsCount ? ceil( $attachmentsCount / $limit ) : 0; /** * Callback function used to decide if attachment already has metadata or not * * @param $attachmentWithMetaData * * @return bool */ $attachmentHasNoMetaData = function ( $attachmentWithMetaData ) { return Obj::prop( 'post_id', $attachmentWithMetaData ) === null && Obj::prop( 'meta_key', $attachmentWithMetaData ) === null && Obj::prop( 'meta_value', $attachmentWithMetaData ) === null; }; /** * Callback function that prepares values to be inserted in the wp_postmeta table * * @param $attachmentId * * @return array */ $prepareInsertAttachmentsMetaValues = function ( $attachmentId ) use ( $wpmlMediaProcessedMetaValue ) { // The order of returned items is important, it represents (meta_value, meta_key, post_id) when insert into wp_postmeta table is done return [ $wpmlMediaProcessedMetaValue, self::WPML_MEDIA_PROCESSED_META_KEY, $attachmentId ]; }; /** * Looping through the retrieved limited number of attachments with metadata */ for ( $i = 0; $i < $attachmentsProcessingLoopRounds; $i ++ ) { /** * Retrieving limited number of attachments with metadata */ $attachmentsWithMetaData = $this->wpdb->get_results( $limitedAttachmentsWithMetaDataQueryPrepared ); if ( is_array( $attachmentsWithMetaData ) && count( $attachmentsWithMetaData ) ) { /** * Filtering data to separate existing and non-existing attachments with metdata */ list( $notExistingMetaAttachmentIds, $existingAttachmentsWithMetaData ) = \WPML\FP\Lst::partition( $attachmentHasNoMetaData, $attachmentsWithMetaData ); if ( is_array( $notExistingMetaAttachmentIds ) && count( $notExistingMetaAttachmentIds ) ) { /** * If we have attachments with no related data in wp_postmeta table, we start inserting values for it in wp_postmeta */ // Getting only attachments Ids $notExistingAttachmentsIds = \WPML\FP\Lst::pluck( 'ID', $notExistingMetaAttachmentIds ); // Preparing placeholders to be used in INSERT query /** @phpstan-ignore-next-line */ $attachmentMetaValuesPlaceholders = implode( ',', \WPML\FP\Lst::repeat( '(%d, %s, %d)', count( $notExistingAttachmentsIds ) ) ); // Preparing INSERT query $insertAttachmentsMetaQuery = "INSERT INTO {$this->wpdb->postmeta} (meta_value, meta_key, post_id) VALUES "; $insertAttachmentsMetaQuery .= $attachmentMetaValuesPlaceholders; // Preparing values to be inserted, at his point they're in separate arrays /** @phpstan-ignore-next-line */ $insertAttachmentsMetaValues = array_map( $prepareInsertAttachmentsMetaValues, $notExistingAttachmentsIds ); // Merging all values together in one array to be used wpdb->prepare function so each value is placed in a placeholder $insertAttachmentsMetaValues = array_merge( ...$insertAttachmentsMetaValues ); // Start replacing placeholders with values and run query $insertAttachmentsMetaQuery = $this->wpdb->prepare( $insertAttachmentsMetaQuery, $insertAttachmentsMetaValues ); $this->wpdb->query( $insertAttachmentsMetaQuery ); } if ( count( $existingAttachmentsWithMetaData ) ) { /** * If we have attachments with related data in wp_postmeta table, we start updating meta_value in wp_postmeta */ $existingAttachmentsIds = \WPML\FP\Lst::pluck( 'ID', $existingAttachmentsWithMetaData ); $attachmentsIn = wpml_prepare_in( $existingAttachmentsIds, '%d' ); $updateAttachmentsMetaQuery = $this->wpdb->prepare( "UPDATE {$this->wpdb->postmeta} SET meta_value = %d WHERE post_id IN ({$attachmentsIn})", [ $wpmlMediaProcessedMetaValue, ] ); $this->wpdb->query( $updateAttachmentsMetaQuery ); } } else { /** * When there are no more attachments with metadata found we get out of the loop */ break; } } Option::setSetupFinished(); $response['message'] = __( 'Done!', 'sitepress' ); if ( $outputResult ) { wp_send_json( $response ); } } public function create_duplicated_media( $attachment ) { static $parents_processed = array(); if ( $attachment->post_parent && ! in_array( $attachment->post_parent, $parents_processed ) ) { // see if we have translations. $post_type_prepared = $this->wpdb->prepare( "SELECT post_type FROM {$this->wpdb->posts} WHERE ID = %d", array( $attachment->post_parent ) ); $post_type = $this->wpdb->get_var( $post_type_prepared ); $trid_prepared = $this->wpdb->prepare( "SELECT trid FROM {$this->wpdb->prefix}icl_translations WHERE element_id=%d AND element_type = %s", array( $attachment->post_parent, 'post_' . $post_type, ) ); $trid = $this->wpdb->get_var( $trid_prepared ); if ( $trid ) { $attachments_prepared = $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_type = %s AND post_parent = %d", array( 'attachment', $attachment->post_parent, ) ); $attachments = $this->wpdb->get_col( $attachments_prepared ); $translations = $this->sitepress->get_element_translations( $trid, 'post_' . $post_type ); foreach ( $translations as $translation ) { if ( $translation->element_id && $translation->element_id != $attachment->post_parent ) { $attachments_in_translation_prepared = $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_type = %s AND post_parent = %d", array( 'attachment', $translation->element_id, ) ); $attachments_in_translation = $this->wpdb->get_col( $attachments_in_translation_prepared ); if ( sizeof( $attachments_in_translation ) == 0 ) { // only duplicate attachments if there a none already. foreach ( $attachments as $attachment_id ) { // duplicate the attachment self::create_duplicate_attachment( $attachment_id, $translation->element_id, $translation->language_code ); } } } } } $parents_processed[] = $attachment->post_parent; } else { // no parent - set to default language $target_language = $this->sitepress->get_default_language(); // Getting the trid and language, just in case image translation already exists $trid = $this->sitepress->get_element_trid( $attachment->ID, 'post_attachment' ); if ( $trid ) { $target_language = $this->sitepress->get_language_for_element( $attachment->ID, 'post_attachment' ); } $this->sitepress->set_element_language_details( $attachment->ID, 'post_attachment', $trid, $target_language ); } // Duplicate the post meta of the source element the translation $source_element_id = SitePress::get_original_element_id_by_trid( $trid ); if ( $source_element_id ) { $this->update_attachment_metadata( $source_element_id ); } update_post_meta( $attachment->ID, 'wpml_media_processed', 1 ); } function set_content_defaults_prepare() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_set_content_prepare' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $response = array( 'message' => __( 'Started...', 'sitepress' ) ); echo wp_json_encode( $response ); exit; } public function wpml_media_set_content_defaults() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'wpml_media_set_content_defaults' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } $this->set_content_defaults(); } private function set_content_defaults() { $always_translate_media = $_POST['always_translate_media']; $duplicate_media = $_POST['duplicate_media']; $duplicate_featured = $_POST['duplicate_featured']; $translateMediaLibraryTexts = \WPML\API\Sanitize::stringProp('translate_media_library_texts', $_POST); $content_defaults_option = [ 'always_translate_media' => $always_translate_media == 'true', 'duplicate_media' => $duplicate_media == 'true', 'duplicate_featured' => $duplicate_featured == 'true', ]; Option::setNewContentSettings( $content_defaults_option ); $settings = get_option( '_wpml_media' ); $settings['new_content_settings'] = $content_defaults_option; $settings['translate_media_library_texts'] = $translateMediaLibraryTexts === 'true'; update_option( '_wpml_media', $settings ); $response = [ 'result' => true, 'message' => __( 'Settings saved', 'sitepress' ), ]; wp_send_json_success( $response ); } } media/duplication/HooksFactory.php 0000755 00000000326 14720342453 0013272 0 ustar 00 <?php namespace WPML\Media\Duplication; class HooksFactory extends AbstractFactory { public function create() { if ( self::shouldActivateHooks() ) { return [ Hooks::class, 'add' ]; } return null; } } media/class-wpml-attachment-action-factory.php 0000755 00000001131 14720342453 0015467 0 ustar 00 <?php /** * WPML_Attachment_Action_Factory * * @package WPML */ /** * Class WPML_Attachment_Action_Factory */ class WPML_Attachment_Action_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Deferred_Action_Loader { /** * Get load action. * * @return string */ public function get_load_action() { return 'wpml_loaded'; } /** * Create attachment action. * * @return WPML_Attachment_Action */ public function create() { global $sitepress, $wpdb; return new WPML_Attachment_Action( $sitepress, $wpdb ); } } media/class-wpml-attachment-action.php 0000755 00000013253 14720342453 0014032 0 ustar 00 <?php use WPML\FP\Str; /** * WPML_Attachment_Action class file. * * @package WPML */ /** * Class WPML_Attachment_Action */ class WPML_Attachment_Action implements IWPML_Action { /** * SitePress instance. * * @var SitePress */ private $sitepress; /** * Wpdb instance. * * @var wpdb */ private $wpdb; /** * WPML_Attachment_Action constructor. * * @param SitePress $sitepress SitePress instance. * @param wpdb $wpdb wpdb instance. */ public function __construct( SitePress $sitepress, wpdb $wpdb ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; } /** * Add hooks. */ public function add_hooks() { if ( $this->is_admin_or_xmlrpc() && ! $this->is_uploading_plugin_or_theme() ) { $active_languages = $this->sitepress->get_active_languages(); if ( count( $active_languages ) > 1 ) { add_filter( 'views_upload', array( $this, 'views_upload_actions' ) ); } } add_filter( 'attachment_link', array( $this->sitepress, 'convert_url' ), 10, 1 ); add_filter( 'wp_delete_file', array( $this, 'delete_file_filter' ) ); } /** * Check if we are in site console or xmlrpc request is active. * * @return bool */ private function is_admin_or_xmlrpc() { $is_admin = is_admin(); $is_xmlrpc = ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ); return $is_admin || $is_xmlrpc; } /** * Check if we are uploading plugin or theme. * * @return bool */ private function is_uploading_plugin_or_theme() { global $action; return ( isset( $action ) && ( 'upload-plugin' === $action || 'upload-theme' === $action ) ); } /** * Filter views. * * @param array $views Views. * * @return array */ public function views_upload_actions( $views ) { global $pagenow; if ( 'upload.php' === $pagenow ) { // Get current language. $lang = $this->sitepress->get_current_language(); foreach ( $views as $key => $view ) { // Extract the base URL and query parameters. $href_count = preg_match( '/(href=["\'])([\s\S]+?)\?([\s\S]+?)(["\'])/', $view, $href_matches ); if ( $href_count && isset( $href_args ) ) { $href_base = $href_matches[2]; wp_parse_str( $href_matches[3], $href_args ); } else { $href_base = 'upload.php'; $href_args = array(); } if ( 'all' !== $lang ) { $sql = $this->wpdb->prepare( " SELECT COUNT(p.id) FROM {$this->wpdb->posts} AS p INNER JOIN {$this->wpdb->prefix}icl_translations AS t ON p.id = t.element_id WHERE p.post_type = 'attachment' AND t.element_type='post_attachment' AND t.language_code = %s ", $lang ); switch ( $key ) { case 'all': $and = " AND p.post_status != 'trash' "; break; case 'detached': $and = " AND p.post_status != 'trash' AND p.post_parent = 0 "; break; case 'trash': $and = " AND p.post_status = 'trash' "; break; default: if ( isset( $href_args['post_mime_type'] ) ) { $and = " AND p.post_status != 'trash' " . wp_post_mime_type_where( $href_args['post_mime_type'], 'p' ); } else { $and = $this->wpdb->prepare( " AND p.post_status != 'trash' AND p.post_mime_type LIKE %s", $key . '%' ); } } // phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores $and = apply_filters( 'wpml-media_view-upload-sql_and', $and, $key, $view, $lang ); $sql_and = $sql . $and; $sql = apply_filters( 'wpml-media_view-upload-sql', $sql_and, $key, $view, $lang ); $res = apply_filters( 'wpml-media_view-upload-count', null, $key, $view, $lang ); // phpcs:enable if ( null === $res ) { $res = $this->wpdb->get_col( $sql ); } // Replace count. $view = preg_replace( '/\((\d+)\)/', '(' . $res[0] . ')', $view ); } // Replace href link, adding the 'lang' argument and the revised count. $href_args['lang'] = $lang; $href_args = array_map( 'urlencode', $href_args ); $new_href = add_query_arg( $href_args, $href_base ); $views[ $key ] = preg_replace( '/(href=["\'])([\s\S]+?)(["\'])/', '$1' . $new_href . '$3', $view ); } } return $views; } /** * Check if the image is not duplicated to another post before deleting it physically. * * @param string $file Full file name. * * @return string|null */ public function delete_file_filter( $file ) { if ( $file ) { $file_name = $this->get_file_name( $file ); $sql = "SELECT pm.meta_id, pm.post_id FROM {$this->wpdb->postmeta} AS pm WHERE pm.meta_value = %s AND pm.meta_key='_wp_attached_file'"; $attachment_prepared = $this->wpdb->prepare( $sql, [ $file_name ] ); $attachment = $this->wpdb->get_row( $attachment_prepared ); if ( ! empty( $attachment ) ) { $file = null; } } return $file; } private function get_file_name( $file ) { $file_name = $this->get_file_name_without_size_from_full_name( $file ); $upload_dir = wp_upload_dir(); /** @phpstan-ignore-next-line */ $path_parts = $file ? explode( "/", Str::replace( $upload_dir['basedir'], '', $file ) ) : []; if ( $path_parts ) { $path_parts[ count( $path_parts ) - 1 ] = $file_name; } return ltrim( implode( '/', $path_parts ), '/' ); } /** * Get file name without a size, i.e. 'a-600x400.png' -> 'a.png'. * * @param string $file Full file name. * * @return mixed|string|string[]|null */ private function get_file_name_without_size_from_full_name( $file ) { $file_name = preg_replace( '/^(.+)\-\d+x\d+(\.\w+)$/', '$1$2', $file ); $file_name = preg_replace( '/^[\s\S]+(\/.+)$/', '$1', $file_name ); $file_name = str_replace( '/', '', $file_name ); return $file_name; } } media/setup/endpoints/PerformSetup.php 0000755 00000001272 14720342453 0014143 0 ustar 00 <?php namespace WPML\Media\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Right; use WPML\FP\Left; use function WPML\Container\make; class PerformSetup implements IHandler { public function run( Collection $data ) { if ( ! defined( 'WPML_MEDIA_VERSION' ) || ! class_exists( 'WPML_Media_Set_Posts_Media_Flag_Factory' ) ) { return Left::of( [ 'key' => false ] ); } $offset = $data->get( 'offset' ); $mediaFlag = make( \WPML_Media_Set_Posts_Media_Flag_Factory::class )->create(); list ( , $newOffset, $continue ) = $mediaFlag->process_batch( $offset ); return Right::of( [ 'continue' => $continue, 'offset' => $newOffset, ] ); } } media/setup/endpoints/PrepareSetup.php 0000755 00000001563 14720342453 0014132 0 ustar 00 <?php namespace WPML\Media\Setup\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\Utilities\KeyedLock; use WPML\FP\Left; use WPML\FP\Right; use function WPML\Container\make; class PrepareSetup implements IHandler { const LOCK_RELEASE_TIMEOUT = 2 * MINUTE_IN_SECONDS; public function run( Collection $data ) { if ( !defined( 'WPML_MEDIA_VERSION' ) || !class_exists( 'WPML_Media_Set_Posts_Media_Flag_Factory' ) ) { return Left::of( [ 'key' => false ] ); } $lock = make( KeyedLock::class, [ ':name' => self::class ] ); $key = $lock->create( $data->get( 'key' ), self::LOCK_RELEASE_TIMEOUT ); if ( $key ) { $mediaFlag = make( \WPML_Media_Set_Posts_Media_Flag_Factory::class)->create(); $mediaFlag->clear_flags(); return Right::of( [ 'key' => $key, ] ); } else { return Left::of( [ 'key' => 'in-use', ] ); } } } media/translate/endpoints/PrepareForTranslation.php 0000755 00000001461 14720342453 0016631 0 ustar 00 <?php namespace WPML\Media\Translate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\FP\Left; use WPML\FP\Right; use WPML\Media\Option; use WPML\Utilities\KeyedLock; class PrepareForTranslation implements IHandler { const LOCK_RELEASE_TIMEOUT = 2 * MINUTE_IN_SECONDS; public function run( Collection $data ) { if ( Option::isSetupFinished() ) { return Left::of( [ 'key' => false ] ); } $lock = make( KeyedLock::class, [ ':name' => self::class ] ); $key = $lock->create( $data->get( 'key' ), self::LOCK_RELEASE_TIMEOUT ); if ( $key ) { make( \WPML_Media_Attachments_Duplication::class )->batch_scan_prepare( false ); return Right::of( [ 'key' => $key, ] ); } else { return Left::of( [ 'key' => 'in-use', ] ); } } } media/translate/endpoints/FinishMediaTranslation.php 0000755 00000000570 14720342453 0016744 0 ustar 00 <?php namespace WPML\Media\Translate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\FP\Right; class FinishMediaTranslation implements IHandler { public function run( Collection $data ) { make( \WPML_Media_Attachments_Duplication::class )->batch_mark_processed( false ); return Right::of( true ); } } media/translate/endpoints/DuplicateFeaturedImages.php 0000755 00000001342 14720342453 0017063 0 ustar 00 <?php namespace WPML\Media\Translate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Obj; use WPML\Media\Option; use function WPML\Container\make; use WPML\FP\Right; class DuplicateFeaturedImages implements IHandler { public function run( Collection $data ) { if ( ! $this->shouldDuplicateFeaturedImages() ) { return Right::of( 0 ); } $numberLeft = $data->get( 'remaining', null ); return Right::of( make( \WPML_Media_Attachments_Duplication::class )->batch_duplicate_featured_images( false, $numberLeft ) ); } /** * @return bool */ private function shouldDuplicateFeaturedImages() { return (bool) Obj::prop( 'duplicate_featured', Option::getNewContentSettings() ); } } media/translate/endpoints/TranslateExistingMedia.php 0000755 00000000566 14720342453 0016762 0 ustar 00 <?php namespace WPML\Media\Translate\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\FP\Right; class TranslateExistingMedia implements IHandler { public function run( Collection $data ) { return Right::of( make( \WPML_Media_Attachments_Duplication::class )->batch_translate_media( false ) ); } } media/FrontendHooks.php 0000755 00000002012 14720342453 0011121 0 ustar 00 <?php namespace WPML\Media; use WPML\Element\API\PostTranslations; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; class FrontendHooks implements \IWPML_Frontend_Action { public function add_hooks() { Hooks::onFilter( 'wp_get_attachment_caption', 10, 2 ) ->then( spreadArgs( Fns::withoutRecursion( Fns::identity(), [ __CLASS__, 'translateCaption' ] ) ) ); } /** * @param string $caption * @param int $postId * * @return string */ public static function translateCaption( $caption, $postId ) { // $convertId :: int -> string|int|null $convertId = pipe( PostTranslations::getInCurrentLanguage(), Obj::prop( 'element_id' ) ); return Maybe::of( $postId ) ->map( $convertId ) ->map( Cast::toInt() ) ->reject( Relation::equals( $postId ) ) ->map( 'wp_get_attachment_caption' ) ->getOrElse( $caption ); } } media/class-wpml-model-attachments.php 0000755 00000012544 14720342453 0014042 0 ustar 00 <?php class WPML_Model_Attachments { const ATTACHMENT_TYPE = 'post_attachment'; /** @var SitePress */ private $sitepress; /** * @var WPML_Post_Status */ private $status_helper; /** * @param SitePress $sitepress * @param WPML_Post_Status $status_helper */ public function __construct( SitePress $sitepress, WPML_Post_Status $status_helper ) { $this->sitepress = $sitepress; $this->status_helper = $status_helper; } /** * @param int $attachment_id * @param int $duplicated_attachment_id */ public function duplicate_post_meta_data( $attachment_id, $duplicated_attachment_id ) { foreach ( array( '_wp_attachment_metadata', '_wp_attached_file' ) as $meta_key ) { $duplicated_meta_value = get_post_meta( $duplicated_attachment_id, $meta_key, true ); if ( ! $duplicated_meta_value ) { $source_meta_value = get_post_meta( $attachment_id, $meta_key, true ); update_post_meta( $duplicated_attachment_id, $meta_key, $source_meta_value ); } } update_post_meta( $duplicated_attachment_id, 'wpml_media_processed', 1 ); do_action( 'wpml_media_create_duplicate_attachment', $attachment_id, $duplicated_attachment_id ); } /** * @param int $trid * @param string $target_language * * @return null|WP_Post */ public function find_duplicated_attachment( $trid, $target_language ) { $attachment_translations = $this->sitepress->get_element_translations( $trid, self::ATTACHMENT_TYPE, true, true ); if ( is_array( $attachment_translations ) ) { foreach ( $attachment_translations as $attachment_translation ) { if ( $attachment_translation->language_code === $target_language ) { return get_post( $attachment_translation->element_id ); } } } return null; } /** * @param WP_Post|null $attachment * @param int|false|null $parent_id_of_attachement * @param string $target_language * * @return int|null */ public function fetch_translated_parent_id( $attachment, $parent_id_of_attachement, $target_language ) { $translated_parent_id = null; if ( null !== $attachment && $attachment->post_parent ) { $translated_parent_id = $attachment->post_parent; } if ( $parent_id_of_attachement ) { $parent_post = get_post( $parent_id_of_attachement ); if ( $parent_post ) { $translated_parent_id = $parent_id_of_attachement; $parent_id_language_code = $this->sitepress->get_language_for_element( $parent_post->ID, 'post_' . $parent_post->post_type ); if ( $parent_id_language_code !== $target_language ) { $translated_parent_id = $this->sitepress->get_object_id( $parent_post->ID, $parent_post->post_type, false, $target_language ); } } } return $translated_parent_id; } /** * @param int $new_parent_id * @param WP_Post $attachment */ public function update_parent_id_in_existing_attachment( $new_parent_id, $attachment ) { if ( $this->is_valid_post_type( $attachment->post_type ) ) { wp_update_post( array( 'ID' => $attachment->ID, 'post_parent' => $new_parent_id ) ); } } /** * @param string $post_type * * @return bool */ private function is_valid_post_type( $post_type ) { $post_types = array_keys( get_post_types( ) ); return in_array( $post_type, $post_types, true ); } /** * @param int $attachment_id * @param string $target_language * @param int $parent_id_in_target_language * @param int $trid * * @return int * @throws WPML_Media_Exception */ public function duplicate_attachment( $attachment_id, $target_language, $parent_id_in_target_language, $trid ) { $post = get_post( $attachment_id ); $post->post_parent = $parent_id_in_target_language; $post->ID = null; $duplicated_attachment_id = $this->insert_attachment( $post ); if ( ! $duplicated_attachment_id ) { throw new WPML_Media_Exception( 'Error occured during inserting duplicated attachment to db' ); } $this->add_language_information_to_attachment( $attachment_id, $duplicated_attachment_id, $target_language, $trid ); return $duplicated_attachment_id; } /** * @param WP_Post $post * * @return int */ private function insert_attachment( $post ) { $add_attachment_filters_temp = null; if ( array_key_exists( 'add_attachment', $GLOBALS['wp_filter'] ) ) { $add_attachment_filters_temp = $GLOBALS['wp_filter']['add_attachment']; unset( $GLOBALS['wp_filter']['add_attachment'] ); } /** @phpstan-ignore-next-line (WP doc issue) */ $duplicated_attachment_id = wp_insert_post( $post ); if ( ! is_int( $duplicated_attachment_id ) ) { $duplicated_attachment_id = 0; } if ( null !== $add_attachment_filters_temp ) { $GLOBALS['wp_filter']['add_attachment'] = $add_attachment_filters_temp; unset( $add_attachment_filters_temp ); } return $duplicated_attachment_id; } /** * @param int $attachment_id * @param int $duplicated_attachment_id * @param string $target_language * @param int $trid */ private function add_language_information_to_attachment( $attachment_id, $duplicated_attachment_id, $target_language, $trid ) { $source_language = $this->sitepress->get_language_for_element( $attachment_id, self::ATTACHMENT_TYPE ); $this->sitepress->set_element_language_details( $duplicated_attachment_id, self::ATTACHMENT_TYPE, $trid, $target_language, $source_language ); $this->status_helper->set_status( $duplicated_attachment_id, ICL_TM_DUPLICATE ); $this->status_helper->set_update_status( $duplicated_attachment_id, false ); } } media/class-wpml-media-exception.php 0000755 00000000070 14720342453 0013473 0 ustar 00 <?php class WPML_Media_Exception extends Exception { } media/Loader.php 0000755 00000002253 14720342453 0007553 0 ustar 00 <?php namespace WPML\Media; use WPML\Core\WP\App\Resources; use WPML\LIB\WP\Hooks; use WPML\Media\Option; use WPML\Media\Setup\Endpoint\PerformSetup; use WPML\Media\Setup\Endpoint\PrepareSetup; use WPML\Media\Translate\Endpoint\DuplicateFeaturedImages; use WPML\Media\Translate\Endpoint\FinishMediaTranslation; use WPML\Media\Translate\Endpoint\PrepareForTranslation; use WPML\Media\Translate\Endpoint\TranslateExistingMedia; class Loader implements \IWPML_Backend_Action { public function add_hooks() { if ( ! Option::isSetupFinished() ) { Hooks::onAction( 'wp_loaded' ) ->then( [ self::class, 'getData' ] ) ->then( Resources::enqueueApp( 'media-setup' ) ); } } public static function getData() { return [ 'name' => 'media_setup', 'data' => [ 'endpoints' => [ 'prepareForTranslation' => PrepareForTranslation::class, 'translateExistingMedia' => TranslateExistingMedia::class, 'duplicateFeaturedImages' => DuplicateFeaturedImages::class, 'finishMediaTranslation' => FinishMediaTranslation::class, 'prepareForSetup' => PrepareSetup::class, 'performSetup' => PerformSetup::class, ] ] ]; } } media/settings/class-wpml-media-settings.php 0000755 00000020273 14720342453 0015204 0 ustar 00 <?php use WPML\LIB\WP\Nonce; class WPML_Media_Settings { const ID = 'ml-content-setup-sec-media'; private $wpdb; public function __construct( $wpdb ) { $this->wpdb = $wpdb; } public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_script' ) ); add_action( 'icl_tm_menu_mcsetup', array( $this, 'render' ) ); add_filter( 'wpml_mcsetup_navigation_links', array( $this, 'mcsetup_navigation_links' ) ); } public function enqueue_script() { $handle = 'wpml-media-settings'; wp_register_script( $handle, ICL_PLUGIN_URL . '/res/js/media/settings.js', [], ICL_SITEPRESS_VERSION, true ); wp_localize_script( $handle, 'wpml_media_settings_data', [ 'nonce_wpml_media_scan_prepare' => wp_create_nonce( 'wpml_media_scan_prepare' ), 'nonce_wpml_media_set_initial_language' => wp_create_nonce( 'wpml_media_set_initial_language' ), 'nonce_wpml_media_translate_media' => wp_create_nonce( 'wpml_media_translate_media' ), 'nonce_wpml_media_duplicate_featured_images' => wp_create_nonce( 'wpml_media_duplicate_featured_images' ), 'nonce_wpml_media_set_content_prepare' => wp_create_nonce( 'wpml_media_set_content_prepare' ), 'nonce_wpml_media_set_content_defaults' => wp_create_nonce( 'wpml_media_set_content_defaults' ), 'nonce_wpml_media_duplicate_media' => wp_create_nonce( 'wpml_media_duplicate_media' ), 'nonce_wpml_media_mark_processed' => wp_create_nonce( 'wpml_media_mark_processed' ), ] ); wp_enqueue_script( $handle ); } public function render() { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $has_orphan_attachments = $this->wpdb->get_var( "SELECT ID FROM {$this->wpdb->posts} as posts LEFT JOIN {$this->wpdb->prefix}icl_translations as translations ON posts.ID = translations.element_id WHERE posts.post_type = 'attachment' AND translations.element_id IS NULL LIMIT 0, 1" ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $has_orphan_attachments = $has_orphan_attachments ? 1 : 0; ?> <div class="wpml-section" id="<?php echo esc_attr( self::ID ); ?>"> <div class="wpml-section-header"> <h3><?php esc_html_e( 'Media Translation', 'sitepress' ); ?></h3> </div> <div class="wpml-section-content"> <?php if ( $has_orphan_attachments ) : ?> <p><?php esc_html_e( "The Media Translation plugin needs to add languages to your site's media. Without this language information, existing media files will not be displayed in the WordPress admin.", 'sitepress' ); ?></p> <?php else : ?> <p><?php esc_html_e( 'You can check if some attachments can be duplicated to translated content:', 'sitepress' ); ?></p> <?php endif ?> <form id="wpml_media_options_form"> <input type="hidden" name="no_lang_attachments" value="<?php echo (int) $has_orphan_attachments; ?>"/> <input type="hidden" id="wpml_media_options_action"/> <table class="wpml-media-existing-content"> <tr> <td colspan="2"> <ul class="wpml_media_options_language"> <li> <label> <input type="checkbox" id="set_language_info" name="set_language_info" value="1" <?php echo $has_orphan_attachments ? ' checked="checked"' : ' disabled="disabled"'; ?> /> <?php esc_html_e( 'Set language information for existing media', 'sitepress' ); ?> </label></li> <li><label><input type="checkbox" id="translate_media" name="translate_media" value="1" checked="checked"/> <?php esc_html_e( 'Translate existing media in all languages', 'sitepress' ); ?></label></li> <li><label><input type="checkbox" id="duplicate_media" name="duplicate_media" value="1" checked="checked"/> <?php esc_html_e( 'Duplicate existing media for translated content', 'sitepress' ); ?></label></li> <li><label><input type="checkbox" id="duplicate_featured" name="duplicate_featured" value="1" checked="checked"/> <?php esc_html_e( 'Duplicate the featured images for translated content', 'sitepress' ); ?></label></li> </ul> </td> </tr> <tr> <td><a href="https://wpml.org/documentation/getting-started-guide/media-translation/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore" target="_blank"><?php esc_html_e( 'Media Translation Documentation', 'sitepress' ); ?></a></td> <td align="right"> <input class="button-primary" name="start" type="submit" value="<?php esc_attr_e( 'Start', 'sitepress' ); ?> »"/> </td> </tr> <tr> <td colspan="2"> <img class="progress" src="<?php echo ICL_PLUGIN_URL; ?>/res/img/ajax-loader.gif" width="16" height="16" alt="loading" style="display: none;"/> <span class="status"> </span> </td> </tr> </table> <table class="wpml-media-new-content-settings"> <tr> <td colspan="2"> <h4><?php esc_html_e( 'How to handle media for new content:', 'sitepress' ); ?></h4> </td> </tr> <tr> <td colspan="2"> <ul class="wpml_media_options_language"> <?php $content_defaults = \WPML\Media\Option::getNewContentSettings(); $always_translate_media_html_checked = $content_defaults['always_translate_media'] ? 'checked="checked"' : ''; $duplicate_media_html_checked = $content_defaults['duplicate_media'] ? 'checked="checked"' : ''; $duplicate_featured_html_checked = $content_defaults['duplicate_featured'] ? 'checked="checked"' : ''; ?> <li> <label><input type="checkbox" name="content_default_always_translate_media" value="1" <?php echo $always_translate_media_html_checked; ?> /> <?php esc_html_e( 'When uploading media to the Media library, make it available in all languages', 'sitepress' ); ?></label> </li> <li> <label><input type="checkbox" name="content_default_duplicate_media" value="1" <?php echo $duplicate_media_html_checked; ?> /> <?php esc_html_e( 'Duplicate media attachments for translations', 'sitepress' ); ?></label> </li> <li> <label><input type="checkbox" name="content_default_duplicate_featured" value="1" <?php echo $duplicate_featured_html_checked; ?> /> <?php esc_html_e( 'Duplicate featured images for translations', 'sitepress' ); ?></label> </li> </ul> </td> </tr> <tr> <td colspan="2"> <h4><?php esc_html_e( 'How to handle media library texts:', 'sitepress' ); ?></h4> </td> </tr> <tr> <td colspan="2"> <ul class="wpml_media_options_media_library_texts"> <?php $translateMediaLibraryTexts = \WPML\Media\Option::getTranslateMediaLibraryTexts() ? 'checked="checked"' : ''; ?> <li> <label><input type="checkbox" name="translate_media_library_texts" value="1" <?php echo $translateMediaLibraryTexts; ?> /> <?php esc_html_e( 'Translate media library texts with posts', 'sitepress' ); ?></label> </li> </ul> </td> </tr> <tr> <td colspan="2" align="right"> <input class="button-secondary" name="set_defaults" type="submit" value="<?php esc_attr_e( 'Apply', 'sitepress' ); ?>"/> </td> </tr> <tr> <td colspan="2"> <img class="content_default_progress" src="<?php echo ICL_PLUGIN_URL; ?>/res/img/ajax-loader.gif" width="16" height="16" alt="loading" style="display: none;"/> <span class="content_default_status"> </span> </td> </tr> </table> <div id="wpml_media_all_done" class="hidden updated"> <p><?php esc_html_e( "You're all done. From now on, all new media files that you upload to content will receive a language. You can automatically duplicate them to translations from the post-edit screen.", 'sitepress' ); ?></p> </div> </form> </div> </div> <?php } public function mcsetup_navigation_links( array $mcsetup_sections ) { $mcsetup_sections[ self::ID ] = esc_html__( 'Media Translation', 'sitepress' ); return $mcsetup_sections; } } media/settings/class-wpml-media-settings-factory.php 0000755 00000000673 14720342453 0016653 0 ustar 00 <?php class WPML_Media_Settings_Factory extends WPML_Current_Screen_Loader_Factory { public function create_hooks() { global $wpdb; return new WPML_Media_Settings( $wpdb ); } public function get_screen_regex() { return defined( 'WPML_TM_FOLDER' ) ? '/(' . WPML_PLUGIN_FOLDER . '\/menu\/translation-options|wpml_page_' . WPML_TM_FOLDER . '\/menu\/settings)/' : '/' . WPML_PLUGIN_FOLDER . '\/menu\/translation-options/'; } } widgets/class-wpml-widgets-support-backend.php 0000755 00000012110 14720342453 0015552 0 ustar 00 <?php use \WPML\FP\Obj; use WPML\LIB\WP\Option as Option; use WPML\LIB\WP\User; /** * This code is inspired by WPML Widgets (https://wordpress.org/plugins/wpml-widgets/), * created by Jeroen Sormani * * @author OnTheGo Systems */ class WPML_Widgets_Support_Backend implements IWPML_Action { const NONCE = 'wpml-language-nonce'; const NONCE_LEGACY_WIDGET = 'wpml_change_selected_language_for_legacy_widget'; private $active_languages; private $template_service; /** * WPML_Widgets constructor. * * @param array $active_languages * @param IWPML_Template_Service $template_service */ public function __construct( array $active_languages, IWPML_Template_Service $template_service ) { $this->active_languages = $active_languages; $this->template_service = $template_service; } public function add_hooks() { add_action( 'in_widget_form', array( $this, 'language_selector' ), 10, 3 ); add_filter( 'widget_update_callback', array( $this, 'update' ), 10, 4 ); if ( User::getCurrent() && User::getCurrent()->has_cap('wpml_manage_languages') ) { add_action( 'wp_ajax_wpml_change_selected_language_for_legacy_widget', array( $this, 'set_selected_language_for_legacy_widget' ) ); } if ( $this->is_widgets_page() ) { add_action('enqueue_block_editor_assets', array($this, 'enqueue_scripts')); } } public function enqueue_scripts() { wp_register_script( 'widgets-language-switcher-script', ICL_PLUGIN_URL . '/dist/js/widgets-language-switcher/app.js', array( 'wp-block-editor' ) ); wp_localize_script( 'widgets-language-switcher-script', 'wpml_active_and_selected_languages', [ "active_languages" => $this->active_languages, "legacy_widgets_languages" => $this->get_legacy_widgets_selected_languages(), 'nonce' => wp_create_nonce( self::NONCE_LEGACY_WIDGET ), ] ); wp_enqueue_script( 'widgets-language-switcher-script' ); } /** * @param WP_Widget|null $widget * @param string|null $form * @param array $instance */ public function language_selector( $widget, $form, $instance ) { /** * This allows to disable the display of the language selector on a widget form. * * @since 4.5.3 * * @param bool $is_disabled If display should be disabled (default: false) */ if ( apply_filters( 'wpml_widget_language_selector_disable', false ) ) { return; } $languages = $this->active_languages; $languages['all'] = array( 'code' => 'all', 'native_name' => __( 'All Languages', 'sitepress' ), ); $model = array( 'strings' => array( 'label' => __( 'Display on language:', 'sitepress' ), ), 'languages' => $languages, 'selected_language' => Obj::propOr( 'all', 'wpml_language', is_array( $instance ) ? $instance : [] ), 'nonce' => wp_create_nonce( self::NONCE ), ); echo $this->template_service->show( $model, 'language-selector.twig' ); } /** * @param array $instance * @param array $new_instance * @param array $old_instance * @param WP_Widget $widget_instance * * @return array */ public function update( $instance, $new_instance, $old_instance, $widget_instance ) { if (wp_verify_nonce( Obj::prop( 'wpml-language-nonce', $_POST ), self::NONCE ) ) { $new_language = filter_var( Obj::prop('wpml_language', $_POST), FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_NULL_ON_FAILURE ); if ( 'all' === $new_language || array_key_exists( $new_language, $this->active_languages ) ) { $instance['wpml_language'] = $new_language; } } return $instance; } public function is_widgets_page() { global $pagenow; return is_admin() && 'widgets.php' === $pagenow; } private function get_legacy_widgets_selected_languages() { $legacy_widgets_languages = []; $widgets = array_values($GLOBALS['wp_widget_factory']->widgets); foreach ($widgets as $widget) { $widget_option = Option::get($widget->option_name); if ( is_array( $widget_option ) ) { foreach ($widget_option as $key => $option) { if (is_array($option) && array_key_exists('wpml_language', $option)) { $legacy_widgets_languages[$widget->id_base . '-' . $key] = $option['wpml_language']; } } } } return $legacy_widgets_languages; } public function set_selected_language_for_legacy_widget() { $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : ''; if ( wp_verify_nonce( $nonce, self::NONCE_LEGACY_WIDGET ) && isset( $_POST['id'] ) && isset( $_POST['selected_language_value'] ) ) { $widget_id = sanitize_text_field( $_POST['id'] ); $selected_language_value = sanitize_text_field( $_POST['selected_language_value'] ); $widget = explode( '-', $widget_id ); $widget_id = array_pop( $widget ); $widget_option_name = 'widget_' . implode( '-', $widget ); $widgets_by_type = get_option( $widget_option_name ); $widgets_by_type[ $widget_id ]['wpml_language'] = $selected_language_value; Option::update( $widget_option_name, $widgets_by_type ); } else { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } } } widgets/class-wpml-widgets-support-frontend.php 0000755 00000004510 14720342453 0016007 0 ustar 00 <?php use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; use WPML\LIB\WP\Hooks; /** * This code is inspired by WPML Widgets (https://wordpress.org/plugins/wpml-widgets/), * created by Jeroen Sormani * * @author OnTheGo Systems */ class WPML_Widgets_Support_Frontend implements IWPML_Action { /** * @see \WPML\PB\Gutenberg\Widgets\Block\DisplayTranslation::PRIORITY_BEFORE_REMOVE_BLOCK_MARKUP */ const PRIORITY_AFTER_TRANSLATION_APPLIED = 0; /** @var array $displayFor */ private $displayFor; /** * WPML_Widgets constructor. * * @param string $current_language */ public function __construct( $current_language ) { $this->displayFor = [ null, $current_language, 'all' ]; } public function add_hooks() { add_filter( 'widget_block_content', [ $this, 'filterByLanguage' ], self::PRIORITY_AFTER_TRANSLATION_APPLIED ); add_filter( 'widget_display_callback', [ $this, 'display' ], - PHP_INT_MAX ); } /** * @param string $content * * @return string */ public function filterByLanguage( $content ) { $render = function () use ( $content ) { return wpml_collect( parse_blocks( $content ) ) ->map( Fns::unary( 'render_block' ) ) ->reduce( Str::concat(), '' ); }; return Hooks::callWithFilter( $render, 'pre_render_block', [ $this, 'shouldRender' ], 10, 2 ); } /** * Determine if a block should be rendered depending on its language * Returning an empty string will stop the block from being rendered. * * @param string|null $pre_render The pre-rendered content. Default null. * @param array $block The block being rendered. * * @return string|null */ public function shouldRender( $pre_render, $block ) { return Lst::includes( Obj::path( [ 'attrs', 'wpml_language' ], $block ), $this->displayFor ) ? $pre_render : ''; } /** * Get display status of the widget. * * @param array|bool $instance * * @return array|bool */ public function display( $instance ) { if ( ! $instance || ( is_array( $instance ) && $this->it_must_display( $instance ) ) ) { return $instance; } return false; } /** * Returns display status of the widget as boolean. * * @param array $instance * * @return bool */ private function it_must_display( $instance ) { return Lst::includes( Obj::propOr( null, 'wpml_language', $instance ), $this->displayFor ); } } widgets/class-wpml-widgets-support-factory.php 0000755 00000003125 14720342453 0015640 0 ustar 00 <?php /** * This code is inspired by WPML Widgets (https://wordpress.org/plugins/wpml-widgets/), * created by Jeroen Sormani * * @author OnTheGo Systems */ class WPML_Widgets_Support_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader, IWPML_Deferred_Action_Loader { public function get_load_action() { return 'wpml_loaded'; } /** * @return WPML_Widgets_Support_Backend|WPML_Widgets_Support_Frontend|null */ public function create() { global $sitepress; if ( ! function_exists( 'is_plugin_active' ) ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( ! is_plugin_active( 'wpml-widgets/wpml-widgets.php' ) && $this->is_human_page( $sitepress ) ) { if ( $sitepress->get_wp_api()->is_admin() ) { return $this->create_backend_ui( $sitepress ); } return $this->create_frontend_ui( $sitepress ); } return null; } private function create_backend_ui( SitePress $sitepress ) { $template_paths = array( WPML_PLUGIN_PATH . '/templates/widgets/', ); $template_loader = new WPML_Twig_Template_Loader( $template_paths ); return new WPML_Widgets_Support_Backend( $sitepress->get_active_languages(), $template_loader->get_template() ); } public function create_frontend_ui( SitePress $sitepress ) { return new WPML_Widgets_Support_Frontend( $sitepress->get_current_language() ); } /** * @param SitePress $sitepress * * @return bool */ private function is_human_page( SitePress $sitepress ) { $wpml_wp_api = $sitepress->get_wp_api(); return ! $wpml_wp_api->is_cron_job() && ! $wpml_wp_api->is_heartbeat(); } } class-wpml-db-chunk.php 0000755 00000002635 14720342453 0011045 0 ustar 00 <?php use PhpMyAdmin\SqlParser\Parser; class WPML_DB_Chunk { /** * @var wpdb */ private $wpdb; /** * @var int */ private $chunk_size; /** * @param wpdb $wpdb * @param int $chunk_size */ public function __construct( wpdb $wpdb, $chunk_size = 1000 ) { $this->wpdb = $wpdb; $this->chunk_size = $chunk_size; } /** * @param string $query * @param array $args * @param int $elements_num * * @return array * * @throws \InvalidArgumentException */ public function retrieve( $query, $args, $elements_num ) { $this->validate_query( $query ); $result = array(); $offset = 0; while ( $offset < $elements_num ) { $new_query = $query . sprintf( ' LIMIT %d OFFSET %s', $this->chunk_size, $offset ); $new_query = $this->wpdb->prepare( $new_query, $args ); $rowset = $this->wpdb->get_results( $new_query, ARRAY_A ); if ( is_array( $rowset ) && count( $rowset ) ) { $result = array_merge( $result, $rowset ); } $offset += $this->chunk_size; } return $result; } /** * @param string $query */ private function validate_query( $query ) { $parser = new Parser( $query ); if ( isset( $parser->statements ) ) { wpml_collect( $parser->statements )->each( function( $statement ) { if ( ! empty( $statement->limit ) ) { throw new InvalidArgumentException( "Query can't contain OFFSET or LIMIT keyword" ); } } ); } } } super-globals/Server.php 0000755 00000000424 14720342453 0011311 0 ustar 00 <?php namespace WPML\SuperGlobals; use WPML\FP\Obj; class Server { public static function getServerName() { return filter_var( Obj::prop( 'HTTP_HOST', $_SERVER ), FILTER_SANITIZE_URL ) ?: filter_var( Obj::prop( 'SERVER_NAME', $_SERVER ), FILTER_SANITIZE_URL ); } } plugins/Plugins.php 0000755 00000011714 14720342453 0010372 0 ustar 00 <?php namespace WPML; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\Setup\Option; class Plugins { const WPML_TM_PLUGIN = 'wpml-translation-management/plugin.php'; const WPML_CORE_PLUGIN = 'sitepress-multilingual-cms/sitepress.php'; const WPML_SUBSCRIPTION_TYPE_BLOG = 6718; const AFTER_INSTALLER = 999; public static function loadCoreFirst() { $plugins = get_option( 'active_plugins' ); $isSitePress = function ( $value ) { return $value === WPML_PLUGIN_BASENAME; }; $newOrder = wpml_collect( $plugins ) ->prioritize( $isSitePress ) ->values() ->toArray(); if ( $newOrder !== $plugins ) { update_option( 'active_plugins', $newOrder ); } } public static function isTMAllowed() { $isTMAllowed = true; if ( function_exists( 'OTGS_Installer' ) ) { $subscriptionType = OTGS_Installer()->get_subscription( 'wpml' )->get_type(); if ( $subscriptionType && $subscriptionType === self::WPML_SUBSCRIPTION_TYPE_BLOG ) { $isTMAllowed = false; } } return $isTMAllowed; } public static function updateTMAllowedOption() { $isTMAllowed = self::isTMAllowed(); Option::setTMAllowed( $isTMAllowed ); return $isTMAllowed; } public static function updateTMAllowedAndTranslateEverythingOnSubscriptionChange() { if ( function_exists( 'OTGS_Installer' ) ) { $type = OTGS_Installer()->get_subscription( 'wpml' )->get_type(); if ( $type ) { Option::setTMAllowed( $type !== self::WPML_SUBSCRIPTION_TYPE_BLOG ); if ( self::WPML_SUBSCRIPTION_TYPE_BLOG === $type ) { Option::setTranslateEverything( false ); } } } } /** * @param bool $isSetupComplete */ public static function loadEmbeddedTM( $isSetupComplete ) { $tmSlug = 'wpml-translation-management/plugin.php'; self::stopPluginActivation( self::WPML_TM_PLUGIN ); add_action( 'otgs_installer_subscription_refreshed', [ self::class, 'updateTMAllowedOption' ] ); if ( ! self::deactivateTm() ) { add_action( "after_plugin_row_$tmSlug", [ self::class, 'showEmbeddedTMNotice' ] ); add_action( 'otgs_installer_initialized', [ self::class, 'updateTMAllowedAndTranslateEverythingOnSubscriptionChange' ] ); $isTMAllowed = Option::isTMAllowed(); if ( $isTMAllowed === null ) { add_action( 'after_setup_theme', [ self::class, 'updateTMAllowedOption' ], self::AFTER_INSTALLER ); } if ( ! $isSetupComplete || $isTMAllowed ) { require_once WPML_PLUGIN_PATH . '/tm.php'; } } } private static function deactivateTm() { if ( ! self::isTMActive() ) { return false; } require_once ABSPATH . 'wp-admin/includes/plugin.php'; require_once ABSPATH . 'wp-includes/pluggable.php'; deactivate_plugins( self::WPML_TM_PLUGIN ); if ( ! wpml_is_cli() && ! wpml_is_ajax() && wp_redirect( $_SERVER['REQUEST_URI'], 302, 'WPML' ) ) { exit; } return true; } public static function isTMActive() { $hasTM = function ( $plugins ) { return is_array( $plugins ) && ( Lst::includes( self::WPML_TM_PLUGIN, $plugins ) || // 'active_plugins' stores plugins as values array_key_exists( self::WPML_TM_PLUGIN, $plugins ) // 'active_sitewide_plugins' stores plugins as keys ); }; if ( \is_multisite() && $hasTM( \get_site_option( 'active_sitewide_plugins', [] ) ) ) { return true; } return $hasTM( \get_option( 'active_plugins', [] ) ); } private static function stopPluginActivation( $pluginSlug ) { if ( Relation::propEq( 'action', 'activate', $_GET ) && Relation::propEq( 'plugin', $pluginSlug, $_GET ) ) { unset( $_GET['plugin'], $_GET['action'] ); } if ( wpml_is_cli() ) { if ( Lst::includesAll( [ 'plugin', 'activate', 'wpml-translation-management' ], $_SERVER['argv'] ) ) { \WP_CLI::warning( __( 'WPML Translation Management is now included in WPML Multilingual CMS.', 'sitepress' ) ); } } if ( Relation::propEq( 'action', 'activate-selected', $_POST ) && Lst::includes( $pluginSlug, Obj::propOr( [], 'checked', $_POST ) ) ) { $_POST['checked'] = Fns::reject( Relation::equals( $pluginSlug ), $_POST['checked'] ); } } public static function showEmbeddedTMNotice() { $wpListTable = _get_list_table( 'WP_Plugins_List_Table' ); ?> <tr class="plugin-update-tr"> <td colspan="<?php echo $wpListTable->get_column_count(); ?>" class="plugin-update colspanchange"> <div class="update-message inline notice notice-error notice-alt"> <p> <?php echo _e( 'This plugin has been deactivated as it is now part of the WPML Multilingual CMS plugin. You can safely delete it.', 'sitepress' ); $readMoreLink = 'https://wpml.org/changelog/2021/10/wpml-4-5-translate-all-of-your-sites-content-with-one-click/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore#fewer-plugins-to-manage'; ?> <a href="<?php echo $readMoreLink; ?>" target="_blank" class="wpml-external-link"> <?php _e( 'Read more', 'sitepress' ); ?> </a> </p> </div> </tr> <?php } } plugins/wpml-plugins-check.php 0000755 00000001521 14720342453 0012455 0 ustar 00 <?php class WPML_Plugins_Check { /** * @param string $bundle_json * @param string $tm_version * @param string $st_version * @param string $wcml_version */ public static function disable_outdated( $bundle_json, $tm_version, $st_version, $wcml_version ) { $required_versions = json_decode( $bundle_json, true ); if ( version_compare( $st_version, $required_versions['wpml-string-translation'], '<' ) ) { remove_action( 'wpml_before_init', 'load_wpml_st_basics' ); } if ( version_compare( $wcml_version, $required_versions['woocommerce-multilingual'], '<' ) ) { global $woocommerce_wpml; if ( $woocommerce_wpml ) { remove_action( 'wpml_loaded', [ $woocommerce_wpml, 'load' ] ); remove_action( 'init', [ $woocommerce_wpml, 'init' ], 2 ); } remove_action( 'wpml_loaded', 'wcml_loader' ); } } } class-wpml-tm-service-activation-ajax.php 0000755 00000010510 14720342453 0014477 0 ustar 00 <?php class WPML_TM_Service_Activation_AJAX extends WPML_TM_AJAX_Factory_Obsolete { private $script_handle = 'wpml_tm_service_activation'; private $ignore_local_jobs; /** * @var WPML_Translation_Job_Factory */ private $job_factory; /** * @param WPML_WP_API $wpml_wp_api * @param WPML_Translation_Job_Factory $job_factory */ public function __construct( &$wpml_wp_api, &$job_factory ) { parent::__construct( $wpml_wp_api ); $this->wpml_wp_api = &$wpml_wp_api; $this->job_factory = &$job_factory; $this->add_ajax_action( 'wp_ajax_wpml_cancel_open_local_translators_jobs', array( $this, 'cancel_open_local_translators_jobs' ) ); $this->add_ajax_action( 'wp_ajax_wpml_keep_open_local_translators_jobs', array( $this, 'keep_open_local_translators_jobs' ) ); $this->init(); $this->ignore_local_jobs = get_transient( $this->script_handle . '_ignore_local_jobs' ); if ( $this->ignore_local_jobs == 1 ) { $this->ignore_local_jobs = true; } else { $this->ignore_local_jobs = false; } } public function get_ignore_local_jobs() { return $this->ignore_local_jobs; } public function set_ignore_local_jobs( $value ) { if ( $value == true ) { set_transient( $this->script_handle . '_ignore_local_jobs', 1 ); $this->ignore_local_jobs = true; } else { delete_transient( $this->script_handle . '_ignore_local_jobs' ); $this->ignore_local_jobs = false; } } public function cancel_open_local_translators_jobs() { $translation_filter = array( 'service' => 'local', 'translator' => 0, 'status__not' => ICL_TM_COMPLETE, ); $translation_jobs = $this->job_factory->get_translation_jobs( $translation_filter, true, true ); $jobs_count = count( $translation_jobs ); $deleted = 0; if ( $jobs_count ) { foreach ( $translation_jobs as $job ) { /** @var WPML_Translation_Job $job */ if ( $job && $job->cancel() ) { $deleted += 1; } } } $this->set_ignore_local_jobs( false ); return $this->wpml_wp_api->wp_send_json_success( array( 'opens' => $jobs_count - $deleted, 'cancelled' => $deleted, ) ); } public function keep_open_local_translators_jobs() { $this->set_ignore_local_jobs( true ); return $this->wpml_wp_api->wp_send_json_success( 'Ok!' ); } public function register_resources() { wp_register_script( $this->script_handle, WPML_TM_URL . '/res/js/service-activation.js', array( 'jquery', 'jquery-ui-dialog', 'underscore' ), false, true ); } public function enqueue_resources( $hook_suffix ) { $this->register_resources(); $strings = array( 'alertTitle' => _x( 'Incomplete local translation jobs', 'Incomplete local jobs after TS activation: [response] 00 Title', 'wpml-translation-management' ), 'cancelledJobs' => _x( 'Cancelled local translation jobs:', 'Incomplete local jobs after TS activation: [response] 01 Cancelled', 'wpml-translation-management' ), 'openJobs' => _x( 'Open local translation jobs:', 'Incomplete local jobs after TS activation: [response] 02 Open', 'wpml-translation-management' ), 'errorCancellingJobs' => _x( 'Unable to cancel some or all jobs', 'Incomplete local jobs after TS activation: [response] 03 Error', 'wpml-translation-management' ), 'errorGeneric' => _x( 'Unable to complete the action', 'Incomplete local jobs after TS activation: [response] 04 Error', 'wpml-translation-management' ), 'keepLocalJobs' => _x( 'Local translation jobs will be kept and the above notice hidden.', 'Incomplete local jobs after TS activation: [response] 10 Close button', 'wpml-translation-management' ), 'closeButton' => _x( 'Close', 'Incomplete local jobs after TS activation: [response] 20 Close button', 'wpml-translation-management' ), 'confirm' => _x( 'Are you sure you want to do this?', 'Incomplete local jobs after TS activation: [confirmation] 01 Message', 'wpml-translation-management' ), 'yes' => _x( 'Yes', 'Incomplete local jobs after TS activation: [confirmation] 01 Yes', 'wpml-translation-management' ), 'no' => _x( 'No', 'Incomplete local jobs after TS activation: [confirmation] 01 No', 'wpml-translation-management' ), ); wp_localize_script( $this->script_handle, $this->script_handle . '_strings', $strings ); wp_enqueue_script( $this->script_handle ); } } class-wpml-tm-troubleshooting-clear-ts-ui.php 0000755 00000001551 14720342453 0015336 0 ustar 00 <?php class WPML_TM_Troubleshooting_Clear_TS_UI extends WPML_Templates_Factory { public function get_model() { $ts_name = TranslationProxy::get_current_service_name(); $model = array( 'strings' => array( 'title' => __( 'Show other professional translation options', 'wpml-translation-management' ), 'button' => __( 'Enable other translation services', 'wpml-translation-management' ), 'message' => sprintf( __( 'Your site is currently configured to use only %s as its professional translation service.', 'wpml-translation-management' ), $ts_name ), ), 'placeHolder' => 'wpml_clear_ts', ); return $model; } public function get_template() { return 'clear-preferred-ts.twig'; } protected function init_template_base_dir() { $this->template_paths = array( dirname( __FILE__ ) . '/../templates/troubleshooting/', ); } } action-filter-loader/class-wpml-ajax-action-validation.php 0000755 00000001076 14720342453 0017702 0 ustar 00 <?php /** * Class WPML_AJAX_Action_Validation * * @author OnTheGoSystems */ class WPML_AJAX_Action_Validation { /** * @param string $action_name * * @return bool */ public function is_valid( $action_name ) { $is_valid = false; if ( array_key_exists( 'action', $_POST ) && $action_name === $_POST['action'] ) { if ( array_key_exists( 'nonce', $_POST ) && wp_verify_nonce( $_POST['nonce'], $action_name ) ) { $is_valid = true; } else { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ) ); } } return $is_valid; } } action-filter-loader/class-wpml-action-type.php 0000755 00000003531 14720342453 0015606 0 ustar 00 <?php namespace WPML\Action; /** * Class Type * * @package WPML\Action * * Determines the type of action that a class implements. Can be * one or more of: * backend, frontend, ajax, rest, cli or dic * * dic means that the class can be loaded via Dependency Injection Container */ class Type { /** * @var string[] Resolved by \WPML\Action\Type::is */ private $backend_actions = [ 'IWPML_Backend_Action_Loader', 'IWPML_Backend_Action' ]; /** * @var string[] Resolved by \WPML\Action\Type::is */ private $frontend_actions = [ 'IWPML_Frontend_Action_Loader', 'IWPML_Frontend_Action' ]; /** * @var string[] Resolved by \WPML\Action\Type::is */ private $ajax_actions = [ 'IWPML_AJAX_Action_Loader', 'IWPML_AJAX_Action' ]; /** * @var string[] Resolved by \WPML\Action\Type::is */ private $rest_actions = [ 'IWPML_REST_Action_Loader', 'IWPML_REST_Action' ]; /** * @var string[] Resolved by \WPML\Action\Type::is */ private $cli_actions = [ 'IWPML_CLI_Action_Loader', 'IWPML_CLI_Action' ]; /** * @var string[] Resolved by \WPML\Action\Type::is */ private $dic_actions = [ 'IWPML_DIC_Action' ]; /** @var array */ private $implementations; /** * Info constructor. * * @param string $class_name The class name of the action or action loader */ public function __construct( $class_name ) { $this->implementations = class_implements( $class_name ); } /** * @param string $type The type of action 'backend', 'frontend', 'ajax', 'rest', 'cli' or 'dic' * * @return bool */ public function is( $type ) { $action_type = $type . '_actions'; return $this->has_implementation( $this->$action_type ); } /** * @param array $interfaces * * @return bool */ private function has_implementation( $interfaces ) { return count( array_intersect( $this->implementations, $interfaces ) ) > 0; } } action-filter-loader/class-wpml-action-filter-loader.php 0000755 00000010070 14720342453 0017352 0 ustar 00 <?php /** * WPML_Action_Filter_Loader class file * * @package WPML\Core */ /** * Class WPML_Action_Filter_Loader */ class WPML_Action_Filter_Loader { /** * Deferred actions * * @var array $defered_actions */ private $defered_actions = array(); /** * Ajax action validation * * @var WPML_AJAX_Action_Validation $ajax_action_validation */ private $ajax_action_validation; /** * Load action filter * * @param string[] $loaders Action loaders. */ public function load( $loaders ) { foreach ( $loaders as $loader ) { $loader_type = new WPML\Action\Type( $loader ); $backend = $loader_type->is( 'backend' ); $frontend = $loader_type->is( 'frontend' ); $ajax = $loader_type->is( 'ajax' ); $rest = $loader_type->is( 'rest' ); $cli = $loader_type->is( 'cli' ); $dic = $loader_type->is( 'dic' ); // Following logic will only be used for the case that // $loader is a class-string. /** @var class-string $loader */ if ( $backend && $frontend ) { $this->load_factory_or_action( $loader, $dic ); } elseif ( $backend && is_admin() ) { $this->load_factory_or_action( $loader, $dic ); } elseif ( $frontend && ! is_admin() ) { $this->load_factory_or_action( $loader, $dic ); } elseif ( $ajax && wpml_is_ajax() ) { $this->load_factory_or_action( $loader, $dic ); } elseif ( $rest ) { $this->load_factory_or_action( $loader, $dic ); } elseif ( $cli && wpml_is_cli() ) { $this->load_factory_or_action( $loader, $dic ); } } } /** * Load factory * * @param class-string $loader Action loader. * @param bool $use_dic */ private function load_factory_or_action( $loader, $use_dic ) { if ( $use_dic ) { $action_or_factory = WPML\Container\make( $loader ); } else { $action_or_factory = new $loader(); } if ( $action_or_factory instanceof IWPML_Action ) { $action_or_factory->add_hooks(); } elseif ( $action_or_factory instanceof IWPML_Action_Loader_Factory ) { $this->load_factory( $action_or_factory ); } } /** * @param IWPML_Action_Loader_Factory $factory */ private function load_factory( IWPML_Action_Loader_Factory $factory ) { if ( $factory instanceof WPML_AJAX_Base_Factory ) { /** @var WPML_AJAX_Base_Factory $factory */ $factory->set_ajax_action_validation( $this->get_ajax_action_validation() ); } if ( $factory instanceof IWPML_Deferred_Action_Loader ) { $this->add_deferred_action( $factory ); } else { $this->run_factory( $factory ); } } /** * Add deferred action * * @param IWPML_Deferred_Action_Loader $factory Action factory. */ private function add_deferred_action( IWPML_Deferred_Action_Loader $factory ) { $action = $factory->get_load_action(); if ( ! isset( $this->defered_actions[ $action ] ) ) { $this->defered_actions[ $action ] = array(); add_action( $action, array( $this, 'deferred_loader' ) ); } $this->defered_actions[ $action ][] = $factory; } /** * Deferred action loader */ public function deferred_loader() { $action = current_action(); foreach ( $this->defered_actions[ $action ] as $factory ) { /** * Deferred action loader factory * * @var IWPML_Deferred_Action_Loader $factory */ $this->run_factory( $factory ); } } /** * Get ajax action validation * * @return WPML_AJAX_Action_Validation */ private function get_ajax_action_validation() { if ( ! $this->ajax_action_validation ) { $this->ajax_action_validation = new WPML_AJAX_Action_Validation(); } return $this->ajax_action_validation; } /** * Run factory * * @param IWPML_Action_Loader_Factory $factory Action loader factory. */ private function run_factory( IWPML_Action_Loader_Factory $factory ) { $load_handlers = $factory->create(); if ( $load_handlers ) { if ( ! is_array( $load_handlers ) || is_callable( $load_handlers ) ) { $load_handlers = array( $load_handlers ); } foreach ( $load_handlers as $load_handler ) { if ( is_callable( $load_handler ) ) { $load_handler(); } else { $load_handler->add_hooks(); } } } } } action-filter-loader/class-wpml-current-screen-loader-factory.php 0000755 00000001670 14720342453 0021224 0 ustar 00 <?php /** * Class WPML_Current_Screen_Loader_Factory * * @author OnTheGoSystems */ abstract class WPML_Current_Screen_Loader_Factory implements IWPML_Backend_Action_Loader, IWPML_Deferred_Action_Loader { /** @return string */ public function get_load_action() { return 'current_screen'; } /** @return string */ abstract protected function get_screen_regex(); /** @return null|IWPML_Action */ abstract protected function create_hooks(); /** @return null|IWPML_Action */ public function create() { if ( $this->is_on_matching_screen() ) { return $this->create_hooks(); } return null; } /** return bool */ private function is_on_matching_screen() { $current_screen = get_current_screen(); foreach ( array( 'id', 'base' ) as $property ) { if ( isset( $current_screen->{$property} ) && preg_match( $this->get_screen_regex(), $current_screen->{$property} ) ) { return true; } } return false; } } action-filter-loader/class-wpml-ajax-action-base-factory.php 0000755 00000001571 14720342453 0020127 0 ustar 00 <?php /** * Class WPML_AJAX_Base_Factory * * @author OnTheGoSystems */ abstract class WPML_AJAX_Base_Factory implements IWPML_AJAX_Action_Loader, IWPML_Deferred_Action_Loader { /** @var WPML_AJAX_Action_Validation $ajax_action_check */ private $ajax_action_validation; /** * This loader must be deferred at least to 'plugins_loaded' to make sure * all the WP functions needed to validate the request are already loaded * * @return string */ public function get_load_action() { return 'plugins_loaded'; } public function is_valid_action( $ajax_action ) { return $this->ajax_action_validation->is_valid( $ajax_action ); } /** * @param WPML_AJAX_Action_Validation $ajax_action_validation */ public function set_ajax_action_validation( WPML_AJAX_Action_Validation $ajax_action_validation ) { $this->ajax_action_validation = $ajax_action_validation; } } admin-menu/class-wpml-admin-menu-root.php 0000755 00000012422 14720342453 0014412 0 ustar 00 <?php class WPML_Admin_Menu_Root { private $capability; private $function; private $icon_url; private $items = array(); private $menu_id; private $menu_title; private $page_title; private $position; /** * WPML_Menu_Root constructor. * * @param array|null $args * * @throws \InvalidArgumentException */ public function __construct( array $args = null ) { if ( $args ) { $required_fields = array( 'capability', 'menu_id', 'menu_title', 'page_title', ); foreach ( $required_fields as $required_field ) { if ( ! array_key_exists( $required_field, $args ) ) { throw new InvalidArgumentException( $required_field . ' is missing.' ); } } $fields = array( 'function', 'icon_url', 'position', ); $fields = array_merge( $required_fields, $fields ); foreach ( $fields as $field ) { if ( array_key_exists( $field, $args ) ) { $this->{$field} = $args[ $field ]; } } } } public function build() { do_action( 'wpml_admin_menu_configure', $this->get_menu_id() ); $this->adjust_items(); $root_slug = $this->get_menu_slug(); add_menu_page( $this->get_page_title(), $this->get_menu_title(), $this->get_capability(), $root_slug, $this->get_function(), $this->get_icon_url(), $this->get_position() ); do_action( 'wpml_admin_menu_root_configured', $this->get_menu_id(), $root_slug ); /** @var WPML_Admin_Menu_Item $menu_item */ foreach ( $this->items as $menu_item ) { $menu_item = apply_filters( 'wpml_menu_item_before_build', $menu_item, $root_slug ); $menu_item->build( $root_slug ); } } private function adjust_items() { if ( $this->items && count( $this->items ) > 1 ) { $menu_items = $this->items; $menu_items = array_unique( $menu_items ); $menu_items = array_map( array( $this, 'menu_order_fixer' ), $menu_items ); /** * Error suppression is required because of https://bugs.php.net/bug.php?id=50688 * which makes PHPUnit (or every case where xDebug is involved) to cause a * `PHP Warning: usort(): Array was modified by the user comparison function` */ @usort( $menu_items, array( $this, 'menu_order_sorter' ) ); $this->items = $menu_items; } } /** * @return string */ public function get_menu_slug() { $top_menu = null; if ( $this->items ) { $this->adjust_items(); $top_menu = $this->items[0]->get_menu_slug(); } return $top_menu; } /** * @return string */ public function get_page_title() { return $this->page_title; } /** * @param string $page_title */ public function set_page_title( $page_title ) { $this->page_title = $page_title; } /** * @return string */ public function get_menu_id() { return $this->menu_id; } /** * @return string */ public function get_menu_title() { return $this->menu_title; } /** * @param string $menu_title */ public function set_menu_title( $menu_title ) { $this->menu_title = $menu_title; } /** * @return string */ public function get_capability() { return $this->capability; } /** * @param string $capability */ public function set_capability( $capability ) { $this->capability = $capability; } /** * @return null|callable */ public function get_function() { return $this->function; } /** * @param null|callable $function */ public function set_function( $function ) { $this->function = $function; } /** * @return string */ public function get_icon_url() { return $this->icon_url; } /** * @param string $icon_url */ public function set_icon_url( $icon_url ) { $this->icon_url = $icon_url; } /** * @return array */ public function get_items() { return $this->items; } /** * @return int */ public function get_position() { return $this->position; } /** * @param int $position */ public function set_position( $position ) { $this->position = $position; } public function init_hooks() { add_action( 'wpml_admin_menu_register_item', array( $this, 'register_menu_item' ) ); add_action( 'admin_menu', array( $this, 'build' ) ); add_action( 'set_wpml_root_menu_capability', [ $this, 'set_capability' ], 10, 1 ); } /** * @param WPML_Admin_Menu_Item $item * * @return WPML_Admin_Menu_Item */ public function menu_order_fixer( WPML_Admin_Menu_Item $item ) { static $last_order = WPML_Main_Admin_Menu::MENU_ORDER_MAX; if ( $item->get_order() === null ) { $item->set_order( $last_order + 1 ); } if ( $last_order < PHP_INT_MAX ) { $last_order = $item->get_order(); } return $item; } /** * @param WPML_Admin_Menu_Item $a * @param WPML_Admin_Menu_Item $b * * @return int */ public function menu_order_sorter( WPML_Admin_Menu_Item $a, WPML_Admin_Menu_Item $b ) { $order_a = $a->get_order() === null ? 0 : $a->get_order(); $order_b = $b->get_order() === null ? 0 : $b->get_order(); if ( $order_a < $order_b ) { return - 1; } if ( $order_a === $order_b ) { return 0; } return 1; } /** * @param WPML_Admin_Menu_Item|array $item * * @throws \InvalidArgumentException */ public function register_menu_item( $item ) { if ( is_array( $item ) ) { $item = new WPML_Admin_Menu_Item( $item ); } $this->add_item( $item ); } public function add_item( WPML_Admin_Menu_Item $item ) { $this->items[] = $item; return $this; } } admin-menu/configurations/class-wpml-main-admin-menu.php 0000755 00000014006 14720342453 0017405 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Main_Admin_Menu { const MENU_ORDER_LANGUAGES = 100; const MENU_ORDER_THEMES_AND_PLUGINS_LOCALIZATION = 200; const MENU_ORDER_TAXONOMY_TRANSLATION = 900; const MENU_ORDER_SETTINGS = 9900; const MENU_ORDER_MAX = 10000; /** @var string */ private $languages_menu_slug; /** * @var WPML_Admin_Menu_Root */ private $root; /** * @var SitePress */ private $sitepress; /** * WPML_Menu_Main constructor. * * @param SitePress $sitepress * * @throws \InvalidArgumentException */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $this->languages_menu_slug = WPML_PLUGIN_FOLDER . '/menu/languages.php'; } /** * @throws \InvalidArgumentException */ public function configure() { $this->root = new WPML_Admin_Menu_Root( array( 'menu_id' => 'WPML', 'page_title' => __( 'WPML', 'sitepress' ), 'menu_title' => __( 'WPML', 'sitepress' ), 'capability' => 'wpml_manage_languages', 'menu_slug', 'function' => null, 'icon_url' => ICL_PLUGIN_URL . '/res/img/icon16.png', ) ); $this->root->init_hooks(); if ( $this->sitepress->is_setup_complete() ) { $this->languages(); do_action( 'icl_wpml_top_menu_added' ); if ( $this->is_wpml_setup_completed() ) { $this->themes_and_plugins_localization(); if ( ! $this->is_tm_active() ) { $this->translation_options(); } } $this->taxonomy_translation(); do_action( 'wpml_core_admin_menus_added' ); } else { $this->wizard(); } $this->support(); do_action( 'wpml_core_admin_menus_completed' ); } /** * @throws \InvalidArgumentException */ private function languages() { $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_LANGUAGES ); $menu->set_page_title( __( 'Languages', 'sitepress' ) ); $menu->set_menu_title( __( 'Languages', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_languages' ); $menu->set_menu_slug( $this->languages_menu_slug ); $this->root->add_item( $menu ); } private function wizard() { $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_LANGUAGES ); $menu->set_page_title( __( 'WPML Setup', 'sitepress' ) ); $menu->set_menu_title( __( 'Setup', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_languages' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/setup.php' ); $this->root->add_item( $menu ); } /** * @return bool */ private function is_wpml_setup_completed() { return $this->sitepress->get_setting( 'existing_content_language_verified' ) && 2 <= count( $this->sitepress->get_active_languages() ); } /** * @throws \InvalidArgumentException */ private function themes_and_plugins_localization() { $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_THEMES_AND_PLUGINS_LOCALIZATION ); $menu->set_page_title( __( 'Theme and plugins localization', 'sitepress' ) ); $menu->set_menu_title( __( 'Theme and plugins localization', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_theme_and_plugin_localization' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/theme-localization.php' ); $this->root->add_item( $menu ); } /** * @return bool */ private function is_tm_active() { return $this->sitepress->get_wp_api()->defined( 'WPML_TM_VERSION' ); } /** * @throws \InvalidArgumentException */ private function translation_options() { $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_SETTINGS ); $menu->set_page_title( __( 'Settings', 'sitepress' ) ); $menu->set_menu_title( __( 'Settings', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_translation_options' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/translation-options.php' ); $this->root->add_item( $menu ); } /** * @throws \InvalidArgumentException */ private function taxonomy_translation() { $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_TAXONOMY_TRANSLATION ); $menu->set_page_title( __( 'Taxonomy translation', 'sitepress' ) ); $menu->set_menu_title( __( 'Taxonomy translation', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_taxonomy_translation' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/taxonomy-translation.php' ); $menu->set_function( array( $this->sitepress, 'taxonomy_translation_page' ) ); $this->root->add_item( $menu ); } /** * @throws \InvalidArgumentException */ private function support() { $menu_slug = WPML_PLUGIN_FOLDER . '/menu/support.php'; $menu = new WPML_Admin_Menu_Item(); $menu->set_order( self::MENU_ORDER_MAX ); $menu->set_page_title( __( 'Support', 'sitepress' ) ); $menu->set_menu_title( __( 'Support', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_support' ); $menu->set_menu_slug( $menu_slug ); $this->root->add_item( $menu ); $this->troubleshooting_menu( $menu_slug ); $this->debug_information_menu( $menu_slug ); } /** * @param string $parent_slug * * @throws \InvalidArgumentException */ private function troubleshooting_menu( $parent_slug ) { $menu = new WPML_Admin_Menu_Item(); $menu->set_parent_slug( $parent_slug ); $menu->set_page_title( __( 'Troubleshooting', 'sitepress' ) ); $menu->set_menu_title( __( 'Troubleshooting', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_troubleshooting' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' ); $this->root->add_item( $menu ); } /** * @param string $parent_slug * * @throws \InvalidArgumentException */ private function debug_information_menu( $parent_slug ) { $menu = new WPML_Admin_Menu_Item(); $menu->set_parent_slug( $parent_slug ); $menu->set_page_title( __( 'Debug information', 'sitepress' ) ); $menu->set_menu_title( __( 'Debug information', 'sitepress' ) ); $menu->set_capability( 'wpml_manage_troubleshooting' ); $menu->set_menu_slug( WPML_PLUGIN_FOLDER . '/menu/debug-information.php' ); $this->root->add_item( $menu ); } } admin-menu/class-wpml-admin-menu-item.php 0000755 00000007172 14720342453 0014373 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Admin_Menu_Item { private $capability; private $function; private $menu_slug; private $menu_title; private $order; private $page_title; private $parent_slug; /** * WPML_Menu_Item constructor. * * @param array $args * * @throws \InvalidArgumentException */ public function __construct( array $args = null ) { if ( $args ) { $required_fields = array( 'capability', 'menu_slug', 'menu_title', 'page_title', ); foreach ( $required_fields as $required_field ) { if ( ! array_key_exists( $required_field, $args ) ) { throw new InvalidArgumentException( $required_field . ' is missing.' ); } } $fields = array( 'function', 'order', 'parent_slug', ); $fields = array_merge( $required_fields, $fields ); foreach ( $fields as $field ) { if ( array_key_exists( $field, $args ) ) { $this->{$field} = $args[ $field ]; } } } } /** * Required by `usort` to remove duplicates, as casts array elements to string * * @return string */ public function __toString() { return $this->serialize(); } public function build( $root_slug ) { $parent = $root_slug; if ( $this->get_parent_slug() ) { $parent = $this->get_parent_slug(); } add_submenu_page( $parent, $this->get_page_title(), $this->get_menu_title(), $this->get_capability(), $this->get_menu_slug(), $this->get_function() ); } /** * @return mixed */ public function get_parent_slug() { return $this->parent_slug; } /** * @param mixed $parent_slug */ public function set_parent_slug( $parent_slug ) { $this->parent_slug = $parent_slug; } /** * @return mixed */ public function get_page_title() { return $this->page_title; } /** * @param mixed $page_title */ public function set_page_title( $page_title ) { $this->page_title = $page_title; } /** * @return mixed */ public function get_menu_title() { return $this->menu_title; } /** * @param mixed $menu_title */ public function set_menu_title( $menu_title ) { $this->menu_title = $menu_title; } /** * @return mixed */ public function get_capability() { return $this->capability; } /** * @param mixed $capability */ public function set_capability( $capability ) { $this->capability = $capability; } /** * @return mixed */ public function get_menu_slug() { return $this->menu_slug; } /** * @param mixed $menu_slug */ public function set_menu_slug( $menu_slug ) { $this->menu_slug = $menu_slug; } /** * @return mixed */ public function get_function() { return $this->function; } /** * @param mixed $function */ public function set_function( $function ) { $this->function = $function; } /** * @return mixed */ public function get_order() { return $this->order; } /** * @param mixed $order */ public function set_order( $order ) { $this->order = $order; } /** * @return string */ private function serialize() { $function = $this->get_function(); if ( is_callable( $function ) ) { /** * "Hash" is for the hash table. That's not an actual hash of the callable, but it should * be good enough for the scope of this function */ $function = spl_object_hash( (object) $function ); } return wp_json_encode( array( 'capability' => $this->get_capability(), 'function' => $function, 'menu_slug' => $this->get_menu_slug(), 'menu_title' => $this->get_menu_title(), 'order' => $this->get_order(), 'page_title' => $this->get_page_title(), 'parent_slug' => $this->get_parent_slug(), ), 0, 1 ); } } admin-menu/Redirect.php 0000755 00000001423 14720342453 0011057 0 ustar 00 <?php namespace WPML\AdminMenu; use WPML\FP\Lst; use WPML\FP\Obj; class Redirect implements \IWPML_Backend_Action { public function add_hooks() { add_action( 'init', [ $this, 'redirectOldMenuUrls' ] ); } public function redirectOldMenuUrls() { $query = $_GET; $redirections = [ 'page' => [ 'wpml-translation-management/menu/main.php' => 'tm/menu/main.php', ], 'sm' => [ 'translation-services' => 'translators' ] ]; foreach ( $query as $param => $value ) { $query[ $param ] = Obj::pathOr( $value, [ $param, $value ], $redirections ); } /** @phpstan-ignore-next-line */ if ( array_diff_assoc( Lst::flatten( $_GET ), Lst::flatten( $query ) ) ) { if ( wp_safe_redirect( add_query_arg( $query ), 301, 'WPML' ) ) { exit; } } } } templates/interface-iwpml-template-service.php 0000755 00000000173 14720342453 0015620 0 ustar 00 <?php /** * @author OnTheGo Systems */ interface IWPML_Template_Service { public function show( $model, $template ); } templates/class-wpml-twig-template.php 0000755 00000000645 14720342453 0014132 0 ustar 00 <?php use WPML\Core\Twig_Environment; /** * @author OnTheGo Systems */ class WPML_Twig_Template implements IWPML_Template_Service { private $twig; /** * WPML_Twig_Template constructor. * * @param Twig_Environment $twig */ public function __construct( Twig_Environment $twig ) { $this->twig = $twig; } public function show( $model, $template ) { return $this->twig->render( $template, $model ); } } templates/wpml-twig-template-loader.php 0000755 00000001330 14720342453 0014263 0 ustar 00 <?php use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Environment; /** * Class WPML_Twig_Template_Loader */ class WPML_Twig_Template_Loader { /** * @var array */ private $paths; /** * WPML_Twig_Template_Loader constructor. * * @param array $paths */ public function __construct( array $paths ) { $this->paths = $paths; } /** * @return WPML_Twig_Template */ public function get_template() { $twig_loader = new Twig_Loader_Filesystem( $this->paths ); $environment_args = array(); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $environment_args['debug'] = true; } $twig = new Twig_Environment( $twig_loader, $environment_args ); return new WPML_Twig_Template( $twig ); } } class-wpml-tm-api.php 0000755 00000007012 14720342453 0010533 0 ustar 00 <?php use WPML\TM\ATE\Review\ReviewStatus; use WPML\FP\Obj; class WPML_TM_API { /** @var TranslationManagement */ private $TranslationManagement; /** @var WPML_TM_Blog_Translators $blog_translators */ private $blog_translators; /** * @var mixed[] */ private $translation_statuses; /** * WPML_TM_API constructor. * * @param WPML_TM_Blog_Translators $blog_translators * @param TranslationManagement $TranslationManagement */ public function __construct( &$blog_translators, &$TranslationManagement ) { $this->blog_translators = &$blog_translators; $this->TranslationManagement = &$TranslationManagement; $this->translation_statuses = [ ICL_TM_NOT_TRANSLATED => __( 'Not translated', 'wpml-translation-management' ), ICL_TM_WAITING_FOR_TRANSLATOR => __( 'Waiting for translator', 'wpml-translation-management' ), ICL_TM_IN_BASKET => __( 'In basket', 'wpml-translation-management' ), ICL_TM_IN_PROGRESS => __( 'In progress', 'wpml-translation-management' ), ICL_TM_DUPLICATE => __( 'Duplicate', 'wpml-translation-management' ), ICL_TM_COMPLETE => __( 'Complete', 'wpml-translation-management' ), ICL_TM_NEEDS_UPDATE => __( 'needs update', 'wpml-translation-management' ), ICL_TM_ATE_NEEDS_RETRY => __( 'In progress (connecting)', 'wpml-translation-management' ), ]; } public function get_translation_status_label( $status ) { return Obj::propOr( null, $status, $this->translation_statuses ); } public function init_hooks() { add_filter( 'wpml_is_translator', array( $this, 'is_translator_filter' ), 10, 3 ); add_filter( 'wpml_translator_languages_pairs', array( $this, 'translator_languages_pairs_filter' ), 10, 2 ); add_action( 'wpml_edit_translator', array( $this, 'edit_translator_action' ), 10, 2 ); } /** * @param bool $default * @param int|WP_User $user * @param array $args * * @return bool */ public function is_translator_filter( $default, $user, $args ) { $result = $default; $user_id = $this->get_user_id( $user ); if ( is_numeric( $user_id ) ) { $result = $this->blog_translators->is_translator( $user_id, $args ); } return $result; } public function edit_translator_action( $user, $language_pairs ) { $user_id = $this->get_user_id( $user ); if ( is_numeric( $user_id ) ) { $this->edit_translator( $user_id, $language_pairs ); } } /** * @param int $user_id * @param array $language_pairs */ private function edit_translator( $user_id, $language_pairs ) { global $wpdb; $user = new WP_User( $user_id ); if ( empty( $language_pairs ) ) { $user->remove_cap( \WPML\LIB\WP\User::CAP_TRANSLATE ); } elseif ( ! $user->has_cap( \WPML\LIB\WP\User::CAP_TRANSLATE ) ) { $user->add_cap( \WPML\LIB\WP\User::CAP_TRANSLATE ); } $language_pair_records = new WPML_Language_Pair_Records( $wpdb, new WPML_Language_Records( $wpdb ) ); $language_pair_records->store( $user_id, $language_pairs ); } public function translator_languages_pairs_filter( $default, $user ) { $result = $default; $user_id = $this->get_user_id( $user ); if ( is_numeric( $user_id ) ) { if ( $this->blog_translators->is_translator( $user_id ) ) { $result = $this->blog_translators->get_language_pairs( $user_id ); } } return $result; } /** * @param $user * * @return int */ private function get_user_id( $user ) { $user_id = $user; if ( is_a( $user, 'WP_User' ) ) { $user_id = $user->ID; return $user_id; } return $user_id; } } hooks/adjacent-links/class-wpml-adjacent-links-hooks.php 0000755 00000005223 14720342453 0017370 0 ustar 00 <?php /** * Class WPML_Adjacent_Links_Hooks * * @author OnTheGoSystems */ class WPML_Adjacent_Links_Hooks implements IWPML_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Language_Where_Clause $language_where_clause */ private $language_where_clause; /** * WPML_Adjacent_Links_Hooks constructor. * * @param SitePress $sitepress * @param wpdb $wpdb * @param WPML_Language_Where_Clause $language_where_clause */ public function __construct( SitePress $sitepress, wpdb $wpdb, WPML_Language_Where_Clause $language_where_clause ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; $this->language_where_clause = $language_where_clause; } public function add_hooks() { add_filter( 'get_previous_post_join', array( $this, 'get_adjacent_post_join' ) ); add_filter( 'get_next_post_join', array( $this, 'get_adjacent_post_join' ) ); add_filter( 'get_previous_post_where', array( $this, 'get_adjacent_post_where' ) ); add_filter( 'get_next_post_where', array( $this, 'get_adjacent_post_where' ) ); } /** * @param string $join_clause * * @return string */ function get_adjacent_post_join( $join_clause ) { $post_type = $this->get_current_post_type(); $cache_key = md5( (string) wp_json_encode( array( $post_type, $join_clause ) ) ); $cache_group = 'adjacent_post_join'; $join_cached = wp_cache_get( $cache_key, $cache_group ); if ( $join_cached ) { return $join_cached; } if ( $this->sitepress->is_translated_post_type( $post_type ) ) { $join_clause .= $this->wpdb->prepare( " JOIN {$this->wpdb->prefix}icl_translations wpml_translations ON wpml_translations.element_id = p.ID AND wpml_translations.element_type = %s", 'post_' . $post_type ); } wp_cache_set( $cache_key, $join_clause, $cache_group ); return $join_clause; } /** * @param string $where_clause * * @return string */ function get_adjacent_post_where( $where_clause ) { $post_type = $this->get_current_post_type(); $current_lang = $this->sitepress->get_current_language(); $cache_key = md5( (string) wp_json_encode( array( $post_type, $where_clause, $current_lang ) ) ); $cache_group = 'adjacent_post_where'; $where_cached = wp_cache_get( $cache_key, $cache_group ); if ( $where_cached ) { return $where_cached; } $where_clause .= $this->language_where_clause->get( $post_type ); wp_cache_set( $cache_key, $where_clause, $cache_group ); return $where_clause; } /** @return string */ private function get_current_post_type() { return get_post_type() ?: 'post'; } } hooks/adjacent-links/class-wpml-adjacent-links-hooks-factory.php 0000755 00000001017 14720342453 0021032 0 ustar 00 <?php /** * Class WPML_Adjacent_Links_Hooks_Factory * * @author OnTheGoSystems */ class WPML_Adjacent_Links_Hooks_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { /** @return WPML_Adjacent_Links_Hooks */ public function create() { global $sitepress, $wpdb; return new WPML_Adjacent_Links_Hooks( $sitepress, $wpdb, new WPML_Language_Where_Clause( $sitepress, $wpdb, new WPML_Display_As_Translated_Posts_Query( $wpdb, 'p' ) ) ); } } records/class-wpml-initialize-language-for-post-type.php 0000755 00000002577 14720342453 0017470 0 ustar 00 <?php class WPML_Initialize_Language_For_Post_Type { private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function run( $post_type, $default_language ) { do { $trid_max = (int) $this->wpdb->get_var( "SELECT MAX(trid) FROM {$this->wpdb->prefix}icl_translations" ) + 1; $sql = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_translations (`element_type`, `element_id`, `trid`, `language_code`)" . PHP_EOL; $sql .= "SELECT CONCAT('post_' , p.post_type) as element_type, p.ID as element_id, %d + p.ID as trid, %s as language_code" . PHP_EOL; $sql .= "FROM {$this->wpdb->posts} p" . PHP_EOL; $sql .= "LEFT OUTER JOIN {$this->wpdb->prefix}icl_translations t" . PHP_EOL; $sql .= "ON t.element_id = p.ID AND t.element_type = CONCAT('post_', p.post_type)" . PHP_EOL; $sql .= "WHERE p.post_type = %s AND t.translation_id IS NULL" . PHP_EOL; $sql .= "LIMIT 500"; $sql_prepared = $this->wpdb->prepare( $sql, array( $trid_max, $default_language, $post_type ) ); $results = $this->wpdb->query( $sql_prepared ); } while ( $results && ! $this->wpdb->last_error ); do_action( 'wpml_translation_update', [ 'type' => 'initialize_language_for_post_type', 'post_type' => $post_type, 'context' => 'post', ] ); return $results === 0 && ! $this->wpdb->last_error; } } records/class-wpml-tm-update-translation-status.php 0000755 00000001060 14720342453 0016557 0 ustar 00 <?php class WPML_TM_Update_Translation_Status { /** * @param int $job_id * @param int $new_status */ public static function by_job_id( $job_id, $new_status ) { /** @var stdClass $job */ $job = wpml_tm_load_job_factory()->get_translation_job( $job_id ); if ( $job ) { $new_status = (int) $new_status; $old_status = (int) $job->status; if ( $new_status !== $old_status ) { wpml_tm_get_records() ->icl_translation_status_by_translation_id( $job->translation_id ) ->update( array( 'status' => $new_status ) ); } } } } records/class-wpml-tm-records.php 0000755 00000010520 14720342453 0013062 0 ustar 00 <?php class WPML_TM_Records { /** @var WPDB $wpdb */ public $wpdb; /** @var array $cache */ private $cache = array( 'icl_translations' => array(), 'status' => array(), ); private $preloaded_statuses = null; /** @var WPML_Frontend_Post_Actions | WPML_Admin_Post_Actions $wpml_post_translations */ private $wpml_post_translations; /** @var WPML_Term_Translation $wpml_term_translations */ private $wpml_term_translations; public function __construct( wpdb $wpdb, WPML_Post_Translation $wpml_post_translations, WPML_Term_Translation $wpml_term_translations ) { $this->wpdb = $wpdb; $this->wpml_post_translations = $wpml_post_translations; $this->wpml_term_translations = $wpml_term_translations; } public function wpdb() { return $this->wpdb; } public function get_post_translations() { return $this->wpml_post_translations; } public function get_term_translations() { return $this->wpml_term_translations; } /** * @param int $translation_id * * @return WPML_TM_ICL_Translation_Status */ public function icl_translation_status_by_translation_id( $translation_id ) { if ( ! isset( $this->cache['status'][ $translation_id ] ) ) { $this->maybe_preload_translation_statuses(); $this->cache['status'][ $translation_id ] = new WPML_TM_ICL_Translation_Status( $this->wpdb, $this, $translation_id ); } return $this->cache['status'][ $translation_id ]; } private function maybe_preload_translation_statuses() { if ( null === $this->preloaded_statuses ) { $translation_ids = $this->wpml_post_translations->get_translations_ids(); if ( $translation_ids ) { $translation_ids = implode( ',', $translation_ids ); $this->preloaded_statuses = $this->wpdb->get_results( "SELECT status, translation_id FROM {$this->wpdb->prefix}icl_translation_status WHERE translation_id in ({$translation_ids})" ); } else { $this->preloaded_statuses = array(); } } } public function get_preloaded_translation_status( $translation_id, $rid ) { $data = null; if ( $this->preloaded_statuses ) { foreach ( $this->preloaded_statuses as $status ) { if ( $translation_id && $status->translation_id == $translation_id ) { $data = $status; break; } elseif ( $rid && $status->translation_id == $rid ) { $data = $status; break; } } } return $data; } /** * @param int $rid * * @return WPML_TM_ICL_Translation_Status */ public function icl_translation_status_by_rid( $rid ) { return new WPML_TM_ICL_Translation_Status( $this->wpdb, $this, $rid, 'rid' ); } /** * @param int $job_id * * @return WPML_TM_ICL_Translate_Job */ public function icl_translate_job_by_job_id( $job_id ) { return new WPML_TM_ICL_Translate_Job( $this, $job_id ); } /** * @param int $translation_id * * @return WPML_TM_ICL_Translations */ public function icl_translations_by_translation_id( $translation_id ) { return new WPML_TM_ICL_Translations( $this, $translation_id ); } /** * @param int $element_id * @param string $type_prefix * * @return WPML_TM_ICL_Translations */ public function icl_translations_by_element_id_and_type_prefix( $element_id, $type_prefix ) { $key = md5( $element_id . $type_prefix ); if ( ! isset( $this->cache['icl_translations'][ $key ] ) ) { $this->cache['icl_translations'][ $key ] = new WPML_TM_ICL_Translations( $this, array( 'element_id' => $element_id, 'type_prefix' => $type_prefix, ), 'id_type_prefix' ); } return $this->cache['icl_translations'][ $key ]; } /** * @param int $trid * @param string $lang * * @return WPML_TM_ICL_Translations */ public function icl_translations_by_trid_and_lang( $trid, $lang ) { $key = md5( $trid . $lang ); if ( ! isset( $this->cache['icl_translations'][ $key ] ) ) { $this->cache['icl_translations'][ $key ] = new WPML_TM_ICL_Translations( $this, array( 'trid' => $trid, 'language_code' => $lang, ), 'trid_lang' ); } return $this->cache['icl_translations'][ $key ]; } /** * @param int $trid * * @return int[] */ public function get_element_ids_from_trid( $trid ) { return $this->wpdb->get_col( $this->wpdb->prepare( "SELECT element_id FROM {$this->wpdb->prefix}icl_translations WHERE trid = %d", $trid ) ); } } records/class-wpml-tm-icl-translate-job.php 0000755 00000004012 14720342453 0014732 0 ustar 00 <?php class WPML_TM_ICL_Translate_Job { private $table = 'icl_translate_job'; private $job_id = 0; /** @var WPML_TM_Records $tm_records */ private $tm_records; /** * WPML_TM_ICL_Translation_Status constructor. * * @param WPML_TM_Records $tm_records * @param int $job_id */ public function __construct( WPML_TM_Records $tm_records, $job_id ) { $this->tm_records = $tm_records; $job_id = (int) $job_id; if ( $job_id > 0 ) { $this->job_id = $job_id; } else { throw new InvalidArgumentException( 'Invalid Job ID: ' . $job_id ); } } /** * @return int */ public function translator_id() { return $this->tm_records->icl_translation_status_by_rid( $this->rid() ) ->translator_id(); } /** * @return string|int */ public function service() { return $this->tm_records->icl_translation_status_by_rid( $this->rid() ) ->service(); } /** * @param array $args in the same format used by \wpdb::update() * * @return $this */ public function update( $args ) { $wpdb = $this->tm_records->wpdb(); $wpdb->update( $wpdb->prefix . $this->table, $args, array( 'job_id' => $this->job_id ) ); return $this; } /** * @return bool true if this job is the most recent job for the element it * belongs to and hence may be updated. */ public function is_open() { $wpdb = $this->tm_records->wpdb(); return $this->job_id === (int) $wpdb->get_var( $wpdb->prepare( "SELECT MAX(job_id) FROM {$wpdb->prefix}{$this->table} WHERE rid = %d", $this->rid() ) ); } public function rid() { return $this->get_job_column( 'rid' ); } public function editor() { return $this->get_job_column( 'editor' ); } private function get_job_column( $column ) { if ( ! trim( $column ) ) { return null; } $wpdb = $this->tm_records->wpdb(); $query = ' SELECT ' . $column . " FROM {$wpdb->prefix}{$this->table} WHERE job_id = %d LIMIT 1"; $prepare = $wpdb->prepare( $query, $this->job_id ); return $wpdb->get_var( $prepare ); } } records/class-wpml-tm-icl-translations.php 0000755 00000012522 14720342453 0014713 0 ustar 00 <?php class WPML_TM_ICL_Translations extends WPML_TM_Record_User { private $table = 'icl_translations'; private $fields = array(); private $related = array(); /** @var wpdb $wpdb */ private $wpdb; private $translation_id = 0; /** @var WPML_Frontend_Post_Actions | WPML_Admin_Post_Actions $post_translations */ private $post_translations; /** @var WPML_Term_Translation $term_translations */ private $term_translations; /** * WPML_TM_ICL_Translations constructor. * * @throws InvalidArgumentException if given data does not correspond to a * record in icl_translations * * @param WPML_TM_Records $tm_records * @param int|array $id * @param string $type translation id, trid_lang or id_prefix for now */ public function __construct( &$tm_records, $id, $type = 'translation_id' ) { $this->wpdb = $tm_records->wpdb(); $this->post_translations = $tm_records->get_post_translations(); $this->term_translations = $tm_records->get_term_translations(); parent::__construct( $tm_records ); if ( $id > 0 && $type === 'translation_id' ) { $this->{$type} = $id; } elseif ( $type === 'id_type_prefix' && isset( $id['element_id'] ) && isset( $id['type_prefix'] ) ) { $this->build_from_element_id( $id ); } elseif ( $type === 'trid_lang' && isset( $id['trid'] ) && isset( $id['language_code'] ) ) { $this->build_from_trid( $id ); } else { throw new InvalidArgumentException( 'Unknown column: ' . $type . ' or invalid id: ' . serialize( $id ) ); } } private function build_from_element_id( $id ) { if ( 'post' === $id['type_prefix'] ) { $this->translation_id = $this->post_translations->get_translation_id( $id['element_id'] ); } if ( 'term' === $id['type_prefix'] ) { $this->translation_id = $this->term_translations->get_translation_id( $id['element_id'] ); } if ( ! $this->translation_id ) { $this->select_translation_id( ' element_id = %d AND element_type LIKE %s ', array( $id['element_id'], $id['type_prefix'] . '%' ) ); } } private function build_from_trid( $id ) { $this->select_translation_id( ' trid = %d AND language_code = %s ', array( $id['trid'], $id['language_code'] ) ); } /** * @return WPML_TM_ICL_Translations[] */ public function translations() { if ( false === (bool) $this->related ) { $trid = $this->trid(); $found = false; $cache = new WPML_WP_Cache( 'WPML_TM_ICL_Translations::translations' ); $translation_ids = $cache->get( $trid, $found ); if ( ! $found ) { $translation_ids = $this->wpdb->get_results( "SELECT translation_id, language_code FROM {$this->wpdb->prefix}{$this->table} WHERE trid = " . $trid ); $cache->set( $trid, $translation_ids ); } foreach ( $translation_ids as $row ) { $this->related[ $row->language_code ] = $this->tm_records ->icl_translations_by_translation_id( $row->translation_id ); } } return $this->related; } private function select_by( $function, $field ) { $result = call_user_func( array( $this->post_translations, $function ), $this->translation_id ); $result = $result ? $result : call_user_func( array( $this->term_translations, $function ), $this->translation_id ); $result = $result ? $result : $this->select_field( $field ); return $result; } /** * @return null|int */ public function trid() { return $this->select_by( 'get_trid_from_translation_id', 'trid' ); } /** * @return int */ public function translation_id() { return $this->translation_id; } /** * @return null|int */ public function element_id() { return $this->select_by( 'get_element_from_translation_id', 'element_id' ); } /** * @return string|null */ public function language_code() { return $this->select_field( 'language_code' ); } /** * @return string|null */ public function source_language_code() { $lang = $this->post_translations->get_source_lang_from_translation_id( $this->translation_id ); $lang = $lang['found'] ? $lang : $this->term_translations->get_source_lang_from_translation_id( $this->translation_id ); $lang = $lang['found'] ? $lang['code'] : $this->select_field( 'source_language_code' ); return $lang; } /** * * @return $this */ public function delete() { $this->tm_records ->icl_translation_status_by_translation_id( $this->translation_id ) ->delete(); $this->wpdb->delete( $this->wpdb->prefix . $this->table, $this->get_args() ); return $this; } private function select_field( $field ) { $this->fields[ $field ] = isset( $this->fields[ $field ] ) ? $this->fields[ $field ] : $this->wpdb->get_var( $this->wpdb->prepare( " SELECT {$field} FROM {$this->wpdb->prefix}{$this->table} WHERE translation_id = %d LIMIT 1", $this->translation_id ) ); return $this->fields[ $field ]; } private function get_args() { return array( 'translation_id' => $this->translation_id ); } private function select_translation_id( $where, $prepare_args ) { $this->translation_id = $this->wpdb->get_var( "SELECT translation_id FROM {$this->wpdb->prefix}{$this->table} WHERE" . $this->wpdb->prepare( $where, $prepare_args ) . ' LIMIT 1' ); if ( ! $this->translation_id ) { throw new InvalidArgumentException( 'No translation entry found for query: ' . serialize( $where ) . serialize( $prepare_args ) ); } } } records/class-wpml-tm-icl-translation-status.php 0000755 00000010322 14720342453 0016045 0 ustar 00 <?php use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Just; use WPML\FP\Nothing; use function WPML\Container\make; use WPML\Element\API\TranslationsRepository; class WPML_TM_ICL_Translation_Status { /** @var wpdb $wpdb */ public $wpdb; private $tm_records; private $table = 'icl_translation_status'; private $translation_id = 0; private $rid = 0; private $status_result; /** * WPML_TM_ICL_Translation_Status constructor. * * @param wpdb $wpdb * @param WPML_TM_Records $tm_records * @param int $id * @param string $type */ public function __construct( wpdb $wpdb, WPML_TM_Records $tm_records, $id, $type = 'translation_id' ) { $this->wpdb = $wpdb; $this->tm_records = $tm_records; if ( $id > 0 && Lst::includes( $type, [ 'translation_id', 'rid' ] ) ) { $this->{$type} = $id; } else { throw new InvalidArgumentException( 'Unknown column: ' . $type . ' or invalid id: ' . $id ); } } /** * @param array $args in the same format used by \wpdb::update() * * @return $this */ public function update( $args ) { $this->wpdb->update( $this->wpdb->prefix . $this->table, $args, $this->get_args() ); $this->status_result = null; return $this; } /** * Wrapper for \wpdb::delete() */ public function delete() { $this->wpdb->delete( $this->wpdb->prefix . $this->table, $this->get_args() ); } /** * @return int */ public function rid() { return (int) $this->wpdb->get_var( "SELECT rid FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } /** * @return int */ public function status() { if ( $this->status_result === null ) { $status = $this->tm_records->get_preloaded_translation_status( $this->translation_id, $this->rid ); if ( $status ) { $this->status_result = (int) $status->status; } else { if ( $this->translation_id ) { $job = TranslationsRepository::getByTranslationId( $this->translation_id ); if ( $job ) { $this->status_result = \WPML\FP\Obj::prop( 'status', $job ); return $this->status_result; } } $this->status_result = (int) $this->wpdb->get_var( "SELECT status FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } } return (int) $this->status_result; } /** * @return Just|Nothing */ public function previous() { return Maybe::fromNullable( $this->wpdb->get_var( "SELECT _prevstate FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ) )->map( 'unserialize' ); } /** * @return string */ public function md5() { return $this->wpdb->get_var( "SELECT md5 FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } /** * @return int */ public function translation_id() { return (int) $this->wpdb->get_var( "SELECT translation_id FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } public function trid() { return $this->tm_records->icl_translations_by_translation_id( $this->translation_id() )->trid(); } public function element_id() { return $this->tm_records->icl_translations_by_translation_id( $this->translation_id() )->element_id(); } /** * @return int */ public function translator_id() { return (int) $this->wpdb->get_var( "SELECT translator_id FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } /** * @return string|int */ public function service() { return (int) $this->wpdb->get_var( "SELECT translation_service FROM {$this->wpdb->prefix}{$this->table} " . $this->get_where() ); } private function get_where() { return ' WHERE ' . ( $this->translation_id ? $this->wpdb->prepare( ' translation_id = %d ', $this->translation_id ) : $this->wpdb->prepare( ' rid = %d ', $this->rid ) ); } private function get_args() { return $this->translation_id ? array( 'translation_id' => $this->translation_id ) : array( 'rid' => $this->rid ); } /** * @param $id * * @return \WPML_TM_ICL_Translation_Status * @throws \WPML\Auryn\InjectionException */ public static function makeByRid( $id ) { return make( self::class, [ ':id' => $id, ':type' => 'rid' ] ); } } twig-extensions/wpml-twig-wp-plugin-extension.php 0000755 00000000750 14720342453 0016316 0 ustar 00 <?php use WPML\Core\Twig_Extension; use WPML\Core\Twig_SimpleFilter; class WPML_Twig_WP_Plugin_Extension extends Twig_Extension { /** * Returns the name of the extension. * @return string The extension name */ public function getName() { return 'wp_plugin'; } public function getFilters() { return array( new Twig_SimpleFilter( 'wp_do_action', array( $this, 'wp_do_action_filter' ) ), ); } public function wp_do_action_filter( $tag ) { do_action( $tag ); } } block-editor/Blocks/LanguageSwitcher.php 0000755 00000005244 14720342453 0014320 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks; use WPML\BlockEditor\Loader; use WPML\Element\API\Languages; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\Hooks; use function WPML\Container\make; use function WPML\FP\spreadArgs; use WPML\BlockEditor\Blocks\LanguageSwitcher\Render; class LanguageSwitcher { const BLOCK_LANGUAGE_SWITCHER = 'wpml/language-switcher'; const BLOCK_NAVIGATION_LANGUAGE_SWITCHER = 'wpml/navigation-language-switcher'; /** @var Render */ private $render; /** * @param Render $render */ public function __construct( Render $render ) { $this->render = $render; } /** * Returns the data that needs to be localized in the JS script. * @return array */ public function register() { $this->registerLanguageSwitcherBlock(); $this->registerNavigationLanguageSwitcherBlock(); return $this->getLanguageSwitcherLocalisedData(); } private function registerLanguageSwitcherBlock() { $blockSettings = [ 'render_callback' => [ $this->render, 'render_block' ], ]; register_block_type( self::BLOCK_LANGUAGE_SWITCHER, $blockSettings ); } private function registerNavigationLanguageSwitcherBlock() { $blockSettings = [ 'render_callback' => [ $this->render, 'render_block' ], 'attributes' => [ 'navigationLsHasSubMenuInSameBlock' => [ 'type' => 'boolean', 'default' => false, ], 'layoutOpenOnClick' => [ 'type' => 'boolean', 'default' => false, ], 'layoutShowArrow' => [ 'type' => 'boolean', 'default' => true, ], ], 'uses_context' => [ 'layout', 'showSubmenuIcon', 'openSubmenusOnClick', 'style', 'textColor', 'customTextColor', 'backgroundColor', 'customBackgroundColor', 'overlayTextColor', 'customOverlayTextColor', 'overlayBackgroundColor', 'customOverlayBackgroundColor', 'fontSize', 'customFontSize' ], ]; register_block_type( self::BLOCK_NAVIGATION_LANGUAGE_SWITCHER, $blockSettings ); } public function render() { /** @var \WPML_LS_Dependencies_Factory $lsFactory */ $lsFactory = make( \WPML_LS_Dependencies_Factory::class ); $shortcodeAPI = $lsFactory->shortcodes(); return $shortcodeAPI->callback( [] ); } private function getLanguageSwitcherLocalisedData() { $languages = Obj::values( Languages::withFlags( Languages::getActive() ) ); $activeLanguage = Lst::find( Relation::propEq( 'code', Languages::getCurrentCode() ), $languages ); $data = [ 'languages' => $languages, 'activeLanguage' => $activeLanguage, 'isRtl' => Languages::isRtl( strval( Obj::prop( 'code', $activeLanguage ) ) ), ]; return ['languageSwitcher' => $data ]; } } block-editor/Blocks/LanguageSwitcher/Model/LanguageSwitcher.php 0000755 00000002266 14720342453 0020615 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Relation; use function WPML\FP\invoke; use function WPML\FP\pipe; class LanguageSwitcher { /** @var LanguageItem[] */ private $languageItems; /** @var string */ private $currentLanguageCode; /** * @param string $currentLanguageItem * @param LanguageItem[] $languageItems */ public function __construct( $currentLanguageCode, array $languageItems ) { $this->currentLanguageCode = $currentLanguageCode; $this->languageItems = $languageItems; } /** * @return LanguageItem[] */ public function getLanguageItems() { return $this->languageItems; } /** * @return null|LanguageItem */ public function getCurrentLanguageItem() { return Lst::find(pipe(invoke('getCode'), Relation::equals($this->currentLanguageCode)), $this->languageItems); } /** * @return LanguageItem[] */ public function getAlternativeLanguageItems() { return Fns::reject(pipe(invoke('getCode'), Relation::equals($this->currentLanguageCode)), $this->languageItems); } /** * @return string */ public function getCurrentLanguageCode() { return $this->currentLanguageCode; } } block-editor/Blocks/LanguageSwitcher/Model/LanguageSwitcherTemplate.php 0000755 00000002503 14720342453 0022303 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model; class LanguageSwitcherTemplate { /** @var LanguageItemTemplate */ private $languageItemTemplate; /** @var LanguageItemTemplate */ private $currentLanguageItemTemplate; /** @var \DOMDocument */ private $DOMDocument; /** @var \DOMXpath */ private $DOMXpath; /** * @param LanguageItemTemplate $languageItemTemplate * @param LanguageItemTemplate $currentLanguageItemTemplate * @param \DOMDocument $DOMDocument */ public function __construct( LanguageItemTemplate $languageItemTemplate, LanguageItemTemplate $currentLanguageItemTemplate, \DOMDocument $DOMDocument ) { $this->languageItemTemplate = $languageItemTemplate; $this->currentLanguageItemTemplate = $currentLanguageItemTemplate; $this->DOMDocument = $DOMDocument; $this->DOMXpath = new \DOMXPath( $DOMDocument ); } /** * @return LanguageItemTemplate */ public function getLanguageItemTemplate() { return $this->languageItemTemplate; } /** * @return LanguageItemTemplate */ public function getCurrentLanguageItemTemplate() { return $this->currentLanguageItemTemplate; } /** * @return \DOMXPath */ public function getDOMXPath() { return $this->DOMXpath; } /** * @return \DOMDocument */ public function getDOMDocument() { return $this->DOMDocument; } } block-editor/Blocks/LanguageSwitcher/Model/LanguageItemTemplate.php 0000755 00000001734 14720342453 0021416 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\LabelTemplateInterface; class LanguageItemTemplate { /** @var \DOMNode */ private $template; /** @var \DOMNode */ private $container; /** @var ?LabelTemplateInterface */ private $labelTemplate; /** * @param \DOMNode $template * @param \DOMNode $container * @param ?LabelTemplateInterface $labelTemplate */ public function __construct( \DOMNode $template, \DOMNode $container, LabelTemplateInterface $labelTemplate = null) { $this->template = $template; $this->container = $container; $this->labelTemplate = $labelTemplate; } /** * @return ?\DOMNode */ public function getTemplate() { return $this->template; } /** * @return ?\DOMNode */ public function getContainer() { return $this->container; } /** * @return ?LabelTemplateInterface */ public function getLabelTemplate() { return $this->labelTemplate; } } block-editor/Blocks/LanguageSwitcher/Model/LabelTemplate/CurrentLanguage.php 0000755 00000000777 14720342453 0023167 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; class CurrentLanguage implements LabelTemplateInterface { const XPATH_LOCATOR = '//*[@data-wpml-label-type="current"]'; public function matchesXPath( \DOMXPath $domXPath, $prefix ) { return $domXPath->query( $prefix . self::XPATH_LOCATOR )->length > 0; } public function getDisplayName( LanguageItem $languageItem ) { return $languageItem->getDisplayName(); } } block-editor/Blocks/LanguageSwitcher/Model/LabelTemplate/BothLanguages.php 0000755 00000001234 14720342453 0022611 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; class BothLanguages implements LabelTemplateInterface { const XPATH_LOCATOR = '//*[@data-wpml-label-type="both"]'; public function matchesXPath( \DOMXPath $domXPath, $prefix ) { return $domXPath->query( $prefix . self::XPATH_LOCATOR )->length > 0; } public function getDisplayName( LanguageItem $languageItem ) { return $languageItem->getNativeName() === $languageItem->getDisplayName() ? $languageItem->getDisplayName() : sprintf( '%s (%s)', $languageItem->getNativeName(), $languageItem->getDisplayName() ); } } block-editor/Blocks/LanguageSwitcher/Model/LabelTemplate/LabelTemplateInterface.php 0000755 00000000700 14720342453 0024417 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; interface LabelTemplateInterface { /** * @param \DOMXPath $domXPath * @param $prefix * @return boolean */ public function matchesXPath( \DOMXPath $domXPath, $prefix ); /** * @param LanguageItem $languageItem * @return string */ public function getDisplayName( LanguageItem $languageItem ); } block-editor/Blocks/LanguageSwitcher/Model/LabelTemplate/LanguageCode.php 0000755 00000000762 14720342453 0022411 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; class LanguageCode implements LabelTemplateInterface { const XPATH_LOCATOR = '//*[@data-wpml-label-type="code"]'; public function matchesXPath( \DOMXPath $domXPath, $prefix ) { return $domXPath->query( $prefix . self::XPATH_LOCATOR )->length > 0; } public function getDisplayName( LanguageItem $languageItem ) { return $languageItem->getCode(); } } block-editor/Blocks/LanguageSwitcher/Model/LabelTemplate/NativeLanguage.php 0000755 00000000774 14720342453 0022770 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; class NativeLanguage implements LabelTemplateInterface { const XPATH_LOCATOR = '//*[@data-wpml-label-type="native"]'; public function matchesXPath( \DOMXPath $domXPath, $prefix ) { return $domXPath->query( $prefix . self::XPATH_LOCATOR )->length > 0; } public function getDisplayName( LanguageItem $languageItem ) { return $languageItem->getNativeName(); } } block-editor/Blocks/LanguageSwitcher/Model/LanguageItem.php 0000755 00000002761 14720342453 0017723 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher\Model; class LanguageItem { /** @var string */ private $displayName; /** @var string */ private $nativeName; /** @var string */ private $code; /** @var string */ private $url; /** @var string */ private $flagUrl; /** @var string */ private $flagTitle; /** @var string */ private $flagAlt; /** * @param string $displayName * @param string $nativeName * @param string $code * @param string $url * @param string $flagUrl * @param string $flagTitle * @param string $flagAlt */ public function __construct( $displayName, $nativeName, $code, $url, $flagUrl, $flagTitle, $flagAlt ) { $this->displayName = $displayName; $this->nativeName = $nativeName; $this->code = $code; $this->url = $url; $this->flagUrl = $flagUrl; $this->flagTitle = $flagTitle; $this->flagAlt = $flagAlt; } /** * @return string */ public function getDisplayName() { return $this->displayName; } /** * @return string */ public function getCode() { return $this->code; } /** * @return string */ public function getUrl() { return $this->url; } /** * @return string */ public function getFlagUrl() { return $this->flagUrl; } /** * @return string */ public function getNativeName() { return $this->nativeName; } /** * @return string */ public function getFlagTitle() { return $this->flagTitle; } /** * @return string */ public function getFlagAlt() { return $this->flagAlt; } } block-editor/Blocks/LanguageSwitcher/Repository.php 0000755 00000003256 14720342453 0016500 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageSwitcher; use WPML\FP\Obj; class Repository { /** @var \WPML_LS_Model_Build */ private $languageSwitcherModelBuilder; public function __construct( \SitePress $sitepress, \WPML_LS_Dependencies_Factory $dependencies = null ) { $dependencies = $dependencies ?: new \WPML_LS_Dependencies_Factory( $sitepress, \WPML_Language_Switcher::parameters() ); $this->languageSwitcherModelBuilder = new \WPML_LS_Model_Build( $dependencies->settings(), $sitepress, 'wpml-ls-' ); } /** * @return LanguageSwitcher */ public function getCurrentLanguageSwitcher( ) { $model = $this->languageSwitcherModelBuilder->get( new \WPML_LS_Slot( [ 'display_link_for_current_lang' => true, 'display_names_in_native_lang' => true, 'display_names_in_current_lang' => true, 'display_flags' => true, ]) ); $languages = $model[ 'languages' ]; $currentLanguageCode = $model[ 'current_language_code' ]; $languageItems = []; foreach( $languages as $language ) { $languageItems[] = $this->buildLanguageItem( $language ); } return new LanguageSwitcher( $currentLanguageCode, $languageItems ); } private function buildLanguageItem( array $language ) { return new LanguageItem( Obj::propOr( '', 'display_name', $language ), Obj::propOr( '', 'native_name', $language ), Obj::propOr( '', 'code', $language ), Obj::propOr( '', 'url', $language ), Obj::propOr( '', 'flag_url', $language ), Obj::propOr( '', 'flag_title', $language ), Obj::propOr( '', 'flag_alt', $language ) ); } } block-editor/Blocks/LanguageSwitcher/Render.php 0000755 00000023151 14720342453 0015534 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItem; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItemTemplate; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageSwitcherTemplate; use WPML\FP\Obj; class Render { const COLOR_CLASSNAMES_STRING = 'has-text-color has-%s-color'; const COLOR_STYLE_STRING = 'color:%s;'; const BACKGROUND_CLASSNAMES_STRING = 'has-background has-%s-background-color'; const BACKGROUND_STYLE_STRING = 'background-color:%s;'; /** @var Parser */ private $parser; /** @var Repository */ private $repository; public function __construct( Parser $parser, Repository $repository ) { $this->parser = $parser; $this->repository = $repository; } /** * @param string $savedHTML * @param string $source_block * @param \WP_Block $parent_block * * @return string */ public function render_block( $blockAttrs, $savedHTML, $parentBlock ) { $context = $parentBlock->context; $languageSwitcherTemplate = $this->parser->parse( $blockAttrs, $savedHTML, $parentBlock, $context ); $languageSwitcher = $this->repository->getCurrentLanguageSwitcher(); foreach ( $languageSwitcher->getLanguageItems() as $languageItem ) { $isCurrent = $languageSwitcher->getCurrentLanguageCode() === $languageItem->getCode(); $parserXPathPrefix = $isCurrent ? Parser::PATH_CURRENT_LANGUAGE_ITEM : Parser::PATH_LANGUAGE_ITEM; $languageSwitcherItemTemplate = $isCurrent ? $languageSwitcherTemplate->getCurrentLanguageItemTemplate() : $languageSwitcherTemplate->getLanguageItemTemplate(); $this->createLanguageItemNode( $languageSwitcherItemTemplate, $parserXPathPrefix, $languageItem, $languageSwitcherTemplate, $parentBlock, $context ); } return $this->getBodyHTML( $languageSwitcherTemplate->getDOMDocument() ); } /** * @param LanguageItemTemplate $languageItemTemplate * @param string $XPathPrefix * @param LanguageItem $languageItem * @param LanguageSwitcherTemplate $languageSwitcherTemplate * * @return \DOMNode|null */ private function createLanguageItemNode( LanguageItemTemplate $languageItemTemplate, $XPathPrefix, LanguageItem $languageItem, LanguageSwitcherTemplate $languageSwitcherTemplate, $sourceBlock, $context ) { $template = $languageItemTemplate->getTemplate(); $container = $languageItemTemplate->getContainer(); if ( empty( $template ) || empty( $container ) ) { return null; } $newLanguageItem = $template->cloneNode( true ); $container->appendChild( $newLanguageItem ); $linkQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix . '/' . Parser::PATH_ITEM_LINK ); $textTarget = &$newLanguageItem; if ( $linkQuery->length > 0 ) { $link = $linkQuery->item( $linkQuery->length - 1 ); $link->setAttribute( 'href', $languageItem->getUrl() ); $textTarget = $link; } if ( $languageItemTemplate->getLabelTemplate() ) { $labelQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix . '/' . Parser::PATH_ITEM_LABEL ); if ( $labelQuery->length > 0 ) { $label = $labelQuery->item( $labelQuery->length - 1 ); if ( $label ) { $label->textContent = $languageItemTemplate->getLabelTemplate()->getDisplayName( $languageItem ); } } else { $textTarget->textContent = $languageItemTemplate->getLabelTemplate()->getDisplayName( $languageItem ); } } $flagQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix . '/' . Parser::PATH_ITEM_FLAG_URL ); if ( $flagQuery->length > 0 ) { $flag = $flagQuery->item( $flagQuery->length - 1 ); if ( $flag ) { $flag->setAttribute( 'src', $languageItem->getFlagUrl() ); } } if ( isset( $sourceBlock->attributes['layoutOpenOnClick'] ) ) { $dropdownFirstItemQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix . "/ancestor::li" ); if ( $dropdownFirstItemQuery->length > 0 ) { $dpFirstItem = $dropdownFirstItemQuery->item( $dropdownFirstItemQuery->length - 1 ); if ( $dpFirstItem ) { $dpFirstItem->setAttribute( 'onclick', "(()=>{const ariaExpanded = this.children[0].getAttribute('aria-expanded'); this.children[0].setAttribute('aria-expanded', ariaExpanded === 'true' ? 'false' : 'true');})(this);" ); } } } // Apply some classNames and Styles according to values in context if the current block is Navigation Language Switcher // We use values from context to inherit them from the parent Navigation Block if ( $sourceBlock->name === LanguageSwitcher::BLOCK_NAVIGATION_LANGUAGE_SWITCHER ) { // Apply specific logic only when the language item = current language item if ( $XPathPrefix === Parser::PATH_CURRENT_LANGUAGE_ITEM ) { $this->maybeApplyColorsForLanguageItems( $languageSwitcherTemplate, $XPathPrefix, $context, true ); } // Apply specific logic only when the language item = secondary language item if ( $XPathPrefix === Parser::PATH_LANGUAGE_ITEM ) { $this->maybeApplyColorsForLanguageItems( $languageSwitcherTemplate, $XPathPrefix, $context, false ); } } return $newLanguageItem; } /** * @param LanguageSwitcherTemplate $languageSwitcherTemplate * @param string $XPathPrefix * @param array $context * @param bool $isCurrentLanguageItem * * @return void */ private function maybeApplyColorsForLanguageItems( $languageSwitcherTemplate, $XPathPrefix, $context, $isCurrentLanguageItem ) { $langItemQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix ); $langItemSpanQuery = $languageSwitcherTemplate->getDOMXPath()->query( $XPathPrefix . "//span[@data-wpml='label']" ); if ( $langItemQuery->length > 0 ) { $langItem = $langItemQuery->item( $langItemQuery->length - 1 ); $langItemSpan = $langItemSpanQuery->item( $langItemSpanQuery->length - 1 ); if ( $langItem && $langItemSpan ) { if ( $isCurrentLanguageItem ) { $this->maybeApplyColorsForCurrentLanguageItem( $langItem, $langItemSpan, $context ); } else { $this->maybeApplyColorsForLanguageItem( $langItem, $langItemSpan, $context ); } } } } /** * @param \DOMNode $langItem * @param \DOMNode $langItemSpan * @param array $context * * @return void */ private function maybeApplyColorsForCurrentLanguageItem( $langItem, $langItemSpan, $context ) { $namedTextColor = Obj::propOr( null, 'textColor', $context ); $namedBackgroundColor = Obj::propOr( null, 'backgroundColor', $context ); $customTextColor = Obj::propOr( null, 'customTextColor', $context ); $customBackgroundColor = Obj::propOr( null, 'customBackgroundColor', $context ); if ( isset( $namedTextColor ) ) { $this->appendAttributeValueToDOMElement( $langItemSpan, 'class', ' ' . sprintf( self::COLOR_CLASSNAMES_STRING, $namedTextColor ) ); } elseif ( isset( $customTextColor ) ) { $this->appendAttributeValueToDOMElement( $langItemSpan, 'style', sprintf( self::COLOR_STYLE_STRING, $customTextColor ) ); } if ( isset( $namedBackgroundColor ) ) { $this->appendAttributeValueToDOMElement( $langItem, 'class', ' ' . sprintf( self::BACKGROUND_CLASSNAMES_STRING, $namedBackgroundColor ) ); } elseif ( isset( $customBackgroundColor ) ) { $this->appendAttributeValueToDOMElement( $langItem, 'style', sprintf( self::BACKGROUND_STYLE_STRING, $customBackgroundColor ) ); } } /** * @param \DOMNode $langItem * @param \DOMNode $langItemSpan * @param array $context * * @return void */ private function maybeApplyColorsForLanguageItem( $langItem, $langItemSpan, $context ) { $namedOverlayTextColor = Obj::propOr( null, 'overlayTextColor', $context ); $namedOverlayBackgroundColor = Obj::propOr( null, 'overlayBackgroundColor', $context ); $customOverlayTextColor = Obj::propOr( null, 'customOverlayTextColor', $context ); $customOverlayBackgroundColor = Obj::propOr( null, 'customOverlayBackgroundColor', $context ); if ( isset( $namedOverlayTextColor ) ) { $this->appendAttributeValueToDOMElement( $langItemSpan, 'class', ' ' . sprintf( self::COLOR_CLASSNAMES_STRING, $namedOverlayTextColor ) ); } elseif ( isset( $customOverlayTextColor ) ) { $this->appendAttributeValueToDOMElement( $langItemSpan, 'style', sprintf( self::COLOR_STYLE_STRING, $customOverlayTextColor ) ); } if ( isset( $namedOverlayBackgroundColor ) ) { $this->appendAttributeValueToDOMElement( $langItem, 'class', ' ' . sprintf( self::BACKGROUND_CLASSNAMES_STRING, $namedOverlayBackgroundColor ) ); } elseif ( isset( $customOverlayBackgroundColor ) ) { $this->appendAttributeValueToDOMElement( $langItem, 'style', sprintf( self::BACKGROUND_STYLE_STRING, $customOverlayBackgroundColor ) ); } } /** * @param \DOMNode $element * @param string $attribute * @param string $value * * @return void */ private function appendAttributeValueToDOMElement( $element, $attribute, $value ) { $currentElementAttributeValue = $this->getDOMElementCurrentAttributeValue( $element, $attribute ); $element->setAttribute( $attribute, $currentElementAttributeValue . $value ); } /** * @param \DOMNode $element * @param string $attribute * * @return string */ private function getDOMElementCurrentAttributeValue( $element, $attribute ) { return $element->getAttribute( $attribute ); } /** * @param \DOMDocument $DOMDocument * * @return string */ private function getBodyHTML( $DOMDocument ) { $html = $DOMDocument->saveHTML(); if ( ! $html ) { return ''; } $start = strpos( $html, '<body>' ); $end = strpos( $html, '</body>' ); return substr( $html, $start + strlen( '<body>' ), $end - $start - strlen( '<body>' ) ) ?: ''; } } block-editor/Blocks/LanguageSwitcher/Parser.php 0000755 00000017150 14720342453 0015553 0 ustar 00 <?php namespace WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\BothLanguages; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\CurrentLanguage; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\LabelTemplateInterface; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\LanguageCode; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\Label\NativeLanguage; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageItemTemplate; use WPML\BlockEditor\Blocks\LanguageSwitcher\Model\LanguageSwitcherTemplate; use WPML\FP\Obj; class Parser { const PATH_LANGUAGE_ITEM = '//*[@data-wpml="language-item"]'; const PATH_CURRENT_LANGUAGE_ITEM = '//*[@data-wpml="current-language-item"]'; const PATH_ITEM_LINK = '/*[@data-wpml="link"]'; const PATH_ITEM_LABEL = '/*[@data-wpml="label"]'; const PATH_ITEM_CODE_LABEL_TYPE = '/*[@data-wpml-label-type="code"]'; const PATH_ITEM_CURRENT_LABEL_TYPE = '/*[@data-wpml-label-type="current"]'; const PATH_ITEM_BOTH_LABEL_TYPE = '/*[@data-wpml-label-type="both"]'; const PATH_ITEM_NATIVE_LABEL_TYPE = '/*[@data-wpml-label-type="native"]'; const PATH_ITEM_FLAG_URL = '/*[@data-wpml="flag-url"]'; const PATH_LANGUAGE_ITEM_CONTAINER = '(//*[@data-wpml="language-item"])[last()]/..'; const PATH_CONTAINER_TEMPLATE = '(%s)[last()]/..'; const LABEL_TYPES = [ CurrentLanguage::class, LanguageCode::class, NativeLanguage::class, BothLanguages::class, ]; /** * @param string $blockHTML * * @return null|LanguageSwitcherTemplate */ public function parse( $blockAttrs, $blockHTML, $sourceBlock, $context ) { if ( empty( $blockHTML ) ) { return null; } // converts double quotes around font family to its corresponding XML entity in order to make it render properly on frontend. if ( isset( $blockAttrs[ 'fontFamilyValue' ] ) ) { $blockHTML = $this->maybeFixFontFamilyInStyle( $blockHTML, $blockAttrs[ 'fontFamilyValue' ] ); } // Replace some classNames according to values in context if the current block is Navigation Language Switcher // We use values from context to inherit them from the parent Navigation Block if ( $sourceBlock->name === LanguageSwitcher::BLOCK_NAVIGATION_LANGUAGE_SWITCHER ) { // Replaces classNames to control openOnClick and showArrow settings according to values in context for the navigation LS $blockHTML = $this->maybeReplaceSubmenuClassnamesForNavBlock( $blockHTML, $blockAttrs, $context ); // Replaces classNames to control orientation settings according to values in context for the navigation LS $blockHTML = $this->maybeReplaceOrientationClassnamesForNavBlock( $blockHTML, $context ); } $blockDOMDocument = new \DOMDocument(); libxml_use_internal_errors( true ); $blockDOMDocument->loadHTML( $blockHTML ); $errors = libxml_get_errors(); // todo: catch real errors here, this is required because usage of html5 tags will work, but will throw a warning. $domXPath = new \DOMXpath( $blockDOMDocument ); $currentLanguageItemContainerNode = $this->getContainerNode( self::PATH_CURRENT_LANGUAGE_ITEM, $domXPath ); $currentLanguageItemLabel = $this->getLanguageItemlabel( $domXPath, self::PATH_CURRENT_LANGUAGE_ITEM ); $currentLanguageItemTemplate = new LanguageItemTemplate( $this->getTemplateNode( self::PATH_CURRENT_LANGUAGE_ITEM, $currentLanguageItemContainerNode, $domXPath ), $currentLanguageItemContainerNode, $currentLanguageItemLabel ); $languageItemContainerNode = $this->getContainerNode( self::PATH_LANGUAGE_ITEM, $domXPath ); $languageItemLabel = $this->getLanguageItemlabel( $domXPath, self::PATH_LANGUAGE_ITEM ); $languageItemTemplateNode = $this->getTemplateNode( self::PATH_LANGUAGE_ITEM, $languageItemContainerNode, $domXPath ); $languageItemTemplate = new LanguageItemTemplate( $languageItemTemplateNode, $languageItemContainerNode, $languageItemLabel ); return new LanguageSwitcherTemplate( $languageItemTemplate, $currentLanguageItemTemplate, $blockDOMDocument ); } /** * @param string $selector * @param \DOMXpath $domQuery * * @return \DOMNode */ private function getContainerNode( $selector, $domQuery ) { return $domQuery->query( sprintf( self::PATH_CONTAINER_TEMPLATE, $selector ) )->item( 0 ); } /** * @param string $selector * @param \DOMNode $container * @param \DOMXpath $domQuery * * @return \DOMNode */ private function getTemplateNode( $selector, $container, $domQuery ) { $itemQuery = $domQuery->query( $selector ); $firstItem = $itemQuery->item( 0 ); if ( empty( $firstItem ) ) { return null; } $template = $firstItem->cloneNode( true ); foreach ( $itemQuery as $item ) { if ( $item instanceof \DOMElement ) { $container->removeChild( $item ); } else if ( $item instanceof \DOMNode ) { $item->remove(); } } return $template; } /** * @param \DOMXPath $DOMXpath * * @return ?LabelTemplateInterface */ private function getLanguageItemlabel( $DOMXpath, $XPathPrefix ) { foreach ( static::LABEL_TYPES as $labelTypeClass ) { /** @var LabelTemplateInterface $labelType */ $labelType = new $labelTypeClass(); if ( $labelType->matchesXPath( $DOMXpath, $XPathPrefix ) ) { return $labelType; } } return null; } private function maybeFixFontFamilyInStyle( $blockHTML, $fontFamilyValue ) { $fontFamilyValuePattern = '/\\--font-family:' . preg_quote( $fontFamilyValue ) . '/'; $convertDoubleQuoteToXMLEntity = function ( $matches ) { return str_replace( '"', '"', $matches[ 0 ] ); }; return preg_replace_callback( $fontFamilyValuePattern, $convertDoubleQuoteToXMLEntity, $blockHTML ); } /** * @param string $blockHTML * @param array $source_block * @param array $context * * @return string */ private function maybeReplaceSubmenuClassnamesForNavBlock( $blockHTML, $blockAttrs, $context ) { $navigationLsHasSubMenuInSameBlock = Obj::propOr( null, 'navigationLsHasSubMenuInSameBlock', $blockAttrs ); if ( ! $navigationLsHasSubMenuInSameBlock ) { $openOnClick = Obj::propOr( null, 'layoutOpenOnClick', $blockAttrs ); $showArrow = Obj::propOr( null, 'layoutShowArrow', $blockAttrs ); } else { // If the navigation LS has submenu block inside the same parent navigation block, // we inherit the values of openOnClick and showArrow settings from the parent block, // otherwise the values from navigation LS attributes will be used $openOnClick = Obj::propOr( null, 'openSubmenusOnClick', $context ); $showArrow = Obj::propOr( null, 'showSubmenuIcon', $context ); } if ( $openOnClick !== null ) { $openOnClickClass = 'open-on-click'; $openOnHoverClass = 'open-on-hover-click'; $blockHTML = $openOnClick ? str_replace( $openOnHoverClass, $openOnClickClass, $blockHTML ) : str_replace( $openOnClickClass, $openOnHoverClass, $blockHTML ); } if ( $showArrow !== null ) { $blockHTML = ! $showArrow ? str_replace( 'wpml-ls-dropdown', 'wpml-ls-dropdown hide-arrow', $blockHTML ) : $blockHTML; } return $blockHTML; } /** * @param string $blockHTML * @param array $context * * @return string */ private function maybeReplaceOrientationClassnamesForNavBlock( $blockHTML, $context ) { $orientation = Obj::pathOr( null, [ 'layout', 'orientation' ], $context ); if ( $orientation === null ) { return $blockHTML; } $verticalClasses = [ 'vertical-list', 'isVertical' ]; $horizontalClasses = [ 'horizontal-list', 'isHorizontal' ]; return $orientation === 'horizontal' ? str_replace( $verticalClasses, $horizontalClasses, $blockHTML ) : str_replace( $horizontalClasses, $verticalClasses, $blockHTML ); } } block-editor/class-wpml-block-editor-helper.php 0000755 00000003357 14720342453 0015565 0 ustar 00 <?php /** * Class WPML_Block_Editor_Helper */ class WPML_Block_Editor_Helper { /** * Check if Block Editor is active. * Must only be used after plugins_loaded action is fired. * * @return bool */ public static function is_active() { // Gutenberg plugin is installed and activated. $gutenberg = ! ( false === has_filter( 'replace_editor', 'gutenberg_init' ) ); // Block editor since 5.0. $block_editor = version_compare( $GLOBALS['wp_version'], '5.0-beta', '>' ); if ( ! $gutenberg && ! $block_editor ) { return false; } if ( self::is_classic_editor_plugin_active() ) { $editor_option = get_option( 'classic-editor-replace' ); $block_editor_active = array( 'no-replace', 'block' ); return in_array( $editor_option, $block_editor_active, true ); } return true; } /** * Check if it is admin page to edit any type of post with Block Editor. * Must be used not earlier than plugins_loaded action fired. * * @return bool */ public static function is_edit_post() { $current_screen = get_current_screen(); return $current_screen && 'post' === $current_screen->base && self::is_active() && self::is_block_editor( $current_screen ); } /** * Check if Classic Editor plugin is active. * * @return bool */ public static function is_classic_editor_plugin_active() { if ( ! function_exists( 'is_plugin_active' ) ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( is_plugin_active( 'classic-editor/classic-editor.php' ) ) { return true; } return false; } public static function is_block_editor( $current_screen ) { if ( version_compare( $GLOBALS['wp_version'], '5.0-beta', '>' ) ) { return $current_screen->is_block_editor(); } else { return false; } } } block-editor/Loader.php 0000755 00000006072 14720342453 0011055 0 ustar 00 <?php namespace WPML\BlockEditor; use WPML\BlockEditor\Blocks\LanguageSwitcher; use WPML\LIB\WP\Hooks; use WPML\Core\WP\App\Resources; use function WPML\Container\make; use function WPML\FP\spreadArgs; class Loader implements \IWPML_Backend_Action, \IWPML_REST_Action { const SCRIPT_NAME = 'wpml-blocks'; /** @var array Contains the script data that needs to be localized for the registered blocks. */ private $localizedScriptData = []; public function add_hooks() { if ( \WPML_Block_Editor_Helper::is_active() ) { Hooks::onAction( 'init' ) ->then( [ $this, 'registerBlocks' ] ) ->then( [ $this, 'maybeEnqueueNavigationBlockStyles' ] ); Hooks::onAction( 'wp_enqueue_scripts' ) ->then( [ $this, 'enqueueBlockStyles' ] ); Hooks::onAction( 'enqueue_block_editor_assets' ) ->then( [ $this, 'enqueueBlockAssets' ] ); Hooks::onFilter( 'block_categories_all', 10, 2 ) ->then( spreadArgs( [ $this, 'registerCategory' ] ) ); } } /** * @param array[] $block_categories * * @return mixed */ public function registerCategory( $block_categories ) { array_push( $block_categories, [ 'slug' => 'wpml', 'title' => __( 'WPML', 'sitepress-multilingual-cms' ), 'icon' => null, ] ); return $block_categories; } /** * Register blocks that need server side render. */ public function registerBlocks() { $LSLocalizedScriptData = make( LanguageSwitcher::class )->register(); $this->localizedScriptData = array_merge( $this->localizedScriptData, $LSLocalizedScriptData ); } /** * @return void */ public function enqueueBlockAssets() { $dependencies = array_merge( [ 'wp-blocks', 'wp-i18n', 'wp-element', ], $this->getEditorDependencies() ); $localizedScriptData = [ 'name' => 'WPMLBlocks', 'data' => $this->localizedScriptData ]; $enqueuedApp = Resources::enqueueApp( 'blocks' ); $enqueuedApp( $localizedScriptData, $dependencies ); } public function enqueueBlockStyles() { wp_enqueue_style( self::SCRIPT_NAME, ICL_PLUGIN_URL . '/dist/css/blocks/styles.css', [], ICL_SITEPRESS_VERSION ); } /** * We inherit the WP navigation block styles while rendering our Language Switcher Block, * so when there's no navigation block is rendered, we still need to enqueue the wp-block-navigation styles so that., * the Language Switcher Block renders properly. * * @return void * @see wpmldev-2422 * @see wpmldev-2491 * */ public function maybeEnqueueNavigationBlockStyles() { if ( ! wp_style_is( 'wp-block-navigation', 'enqueued' ) || ! wp_style_is( 'wp-block-navigation', 'queue' ) ) { add_filter( 'render_block', function ( $blockContent, $block ) { if ( $block['blockName'] === LanguageSwitcher::BLOCK_LANGUAGE_SWITCHER ) { wp_enqueue_style( 'wp-block-navigation' ); } return $blockContent; }, 10, 2 ); } } /** * @return string[] */ public function getEditorDependencies() { global $pagenow; if ( is_admin() && 'widgets.php' === $pagenow ) { return [ 'wp-edit-widgets' ]; } return [ 'wp-editor' ]; } } cookie/class-wpml-cookie-admin-ui.php 0000755 00000003627 14720342453 0013577 0 ustar 00 <?php /** * Class WPML_Cookie_Admin_UI */ class WPML_Cookie_Admin_UI { const BOX_TEMPLATE = 'admin-cookie-box.twig'; const BUTTON_ID = 'js-wpml-store-frontend-cookie'; /** * @var WPML_Twig_Template */ private $template_service; /** * @var WPML_Cookie_Setting */ private $cookie_setting; /** * WPML_Cookie_Admin_UI constructor. * * @param WPML_Twig_Template $template_service * @param WPML_Cookie_Setting $cookie_setting */ public function __construct( WPML_Twig_Template $template_service, WPML_Cookie_Setting $cookie_setting ) { $this->template_service = $template_service; $this->cookie_setting = $cookie_setting; } public function add_hooks() { add_action( 'wpml_after_settings', array( $this, 'render_cookie_box' ) ); } public function render_cookie_box() { echo $this->template_service->show( $this->get_model(), self::BOX_TEMPLATE ); } /** * @return array */ private function get_model() { return array( 'strings' => array( 'title' => __( 'Language filtering for AJAX operations', 'sitepress' ), 'field_name' => WPML_Cookie_Setting::COOKIE_SETTING_FIELD, 'field_label' => __( 'Store a language cookie to support language filtering for AJAX', 'sitepress' ), 'tooltip' => __( 'Select this option if your theme or plugins use AJAX operations on the front-end, that WPML needs to filter. WPML will set a cookie using JavaScript which will allow it to return the correct content for AJAX operations.', 'sitepress' ), 'button_text' => __( 'Save', 'sitepress' ), 'button_id' => self::BUTTON_ID, ), 'ajax_response_id' => WPML_Cookie_Setting_Ajax::AJAX_RESPONSE_ID, 'nonce_field' => WPML_Cookie_Setting_Ajax::NONCE_COOKIE_SETTING, 'nonce_value' => wp_create_nonce( WPML_Cookie_Setting_Ajax::NONCE_COOKIE_SETTING ), 'checked' => checked( $this->cookie_setting->get_setting(), true, false ), ); } } cookie/class-wpml-cookie-admin-scripts.php 0000755 00000001546 14720342453 0014647 0 ustar 00 <?php /** * Class WPML_Cookie_Admin_Scripts */ class WPML_Cookie_Admin_Scripts { public function enqueue() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } public function enqueue_scripts() { wp_enqueue_style( 'wp-pointer' ); wp_enqueue_script( 'wpml-cookie-ajax-setting', ICL_PLUGIN_URL . '/res/js/cookies/cookie-ajax-setting.js', array( 'jquery', 'wp-pointer' ), ICL_SITEPRESS_VERSION ); wp_localize_script( 'wpml-cookie-ajax-setting', 'wpml_cookie_setting', array( 'nonce' => WPML_Cookie_Setting_Ajax::NONCE_COOKIE_SETTING, 'button_id' => WPML_Cookie_Admin_UI::BUTTON_ID, 'ajax_response_id' => WPML_Cookie_Setting_Ajax::AJAX_RESPONSE_ID, 'field_name' => WPML_Cookie_Setting::COOKIE_SETTING_FIELD, 'ajax_action' => WPML_Cookie_Setting_Ajax::ACTION, ) ); } } cookie/class-wpml-cookie-scripts.php 0000755 00000002067 14720342453 0013560 0 ustar 00 <?php /** * Class WPML_Cookie_Scripts */ class WPML_Cookie_Scripts { /** * @var string */ private $language_cookie_name; /** * @var string */ private $current_language; /** * WPML_Cookie_Scripts constructor. * * @param string $language_cookie_name * @param string $current_language */ public function __construct( $language_cookie_name, $current_language ) { $this->language_cookie_name = $language_cookie_name; $this->current_language = $current_language; } public function add_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ), - PHP_INT_MAX ); } public function enqueue_scripts() { wp_enqueue_script( 'wpml-cookie', ICL_PLUGIN_URL . '/res/js/cookies/language-cookie.js', array(), ICL_SITEPRESS_VERSION, false ); wp_script_add_data( 'wpml-cookie', 'strategy', 'defer' ); $cookies = array( $this->language_cookie_name => array( 'value' => $this->current_language, 'expires' => 1, 'path' => '/', ), ); wp_localize_script( 'wpml-cookie', 'wpml_cookies', $cookies ); } } cookie/class-wpml-cookie-setting-ajax.php 0000755 00000003045 14720342453 0014464 0 ustar 00 <?php /** * Class WPML_Frontend_Cookie_Setting_Ajax */ class WPML_Cookie_Setting_Ajax { const NONCE_COOKIE_SETTING = 'wpml-frontend-cookie-setting-nonce'; const AJAX_RESPONSE_ID = 'icl_ajx_response_cookie'; const ACTION = 'wpml_update_cookie_setting'; /** * @var WPML_Cookie_Setting */ private $wpml_frontend_cookie_setting; /** * WPML_Frontend_Cookie_Setting_Ajax constructor. * * @param WPML_Cookie_Setting $wpml_frontend_cookie_setting */ public function __construct( WPML_Cookie_Setting $wpml_frontend_cookie_setting ) { $this->wpml_frontend_cookie_setting = $wpml_frontend_cookie_setting; } public function add_hooks() { add_action( 'wp_ajax_wpml_update_cookie_setting', array( $this, 'update_cookie_setting' ) ); } public function update_cookie_setting() { if ( ! $this->is_valid_request() ) { wp_send_json_error(); } else { if ( array_key_exists( WPML_Cookie_Setting::COOKIE_SETTING_FIELD, $_POST ) ) { $store_frontend_cookie = filter_var( $_POST[ WPML_Cookie_Setting::COOKIE_SETTING_FIELD ], FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE ); $this->wpml_frontend_cookie_setting->set_setting( $store_frontend_cookie ); } else { $this->wpml_frontend_cookie_setting->set_setting( 0 ); } wp_send_json_success(); } } /** * @return bool */ private function is_valid_request() { $valid_request = false; if ( array_key_exists( 'nonce', $_POST ) ) { $valid_request = wp_verify_nonce( $_POST['nonce'], self::NONCE_COOKIE_SETTING ); } return $valid_request; } } cookie/class-wpml-cookie.php 0000755 00000003737 14720342453 0012100 0 ustar 00 <?php class WPML_Cookie { /** * @param string $name * @param string $value * @param int $expires * @param string $path * @param string $domain * @param bool $HTTPOnly * @param string|null $sameSite */ public function set_cookie( $name, $value, $expires, $path, $domain, $HTTPOnly = false, $sameSite = null ) { wp_cache_add_non_persistent_groups( __CLASS__ ); $entryHash = md5( (string) wp_json_encode( [ $name, $value, $path, $domain, $HTTPOnly, $sameSite ] ) ); if ( wp_cache_get( $name, __CLASS__ ) !== $entryHash ) { $this->handle_cache_plugins( $name ); if ($sameSite) { header( 'Set-Cookie: ' . rawurlencode( $name ) . '=' . rawurlencode( $value ) . ( $domain ? '; Domain=' . $domain : '' ) . ( $expires ? '; expires=' . gmdate( 'D, d-M-Y H:i:s', $expires ) . ' GMT' : '' ) . ( $path ? '; Path=' . $path : '' ) . ( $this->is_secure_connection() ? '; Secure' : '') . ( $HTTPOnly ? '; HttpOnly' : '' ) . '; SameSite=' . $sameSite, false ); } else { setcookie( $name, (string) $value, $expires, $path, $domain, $this->is_secure_connection(), $HTTPOnly ); } wp_cache_set( $name, $entryHash, __CLASS__ ); } } /** * @param string $name * * @return string */ public function get_cookie( $name ) { if ( isset( $_COOKIE[ $name ] ) ) { return $_COOKIE[ $name ]; } return ''; } /** * simple wrapper for \headers_sent * * @return bool */ public function headers_sent() { return headers_sent(); } /** * @param string $name */ private function handle_cache_plugins( $name ) { // @todo uncomment or delete when #wpmlcore-5796 is resolved // do_action( 'wpsc_add_cookie', $name ); } private function is_secure_connection() { if ( \WPML\FP\Obj::prop( 'HTTPS', $_SERVER ) === 'on' || \WPML\FP\Obj::prop( 'HTTP_X_FORWARDED_PROTO', $_SERVER ) === 'https' || \WPML\FP\Obj::prop( 'HTTP_X_FORWARDED_SSL', $_SERVER ) === 'on' ) { return true; } return false; } } cookie/class-wpml-cookie-setting.php 0000755 00000001275 14720342453 0013546 0 ustar 00 <?php /** * Class WPML_Frontend_Cookie_Setting */ class WPML_Cookie_Setting { const COOKIE_SETTING_FIELD = 'store_frontend_cookie'; /** * @var SitePress */ private $sitepress; /** * WPML_Frontend_Cookie_Setting constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * @return bool|mixed */ public function get_setting() { return $this->sitepress->get_setting( self::COOKIE_SETTING_FIELD ); } /** * @param mixed $value */ public function set_setting( $value ) { $this->sitepress->set_setting( self::COOKIE_SETTING_FIELD, $value ); $this->sitepress->save_settings(); } } templating/class-wpml-templates-factory.php 0000755 00000020377 14720342453 0015164 0 ustar 00 <?php use WPML\FP\Obj; use WPML\Core\Twig_Environment; use WPML\Core\Twig_Error_Syntax; abstract class WPML_Templates_Factory { const NOTICE_GROUP = 'template_factory'; const OTGS_TWIG_CACHE_DISABLED_KEY = '_otgs_twig_cache_disabled'; /* * List of tags and filters that are allowed in the sandbox mode. * Specifically excluded 'include' and 'import' tags. * Excluded the 'filter', 'reduce', 'map' filters. */ const SANDBOX_FUNCTIONS = [ 'attribute', 'block', 'constant', 'country_names', 'country_timezones', 'currency_names', 'cycle', 'date', 'html_classes', 'language_names', 'locale_names', 'max', 'min', 'parent', 'random', 'range', 'script_names', 'source', 'template_from_string', 'timezone_names' ]; const SANDBOX_TAGS = [ 'apply', 'autoescape', 'block', 'cache', 'do', 'embed', 'flush', 'for', 'from', 'if', 'macro', 'set', 'with' ]; const SANDBOX_FILTERS = ['abs', 'batch', 'capitalize', 'column', 'convert_encoding', 'country_name', 'currency_name', 'currency_symbol', 'data_uri', 'date', 'date_modify', 'default', 'escape', 'first', 'format','format_currency', 'format_date', 'format_datetime', 'format_number', 'format_time', 'html_to_markdown', 'inky_to_html', 'inline_css', 'join', 'json_encode', 'keys', 'language_name', 'last', 'length', 'locale_name', 'lower', 'markdown_to_html', 'merge', 'nl2br', 'number_format', 'replace', 'reverse', 'round', 'sort', 'spaceless', 'striptags', 'title', 'trim', 'upper', 'url_encode', 'url_decode', 'u', 'wordwrap' ]; /** @var array */ protected $custom_filters; /** @var array */ protected $custom_functions; /** @var string|array */ protected $template_paths; /** @var string|bool */ protected $cache_directory; protected $template_string; /** @var WPML_WP_API $wp_api */ private $wp_api; /** @var Twig_Environment */ protected $twig; /** @var Twig_Environment */ protected $sandboxTwig; /** * WPML_Templates_Factory constructor. * * @param array $custom_functions * @param array $custom_filters * @param WPML_WP_API $wp_api */ public function __construct( array $custom_functions = array(), array $custom_filters = array(), $wp_api = null ) { $this->init_template_base_dir(); $this->custom_functions = $custom_functions; $this->custom_filters = $custom_filters; if ( $wp_api ) { $this->wp_api = $wp_api; } } abstract protected function init_template_base_dir(); /** * @param ?string $template * @param ?array<string,mixed> $model * * @throws \WPML\Core\Twig\Error\LoaderError * @throws \WPML\Core\Twig\Error\RuntimeError * @throws \WPML\Core\Twig\Error\SyntaxError */ public function show( $template = null, $model = null ) { echo $this->get_view( $template, $model ); } public function get_sandbox_view( $template = null, $model = null ) { $output = ''; $this->maybe_init_sandbox_twig(); if ( null === $model ) { $model = $this->get_model(); } if ( null === $template ) { $template = $this->get_template(); } try { $output = $this->sandboxTwig->render( $template, $model ); } catch ( RuntimeException $e ) { if ( $this->is_caching_enabled() ) { $this->disable_twig_cache(); $this->sandboxTwig = null; $this->maybe_init_sandbox_twig(); $output = $this->get_sandbox_view( $template, $model ); } else { $this->add_exception_notice( $e ); } } catch ( Twig_Error_Syntax $e ) { $message = 'Invalid Twig template string: ' . $e->getRawMessage() . "\n" . $template; $this->get_wp_api()->error_log( $message ); } catch ( WPML\Core\Twig\Sandbox\SecurityNotAllowedFilterError $e ) { $this->get_wp_api()->error_log( $e->getMessage() ); } return $output; } /** * @param ?string $template * @param ?array<string,mixed> $model * * @return string * @throws \WPML\Core\Twig\Error\LoaderError * @throws \WPML\Core\Twig\Error\RuntimeError * @throws \WPML\Core\Twig\Error\SyntaxError */ public function get_view( $template = null, $model = null ) { $output = ''; $this->maybe_init_twig(); if ( null === $model ) { $model = $this->get_model(); } if ( null === $template ) { $template = $this->get_template(); } try { $output = $this->twig->render( $template, $model ); } catch ( RuntimeException $e ) { if ( $this->is_caching_enabled() ) { $this->disable_twig_cache(); $this->twig = null; $this->maybe_init_twig(); $output = $this->get_view( $template, $model ); } else { $this->add_exception_notice( $e ); } } catch ( Twig_Error_Syntax $e ) { $message = 'Invalid Twig template string: ' . $e->getRawMessage() . "\n" . $template; $this->get_wp_api()->error_log( $message ); } return $output; } protected function maybe_init_twig() { $this->_init_twig( false ); } protected function maybe_init_sandbox_twig() { $this->_init_twig( true ); } abstract public function get_template(); abstract public function get_model(); /** * @return Twig_Environment */ protected function get_twig() { return $this->twig; } /** * @param RuntimeException $e */ protected function add_exception_notice( RuntimeException $e ) { if ( false !== strpos( $e->getMessage(), 'create' ) ) { /* translators: %s: Cache directory path */ $text = sprintf( __( 'WPML could not create a cache directory in %s', 'sitepress' ), $this->cache_directory ); } else { /* translators: %s: Cache directory path */ $text = sprintf( __( 'WPML could not write in the cache directory: %s', 'sitepress' ), $this->cache_directory ); } $notice = new WPML_Notice( 'exception', $text, self::NOTICE_GROUP ); $notice->set_dismissible( true ); $notice->set_css_class_types( 'notice-error' ); $admin_notices = $this->get_wp_api()->get_admin_notices(); $admin_notices->add_notice( $notice ); } /** * @return WPML_WP_API */ protected function get_wp_api() { if ( ! $this->wp_api ) { $this->wp_api = new WPML_WP_API(); } return $this->wp_api; } protected function disable_twig_cache() { update_option( self::OTGS_TWIG_CACHE_DISABLED_KEY, true, 'no' ); } protected function is_caching_enabled() { return ! (bool) get_option( self::OTGS_TWIG_CACHE_DISABLED_KEY, false ); } /** * @return bool */ protected function is_string_template() { return isset( $this->template_string ); } /** * @return \WPML\Core\Twig_LoaderInterface */ protected function get_twig_loader() { if ( $this->is_string_template() ) { $loader = $this->get_wp_api()->get_twig_loader_string(); } else { $loader = $this->get_wp_api()->get_twig_loader_filesystem( $this->template_paths ); } return $loader; } protected function _init_twig( $sandbox = false ) { if ( ( ! $this->twig && ! $sandbox ) || ( ! $this->sandboxTwig && $sandbox ) ) { $loader = $this->get_twig_loader(); $environment_args = array(); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $environment_args['debug'] = true; } if ( $this->is_caching_enabled() ) { $wpml_cache_directory = new WPML_Cache_Directory( $this->get_wp_api() ); $this->cache_directory = $wpml_cache_directory->get( 'twig' ); if ( $this->cache_directory ) { $environment_args['cache'] = $this->cache_directory; $environment_args['auto_reload'] = true; } else { $this->disable_twig_cache(); } } $twig = $this->get_wp_api()->get_twig_environment( $loader, $environment_args ); if ( $this->custom_functions && count( $this->custom_functions ) > 0 ) { foreach ( $this->custom_functions as $custom_function ) { $twig->addFunction( $custom_function ); } } if ( $this->custom_filters && count( $this->custom_filters ) > 0 ) { foreach ( $this->custom_filters as $custom_filter ) { $twig->addFilter( $custom_filter ); } } if ( Obj::propOr( false, 'debug', $environment_args ) ) { $twig->addExtension( new \WPML\Core\Twig\Extension\DebugExtension() ); } if ( $sandbox && ( ! defined( 'WPML_LS_TEMPLATE_UNSAFE_MODE' ) || ! WPML_LS_TEMPLATE_UNSAFE_MODE ) ) { $policy = new \WPML\Core\Twig\Sandbox\SecurityPolicy( self::SANDBOX_TAGS, self::SANDBOX_FILTERS, [], [], self::SANDBOX_FUNCTIONS ); $twig->addExtension( new \WPML\Core\Twig\Extension\SandboxExtension( $policy, true ) ); } if ( $sandbox ) { $this->sandboxTwig = $twig; } else { $this->twig = $twig; } } } } class-wpml-tm-troubleshooting-clear-ts.php 0000755 00000004721 14720342453 0014725 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_TM_Troubleshooting_Clear_TS extends WPML_TM_AJAX_Factory_Obsolete { private $script_handle = 'wpml_clear_ts'; /** * WPML_TM_Troubleshooting_Clear_TS constructor. * * @param WPML_WP_API $wpml_wp_api */ public function __construct( &$wpml_wp_api ) { parent::__construct( $wpml_wp_api ); add_action( 'init', array( $this, 'load_action' ) ); $this->add_ajax_action( 'wp_ajax_wpml_clear_ts', array( $this, 'clear_ts_action' ) ); $this->init(); } public function clear_ts_action() { $action = Sanitize::stringProp( 'action', $_POST ); $wpml_clear_ts_nonce = Sanitize::stringProp( 'nonce', $_POST ); if ( $action && $wpml_clear_ts_nonce && wp_verify_nonce( $wpml_clear_ts_nonce, $action ) ) { $this->clear_tp_default_suid(); return $this->wpml_wp_api->wp_send_json_success( __( 'Ok!', 'wpml-translation-management' ) ); } else { return $this->wpml_wp_api->wp_send_json_error( __( "You can't do that!", 'wpml-translation-management' ) ); } } protected function clear_tp_default_suid() { TranslationProxy::clear_preferred_translation_service(); } public function enqueue_resources( $hook_suffix ) { if ( $this->wpml_wp_api->is_troubleshooting_page() ) { $this->register_resources(); $strings = array( 'placeHolder' => $this->script_handle, 'action' => $this->script_handle, 'nonce' => wp_create_nonce( $this->script_handle ), ); wp_localize_script( $this->script_handle, $this->script_handle . '_strings', $strings ); wp_enqueue_script( $this->script_handle ); } } public function register_resources() { wp_register_script( $this->script_handle, WPML_TM_URL . '/res/js/clear-preferred-ts.js', array( 'jquery', 'jquery-ui-dialog' ), false, true ); } public function load_action() { $page = Sanitize::stringProp( 'page', $_GET ); $should_proceed = ! $this->wpml_wp_api->is_heartbeat() && ! $this->wpml_wp_api->is_ajax() && ! $this->wpml_wp_api->is_cron_job() && $page && strpos( $page, 'sitepress-multilingual-cms/menu/troubleshooting.php' ) === 0; if ( $should_proceed && TranslationProxy::get_tp_default_suid() ) { $this->add_hooks(); } } private function add_hooks() { add_action( 'after_setup_complete_troubleshooting_functions', array( $this, 'render_ui' ) ); } public function render_ui() { if ( TranslationProxy::get_tp_default_suid() ) { $clear_ts = new WPML_TM_Troubleshooting_Clear_TS_UI(); $clear_ts->show(); } } } jobs/class-wpml-tm-jobs-date-range.php 0000755 00000001574 14720342453 0013670 0 ustar 00 <?php class WPML_TM_Jobs_Date_Range { /** @var DateTime|null */ private $begin; /** @var DateTime|null */ private $end; /** * Specify how we should treat date values which are NULL * * @var bool */ private $include_null_date; /** * @param DateTime|null $begin * @param DateTime|null $end * @param bool $include_null_date */ public function __construct( DateTime $begin = null, DateTime $end = null, $include_null_date = false ) { $this->begin = $begin; $this->end = $end; $this->include_null_date = (bool) $include_null_date; } /** * @return DateTime|null */ public function get_begin() { return $this->begin; } /** * @return DateTime|null */ public function get_end() { return $this->end; } /** * @return bool */ public function is_include_null_date() { return $this->include_null_date; } } jobs/class-wpml-tm-jobs-sorting-param.php 0000755 00000001164 14720342453 0014437 0 ustar 00 <?php class WPML_TM_Jobs_Sorting_Param { /** @var string */ private $column; /** @var string */ private $direction; /** * @param string $column * @param string $direction */ public function __construct( $column, $direction = 'asc' ) { $direction = strtolower( $direction ); if ( 'asc' !== $direction && 'desc' !== $direction ) { $direction = 'asc'; } $this->column = $column; $this->direction = $direction; } /** * @return string */ public function get_column() { return $this->column; } /** * @return string */ public function get_direction() { return $this->direction; } } jobs/class-wpml-tm-post-job-entity.php 0000755 00000010051 14720342453 0013763 0 ustar 00 <?php class WPML_TM_Post_Job_Entity extends WPML_TM_Job_Entity { /** @var WPML_TM_Job_Element_Entity[]|callable */ private $elements; /** @var int */ private $translate_job_id; /** @var string */ private $editor; /** @var int */ private $editor_job_id; /** @var null|DateTime */ private $completed_date; /** @var bool */ private $automatic; /** @var null|string */ private $review_status = null; /** @var int */ private $trid; /** @var string */ private $element_type; /** @var int */ private $element_id; /** @var string */ private $element_type_prefix; /** @var string */ private $job_title; public function __construct( $id, $type, $tp_id, $batch, $status, $elements ) { parent::__construct( $id, $type, $tp_id, $batch, $status ); if ( is_callable( $elements ) ) { $this->elements = $elements; } elseif ( is_array( $elements ) ) { foreach ( $elements as $element ) { if ( $element instanceof WPML_TM_Job_Element_Entity ) { $this->elements[] = $element; } } } } /** * @return WPML_TM_Job_Element_Entity[] */ public function get_elements() { if ( is_callable( $this->elements ) ) { return call_user_func( $this->elements, $this ); } elseif ( is_array( $this->elements ) ) { return $this->elements; } else { return array(); } } /** * @return int */ public function get_translate_job_id() { return $this->translate_job_id; } /** * @param int $translate_job_id */ public function set_translate_job_id( $translate_job_id ) { $this->translate_job_id = (int) $translate_job_id; } /** * @return string */ public function get_editor() { return $this->editor; } /** * @param string $editor */ public function set_editor( $editor ) { $this->editor = (string) $editor; } /** * @return int */ public function get_editor_job_id() { return $this->editor_job_id; } /** * @param int $editor_job_id */ public function set_editor_job_id( $editor_job_id ) { $this->editor_job_id = (int) $editor_job_id; } /** * @return bool */ public function is_ate_job() { return 'local' === $this->get_translation_service() && $this->is_ate_editor(); } /** * @return bool */ public function is_ate_editor() { return WPML_TM_Editors::ATE === $this->get_editor(); } /** * @return DateTime|null */ public function get_completed_date() { return $this->completed_date; } /** * @param DateTime|null $completed_date */ public function set_completed_date( DateTime $completed_date = null ) { $this->completed_date = $completed_date; } /** * @return bool */ public function is_automatic() { return $this->automatic; } /** * @param bool $automatic */ public function set_automatic( $automatic ) { $this->automatic = (bool) $automatic; } /** * @return string|null */ public function get_review_status() { return $this->review_status; } /** * @param string|null $review_status */ public function set_review_status( $review_status ) { $this->review_status = $review_status; } /** * @return int */ public function get_trid() { return $this->trid; } /** * @param int $trid */ public function set_trid( $trid ) { $this->trid = $trid; } /** * @return string */ public function get_element_type() { return $this->element_type; } /** * @param string $element_type */ public function set_element_type( $element_type ) { $this->element_type = $element_type; } /** * @return int */ public function get_element_id() { return $this->element_id; } /** * @param int $element_id */ public function set_element_id( $element_id ) { $this->element_id = $element_id; } public function get_element_type_prefix() { return $this->element_type_prefix; } public function set_element_type_prefix( $element_type ) { $this->element_type_prefix = explode( '_', $element_type )[0]; } /** * @return string */ public function get_job_title() { return $this->job_title; } /** * @param string $job_title */ public function set_job_title( $job_title ) { $this->job_title = $job_title; } } jobs/class-wpml-tm-jobs-needs-update-param.php 0000755 00000001607 14720342453 0015332 0 ustar 00 <?php class WPML_TM_Jobs_Needs_Update_Param { const INCLUDE_NEEDS_UPDATE = 'include'; const EXCLUDE_NEEDS_UPDATE = 'exclude'; /** @var string */ private $value; /** * @param string $value */ public function __construct( $value ) { if ( ! self::is_valid( $value ) ) { throw new \InvalidArgumentException( 'Invalid value of NEEDS_UPDATE param: ' . $value ); } $this->value = $value; } /** * @return bool */ public function is_needs_update_excluded() { return $this->value === self::EXCLUDE_NEEDS_UPDATE; } /** * @return bool */ public function is_needs_update_included() { return $this->value === self::INCLUDE_NEEDS_UPDATE; } /** * @param string $value * * @return bool */ public static function is_valid( $value ) { return in_array( (string) $value, [ self::INCLUDE_NEEDS_UPDATE, self::EXCLUDE_NEEDS_UPDATE, ], true ); } } jobs/class-wpml-tm-job-ts-status.php 0000755 00000001274 14720342453 0013442 0 ustar 00 <?php class WPML_TM_Job_TS_Status { /** @var string */ private $status; /** @var array */ private $links = array(); /** * WPML_TM_Job_TS_Status constructor. * * @param string $status * @param array $links */ public function __construct( $status, $links ) { $this->status = $status; $this->links = $links; } /** * @return string */ public function get_status() { return $this->status; } /** * @return array */ public function get_links() { return $this->links; } public function __toString() { if ( $this->status ) { return wp_json_encode( array( 'status' => $this->status, 'links' => $this->links, ) ); } return ''; } } jobs/class-wpml-tm-jobs-search-params.php 0000755 00000030025 14720342453 0014400 0 ustar 00 <?php class WPML_TM_Jobs_Search_Params { const SCOPE_REMOTE = 'remote'; const SCOPE_LOCAL = 'local'; const SCOPE_ALL = 'all'; const SCOPE_ATE = 'ate'; private static $scopes = array( self::SCOPE_LOCAL, self::SCOPE_REMOTE, self::SCOPE_ALL, self::SCOPE_ATE, ); /** @var array */ private $status = array(); /** @var WPML_TM_Jobs_Needs_Update_Param|null */ private $needs_update; /** @var string */ private $scope = self::SCOPE_ALL; /** @var array */ private $job_types = array(); /** @var int[] */ private $local_job_ids; /** @var int */ private $limit; /** @var int */ private $offset; /** @var int */ private $id; /** @var int[] */ private $ids; /** @var string[] */ private $title; /** @var string[] */ private $batch_name; /** @var string */ private $source_language; /** @var string[] */ private $target_language; /** @var array */ private $tp_id = ''; /** @var WPML_TM_Jobs_Sorting_Param[] */ private $sorting = array(); /** @var int */ private $translated_by; /** @var WPML_TM_Jobs_Date_Range */ private $deadline; /** @var WPML_TM_Jobs_Date_Range */ private $sent; /** @var WPML_TM_Jobs_Date_Range */ private $completed_date; /** @var int */ private $original_element_id; /** @var bool|null */ private $needs_review = null; /** @var bool */ private $exclude_hidden_jobs = true; /** @var int */ private $max_ate_retries; /** @var bool */ private $exclude_manual = false; /** @var bool */ private $exclude_longstanding = false; /** @var bool */ private $exclude_cancelled = false; /** * Corresponds with `wp_icl_translations.element_type` column * * @var string */ private $element_type; /** @var null|array */ private $columns_to_select = null; /** @var array */ private $custom_where_conditions = []; public function __construct( array $params = array() ) { if ( array_key_exists( 'limit', $params ) ) { $this->set_limit( $params['limit'] ); if ( array_key_exists( 'offset', $params ) ) { $this->set_offset( $params['offset'] ); } } $fields = array( 'status', 'scope', 'job_types', 'local_job_id', 'id', 'ids', 'title', 'batch_name', 'source_language', 'target_language', 'sorting', 'tp_id', 'translated_by', 'deadline', 'completed_date', 'sent', 'original_element_id', 'exclude_manual', 'exclude_longstanding', ); foreach ( $fields as $field ) { if ( array_key_exists( $field, $params ) ) { $this->{'set_' . $field}( $params[ $field ] ); } } } /** * @return array */ public function get_status() { return $this->status; } /** * @param array $status * * @return self */ public function set_status( array $status ) { $this->status = array_map( 'intval', array_values( array_filter( $status, 'is_numeric' ) ) ); return $this; } /** * @return string */ public function get_scope() { return $this->scope; } /** * @return array */ public function get_tp_id() { return $this->tp_id; } /** * @param string $scope * * @retun self */ public function set_scope( $scope ) { if ( ! $this->is_valid_scope( $scope ) ) { throw new InvalidArgumentException( 'Invalid scope. Accepted values: ' . implode( ', ', self::$scopes ) ); } $this->scope = $scope; return $this; } /** * @return array */ public function get_job_types() { return $this->job_types; } /** * @param int|array $tp_id * * @return $this */ public function set_tp_id( $tp_id ) { $this->tp_id = is_array( $tp_id ) ? $tp_id : array( $tp_id ); return $this; } /** * @param string|array $job_types * * @return self */ public function set_job_types( $job_types ) { $correct_types = [ WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE, WPML_TM_Job_Entity::STRING_TYPE, WPML_TM_Job_Entity::STRING_BATCH, ]; if ( ! is_array( $job_types ) ) { $job_types = array( $job_types ); } foreach ( $job_types as $job_type ) { if ( ! in_array( $job_type, $correct_types, true ) ) { throw new InvalidArgumentException( 'Invalid job type' ); } $this->job_types[] = $job_type; } return $this; } /** * @return int|null */ public function get_first_local_job_id() { return ! empty( $this->local_job_ids ) ? current( $this->local_job_ids ) : null; } /** * @return int[] */ public function get_local_job_ids() { return $this->local_job_ids; } /** * @param int $local_job_id * * @return self */ public function set_local_job_id( $local_job_id ) { $this->local_job_ids[] = (int) $local_job_id; return $this; } /** * @param int[] $local_job_ids * * @return self */ public function set_local_job_ids( array $local_job_ids ) { $this->local_job_ids = array_map( 'intval', $local_job_ids ); return $this; } /** * @return int */ public function get_limit() { return $this->limit; } /** * @param int $limit * * @return self */ public function set_limit( $limit ) { $this->limit = $limit; return $this; } /** * @return int */ public function get_offset() { return $this->offset; } /** * @param int $offset * * @return self */ public function set_offset( $offset ) { $this->offset = $offset; return $this; } /** * @return int */ public function get_id() { return $this->id; } /** * @param int $id * * @return self */ public function set_id( $id ) { $this->id = $id; return $this; } /** * @return int[] */ public function get_ids() { return $this->ids; } /** * @param int[] $ids * * @return self */ public function set_ids( array $ids ) { $this->ids = array_map( 'intval', $ids ); return $this; } /** * @return string[] */ public function get_title() { return $this->title; } /** * @param array|string $title * * @return self */ public function set_title( $title ) { $this->title = is_array( $title ) ? $title : array( $title ); return $this; } /** * @return string[] */ public function get_batch_name() { return $this->batch_name; } /** * @param string[] $batch_name */ public function set_batch_name( $batch_name ) { $this->batch_name = $batch_name; } /** * @return string */ public function get_source_language() { return $this->source_language; } /** * @param string $source_language * * @return self */ public function set_source_language( $source_language ) { $this->source_language = $source_language; return $this; } /** * @return string[] */ public function get_target_language() { return $this->target_language; } /** * @param array|string $target_language * * @return self */ public function set_target_language( $target_language ) { $this->target_language = is_array( $target_language ) ? $target_language : array( $target_language ); return $this; } /** * @return WPML_TM_Jobs_Sorting_Param[] */ public function get_sorting() { return $this->sorting; } /** * @param WPML_TM_Jobs_Sorting_Param[] $sorting * * @return self */ public function set_sorting( array $sorting ) { $this->sorting = array(); foreach ( $sorting as $sorting_param ) { if ( $sorting_param instanceof WPML_TM_Jobs_Sorting_Param ) { $this->sorting[] = $sorting_param; } } return $this; } /** * @return int */ public function get_translated_by() { return $this->translated_by; } /** * @param int|null $translated_by * * @return self */ public function set_translated_by( $translated_by ) { $this->translated_by = (int) $translated_by; return $this; } /** * @return WPML_TM_Jobs_Date_Range */ public function get_deadline() { return $this->deadline; } /** * @param WPML_TM_Jobs_Date_Range $deadline * * @return self */ public function set_deadline( WPML_TM_Jobs_Date_Range $deadline ) { $this->deadline = $deadline; return $this; } /** * @return WPML_TM_Jobs_Date_Range */ public function get_sent() { return $this->sent; } /** * @return WPML_TM_Jobs_Date_Range */ public function get_completed_date() { return $this->completed_date; } /** * @return int */ public function get_original_element_id() { return $this->original_element_id; } /** * @param WPML_TM_Jobs_Date_Range $sent * * @return self */ public function set_sent( WPML_TM_Jobs_Date_Range $sent ) { $this->sent = $sent; return $this; } /** * @param WPML_TM_Jobs_Date_Range $completed_date * * @return self */ public function set_completed_date( WPML_TM_Jobs_Date_Range $completed_date ) { $this->completed_date = $completed_date; return $this; } /** * @param int $original_element_id * * @return $this */ public function set_original_element_id( $original_element_id ) { $this->original_element_id = $original_element_id; return $this; } /** * @return WPML_TM_Jobs_Needs_Update_Param|null */ public function get_needs_update() { return $this->needs_update; } /** * @param WPML_TM_Jobs_Needs_Update_Param|null $needs_update * * @return $this */ public function set_needs_update( WPML_TM_Jobs_Needs_Update_Param $needs_update = null ) { $this->needs_update = $needs_update; return $this; } /** * @return bool */ public function needs_review() { return $this->needs_review; } /** * @return bool */ public function exclude_hidden_jobs() { return $this->exclude_hidden_jobs; } /** * @param int $max_ate_retries * * @return $this */ public function set_max_ate_retries( $max_ate_retries ) { $this->max_ate_retries = $max_ate_retries; return $this; } /** * @return int */ public function get_max_ate_retries() { return $this->max_ate_retries; } /** * If value is set to NULL then the filter is ignored * If value is true then we INCLUDE needs review jobs * If value is false then we EXCLUDE needs review jobs * * @param bool|null $needs_review */ public function set_needs_review( $needs_review = true ) { $this->needs_review = $needs_review; return $this; } /** * @param bool $exclude_hidden_jobs */ public function set_exclude_hidden_jobs( $exclude_hidden_jobs ) { $this->exclude_hidden_jobs = $exclude_hidden_jobs; return $this; } /** * @param mixed $value * * @return bool */ public static function is_valid_scope( $value ) { return in_array( $value, self::$scopes, true ); } /** * @param bool $excludeManual */ public function set_exclude_manual( $excludeManual ) { $this->exclude_manual = $excludeManual; } /** * @return bool */ public function should_exclude_manual() { return $this->exclude_manual; } /** * @param bool $excludeLongstanding */ public function set_exclude_longstanding( $excludeLongstanding ) { $this->exclude_longstanding = $excludeLongstanding; } /** * @return bool */ public function should_exclude_longstanding() { return $this->exclude_longstanding; } /** * @return array|null */ public function get_columns_to_select() { return $this->columns_to_select; } /** * @param array|null $columns_to_select * * @retun $this */ public function set_columns_to_select( $columns_to_select ) { $this->columns_to_select = $columns_to_select; return $this; } /** * @return string */ public function get_element_type() { return $this->element_type; } /** * @param string $element_type * * @retrun $this */ public function set_element_type( $element_type ) { $this->element_type = $element_type; return $this; } /** * @return bool */ public function should_exclude_cancelled() { return $this->exclude_cancelled; } /** * @param bool $exclude_cancelled * * @return $this */ public function set_exclude_cancelled( $exclude_cancelled = true ) { $this->exclude_cancelled = $exclude_cancelled; return $this; } /** * @return array */ public function get_custom_where_conditions() { return $this->custom_where_conditions; } /** * @param array $custom_where_conditions * * @retun $this */ public function set_custom_where_conditions( $custom_where_conditions ) { $this->custom_where_conditions = $custom_where_conditions; return $this; } } jobs/query/AbstractQuery.php 0000755 00000022675 14720342453 0012173 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use wpdb; use WPML_TM_Editors; use WPML_TM_Jobs_Search_Params; use WPML\TM\ATE\Jobs; abstract class AbstractQuery implements Query { /** @var wpdb */ protected $wpdb; /** @var QueryBuilder */ protected $query_builder; /** @var string */ protected $title_column = 'posts.post_title'; /** @var string */ protected $batch_name_column = 'batches.batch_name'; /** * @param wpdb $wpdb * @param QueryBuilder $query_builder */ public function __construct( wpdb $wpdb, QueryBuilder $query_builder ) { $this->wpdb = $wpdb; $this->query_builder = $query_builder; } public function get_data_query( WPML_TM_Jobs_Search_Params $params ) { $has_completed_translation_subquery = " SELECT COUNT(job_id) FROM {$this->wpdb->prefix}icl_translate_job as copmpleted_translation_job WHERE copmpleted_translation_job.rid = translation_status.rid AND copmpleted_translation_job.translated = 1 "; if ( $params->get_columns_to_select() ) { $columns = $params->get_columns_to_select(); } else { $columns = [ 'translation_status.rid AS id', "'" . $this->get_type() . "' AS type", 'translation_status.tp_id AS tp_id', 'batches.id AS local_batch_id', 'batches.tp_id AS tp_batch_id', $this->batch_name_column, 'translation_status.status AS status', 'original_translations.element_id AS original_element_id', 'translations.source_language_code AS source_language', 'translations.language_code AS target_language', 'translations.trid', 'translations.element_type', 'translations.element_id', 'translation_status.translation_service AS translation_service', 'translation_status.timestamp AS sent_date', 'translate_job.deadline_date AS deadline_date', 'translate_job.completed_date AS completed_date', "{$this->title_column} AS title", 'source_languages.english_name AS source_language_name', 'target_languages.english_name AS target_language_name', 'translate_job.translator_id AS translator_id', 'translate_job.job_id AS translate_job_id', 'translation_status.tp_revision AS revision', 'translation_status.ts_status AS ts_status', 'translation_status.needs_update AS needs_update', 'translate_job.editor AS editor', "({$has_completed_translation_subquery}) > 0 AS has_completed_translation", 'translate_job.editor_job_id AS editor_job_id', 'translate_job.automatic AS automatic', 'translate_job.title AS job_title', 'translation_status.review_status AS review_status', ]; } return $this->build_query( $params, $columns ); } public function get_count_query( WPML_TM_Jobs_Search_Params $params ) { $columns = array( 'COUNT(translation_status.rid)' ); return $this->build_query( $params, $columns ); } /** * @param WPML_TM_Jobs_Search_Params $params * @param array $columns * * @return string */ protected function build_query( WPML_TM_Jobs_Search_Params $params, array $columns ) { if ( $this->check_job_type( $params ) ) { return ''; } $query_builder = clone $this->query_builder; $query_builder->set_columns( $columns ); $query_builder->set_from( "{$this->wpdb->prefix}icl_translation_status translation_status" ); $this->define_joins( $query_builder ); $this->define_filters( $query_builder, $params ); $query_builder->set_limit( $params ); $query_builder->set_order( $params ); return $query_builder->build(); } /** * @param WPML_TM_Jobs_Search_Params $params * * @return bool */ protected function check_job_type( WPML_TM_Jobs_Search_Params $params ) { return $params->get_job_types() && ! in_array( $this->get_type(), $params->get_job_types(), true ); } /** * @return string */ abstract protected function get_type(); protected function define_joins( QueryBuilder $query_builder ) { $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_translations translations ON translations.translation_id = translation_status.translation_id" ); $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_translations original_translations ON original_translations.trid = translations.trid AND original_translations.language_code = translations.source_language_code" ); $subquery = " SELECT * FROM {$this->wpdb->prefix}icl_translate_job as translate_job WHERE job_id = ( SELECT MAX(job_id) AS job_id FROM {$this->wpdb->prefix}icl_translate_job as sub_translate_job WHERE sub_translate_job.rid = translate_job.rid ) "; $query_builder->add_join( "INNER JOIN ({$subquery}) AS translate_job ON translate_job.rid = translation_status.rid" ); $this->add_resource_join( $query_builder ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_languages source_languages ON source_languages.code = translations.source_language_code" ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_languages target_languages ON target_languages.code = translations.language_code" ); $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_translation_batches batches ON batches.id = translation_status.batch_id" ); } abstract protected function add_resource_join( QueryBuilder $query_builder ); protected function define_filters( QueryBuilder $query_builder, WPML_TM_Jobs_Search_Params $params ) { $this->set_status_filter( $query_builder, $params ); $query_builder = $this->set_scope_filter( $query_builder, $params ); $query_builder->set_multi_value_text_filter( $this->title_column, $params->get_title() ); $query_builder->set_multi_value_text_filter( $this->batch_name_column, $params->get_batch_name() ); $query_builder->set_source_language( 'translations.source_language_code', $params ); $query_builder->set_target_language( 'translations.language_code', $params ); $query_builder->set_translated_by_filter( 'translate_job.translator_id', 'translation_status.translation_service', $params ); if ( $params->get_sent() ) { $query_builder->set_date_range( 'translation_status.timestamp', $params->get_sent() ); } if ( $params->get_deadline() ) { $query_builder->set_date_range( 'translate_job.deadline_date', $params->get_deadline() ); } if ( $params->get_completed_date() ) { $query_builder->set_date_range( 'translate_job.completed_date', $params->get_completed_date() ); } $query_builder->set_numeric_value_filter( 'translation_status.rid', $params->get_id() ); $query_builder->set_numeric_value_filter( 'translation_status.rid', $params->get_ids() ); $query_builder->set_numeric_value_filter( 'translation_status.rid', $params->get_local_job_ids() ); $query_builder->set_numeric_value_filter( 'original_translations.element_id', $params->get_original_element_id() ); $query_builder->set_tp_id_filter( 'translation_status.tp_id', $params ); if ( $params->needs_review() ) { $query_builder->set_needs_review(); } if ( $params->should_exclude_manual() ) { $query_builder->set_automatic(); } if ( $params->should_exclude_longstanding() ) { $query_builder->set_max_ate_sync_count( Jobs::LONGSTANDING_AT_ATE_SYNC_COUNT ); } if ( $params->get_max_ate_retries() ) { $query_builder->set_max_retries( $params->get_max_ate_retries() ); } if ( $params->exclude_hidden_jobs() ) { $query_builder->set_set_excluded_jobs(); } if ( $params->get_element_type() ) { $query_builder->set_element_type( $params->get_element_type() ); } if ( $params->get_custom_where_conditions() ) { foreach ( $params->get_custom_where_conditions() as $whereCondition ) { $query_builder->add_AND_where_condition( $whereCondition ); } } } private function set_status_filter( QueryBuilder $query_builder, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_needs_update() ) { $statuses = array_diff( $params->get_status(), [ ICL_TM_NEEDS_UPDATE ] ); if ( $params->get_needs_update()->is_needs_update_excluded() ) { $query_builder->add_AND_where_condition( 'translation_status.needs_update != 1' ); if ( $statuses ) { $query_builder->set_status_filter( 'translation_status.status', $params ); } } else { if ( $statuses ) { $statuses = wpml_prepare_in( $params->get_status(), '%d' ); $statuses = sprintf( 'translation_status.status IN (%s)', $statuses ); $query_builder->add_AND_where_condition( "( translation_status.needs_update = 1 AND {$statuses} )" ); } else { $query_builder->add_AND_where_condition( 'translation_status.needs_update = 1' ); } } } else { $query_builder->set_status_filter( 'translation_status.status', $params ); } if ( $params->should_exclude_cancelled() ) { $query_builder->add_AND_where_condition( 'translation_status.status != 0' ); } } private function set_scope_filter( QueryBuilder $query_builder, WPML_TM_Jobs_Search_Params $params ) { switch ( $params->get_scope() ) { case WPML_TM_Jobs_Search_Params::SCOPE_LOCAL: $query_builder->add_AND_where_condition( "translation_status.translation_service = 'local'" ); break; case WPML_TM_Jobs_Search_Params::SCOPE_REMOTE: $query_builder->add_AND_where_condition( "translation_status.translation_service != 'local'" ); break; case WPML_TM_Jobs_Search_Params::SCOPE_ATE: $query_builder->add_AND_where_condition( "translation_status.translation_service = 'local'" ); $query_builder->add_AND_where_condition( $this->wpdb->prepare( 'translate_job.editor = %s', WPML_TM_Editors::ATE ) ); break; } return $query_builder; } } jobs/query/PackageQuery.php 0000755 00000001127 14720342453 0011750 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use WPML_TM_Job_Entity; class PackageQuery extends PostQuery { /** @var string */ protected $title_column = 'string_packages.title'; protected function add_resource_join( QueryBuilder $query_builder ) { $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_string_packages string_packages ON string_packages.ID = original_translations.element_id" ); $query_builder->add_AND_where_condition( "original_translations.element_type LIKE 'package%'" ); } protected function get_type() { return WPML_TM_Job_Entity::PACKAGE_TYPE; } } jobs/query/CompositeQuery.php 0000755 00000010505 14720342453 0012357 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use \InvalidArgumentException; use \WPML_TM_Jobs_Search_Params; use \RuntimeException; class CompositeQuery implements Query { const METHOD_UNION = 'union'; const METHOD_COUNT = 'count'; /** * Job queries * * @var Query[] */ private $queries; /** * Limit query helper * * @var LimitQueryHelper */ private $limit_query_helper; /** * Order query helper * * @var OrderQueryHelper */ private $order_query_helper; /** * @param Query[] $queries Job queries. * @param LimitQueryHelper $limit_helper Limit helper. * @param OrderQueryHelper $order_helper Order helper. * * @throws InvalidArgumentException In case of error. */ public function __construct( array $queries, LimitQueryHelper $limit_helper, OrderQueryHelper $order_helper ) { $queries = array_filter( $queries, array( $this, 'is_query_valid' ) ); if ( empty( $queries ) ) { throw new InvalidArgumentException( 'Collection of sub-queries is empty or contains only invalid elements' ); } $this->queries = $queries; $this->limit_query_helper = $limit_helper; $this->order_query_helper = $order_helper; } /** * Get data query * * @param WPML_TM_Jobs_Search_Params $params Job search params. * * @throws InvalidArgumentException In case of error. * @return string */ public function get_data_query( WPML_TM_Jobs_Search_Params $params ) { if ( ! $params->get_job_types() ) { // We are merging subqueries here, that's why LIMIT must be applied to final query. $params_without_pagination_and_sorting = clone $params; $params_without_pagination_and_sorting->set_limit( 0 )->set_offset( 0 ); $params_without_pagination_and_sorting->set_sorting( array() ); $query = $this->get_sql( $params_without_pagination_and_sorting, self::METHOD_UNION ); $order = $this->order_query_helper->get_order( $params ); if ( $order ) { $query .= ' ' . $order; } $limit = $this->limit_query_helper->get_limit( $params ); if ( $limit ) { $query .= ' ' . $limit; } return $query; } else { return $this->get_sql( $params, self::METHOD_UNION ); } } /** * Get count query * * @param WPML_TM_Jobs_Search_Params $params Job search params. * * @return int|string */ public function get_count_query( WPML_TM_Jobs_Search_Params $params ) { $params_without_pagination_and_sorting = clone $params; $params_without_pagination_and_sorting->set_limit( 0 )->set_offset( 0 ); $params_without_pagination_and_sorting->set_sorting( array() ); return $this->get_sql( $params_without_pagination_and_sorting, self::METHOD_COUNT ); } /** * Get SQL request string * * @param WPML_TM_Jobs_Search_Params $params Job search params. * @param string $method Query method. * * @throws InvalidArgumentException In case of error. * @throws RuntimeException In case of error. * @return string */ private function get_sql( WPML_TM_Jobs_Search_Params $params, $method ) { switch ( $method ) { case self::METHOD_UNION: $query_method = 'get_data_query'; break; case self::METHOD_COUNT: $query_method = 'get_count_query'; break; default: throw new InvalidArgumentException( 'Invalid method argument: ' . $method ); } $parts = array(); foreach ( $this->queries as $query ) { $query_string = $query->$query_method( $params ); if ( $query_string ) { $parts[] = $query_string; } } if ( ! $parts ) { throw new RuntimeException( 'None of subqueries matches to specified search parameters' ); } if ( 1 === count( $parts ) ) { return current( $parts ); } switch ( $method ) { case self::METHOD_UNION: return $this->get_union( $parts ); case self::METHOD_COUNT: return $this->get_count( $parts ); } return null; } /** * Get union * * @param array $parts Query parts. * * @return string */ private function get_union( array $parts ) { return '( ' . implode( ' ) UNION ( ', $parts ) . ' )'; } /** * Get count * * @param array $parts Query parts. * * @return string */ private function get_count( array $parts ) { return 'SELECT ( ' . implode( ' ) + ( ', $parts ) . ' )'; } /** * Is query valid * * @param mixed $query SQL query. * * @return bool */ private function is_query_valid( $query ) { return $query instanceof Query; } } jobs/query/LimitQueryHelper.php 0000755 00000001004 14720342453 0012625 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use \WPML_TM_Jobs_Search_Params; class LimitQueryHelper { /** * @param WPML_TM_Jobs_Search_Params $params * * @return string */ public function get_limit( WPML_TM_Jobs_Search_Params $params ) { $result = ''; if ( $params->get_limit() ) { if ( $params->get_offset() ) { $result = sprintf( 'LIMIT %d, %d', $params->get_offset(), $params->get_limit() ); } else { $result = sprintf( 'LIMIT %d', $params->get_limit() ); } } return $result; } } jobs/query/Query.php 0000755 00000000607 14720342453 0010476 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use \WPML_TM_Jobs_Search_Params; interface Query { /** * @param WPML_TM_Jobs_Search_Params $params * * @return string */ public function get_data_query( WPML_TM_Jobs_Search_Params $params ); /** * @param WPML_TM_Jobs_Search_Params $params * * @return int */ public function get_count_query( WPML_TM_Jobs_Search_Params $params ); } jobs/query/StringsBatchQuery.php 0000755 00000001160 14720342453 0013005 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use WPML_TM_Job_Entity; class StringsBatchQuery extends AbstractQuery { /** @var string */ protected $title_column = 'translation_batches.batch_name'; protected function add_resource_join( QueryBuilder $query_builder ) { $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_translation_batches translation_batches ON translation_batches.id = original_translations.element_id" ); $query_builder->add_AND_where_condition( "original_translations.element_type = 'st-batch_strings'" ); } protected function get_type() { return WPML_TM_Job_Entity::STRING_BATCH; } } jobs/query/QueryBuilder.php 0000755 00000020211 14720342453 0011776 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use \wpdb; use WPML\TM\ATE\Review\ReviewStatus; use \WPML_TM_Jobs_Search_Params; use \WPML_TM_Jobs_Date_Range; use \InvalidArgumentException; class QueryBuilder { /** @var wpdb */ private $wpdb; /** @var LimitQueryHelper */ protected $limit_helper; /** @var OrderQueryHelper */ protected $order_helper; /** @var array */ private $columns = array(); /** @var string */ private $from; /** @var array */ private $joins = array(); /** @var array */ private $where = array(); /** @var string */ private $order; /** @var string */ private $limit; /** * @param LimitQueryHelper $limit_helper * @param OrderQueryHelper $order_helper */ public function __construct( LimitQueryHelper $limit_helper, OrderQueryHelper $order_helper ) { global $wpdb; $this->wpdb = $wpdb; $this->limit_helper = $limit_helper; $this->order_helper = $order_helper; } /** * @param array $columns * * @return self */ public function set_columns( array $columns ) { $this->columns = $columns; return $this; } /** * @param $column * * @return self */ public function add_column( $column ) { $this->columns[] = $column; return $this; } /** * @param string $from * * @return self */ public function set_from( $from ) { $this->from = $from; return $this; } /** * @param $join * * @return self */ public function add_join( $join ) { $this->joins[] = $join; return $this; } /** * @param $column * @param WPML_TM_Jobs_Search_Params $params * * @return self */ public function set_status_filter( $column, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_status() ) { $statuses = wpml_prepare_in( $params->get_status(), '%d' ); $this->where[] = sprintf( $column . ' IN (%s)', $statuses ); } return $this; } /** * @param string $column * @param array|null $values * * @return $this */ public function set_multi_value_text_filter( $column, $values ) { if ( $values ) { $where = \wpml_collect( $values )->map( function ( $value ) use ( $column ) { return $this->wpdb->prepare( "{$column} LIKE %s", '%' . $value . '%' ); } )->toArray(); $this->where[] = '( ' . implode( ' OR ', $where ) . ' )'; } return $this; } /** * @param $column * @param WPML_TM_Jobs_Search_Params $params * * @return $this */ public function set_source_language( $column, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_source_language() ) { $this->where[] = $this->wpdb->prepare( "{$column} = %s", $params->get_source_language() ); } return $this; } /** * @param $column * @param WPML_TM_Jobs_Search_Params $params * * @return $this */ public function set_target_language( $column, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_target_language() ) { $this->where[] = sprintf( '%s IN (%s)', $column, wpml_prepare_in( $params->get_target_language() ) ); } return $this; } public function set_translated_by_filter( $local_translator_column, $translation_service_column, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_scope() !== WPML_TM_Jobs_Search_Params::SCOPE_ALL && $params->get_translated_by() ) { if ( $params->get_scope() === WPML_TM_Jobs_Search_Params::SCOPE_LOCAL ) { $this->where[] = $this->wpdb->prepare( "{$local_translator_column} = %d", $params->get_translated_by() ); } else { $this->where[] = $this->wpdb->prepare( "{$translation_service_column} = %d", $params->get_translated_by() ); } } return $this; } /** * @param string $column * @param int|int[] $value * * @return $this */ public function set_numeric_value_filter( $column, $value ) { if ( $value ) { if ( is_array( $value ) ) { $this->where[] = sprintf( "{$column} IN(%s)", wpml_prepare_in( $value, '%d' ) ); } else { $this->where[] = sprintf( "{$column} = %d", $value ); } } return $this; } /** * @param $column * @param WPML_TM_Jobs_Search_Params $params * * @return $this */ public function set_tp_id_filter( $column, WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_tp_id() ) { $where = array(); $tp_ids = $params->get_tp_id(); if ( in_array( null, $tp_ids, true ) ) { $tp_ids = array_filter( $tp_ids ); $where[] = $column . ' IS NULL'; } if ( $tp_ids ) { $where[] = sprintf( $column . ' IN (%s)', wpml_prepare_in( $tp_ids ) ); } $this->where[] = '( ' . implode( ' OR ', $where ) . ' )'; } return $this; } /** * @param string $column * @param WPML_TM_Jobs_Date_Range $date_range * * @return self */ public function set_date_range( $column, WPML_TM_Jobs_Date_Range $date_range ) { $sql_parts = array(); if ( $date_range->get_begin() ) { $sql_parts[] = $this->wpdb->prepare( $column . ' >= %s', $date_range->get_begin()->format( 'Y-m-d' ) ); } if ( $date_range->get_end() ) { $sql_parts[] = $this->wpdb->prepare( $column . ' <= %s', $date_range->get_end()->format( 'Y-m-d 23:59:59' ) ); } if ( $sql_parts ) { $sql = '( ' . implode( ' AND ', $sql_parts ) . ' )'; if ( $date_range->is_include_null_date() ) { $sql .= " OR $column IS NULL"; $sql = "( $sql )"; } $this->where[] = $sql; } return $this; } public function set_needs_review() { $this->add_AND_where_condition( $this->wpdb->prepare( '(translation_status.review_status = %s OR translation_status.review_status = %s)', ReviewStatus::NEEDS_REVIEW, ReviewStatus::EDITING ) ); } public function set_do_not_need_review() { $this->add_AND_where_condition( $this->wpdb->prepare( '(translation_status.review_status IS NULL OR translation_status.review_status = %s)', ReviewStatus::ACCEPTED ) ); } public function set_max_retries( $maxRetries ) { $this->add_AND_where_condition( $this->wpdb->prepare( '(translation_status.ate_comm_retry_count <= %d )', $maxRetries ) ); } public function set_element_type( $element_type ) { $this->add_AND_where_condition( $this->wpdb->prepare( '(translations.element_type = %s )', $element_type ) ); } /** * @param bool $automatic */ public function set_automatic( $automatic = true ) { $this->add_AND_where_condition( $this->wpdb->prepare( '(translate_job.automatic = %d )', $automatic ? 1 : 0 ) ); } /** * @param int $maxAteSyncCount */ public function set_max_ate_sync_count( $maxAteSyncCount ) { $this->add_AND_where_condition( $this->wpdb->prepare( '(translate_job.ate_sync_count <= %d )', $maxAteSyncCount ) ); } public function set_set_excluded_jobs() { $this->add_AND_where_condition( $this->wpdb->prepare( 'translation_status.status != %s', \WPML_TM_ATE_API::SHOULD_HIDE_STATUS ) ); } /** * @param string|void $where * * @return self */ public function add_AND_where_condition( $where ) { if ( $where ) { $this->where[] = $where; } return $this; } /** * @param WPML_TM_Jobs_Search_Params $params * * @return self */ public function set_order( WPML_TM_Jobs_Search_Params $params ) { $this->order = $this->order_helper->get_order( $params ); return $this; } /** * @param WPML_TM_Jobs_Search_Params $params * * @return self */ public function set_limit( WPML_TM_Jobs_Search_Params $params ) { $this->limit = $this->limit_helper->get_limit( $params ); return $this; } public function build() { if ( ! $this->columns ) { throw new InvalidArgumentException( 'You have to specify columns list' ); } if ( ! $this->from ) { throw new InvalidArgumentException( 'You have to specify FROM table' ); } $sql = ' SELECT %s FROM %s '; $sql = sprintf( $sql, implode( ', ', $this->columns ), $this->from ); if ( $this->joins ) { $sql .= implode( ' ', $this->joins ); } if ( $this->where ) { $sql .= ' WHERE ' . implode( ' AND ', $this->where ); } if ( $this->order ) { $sql .= ' ' . $this->order; } if ( $this->limit ) { $sql .= ' ' . $this->limit; } return $sql; } } jobs/query/PostQuery.php 0000755 00000001020 14720342453 0011332 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use WPML_TM_Job_Entity; class PostQuery extends AbstractQuery { /** * @param QueryBuilder $query_builder */ protected function add_resource_join( QueryBuilder $query_builder ) { $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}posts posts ON posts.ID = original_translations.element_id" ); $query_builder->add_AND_where_condition( "original_translations.element_type LIKE 'post%'" ); } protected function get_type() { return WPML_TM_Job_Entity::POST_TYPE; } } jobs/query/StringQuery.php 0000755 00000015546 14720342453 0011675 0 ustar 00 <?php /** * WPML_TM_Jobs_String_Query class file * * @package wpml-translation-management */ namespace WPML\TM\Jobs\Query; use \wpdb; use \WPML_TM_Jobs_Search_Params; use \WPML_TM_Job_Entity; class StringQuery implements Query { /** * WP database instance * * @var wpdb */ protected $wpdb; /** * Query builder instance * * @var QueryBuilder */ protected $query_builder; /** @var string */ protected $batch_name_column = 'batches.batch_name'; /** * @param wpdb $wpdb WP database instance. * @param QueryBuilder $query_builder Query builder instance. */ public function __construct( wpdb $wpdb, QueryBuilder $query_builder ) { $this->wpdb = $wpdb; $this->query_builder = $query_builder; } /** * Get data query * * @param WPML_TM_Jobs_Search_Params $params Job search params. * * @return string */ public function get_data_query( WPML_TM_Jobs_Search_Params $params ) { $columns = array( 'string_translations.id as id', '"' . WPML_TM_Job_Entity::STRING_TYPE . '" as type', 'string_status.rid as tp_id', 'batches.id as local_batch_id', 'batches.tp_id as tp_batch_id', $this->batch_name_column, 'string_translations.status as status', 'strings.id as original_element_id', 'strings.language as source_language', 'string_translations.language as target_language', 'NULL AS trid', 'NULL AS element_type', 'NULL AS element_id', 'string_translations.translation_service as translation_service', 'string_status.timestamp as sent_date', 'NULL as deadline_date', 'NULL as completed_date', 'strings.value as title', 'source_languages.english_name as source_language_name', 'target_languages.english_name as target_language_name', 'string_translations.translator_id as translator_id', 'NULL as translate_job_id', 'core_status.tp_revision AS revision', 'core_status.ts_status AS ts_status', 'NULL AS needs_update', 'NULL AS editor', 'string_translations.status = ' . ICL_TM_COMPLETE . ' AS has_completed_translation', 'NULL AS editor_job_id', '0 AS automatic', 'NULL AS job_title', 'NULL AS review_status', ); return $this->build_query( $params, $columns ); } /** * Get count query * * @param WPML_TM_Jobs_Search_Params $params Job search params. * * @return int|string */ public function get_count_query( WPML_TM_Jobs_Search_Params $params ) { $columns = array( 'COUNT(string_translations.id)' ); return $this->build_query( $params, $columns ); } /** * Build query * * @param WPML_TM_Jobs_Search_Params $params Job search params. * @param array $columns Database columns. * * @return string */ private function build_query( WPML_TM_Jobs_Search_Params $params, array $columns ) { if ( $this->check_job_type( $params ) ) { return ''; } $query_builder = clone $this->query_builder; $query_builder->set_columns( $columns ); $query_builder->set_from( "{$this->wpdb->prefix}icl_translation_batches AS translation_batches" ); $this->define_joins( $query_builder ); $this->define_filters( $query_builder, $params ); $query_builder->set_limit( $params ); $query_builder->set_order( $params ); return $query_builder->build(); } /** * Check job type. * * @param WPML_TM_Jobs_Search_Params $params Job search params. * * @return bool */ protected function check_job_type( WPML_TM_Jobs_Search_Params $params ) { return $params->get_job_types() && ! in_array( WPML_TM_Job_Entity::STRING_TYPE, $params->get_job_types(), true ); } /** * Define joins * * @param QueryBuilder $query_builder Query builder instance. */ private function define_joins( QueryBuilder $query_builder ) { $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_string_translations AS string_translations ON string_translations.batch_id = translation_batches.id" ); $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_strings AS strings ON strings.id = string_translations.string_id" ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_string_status AS string_status ON string_status.string_translation_id = string_translations.id" ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_core_status AS core_status ON core_status.rid = string_status.rid" ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_languages source_languages ON source_languages.code = strings.language" ); $query_builder->add_join( "LEFT JOIN {$this->wpdb->prefix}icl_languages target_languages ON target_languages.code = string_translations.language" ); $query_builder->add_join( "INNER JOIN {$this->wpdb->prefix}icl_translation_batches batches ON batches.id = string_translations.batch_id" ); } /** * Define filters * * @param QueryBuilder $query_builder Query builder instance. * @param WPML_TM_Jobs_Search_Params $params Job search params. */ private function define_filters( QueryBuilder $query_builder, WPML_TM_Jobs_Search_Params $params ) { $query_builder->set_status_filter( 'string_translations.status', $params ); $query_builder = $this->set_scope_filter( $query_builder, $params ); $query_builder->set_multi_value_text_filter( 'strings.value', $params->get_title() ); $query_builder->set_multi_value_text_filter( $this->batch_name_column, $params->get_batch_name() ); $query_builder->set_source_language( 'strings.language', $params ); $query_builder->set_target_language( 'string_translations.language', $params ); $query_builder->set_translated_by_filter( 'string_translations.translator_id', 'string_translations.translation_service', $params ); if ( $params->get_sent() ) { $query_builder->set_date_range( 'string_status.timestamp', $params->get_sent() ); } $query_builder->set_numeric_value_filter( 'string_translations.id', $params->get_first_local_job_id() ); $query_builder->set_numeric_value_filter( 'strings.id', $params->get_original_element_id() ); $query_builder->set_tp_id_filter( 'string_status.rid', $params ); if ( $params->get_deadline() ) { $query_builder->add_AND_where_condition( '1 = 0' ); } } private function set_scope_filter( QueryBuilder $query_builder, WPML_TM_Jobs_Search_Params $params ) { switch ( $params->get_scope() ) { case WPML_TM_Jobs_Search_Params::SCOPE_LOCAL: $query_builder->add_AND_where_condition( 'string_status.rid IS NULL' ); break; case WPML_TM_Jobs_Search_Params::SCOPE_REMOTE: $query_builder->add_AND_where_condition( 'string_status.rid IS NOT NULL' ); break; case WPML_TM_Jobs_Search_Params::SCOPE_ATE: /* This class serves the old fashioned string jobs which did not support ATE. Due to that, it should return nothing when ATE Scope is set. */ $query_builder->add_AND_where_condition( '1 <> 1' ); break; } return $query_builder; } } jobs/query/OrderQueryHelper.php 0000755 00000002051 14720342453 0012625 0 ustar 00 <?php namespace WPML\TM\Jobs\Query; use \WPML_TM_Jobs_Search_Params; class OrderQueryHelper { public function get_order( \WPML_TM_Jobs_Search_Params $params ) { $orders = $this->map_sort_parameters( $params ); if ( $orders ) { return 'ORDER BY ' . implode( ', ', $orders ); } else { return ''; } } /** * @param WPML_TM_Jobs_Search_Params $params * * @return array */ private function map_sort_parameters( WPML_TM_Jobs_Search_Params $params ) { $orders = array(); if ( $params->get_sorting() ) { foreach ( $params->get_sorting() as $order ) { if ( $order->get_column() === 'language' ) { $orders[] = 'source_language_name ' . $order->get_direction(); $orders[] = 'target_language_name ' . $order->get_direction(); } elseif ( $order->get_column() === 'sent_date' || $order->get_column() === 'deadline_date' ) { $orders[] = "DATE({$order->get_column()}) {$order->get_direction()}"; } else { $orders[] = $order->get_column() . ' ' . $order->get_direction(); } } } return $orders; } } jobs/class-wpml-tm-job-entity.php 0000755 00000014603 14720342453 0013007 0 ustar 00 <?php use WPML\FP\Lst; class WPML_TM_Job_Entity { const POST_TYPE = 'post'; const STRING_TYPE = 'string'; const STRING_BATCH = 'st-batch_strings'; const PACKAGE_TYPE = 'package'; /** @var int */ private $id; /** @var string */ private $type; /** @var int */ private $tp_id; /** @var WPML_TM_Jobs_Batch */ private $batch; /** @var int */ private $status; /** @var int */ private $original_element_id; /** @var string */ private $source_language; /** @var string */ private $target_language; /** @var string */ private $translation_service; /** @var DateTime */ private $sent_date; /** @var DateTime|null */ private $deadline; /** @var int */ private $translator_id; /** @var int */ private $revision; /** @var WPML_TM_Job_TS_Status */ private $ts_status; /** @var bool */ private $needs_update; /** @var bool */ private $has_completed_translation = false; /** @var string */ private $title; /** * @param int $id * @param string $type * @param int $tp_id * @param WPML_TM_Jobs_Batch $batch * @param int $status */ public function __construct( $id, $type, $tp_id, WPML_TM_Jobs_Batch $batch, $status ) { $this->id = (int) $id; if ( ! self::is_type_valid( $type ) ) { throw new InvalidArgumentException( 'Invalid type value: ' . $type ); } $this->type = $type; $this->tp_id = (int) $tp_id; $this->batch = $batch; $this->set_status( $status ); } /** * @deprecated Use `get_rid` instead. * * This method is deprecated because it caused confusion * between the `job_id` and the `rid`. * * It's actually returning the `rid`. * * @return int */ public function get_id() { return $this->get_rid(); } /** * @return int */ public function get_rid() { return $this->id; } /** * @return string */ public function get_type() { return $this->type; } /** * @return int */ public function get_tp_id() { return $this->tp_id; } /** * @return WPML_TM_Jobs_Batch */ public function get_batch() { return $this->batch; } /** * @return int */ public function get_status() { return $this->status; } /** * @param int $status */ public function set_status( $status ) { $status = (int) $status; if ( ! in_array( $status, array( ICL_TM_NOT_TRANSLATED, ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS, ICL_TM_TRANSLATION_READY_TO_DOWNLOAD, ICL_TM_DUPLICATE, ICL_TM_COMPLETE, ICL_TM_NEEDS_UPDATE, ICL_TM_NEEDS_REVIEW, ICL_TM_ATE_CANCELLED, ICL_TM_ATE_NEEDS_RETRY, ), true ) ) { $status = ICL_TM_NOT_TRANSLATED; } $this->status = $status; } /** * @return int */ public function get_original_element_id() { return $this->original_element_id; } /** * @param int $original_element_id */ public function set_original_element_id( $original_element_id ) { $this->original_element_id = $original_element_id; } /** * @return string */ public function get_source_language() { return $this->source_language; } /** * @param string $source_language */ public function set_source_language( $source_language ) { $this->source_language = $source_language; } /** * @return string */ public function get_target_language() { return $this->target_language; } /** * @param string $target_language */ public function set_target_language( $target_language ) { $this->target_language = $target_language; } /** * @return string */ public function get_translation_service() { return $this->translation_service; } /** * @param string $translation_service */ public function set_translation_service( $translation_service ) { $this->translation_service = $translation_service; } /** * @return DateTime */ public function get_sent_date() { return $this->sent_date; } /** * @param DateTime $sent_date */ public function set_sent_date( DateTime $sent_date ) { $this->sent_date = $sent_date; } /** * @param int $tp_id */ public function set_tp_id( $tp_id ) { $this->tp_id = $tp_id; } /** * @return DateTime|null */ public function get_deadline() { return $this->deadline; } /** * @param DateTime|null $deadline */ public function set_deadline( DateTime $deadline = null ) { $this->deadline = $deadline; } /** * @return int */ public function get_translator_id() { return $this->translator_id; } /** * @param int $translator_id * * @return self */ public function set_translator_id( $translator_id ) { $this->translator_id = $translator_id; return $this; } /** * @return int */ public function get_revision() { return $this->revision; } /** * @param int $revision */ public function set_revision( $revision ) { $this->revision = max( (int) $revision, 1 ); } /** * @return WPML_TM_Job_TS_Status */ public function get_ts_status() { return $this->ts_status; } /** * @param WPML_TM_Job_TS_Status|string $ts_status */ public function set_ts_status( $ts_status ) { if ( is_string( $ts_status ) ) { $status = json_decode( $ts_status ); if ( $status ) { $ts_status = new WPML_TM_Job_TS_Status( $status->ts_status->status, $status->ts_status->links ); } } $this->ts_status = $ts_status; } /** * @param WPML_TM_Job_Entity $job * * @return bool */ public function is_equal( WPML_TM_Job_Entity $job ) { return $this->get_id() === $job->get_id() && $this->get_type() === $job->get_type(); } /** * @return bool */ public function does_need_update() { return $this->needs_update; } /** * @param bool $needs_update */ public function set_needs_update( $needs_update ) { $this->needs_update = (bool) $needs_update; } /** * @return bool */ public function has_completed_translation() { return $this->has_completed_translation; } /** * @param bool $has_completed_translation */ public function set_has_completed_translation( $has_completed_translation ) { $this->has_completed_translation = (bool) $has_completed_translation; } /** * @return string */ public function get_title() { return $this->title; } /** * @param string $title */ public function set_title( $title ) { $this->title = $title; } /** * @param string $type * * @return bool */ public static function is_type_valid( $type ) { return Lst::includes( $type, [ self::POST_TYPE, self::STRING_TYPE, self::PACKAGE_TYPE, self::STRING_BATCH ] ); } } jobs/class-wpml-tm-job-element-entity.php 0000755 00000003773 14720342453 0014444 0 ustar 00 <?php class WPML_TM_Job_Element_Entity { /** @var int */ private $id; /** @var int */ private $content_id; /** @var int */ private $timestamp; /** @var string */ private $type; /** @var string */ private $format; /** @var bool */ private $translatable; /** @var string */ private $data; /** @var string */ private $data_translated; /** @var bool */ private $finished; /** * @param int $id * @param int $content_id * @param int $timestamp * @param string $type * @param string $format * @param bool $is_translatable * @param string $data * @param string $data_translated * @param bool $finished */ public function __construct( $id, $content_id, $timestamp, $type, $format, $is_translatable, $data, $data_translated, $finished ) { $this->id = (int) $id; $this->content_id = (int) $content_id; $this->timestamp = (int) $timestamp; $this->type = (string) $type; $this->format = (string) $format; $this->translatable = (bool) $is_translatable; $this->data = (string) $data; $this->data_translated = (bool) $data_translated; $this->finished = (bool) $finished; } /** * @return int */ public function get_id() { return $this->id; } /** * @return int */ public function get_content_id() { return $this->content_id; } /** * @return int */ public function get_timestamp() { return $this->timestamp; } /** * @return string */ public function get_type() { return $this->type; } /** * @return string */ public function get_format() { return $this->format; } /** * @return bool */ public function is_translatable() { return $this->translatable; } /** * @return string */ public function get_data() { return $this->data; } /** * @return string */ public function get_data_translated() { return $this->data_translated; } /** * @return bool */ public function is_finished() { return $this->finished; } } jobs/class-wpml-tm-jobs-batch.php 0000755 00000001217 14720342453 0012734 0 ustar 00 <?php class WPML_TM_Jobs_Batch { /** @var int */ private $id; /** @var string */ private $name; /** @var int|null */ private $tp_id; /** * @param int $id * @param string $name * @param int|null $tp_id */ public function __construct( $id, $name, $tp_id = null ) { $this->id = (int) $id; $this->name = (string) $name; $this->tp_id = $tp_id ? (int) $tp_id : null; } /** * @return int */ public function get_id() { return $this->id; } /** * @return string */ public function get_name() { return $this->name; } /** * @return int|null */ public function get_tp_id() { return $this->tp_id; } } jobs/utils/ElementLinkFactory.php 0000755 00000000560 14720342453 0013121 0 ustar 00 <?php namespace WPML\TM\Jobs\Utils; use function WPML\Container\make; use WPML_Post_Translation; class ElementLinkFactory { public static function create() { /** * @var WPML_Post_Translation $wpml_post_translations; */ global $wpml_post_translations; return make( ElementLink::class, [ ':postTranslation' => $wpml_post_translations ] ); } } jobs/utils/ElementLink.php 0000755 00000003716 14720342453 0011577 0 ustar 00 <?php namespace WPML\TM\Jobs\Utils; use WPML\TM\Menu\PostLinkUrl; use WPML_Post_Translation; class ElementLink { /** @var PostLinkUrl $postLinkUrl */ private $postLinkUrl; /** @var WPML_Post_Translation $postTranslation */ private $postTranslation; public function __construct( PostLinkUrl $postLinkUrl, WPML_Post_Translation $postTranslation ) { $this->postLinkUrl = $postLinkUrl; $this->postTranslation = $postTranslation; } public function getOriginal( \WPML_TM_Post_Job_Entity $job ) { return $this->get( $job, $job->get_original_element_id() ); } /** * @param \WPML_TM_Post_Job_Entity $job * * @return string */ public function getTranslation( \WPML_TM_Post_Job_Entity $job ) { if ( $this->isExternalType( $job->get_element_type_prefix() ) ) { return ''; } $translatedId = $this->postTranslation->element_id_in( $job->get_original_element_id(), $job->get_target_language() ); if ( $translatedId ) { return $this->get( $job, $translatedId ); } return ''; } /** * @param \WPML_TM_Post_Job_Entity $job * @param string|int|null $elementId * * @return mixed|string|void */ private function get( \WPML_TM_Post_Job_Entity $job, $elementId = null ) { $elementId = $elementId ?: $job->get_target_language(); $elementType = preg_replace( '/^' . $job->get_element_type_prefix() . '_/', '', $job->get_element_type() ); if ( $this->isExternalType( $job->get_element_type_prefix() ) ) { $tmPostLink = apply_filters( 'wpml_external_item_url', '', $elementId ); } else { $tmPostLink = $this->postLinkUrl->viewLinkUrl( $elementId ); } $tmPostLink = apply_filters( 'wpml_document_view_item_link', $tmPostLink, '', $job, $job->get_element_type_prefix(), $elementType ); return $tmPostLink; } /** * @param string $elementTypePrefix * * @return bool */ private function isExternalType( $elementTypePrefix ) { return apply_filters( 'wpml_is_external', false, $elementTypePrefix ); } } jobs/Manual.php 0000755 00000016230 14720342453 0007440 0 ustar 00 <?php namespace WPML\TM\Jobs; use WPML\Element\API\PostTranslations; use WPML\FP\Lst; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\LIB\WP\User; use WPML\Records\Translations as TranslationRecords; use WPML\TM\API\Jobs; use function WPML\FP\pipe; class Manual { /** * @param array $params * * @return \WPML_Translation_Job|null */ public function createOrReuse( array $params ) { $jobId = (int) filter_var( Obj::propOr( 0, 'job_id', $params ), FILTER_SANITIZE_NUMBER_INT ); $isReview = (bool) filter_var( Obj::propOr( 0, 'preview', $params ), FILTER_SANITIZE_NUMBER_INT ); list( $jobId, $trid, $updateNeeded, $targetLanguageCode, $elementType ) = $this->get_job_data_for_restore( $jobId, $params ); $sourceLangCode = filter_var( Obj::prop( 'source_language_code', $params ), FILTER_SANITIZE_FULL_SPECIAL_CHARS ); // When the post needs update, but the user is reviewing a specific job, we shall not create a new job neither, it leads to wrong state. $needsUpdateAndIsNotReviewMode = $updateNeeded && ! $isReview; if ( $trid && $targetLanguageCode && ( $needsUpdateAndIsNotReviewMode || ! $jobId ) ) { $postId = $this->getOriginalPostId( $trid ); // if $jobId is not a truthy value this means that a new translation is going to be created in $targetLanguageCode (the + icon is clicked in posts list page) // and in this case we try to get the post id that exists in $sourceLangCode // @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmldev-1934 if ( ! $jobId ) { $postId = $this->getPostIdInLang( $trid, $sourceLangCode ) ?: $postId; } if ( $postId && $this->can_user_translate( $sourceLangCode, $targetLanguageCode, $postId ) ) { return $this->markJobAsManual( $this->createLocalJob( $postId, $sourceLangCode, $targetLanguageCode, $elementType ) ); } } return $jobId ? $this->markJobAsManual( wpml_tm_load_job_factory()->get_translation_job_as_active_record( $jobId ) ) : null; } /** * @param array $params * * @return array{targetLanguageCode: string, translatedPostId: int, originalPostId: int, postType: string}|null */ public function maybeGetDataIfTranslationCreatedInNativeEditorViaConnection( array $params ) { $jobId = (int) filter_var( Obj::propOr( 0, 'job_id', $params ), FILTER_SANITIZE_NUMBER_INT ); list( $jobId, $trid, , $targetLanguageCode ) = $this->get_job_data_for_restore( $jobId, $params ); if ( $trid && $targetLanguageCode && ! $jobId ) { $originalPostId = $this->getOriginalPostId( $trid ); if ( $this->isDuplicate( $originalPostId, $targetLanguageCode ) ) { return null; } $translatedPostId = (int) $this->getPostIdInLang( $trid, $targetLanguageCode ); if ( $translatedPostId ) { $translatedPost = get_post( $translatedPostId ); if ( $translatedPost ) { $enforcedNativeEditor = get_post_meta( $originalPostId, \WPML_TM_Post_Edit_TM_Editor_Mode::POST_META_KEY_USE_NATIVE, true ); if ( $enforcedNativeEditor === 'no' ) { // a user deliberately chose to use the WPML editor return null; } return [ 'targetLanguageCode' => $targetLanguageCode, 'translatedPostId' => $translatedPostId, 'originalPostId' => $originalPostId, 'postType' => $translatedPost->post_type, ]; } } } return null; } private function getOriginalPostId( $trid ) { return Obj::prop( 'element_id', TranslationRecords::getSourceByTrid( $trid ) ); } /** * @param string|int $trid * @param string $lang * * @return string|int */ private function getPostIdInLang( $trid, $lang ) { $getElementId = pipe( Lst::find( Relation::propEq( 'language_code', $lang ) ), Obj::prop( 'element_id' ) ); return $getElementId( TranslationRecords::getByTrid( $trid ) ); } /** * @param $jobId * @param array $params * * @return array ( job_id, trid, updated_needed, language_code, post_type ) */ private function get_job_data_for_restore( $jobId, array $params ) { $trid = (int) filter_var( Obj::prop( 'trid', $params ), FILTER_SANITIZE_NUMBER_INT ); $updateNeeded = (bool) filter_var( Obj::prop( 'update_needed', $params ), FILTER_SANITIZE_NUMBER_INT ); $languageCode = (string) filter_var( Obj::prop( 'language_code', $params ), FILTER_SANITIZE_FULL_SPECIAL_CHARS ); $job = null; if ( $jobId ) { $job = Jobs::get( $jobId ); } else if ( $trid && $languageCode ) { $job = Jobs::getTridJob( $trid, $languageCode ); } if ( is_object( $job ) ) { return [ Obj::prop( 'job_id', $job ), Obj::prop( 'trid', $job ), Obj::prop( 'needs_update', $job ), Obj::prop( 'language_code', $job ), Obj::prop( 'original_post_type', $job ) ]; } $elementType = $trid ? Obj::path( [ 0, 'element_type' ], TranslationRecords::getByTrid( $trid ) ) : null; return [ $jobId, $trid, $updateNeeded, $languageCode, $elementType, ]; } /** * @param string $sourceLangCode * @param string $targetLangCode * @param string $postId * * @return bool */ private function can_user_translate( $sourceLangCode, $targetLangCode, $postId ) { $args = [ 'lang_from' => $sourceLangCode, 'lang_to' => $targetLangCode, 'post_id' => $postId, ]; return wpml_tm_load_blog_translators()->is_translator( User::getCurrentId(), $args ); } /** * @param int $originalPostId * @param string $sourceLangCode * @param string $targetLangCode * @param string $elementType * * @return \WPML_Translation_Job|null */ private function createLocalJob( $originalPostId, $sourceLangCode, $targetLangCode, $elementType ) { $jobId = wpml_tm_load_job_factory()->create_local_job( $originalPostId, $targetLangCode, null, $elementType, Jobs::SENT_MANUALLY, $sourceLangCode ); return Maybe::fromNullable( $jobId ) ->map( [ wpml_tm_load_job_factory(), 'get_translation_job_as_active_record' ] ) ->map( $this->maybeAssignTranslator() ) ->map( $this->maybeSetJobStatus() ) ->getOrElse( null ); } private function maybeAssignTranslator() { return function ( $jobObject ) { if ( $jobObject->get_translator_id() <= 0 ) { $jobObject->assign_to( User::getCurrentId() ); } return $jobObject; }; } private function maybeSetJobStatus() { return function ( $jobObject ) { if ( $this->isDuplicate( $jobObject->get_original_element_id(), $jobObject->get_language_code() ) ) { Jobs::setStatus( (int) $jobObject->get_id(), ICL_TM_DUPLICATE ); } elseif ( (int) $jobObject->get_status_value() !== ICL_TM_COMPLETE ) { Jobs::setStatus( (int) $jobObject->get_id(), ICL_TM_IN_PROGRESS ); } return $jobObject; }; } private function markJobAsManual( $jobObject ) { $jobObject && Jobs::clearAutomatic( $jobObject->get_id() ); return $jobObject; } /** * @param int $originalElementId * @param string $targetLanguageCode * * @return bool */ private function isDuplicate( $originalElementId, $targetLanguageCode ): bool { return Maybe::of( $originalElementId ) ->map( PostTranslations::get() ) ->map( Obj::prop( $targetLanguageCode ) ) ->map( Obj::prop( 'element_id' ) ) ->map( [ wpml_get_post_status_helper(), 'is_duplicate' ] ) ->getOrElse( false ); } } jobs/class-wpml-tm-job-elements-repository.php 0000755 00000002134 14720342453 0015520 0 ustar 00 <?php class WPML_TM_Job_Elements_Repository { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param WPML_TM_Post_Job_Entity $job * * @return WPML_TM_Job_Element_Entity[] */ public function get_job_elements( WPML_TM_Post_Job_Entity $job ) { $sql = " SELECT translate.* FROM {$this->wpdb->prefix}icl_translate translate WHERE job_id = %d "; $rowset = $this->wpdb->get_results( $this->wpdb->prepare( $sql, $job->get_translate_job_id() ) ); return is_array( $rowset ) ? array_map( array( $this, 'build_element_entity' ), $rowset ) : []; } /** * @param stdClass $raw_data * * @return WPML_TM_Job_Element_Entity */ private function build_element_entity( stdClass $raw_data ) { return new WPML_TM_Job_Element_Entity( $raw_data->tid, $raw_data->content_id, $raw_data->timestamp, $raw_data->field_type, $raw_data->field_format, $raw_data->field_translate, $raw_data->field_data, $raw_data->field_data_translated, $raw_data->field_finished ); } } jobs/class-wpml-tm-jobs-collection.php 0000755 00000005377 14720342453 0014021 0 ustar 00 <?php class WPML_TM_Jobs_Collection implements IteratorAggregate, Countable { /** @var WPML_TM_Job_Entity[] */ private $jobs = array(); public function __construct( array $jobs ) { foreach ( $jobs as $job ) { if ( $job instanceof WPML_TM_Job_Entity ) { $this->add( $job ); } } } /** * @param WPML_TM_Job_Entity $job */ private function add( WPML_TM_Job_Entity $job ) { $this->jobs[] = $job; } /** * @param int $tp_id * * @return null|WPML_TM_Job_Entity */ public function get_by_tp_id( $tp_id ) { foreach ( $this->jobs as $job ) { if ( $tp_id === $job->get_tp_id() ) { return $job; } } return null; } /** * @param callable $callback * * @return WPML_TM_Jobs_Collection */ public function filter( $callback ) { return new WPML_TM_Jobs_Collection( array_filter( $this->jobs, $callback ) ); } /** * @param array|int $status * @param bool $exclude * * @return WPML_TM_Jobs_Collection */ public function filter_by_status( $status, $exclude = false ) { if ( ! is_array( $status ) ) { $status = array( $status ); } $result = array(); if ( $exclude ) { foreach ( $this->jobs as $job ) { if ( ! in_array( $job->get_status(), $status, true ) ) { $result[] = $job; } } } else { foreach ( $this->jobs as $job ) { if ( in_array( $job->get_status(), $status, true ) ) { $result[] = $job; } } } return new WPML_TM_Jobs_Collection( $result ); } /** * @param callable $callback * @param bool $return_job_collection * * @return array|WPML_TM_Jobs_Collection * * @psalm-return ($return_job_collection is false ? array : WPML_TM_Jobs_Collection) */ public function map( $callback, $return_job_collection = false ) { $mapped_result = array_map( $callback, $this->jobs ); return $return_job_collection ? new WPML_TM_Jobs_Collection( $mapped_result ) : $mapped_result; } public function map_to_property( $property ) { $method = 'get_' . $property; $result = array(); foreach ( $this->jobs as $job ) { if ( ! method_exists( $job, $method ) ) { throw new InvalidArgumentException( 'Property ' . $property . ' does not exist' ); } $result[] = $job->{$method}(); } return $result; } /** * @param $jobs * * @return WPML_TM_Jobs_Collection */ public function append( $jobs ) { if ( $jobs instanceof WPML_TM_Jobs_Collection ) { $jobs = $jobs->toArray(); } return new WPML_TM_Jobs_Collection( array_merge( $this->jobs, $jobs ) ); } /** * @return ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new ArrayIterator( $this->jobs ); } public function toArray() { return $this->jobs; } #[\ReturnTypeWillChange] public function count() { return count( $this->jobs ); } } jobs/endpoint/Resign.php 0000755 00000000713 14720342453 0011271 0 ustar 00 <?php namespace WPML\TM\Jobs\Endpoint; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\TM\API\Jobs; class Resign implements IHandler { public function run( Collection $data ) { $result = \wpml_collect( $data->get( 'jobIds' ) ) ->filter( Jobs::get() ) ->map( Fns::tap( [ wpml_load_core_tm(), 'resign_translator' ] ) ) ->values() ->toArray(); return Either::of( $result ); } } jobs/Loader.php 0000755 00000001250 14720342453 0007425 0 ustar 00 <?php namespace WPML\TM\Jobs; use WPML\Core\WP\App\Resources; use WPML\LIB\WP\Hooks; use WPML\UIPage; class Loader implements \IWPML_Backend_Action { public function add_hooks() { if ( wpml_is_ajax() ) { return; } if ( UIPage::isTMJobs( $_GET ) || UIPage::isTranslationQueue( $_GET ) ) { Hooks::onAction( 'wp_loaded' ) ->then( [ $this, 'getData' ] ) ->then( Resources::enqueueApp( 'jobs' ) ); } } public function getData() { $data = ( new \WPML_TM_Jobs_List_Script_Data() )->get(); $data = ( new \WPML_TM_Scripts_Factory() )->build_localize_script_data( $data ); return [ 'name' => 'WPML_TM_SETTINGS', 'data' => $data, ]; } } jobs/class-wpml-tm-jobs-repository.php 0000755 00000012212 14720342453 0014067 0 ustar 00 <?php use \WPML\TM\Jobs\Query\Query; use WPML\FP\Fns; class WPML_TM_Jobs_Repository { /** @var wpdb */ private $wpdb; /** @var Query */ private $query_builder; /** @var WPML_TM_Job_Elements_Repository */ private $elements_repository; /** * @param wpdb $wpdb * @param Query $query_builder * @param WPML_TM_Job_Elements_Repository $elements_repository */ public function __construct( wpdb $wpdb, Query $query_builder, WPML_TM_Job_Elements_Repository $elements_repository ) { $this->wpdb = $wpdb; $this->query_builder = $query_builder; $this->elements_repository = $elements_repository; } /** * @param WPML_TM_Jobs_Search_Params $params * * @return WPML_TM_Jobs_Collection|array */ public function get( WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_columns_to_select() ) { return $this->wpdb->get_results( $this->query_builder->get_data_query( $params ) ); } $results = $this->wpdb->get_results( $this->query_builder->get_data_query( $params ) ); return is_array( $results ) ? new WPML_TM_Jobs_Collection( array_map( array( $this, 'build_job_entity' ), $results ) ) : new WPML_TM_Jobs_Collection( [] ); } /** * @param WPML_TM_Jobs_Search_Params $params * * @throws \InvalidArgumentException When get_columns_to_select() is used. In that case use get(). * * @return WPML_TM_Jobs_Collection */ public function get_collection( WPML_TM_Jobs_Search_Params $params ) { if ( $params->get_columns_to_select() ) { throw new \InvalidArgumentException( 'Not valid with get_columns_to_select().' ); } return $this->get( $params ); } /** * @param array $ateJobIds * * @return bool */ public function increment_ate_sync_count( array $ateJobIds ) { if ( empty( $ateJobIds ) ) { return true; } else { $query = sprintf( 'UPDATE %sicl_translate_job SET ate_sync_count=ate_sync_count+1 WHERE editor_job_id IN( %s )', $this->wpdb->prefix, wpml_prepare_in( $ateJobIds ) ); return (bool) $this->wpdb->query( $query ); } } /** * @param WPML_TM_Jobs_Search_Params $params * * @return int */ public function get_count( WPML_TM_Jobs_Search_Params $params ) { return (int) $this->wpdb->get_var( $this->query_builder->get_count_query( $params ) ); } /** * @param int $local_job_id * @param string $job_type * * @throws InvalidArgumentException * @return WPML_TM_Job_Entity|false */ public function get_job( $local_job_id, $job_type ) { $params = new WPML_TM_Jobs_Search_Params(); $params->set_local_job_id( $local_job_id ); $params->set_job_types( $job_type ); $data = $this->wpdb->get_row( $this->query_builder->get_data_query( $params ) ); if ( is_object( $data ) ) { $data = $this->build_job_entity( $data ); } return $data; } /** * @param object $raw_data * * @return WPML_TM_Job_Entity */ private function build_job_entity( $raw_data ) { $types = [ WPML_TM_Job_Entity::POST_TYPE, WPML_TM_Job_Entity::PACKAGE_TYPE, WPML_TM_Job_Entity::STRING_BATCH ]; $batch = new WPML_TM_Jobs_Batch( $raw_data->local_batch_id, $raw_data->batch_name, $raw_data->tp_batch_id ); if ( in_array( $raw_data->type, $types, true ) ) { $job = new WPML_TM_Post_Job_Entity( $raw_data->id, $raw_data->type, $raw_data->tp_id, $batch, (int) $raw_data->status, array( $this->elements_repository, 'get_job_elements' ) ); $job->set_translate_job_id( $raw_data->translate_job_id ); $job->set_editor( $raw_data->editor ); $job->set_completed_date( $raw_data->completed_date ? new DateTime( $raw_data->completed_date ) : null ); $job->set_editor_job_id( $raw_data->editor_job_id ); $job->set_automatic( $raw_data->automatic ); $job->set_review_status( $raw_data->review_status ); $job->set_trid( $raw_data->trid ); $job->set_element_type( $raw_data->element_type ); $job->set_element_id( $raw_data->element_id ); $job->set_element_type_prefix( $raw_data->element_type ); $job->set_job_title( $raw_data->job_title ); } else { $job = new WPML_TM_Job_Entity( $raw_data->id, $raw_data->type, $raw_data->tp_id, $batch, (int) $raw_data->status ); } $job->set_original_element_id( $raw_data->original_element_id ); $job->set_source_language( $raw_data->source_language ); $job->set_target_language( $raw_data->target_language ); $job->set_translation_service( $raw_data->translation_service ); $job->set_sent_date( new DateTime( $raw_data->sent_date ) ); $job->set_deadline( $this->get_deadline( $raw_data ) ); $job->set_translator_id( $raw_data->translator_id ); $job->set_revision( $raw_data->revision ); $job->set_ts_status( $raw_data->ts_status ); $job->set_needs_update( $raw_data->needs_update ); $job->set_has_completed_translation( $raw_data->has_completed_translation ); $job->set_title( $raw_data->title ); return $job; } /** * @param object $raw_data * * @return DateTime|null */ private function get_deadline( $raw_data ) { if ( $raw_data->deadline_date && '0000-00-00 00:00:00' !== $raw_data->deadline_date ) { return new DateTime( $raw_data->deadline_date ); } return null; } } class-wpml-site-id.php 0000755 00000005220 14720342453 0010701 0 ustar 00 <?php /** * Class for handling a unique ID of the site. * * @author OnTheGo Systems */ class WPML_Site_ID { /** * The name prefix of the option where the ID is stored. */ const SITE_ID_KEY = 'WPML_SITE_ID'; /** * The default scope. */ const SITE_SCOPES_GLOBAL = 'global'; /** * Memory cache of the IDs. * * @var array */ private $site_ids = array(); /** * Read and, if needed, generate the site ID based on the scope. * * @param string $scope Defaults to "global". * Use a different value when the ID is used for specific scopes. * * @param bool $create_new Forces the creation of a new ID. * * @return string|null The generated/stored ID or null if it wasn't possible to generate/store the value. */ public function get_site_id( $scope = self::SITE_SCOPES_GLOBAL, $create_new = false ) { $generate = ! $this->read_value( $scope ) || $create_new; if ( $generate && ! $this->generate_site_id( $scope ) ) { return null; } return $this->get_from_cache( $scope ); } /** * Geenrates the ID. * * @param string $scope The scope of the ID. * * @return bool */ private function generate_site_id( $scope ) { $site_url = get_site_url(); $site_uuid = uuid_v5( $site_url, wp_generate_uuid4() ); $time_uuid = uuid_v5( time(), wp_generate_uuid4() ); return $this->write_value( uuid_v5( $site_uuid, $time_uuid ), $scope ); } /** * Read the value from cache, if present, or from the DB. * * @param string $scope The scope of the ID. * * @return string */ private function read_value( $scope ) { if ( ! $this->get_from_cache( $scope ) ) { $this->site_ids[ $scope ] = get_option( $this->get_option_key( $scope ), null ); } return $this->site_ids[ $scope ]; } /** * Writes the value in DB and cache. * * @param string $value The value to write. * @param string $scope The scope of the ID. * * @return bool */ private function write_value( $value, $scope ) { if ( update_option( $this->get_option_key( $scope ), $value, false ) ) { $this->site_ids[ $scope ] = $value; return true; } return false; } /** * Gets the options key name based on the scope. * * @param string $scope The scope of the ID. * * @return string */ private function get_option_key( $scope ) { return self::SITE_ID_KEY . ':' . $scope; } /** * Gets the value from the memory cache. * * @param string $scope The scope of the ID. * * @return mixed|null */ private function get_from_cache( $scope ) { if ( array_key_exists( $scope, $this->site_ids ) && $this->site_ids[ $scope ] ) { return $this->site_ids[ $scope ]; } return null; } } LanguageNegotiation.php 0000755 00000003405 14720342453 0011212 0 ustar 00 <?php namespace WPML\Core; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Obj; use function WPML\FP\curryN; use function WPML\FP\partial; /** * Class LanguageNegotiation * @package WPML\Core * * @method static callable|void saveMode( ...$mode ) - int|string->void * * @method static int getMode() * * @method static string getModeAsString( $mode = null ) * * @method static callable|void saveDomains( ...$domains ) - array->void * * @method static array getDomains() */ class LanguageNegotiation { use Macroable; const DIRECTORY = 1; const DOMAIN = 2; const PARAMETER = 3; const DIRECTORY_STRING = 'directory'; const DOMAIN_STRING = 'domain'; const PARAMETER_STRING = 'parameter'; private static $modeMap = [ self::DIRECTORY_STRING => self::DIRECTORY, self::DOMAIN_STRING => self::DOMAIN, self::PARAMETER_STRING => self::PARAMETER, ]; /** * @ignore */ public static function init() { global $sitepress; self::macro( 'saveMode', curryN( 1, function ( $mode ) use ( $sitepress ) { $mode = is_numeric( $mode ) ? (int) $mode : Obj::propOr( self::PARAMETER, $mode, self::$modeMap ); $sitepress->set_setting( 'language_negotiation_type', $mode, true ); } ) ); self::macro( 'getMode', partial( [ $sitepress, 'get_setting' ], 'language_negotiation_type' ) ); self::macro( 'getModeAsString', function ( $mode = null ) { return \wpml_collect( self::$modeMap )->flip()->get( $mode ?: self::getMode(), self::DIRECTORY_STRING ); } ); self::macro( 'saveDomains', curryN( 1, function ( $domains ) use ( $sitepress ) { $sitepress->set_setting( 'language_domains', $domains, true ); } ) ); self::macro( 'getDomains', partial( [ $sitepress, 'get_setting' ], 'language_domains' ) ); } } LanguageNegotiation::init(); display-as-translated/class-wpml-display-as-translated-message-for-new-post.php 0000755 00000004534 14720342453 0023744 0 ustar 00 <?php class WPML_Display_As_Translated_Message_For_New_Post implements IWPML_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Notices $notices */ private $notices; public function __construct( SitePress $sitepress, WPML_Notices $notices ) { $this->sitepress = $sitepress; $this->notices = $notices; } public function add_hooks() { add_action( 'wp_loaded', array( $this, 'init' ), 10 ); } public function init() { if ( $this->is_display_as_translated_mode() && $this->current_language_is_not_default_language() ) { $notice = $this->notices->create_notice( __CLASS__, $this->get_notice_content() ); $notice->set_css_class_types( 'warning' ); $notice->set_dismissible( true ); $this->notices->add_notice( $notice ); } else { $this->notices->remove_notice( WPML_Notices::DEFAULT_GROUP, __CLASS__ ); } } public function get_notice_content() { $current_lang = $this->sitepress->get_language_details( $this->sitepress->get_current_language() ); $default_lang = $this->sitepress->get_language_details( $this->sitepress->get_default_language() ); $post_type_name = $this->get_post_type(); $post_type = get_post_type_object( $post_type_name ); $singular_name = strtolower( $post_type->labels->singular_name ); $plural_name = strtolower( $post_type->labels->name ); $output = esc_html( sprintf( __( "You are creating a %1\$s in %3\$s and you've set %2\$s to display even when not translated. Please note that this %1\$s will only appear in %3\$s. Only %2\$s that you create in the site's default language (%4\$s) will appear in all the site's languages.", 'sitepress' ), $singular_name, $plural_name, $current_lang['display_name'], $default_lang['display_name'] ) ); $output .= '<br /><br />'; $output .= '<a href="https://wpml.org/?page_id=1451509" target="_blank">' . esc_html__( 'Read how this works', 'sitepress' ) . '</a>'; return $output; } private function is_display_as_translated_mode() { return $this->sitepress->is_display_as_translated_post_type( $this->get_post_type() ); } private function current_language_is_not_default_language() { return $this->sitepress->get_current_language() != $this->sitepress->get_default_language(); } private function get_post_type() { return isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; } } display-as-translated/class-wpml-display-as-translated-default-lang-messages-factory.php 0000755 00000001265 14720342453 0025575 0 ustar 00 <?php class WPML_Display_As_Translated_Default_Lang_Messages_Factory extends WPML_Current_Screen_Loader_Factory { /** * @return WPML_Display_As_Translated_Default_Lang_Messages */ public function create_hooks() { global $sitepress; $template_service_loader = new WPML_Twig_Template_Loader( array( WPML_PLUGIN_PATH . '/templates/display-as-translated' ) ); return new WPML_Display_As_Translated_Default_Lang_Messages( $sitepress, new WPML_Display_As_Translated_Default_Lang_Messages_View( $template_service_loader->get_template() ) ); } /** @return string */ public function get_screen_regex() { return '/^sitepress-multilingual-cms\/menu\/languages$/'; } } display-as-translated/class-wpml-display-as-translated-message-for-new-post-factory.php 0000755 00000000744 14720342453 0025410 0 ustar 00 <?php class WPML_Display_As_Translated_Message_For_New_Post_Factory implements IWPML_Backend_Action_Loader { public function create() { global $pagenow, $sitepress; $notices = wpml_get_admin_notices(); if ( 'post-new.php' === $pagenow ) { return new WPML_Display_As_Translated_Message_For_New_Post( $sitepress, $notices ); } else { $notices->remove_notice( WPML_Notices::DEFAULT_GROUP, 'WPML_Display_As_Translated_Message_For_New_Post' ); return null; } } } display-as-translated/class-wpml-display-as-translated-snippet-filters.php 0000755 00000002073 14720342453 0023106 0 ustar 00 <?php class WPML_Display_As_Translated_Snippet_Filters implements IWPML_Action { public function add_hooks() { add_filter( 'wpml_should_use_display_as_translated_snippet', array( $this, 'filter_post_types' ), 10, 2 ); } public function filter_post_types( $should_use_snippet, array $post_type ) { return $should_use_snippet || $this->is_media_ajax_query( $post_type ) || $this->is_admin_media_list_page() || WPML_Ajax::is_frontend_ajax_request(); } private function is_admin_media_list_page() { if ( ! function_exists( 'get_current_screen' ) ) { return false; } $screen = get_current_screen(); if ( null !== $screen && 'upload' === $screen->base && 'list' === get_user_meta( get_current_user_id(), 'wp_media_library_mode', true ) ) { return true; } return false; } private function is_media_ajax_query( array $post_type ) { return false !== strpos( $_SERVER['REQUEST_URI'], 'admin-ajax' ) && isset( $_REQUEST['action'] ) && 'query-attachments' === $_REQUEST['action'] && array_key_exists( 'attachment', $post_type ); } } display-as-translated/class-wpml-display-as-translated-snippet-filters-factory.php 0000755 00000000343 14720342453 0024551 0 ustar 00 <?php class WPML_Display_As_Translated_Snippet_Filters_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { public function create() { return new WPML_Display_As_Translated_Snippet_Filters(); } } display-as-translated/class-wpml-fix-links-in-display-as-translated-content.php 0000755 00000004530 14720342453 0023736 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 28/10/17 * Time: 5:07 PM */ class WPML_Fix_Links_In_Display_As_Translated_Content implements IWPML_Action, IWPML_Frontend_Action, IWPML_DIC_Action { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Translate_Link_Targets $translate_link_targets */ private $translate_link_targets; public function __construct( SitePress $sitepress, WPML_Translate_Link_Targets $translate_link_targets ) { $this->sitepress = $sitepress; $this->translate_link_targets = $translate_link_targets; } public function add_hooks() { add_filter( 'the_content', array( $this, 'fix_fallback_links', ), WPML_LS_Render::THE_CONTENT_FILTER_PRIORITY - 1 ); } public function fix_fallback_links( $content ) { if ( stripos( $content, '<a' ) !== false ) { if ( $this->is_display_as_translated_content_type() ) { list( $content, $encoded_ls_links ) = $this->encode_language_switcher_links( $content ); $content = $this->translate_link_targets->convert_text( $content ); $content = $this->decode_language_switcher_links( $content, $encoded_ls_links ); } } return $content; } private function is_display_as_translated_content_type() { $queried_object = get_queried_object(); if ( isset( $queried_object->post_type ) ) { return $this->sitepress->is_display_as_translated_post_type( $queried_object->post_type ); } else { return false; } } private function encode_language_switcher_links( $content ) { $encoded_ls_links = array(); if ( preg_match_all( '/<a\s[^>]*class\s*=\s*"([^"]*)"[^>]*>/', $content, $matches ) ) { foreach ( $matches[1] as $index => $match ) { if ( strpos( $match, WPML_LS_Model_Build::LINK_CSS_CLASS ) !== false ) { $link = $matches[0][ $index ]; $encoded_link = md5( $link ); $encoded_ls_links[ $encoded_link ] = $link; $content = str_replace( $link, $encoded_link, $content ); } } } return array( $content, $encoded_ls_links ); } private function decode_language_switcher_links( $content, $encoded_ls_links ) { foreach ( $encoded_ls_links as $encoded => $link ) { $content = str_replace( $encoded, $link, $content ); } return $content; } } display-as-translated/class-wpml-display-as-translated-default-lang-messages.php 0000755 00000004344 14720342453 0024131 0 ustar 00 <?php class WPML_Display_As_Translated_Default_Lang_Messages { const PREVIOUS_LANG_KEY = 'wpml-previous-default-language'; /** * @var SitePress */ private $sitepress; /** * @var WPML_Display_As_Translated_Default_Lang_Messages_View */ private $view; public function __construct( SitePress $sitepress, WPML_Display_As_Translated_Default_Lang_Messages_View $view ) { $this->sitepress = $sitepress; $this->view = $view; } public function add_hooks() { if ( $this->should_display_message() ) { add_action( 'wpml_after_active_languages_display', array( $this, 'display_messages' ) ); add_action( 'icl_after_set_default_language', array( $this, 'save_previous_lang' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } } public function enqueue_scripts() { wp_enqueue_script( 'wpml-default-lang-change-message', ICL_PLUGIN_URL . '/res/js/display-as-translated/toggle-default-lang-change-message.js', array( 'jquery' ) ); } /** * @param string $prev_lang */ public function save_previous_lang( $prev_lang ) { update_option( self::PREVIOUS_LANG_KEY, $prev_lang ); } public function display_messages() { $previous_lang = get_option( self::PREVIOUS_LANG_KEY ); $this->view->display( $this->sitepress->get_display_language_name( $previous_lang ? $previous_lang : $this->sitepress->get_default_language() ), $this->sitepress->get_display_language_name( $this->sitepress->get_default_language() ) ); update_option( self::PREVIOUS_LANG_KEY, $this->sitepress->get_default_language() ); } /** * @return bool */ private function should_display_message() { $post_types = get_post_types(); $taxonomies = get_taxonomies(); foreach ( $post_types as $post_type ) { if ( $this->sitepress->is_display_as_translated_post_type( $post_type ) && get_posts( /** @phpstan-ignore-next-line get_posts() has no "post_type" key defined. */ array( 'post_type' => $post_type, 'posts_per_page' => 1, ) ) ) { return true; } } foreach ( $taxonomies as $taxonomy ) { if ( $this->sitepress->is_display_as_translated_taxonomy( $taxonomy ) && get_terms( array( 'taxonomy' => $taxonomy ) ) ) { return true; } } return false; } } display-as-translated/class-wpml-display-as-translated-default-lang-messages-view.php 0000755 00000002476 14720342453 0025105 0 ustar 00 <?php class WPML_Display_As_Translated_Default_Lang_Messages_View { const TEMPLATE = 'default-language-change.twig'; /** * @var WPML_Twig_Template */ private $template_service; public function __construct( WPML_Twig_Template $template_service ) { $this->template_service = $template_service; } /** * @param string $prev_default_lang * @param string $default_lang */ public function display( $prev_default_lang, $default_lang ) { echo $this->template_service->show( $this->get_model( $prev_default_lang, $default_lang ), self::TEMPLATE ); } /** * @param string $prev_default_lang * @param string $default_lang * * @return array */ private function get_model( $prev_default_lang, $default_lang ) { return array( 'before_message' => __( "Changing the site's default language can cause some content to disappear.", 'sitepress' ), 'after_message' => sprintf( __( "If some content appears gone, it might be because you switched the site's default language from %1\$s to %2\$s.", 'sitepress' ), $prev_default_lang, $default_lang ), 'help_text' => __( 'Tell me more', 'sitepress' ), 'help_link' => 'https://wpml.org/?page_id=1451509', 'got_it' => __( 'Got it', 'sitepress' ), 'lang_has_changed' => $prev_default_lang !== $default_lang, ); } } wpml-st/StringTranslationRequest.php 0000755 00000001235 14720342453 0013726 0 ustar 00 <?php namespace WPML\TM\StringTranslation; class StringTranslationRequest { /** * @param array $post clone of $_POST * @param callable $addStringsToBasket :: array $stringIds -> string $fromLang -> array $toLangs -> void */ public static function sendToTranslation( $post, callable $addStringsToBasket ) { $post = stripslashes_deep( $post ); $string_ids = explode( ',', $post['strings'] ); $translate_to = array(); foreach ( $post['translate_to'] as $lang_to => $one ) { $translate_to[ $lang_to ] = $lang_to; } if ( ! empty( $translate_to ) ) { $addStringsToBasket( $string_ids, $post['icl-tr-from'], $translate_to ); } } } wpml-st/class-wpml-remote-string-translation.php 0000755 00000017321 14720342453 0016106 0 ustar 00 <?php use WPML\TM\StringTranslation\Strings; use \WPML\TM\API\Basket; class WPML_Remote_String_Translation { public static function get_string_status_labels() { return array( ICL_TM_COMPLETE => __( 'Translation complete', 'wpml-translation-management' ), ICL_STRING_TRANSLATION_PARTIAL => __( 'Partial translation', 'wpml-translation-management' ), ICL_TM_NEEDS_UPDATE => __( 'Translation needs update', 'wpml-translation-management' ), ICL_TM_NOT_TRANSLATED => __( 'Not translated', 'wpml-translation-management' ), ICL_TM_WAITING_FOR_TRANSLATOR => __( 'Waiting for translator / In progress', 'wpml-translation-management' ), ICL_TM_IN_BASKET => __( 'Strings in the basket', 'wpml-translation-management' ), ICL_TM_TRANSLATION_READY_TO_DOWNLOAD => __('Translation ready to download', 'wpml-translation-management'), ); } public static function get_string_status_label( $status ) { $string_translation_states_enumeration = self::get_string_status_labels(); if ( isset( $string_translation_states_enumeration[ $status ] ) ) { return $string_translation_states_enumeration[ $status ]; } return false; } public static function translation_send_strings_local( $string_ids, $target, $translator_id = null, $basket_name = null ) { $batch_id = TranslationProxy_Batch::update_translation_batch( $basket_name ); foreach ( $string_ids as $string_id ) { $string_translation_id = icl_add_string_translation( $string_id, $target, null, ICL_TM_WAITING_FOR_TRANSLATOR, $translator_id, 'local', $batch_id ); if ( $string_translation_id ) { $job = new WPML_String_Translation_Job( $string_translation_id ); do_action( 'wpml_tm_local_string_sent', $job ); } } return 1; } public static function display_string_menu( $lang_filter ) { global $sitepress; $target_status = array(); $target_rate = array(); $lang_status = $sitepress->get_setting( 'icl_lang_status' ); $strings_target_languages = $sitepress->get_active_languages(); if ( $lang_status ) { foreach ( $lang_status as $lang ) { if ( $lang['from'] == $sitepress->get_current_language() ) { $target_status[ $lang['to'] ] = $lang['have_translators']; $target_rate[ $lang['to'] ] = $lang['max_rate']; } } } $useBasket = Basket::shouldUse(); $buttonText = $useBasket ? __( 'Add selected strings to translation basket', 'wpml-translation-management' ) : __( 'Translate', 'wpml-translation-management' ); $buttonIcon = $useBasket ? '<span class="otgs-ico-basket"></span>' : ''; ?> <form method="post" id="icl_st_send_strings" name="icl_st_send_strings" action=""> <input type="hidden" name="icl_st_action" value="send_strings"/> <input type="hidden" name="strings" value=""/> <input type="hidden" name="icl-tr-from" value="<?php echo $lang_filter; ?>"/> <input type="hidden" name="icl-basket-language" value="<?php echo TranslationProxy_Basket::get_source_language(); ?>"/> <table id="icl-tr-opt" class="widefat fixed" cellspacing="0" style="width:100%"> <thead> <tr> <th><h3><?php _e( 'Translate selected strings', 'wpml-translation-management' ) ?></h3></th> </tr> </thead> <tbody> <tr> <td> <table class="st-translate-strings-table"> <tbody> <tr> <td> <input type="checkbox" id="icl_st_translate_to_all" checked /> </td> <td><h4><?php _e( 'All languages', 'wpml-translation-management'); ?></h4></td> <td></td> </tr> </tbody> <tbody id="icl_tm_languages"> <?php foreach ( $strings_target_languages as $lang ) { if ( $lang['code'] == $lang_filter ) { continue; } $is_active_language = $sitepress->is_active_language( $lang['code'] ); $checked = checked( true, $is_active_language, false ); $label_class = $is_active_language ? 'active' : 'non-active' ?> <tr> <td> <input type="checkbox" id="translate_to[<?php echo $lang['code'] ?>]" name="translate_to[<?php echo $lang['code'] ?>]" value="1" id="icl_st_translate_to_<?php echo $lang['code'] ?>" <?php echo $checked; ?> data-language="<?php echo $lang['code'] ?>" /> </td> <td> <?php echo $sitepress->get_flag_image($lang['code']) ?> <label for="translate_to[<?php echo $lang['code'] ?>]" class="<?php echo $label_class; ?>"> <strong><?php echo $lang['display_name']; ?></strong> </label> </td> <td> <?php if ( isset( $target_status[ $lang['code'] ] ) && $target_status[ $lang['code'] ] ) { ?> <span style="display: none;" id="icl_st_max_rate_<?php echo $lang['code'] ?>"><?php echo $target_rate[ $lang['code'] ] ?></span> <span style="display: none;" id="icl_st_estimate_<?php echo $lang['code'] ?>_wrap" class="icl_st_estimate_wrap"> (<?php printf( __( 'Estimated cost: %s USD', 'wpml-translation-management' ), '<span id="icl_st_estimate_' . $lang['code'] . '">0</span>' ); ?>) </span> <?php } ?> </td> </tr> <?php }?> </tbody> </table> <?php echo wpml_nonce_field('icl-string-translation') ?> <button id="icl_send_strings" class="button-primary button-lg" type="submit" disabled="disabled" data-lang-not-active-message="<?php _e( 'One of the selected strings is in a language that is not activate. It can not be added to the translation basket.', 'wpml-translation-management' ); ?>" data-more-than-one-lang-message="<?php _e( 'Strings in different languages are selected. They can not be added to the translation basket.', 'wpml-translation-management' ); ?>" data-translation-basket-lang-message="<?php _e( 'You cannot add strings in this language to the basket since it already contains posts or strings of another source language! Either submit the current basket or delete the posts of differing language in the current basket', 'wpml-translation-management' ); ?>" ><?php echo $buttonIcon ?> <?php echo $buttonText ?></button> <div class="update-nag js-translation-message" style="display:none"></div> <div style="width: 45%; margin: auto"> <?php ICL_AdminNotifier::display_messages( 'string-translation-under-translation-options' ); ICL_AdminNotifier::remove_message( 'items_added_to_basket' ); ?> </div> </td> </tr> </tbody> </table> </form> <?php } public static function string_status_text_filter( $text, $string_id ) { if ( TranslationProxy_Basket::is_string_in_basket_anywhere( $string_id ) ) { $text = __( 'In the translation basket', 'wpml-translation-management' ); } else { global $wpdb; $translation_service = $wpdb->get_var( $wpdb->prepare( " SELECT translation_service FROM {$wpdb->prefix}icl_string_translations WHERE string_id = %d AND translation_service > 0 AND status IN (%d, %d) LIMIT 1 ", $string_id, ICL_TM_WAITING_FOR_TRANSLATOR, ICL_TM_IN_PROGRESS ) ); if ( $translation_service ) { $text = $text . " : " . sprintf( __( 'One or more strings sent to %s', 'wpml-translation-management' ), TranslationProxy::get_service_name( $translation_service ) ); } } return $text; } } seo/class-wpml-seo-headlangs.php 0000755 00000015406 14720342453 0012652 0 ustar 00 <?php class WPML_SEO_HeadLangs { private $sitepress; /** * WPML_SEO_HeadLangs constructor. * * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } private function get_seo_settings() { $seo_settings = $this->sitepress->get_setting( 'seo', array() ); if ( ! array_key_exists( 'head_langs', $seo_settings ) ) { $seo_settings['head_langs'] = 1; } if ( ! array_key_exists( 'head_langs_priority', $seo_settings ) ) { $seo_settings['head_langs_priority'] = 1; } return $seo_settings; } public function init_hooks() { if ( $this->sitepress->get_wp_api()->is_front_end() ) { $seo_settings = $this->get_seo_settings(); $head_langs = $seo_settings['head_langs']; if ( $head_langs ) { $priority = $seo_settings['head_langs_priority']; add_action( 'wp_head', array( $this, 'head_langs' ), (int) $priority ); } } } function head_langs() { $languages = $this->sitepress->get_ls_languages( array( 'skip_missing' => true ) ); /** * @since 3.4.0 */ $languages = apply_filters( 'wpml_head_langs', $languages ); if ( $this->must_render( $languages ) ) { $hreflang_items = []; $xdefault_href_lang = null; foreach ( $languages as $lang ) { /** * @since 3.3.7 */ $alternate_hreflang = apply_filters( 'wpml_alternate_hreflang', $lang['url'], $lang['code'] ); $hreflang_code = $this->get_hreflang_code( $lang ); if ( $hreflang_code ) { $hreflang_items[ $hreflang_code ] = str_replace( '&', '&', $alternate_hreflang ); if ( $this->sitepress->get_default_language() === $lang['code'] ) { $xdefault_href_lang = $hreflang_items[ $hreflang_code ]; } } } if ( $xdefault_href_lang ) { $hreflang_items['x-default'] = $xdefault_href_lang; } $hreflang_items = apply_filters( 'wpml_hreflangs', $hreflang_items ); $hreflang = ''; if ( is_array( $hreflang_items ) ) { foreach ( $hreflang_items as $hreflang_code => $hreflang_url ) { $hreflang .= '<link rel="alternate" hreflang="' . esc_attr( $hreflang_code ) . '" href="' . esc_url( $hreflang_url ) . '" />' . PHP_EOL; } echo apply_filters( 'wpml_hreflangs_html', $hreflang ); } } } function render_menu() { $seo = $this->get_seo_settings(); $options = array(); foreach ( array( 1, 10 ) as $priority ) { $label = __( 'As early as possible', 'sitepress' ); if ( $priority > 1 ) { $label = sprintf( esc_html__( 'Later in the head section (priority %d)', 'sitepress' ), $priority ); } $options[ $priority ] = array( 'selected' => ( $priority == $seo['head_langs_priority'] ), 'label' => $label, ); } ?> <div class="wpml-section wpml-section-seo-options" id="lang-sec-9-5"> <div class="wpml-section-header"> <h3><?php esc_html_e( 'SEO Options', 'sitepress' ); ?></h3> </div> <div class="wpml-section-content"> <form id="icl_seo_options" name="icl_seo_options" action=""> <?php wp_nonce_field( 'icl_seo_options_nonce', '_icl_nonce' ); ?> <p> <input type="checkbox" id="icl_seo_head_langs" name="icl_seo_head_langs" <?php if ( $seo['head_langs'] ) { echo 'checked="checked"'; } ?> value="1"/> <label for="icl_seo_head_langs"><?php esc_html_e( 'Display alternative languages in the HEAD section.', 'sitepress' ); ?></label> </p> <p> <label for="wpml-seo-head-langs-priority"><?php esc_html_e( 'Position of hreflang links', 'sitepress' ); ?></label> <select name="wpml_seo_head_langs_priority" id="wpml-seo-head-langs-priority" <?php if ( ! $seo['head_langs'] ) { echo 'disabled="disabled"'; } ?> > <?php foreach ( $options as $priority => $option ) { ?> <option value="<?php echo esc_html( (string) $priority ); ?>" <?php echo $option['selected'] ? 'selected="selected"' : ''; ?>><?php echo esc_html( $option['label'] ); ?></option> <?php } ?> </select> </p> <p class="buttons-wrap"> <span class="icl_ajx_response" id="icl_ajx_response_seo"> </span> <input class="button button-primary" name="save" value="<?php esc_attr_e( 'Save', 'sitepress' ); ?>" type="submit"/> </p> </form> </div> </div> <?php } private function must_render( $languages ) { $must_render = false; $wpml_queried_object = new WPML_Queried_Object( $this->sitepress ); $has_languages = is_array( $languages ) && count( $languages ) > 0; if ( $has_languages && ! $this->sitepress->get_wp_api()->is_paged() ) { if ( $wpml_queried_object->has_object() ) { if ( $wpml_queried_object->is_instance_of_post() ) { $post_id = $wpml_queried_object->get_id(); $is_single_or_page = $this->sitepress->get_wp_api()->is_single() || $this->sitepress->get_wp_api()->is_page(); $is_published = $is_single_or_page && $post_id && $this->sitepress->get_wp_api()->get_post_status( $post_id ) === 'publish'; $must_render = $this->sitepress->is_translated_post_type( $wpml_queried_object->get_post_type() ) && ( $is_published || $this->is_home_front_or_archive_page() ); } if ( $wpml_queried_object->is_instance_of_taxonomy() ) { $must_render = $this->sitepress->is_translated_taxonomy( $wpml_queried_object->get_taxonomy() ); } if ( $wpml_queried_object->is_instance_of_post_type() ) { $must_render = $this->sitepress->is_translated_post_type( $wpml_queried_object->get_post_type_name() ); } if ( $wpml_queried_object->is_instance_of_user() ) { $must_render = true; } } elseif ( $this->is_home_front_or_archive_page() ) { $must_render = true; } } return $must_render; } /** * @return bool */ private function is_home_front_or_archive_page() { return $this->sitepress->get_wp_api()->is_home() || $this->sitepress->get_wp_api()->is_front_page() || $this->sitepress->get_wp_api()->is_archive() || is_search(); } /** * @param array $lang * * @return string */ private function get_hreflang_code( $lang ) { $ordered_keys = [ 'tag', 'default_locale' ]; $hreflang_code = ''; foreach ( $ordered_keys as $key ) { if ( array_key_exists( $key, $lang ) && trim( $lang[ $key ] ) ) { $hreflang_code = $lang[ $key ]; break; } } $hreflang_code = strtolower( str_replace( '_', '-', $hreflang_code ) ); if ( $this->is_valid_hreflang_code( $hreflang_code ) ) { return trim( $hreflang_code ); } return ''; } private function is_valid_hreflang_code( $code ) { return strlen( trim( $code ) ) >= 2; } } upgrade/class-wpml-upgrade-command-definition.php 0000755 00000003706 14720342453 0016172 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Upgrade_Command_Definition { private $class_name; private $dependencies = array(); /** @var array Can be 'admin', 'ajax' or 'front-end' */ private $scopes = array(); private $method; /** @var callable|null */ private $factory_method; /** * WPML_Upgrade_Command_Definition constructor. * * @param string $class_name A class implementing \IWPML_Upgrade_Command. * @param array $dependencies An array of dependencies passed to the `$class_name`'s constructor. * @param array $scopes An array of scope values. Accepted values are: `\WPML_Upgrade::SCOPE_ADMIN`, `\WPML_Upgrade::SCOPE_AJAX`, and `\WPML_Upgrade::SCOPE_FRONT_END`. * @param string|null $method The method to call to run the upgrade (otherwise, it calls the "run" method), * @param callable $factory_method */ public function __construct( $class_name, array $dependencies, array $scopes, $method = null, callable $factory_method = null ) { $this->class_name = $class_name; $this->dependencies = $dependencies; $this->scopes = $scopes; $this->method = $method; $this->factory_method = $factory_method; } /** * @return array */ public function get_dependencies() { return $this->dependencies; } /** * @return string */ public function get_class_name() { return $this->class_name; } /** * @return string */ public function get_method() { return $this->method; } /** * @return array */ public function get_scopes() { return $this->scopes; } /** * @return callable|null */ public function get_factory_method() { return $this->factory_method; } /** * @return IWPML_Upgrade_Command */ public function create() { if ( $this->get_factory_method() ) { $factory_method = $this->get_factory_method(); return $factory_method(); } $class_name = $this->get_class_name(); return new $class_name( $this->get_dependencies() ); } } upgrade/class-wpml-upgrade-loader-factory.php 0000755 00000000501 14720342453 0015327 0 ustar 00 <?php class WPML_Upgrade_Loader_Factory implements IWPML_Backend_Action_Loader { public function create() { global $sitepress; return new WPML_Upgrade_Loader( $sitepress, wpml_get_upgrade_schema(), wpml_load_settings_helper(), wpml_get_admin_notices(), wpml_get_upgrade_command_factory() ); } } upgrade/class-wpml-upgrade-command-factory.php 0000755 00000001055 14720342453 0015504 0 ustar 00 <?php class WPML_Upgrade_Command_Factory { /** * @param string $class_name * @param array $dependencies * @param array $scopes * @param string|null $method * @param callable|null $factory_method * * @return WPML_Upgrade_Command_Definition */ public function create_command_definition( $class_name, array $dependencies, array $scopes, $method = null, callable $factory_method = null ) { return new WPML_Upgrade_Command_Definition( $class_name, $dependencies, $scopes, $method, $factory_method ); } } upgrade/class-wpml-upgrade-run-all.php 0000755 00000000655 14720342453 0014000 0 ustar 00 <?php abstract class WPML_Upgrade_Run_All implements IWPML_Upgrade_Command { /** @var bool $result */ protected $result = true; abstract protected function run(); public function run_admin() { return $this->run(); } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** @return bool */ public function get_results() { return $this->result; } } upgrade/class-wpml-upgrade-loader.php 0000755 00000020414 14720342453 0013667 0 ustar 00 <?php /** * WPML_Upgrade_Loader class file. * * @package WPML */ use WPML\Upgrade\Commands\AddContextIndexToStrings; use WPML\Upgrade\Commands\AddStatusIndexToStringTranslations; use WPML\Upgrade\Commands\AddStringPackageIdIndexToStrings; use WPML\Upgrade\Command\DisableOptionsAutoloading; use WPML\Upgrade\Commands\AddTranslationManagerCapToAdmin; use WPML\Upgrade\Commands\RemoveRestDisabledNotice; use WPML\Upgrade\Commands\DropCodeLocaleIndexFromLocaleMap; use WPML\Upgrade\Commands\AddPrimaryKeyToLocaleMap; use WPML\Upgrade\Commands\AddCountryColumnToLanguages; use WPML\Upgrade\Commands\AddAutomaticColumnToIclTranslateJob; use WPML\Upgrade\Commands\RemoveEndpointsOption; use WPML\TM\Upgrade\Commands\AddReviewStatusColumnToTranslationStatus; use WPML\TM\Upgrade\Commands\AddAteCommunicationRetryColumnToTranslationStatus; use WPML\TM\Upgrade\Commands\AddAteSyncCountToTranslationJob; use WPML\TM\Upgrade\Commands\ResetTranslatorOfAutomaticJobs; use WPML\Upgrade\Commands\CreateBackgroundTaskTable; use WPML\Upgrade\Commands\RemoveTmWcmlPromotionNotice; /** * Class WPML_Upgrade_Loader */ class WPML_Upgrade_Loader implements IWPML_Action { const TRANSIENT_UPGRADE_IN_PROGRESS = 'wpml_core_update_in_progress'; /** * SitePress instance. * * @var SitePress */ private $sitepress; /** * Upgrade Schema instance. * * @var WPML_Upgrade_Schema */ private $upgrade_schema; /** * Settings Helper instance. * * @var WPML_Settings_Helper */ private $settings; /** * Upgrade Command Factory instance. * * @var WPML_Upgrade_Command_Factory */ private $factory; /** * Notices instance. * * @var WPML_Notices */ private $notices; /** * WPML_Upgrade_Loader constructor. * * @param SitePress $sitepress SitePress instance. * @param WPML_Upgrade_Schema $upgrade_schema Upgrade schema instance. * @param WPML_Settings_Helper $settings Settings Helper instance. * @param WPML_Notices $wpml_notices Notices instance. * @param WPML_Upgrade_Command_Factory $factory Upgrade Command Factory instance. */ public function __construct( SitePress $sitepress, WPML_Upgrade_Schema $upgrade_schema, WPML_Settings_Helper $settings, WPML_Notices $wpml_notices, WPML_Upgrade_Command_Factory $factory ) { $this->sitepress = $sitepress; $this->upgrade_schema = $upgrade_schema; $this->settings = $settings; $this->notices = $wpml_notices; $this->factory = $factory; } /** * Add hooks. */ public function add_hooks() { add_action( 'wpml_loaded', array( $this, 'wpml_upgrade' ) ); register_activation_hook( WPML_PLUGIN_PATH . '/' . WPML_PLUGIN_FILE, array( $this, 'wpml_upgrade' ) ); } /** * Upgrade WPML plugin. */ public function wpml_upgrade() { if ( get_transient( self::TRANSIENT_UPGRADE_IN_PROGRESS ) ) { return; } $commands = [ $this->factory->create_command_definition( 'WPML_Upgrade_Localization_Files', [ $this->sitepress ], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Fix_Non_Admin_With_Admin_Cap', [], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Table_Translate_Job_For_3_9_0', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Remove_Translation_Services_Transient', [], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Display_Mode_For_Posts', [ $this->sitepress, $this->settings, $this->notices ], [ 'admin', 'ajax' ] ), $this->factory->create_command_definition( 'WPML_Add_UUID_Column_To_Translation_Status', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Element_Type_Length_And_Collation', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Add_Word_Count_Column_To_Strings', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Media_Without_Language', [ $this->upgrade_schema->get_wpdb(), $this->sitepress->get_default_language() ], [ 'admin', 'ajax' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Media_Duplication_In_Core', [ $this->sitepress, $this->upgrade_schema, $this->notices ], [ 'admin', 'ajax' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Chinese_Flags', [ 'wpdb' => $this->sitepress->wpdb() ], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Add_Editor_Column_To_Icl_Translate_Job', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_WPML_Site_ID', [], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_WPML_Site_ID_Remaining', [], [ 'admin' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Add_Location_Column_To_Strings', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Add_Wrap_Column_To_Translate', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( 'WPML_Upgrade_Add_Wrap_Column_To_Strings', [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddContextIndexToStrings::class, array( $this->upgrade_schema ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( AddStatusIndexToStringTranslations::class, array( $this->upgrade_schema ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( AddStringPackageIdIndexToStrings::class, array( $this->upgrade_schema ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( CreateBackgroundTaskTable::class, array( $this->upgrade_schema ), array( 'admin' ) ), $this->factory->create_command_definition( DisableOptionsAutoloading::class, [], [ 'admin' ] ), $this->factory->create_command_definition( RemoveRestDisabledNotice::class, [], [ 'admin' ] ), $this->factory->create_command_definition( ResetTranslatorOfAutomaticJobs::class, [], [ 'admin' ] ), $this->factory->create_command_definition( DropCodeLocaleIndexFromLocaleMap::class, array( $this->upgrade_schema ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( AddPrimaryKeyToLocaleMap::class, array( $this->upgrade_schema ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( AddCountryColumnToLanguages::class, [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddAutomaticColumnToIclTranslateJob::class, [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddTMAllowedOption::class, [], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddTranslationManagerCapToAdmin::class, [], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddReviewStatusColumnToTranslationStatus::class, [ $this->upgrade_schema ], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( AddAteCommunicationRetryColumnToTranslationStatus::class, [ $this->upgrade_schema ], [ 'admin', 'ajax' ] ), $this->factory->create_command_definition( AddAteSyncCountToTranslationJob::class, [ $this->upgrade_schema ], [ 'admin', 'ajax' ] ), $this->factory->create_command_definition( 'WPML_TM_Add_TP_ID_Column_To_Translation_Status', [ $this->upgrade_schema ], array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( 'WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Translation_Status', [ $this->upgrade_schema ], array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( 'WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Core_Status', [ $this->upgrade_schema ], array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( RemoveEndpointsOption::class, [], [ 'admin', 'ajax', 'front-end' ] ), $this->factory->create_command_definition( RemoveTmWcmlPromotionNotice::class, [], [ 'admin' ] ), ]; $upgrade = new WPML_Upgrade( $commands, $this->sitepress, $this->factory ); $upgrade->run(); } } upgrade/class-wpml-upgrade-localizations-files.php 0000755 00000001745 14720342453 0016402 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Upgrade_Localization_Files implements IWPML_Upgrade_Command { private $download_localization; private $results = null; /** @var SitePress */ private $sitepress; /** * WPML_Upgrade_Localization_Files constructor. * * @param array $args */ public function __construct( array $args ) { $this->sitepress = $args[0]; $this->download_localization = new WPML_Download_Localization( $this->sitepress->get_active_languages(), $this->sitepress->get_default_language() ); } public function run_admin() { if ( ! $this->sitepress->get_wp_api()->is_back_end() ) { return false; } $this->results = $this->download_localization->download_language_packs(); return true; } public function run_ajax() { return false; } public function run_frontend() { return false; } public function get_command_id() { return 'wpml-upgrade-localization-files'; } public function get_results() { return $this->results; } } upgrade/interface-iwpml-upgrade-command.php 0000755 00000000253 14720342453 0015042 0 ustar 00 <?php interface IWPML_Upgrade_Command { public function run_admin(); public function run_ajax(); public function run_frontend(); public function get_results(); } upgrade/class-wpml-upgrade-schema.php 0000755 00000012014 14720342453 0013656 0 ustar 00 <?php class WPML_Upgrade_Schema { /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string $table_name * * @return bool */ public function does_table_exist( $table_name ) { return $this->has_results( $this->wpdb->get_results( "SHOW TABLES LIKE '{$this->wpdb->prefix}{$table_name}'" ) ); } /** * @param string $table_name * @param string $column_name * * @return bool */ public function does_column_exist( $table_name, $column_name ) { return $this->has_results( $this->wpdb->get_results( "SHOW COLUMNS FROM {$this->wpdb->prefix}{$table_name} LIKE '{$column_name}'" ) ); } /** * @param string $table_name * @param string $index_name * * @return bool */ public function does_index_exist( $table_name, $index_name ) { return $this->has_results( $this->wpdb->get_results( "SHOW INDEXES FROM {$this->wpdb->prefix}{$table_name} WHERE key_name = '{$index_name}'" ) ); } /** * @param string $table_name * @param string $key_name * * @return bool */ public function does_key_exist( $table_name, $key_name ) { return $this->has_results( $this->wpdb->get_results( "SHOW KEYS FROM {$this->wpdb->prefix}{$table_name} WHERE key_name = '{$key_name}'" ) ); } private function has_results( $results ) { return is_array( $results ) && count( $results ); } /** * @param string $table_name * @param string $column_name * @param string $attribute_string * * @return false|int */ public function add_column( $table_name, $column_name, $attribute_string ) { return $this->wpdb->query( "ALTER TABLE {$this->wpdb->prefix}{$table_name} ADD `{$column_name}` {$attribute_string}" ); } /** * @param string $table_name * @param string $column_name * @param string $attribute_string * * @return false|int */ public function modify_column( $table_name, $column_name, $attribute_string ) { return $this->wpdb->query( "ALTER TABLE {$this->wpdb->prefix}{$table_name} MODIFY COLUMN `{$column_name}` {$attribute_string}" ); } /** * @param string $table_name * @param string $index_name * @param string $attribute_string * * @return false|int */ public function add_index( $table_name, $index_name, $attribute_string ) { return $this->wpdb->query( "ALTER TABLE {$this->wpdb->prefix}{$table_name} ADD INDEX `{$index_name}` {$attribute_string}" ); } /** * @param string $table_name * @param array $key_columns * * @return false|int */ public function add_primary_key( $table_name, $key_columns ) { return $this->wpdb->query( "ALTER TABLE {$this->wpdb->prefix}{$table_name} ADD PRIMARY KEY (`" . implode( '`, `', $key_columns ) . '`)' ); } /** * @param string $table_name * @param string $index_name * * @return false|int */ public function drop_index( $table_name, $index_name ) { return $this->wpdb->query( "ALTER TABLE {$this->wpdb->prefix}{$table_name} DROP INDEX `{$index_name}`" ); } /** * @param string $table_name * @param string $column_name * * @return null|string */ public function get_column_collation( $table_name, $column_name ) { return $this->wpdb->get_var( "SELECT COLLATION_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '{$this->wpdb->dbname}' AND TABLE_NAME = '{$this->wpdb->prefix}{$table_name}' AND COLUMN_NAME = '{$column_name}'" ); } /** * @param string $table_name * * @return string|null */ public function get_table_collation( $table_name ) { $table_data = $this->wpdb->get_row( $this->wpdb->prepare( 'SHOW TABLE status LIKE %s', $table_name ) ); if ( isset( $table_data->Collation ) ) { return $table_data->Collation; } return null; } /** * We try to get the collation from the posts table first. * * @return string|null */ public function get_default_collate() { $posts_table_collate = $this->get_table_collation( $this->wpdb->posts ); if ( $posts_table_collate ) { return $posts_table_collate; } elseif ( ! empty( $this->wpdb->collate ) ) { return $this->wpdb->collate; } return null; } /** * @param string $table_name * * @return string|null */ public function get_table_charset( $table_name ) { try { return $this->wpdb->get_var( $this->wpdb->prepare( 'SELECT CCSA.character_set_name FROM information_schema.`TABLES` T, information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA WHERE CCSA.collation_name = T.table_collation AND T.table_schema = "%s" AND T.table_name = "%s";', $this->wpdb->dbname, $table_name ) ); } catch ( Exception $e ) { return null; } } /** * We try to get the charset from the posts table first. * * @return string|null */ public function get_default_charset() { $post_table_charset = $this->get_table_charset( $this->wpdb->posts ); if ( $post_table_charset ) { return $post_table_charset; } elseif ( ! empty( $this->wpdb->charset ) ) { return $this->wpdb->charset; } return null; } /** * @return wpdb */ public function get_wpdb() { return $this->wpdb; } } upgrade/commands/ResetTranslatorOfAutomaticJobs.php 0000755 00000003275 14720342453 0016631 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class ResetTranslatorOfAutomaticJobs implements \IWPML_Upgrade_Command { /** @var bool $result */ private $result = false; public function run_admin() { global $wpdb; $automatic_column_exists = $wpdb->get_var( "SHOW COLUMNS FROM `{$wpdb->prefix}icl_translate_job` LIKE 'automatic'" ); if ( ! $automatic_column_exists ) { // No need to reset translator of automatic jobs // as this site never used automatic translation. // Return true to mark this upgrade as done. return true; } $subquery = " SELECT job_id, rid FROM {$wpdb->prefix}icl_translate_job WHERE job_id IN ( SELECT MAX(job_id) FROM {$wpdb->prefix}icl_translate_job GROUP BY rid ) AND automatic = 1 "; $rowsToUpdate = $wpdb->get_results( $subquery ); if ( count( $rowsToUpdate ) ) { $rids = \wpml_prepare_in( array_column( $rowsToUpdate, 'rid' ), '%d' ); $sql = " UPDATE {$wpdb->prefix}icl_translation_status translation_status SET translation_status.translator_id = 0 WHERE translation_status.rid IN ( $rids ) "; $wpdb->query( $sql ); $jobIds = \wpml_prepare_in( array_column( $rowsToUpdate, 'job_id' ), '%d' ); $sql = " UPDATE {$wpdb->prefix}icl_translate_job SET translator_id = 0 WHERE job_id IN ( $jobIds ) "; $wpdb->query( $sql ); } $this->result = true; return $this->result; } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/RemoveTmWcmlPromotionNotice.php 0000755 00000000553 14720342453 0016151 0 ustar 00 <?php namespace WPML\Upgrade\Commands; use ICL_AdminNotifier; class RemoveTmWcmlPromotionNotice implements \IWPML_Upgrade_Command { public function run_admin() { ICL_AdminNotifier::remove_message( 'promote-wcml' ); return true; } public function run_ajax() {} public function run_frontend() {} public function get_results() { return true; } } upgrade/commands/class-wpml-upgrade-table-translate-job-for-3-9-0.php 0000755 00000002110 14720342453 0021274 0 ustar 00 <?php class WPML_Upgrade_Table_Translate_Job_For_3_9_0 implements IWPML_Upgrade_Command { /** @var bool $result */ private $result = true; /** @var WPML_Upgrade_Schema */ private $upgrade_schema; public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** @return bool */ private function run() { $table = 'icl_translate_job'; $columns = array( 'title' => 'VARCHAR(160) NULL', 'deadline_date' => 'DATETIME NULL', 'completed_date' => 'DATETIME NULL', ); if ( $this->upgrade_schema->does_table_exist( $table ) ) { foreach ( $columns as $column => $attribute_string ) { if ( ! $this->upgrade_schema->does_column_exist( $table, $column ) ) { $this->upgrade_schema->add_column( $table, $column, $attribute_string ); } } } return $this->result; } public function run_admin() { return $this->run(); } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** @return bool */ public function get_results() { return $this->result; } } upgrade/commands/class-wpml-upgrade-add-location-column-to-strings.php 0000755 00000001211 14720342453 0022154 0 ustar 00 <?php /** * Upgrade 'icl_strings' table by adding 'location' column. * * @package WPML */ /** * Class WPML_Upgrade_Add_Location_Column_To_Strings */ class WPML_Upgrade_Add_Location_Column_To_Strings extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_strings'; } /** * Get column name. * * @return string */ protected function get_column() { return 'location'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'BIGINT unsigned NULL AFTER `string_package_id`'; } } upgrade/commands/RefreshTranslationServices.php 0000755 00000002371 14720342453 0016040 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; use WPML\TM\Menu\TranslationServices\Troubleshooting\RefreshServices; use WPML\TM\Menu\TranslationServices\Troubleshooting\RefreshServicesFactory; class RefreshTranslationServices implements \IWPML_Upgrade_Command { const WPML_VERSION_SINCE_PREVIEW_LOGOS_AVAILABLE = '4.4.0'; /** @var bool $result */ private $result = false; /** @var RefreshServicesFactory */ private $refreshServicesFactory; /** @var callable */ private $isHigherThanInstallationVersion; public function __construct( array $args ) { $this->refreshServicesFactory = $args[0]; $this->isHigherThanInstallationVersion = $args[1]; } /** * @return bool */ public function run_admin() { $this->result = true; if ( call_user_func( $this->isHigherThanInstallationVersion, self::WPML_VERSION_SINCE_PREVIEW_LOGOS_AVAILABLE ) ) { $this->result = $this->refreshServicesFactory->create_an_instance()->refresh_services(); } return $this->result; } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/CreateAteDownloadQueueTable.php 0000755 00000002455 14720342453 0016024 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; use SitePress_Setup; class CreateAteDownloadQueueTable implements \IWPML_Upgrade_Command { const TABLE_NAME = 'icl_translation_downloads'; /** @var \WPML_Upgrade_Schema $schema */ private $schema; /** @var bool $result */ private $result = false; public function __construct( array $args ) { $this->schema = $args[0]; } /** * @return bool */ public function run() { $wpdb = $this->schema->get_wpdb(); $tableName = $wpdb->prefix . self::TABLE_NAME; $charsetCollate = SitePress_Setup::get_charset_collate(); $query = " CREATE TABLE IF NOT EXISTS `{$tableName}` ( `editor_job_id` BIGINT(20) UNSIGNED NOT NULL, `download_url` VARCHAR(2000) NOT NULL, `lock_timestamp` INT(11) UNSIGNED NULL, PRIMARY KEY (`editor_job_id`) ) ENGINE=INNODB {$charsetCollate}; "; $this->result = $wpdb->query( $query ); return $this->result; } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/class-wpml-upgrade-display-mode-for-posts.php 0000755 00000010236 14720342453 0020544 0 ustar 00 <?php class WPML_Upgrade_Display_Mode_For_Posts implements IWPML_Upgrade_Command { const DISPLAY_MODE_SETTING = 'show_untranslated_blog_posts'; /** @var SitePress */ private $sitepress; /** @var WPML_Settings_Helper */ private $settings; /** @var WPML_Notices */ private $wpml_notices; public function __construct( array $args ) { $this->sitepress = $args[0]; $this->settings = $args[1]; $this->wpml_notices = $args[2]; } /** * @return bool */ public function run_admin() { if ( $this->sitepress->get_setting( self::DISPLAY_MODE_SETTING ) ) { add_action( 'init', [ $this, 'add_notice' ] ); return false; } else { return true; } } public function add_notice() { $notice = $this->wpml_notices->create_notice( __CLASS__, $this->get_notice_content() ); $notice->add_display_callback( array( 'WPML_Notice_Show_On_Dashboard_And_WPML_Pages', 'is_on_page' ) ); $notice->set_css_class_types( 'info' ); $this->wpml_notices->add_notice( $notice ); } /** * @return bool */ public function run_ajax() { if ( isset( $_POST['mode'] ) ) { if ( 'translate' === $_POST['mode'] ) { $this->settings->set_post_type_translatable( 'post' ); $this->sitepress->set_setting( self::DISPLAY_MODE_SETTING, false, true ); $this->wpml_notices->remove_notice( 'default', __CLASS__ ); return true; } if ( 'display-as-translated' === $_POST['mode'] ) { $this->settings->set_post_type_display_as_translated( 'post' ); $this->sitepress->set_setting( self::DISPLAY_MODE_SETTING, false, true ); $this->wpml_notices->remove_notice( 'default', __CLASS__ ); return true; } } return false; } /** * @return bool */ public function run_frontend() { return false; } /** * @return array */ public function get_results() { return array(); } private function get_notice_content() { ob_start(); $action = str_replace( '_', '-', strtolower( __CLASS__ ) ); $setup_url = WPML_Admin_URL::multilingual_setup( 7 ); ?> <div class="js-main-content"> <h2><?php esc_html_e( 'Display mode for blog posts has changed', 'sitepress' ); ?></h2> <p><?php esc_html_e( 'Until now, your site was set to display "all blog posts", even if they are not translated. That feature is now replaced with a better and more complete translation mode.', 'sitepress' ); ?></p> <p><?php esc_html_e( "Which blog posts do you want to display on your site's translations?", 'sitepress' ); ?></p> <p><label><input type="radio" name="mode" value="display-as-translated"/> <?php esc_html_e( "Blog posts from the site's default language, or translations when they exist", 'sitepress' ); ?> </label></p> <p><label><input type="radio" name="mode" value="translate"/> <?php esc_html_e( "Only translated blog posts (never display posts from the default language on translation languages)", 'sitepress' ); ?> </label></p> <input type="button" class="button-primary" name="save" value="<?php esc_attr_e( 'Save' ); ?>" disabled="disabled"/> <?php wp_nonce_field( $action . '-nonce', $action . '-nonce' ); ?> </div> <div class="js-thankyou-content" style="display: none"> <p><?php echo sprintf( esc_html__( 'Thank you for choosing. You can always change your selection in %sPost Types Translation setup%s.', 'sitepress' ), '<a href="' . $setup_url . '">', '</a>' ); ?></p> </div> <script> jQuery( document ).ready( function ( $ ) { $( '.js-main-content' ).find( 'input[name=mode]' ).on( 'change', function ( e ) { $( '.js-main-content' ).find( 'input[name=save]' ).prop( 'disabled', false ); } ); $( '.js-main-content' ).find( 'input[name=save]' ).on( 'click', function ( e ) { $( this ).prop( 'disabled', true ); $.ajax( { url: ajaxurl, type: "POST", data: { action: '<?php echo $action; ?>', nonce: $( '#<?php echo $action; ?>-nonce' ).val(), mode: $( 'input[name=mode]:checked', '.js-main-content' ).val() }, success: function ( response ) { $( '.js-main-content' ).hide(); $( '.js-thankyou-content' ).show(); } } ); } ); } ); </script> <?php return ob_get_clean(); } } upgrade/commands/class-wpml-tm-upgrade-service-redirect-to-field.php 0000755 00000002221 14720342453 0021574 0 ustar 00 <?php use WPML\TM\Menu\TranslationServices\Troubleshooting\RefreshServices; use WPML\TM\Menu\TranslationServices\Troubleshooting\RefreshServicesFactory; class WPML_TM_Upgrade_Service_Redirect_To_Field implements IWPML_Upgrade_Command { /** @var bool $result */ private $result = true; /** @var RefreshServices */ private $service_refresh; public function __construct( $args ) { if ( isset( $args[0] ) && $args[0] instanceof RefreshServices ) { $this->service_refresh = $args[0]; } } /** * Add the default terms for Translation Priority taxonomy * * @return bool */ private function run() { $this->result = $this->get_service_refresh()->refresh_services(); return $this->result; } public function run_admin() { return $this->run(); } public function run_ajax() { } public function run_frontend() { } /** @return bool */ public function get_results() { return $this->result; } private function get_service_refresh() { if ( ! $this->service_refresh ) { $factory = new RefreshServicesFactory(); $this->service_refresh = $factory->create_an_instance(); } return $this->service_refresh; } } upgrade/commands/class-wpml-add-uuid-column-to-translation-status.php 0000755 00000001167 14720342453 0022065 0 ustar 00 <?php /** * Upgrade 'icl_translation_status' table by adding 'uuid' column. * * @package WPML */ /** * Class WPML_Add_UUID_Column_To_Translation_Status */ class WPML_Add_UUID_Column_To_Translation_Status extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_translation_status'; } /** * Get column name. * * @return string */ protected function get_column() { return 'uuid'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'VARCHAR(36) NULL'; } } upgrade/commands/AddTMAllowedOption.php 0000755 00000000340 14720342453 0014143 0 ustar 00 <?php use WPML\Plugins; class AddTMAllowedOption extends WPML_Upgrade_Run_All { /** * @return bool */ public function run() { add_action('init', [ Plugins::class, 'updateTMAllowedOption' ] ); return true; } } upgrade/commands/DropCodeLocaleIndexFromLocaleMap.php 0000755 00000000347 14720342453 0016731 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class DropCodeLocaleIndexFromLocaleMap extends DropIndexFromTable { protected function get_table() { return 'icl_locale_map'; } protected function get_index() { return 'code'; } } upgrade/commands/AddAteSyncCountToTranslationJob.php 0000755 00000000664 14720342453 0016667 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class AddAteSyncCountToTranslationJob extends \WPML_Upgrade_Add_Column_To_Table { /** * @return string */ protected function get_table() { return 'icl_translate_job'; } /** * @return string */ protected function get_column() { return 'ate_sync_count'; } /** * @return string */ protected function get_column_definition() { return "INT(6) UNSIGNED DEFAULT 0"; } } upgrade/commands/RemoveRestDisabledNotice.php 0000755 00000000541 14720342453 0015401 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class RemoveRestDisabledNotice implements \IWPML_Upgrade_Command { public function run_admin() { wpml_get_admin_notices()->remove_notice( 'default', 'rest-disabled' ); return true; } public function run_ajax() {} public function run_frontend() {} public function get_results() { return true; } } upgrade/commands/AddContextIndexToStrings.php 0000755 00000000447 14720342453 0015423 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddContextIndexToStrings extends AddIndexToTable { protected function get_table() { return 'icl_strings'; } protected function get_index() { return 'context'; } protected function get_index_definition() { return '( `context` )'; } } upgrade/commands/wpml-tm-upgrade-cancel-orphan-jobs.php 0000755 00000002342 14720342453 0017202 0 ustar 00 <?php class WPML_TM_Upgrade_Cancel_Orphan_Jobs implements IWPML_Upgrade_Command { /** @var WPML_TP_Sync_Orphan_Jobs_Factory */ private $factory; /** @var WPML_TM_Jobs_Migration_State */ private $migration_state; /** * @param array $args */ public function __construct( array $args ) { if ( ! isset( $args[0] ) || ! $args[0] instanceof WPML_TP_Sync_Orphan_Jobs_Factory ) { throw new InvalidArgumentException( 'The factory class must be passed as the first argument in the constructor' ); } if ( ! isset( $args[1] ) || ! $args[1] instanceof WPML_TM_Jobs_Migration_State ) { throw new InvalidArgumentException( 'The WPML_TM_Jobs_Migration_State class must be passed as the second argument in the constructor' ); } $this->factory = $args[0]; $this->migration_state = $args[1]; } /** * @return bool */ public function run_admin() { if ( ! $this->migration_state->is_migrated() ) { return false; } $this->factory->create()->cancel_orphans(); return true; } /** * @return null */ public function run_ajax() { return null; } /** * @return null */ public function run_frontend() { return null; } /** * @return null */ public function get_results() { return null; } } upgrade/commands/class-wpml-tm-upgrade-translation-priorities-for-posts.php 0000755 00000001636 14720342453 0023324 0 ustar 00 <?php class WPML_TM_Upgrade_Translation_Priorities_For_Posts implements IWPML_Upgrade_Command { /** @var bool $result */ private $result = true; const TRANSLATION_PRIORITY_TAXONOMY = 'translation_priority'; /** * Add the default terms for Translation Priority taxonomy * * @return bool */ private function run() { $translation_priorities_factory = new WPML_TM_Translation_Priorities_Factory(); $translation_priorities_actions = $translation_priorities_factory->create(); $translation_priorities_actions->register_translation_priority_taxonomy(); WPML_TM_Translation_Priorities::insert_missing_default_terms(); return $this->result; } public function run_admin() { return $this->run(); } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** @return bool */ public function get_results() { return $this->result; } } upgrade/commands/class-wpml-upgrade-wpml-site-id.php 0000755 00000002323 14720342453 0016534 0 ustar 00 <?php /** * Upgrades the former option to the new one. */ class WPML_Upgrade_WPML_Site_ID implements IWPML_Upgrade_Command { /** * Runs the upgrade process. * * @return bool */ public function run() { if ( $this->old_option_exists() ) { $value_from_old_option = get_option( WPML_Site_ID::SITE_ID_KEY, null ); update_option( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_Site_ID::SITE_SCOPES_GLOBAL, $value_from_old_option, false ); return delete_option( WPML_Site_ID::SITE_ID_KEY ); } return true; } /** * Checks has the old option. * * @return bool */ protected function old_option_exists() { get_option( WPML_Site_ID::SITE_ID_KEY, null ); $notoptions = wp_cache_get( 'notoptions', 'options' ); return false === $notoptions || ! array_key_exists( WPML_Site_ID::SITE_ID_KEY, $notoptions ); } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * Unused. * * @return null */ public function get_results() { return null; } } upgrade/commands/AddReviewStatusColumnToTranslationStatus.php 0000755 00000000723 14720342453 0020700 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class AddReviewStatusColumnToTranslationStatus extends \WPML_Upgrade_Add_Column_To_Table { /** * @return string */ protected function get_table() { return 'icl_translation_status'; } /** * @return string */ protected function get_column() { return 'review_status'; } /** * @return string */ protected function get_column_definition() { return "ENUM('NEEDS_REVIEW', 'EDITING', 'ACCEPTED')"; } } upgrade/commands/AddTranslationManagerCapToAdmin.php 0000755 00000000610 14720342453 0016613 0 ustar 00 <?php namespace WPML\Upgrade\Commands; use WPML\LIB\WP\User; class AddTranslationManagerCapToAdmin extends \WPML_Upgrade_Run_All { protected function run() { get_role( 'administrator' )->add_cap( User::CAP_MANAGE_TRANSLATIONS ); do_action( 'wpml_tm_ate_synchronize_managers' ); wp_get_current_user()->get_role_caps(); // Refresh the current user capabilities. return true; } } upgrade/commands/MigrateAteRepository.php 0000755 00000005173 14720342453 0014644 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class MigrateAteRepository implements \IWPML_Upgrade_Command { const TABLE_NAME = 'icl_translate_job'; const COLUMN_EDITOR_JOB_ID = 'editor_job_id'; const COLUMN_EDIT_TIMESTAMP = 'edit_timestamp'; const OPTION_NAME_REPO = 'WPML_TM_ATE_JOBS'; /** @var \WPML_Upgrade_Schema $schema */ private $schema; /** @var bool $result */ private $result = false; public function __construct( array $args ) { $this->schema = $args[0]; } /** * @return bool */ public function run() { $this->result = $this->addColumnsToJobsTable(); if ( $this->result ) { $this->migrateOldRepository(); } return $this->result; } private function addColumnsToJobsTable() { $result = true; if ( ! $this->schema->does_column_exist( self::TABLE_NAME, self::COLUMN_EDITOR_JOB_ID ) ) { $result = $this->schema->add_column( self::TABLE_NAME, self::COLUMN_EDITOR_JOB_ID, 'bigint(20) unsigned NULL' ); } return $result; } private function migrateOldRepository() { $records = get_option( self::OPTION_NAME_REPO ); if ( is_array( $records ) && $records ) { $wpdb = $this->schema->get_wpdb(); $recordPairs = wpml_collect( array_keys( $records ) )->zip( $records ); $ateJobIdCases = $recordPairs->reduce( $this->getCasesReducer(), '' ) . "ELSE 0\n"; $sql = " UPDATE {$wpdb->prefix}" . self::TABLE_NAME . " SET " . self::COLUMN_EDITOR_JOB_ID . " = ( CASE job_id " . $ateJobIdCases . " END ) WHERE " . self::COLUMN_EDITOR_JOB_ID . " IS NULL AND job_id IN(" . wpml_prepare_in( array_keys( $records ), '%d' ) . ") "; $wpdb->query( $sql ); } $this->disableAutoloadOnOldOption(); } /** * @param string $field * * @return \Closure */ private function getCasesReducer() { $wpdb = $this->schema->get_wpdb(); return function( $cases, $data ) use ( $wpdb ) { $cases .= isset( $data[1]['ate_job_id'] ) ? $wpdb->prepare( "WHEN %d THEN %d\n", $data[0], $data[1]['ate_job_id'] ) : ''; return $cases; }; } private function disableAutoloadOnOldOption() { $wpdb = $this->schema->get_wpdb(); $wpdb->update( $wpdb->options, [ 'autoload' => 'no' ], [ 'option_name' => self::OPTION_NAME_REPO ] ); } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/AddAteCommunicationRetryColumnToTranslationStatus.php 0000755 00000000722 14720342453 0022517 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class AddAteCommunicationRetryColumnToTranslationStatus extends \WPML_Upgrade_Add_Column_To_Table { /** * @return string */ protected function get_table() { return 'icl_translation_status'; } /** * @return string */ protected function get_column() { return 'ate_comm_retry_count'; } /** * @return string */ protected function get_column_definition() { return "INT(11) UNSIGNED DEFAULT 0"; } } upgrade/commands/AddAutomaticColumnToIclTranslateJob.php 0000755 00000001013 14720342453 0017470 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddAutomaticColumnToIclTranslateJob extends \WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_translate_job'; } /** * Get column name. * * @return string */ protected function get_column() { return 'automatic'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'TINYINT UNSIGNED NOT NULL DEFAULT 0'; } } upgrade/commands/class-wpml-upgrade-add-editor-column-to-icl-translate-job.php 0000755 00000001203 14720342453 0023454 0 ustar 00 <?php /** * Upgrade 'icl_translate_job' table by adding 'editor' column. * * @package WPML */ /** * Class WPML_Upgrade_Add_Editor_Column_To_Icl_Translate_Job */ class WPML_Upgrade_Add_Editor_Column_To_Icl_Translate_Job extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_translate_job'; } /** * Get column name. * * @return string */ protected function get_column() { return 'editor'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'VARCHAR(16) NULL'; } } upgrade/commands/AddStatusIndexToStringTranslations.php 0000755 00000000473 14720342453 0017500 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddStatusIndexToStringTranslations extends AddIndexToTable { protected function get_table() { return 'icl_string_translations'; } protected function get_index() { return 'status'; } protected function get_index_definition() { return '( `status` )'; } } upgrade/commands/class-wpml-upgrade-add-wrap-column-to-strings.php 0000755 00000001166 14720342453 0021326 0 ustar 00 <?php /** * Upgrade 'icl_strings' table by adding 'wrap' column. * * @package WPML */ /** * Class WPML_Upgrade_Add_Wrap_Column_To_Strings */ class WPML_Upgrade_Add_Wrap_Column_To_Strings extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_strings'; } /** * Get column name. * * @return string */ protected function get_column() { return 'wrap_tag'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'VARCHAR( 16 ) NOT NULL AFTER `location`'; } } upgrade/commands/wpml-upgrade-chinese-flags.php 0000755 00000002416 14720342453 0015631 0 ustar 00 <?php class WPML_Upgrade_Chinese_Flags implements IWPML_Upgrade_Command { private $wpdb; /** * WPML_Upgrade_Chinese_Flags constructor. * * @param array $args { * 'wpdb' => @type wpdb * } */ public function __construct( array $args ) { $this->wpdb = $args['wpdb']; } public function run() { $codes = array( 'zh-hans', 'zh-hant' ); $flags_query = 'SELECT id, lang_code, flag FROM ' . $this->wpdb->prefix . 'icl_flags WHERE lang_code IN (' . wpml_prepare_in( $codes ) . ')'; $flags = $this->wpdb->get_results( $flags_query ); if ( $flags ) { foreach ( $flags as $flag ) { if ( $this->must_update( $flag ) ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_flags', array( 'flag' => 'zh.png', ), array( 'id' => $flag->id ), array( '%s' ), array( '%d' ) ); } } } return true; } /** * @param \stdClass $flag * * @return bool */ protected function must_update( $flag ) { return $flag->flag === $flag->lang_code . '.png'; } public function run_admin() { return $this->run(); } public function run_ajax() { return null; } public function run_frontend() { return null; } public function get_results() { return null; } } upgrade/commands/class-wpml-tm-upgrade-default-editor-for-old-jobs.php 0000755 00000001556 14720342453 0022051 0 ustar 00 <?php class WPML_TM_Upgrade_Default_Editor_For_Old_Jobs implements IWPML_Upgrade_Command { /** @var SitePress */ private $sitepress; public function __construct( $args ) { $this->sitepress = $args[0]; } /** * @return bool */ private function run() { $default = get_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME ); if ( ! $default ) { $method = $this->sitepress->get_setting( 'doc_translation_method' ); $default = WPML_TM_Editors::ATE === strtolower( $method ) ? WPML_TM_Editors::ATE : WPML_TM_Editors::WPML; update_option( WPML_TM_Old_Jobs_Editor::OPTION_NAME, $default ); } return true; } public function run_admin() { return $this->run(); } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** @return bool */ public function get_results() { return true; } } upgrade/commands/class-wpml-upgrade-add-wrap-column-to-translate.php 0000755 00000001220 14720342453 0021621 0 ustar 00 <?php /** * Upgrade 'icl_translate' table by adding 'field_wrap_tag' column. * * @package WPML */ /** * Class WPML_Upgrade_Add_Wrap_Column_To_Translate */ class WPML_Upgrade_Add_Wrap_Column_To_Translate extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_translate'; } /** * Get column name. * * @return string */ protected function get_column() { return 'field_wrap_tag'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'VARCHAR( 16 ) NOT NULL AFTER `field_type`'; } } upgrade/commands/class-wpml-upgrade-element-type-length-and-collation.php 0000755 00000002326 14720342453 0022635 0 ustar 00 <?php class WPML_Upgrade_Element_Type_Length_And_Collation implements IWPML_Upgrade_Command { /** @var bool $result */ private $result = true; /** @var WPML_Upgrade_Schema */ private $upgrade_schema; public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** @return bool */ private function run() { $table = 'icl_translations'; $column = 'element_type'; $column_attr = "VARCHAR(60) NOT NULL DEFAULT 'post_post'"; if ( $this->upgrade_schema->does_table_exist( $table ) ) { $column_attr = $this->add_collation_from_post_type( $column_attr ); $this->upgrade_schema->modify_column( $table, $column, $column_attr ); } return $this->result; } private function add_collation_from_post_type( $element_type_attr ) { $collation = $this->upgrade_schema->get_column_collation( 'posts', 'post_type' ); if ( $collation ) { $element_type_attr .= ' COLLATE ' . $collation; } return $element_type_attr; } public function run_admin() { return $this->run(); } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** @return bool */ public function get_results() { return $this->result; } } upgrade/commands/class-wpml-tm-add-tp-revision-and-ts-status-columns-to-translation-status.php 0000755 00000001433 14720342453 0026700 0 ustar 00 <?php class WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Translation_Status extends WPML_Upgrade_Run_All { /** @var WPML_Upgrade_Schema */ private $upgrade_schema; public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** @return bool */ protected function run() { $table = 'icl_translation_status'; $columns = array( 'tp_revision' => 'INT NOT NULL DEFAULT 1', 'ts_status' => 'TEXT NULL DEFAULT NULL', ); if ( $this->upgrade_schema->does_table_exist( $table ) ) { foreach ( $columns as $column => $definition ) { if ( ! $this->upgrade_schema->does_column_exist( $table, $column ) ) { $this->upgrade_schema->add_column( $table, $column, $definition ); } } } $this->result = true; return $this->result; } } upgrade/commands/class-wpml-upgrade-add-word-count-column-to-strings.php 0000755 00000001164 14720342453 0022454 0 ustar 00 <?php /** * Upgrade 'icl_strings' table by adding 'word_count' column. * * @package WPML */ /** * Class WPML_Upgrade_Add_Word_Count_Column_To_Strings */ class WPML_Upgrade_Add_Word_Count_Column_To_Strings extends WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_strings'; } /** * Get column name. * * @return string */ protected function get_column() { return 'word_count'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'int unsigned NULL'; } } upgrade/commands/class-wpml-upgrade-remove-translation-services-transient.php 0000755 00000000735 14720342453 0023705 0 ustar 00 <?php class WPML_Upgrade_Remove_Translation_Services_Transient implements IWPML_Upgrade_Command { /** * @return bool|void */ public function run_admin() { delete_transient( 'wpml_translation_service_list' ); return true; } /** * @return bool */ public function run_ajax() { return false; } /** * @return bool */ public function run_frontend() { return false; } /** * @return null */ public function get_results() { return array(); } } upgrade/commands/class-wpml-upgrade-media-duplication-in-core.php 0000755 00000024676 14720342453 0021162 0 ustar 00 <?php class WPML_Upgrade_Media_Duplication_In_Core implements IWPML_Upgrade_Command { const FEATURED_AS_TRANSLATED_META_KEY = '_wpml_featured_image_as_translated'; const TRANSIENT_DEFERRED_UPGRADE_IN_PROGRESS = 'wpml_upgrade_media_duplication_in_progress'; const MAX_TIME = 10; /** @var SitePress */ private $sitepress; /** @var WPML_Upgrade $wpml_upgrade */ private $wpml_upgrade; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Notices $notices */ private $notices; /** @var WPML_Media_Attachments_Duplication $media_attachment_duplication */ private $media_attachment_duplication; /** @var array $post_thumbnail_map */ private $post_thumbnail_map; /** @var int $start_time */ private $start_time; public function __construct( array $args ) { $this->sitepress = $args[0]; $this->wpdb = $args[1]->get_wpdb(); $this->notices = $args[2]; } /** * @return bool */ public function run_admin() { if ( $this->has_notice() ) { $this->create_or_refresh_notice(); return false; } if ( $this->find_posts_altered_between_402_and_404() ) { /** * The rest of the upgrade needs to run when all the custom post types are registered */ add_action( 'init', array( $this, 'deferred_upgrade_admin' ), PHP_INT_MAX ); return false; } $this->remove_notice(); return true; } public function deferred_upgrade_admin() { list( $is_complete ) = $this->process_upgrade(); if ( ! $is_complete ) { // We could not complete the upgrade in the same request $this->create_or_refresh_notice(); } } /** * @return bool */ public function run_ajax() { /** * The rest of the upgrade needs to run when all the custom post types are registered */ add_action( 'init', array( $this, 'deferred_upgrade_ajax' ), PHP_INT_MAX ); return false; } public function deferred_upgrade_ajax() { list( $is_complete, $remaining ) = $this->process_upgrade(); if ( $is_complete ) { $data = array( 'response' => esc_html__( 'The upgrade is complete.', 'sitepress' ), 'complete' => true, ); } elseif ( $remaining ) { $data = array( 'response' => sprintf( esc_html__( '%d items remaining...', 'sitepress' ), $remaining ), 'complete' => false, ); } else { $data = array( 'concurrent_request' => true ); } wp_send_json_success( $data ); } /** * @return bool */ public function run_frontend() { return false; } /** * @return array */ public function get_results() { return array(); } private function process_upgrade() { $remaining = null; $is_complete = false; if ( ! $this->acquire_lock() ) { return $is_complete; } $this->start_timer(); $source_posts = $this->find_posts_altered_between_402_and_404(); $remaining = count( $source_posts ); $should_duplicate_media = $this->should_duplicate_media(); foreach ( $source_posts as $key => $source_post ) { if ( $should_duplicate_media ) { $this->duplicate_missing_attachments_for_post( $source_post ); } $this->duplicate_missing_featured_image_for_post( $source_post ); $remaining--; if ( $this->is_max_time_elapsed() ) { break; } } if ( ! $this->is_max_time_elapsed() ) { $this->cleanup_display_featured_as_translated_meta(); $this->remove_notice(); $is_complete = true; } $this->release_lock(); return array( $is_complete, $remaining ); } private function get_notice_content() { ob_start(); $action = str_replace( '_', '-', strtolower( __CLASS__ ) ); ?> <div class="js-main-content"> <h2><?php esc_html_e( "WPML needs to upgrade the post's media information.", 'sitepress' ); ?></h2> <p><?php esc_html_e( "We couldn't complete the whole process in one request. Please click on the \"Upgrade\" button to continue.", 'sitepress' ); ?></p> <input type="button" class="button-primary" name="upgrade" value="<?php esc_attr_e( 'Upgrade' ); ?>"/> <span class="js-wpml-upgrade-progress" style="display:none"><?php esc_html_e( 'Starting...', 'sitepress' ); ?></span> <?php wp_nonce_field( $action . '-nonce', $action . '-nonce' ); ?> </div> <script> jQuery( document ).ready( function ( $ ) { var upgradeProgress = $('.js-wpml-upgrade-progress'); var ajax_request = function () { $.ajax( { url: ajaxurl, type: "POST", data: { action: '<?php echo $action; ?>', nonce: $( '#<?php echo $action; ?>-nonce' ).val() }, success: function ( response ) { if ( response.data.concurrent_request ) { setTimeout(ajax_request, 3000); } else { upgradeProgress.text( response.data.response ); if ( ! response.data.complete ) { ajax_request(); } } }, error: function(jqXHR, textStatus, errorThrown) { var errorData = '<p>status code: '+jqXHR.status+'</p><p>errorThrown: ' + errorThrown + '</p><p>jqXHR.responseText:</p><div>'+jqXHR.responseText + '</div>'; upgradeProgress.html( '<?php echo esc_html__( 'The following exception has occurred while running the migration, please try again later or contact support if the problem persists.', 'sitepress' ); ?><hr>' + errorData ); console.log('jqXHR:'); console.log(jqXHR); console.log('textStatus:'); console.log(textStatus); console.log('errorThrown:'); console.log(errorThrown); } } ); }; $( '.js-main-content' ).find( 'input[name="upgrade"]' ).on( 'click', function ( e ) { $( this ).prop( 'disabled', true ); $('.js-wpml-upgrade-progress').show(); ajax_request(); } ); } ); </script> <?php return ob_get_clean(); } /** * Some posts could have been created between WPML 4.0.2 and WPML 4.0.4 * And they would have '_wpml_featured_image_as_translated' but not '_wpml_media_featured' */ private function find_posts_altered_between_402_and_404() { $source_posts_missing_duplicate_featured_meta = "SELECT pm.post_id AS ID, pm.meta_value AS duplicate_featured, t.trid, t.element_type FROM {$this->wpdb->postmeta} AS pm LEFT JOIN {$this->wpdb->prefix}icl_translations AS t ON t.element_id = pm.post_id AND t.element_type LIKE 'post_%' LEFT JOIN {$this->wpdb->postmeta} AS duplicate_featured ON duplicate_featured.post_id = pm.post_id AND duplicate_featured.meta_key = '" . \WPML\Media\Option::DUPLICATE_FEATURED_KEY . "' WHERE pm.meta_key = '" . self::FEATURED_AS_TRANSLATED_META_KEY . "' AND t.source_language_code IS NULL AND duplicate_featured.meta_value IS NULL "; return $this->wpdb->get_results( $source_posts_missing_duplicate_featured_meta ); } private function duplicate_missing_featured_image_for_post( $post ) { if ( $post->duplicate_featured == 1 && $this->has_thumbnail( $post->ID ) ) { $post->post_type = preg_replace( '/^post_/', '', $post->element_type ); $this->get_media_attachment_duplication()->duplicate_featured_image_in_post( $post, $this->get_post_thumbnail_map() ); } // Add the meta to the source post and its translations $translations = $this->sitepress->get_element_translations( $post->trid, $post->element_type ); $post_ids = wp_list_pluck( $translations, 'element_id' ); $this->wpdb->query( $this->wpdb->prepare( "INSERT INTO {$this->wpdb->prefix}postmeta ( post_id, meta_key, meta_value ) SELECT post_id, '" . \WPML\Media\Option::DUPLICATE_FEATURED_KEY . "', %d FROM {$this->wpdb->postmeta} WHERE post_id IN(" . wpml_prepare_in( $post_ids ) . ") AND meta_key = '" . self::FEATURED_AS_TRANSLATED_META_KEY . "'", $post->duplicate_featured ) ); } private function has_thumbnail( $post_id ) { return (bool) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT meta_value FROM {$this->wpdb->postmeta} WHERE meta_key = '_thumbnail_id' AND post_id = %d", $post_id ) ); } /** * @return array */ private function get_post_thumbnail_map() { if ( ! $this->post_thumbnail_map ) { list( $this->post_thumbnail_map ) = $this->get_media_attachment_duplication()->get_post_thumbnail_map(); } return $this->post_thumbnail_map; } private function duplicate_missing_attachments_for_post( $post ) { $attachment_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT ID FROM {$this->wpdb->posts} WHERE post_type = 'attachment' AND post_parent = %d", $post->ID ) ); foreach ( $attachment_ids as $attachment_id ) { foreach ( $this->sitepress->get_active_languages() as $language_code => $active_language ) { $this->get_media_attachment_duplication()->create_duplicate_attachment( (int) $attachment_id, (int) $post->ID, $language_code ); } } } private function should_duplicate_media() { return \WPML\FP\Obj::prop( 'duplicate_media', \WPML\Media\Option::getNewContentSettings() ); } private function cleanup_display_featured_as_translated_meta() { $this->wpdb->query( "DELETE FROM {$this->wpdb->postmeta} WHERE meta_key = '" . self::FEATURED_AS_TRANSLATED_META_KEY . "'" ); } private function mark_migration_completed() { $this->wpml_upgrade->mark_command_as_executed( $this ); } private function get_media_attachment_duplication() { global $wpml_language_resolution; if ( ! $this->media_attachment_duplication ) { $this->media_attachment_duplication = new WPML_Media_Attachments_Duplication( $this->sitepress, new WPML_Model_Attachments( $this->sitepress, wpml_get_post_status_helper() ), $this->wpdb, $wpml_language_resolution ); } return $this->media_attachment_duplication; } private function acquire_lock() { $lock = get_transient( self::TRANSIENT_DEFERRED_UPGRADE_IN_PROGRESS ); if ( $lock ) { return false; } set_transient( self::TRANSIENT_DEFERRED_UPGRADE_IN_PROGRESS, true, MINUTE_IN_SECONDS ); return true; } private function release_lock() { delete_transient( self::TRANSIENT_DEFERRED_UPGRADE_IN_PROGRESS ); } private function start_timer() { $this->start_time = time(); } private function is_max_time_elapsed() { return self::MAX_TIME <= ( time() - $this->start_time ); } private function remove_notice() { $this->notices->remove_notice( 'default', __CLASS__ ); } private function create_or_refresh_notice() { $notice = $this->notices->create_notice( __CLASS__, $this->get_notice_content() ); $notice->add_display_callback( array( 'WPML_Notice_Show_On_Dashboard_And_WPML_Pages', 'is_on_page' ) ); $notice->set_css_class_types( 'info' ); $this->notices->add_notice( $notice ); } private function has_notice() { return $this->notices->get_notice( __CLASS__, 'default' ); } } upgrade/commands/class-wpml-tm-upgrade-wpml-site-id-ate.php 0000755 00000003005 14720342453 0017717 0 ustar 00 <?php /** * Upgrades the former option to the new one. */ class WPML_TM_Upgrade_WPML_Site_ID_ATE implements IWPML_Upgrade_Command { /** * Runs the upgrade process. * * @return bool */ public function run() { if ( $this->must_run() ) { $site_id = new WPML_Site_ID(); $old_value = $site_id->get_site_id( WPML_Site_ID::SITE_SCOPES_GLOBAL ); return update_option( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_TM_ATE::SITE_ID_SCOPE, $old_value, false ); } return true; } /** * True if all conditions are met. * * @return bool */ private function must_run() { return WPML_TM_ATE_Status::is_enabled_and_activated() && (bool) get_option( WPML_TM_Wizard_Options::WIZARD_COMPLETE_FOR_MANAGER, false ) && $this->site_id_ate_does_not_exist(); } /** * Checks has the old option. * * @return bool */ protected function site_id_ate_does_not_exist() { get_option( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_TM_ATE::SITE_ID_SCOPE, null ); $notoptions = wp_cache_get( 'notoptions', 'options' ); return ( array_key_exists( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_TM_ATE::SITE_ID_SCOPE, $notoptions ) ); } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * Unused. * * @return null */ public function get_results() { return null; } } upgrade/commands/AddCountryColumnToLanguages.php 0000755 00000000771 14720342453 0016105 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddCountryColumnToLanguages extends \WPML_Upgrade_Add_Column_To_Table { /** * Get table name. * * @return string */ protected function get_table() { return 'icl_languages'; } /** * Get column name. * * @return string */ protected function get_column() { return 'country'; } /** * Get column definition. * * @return string */ protected function get_column_definition() { return 'VARCHAR(10) NULL DEFAULT NULL'; } } upgrade/commands/ATEProxyUpdateRewriteRules.php 0000755 00000001305 14720342453 0015706 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands; class ATEProxyUpdateRewriteRules implements \IWPML_Upgrade_Command { /** @var bool $result */ private $result = false; /** * @return bool */ public function run_admin() { // By doing this, we ensure that the rewrite rules get updated for the `ate/widget/script` endpoint. update_option( 'plugin_permalinks_flushed', 0 ); $this->result = true; return $this->result; } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/AddPrimaryKeyToLocaleMap.php 0000755 00000000462 14720342453 0015304 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddPrimaryKeyToLocaleMap extends AddPrimaryKeyToTable { protected function get_table() { return 'icl_locale_map'; } protected function get_key_name() { return 'PRIMARY'; } protected function get_key_columns() { return [ 'code', 'locale' ]; } } upgrade/commands/CreateBackgroundTaskTable.php 0000755 00000003151 14720342453 0015512 0 ustar 00 <?php namespace WPML\Upgrade\Commands; use WPML\Core\BackgroundTask\Model\BackgroundTask; use SitePress_Setup; class CreateBackgroundTaskTable implements \IWPML_Upgrade_Command { /** @var \WPML_Upgrade_Schema $schema */ private $schema; /** @var bool $result */ private $result = false; public function __construct( array $args ) { $this->schema = $args[0]; } /** * @return bool */ public function run() { $wpdb = $this->schema->get_wpdb(); $table_name = $wpdb->prefix . BackgroundTask::TABLE_NAME; $charset_collate = SitePress_Setup::get_charset_collate(); $query = " CREATE TABLE IF NOT EXISTS `{$table_name}` ( `task_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, `task_type` VARCHAR(500) NOT NULL, `task_status` SMALLINT UNSIGNED NOT NULL DEFAULT 0, `starting_date` DATETIME NULL, `total_count` INT UNSIGNED NOT NULL DEFAULT 0, `completed_count` INT UNSIGNED NOT NULL DEFAULT 0, `completed_ids` TEXT NULL DEFAULT NULL, `payload` TEXT NULL DEFAULT NULL, `retry_count` SMALLINT UNSIGNED NOT NULL DEFAULT 0 ) {$charset_collate}; "; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $this->result = $wpdb->query( $query ); return $this->result; } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } } upgrade/commands/SynchronizeSourceIdOfATEJobs/Repository.php 0000755 00000001252 14720342453 0020301 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs; class Repository { /** @var \wpdb */ private $wpdb; /** * @param \wpdb $wpdb */ public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @return \WPML\Collect\Support\Collection */ public function getPairs() { $sql = " SELECT MAX(editor_job_id) as editor_job_id, rid FROM {$this->wpdb->prefix}icl_translate_job WHERE editor = 'ate' AND editor_job_id IS NOT NULL GROUP BY rid "; $rowset = $this->wpdb->get_results( $sql, ARRAY_A ); $rowset = \wpml_collect( is_array( $rowset ) ? $rowset : [] ); return $rowset->pluck( 'rid', 'editor_job_id' ); } } upgrade/commands/SynchronizeSourceIdOfATEJobs/Command.php 0000755 00000003372 14720342453 0017505 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs; use WPML\Utils\Pager; use WPML\TM\Upgrade\Commands\MigrateAteRepository; use WPML\Collect\Support\Collection; use WPML\Upgrade\CommandsStatus; use function WPML\Container\make; class Command implements \IWPML_Upgrade_Command { const CHUNK_SIZE = 1000; /** @var Repository */ private $repository; /** @var \WPML_TM_ATE_API */ private $api; /** @var Pager */ private $pager; /** @var CommandsStatus */ private $commandStatus; /** @var bool $result */ private $result = false; /** * Command constructor. * * @param Repository $repository * @param \WPML_TM_ATE_API $api * @param Pager $pager * @param CommandsStatus $commandStatus */ public function __construct( Repository $repository, \WPML_TM_ATE_API $api, Pager $pager, CommandsStatus $commandStatus ) { $this->repository = $repository; $this->api = $api; $this->pager = $pager; $this->commandStatus = $commandStatus; } public function run_admin() { if ( ! $this->hasBeenMigrateATERepositoryUpgradeRun() ) { return false; } $chunks = $this->repository->getPairs()->chunk( self::CHUNK_SIZE ); $this->result = $this->pager->iterate( $chunks, function ( Collection $pairs ) { return $this->api->migrate_source_id( $pairs->toArray() ); } ) === 0; return $this->result; } public function run_ajax() { return null; } public function run_frontend() { return null; } /** * @return bool */ public function get_results() { return $this->result; } /** * @return mixed */ private function hasBeenMigrateATERepositoryUpgradeRun() { return $this->commandStatus->hasBeenExecuted( MigrateAteRepository::class ); } } upgrade/commands/SynchronizeSourceIdOfATEJobs/CommandFactory.php 0000755 00000000560 14720342453 0021031 0 ustar 00 <?php namespace WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs; use WPML\Utils\Pager; use function WPML\Container\make; class CommandFactory { const PAGER_OPTION_NAME = 'sync-source-id-ate-jobs-pager'; /** * @return Command */ public function create() { return make( Command::class, [ ':pager' => new Pager( self::PAGER_OPTION_NAME, 1 ) ] ); } } upgrade/commands/wpml-upgrade-admin-users-languages.php 0000755 00000002055 14720342453 0017313 0 ustar 00 <?php class WPML_Upgrade_Admin_Users_Languages { private $sitepress; const ICL_ADMIN_LANGUAGE_MIGRATED_TO_WP_47 = 'icl_admin_language_migrated_to_wp47'; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $this->add_hooks(); } public function add_hooks() { add_action( 'admin_init', array( $this, 'run' ) ); } public function run() { $user_id = get_current_user_id(); $wpml_user_lang = get_user_meta( $user_id, 'icl_admin_language', true ); $wpml_user_lang_migrated = get_user_meta( $user_id, self::ICL_ADMIN_LANGUAGE_MIGRATED_TO_WP_47, false ); $wpml_user_locale = $this->sitepress->get_locale_from_language_code( $wpml_user_lang ); $wp_user_locale = get_user_meta( $user_id, 'locale', true ); if ( ! $wpml_user_lang_migrated ) { if ( $wpml_user_locale && $wpml_user_locale !== $wp_user_locale ) { update_user_meta( $user_id, 'locale', $wpml_user_locale ); } update_user_meta( $user_id, self::ICL_ADMIN_LANGUAGE_MIGRATED_TO_WP_47, true ); } } } upgrade/commands/class-disable-options-autoloading.php 0000755 00000002302 14720342453 0017214 0 ustar 00 <?php namespace WPML\Upgrade\Command; class DisableOptionsAutoloading implements \IWPML_Upgrade_Command { /** @var bool */ private $results; public function run() { global $wpdb; $do_not_autoload_options = [ '_icl_cache', 'wp_installer_settings', 'wpml_translation_services', '_wpml_batch_report', ]; $where = 'WHERE option_name IN (' . wpml_prepare_in( $do_not_autoload_options ) . ") AND autoload = 'yes'"; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $query_result = $wpdb->query( "UPDATE {$wpdb->prefix}options SET autoload = 'no' {$where}" ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery $this->results = false !== $query_result; return $this->results; } public function run_admin() { return $this->run(); } public function run_ajax() { return null; } public function run_frontend() { return null; } public function get_results() { return $this->results; } } upgrade/commands/class-wpml-tm-add-tpid-column-to-translation-status.php 0000755 00000001165 14720342453 0022473 0 ustar 00 <?php class WPML_TM_Add_TP_ID_Column_To_Translation_Status extends WPML_Upgrade_Run_All { /** @var WPML_Upgrade_Schema */ private $upgrade_schema; public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** @return bool */ protected function run() { $table = 'icl_translation_status'; $column = 'tp_id'; if ( $this->upgrade_schema->does_table_exist( $table ) ) { if ( ! $this->upgrade_schema->does_column_exist( $table, $column ) ) { $this->upgrade_schema->add_column( $table, $column, 'INT NULL DEFAULT NULL' ); } } $this->result = true; return $this->result; } } upgrade/commands/AddStringPackageIdtIndexToStrings.php 0000755 00000000503 14720342453 0017153 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class AddStringPackageIdIndexToStrings extends AddIndexToTable { protected function get_table() { return 'icl_strings'; } protected function get_index() { return 'string_package_id'; } protected function get_index_definition() { return '( `string_package_id` )'; } } upgrade/commands/abstracts/DropIndexFromTable.php 0000755 00000001534 14720342453 0016175 0 ustar 00 <?php namespace WPML\Upgrade\Commands; abstract class DropIndexFromTable extends \WPML_Upgrade_Run_All { /** * @return string */ abstract protected function get_table(); /** * @return string */ abstract protected function get_index(); /** * @var \WPML_Upgrade_Schema */ private $upgrade_schema; /** * @param array $args */ public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** * @return bool */ protected function run() { $this->result = false; if ( $this->upgrade_schema->does_table_exist( $this->get_table() ) ) { if ( $this->upgrade_schema->does_index_exist( $this->get_table(), $this->get_index() ) ) { $this->result = $this->upgrade_schema->drop_index( $this->get_table(), $this->get_index() ); } else { $this->result = true; } } return $this->result; } } upgrade/commands/abstracts/class-add-index-to-table.php 0000755 00000002370 14720342453 0017151 0 ustar 00 <?php /** * Abstract class to upgrade a table by adding a column to it. * * @package WPML */ namespace WPML\Upgrade\Commands; /** * Class Add_Index_To_Table */ abstract class AddIndexToTable extends \WPML_Upgrade_Run_All { /** * Get table name. * * @return string */ abstract protected function get_table(); /** * Get index name. * * @return string */ abstract protected function get_index(); /** * Get index definition. * * @return string */ abstract protected function get_index_definition(); /** * Upgrade schema. * * @var \WPML_Upgrade_Schema */ private $upgrade_schema; /** * Add_Index_To_Table constructor. * * @param array $args */ public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** * Run the table upgrade. * * @return bool */ protected function run() { $this->result = false; if ( $this->upgrade_schema->does_table_exist( $this->get_table() ) ) { if ( ! $this->upgrade_schema->does_index_exist( $this->get_table(), $this->get_index() ) ) { $this->result = $this->upgrade_schema->add_index( $this->get_table(), $this->get_index(), $this->get_index_definition() ); } else { $this->result = true; } } return $this->result; } } upgrade/commands/abstracts/class-wpml-upgrade-add-column-to-table.php 0000755 00000003157 14720342453 0021745 0 ustar 00 <?php /** * Abstract class to upgrade a table by adding a column to it. * * @package WPML */ /** * Class WPML_Upgrade_Add_Column_To_Table */ abstract class WPML_Upgrade_Add_Column_To_Table implements IWPML_Upgrade_Command { /** * Get table name. * * @return string */ abstract protected function get_table(); /** * Get column name. * * @return string */ abstract protected function get_column(); /** * Get column definition. * * @return string */ abstract protected function get_column_definition(); /** * Upgrade schema. * * @var WPML_Upgrade_Schema */ private $upgrade_schema; /** * WPML_Upgrade_Add_Column_To_Table constructor. * * @param array $args Arguments. */ public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** * Run the table upgrade. * * @return bool */ private function run() { if ( $this->upgrade_schema->does_table_exist( $this->get_table() ) ) { if ( ! $this->upgrade_schema->does_column_exist( $this->get_table(), $this->get_column() ) ) { $this->upgrade_schema->add_column( $this->get_table(), $this->get_column(), $this->get_column_definition() ); } } return true; } /** * Run in admin. * * @return bool */ public function run_admin() { return $this->run(); } /** * Run in ajax. * * @return bool */ public function run_ajax() { return $this->run(); } /** * Run in frontend. * * @return bool */ public function run_frontend() { return $this->run(); } /** * Get upgrade results. * * @return bool */ public function get_results() { return true; } } upgrade/commands/abstracts/AddPrimaryKeyToTable.php 0000755 00000001674 14720342453 0016472 0 ustar 00 <?php namespace WPML\Upgrade\Commands; abstract class AddPrimaryKeyToTable extends \WPML_Upgrade_Run_All { /** * @return string */ abstract protected function get_table(); /** * @return string */ abstract protected function get_key_name(); /** * @return array */ abstract protected function get_key_columns(); /** * @var \WPML_Upgrade_Schema */ private $upgrade_schema; /** * @param array $args */ public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** * @return bool */ protected function run() { $this->result = false; if ( $this->upgrade_schema->does_table_exist( $this->get_table() ) ) { if ( ! $this->upgrade_schema->does_key_exist( $this->get_table(), $this->get_key_name() ) ) { $this->result = $this->upgrade_schema->add_primary_key( $this->get_table(), $this->get_key_columns() ); } else { $this->result = true; } } return $this->result; } } upgrade/commands/class-wpml-upgrade-fix-non-admin-cap.php 0000755 00000001315 14720342453 0017426 0 ustar 00 <?php class WPML_Upgrade_Fix_Non_Admin_With_Admin_Cap implements IWPML_Upgrade_Command { private $results = array(); /** * @return bool|void */ public function run_admin() { $user = new WP_User( 'admin' ); if ( $user->exists() && ! is_super_admin( $user->get( 'ID' ) ) ) { $wpml_capabilities = array_keys( wpml_get_capabilities() ); foreach ( $wpml_capabilities as $capability ) { $user->remove_cap( $capability ); } } return true; } /** * @return bool */ public function run_ajax() { return false; } /** * @return bool */ public function run_frontend() { return false; } /** * @return null */ public function get_results() { return $this->results; } } upgrade/commands/class-wpml-tm-add-tp-revision-and-ts-status-columns-to-core-status.php 0000755 00000001726 14720342453 0025277 0 ustar 00 <?php class WPML_TM_Add_TP_Revision_And_TS_Status_Columns_To_Core_Status implements IWPML_Upgrade_Command { /** @var WPML_Upgrade_Schema */ private $upgrade_schema; public function __construct( array $args ) { $this->upgrade_schema = $args[0]; } /** @return bool */ private function run() { $table = 'icl_core_status'; $columns = array( 'tp_revision' => 'INT NOT NULL DEFAULT 1', 'ts_status' => 'TEXT NULL DEFAULT NULL', ); if ( $this->upgrade_schema->does_table_exist( $table ) ) { foreach ( $columns as $column => $definition ) { if ( ! $this->upgrade_schema->does_column_exist( $table, $column ) ) { $this->upgrade_schema->add_column( $table, $column, $definition ); } } } return true; } public function run_admin() { return $this->run(); } public function run_ajax() { return null; } public function run_frontend() { return null; } /** @return bool */ public function get_results() { return null; } } upgrade/commands/class-wpml-upgrade-media-without-language.php 0000755 00000000774 14720342453 0020572 0 ustar 00 <?php class WPML_Upgrade_Media_Without_Language extends WPML_Upgrade_Run_All { /** @var wpdb */ private $wpdb; /** @var string */ private $default_language; public function __construct( array $args ) { $this->wpdb = $args[0]; $this->default_language = $args[1]; } /** @return bool */ protected function run() { $initialize_post_type = new WPML_Initialize_Language_For_Post_Type( $this->wpdb ); return $initialize_post_type->run( 'attachment', $this->default_language ); } } upgrade/commands/class-wpml-upgrade-wpml-site-id-remaining.php 0000755 00000004221 14720342453 0020502 0 ustar 00 <?php /** * Some sites were not properly upgraded in 4.2.0. * In that case the old option was not deleted * and new site IDs were wrongly created. */ class WPML_Upgrade_WPML_Site_ID_Remaining implements IWPML_Upgrade_Command { /** * @var string * * @see WPML_TM_ATE::SITE_ID_SCOPE */ const SCOPE_ATE = 'ate'; /** * @return bool */ public function run() { if ( $this->old_and_new_options_exist() ) { $value_from_old_option = get_option( WPML_Site_ID::SITE_ID_KEY, null ); $value_from_new_option = get_option( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_Site_ID::SITE_SCOPES_GLOBAL, null ); // 1. We update the global option with the old value. update_option( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_Site_ID::SITE_SCOPES_GLOBAL, $value_from_old_option, false ); // 2. If the ate option has the same value as the new global, we also update it. if ( $this->option_exists( WPML_Site_ID::SITE_ID_KEY . ':' . self::SCOPE_ATE ) ) { $ate_uuid = get_option( WPML_Site_ID::SITE_ID_KEY . ':' . self::SCOPE_ATE, null ); if ( $ate_uuid === $value_from_new_option ) { update_option( WPML_Site_ID::SITE_ID_KEY . ':' . self::SCOPE_ATE, $value_from_old_option, false ); } } return delete_option( WPML_Site_ID::SITE_ID_KEY ); } return true; } /** * @return bool */ protected function old_and_new_options_exist() { return $this->option_exists( WPML_Site_ID::SITE_ID_KEY ) && $this->option_exists( WPML_Site_ID::SITE_ID_KEY . ':' . WPML_Site_ID::SITE_SCOPES_GLOBAL ); } /** * @param string $key * * @return bool */ private function option_exists( $key ) { get_option( $key, null ); $notoptions = wp_cache_get( 'notoptions', 'options' ); return false === $notoptions || ! array_key_exists( $key, $notoptions ); } /** * Runs in admin pages. * * @return bool */ public function run_admin() { return $this->run(); } /** * Unused. * * @return null */ public function run_ajax() { return null; } /** * Unused. * * @return null */ public function run_frontend() { return null; } /** * Unused. * * @return null */ public function get_results() { return null; } } upgrade/commands/RemoveEndpointsOption.php 0000755 00000000304 14720342453 0015023 0 ustar 00 <?php namespace WPML\Upgrade\Commands; class RemoveEndpointsOption extends \WPML_Upgrade_Run_All { public function run() { delete_option( 'wpml_registered_endpoints' ); return true; } } upgrade/class-wpml-upgrade.php 0000755 00000014256 14720342453 0012432 0 ustar 00 <?php use WPML\Upgrade\CommandsStatus; class WPML_Upgrade { const SCOPE_ADMIN = 'admin'; const SCOPE_AJAX = 'ajax'; const SCOPE_FRONT_END = 'front-end'; /** @var array */ private $commands; const UPDATE_STATUSES_KEY = 'wpml_update_statuses'; /** @var SitePress */ private $sitepress; /** @var WPML_Upgrade_Command_Factory */ private $command_factory; /** @var CommandsStatus */ private $command_status; /** @var bool $upgrade_in_progress */ private $upgrade_in_progress; /** * WPML_Upgrade constructor. * * @param array $commands * @param SitePress $sitepress * @param WPML_Upgrade_Command_Factory $command_factory * @param CommandsStatus $command_status */ public function __construct( array $commands, SitePress $sitepress, WPML_Upgrade_Command_Factory $command_factory, CommandsStatus $command_status = null ) { $this->add_commands( $commands ); $this->sitepress = $sitepress; $this->command_factory = $command_factory; $this->command_status = $command_status ?: new CommandsStatus(); } /** * @param array $commands */ public function add_commands( array $commands ) { foreach ( $commands as $command ) { if ( $command instanceof WPML_Upgrade_Command_Definition ) { $this->commands[] = $command; } } } public function run() { $result = false; /** * Add commands to the upgrade logic. * * The filter must be added before the `wpml_loaded` action is fired (the action is fired on `plugins_loaded`). * * @param array $commands An empty array. * @return array Array of classes created with \wpml_create_upgrade_command_definition. * * @since 4.1.0 * @see \wpml_create_upgrade_command_definition */ $new_commands = apply_filters( 'wpml_upgrade_commands', array() ); if ( $new_commands && is_array( $new_commands ) ) { $this->add_commands( $new_commands ); } if ( $this->sitepress->get_wp_api()->is_admin() ) { if ( $this->sitepress->get_wp_api()->is_ajax() ) { $result = $this->run_ajax(); } else { $result = $this->run_admin(); } } elseif ( $this->sitepress->get_wp_api()->is_front_end() ) { $result = $this->run_front_end(); } $this->set_upgrade_completed(); return $result; } private function get_commands_by_scope( $scope ) { $results = array(); /** @var WPML_Upgrade_Command_Definition $command */ foreach ( $this->commands as $command ) { if ( in_array( $scope, $command->get_scopes(), true ) ) { $results[] = $command; } } return $results; } private function get_admin_commands() { return $this->get_commands_by_scope( self::SCOPE_ADMIN ); } private function get_ajax_commands() { return $this->get_commands_by_scope( self::SCOPE_AJAX ); } private function get_front_end_commands() { return $this->get_commands_by_scope( self::SCOPE_FRONT_END ); } private function run_admin() { return $this->run_commands( $this->get_admin_commands(), 'maybe_run_admin' ); } private function run_ajax() { return $this->run_commands( $this->get_ajax_commands(), 'maybe_run_ajax' ); } private function run_front_end() { return $this->run_commands( $this->get_front_end_commands(), 'maybe_run_front_end' ); } private function run_commands( $commands, $default ) { $results = array(); /** @var WPML_Upgrade_Command_Definition $command */ foreach ( $commands as $command ) { $results[] = $this->run_command( $command, $default ); } return $results; } private function run_command( WPML_Upgrade_Command_Definition $command_definition, $default ) { $method = $default; if ( $command_definition->get_method() ) { $method = $command_definition->get_method(); } if ( ! $this->command_status->hasBeenExecuted( $command_definition->get_class_name() ) ) { $this->set_upgrade_in_progress(); $upgrade = $command_definition->create(); return $this->$method( $upgrade ); } return null; } /** @noinspection PhpUnusedPrivateMethodInspection * @param IWPML_Upgrade_Command $upgrade * * @return null */ private function maybe_run_admin( IWPML_Upgrade_Command $upgrade ) { if ( $upgrade->run_admin() ) { $this->mark_command_as_executed( $upgrade ); } return $upgrade->get_results(); } /** @noinspection PhpUnusedPrivateMethodInspection * @param IWPML_Upgrade_Command $upgrade * * @return null */ private function maybe_run_front_end( IWPML_Upgrade_Command $upgrade ) { if ( $upgrade->run_frontend() ) { $this->mark_command_as_executed( $upgrade ); } return $upgrade->get_results(); } /** @noinspection PhpUnusedPrivateMethodInspection * @param IWPML_Upgrade_Command $upgrade * * @return null */ private function maybe_run_ajax( IWPML_Upgrade_Command $upgrade ) { if ( $this->nonce_ok( $upgrade ) && $upgrade->run_ajax() ) { $this->mark_command_as_executed( $upgrade ); $this->sitepress->get_wp_api()->wp_send_json_success( '' ); } return $upgrade->get_results(); } private function nonce_ok( $class ) { $class_name = get_class( $class ); if ( ! $class_name ) { return false; } $class_name = $this->get_command_id( $class_name ); if ( isset( $_POST['action'] ) && $_POST['action'] === $class_name ) { $nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); if ( $this->sitepress->get_wp_api()->wp_verify_nonce( $nonce, $class_name . '-nonce' ) ) { return true; } } return false; } /** * @param IWPML_Upgrade_Command $class */ public function mark_command_as_executed( IWPML_Upgrade_Command $class ) { $this->command_status->markAsExecuted( get_class( $class ) ); } /** * @param string $class_name * * @return string */ private function get_command_id( $class_name ) { return str_replace( '_', '-', strtolower( $class_name ) ); } private function set_upgrade_in_progress() { if ( ! $this->upgrade_in_progress ) { $this->upgrade_in_progress = true; set_transient( WPML_Upgrade_Loader::TRANSIENT_UPGRADE_IN_PROGRESS, true, MINUTE_IN_SECONDS ); } } private function set_upgrade_completed() { if ( $this->upgrade_in_progress ) { $this->upgrade_in_progress = false; delete_transient( WPML_Upgrade_Loader::TRANSIENT_UPGRADE_IN_PROGRESS ); } } } upgrade/class-wpml-tm-upgrade-loader.php 0000755 00000005473 14720342453 0014315 0 ustar 00 <?php use WPML\TM\Menu\TranslationServices\Troubleshooting\RefreshServicesFactory; use WPML\API\Version; class WPML_TM_Upgrade_Loader implements IWPML_Action { /** @var SitePress */ private $sitepress; /** @var WPML_Upgrade_Schema */ private $upgrade_schema; /** @var WPML_Settings_Helper */ private $settings; /** @var WPML_Upgrade_Command_Factory */ private $factory; /** @var WPML_Notices */ private $notices; public function __construct( SitePress $sitepress, WPML_Upgrade_Schema $upgrade_schema, WPML_Settings_Helper $settings, WPML_Notices $wpml_notices, WPML_Upgrade_Command_Factory $factory ) { $this->sitepress = $sitepress; $this->upgrade_schema = $upgrade_schema; $this->settings = $settings; $this->notices = $wpml_notices; $this->factory = $factory; } public function add_hooks() { add_action( 'init', array( $this, 'wpml_tm_upgrade' ) ); } public function wpml_tm_upgrade() { $commands = array( $this->factory->create_command_definition( 'WPML_TM_Upgrade_Translation_Priorities_For_Posts', array(), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( 'WPML_TM_Upgrade_Default_Editor_For_Old_Jobs', array( $this->sitepress ), array( 'admin', 'ajax', 'front-end' ) ), $this->factory->create_command_definition( 'WPML_TM_Upgrade_Service_Redirect_To_Field', array(), array( 'admin' ) ), $this->factory->create_command_definition( 'WPML_TM_Upgrade_WPML_Site_ID_ATE', array( $this->upgrade_schema ), array( 'admin' ) ), $this->factory->create_command_definition( 'WPML_TM_Upgrade_Cancel_Orphan_Jobs', array( new WPML_TP_Sync_Orphan_Jobs_Factory(), new WPML_TM_Jobs_Migration_State() ), array( 'admin' ) ), $this->factory->create_command_definition( WPML\TM\Upgrade\Commands\MigrateAteRepository::class, [ $this->upgrade_schema ], [ 'admin' ] ), $this->factory->create_command_definition( WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs\Command::class, [], [ 'admin' ], null, [ new WPML\TM\Upgrade\Commands\SynchronizeSourceIdOfATEJobs\CommandFactory(), 'create' ] ), $this->factory->create_command_definition( WPML\TM\Upgrade\Commands\CreateAteDownloadQueueTable::class, [ $this->upgrade_schema ], [ 'admin' ] ), $this->factory->create_command_definition( WPML\TM\Upgrade\Commands\RefreshTranslationServices::class, [ \WPML\Container\make( RefreshServicesFactory::class ), Version::isHigherThanInstallation() ], [ 'admin' ] ), $this->factory->create_command_definition( WPML\TM\Upgrade\Commands\ATEProxyUpdateRewriteRules::class, [], [ \WPML_Upgrade::SCOPE_ADMIN ] ), ); $upgrade = new WPML_Upgrade( $commands, $this->sitepress, $this->factory ); $upgrade->run(); } } upgrade/CommandsStatus.php 0000755 00000002211 14720342453 0011654 0 ustar 00 <?php namespace WPML\Upgrade; class CommandsStatus { const OPTION_KEY = 'wpml_update_statuses'; /** * @param string $className * * @return bool */ public function hasBeenExecuted( $className ) { return (bool) $this->get_update_option_value( $this->get_command_id( $className ) ); } /** * @param string $className * @param bool $flag */ public function markAsExecuted( $className, $flag = true ) { $this->set_update_status( $this->get_command_id( $className ), $flag ); wp_cache_flush(); } /** * @param string $className * * @return string */ private function get_command_id( $className ) { return str_replace( '_', '-', strtolower( $className ) ); } private function set_update_status( $id, $value ) { $update_options = get_option( self::OPTION_KEY, array() ); $update_options[ $id ] = $value; update_option( self::OPTION_KEY, $update_options, true ); } private function get_update_option_value( $id ) { $update_options = get_option( self::OPTION_KEY, array() ); if ( $update_options && array_key_exists( $id, $update_options ) ) { return $update_options[ $id ]; } return null; } } upgrade/class-wpml-tm-upgrade-loader-factory.php 0000755 00000000533 14720342453 0015752 0 ustar 00 <?php class WPML_TM_Upgrade_Loader_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { public function create() { global $sitepress, $wpdb; return new WPML_TM_Upgrade_Loader( $sitepress, wpml_get_upgrade_schema(), wpml_load_settings_helper(), wpml_get_admin_notices(), new WPML_Upgrade_Command_Factory() ); } } class-wpml-file.php 0000755 00000010615 14720342453 0010266 0 ustar 00 <?php /** * WPML_File class file. * * @package wpml-core */ /** * Class WPML_File */ class WPML_File { /** * WPML WP API instance. * * @var WPML_WP_API $wp_api */ private $wp_api; /** * WP_Filesystem_Direct instance. * * @var WP_Filesystem_Direct */ private $filesystem; /** * WPML_File constructor. * * @param WPML_WP_API|null $wp_api WPML WP API instance. * @param WP_Filesystem_Direct|null $filesystem WP_Filesystem_Direct instance. */ public function __construct( WPML_WP_API $wp_api = null, WP_Filesystem_Direct $filesystem = null ) { if ( ! $wp_api ) { $wp_api = new WPML_WP_API(); } $this->wp_api = $wp_api; if ( ! $filesystem ) { $filesystem = new WP_Filesystem_Direct( null ); } $this->filesystem = $filesystem; } /** * Fix directory separator if backslash is used. * * @param string $path Path to fix. * * @return string */ public function fix_dir_separator( $path ) { $directory_separator = $this->wp_api->constant( 'DIRECTORY_SEPARATOR' ); return ( '\\' === $directory_separator ) ? str_replace( '/', '\\', $path ) : str_replace( '\\', '/', $path ); } /** * Get uri from file path. * * @param string $path File path. * @param boolean $trimProtocol * * @return string */ public function get_uri_from_path( $path, $trimProtocol = true ) { $base = null; if ( $this->wp_api->defined( 'WP_CONTENT_DIR' ) && $this->wp_api->defined( 'WP_CONTENT_URL' ) ) { $base_path = $this->fix_dir_separator( (string) $this->wp_api->constant( 'WP_CONTENT_DIR' ) ); if ( 0 === strpos( $path, $base_path ) ) { $base = array( 'path' => $base_path, 'uri' => $this->wp_api->constant( 'WP_CONTENT_URL' ), ); } if ( false === strpos( $path, $base_path ) ) { $base = array( 'path' => $base_path, 'uri' => $this->wp_api->constant( 'WP_CONTENT_URL' ), ); $wpml_plugin_folder_parts = explode( (string) $this->wp_api->constant( 'DIRECTORY_SEPARATOR' ), (string) $this->wp_api->constant( 'WPML_PLUGIN_PATH' ) ); if ( $wpml_plugin_folder_parts ) { $wpml_plugin_folder_parts = $this->pop_folder_array( 1, $wpml_plugin_folder_parts ); $wpml_plugins_folder = implode( (string) $this->wp_api->constant( 'DIRECTORY_SEPARATOR' ), $wpml_plugin_folder_parts ); $path = str_replace( $wpml_plugins_folder, (string) $this->wp_api->constant( 'WP_PLUGIN_DIR' ), $path ); } } } if ( ! $base ) { $base = array( 'path' => (string) $this->wp_api->constant( 'ABSPATH' ), 'uri' => (string) site_url(), ); } if ( $trimProtocol ) { $base['uri'] = preg_replace( '/(^https?:)/', '', (string) $base['uri'] ); } $relative_path = substr( $path, strlen( $base['path'] ) ); $relative_path = str_replace( array( '/', '\\' ), '/', $relative_path ); $relative_path = ltrim( $relative_path, '/' ); return trailingslashit( (string) $base['uri'] ) . $relative_path; } /** * Recursive function to pop a folder array. * * @param int $times * @param string[] $folder_array * @return string[] */ private function pop_folder_array( $times, $folder_array ) { $latest_folder = array_pop( $folder_array ); if ( '..' === $latest_folder ) { return $this->pop_folder_array( $times + 1, $folder_array ); } if ( '.' === $latest_folder ) { return $this->pop_folder_array( $times, $folder_array ); } if ( $times > 1 ) { return $this->pop_folder_array( $times - 1, $folder_array ); } return $folder_array; } /** * Get path relative to ABSPATH. * * @param string $path File path. * * @return string */ public function get_relative_path( $path ) { return str_replace( $this->fix_dir_separator( ABSPATH ), '', $this->fix_dir_separator( $path ) ); } /** * Get full file path. * * @param string $path File path. * * @return string */ public function get_full_path( $path ) { return ABSPATH . $this->get_relative_path( $path ); } /** * Check if file exists. * * @param string $path File path. * * @return bool */ public function file_exists( $path ) { return $this->filesystem->is_readable( $this->get_full_path( $path ) ); } /** * Get file modification time. * * @param string $path File path. * * @return int */ public function get_file_modified_timestamp( $path ) { return $this->filesystem->mtime( $this->get_full_path( $path ) ); } } language/Detection/Backend.php 0000755 00000004745 14720342453 0012326 0 ustar 00 <?php namespace WPML\Language\Detection; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Relation; use function WPML\FP\System\filterVar; use function \WPML\FP\partialRight; /** * Class WPML_Backend_Request * * @package wpml-core * @subpackage wpml-requests */ class Backend extends \WPML_Request { /** * Determines the requested language in the WP Admin backend from URI, $_POST, $_GET and cookies. * * @return string The requested language code. */ public function get_requested_lang() { $findFromSystemVars = Fns::until( Logic::isNotNull(), [ $this->getForPage(), $this->getFromParam( [ 'get', 'lang' ], true ), $this->getFromParam( [ 'post', 'icl_post_language' ], false ), $this->getPostElementLanguage(), ] ); return Maybe::of( [ 'get' => $_GET, 'post' => $_POST, ] ) ->map( $findFromSystemVars ) ->getOrElse( function () { return $this->get_cookie_lang(); } ); } private function getForPage() { return function ( $system ) { return $this->is_string_translation_or_translation_queue_page( $system ) ? $this->default_language : null; }; } protected function get_cookie_name() { return $this->cookieLanguage->getBackendCookieName(); } private function getFromParam( $path, $allowAllValue ) { return function ( $system ) use ( $path, $allowAllValue ) { /** @var \WPML_Language_Resolution $wpml_language_resolution */ global $wpml_language_resolution; return Maybe::of( $system ) ->map( Obj::path( $path ) ) ->map( filterVar( FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) ->filter( partialRight( [ $wpml_language_resolution, 'is_language_active' ], $allowAllValue ) ) ->getOrElse( null ); }; } private function getPostElementLanguage() { return function ( $system ) { /** @var \WPML_Post_Translation $wpml_post_translations */ global $wpml_post_translations; return Maybe::of( $system ) ->map( Obj::path( [ 'get', 'p' ] ) ) ->filter( Relation::gt( Fns::__, 0 ) ) ->map( [ $wpml_post_translations, 'get_element_lang_code' ] ) ->getOrElse( null ); }; } private function is_string_translation_or_translation_queue_page( $system ) { $page = Obj::path( [ 'get', 'page' ], $system ); return ( defined( 'WPML_ST_FOLDER' ) && $page === WPML_ST_FOLDER . '/menu/string-translation.php' ) || ( defined( 'WPML_TM_FOLDER' ) && $page === WPML_TM_FOLDER . '/menu/translations-queue.php' ); } } language/Detection/CookieLanguage.php 0000755 00000006206 14720342453 0013646 0 ustar 00 <?php namespace WPML\Language\Detection; class CookieLanguage { /** @var \WPML_Cookie */ private $cookie; /** @var string */ private $defaultLanguage; /** * @param \WPML_Cookie $cookie * @param string $defaultLanguage */ public function __construct( \WPML_Cookie $cookie, $defaultLanguage ) { $this->cookie = $cookie; $this->defaultLanguage = $defaultLanguage; } /** * @param bool $isBackend * * @return string */ public function getAjaxCookieName( $isBackend ) { return $isBackend ? $this->getBackendCookieName() : $this->getFrontendCookieName(); } public function getBackendCookieName() { return 'wp-wpml_current_admin_language_' . md5( (string) $this->get_cookie_domain() ); } public function getFrontendCookieName() { return 'wp-wpml_current_language'; } public function get( $cookieName ) { global $wpml_language_resolution; $cookie_value = esc_attr( $this->cookie->get_cookie( $cookieName ) ); $lang = $cookie_value ? substr( $cookie_value, 0, 10 ) : null; $lang = $wpml_language_resolution->is_language_active( $lang ) ? $lang : $this->defaultLanguage; return $lang; } public function set( $cookieName, $lang_code ) { global $sitepress; if ( is_user_logged_in() ) { if ( ! $this->cookie->headers_sent() ) { if ( preg_match( '@\.(css|js|png|jpg|gif|jpeg|bmp|ico)@i', basename( preg_replace( '@\?.*$@', '', $_SERVER['REQUEST_URI'] ) ) ) || isset( $_POST['icl_ajx_action'] ) || isset( $_POST['_ajax_nonce'] ) || defined( 'DOING_AJAX' ) ) { return; } $current_cookie_value = $this->cookie->get_cookie( $cookieName ); if ( ! $current_cookie_value || $current_cookie_value !== $lang_code ) { $cookie_domain = $this->get_cookie_domain(); $cookie_path = defined( 'COOKIEPATH' ) ? COOKIEPATH : '/'; $this->cookie->set_cookie( $cookieName, $lang_code, time() + DAY_IN_SECONDS, $cookie_path, $cookie_domain ); } } } elseif ( $sitepress->get_setting( \WPML_Cookie_Setting::COOKIE_SETTING_FIELD ) ) { $wpml_cookie_scripts = new \WPML_Cookie_Scripts( $cookieName, $sitepress->get_current_language() ); $wpml_cookie_scripts->add_hooks(); } $_COOKIE[ $cookieName ] = $lang_code; do_action( 'wpml_language_cookie_added', $lang_code ); } /** * @return bool|string */ public function get_cookie_domain() { return defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : self::get_server_host_name(); } /** * Returns SERVER_NAME, or HTTP_HOST if the first is not available * * @return string */ private static function get_server_host_name() { $host = ''; if ( isset( $_SERVER['HTTP_HOST'] ) ) { $host = $_SERVER['HTTP_HOST']; } elseif ( isset( $_SERVER['SERVER_NAME'] ) ) { $host = $_SERVER['SERVER_NAME'] . self::get_port(); // Removes standard ports 443 (80 should be already omitted in all cases) $host = preg_replace( '@:[443]+([/]?)@', '$1', $host ); } return $host; } private static function get_port() { return isset( $_SERVER['SERVER_PORT'] ) && ! in_array( $_SERVER['SERVER_PORT'], [ 80, 443 ] ) ? ':' . $_SERVER['SERVER_PORT'] : ''; } } language/Detection/Rest.php 0000755 00000002107 14720342453 0011702 0 ustar 00 <?php namespace WPML\Language\Detection; use \WPML_Request; class Rest extends WPML_Request { /** @var Backend */ private $backend; public function __construct( $url_converter, $active_languages, $default_language, $cookieLanguage, $backend ) { parent::__construct( $url_converter, $active_languages, $default_language, $cookieLanguage ); $this->backend = $backend; } protected function get_cookie_name() { return $this->cookieLanguage->getAjaxCookieName( ! $this->getFrontendLanguage() ); } public function get_requested_lang() { return $this->getFrontendLanguage() ?: $this->backend->get_requested_lang(); } /** * It tries to detect language in FRONTEND manner. * * We ignore a default language due to fallback mechanism in WPML_URL_Converter_Subdir_Strategy which never returns * NULL when `use_directory_for_default_lang` option is enabled. * * @return string|null */ private function getFrontendLanguage() { $language = $this->get_request_uri_lang(); return $language && $language !== $this->default_language ? $language : null; } } language/Detection/Ajax.php 0000755 00000001545 14720342453 0011655 0 ustar 00 <?php namespace WPML\Language\Detection; use WPML\FP\Maybe; use WPML\FP\Obj; use WPML\FP\Lst; use WPML\FP\Str; use WPML\FP\Fns; use \WPML_Request; class Ajax extends WPML_Request { public function get_requested_lang() { return Maybe::of( $_REQUEST ) ->map( Obj::prop( 'lang' ) ) ->filter( Lst::includes( Fns::__, $this->active_languages ) ) ->map( 'sanitize_text_field' ) ->getOrElse( function () { return $this->get_cookie_lang(); } ); } protected function get_cookie_name() { return $this->cookieLanguage->getAjaxCookieName( $this->is_admin_action_from_referer() ); } /** * @return bool */ private function is_admin_action_from_referer() { return (bool) Maybe::of( $_SERVER ) ->map( Obj::prop( 'HTTP_REFERER' ) ) ->map( Str::pos( '/wp-admin/' ) ) ->getOrElse( false ); } } language/Detection/Frontend.php 0000755 00000002156 14720342453 0012550 0 ustar 00 <?php namespace WPML\Language\Detection; use WPML\FP\Maybe; use WPML\FP\Obj; use \WPML_Request; use \WPML_WP_Comments; use function WPML\FP\System\filterVar; /** * @package wpml-core * @subpackage wpml-requests */ class Frontend extends WPML_Request { /** @var \WPML_WP_API */ private $wp_api; public function __construct( \WPML_URL_Converter $url_converter, $active_languages, $default_language, CookieLanguage $cookieLanguage, \WPML_WP_API $wp_api ) { parent::__construct( $url_converter, $active_languages, $default_language, $cookieLanguage ); $this->wp_api = $wp_api; } public function get_requested_lang() { return $this->wp_api->is_comments_post_page() ? $this->get_comment_language() : $this->get_request_uri_lang(); } /** * @return string */ private function get_comment_language() { return Maybe::of( $_POST ) ->map( Obj::prop( WPML_WP_Comments::LANG_CODE_FIELD ) ) ->map( filterVar( FILTER_SANITIZE_SPECIAL_CHARS ) ) ->getOrElse( $this->default_language ); } protected function get_cookie_name() { return $this->cookieLanguage->getFrontendCookieName(); } } language/class-wpml-language-pair-records.php 0000755 00000007400 14720342453 0015303 0 ustar 00 <?php use WPML\Element\API\Languages; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\User\LanguagePairs\ILanguagePairs; /** * Class WPML_Language_Pair_Records * * Language pairs are stored as user meta as an array of the form * array( $from_lang => array( $to_lang_1 => '1', $to_lang_2 => '1' ) */ class WPML_Language_Pair_Records implements ILanguagePairs { private $meta_key; /** @var WPML_Language_Records $language_records */ private $language_records; /** @var array|null */ private $active_language_codes; /** * @param wpdb $wpdb * @param WPML_Language_Records $language_records * @param array|null $active_language_codes */ public function __construct( wpdb $wpdb, WPML_Language_Records $language_records, $active_language_codes = null ) { $this->meta_key = $wpdb->prefix . 'language_pairs'; $this->language_records = $language_records; $this->active_language_codes = $active_language_codes ?: Lst::pluck( 'code', Languages::getActive() ); } /** * @param int $user_id * @param array $language_pairs * * Language pairs are an array of the form * array( $from_lang => array( $to_lang_1, $to_lang_2 ) */ public function store( $user_id, $language_pairs ) { $language_pairs = $this->convert_to_storage_format( $language_pairs ); update_user_meta( $user_id, $this->meta_key, $language_pairs ); do_action( 'wpml_update_translator' ); } /** * @param int $user_id * @param array $language_pairs * * Stores only the language pairs that are active. */ public function store_active( $user_id, $language_pairs ) { $language_pairs = wpml_collect( $language_pairs ) ->mapWithKeys( function( $to, $from ) { if ( ! $this->active_language_codes || ! in_array( $from, $this->active_language_codes, true ) ) { return []; } return [ $from => array_intersect( $to, $this->active_language_codes ) ]; } ) ->filter( Logic::complement( Logic::isEmpty() ) ) ->toArray(); $this->store( $user_id, $language_pairs ); } /** * @param int $user_id */ public function remove_all( $user_id ) { delete_user_meta( $user_id, $this->meta_key ); } /** * @param int $user_id * @return array * * Language pairs are returned in an array of the form * array( $from_lang => array( $to_lang_1, $to_lang_2 ) */ public function get( $user_id ) { $language_pairs = get_user_meta( $user_id, $this->meta_key, true ); if ( ! $language_pairs ) { $language_pairs = array(); } return $this->convert_from_storage_format( $language_pairs ); } private function convert_to_storage_format( $language_pairs ) { if ( $this->is_in_storage_format( $language_pairs ) ) { return $language_pairs; } foreach ( $language_pairs as $from => $to_langs ) { $targets = array(); foreach ( $to_langs as $lang ) { $targets[ $lang ] = 1; } $language_pairs[ $from ] = $targets; } return $language_pairs; } private function is_in_storage_format( $language_pairs ) { $first_from = reset( $language_pairs ); if ( $first_from && ! is_numeric( key( $first_from ) ) ) { return true; } return false; } private function convert_from_storage_format( array $language_pairs ) { foreach ( $language_pairs as $from => $to_langs ) { if ( $this->language_records->is_valid( $from ) ) { $language_pairs[ $from ] = array(); foreach ( array_keys( $to_langs ) as $lang ) { if ( $this->language_records->is_valid( $lang ) ) { $language_pairs[ $from ][] = $lang; } } } else { unset( $language_pairs[ $from ] ); } } return $language_pairs; } /** * @param int $user_id */ public function remove_invalid_language_pairs( $user_id ) { $language_pairs = $this->get( $user_id ); $this->store( $user_id, $language_pairs ); } } language/class-wpml-all-language-pairs.php 0000755 00000000641 14720342453 0014575 0 ustar 00 <?php class WPML_All_Language_Pairs { public static function get() { $languages = array_keys( \WPML\Element\API\Languages::getActive() ); $lang_pairs = array(); foreach ( $languages as $from_lang ) { $lang_pairs[ $from_lang ] = array(); foreach ( $languages as $to_lang ) { if ( $from_lang !== $to_lang ) { $lang_pairs[ $from_lang ][] = $to_lang; } } } return $lang_pairs; } } class-wpml-config-update-integrator.php 0000755 00000005337 14720342453 0014255 0 ustar 00 <?php class WPML_Config_Update_Integrator { /** @var WPML_Config_Update_Log */ private $log; /** @var WPML_Config_Update */ private $worker; /** * @param WPML_Log $log * @param WPML_Config_Update|null $worker */ public function __construct( WPML_Log $log, WPML_Config_Update $worker = null ) { $this->log = $log; $this->worker = $worker; } /** * @return WPML_Config_Update */ public function get_worker() { if ( null === $this->worker ) { global $sitepress; $http = new WP_Http(); $this->worker = new WPML_Config_Update( $sitepress, $http, $this->log ); } return $this->worker; } /** * @param WPML_Config_Update $worker */ public function set_worker( WPML_Config_Update $worker ) { $this->worker = $worker; } public function add_hooks() { add_action( 'update_wpml_config_index', array( $this, 'update_event_cron' ) ); add_action( 'wp_ajax_update_wpml_config_index', array( $this, 'update_event_ajax' ) ); add_action( 'after_switch_theme', array( $this, 'update_event' ) ); add_action( 'activated_plugin', array( $this, 'update_event' ) ); add_action( 'wpml_setup_completed', array( $this, 'update_event' ) ); add_action( 'wpml_refresh_remote_xml_config', array( $this, 'update_event' ) ); add_action( 'wpml_loaded', array( $this, 'handle_requests' ) ); } public function handle_requests() { $action_name = WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-action'; $action_nonce = WPML_XML_Config_Log_Notice::NOTICE_ERROR_GROUP . '-nonce'; $action = array_key_exists( $action_name, $_GET ) ? $_GET[ $action_name ] : null; $nonce = array_key_exists( $action_nonce, $_GET ) ? $_GET[ $action_nonce ] : null; if ( $action && $nonce && wp_verify_nonce( $nonce, $action ) ) { if ( 'wpml_xml_update_clear' === $action ) { $this->log->clear(); wp_safe_redirect( $this->log->get_log_url(), 302, 'WPML' ); } if ( 'wpml_xml_update_refresh' === $action ) { $this->upgrader_process_complete_event(); } } } public function update_event() { $this->get_worker() ->run(); } public function upgrader_process_complete_event() { $this->get_worker() ->run(); } public function update_event_ajax() { $nonce = isset( $_POST['_icl_nonce'] ) ? sanitize_text_field( $_POST['_icl_nonce'] ) : ''; if ( ! wp_verify_nonce( $nonce, 'icl_theme_plugins_compatibility_nonce' ) ) { wp_send_json_error( esc_html__( 'Invalid request!', 'sitepress' ), 400 ); return; } if ( $this->get_worker() ->run() ) { echo date( 'F j, Y H:i a', time() ); } die; } public function update_event_cron() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $this->update_event(); } } wp/screens/class-wpml-wp-options-general-hooks.php 0000755 00000002067 14720342453 0016314 0 ustar 00 <?php class WPML_WP_Options_General_Hooks implements IWPML_Action { public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); } public function admin_enqueue_scripts( $hook ) { wp_enqueue_script( 'wpml-options-general', ICL_PLUGIN_URL . '/dist/js/wp-options-general/app.js', array(), ICL_SITEPRESS_VERSION ); $link = '<a href="' . admin_url( 'admin.php?page=' . WPML_PLUGIN_FOLDER . '/menu/languages.php#lang-sec-1' ) . '">' . /* translators: "WPML Site Languages section" is the title of the WPML settings page where administrators can configure the site's languages */ esc_html__( 'WPML Site Languages section', 'sitepress' ) . '</a>'; /* translators: "%s" will be replaced with a link to "WPML Site Languages section" page */ $message = sprintf( __( 'When WPML is activated, the site language should be changed in the %s.', 'sitepress' ), $link ); wp_localize_script( 'wpml-options-general', 'wpmlOptionsGeneral', array( 'languageSelectMessage' => $message ) ); } } wp/screens/class-wpml-wp-options-general-hooks-factory.php 0000755 00000000406 14720342453 0017754 0 ustar 00 <?php class WPML_WP_Options_General_Hooks_Factory extends WPML_Current_Screen_Loader_Factory { protected function get_screen_regex() { return '/^options-general$/'; } protected function create_hooks() { return new WPML_WP_Options_General_Hooks(); } } class-wpml-theme-localization-type.php 0000755 00000000417 14720342453 0014115 0 ustar 00 <?php /** * @deprecated since WPML 4.2.8 * * Some constants are used in other projects, but it should be removed soon. */ class WPML_Theme_Localization_Type { const USE_ST = 1; const USE_MO_FILES = 2; const USE_ST_AND_NO_MO_FILES = 3; } roles/DefaultCapabilities.php 0000755 00000003107 14720342453 0012307 0 ustar 00 <?php namespace WPML; class DefaultCapabilities { public static function get() { return [ 'wpml_manage_translation_management' => __( 'Manage Translation Management', 'sitepress' ), 'wpml_manage_languages' => __( 'Manage Languages', 'sitepress' ), 'wpml_manage_theme_and_plugin_localization' => __( 'Manage Theme and Plugin localization', 'sitepress' ), 'wpml_manage_support' => __( 'Manage Support', 'sitepress' ), 'wpml_manage_woocommerce_multilingual' => __( 'Manage WooCommerce Multilingual', 'sitepress' ), 'wpml_operate_woocommerce_multilingual' => __( 'Operate WooCommerce Multilingual. Everything on WCML except the settings tab.', 'sitepress' ), 'wpml_manage_media_translation' => __( 'Manage Media translation', 'sitepress' ), 'wpml_manage_navigation' => __( 'Manage Navigation', 'sitepress' ), 'wpml_manage_sticky_links' => __( 'Manage Sticky Links', 'sitepress' ), 'wpml_manage_string_translation' => __( 'Manage String Translation', 'sitepress' ), 'wpml_manage_translation_analytics' => __( 'Manage Translation Analytics', 'sitepress' ), 'wpml_manage_wp_menus_sync' => __( 'Manage WPML Menus Sync', 'sitepress' ), 'wpml_manage_taxonomy_translation' => __( 'Manage Taxonomy Translation', 'sitepress' ), 'wpml_manage_troubleshooting' => __( 'Manage Troubleshooting', 'sitepress' ), 'wpml_manage_translation_options' => __( 'Translation options', 'sitepress' ), ]; } } roles/Roles.php 0000755 00000001132 14720342453 0007471 0 ustar 00 <?php namespace WPML; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\User; use WPML\LIB\WP\Roles as WPRoles; use function WPML\FP\spreadArgs; class Roles implements \IWPML_Backend_Action, \IWPML_AJAX_Action { public function add_hooks() { Hooks::onAction( 'set_user_role', 10, 3 )->then( spreadArgs( [ self::class, 'remove_caps' ] ) ); } public static function remove_caps( $userId, $role, $oldRoles ) { if ( ! WPRoles::hasCap( 'manage_options', $role ) ) { $user = User::get( $userId ); wpml_collect( DefaultCapabilities::get() ) ->keys() ->map( [ $user, 'remove_cap' ] ); } } } settings/class-wpml-post-custom-field-setting-keys.php 0000755 00000001626 14720342453 0017213 0 ustar 00 <?php class WPML_Post_Custom_Field_Setting_Keys { /** * @return string */ public static function get_state_array_setting_index() { return 'custom_fields_translation'; } /** * @return string */ public static function get_unlocked_setting_index() { return defined( 'WPML_POST_META_UNLOCKED_SETTING_INDEX' ) ? WPML_POST_META_UNLOCKED_SETTING_INDEX : 'custom_fields_unlocked_config'; } /** * @return string */ public static function get_setting_prefix() { return 'custom_fields_'; } /** * @return string[] */ public static function get_excluded_keys() { return array( '_edit_last', '_edit_lock', '_wp_page_template', '_wp_attachment_metadata', '_icl_translator_note', '_alp_processed', '_pingme', '_encloseme', '_icl_lang_duplicate_of', '_wpml_media_duplicate', 'wpml_media_processed', '_wpml_media_featured', '_thumbnail_id' ); } } settings/class-wpml-page-builder-settings.php 0000755 00000001744 14720342453 0015410 0 ustar 00 <?php class WPML_Page_Builder_Settings { const OPTION_KEY = 'wpml_page_builders_options'; private $settings; /** @return bool */ public function is_raw_html_translatable() { return (bool) $this->get_setting( 'translate_raw_html', true ); } /** @param bool $is_enabled */ public function set_raw_html_translatable( $is_enabled ) { $this->set_setting( 'translate_raw_html', $is_enabled ); } /** * @param string $key * @param mixed $value */ private function set_setting( $key, $value ) { $this->settings[ $key ] = $value; } /** * @param string $key * @param mixed $default * * @return mixed */ private function get_setting( $key, $default = null ) { if ( null === $this->settings ) { $this->settings = get_option( self::OPTION_KEY, array() ); } if ( array_key_exists( $key, $this->settings ) ) { return $this->settings[ $key ]; } return $default; } public function save() { update_option( self::OPTION_KEY, $this->settings ); } } settings/class-wpml-tm-serialized-custom-field-package-handler.php 0000755 00000006013 14720342453 0021352 0 ustar 00 <?php class WPML_TM_Serialized_Custom_Field_Package_Handler { /** @var WPML_Custom_Field_Setting_Factory $custom_field_setting_factory */ private $custom_field_setting_factory; public function __construct( WPML_Custom_Field_Setting_Factory $custom_field_setting_factory ) { $this->custom_field_setting_factory = $custom_field_setting_factory; } public function add_hooks() { add_filter( 'wpml_translation_job_post_meta_value_translated', array( $this, 'translate_only_whitelisted_attributes', ), 10, 2 ); } /** * @param int $translated * @param string $custom_field_job_type - e.g: field-my_custom_field-0-my_attribute. * * @return int */ public function translate_only_whitelisted_attributes( $translated, $custom_field_job_type ) { if ( $translated ) { list( $custom_field, $attributes ) = WPML_TM_Field_Type_Encoding::decode( $custom_field_job_type ); if ( $custom_field && $attributes ) { $settings = $this->custom_field_setting_factory->post_meta_setting( $custom_field ); $attributes_whitelist = $settings->get_attributes_whitelist(); if ( $attributes_whitelist ) { $translated = $this->match_in_order( $attributes, $attributes_whitelist ) ? $translated : 0; } } } return $translated; } /** * Matches the attributes array to the whitelist array * The whitelist array has the attribute as the key to another array for sub keys * eg. array( 'attribute1' => array( 'subkey1' => array() ) ) * * @param array $attributes - The attributes in the custom field. * @param array $whitelist - The whitelist attributes to match against. * @param int $current_depth - The current depth in the attributes array. * * @return bool */ private function match_in_order( $attributes, $whitelist, $current_depth = 0 ) { $current_attribute = $attributes[ $current_depth ]; $wildcard_match = $this->match_with_wildcards( $current_attribute, array_keys( $whitelist ) ); if ( $wildcard_match ) { if ( count( $attributes ) === $current_depth + 1 ) { return true; } else { return $this->match_in_order( $attributes, $whitelist[ $wildcard_match ], $current_depth + 1 ); } } return false; } /** * Matches the attribute to the whitelist array using wildcards. * Wildcards can only be used at the end of the string. * eg. 'title-*', 'data*', '*' * A '*' matches everything. * * @param string $attribute - the current attributes. * @param array $whitelist - the whitelist to match against. * * @return string - Returns the whitelist string match. */ private function match_with_wildcards( $attribute, $whitelist ) { foreach ( $whitelist as $white_value ) { $asterisk_pos = strpos( $white_value, '*' ); if ( false === $asterisk_pos ) { if ( $attribute === $white_value ) { return $white_value; } } else { if ( 0 === $asterisk_pos || substr( $attribute, 0, $asterisk_pos ) === substr( $white_value, 0, $asterisk_pos ) ) { return $white_value; } } } return ''; } } settings/class-wpml-tm-settings-post-process.php 0000755 00000002006 14720342453 0016117 0 ustar 00 <?php class WPML_TM_Settings_Post_Process extends WPML_TM_User { /** * Saves TM settings to the database in case they have changed after reading a config file. */ public function run() { $changed = false; $settings = &$this->tm_instance->settings; foreach ( array( WPML_POST_META_READONLY_SETTING_INDEX, WPML_TERM_META_READONLY_SETTING_INDEX, WPML_POST_TYPE_READONLY_SETTING_INDEX ) as $index ) { $prev_index = $this->prev_index( $index ); if ( isset( $settings[ $index ] ) && isset( $settings[ $prev_index ] ) ) { foreach ( array( $index => $prev_index, $prev_index => $index ) as $left_index => $right_index ) { foreach ( $settings[ $right_index ] as $cf ) { if ( ! in_array( $cf, $settings[ $left_index ] ) ) { $changed = true; break; } } } } } if ( $changed ) { $this->tm_instance->save_settings(); } } private function prev_index( $index ) { return '__' . $index . '_prev'; } } settings/class-wpml-verify-sitepress-settings.php 0000755 00000007116 14720342453 0016372 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 5/10/17 * Time: 10:23 PM */ class WPML_Verify_SitePress_Settings { /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_WP_API $wp_api ) { $this->wp_api = $wp_api; } /** * @param array $settings * * @return array */ public function verify( $settings ) { $default_settings = [ 'interview_translators' => 1, 'existing_content_language_verified' => 0, 'language_negotiation_type' => 1, 'icl_lso_link_empty' => 0, 'sync_page_ordering' => 1, 'sync_page_parent' => 1, 'sync_page_template' => 1, 'sync_ping_status' => 1, 'sync_comment_status' => 1, 'sync_sticky_flag' => 1, 'sync_password' => 1, 'sync_private_flag' => 1, 'sync_post_format' => 1, 'sync_delete' => 0, 'sync_delete_tax' => 0, 'sync_post_taxonomies' => 1, 'sync_post_date' => 1, 'sync_taxonomy_parents' => 0, 'translation_pickup_method' => 0, 'notify_complete' => 1, 'translated_document_status' => 1, 'translated_document_status_sync' => 0, 'remote_management' => 0, 'auto_adjust_ids' => 1, 'alert_delay' => 0, 'promote_wpml' => 0, 'automatic_redirect' => 0, 'remember_language' => 24, 'icl_lang_sel_copy_parameters' => '', 'translated_document_page_url' => 'auto-generate', 'sync_comments_on_duplicates' => 0, 'seo' => [ 'head_langs' => 1, 'canonicalization_duplicates' => 1, 'head_langs_priority' => 1, ], 'posts_slug_translation' => [ /** @deprected key `on`, use option `wpml_base_slug_translation` instead */ 'on' => 1, ], 'languages_order' => [], 'urls' => [ 'directory_for_default_language' => 0, 'show_on_root' => '', 'root_html_file_path' => '', 'root_page' => 0, 'hide_language_switchers' => 1, ], 'xdomain_data' => $this->wp_api->constant( 'WPML_XDOMAIN_DATA_GET' ), 'custom_posts_sync_option' => [ 'post' => WPML_CONTENT_TYPE_TRANSLATE, 'page' => WPML_CONTENT_TYPE_TRANSLATE, ], 'taxonomies_sync_option' => [ 'category' => WPML_CONTENT_TYPE_TRANSLATE, 'post_tag' => WPML_CONTENT_TYPE_TRANSLATE, ], 'tm_block_retranslating_terms' => 1, ]; // configured for three levels $update_settings = false; foreach ( $default_settings as $key => $value ) { if ( is_array( $value ) ) { foreach ( $value as $k2 => $v2 ) { if ( is_array( $v2 ) ) { foreach ( $v2 as $k3 => $v3 ) { if ( ! isset( $settings[ $key ][ $k2 ][ $k3 ] ) ) { $settings[ $key ][ $k2 ][ $k3 ] = $v3; $update_settings = true; } } } else { if ( ! isset( $settings[ $key ][ $k2 ] ) ) { $settings[ $key ][ $k2 ] = $v2; $update_settings = true; } } } } else { if ( ! isset( $settings[ $key ] ) ) { $settings[ $key ] = $value; $update_settings = true; } } } return [ $settings, $update_settings ]; } } settings/class-wpml-custom-field-setting.php 0000755 00000016205 14720342453 0015256 0 ustar 00 <?php use WPML\FP\Logic; abstract class WPML_Custom_Field_Setting extends WPML_TM_User { /** @var string $index */ private $index; /** * WPML_Custom_Field_Setting constructor. * * @param TranslationManagement $tm_instance * @param string $index */ public function __construct( &$tm_instance, $index ) { parent::__construct( $tm_instance ); $this->index = $index; } /** * @return string */ public function get_index() { return $this->index; } /** * @return bool true if the custom field setting is given by a setting in * a wpml-config.xml */ public function is_read_only() { return in_array( $this->index, $this->tm_instance->settings[ $this->get_array_setting_index( 'readonly_config' ) ], true ); } /** * @return bool */ public function is_unlocked() { return isset( $this->tm_instance->settings[ $this->get_unlocked_setting_index() ][ $this->index ] ) && (bool) $this->tm_instance->settings[ $this->get_unlocked_setting_index() ][ $this->index ]; } /** * @return bool */ public function excluded() { return in_array( $this->index, $this->get_excluded_keys() ) || ( $this->is_read_only() && $this->status() === WPML_IGNORE_CUSTOM_FIELD && ! $this->is_unlocked() ); } public function status() { $state_index = $this->get_state_array_setting_index(); if ( ! isset( $this->tm_instance->settings[ $state_index ][ $this->index ] ) ) { $this->tm_instance->settings[ $state_index ][ $this->index ] = WPML_IGNORE_CUSTOM_FIELD; } return (int) $this->tm_instance->settings[ $state_index ][ $this->index ]; } public function make_read_only() { $ro_index = $this->get_array_setting_index( 'readonly_config' ); $this->tm_instance->settings[ $ro_index ][] = $this->index; $this->tm_instance->settings[ $ro_index ] = array_unique( $this->tm_instance->settings[ $ro_index ] ); } public function set_to_copy() { $this->set_state( WPML_COPY_CUSTOM_FIELD ); } public function set_to_copy_once() { $this->set_state( WPML_COPY_ONCE_CUSTOM_FIELD ); } public function set_to_translatable() { $this->set_state( WPML_TRANSLATE_CUSTOM_FIELD ); } public function set_to_nothing() { $this->set_state( WPML_IGNORE_CUSTOM_FIELD ); } public function set_editor_style( $style ) { $this->tm_instance->settings[ $this->get_array_setting_index( 'editor_style' ) ][ $this->index ] = $style; } public function get_editor_style() { $setting = $this->get_array_setting_index( 'editor_style' ); return isset( $this->tm_instance->settings[ $setting ][ $this->index ] ) ? $this->tm_instance->settings[ $setting ][ $this->index ] : ''; } public function set_editor_label( $label ) { $this->tm_instance->settings[ $this->get_array_setting_index( 'editor_label' ) ][ $this->index ] = $label; } public function get_editor_label() { $setting = $this->get_array_setting_index( 'editor_label' ); return isset( $this->tm_instance->settings[ $setting ][ $this->index ] ) ? $this->tm_instance->settings[ $setting ][ $this->index ] : ''; } public function set_editor_group( $group ) { $this->tm_instance->settings[ $this->get_array_setting_index( 'editor_group' ) ][ $this->index ] = $group; } public function get_editor_group() { $setting = $this->get_array_setting_index( 'editor_group' ); return isset( $this->tm_instance->settings[ $setting ][ $this->index ] ) ? $this->tm_instance->settings[ $setting ][ $this->index ] : ''; } public function set_translate_link_target( $state, $sub_fields ) { if ( isset( $sub_fields['value'] ) ) { // it's a single sub field $sub_fields = array( $sub_fields ); } $this->tm_instance->settings[ $this->get_array_setting_index( 'translate_link_target' ) ][ $this->index ] = array( 'state' => $state, 'sub_fields' => $sub_fields, ); } public function is_translate_link_target() { $array_index = $this->get_array_setting_index( 'translate_link_target' ); return isset( $this->tm_instance->settings[ $array_index ][ $this->index ] ) ? ( $this->tm_instance->settings[ $array_index ][ $this->index ]['state'] || $this->get_translate_link_target_sub_fields() ) : false; } public function get_translate_link_target_sub_fields() { $array_index = $this->get_array_setting_index( 'translate_link_target' ); return isset( $this->tm_instance->settings[ $array_index ][ $this->index ]['sub_fields'] ) ? $this->tm_instance->settings[ $array_index ][ $this->index ]['sub_fields'] : array(); } public function set_convert_to_sticky( $state ) { $this->tm_instance->settings[ $this->get_array_setting_index( 'convert_to_sticky' ) ][ $this->index ] = $state; } public function is_convert_to_sticky() { $array_index = $this->get_array_setting_index( 'convert_to_sticky' ); return isset( $this->tm_instance->settings[ $array_index ][ $this->index ] ) ? $this->tm_instance->settings[ $array_index ][ $this->index ] : false; } public function set_encoding( $encoding ) { if ( Logic::isNotNull( $encoding ) ) { $this->tm_instance->settings[ $this->get_array_setting_index( 'encoding' ) ][ $this->index ] = $encoding; } else { unset( $this->tm_instance->settings[ $this->get_array_setting_index( 'encoding' ) ][ $this->index ] ); } } public function get_encoding() { $setting = $this->get_array_setting_index( 'encoding' ); return isset( $this->tm_instance->settings[ $setting ][ $this->index ] ) ? $this->tm_instance->settings[ $setting ][ $this->index ] : ''; } /** * @param array $whitelist */ public function set_attributes_whitelist( $whitelist ) { if ( ! is_array( $whitelist ) ) { throw new InvalidArgumentException( '$whitelist should be an array.' ); } $this->tm_instance->settings[ $this->get_array_setting_index( 'attributes_whitelist' ) ][ $this->index ] = $whitelist; } public function get_attributes_whitelist() { $setting = $this->get_array_setting_index( 'attributes_whitelist' ); return isset( $this->tm_instance->settings[ $setting ][ $this->index ] ) ? $this->tm_instance->settings[ $setting ][ $this->index ] : array(); } private function set_state( $state ) { $this->tm_instance->settings[ $this->get_state_array_setting_index() ][ $this->index ] = $state; } /** * @return string */ private function get_array_setting_index( $index ) { return $this->get_setting_prefix() . $index; } /** * @return string */ abstract protected function get_state_array_setting_index(); abstract protected function get_unlocked_setting_index(); /** * @return string[] */ abstract protected function get_excluded_keys(); /** * @return string */ abstract protected function get_setting_prefix(); /** * @return string */ public function get_html_disabled() { $isDisabled = $this->is_read_only() && ! $this->is_unlocked(); /** * This filter hook give the ability to disable the HTML radio buttons * for the custom field preference. * * @since 4.6.0 * * @param bool $isDisabled * @param WPML_Custom_Field_Setting $instance */ return apply_filters( 'wpml_custom_field_setting_is_html_disabled', $isDisabled, $this ) ? 'disabled="disabled"' : ''; } } settings/class-wpml-post-custom-field-setting.php 0000755 00000001270 14720342453 0016235 0 ustar 00 <?php class WPML_Post_Custom_Field_Setting extends WPML_Custom_Field_Setting { /** * @return string */ protected function get_state_array_setting_index() { return WPML_Post_Custom_Field_Setting_Keys::get_state_array_setting_index(); } /** * @return string */ protected function get_unlocked_setting_index() { return WPML_Post_Custom_Field_Setting_Keys::get_unlocked_setting_index(); } /** * @return string */ protected function get_setting_prefix() { return WPML_Post_Custom_Field_Setting_Keys::get_setting_prefix(); } /** * @return string[] */ protected function get_excluded_keys() { return WPML_Post_Custom_Field_Setting_Keys::get_excluded_keys(); } } settings/class-wpml-settings-filters.php 0000755 00000002013 14720342453 0014506 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_Settings_Filters { /** * @param array $types * @param array $read_only_cpt_settings * @param array $cpt_unlocked_options * * @return array * @see \WPML_Config::maybe_add_filter */ function get_translatable_documents( array $types, array $read_only_cpt_settings, array $cpt_unlocked_options ) { global $wp_post_types; foreach ( $read_only_cpt_settings as $cp => $translate ) { if ( $this->is_cpt_unlocked( $cpt_unlocked_options, $cp ) ) { continue; } if ( $translate && ! isset( $types[ $cp ] ) && isset( $wp_post_types[ $cp ] ) ) { $types[ $cp ] = $wp_post_types[ $cp ]; } elseif ( ! $translate && isset( $types[ $cp ] ) ) { unset( $types[ $cp ] ); } } return $types; } /** * @param array $cpt_unlocked_options * @param string $cp * * @return bool */ private function is_cpt_unlocked( array $cpt_unlocked_options, $cp ) { return isset( $cpt_unlocked_options[ $cp ] ) && (bool) $cpt_unlocked_options[ $cp ]; } } settings/CustomFieldChangeDetector.php 0000755 00000005313 14720342453 0014144 0 ustar 00 <?php namespace WPML\TM\Settings; use WPML\Core\BackgroundTask\Service\BackgroundTaskService; use WPML\FP\Lst; use WPML\LIB\WP\Hooks; use WPML\WP\OptionManager; use function WPML\Container\make; class CustomFieldChangeDetector implements \IWPML_Backend_Action, \IWPML_DIC_Action { const PREVIOUS_SETTING = 'previous-custom-fields-to-translate'; const DETECTED_SETTING = 'detected-custom-fields-to-translate'; /** @var BackgroundTaskService */ private $backgroundTaskService; /** * @param BackgroundTaskService $backgroundTaskService */ public function __construct( BackgroundTaskService $backgroundTaskService ) { $this->backgroundTaskService = $backgroundTaskService; } public function add_hooks() { Hooks::onAction( 'wpml_after_tm_loaded', 1 ) ->then( [ self::class, 'getNew' ] ) ->then( [ self::class, 'notify' ] ) ->then( [ self::class, 'updatePrevious' ] ); } public static function getNew() { if ( is_null( OptionManager::getOr( null, 'TM', self::PREVIOUS_SETTING ) ) ) { self::updatePrevious(); } return Lst::diff( Repository::getCustomFieldsToTranslate() ?: [], OptionManager::getOr( [], 'TM', self::PREVIOUS_SETTING ) ); } public static function notify( array $newFields ) { if ( count( $newFields ) ) { OptionManager::update( 'TM', self::DETECTED_SETTING, Lst::concat( self::getDetected(), $newFields ) ); } } public static function remove( array $fields ) { if ( count( $fields ) ) { OptionManager::update( 'TM', self::DETECTED_SETTING, Lst::diff( self::getDetected(), $fields ) ); } } public static function updatePrevious() { OptionManager::update( 'TM', self::PREVIOUS_SETTING, Repository::getCustomFieldsToTranslate() ); } public static function getDetected() { return OptionManager::getOr( [], 'TM', self::DETECTED_SETTING ); } public function processNewFields() { $newFields = self::getDetected(); if ( count( $newFields ) > 0 ) { $newFields = array_unique( $newFields ); /** @var ProcessNewTranslatableFields $backroundTaskEndpoint */ $backroundTaskEndpoint = make( ProcessNewTranslatableFields::class ); $payload = wpml_collect( [ 'newFields' => $newFields ] ); if ( $backroundTaskEndpoint->getTotalRecords( $payload ) ) { // We could do some optimization to avoid running again after consecutive changes on same field. // But currently, it's more consistent to enqueue a new task every time, since there may be cases // when the user is running a task affecting some custom field for long time, and wants to update again // and ghet the posts re-processed. $this->backgroundTaskService->add( $backroundTaskEndpoint, $payload ); } self::remove( $newFields ); } } } settings/class-wpml-term-custom-field-setting.php 0000755 00000001271 14720342453 0016220 0 ustar 00 <?php class WPML_Term_Custom_Field_Setting extends WPML_Custom_Field_Setting { /** * @return string */ protected function get_state_array_setting_index() { return WPML_Term_Custom_Field_Setting_Keys::get_state_array_setting_index(); } /** * @return string */ protected function get_unlocked_setting_index() { return WPML_Term_Custom_Field_Setting_Keys::get_unlocked_setting_index(); } /** * @return string */ protected function get_setting_prefix() { return WPML_Term_Custom_Field_Setting_Keys::get_setting_prefix(); } /** * @return string[] */ protected function get_excluded_keys() { return WPML_Term_Custom_Field_Setting_Keys::get_excluded_keys(); } } settings/class-wpml-tm-settings-update.php 0000755 00000010206 14720342453 0014741 0 ustar 00 <?php use WPML\FP\Obj; class WPML_TM_Settings_Update extends WPML_SP_User { private $index_singular; private $index_ro; private $index_sync; private $index_plural; private $index_unlocked; /** @var TranslationManagement $tm_instance */ private $tm_instance; /** @var WPML_Settings_Helper $settings_helper */ private $settings_helper; /** * @param string $index_singular * @param string $index_plural * @param TranslationManagement $tm_instance * @param SitePress $sitepress * @param WPML_Settings_Helper $settings_helper */ public function __construct( $index_singular, $index_plural, &$tm_instance, &$sitepress, $settings_helper ) { parent::__construct( $sitepress ); $this->tm_instance = &$tm_instance; $this->index_singular = $index_singular; $this->index_plural = $index_plural; $this->index_ro = $index_plural . '_readonly_config'; $this->index_sync = $index_plural . '_sync_option'; $this->index_unlocked = 'custom-type' == $index_singular ? 'custom_posts_unlocked_option' : 'taxonomies_unlocked_option'; $this->settings_helper = $settings_helper; } /** * @param array $config */ public function update_from_config( array $config ) { $this->update_tm_settings( Obj::propOr( [], $this->index_plural, $config ) ); } private function sync_settings( array $config ) { $section_singular = $this->index_singular; $section_plural = $this->index_plural; if ( ! empty( $config[ $section_singular ] ) ) { $sync_option = $this->sitepress->get_setting( $this->index_sync, [] ); $unlocked_option = $this->sitepress->get_setting( $this->index_unlocked, [] ); if ( ! is_numeric( key( current( $config ) ) ) ) { $cf[0] = $config[ $section_singular ]; } else { $cf = $config[ $section_singular ]; } foreach ( $cf as $c ) { $val = $c['value']; if ( ! $this->is_unlocked_type( $val, $unlocked_option ) ) { $sync_existing_setting = isset( $sync_option[ $val ] ) ? $sync_option[ $val ] : false; $sync_new_setting = (int) $c['attr']['translate']; $this->tm_instance->settings[ $this->index_ro ][ $val ] = $sync_new_setting; $sync_option[ $val ] = $sync_new_setting; if ( $this->is_making_type_translatable( $sync_new_setting, $sync_existing_setting ) ) { if ( $section_plural === 'taxonomies' ) { $this->sitepress->verify_taxonomy_translations( $val ); } else { $this->sitepress->verify_post_translations( $val ); } $this->tm_instance->save_settings(); } } } $this->sitepress->set_setting( $this->index_sync, $sync_option ); $this->settings_helper->maybe_add_filter( $section_plural ); } } /** * @param int $new_sync 0, 1 or 2 * @param int $old_sync 0, 1 or 2 * * @return bool */ private function is_making_type_translatable( $new_sync, $old_sync ) { return in_array( $new_sync, [ WPML_CONTENT_TYPE_TRANSLATE, WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED, ] ) && WPML_CONTENT_TYPE_DONT_TRANSLATE === $old_sync; } private function update_tm_settings( array $config ) { $section_singular = $this->index_singular; $config = array_filter( $config ); $config[ $section_singular ] = Obj::propOr( [], $section_singular, $config ); $this->sync_settings( $config ); // taxonomies - check what's been removed if ( ! empty( $this->tm_instance->settings[ $this->index_ro ] ) ) { $config_values = []; foreach ( $config[ $section_singular ] as $config_value ) { $config_values[ $config_value['value'] ] = $config_value['attr']['translate']; } foreach ( $this->tm_instance->settings[ $this->index_ro ] as $key => $translation_option ) { if ( ! isset( $config_values[ $key ] ) ) { unset( $this->tm_instance->settings[ $this->index_ro ][ $key ] ); } } $this->tm_instance->save_settings(); } } private function is_unlocked_type( $type, $unlocked_options ) { return isset( $unlocked_options[ $type ] ) && $unlocked_options[ $type ]; } } settings/Repository.php 0000755 00000002457 14720342453 0011313 0 ustar 00 <?php namespace WPML\TM\Settings; class Repository { public static function getSetting( $indexes ) { $settings = self::getAllSettings(); /** * I do not know why the foreach loop looks like that. I have just copied it from WPML_Translation_Job_Helper * @todo Review it later and try simplify if possible */ foreach ( $indexes as $index ) { $settings = isset( $settings[ $index ] ) ? $settings[ $index ] : null; if ( ! isset( $settings ) ) { break; } } return $settings; } public static function getCustomFieldsToTranslate() { return array_values( array_filter( array_keys( self::getSetting( [ 'custom_fields_translation' ] ) ?: [], WPML_TRANSLATE_CUSTOM_FIELD ) ) ); } public static function getCustomFields() { return \wpml_collect( self::getSetting( [ 'custom_fields_translation' ] ) ?: [] ) ->filter( function ( $value, $key ) { return (bool) $key; } )->toArray(); } /** * @return array */ private static function getAllSettings() { /** @var \TranslationManagement $iclTranslationManagement */ global $iclTranslationManagement; if ( ! $iclTranslationManagement ) { return []; } if ( empty( $iclTranslationManagement->settings ) ) { $iclTranslationManagement->init(); } return $iclTranslationManagement->get_settings(); } } settings/class-wpml-tm-serialized-custom-field-package-handler-factory.php 0000755 00000000511 14720342453 0023014 0 ustar 00 <?php class WPML_TM_Serialized_Custom_Field_Package_Handler_Factory implements IWPML_Backend_Action_Loader { public function create() { $translation_management = wpml_load_core_tm(); return new WPML_TM_Serialized_Custom_Field_Package_Handler( new WPML_Custom_Field_Setting_Factory( $translation_management ) ); } } settings/class-wpml-element-sync-settings.php 0000755 00000000742 14720342453 0015450 0 ustar 00 <?php class WPML_Element_Sync_Settings { /** @var array $settings */ private $settings; public function __construct( array $settings ) { $this->settings = $settings; } /** * @param string $type * * @return bool */ public function is_sync( $type ) { return isset( $this->settings[ $type ] ) && ( $this->settings[ $type ] == WPML_CONTENT_TYPE_TRANSLATE || $this->settings[ $type ] == WPML_CONTENT_TYPE_DISPLAY_AS_IF_TRANSLATED ); } } settings/wpml-tm-default-settings.php 0000755 00000004352 14720342453 0014005 0 ustar 00 <?php class WPML_TM_Default_Settings implements IWPML_Action { /** @var TranslationManagement */ private $tm; public function __construct( TranslationManagement $tm ) { $this->tm = $tm; } public function add_hooks() { add_action( 'init', array( $this, 'init_action' ), $this->tm->get_init_priority() ); } public function init_action() { $this->maybe_update_notification( 'new-job', WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY ); $this->maybe_update_notification( 'include_xliff', (int) apply_filters( 'wpml_setting', 0, 'include_xliff_in_notification' ) ); if ( ! $this->has_notification( WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY ) ) { if ( $this->has_notification( 'completed' ) ) { $this->update_notification( WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY, $this->get_notification( 'completed' ) ); } else { $this->update_notification( WPML_TM_Emails_Settings::COMPLETED_JOB_FREQUENCY, WPML_TM_Emails_Settings::NOTIFY_WEEKLY ); } } $this->maybe_update_notification( 'completed', WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY ); $this->maybe_update_notification( 'resigned', WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY ); $this->maybe_update_notification( 'overdue', WPML_TM_Emails_Settings::NOTIFY_IMMEDIATELY ); $this->maybe_update_notification( 'overdue_offset', 7 ); $this->maybe_update_notification( 'dashboard', true ); $this->maybe_update_notification( 'purge-old', 7 ); } /** * @param string $key * @param mixed $default * * @return bool */ private function get_notification( $key, $default = null ) { return isset( $this->tm->settings['notification'][ $key ] ) ? $this->tm->settings['notification'][ $key ] : $default; } /** * @param string $key * * @return bool */ private function has_notification( $key ) { return isset( $this->tm->settings['notification'][ $key ] ); } /** * @param string $key * @param mixed $value */ private function maybe_update_notification( $key, $value ) { if ( ! $this->has_notification( $key ) ) { $this->update_notification( $key, $value ); } } /** * @param string $key * @param mixed $value */ private function update_notification( $key, $value ) { $this->tm->settings['notification'][ $key ] = $value; } } settings/class-wpml-term-custom-field-setting-keys.php 0000755 00000001032 14720342453 0017164 0 ustar 00 <?php class WPML_Term_Custom_Field_Setting_Keys { /** * @return string */ public static function get_state_array_setting_index() { return WPML_TERM_META_SETTING_INDEX_PLURAL; } /** * @return string */ public static function get_unlocked_setting_index() { return WPML_TERM_META_UNLOCKED_SETTING_INDEX; } /** * @return string */ public static function get_setting_prefix() { return 'custom_term_fields_'; } /** * @return string[] */ public static function get_excluded_keys() { return array(); } } settings/wpml-tm-default-settings-factory.php 0000755 00000000444 14720342453 0015450 0 ustar 00 <?php class WPML_TM_Default_Settings_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader { public function create() { /** @var TranslationManagement */ global $iclTranslationManagement; return new WPML_TM_Default_Settings( $iclTranslationManagement ); } } settings/Flags/Options.php 0000755 00000002131 14720342453 0011610 0 ustar 00 <?php namespace WPML\TM\Settings\Flags; use WPML\FP\Either; use WPML\FP\Lst; use WPML\FP\Fns; use WPML\LIB\WP\Option; use function WPML\Container\make; class Options { const FORMAT_OPTION = 'wpml_flags_format'; /** * @param string $format One of the values defined in the `getAllowedTypes` method. * * @return Either */ public static function saveFormat( $format ) { return Either::of( $format ) ->filter( Lst::includes( Fns::__, self::getAllowedFormats() ) ) ->map( Fns::tap( function ( $format ) { Option::update( self::FORMAT_OPTION, $format ); } ) ); } public static function getFormat() { /** @var \WPML\TM\Settings\Flags\FlagsRepository */ $repo = make( \WPML\TM\Settings\Flags\FlagsRepository::class ); $notset = 'notset'; $res = Option::getOr( self::FORMAT_OPTION, $notset ); if ( $res !== $notset ) { return $res; } $formats = self::getAllowedFormats(); return ( $repo->hasSvgFlags() ) ? $formats[1] : $formats[0]; } public static function getAllowedFormats() { return [ 'png', 'svg' ]; } } settings/Flags/FlagsRepository.php 0000755 00000002616 14720342453 0013321 0 ustar 00 <?php namespace WPML\TM\Settings\Flags; use WPML\FP\Obj; class FlagsRepository { /** @var \wpdb */ private $wpdb; /** * @param \wpdb $wpdb */ public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } public function getItems( $data = array() ) { $whereSqlParts = []; $whereArgs = []; if ( Obj::has( 'onlyInstalledByDefault', $data ) ) { $whereSqlParts[] = 'flags.from_template = 0'; } $whereSqlParts[] = '1=%d'; $whereArgs[] = 1; $whereSql = 'WHERE ' . implode( ' AND ', $whereSqlParts ); $flags = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT flags.id, flags.lang_code, flags.flag, flags.from_template FROM {$this->wpdb->prefix}icl_flags flags {$whereSql}", $whereArgs ) ); return $flags; } public function getItemsInstalledByDefault( $data = array() ) { return $this->getItems( array_merge( $data, [ 'onlyInstalledByDefault' => true, ] ) ); } private function getFlagsCountByExt( $ext ) { return (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(flags.id) FROM {$this->wpdb->prefix}icl_flags flags WHERE flags.from_template = 0 AND flags.flag LIKE %s", '%.' . $ext ) ); } public function hasSvgFlags() { return $this->getFlagsCountByExt( 'svg' ) > 0; } public function hasPngFlags() { return $this->getFlagsCountByExt( 'png' ) > 0; } } settings/Flags/Endpoints/SetFormat.php 0000755 00000001710 14720342453 0014026 0 ustar 00 <?php namespace WPML\TM\Settings\Flags\Endpoints; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Str; use WPML\TM\Settings\Flags\Command\ConvertFlags; use WPML\TM\Settings\Flags\Options; use function WPML\Container\make; class SetFormat { public function run( Collection $data ) { return Either::of( $data->get( 'format' ) ) ->filter( Lst::includes( Fns::__, Options::getAllowedFormats() ) ) ->chain( function ( $format ) { /** @var ConvertFlags $convertFlag */ $convertFlag = make( ConvertFlags::class ); return $convertFlag->run( $format ); } ) ->chain( function ( $format ) { return Options::saveFormat( $format ); } ) ->map( Str::concat( 'Flags converted to: ' ) ) ->bimap( Str::concat( 'Invalid format: ' ), Fns::identity() ); } } settings/Flags/Command/ConvertFlags.php 0000755 00000003652 14720342453 0014141 0 ustar 00 <?php namespace WPML\TM\Settings\Flags\Command; use WPML\FP\Either; use WPML\FP\Left; use WPML\FP\Lst; use WPML\FP\Right; use WPML\TM\Settings\Flags\Options; use WPML\TM\Settings\Flags\FlagsRepository; class ConvertFlags { /** @var \wpdb */ private $wpdb; /** @var FlagsRepository */ private $flags_repository; /** * @param \wpdb $wpdb * @param FlagsRepository $flags_repository */ public function __construct( \wpdb $wpdb, FlagsRepository $flags_repository ) { $this->wpdb = $wpdb; $this->flags_repository = $flags_repository; } /** * @param string $targetExt * * @return Left<string>|Right<string> */ public function run( $targetExt = 'svg' ) { if ( ! Lst::includes( $targetExt, Options::getAllowedFormats() ) ) { return Either::left( 'Invalid target extension' ); } $flags = $this->flags_repository->getItemsInstalledByDefault(); if ( ! is_array( $flags ) || ( is_array( $flags ) && 0 === count( $flags ) ) ) { // DB was manipulated. Return true to not try upgrading again. return Either::left( 'There is no flags in DB. Your data are corrupted' ); } foreach ( $flags as $flag ) { // Get flag name from current flag column (.png file). $flagName = pathinfo( $flag->flag, PATHINFO_FILENAME ); if ( empty( $flagName ) ) { // DB entry was manipulated. continue; } // Update flag to svg or png version. $flagFilename = $flagName . '.' . $targetExt; if ( file_exists( WPML_PLUGIN_PATH . '/res/flags/' . $flagFilename ) ) { $this->updateFlagFile( $flagFilename, $flag ); } } icl_cache_clear(); return Either::of( $targetExt ); } /** * @param \stdClass $flag * * @return bool */ private function is_custom_flag( $flag ) { return '1' === $flag->from_template; } private function updateFlagFile( $file, $flag ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_flags', [ 'flag' => $file ], [ 'id' => $flag->id ] ); } } settings/class-wpml-custom-field-setting-query-factory.php 0000755 00000005754 14720342453 0020075 0 ustar 00 <?php class WPML_Custom_Field_Setting_Query_Factory { const TYPE_POSTMETA = 'postmeta'; const TYPE_TERMMETA = 'termmeta'; public function create( $type ) { global $wpdb; if ( self::TYPE_TERMMETA === $type ) { $excluded_keys = $this->get_excluded_term_meta_keys(); $table = $wpdb->termmeta; } else { $excluded_keys = $this->get_excluded_post_meta_keys(); $table = $wpdb->postmeta; } return new WPML_Custom_Field_Setting_Query( $wpdb, $excluded_keys, $table ); } /** * @return array */ private function get_excluded_post_meta_keys() { return $this->get_excluded_meta_keys( WPML_Post_Custom_Field_Setting_Keys::get_excluded_keys(), WPML_Post_Custom_Field_Setting_Keys::get_setting_prefix(), WPML_Post_Custom_Field_Setting_Keys::get_state_array_setting_index(), WPML_Post_Custom_Field_Setting_Keys::get_unlocked_setting_index() ); } /** * @return array */ private function get_excluded_term_meta_keys() { return $this->get_excluded_meta_keys( WPML_Term_Custom_Field_Setting_Keys::get_excluded_keys(), WPML_Term_Custom_Field_Setting_Keys::get_setting_prefix(), WPML_Term_Custom_Field_Setting_Keys::get_state_array_setting_index(), WPML_Term_Custom_Field_Setting_Keys::get_unlocked_setting_index() ); } /** * @param array $hardcoded_excluded_keys * @param string $settings_prefix * @param string $settings_state_index * @param string $settings_unlock_index * * @return array */ private function get_excluded_meta_keys( array $hardcoded_excluded_keys, $settings_prefix, $settings_state_index, $settings_unlock_index ) { /** @var TranslationManagement $tm_instance */ $tm_instance = wpml_load_core_tm(); /** * @see WPML_Custom_Field_Setting::excluded() for the logic ran on a single key */ $read_only_keys = isset( $tm_instance->settings[ $settings_prefix . 'read_only' ] ) ? $tm_instance->settings[ $settings_prefix . 'read_only' ] : array(); $not_ignore_keys = $this->get_not_ignore_keys( $tm_instance, $settings_state_index ); $unlocked_keys = isset( $tm_instance->settings[ $settings_unlock_index ] ) ? $tm_instance->settings[ $settings_unlock_index ] : array(); $read_only_and_ignored_keys = array_diff( $read_only_keys, $not_ignore_keys ); $read_only_and_ignored_and_not_unlocked_keys = array_diff( $read_only_and_ignored_keys, $unlocked_keys ); $excluded_keys = array_merge( $hardcoded_excluded_keys, $read_only_and_ignored_and_not_unlocked_keys ); return $excluded_keys; } /** * @param TranslationManagement $tm_settings * @param string $index * * @return array */ private function get_not_ignore_keys( TranslationManagement $tm_settings, $index ) { $statuses = isset( $tm_settings->settings[ $index ] ) ? $tm_settings->settings[ $index ] : array(); foreach ( $statuses as $meta_key => $status ) { if ( WPML_IGNORE_CUSTOM_FIELD === (int) $status ) { unset( $statuses[ $meta_key ] ); } } return array_keys( $statuses ); } } settings/class-wpml-element-sync-settings-factory.php 0000755 00000001633 14720342453 0017115 0 ustar 00 <?php class WPML_Element_Sync_Settings_Factory { const POST = 'post'; const TAX = 'taxonomy'; const KEY_POST_SYNC_OPTION = 'custom_posts_sync_option'; const KEY_TAX_SYNC_OPTION = 'taxonomies_sync_option'; /** * @param string $type * * @return WPML_Element_Sync_Settings * @throws Exception */ public static function create( $type ) { /** @var SitePress $sitepress */ global $sitepress; if ( self::POST === $type ) { $settings = $sitepress->get_setting( self::KEY_POST_SYNC_OPTION, array() ); } elseif ( self::TAX === $type ) { $settings = $sitepress->get_setting( self::KEY_TAX_SYNC_OPTION, array() ); } else { throw new Exception( 'Unknown element type.' ); } return new WPML_Element_Sync_Settings( $settings ); } public static function createPost() { return self::create( self::POST ); } public static function createTax() { return self::create( self::TAX ); } } settings/class-wpml-custom-field-setting-factory.php 0000755 00000003065 14720342453 0016723 0 ustar 00 <?php class WPML_Custom_Field_Setting_Factory extends WPML_TM_User { public $show_system_fields = false; /** * @param string $meta_key * * @return WPML_Post_Custom_Field_Setting */ public function post_meta_setting( $meta_key ) { return new WPML_Post_Custom_Field_Setting( $this->tm_instance, $meta_key ); } /** * @param string $meta_key * * @return WPML_Term_Custom_Field_Setting */ public function term_meta_setting( $meta_key ) { return new WPML_Term_Custom_Field_Setting( $this->tm_instance, $meta_key ); } /** * Returns all custom field names for which a site has either a setting * in the TM settings or that can be found on any post. * * @return string[] */ public function get_post_meta_keys() { return $this->filter_custom_field_keys( $this->tm_instance->initial_custom_field_translate_states() ); } /** * Returns all term custom field names for which a site has either a setting * in the TM settings or that can be found on any term. * * @return string[] */ public function get_term_meta_keys() { return $this->filter_custom_field_keys( $this->tm_instance->initial_term_custom_field_translate_states() ); } private function filter_custom_field_key( $custom_fields_key ) { return $this->show_system_fields || '_' !== substr( $custom_fields_key, 0, 1 ); } /** * @param array $keys * * @return array */ public function filter_custom_field_keys( $keys ) { if ( is_array( $keys ) ) { return array_filter( $keys, array( $this, 'filter_custom_field_key' ) ); } else { return array(); } } } settings/class-wpml-custom-field-xml-settings-import.php 0000755 00000007050 14720342453 0017545 0 ustar 00 <?php use WPML\FP\Obj; class WPML_Custom_Field_XML_Settings_Import { /** @var WPML_Custom_Field_Setting_Factory $setting_factory */ private $setting_factory; /** @var array $settings_array */ private $settings_array; /** * WPML_Custom_Field_XML_Settings_Import constructor. * * @param WPML_Custom_Field_Setting_Factory $setting_factory * @param array $settings_array */ public function __construct( $setting_factory, $settings_array ) { $this->setting_factory = $setting_factory; $this->settings_array = $settings_array; } /** * Runs the actual import of the xml */ public function run() { $config = $this->settings_array; foreach ( array( 'post_meta_setting' => array( WPML_POST_META_CONFIG_INDEX_PLURAL, WPML_POST_META_CONFIG_INDEX_SINGULAR ), 'term_meta_setting' => array( WPML_TERM_META_CONFIG_INDEX_PLURAL, WPML_TERM_META_CONFIG_INDEX_SINGULAR ) ) as $setting_constructor => $settings ) { if ( ! empty( $config[ $settings[0] ] ) ) { $field = $config[ $settings[0] ][ $settings[1] ]; $cf = ! is_numeric( key( current( $config[ $settings[0] ] ) ) ) ? array( $field ) : $field; foreach ( $cf as $c ) { $setting = call_user_func_array( array( $this->setting_factory, $setting_constructor ), array( trim( $c['value'] ) ) ); $this->import_action( $c, $setting ); $setting->make_read_only(); $this->import_editor_settings( $c, $setting ); if ( isset( $c[ 'attr' ][ 'translate_link_target' ] ) || isset( $c[ 'custom-field' ] ) ) { $setting->set_translate_link_target( isset( $c[ 'attr' ][ 'translate_link_target' ] ) ? (bool) $c[ 'attr' ][ 'translate_link_target' ] : false, isset( $c[ 'custom-field' ] ) ? $c[ 'custom-field' ] : array() ); } if ( isset( $c[ 'attr' ][ 'convert_to_sticky' ] ) ) { $setting->set_convert_to_sticky( (bool) $c[ 'attr' ][ 'convert_to_sticky' ] ); } $setting->set_encoding( Obj::path( [ 'attr', 'encoding' ], $c ) ); } } } $this->import_custom_field_texts(); } private function import_action( $c, $setting ) { if ( ! $setting->is_unlocked() ) { switch ( $c['attr']['action'] ) { case 'translate': $setting->set_to_translatable(); break; case 'copy': $setting->set_to_copy(); break; case 'copy-once': $setting->set_to_copy_once(); break; default: $setting->set_to_nothing(); break; } } } private function import_editor_settings( $c, $setting ) { if ( isset( $c[ 'attr' ][ 'style' ] ) ) { $setting->set_editor_style( $c[ 'attr' ][ 'style' ] ); } if ( isset( $c[ 'attr' ][ 'label' ] ) ) { $setting->set_editor_label( $c[ 'attr' ][ 'label' ] ); } if ( isset( $c[ 'attr' ][ 'group' ] ) ) { $setting->set_editor_group( $c[ 'attr' ][ 'group' ] ); } } private function import_custom_field_texts() { $config = $this->settings_array; if ( isset( $config['custom-fields-texts']['key'] ) ) { foreach( $config['custom-fields-texts']['key'] as $field ) { $setting = $this->setting_factory->post_meta_setting( $field['attr']['name'] ); $setting->set_attributes_whitelist( $this->get_custom_field_texts_keys( $field['key'] ) ); } } } private function get_custom_field_texts_keys( $data ) { if ( isset( $data['attr'] ) ) { // single $data = array( $data ); } $sub_fields = array(); foreach( $data as $key ) { $sub_fields[ $key['attr']['name'] ] = isset( $key['key'] ) ? $this->get_custom_field_texts_keys( $key['key'] ) : array(); } return $sub_fields; } } settings/ProcessNewTranslatableFields.php 0000755 00000010051 14720342453 0014675 0 ustar 00 <?php namespace WPML\TM\Settings; use WPML\Collect\Support\Collection; use WPML\Core\BackgroundTask\Command\UpdateBackgroundTask; use WPML\Core\BackgroundTask\Model\BackgroundTask; use WPML\Core\BackgroundTask\Repository\BackgroundTaskRepository; use WPML\BackgroundTask\AbstractTaskEndpoint; use WPML\Core\BackgroundTask\Service\BackgroundTaskService; use WPML\Element\API\PostTranslations; use WPML\FP\Obj; use WPML\Setup\Option; use WPML\TM\AutomaticTranslation\Actions\Actions as AutotranslateActions; class ProcessNewTranslatableFields extends AbstractTaskEndpoint { const LOCK_TIME = 5; const MAX_RETRIES = 10; const DESCRIPTION = 'Updating affected posts for changes in translatable fields %s.'; const POSTS_PER_REQUEST = 10; /** @var \wpdb */ private $wpdb; /** @var \WPML_TM_Post_Actions $postActions */ private $postActions; /** @var AutotranslateActions $autotranslateActions */ private $autotranslateActions; /** * @param \wpdb $wpdb * @param \WPML_TM_Post_Actions $postActions * @param AutotranslateActions $autotranslateActions * @param UpdateBackgroundTask $updateBackgroundTask * @param BackgroundTaskService $backgroundTaskService */ public function __construct( \wpdb $wpdb, \WPML_TM_Post_Actions $postActions, AutotranslateActions $autotranslateActions, UpdateBackgroundTask $updateBackgroundTask, BackgroundTaskService $backgroundTaskService ) { $this->wpdb = $wpdb; $this->postActions = $postActions; $this->autotranslateActions = $autotranslateActions; parent::__construct( $updateBackgroundTask, $backgroundTaskService ); } public function runBackgroundTask( BackgroundTask $task ) { $payload = $task->getPayload(); $fieldsToProcess = Obj::propOr( [], 'newFields', $payload ); $page = Obj::propOr( 1, 'page', $payload ); $postIds = $this->getPosts( $fieldsToProcess, $page ); if ( count( $postIds ) > 0 ) { $this->updateNeedsUpdate( $postIds ); $payload['page'] = $page + 1; $task->setPayload( $payload ); $task->addCompletedCount( count( $postIds ) ); $task->setRetryCount(0 ); if ( $task->getCompletedCount() >= $task->getTotalCount() ) { $task->finish(); } } else { $task->finish(); } return $task; } public function getDescription( Collection $data ) { return sprintf( __( self::DESCRIPTION, 'sitepress' ), implode( ', ', $data->get( 'newFields', [] ) ) ); } public function getTotalRecords( Collection $data ) { return $this->getPostsCount( $data->all()['newFields'] ); } /** * @param array $fields * @param int $page * * @return array */ private function getPosts( array $fields, $page ) { if ( empty( $fields ) ) { return []; } $fieldsIn = wpml_prepare_in( $fields, '%s' ); return $this->wpdb->get_col( $this->wpdb->prepare( "SELECT DISTINCT post_id FROM {$this->wpdb->prefix}postmeta WHERE meta_key IN ({$fieldsIn}) ORDER BY post_id ASC LIMIT %d OFFSET %d", self::POSTS_PER_REQUEST, ($page-1)*self::POSTS_PER_REQUEST ) ); } /** * @param array $fields * * @return int */ private function getPostsCount( array $fields ) { if ( empty( $fields ) ) { return 0; } $fields_in = wpml_prepare_in( $fields, '%s' ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return (int) $this->wpdb->get_var( "SELECT COUNT(DISTINCT(post_id)) FROM {$this->wpdb->prefix}postmeta WHERE meta_key IN ({$fields_in}) AND meta_key <> ''" ); } /** * @param array $postIds */ private function updateNeedsUpdate( array $postIds ) { foreach ( $postIds as $postId ) { $translations = PostTranslations::getIfOriginal( $postId ); $updater = $this->postActions->get_translation_statuses_updater( $postId, $translations ); $needsUpdate = $updater(); if ( $needsUpdate && \WPML_TM_ATE_Status::is_enabled_and_activated() && Option::shouldTranslateEverything() ) { $this->autotranslateActions->sendToTranslation( $postId ); } } } } settings/UI.php 0000755 00000002403 14720342453 0007440 0 ustar 00 <?php namespace WPML\Settings; use WPML\API\PostTypes; use WPML\Core\WP\App\Resources; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\LIB\WP\Hooks; use WPML\Setup\Option; use WPML\TM\ATE\AutoTranslate\Endpoint\GetNumberOfPosts; use WPML\TM\ATE\AutoTranslate\Endpoint\SetForPostType; use WPML\UIPage; class UI implements \IWPML_Backend_Action { public function add_hooks() { Hooks::onAction( 'admin_enqueue_scripts' ) ->then( Fns::always( $_GET ) ) ->then( Logic::anyPass( [ [ UIPage::class, 'isMainSettingsTab' ], [ UIPage::class, 'isTroubleshooting' ] ] ) ) ->then( Either::fromBool() ) ->then( [ self::class, 'getData' ] ) ->then( Resources::enqueueApp( 'settings' ) ); } public static function getData() { return [ 'name' => 'wpmlSettingsUI', 'data' => [ 'endpoints' => [ 'getCount' => GetNumberOfPosts::class, 'setAutomatic' => SetForPostType::class, ], 'shouldTranslateEverything' => Option::shouldTranslateEverything(), 'settingsUrl' => admin_url( UIPage::getSettings() ), 'existingPostTypes' => PostTypes::getOnlyTranslatable(), 'isTMLoaded' => ! wpml_is_setup_complete() || Option::isTMAllowed(), ] ]; } } settings/class-wpml-custom-field-setting-query.php 0000755 00000004307 14720342453 0016421 0 ustar 00 <?php class WPML_Custom_Field_Setting_Query { /** @var wpdb $wpdb */ private $wpdb; /** @var array $excluded_keys */ private $excluded_keys; /** @var string $table */ private $table; /** * @param wpdb $wpdb * @param array $excluded_keys * @param string $table */ public function __construct( wpdb $wpdb, array $excluded_keys, $table ) { $this->wpdb = $wpdb; $this->excluded_keys = $excluded_keys; $this->table = $table; } /** * @param array $args * * @return array */ public function get( array $args ) { $args = array_merge( array( 'search' => null, 'hide_system_fields' => false, 'items_per_page' => null, 'page' => null, ), $args ); $where = ' WHERE 1=1'; $where .= $this->add_AND_excluded_fields_condition(); $where .= $this->add_AND_search_condition( $args['search'] ); $where .= $this->add_AND_system_fields_condition( $args['hide_system_fields'] ); $limit_offset = $this->get_limit_offset( $args ); $query = "SELECT DISTINCT meta_key FROM {$this->table}" . $where . ' ORDER BY meta_id ASC ' . $limit_offset; return $this->wpdb->get_col( $query ); } /** * @return string */ private function add_AND_excluded_fields_condition() { if ( $this->excluded_keys ) { return ' AND meta_key NOT IN(' . wpml_prepare_in( $this->excluded_keys ) . ')'; } return ''; } /** * @param string $search * * @return string */ private function add_AND_search_condition( $search ) { return $search ? $this->wpdb->prepare( " AND meta_key LIKE '%s'", '%' . $search . '%' ) : ''; } /** * @param bool $hide_system_fields * * @return string */ private function add_AND_system_fields_condition( $hide_system_fields ) { return $hide_system_fields ? " AND meta_key NOT LIKE '\_%'" : ''; } /** * @param array $args * * @return string */ private function get_limit_offset( array $args ) { $limit_offset = ''; if ( $args['items_per_page'] && 0 < (int) $args['page'] ) { $limit_offset = $this->wpdb->prepare( ' LIMIT %d OFFSET %d', $args['items_per_page'], ( $args['page'] - 1 ) * ( $args['items_per_page'] - 1 ) ); } return $limit_offset; } } helpers/class-wpml-tm-post-data.php 0000755 00000000362 14720342453 0013321 0 ustar 00 <?php /** * Class WPML_TM_Post_Data */ class WPML_TM_Post_Data { /** * @param string $data * * @return string */ public static function strip_slashes_for_single_quote( $data ) { return str_replace( '\\\'', '\'', $data ); } } media-translation/MediaTranslationStatus.php 0000755 00000012052 14720342453 0015341 0 ustar 00 <?php namespace WPML\MediaTranslation; use SitePress; use WPML_Element_Translation_Package; use WPML_TM_Translation_Batch; use WPML_Post_Element; class MediaTranslationStatus implements \IWPML_Action { const NOT_TRANSLATED = 'media-not-translated'; const IN_PROGRESS = 'in-progress'; const TRANSLATED = 'media-translated'; const NEEDS_MEDIA_TRANSLATION = 'needs-media-translation'; const STATUS_PREFIX = '_translation_status_'; /** * @var SitePress */ private $sitepress; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } public function add_hooks() { add_action( 'wpml_tm_send_post_jobs', array( $this, 'set_translation_status_in_progress' ) ); add_action( 'wpml_pro_translation_completed', array( $this, 'save_bundled_media_translation' ), 10, 3 ); } public function set_translation_status_in_progress( WPML_TM_Translation_Batch $batch ) { foreach ( $batch->get_elements() as $item ) { foreach ( $item->get_media_to_translations() as $attachment_id ) { foreach ( array_keys( $item->get_target_langs() ) as $lang ) { $this->set_status( $attachment_id, $lang, self::IN_PROGRESS ); } } } } private function set_status( $attachment_id, $language, $status ) { update_post_meta( $attachment_id, self::STATUS_PREFIX . $language, $status ); } public function save_bundled_media_translation( $new_post_id, $fields, $job ) { $media_translations = $this->get_media_translations( $job ); $translation_package = new WPML_Element_Translation_Package(); foreach ( $media_translations as $attachment_id => $translation_data ) { $attachment_translation_id = $this->save_attachment_translation( $attachment_id, $translation_data, $translation_package, $job->language_code ); if ( $this->should_translate_media_image( $job, $attachment_id ) ) { $this->set_status( $attachment_id, $job->language_code, self::NEEDS_MEDIA_TRANSLATION ); } } } private function should_translate_media_image( $job, $attachment_id ) { foreach ( $job->elements as $element ) { if ( 'should_translate_media_image_' . $attachment_id === $element->field_type && $element->field_data ) { return true; } } return false; } private function get_media_translations( $job ) { $media = array(); $media_field_regexp = '#^media_([0-9]+)_([a-z_]+)$#'; foreach ( $job->elements as $element ) { if ( preg_match( $media_field_regexp, $element->field_type, $matches ) ) { list( , $attachment_id, $media_field ) = $matches; $media[ $attachment_id ][ $media_field ] = $element; } } return $media; } /** * @param int $attachment_id * @param array $translation_data * @param WPML_Element_Translation_Package $translation_package * @param string $language * * @return bool|int|WP_Error */ private function save_attachment_translation( $attachment_id, $translation_data, $translation_package, $language ) { $postarr = array(); $alt_text = null; foreach ( $translation_data as $field => $data ) { $translated_value = $translation_package->decode_field_data( $data->field_data_translated, $data->field_format ); if ( 'alt_text' === $field ) { $alt_text = $translated_value; } else { switch ( $field ) { case 'title': $wp_post_field = 'post_title'; break; case 'caption': $wp_post_field = 'post_excerpt'; break; case 'description': $wp_post_field = 'post_content'; break; default: $wp_post_field = ''; } if ( $wp_post_field ) { $postarr[ $wp_post_field ] = $translated_value; } } } $post_element = new WPML_Post_Element( $attachment_id, $this->sitepress ); $attachment_translation = $post_element->get_translation( $language ); $attachment_translation_id = null !== $attachment_translation ? $attachment_translation->get_id() : false; if ( $attachment_translation_id ) { $postarr['ID'] = $attachment_translation_id; wp_update_post( $postarr ); } else { $postarr['post_type'] = 'attachment'; $postarr['post_status'] = 'inherit'; $postarr['guid'] = get_post_field( 'guid', $attachment_id ); $postarr['post_mime_type'] = get_post_field( 'post_mime_type', $attachment_id ); $attachment_translation_id = wp_insert_post( $postarr ); $this->sitepress->set_element_language_details( $attachment_translation_id, 'post_attachment', $post_element->get_trid(), $language ); $this->copy_attached_file_info_from_original( $attachment_translation_id, $attachment_id ); } if ( null !== $alt_text ) { update_post_meta( $attachment_translation_id, '_wp_attachment_image_alt', $alt_text ); } return $attachment_translation_id; } private function copy_attached_file_info_from_original( $attachment_id, $original_attachment_id ) { $meta_keys = array( '_wp_attachment_metadata', '_wp_attached_file', '_wp_attachment_backup_sizes', ); foreach ( $meta_keys as $meta_key ) { update_post_meta( $attachment_id, $meta_key, get_post_meta( $original_attachment_id, $meta_key, true ) ); } } } media-translation/AddMediaDataToTranslationPackage.php 0000755 00000011530 14720342453 0017117 0 ustar 00 <?php namespace WPML\MediaTranslation; use WPML\Media\Option; class AddMediaDataToTranslationPackage implements \IWPML_Backend_Action { const ALT_PLACEHOLDER = '{%ALT_TEXT%}'; const CAPTION_PLACEHOLDER = '{%CAPTION%}'; /** @var PostWithMediaFilesFactory $post_media_factory */ private $post_media_factory; public function __construct( PostWithMediaFilesFactory $post_media_factory ) { $this->post_media_factory = $post_media_factory; } public function add_hooks() { if ( Option::getTranslateMediaLibraryTexts() ) { add_action( 'wpml_tm_translation_job_data', [ $this, 'add_media_strings' ], PHP_INT_MAX, 2 ); } } public function add_media_strings( $package, $post ) { $basket = \TranslationProxy_Basket::get_basket( true ); $bundled_media_data = $this->get_bundled_media_to_translate( $post ); if ( $bundled_media_data ) { foreach ( $bundled_media_data as $attachment_id => $data ) { foreach ( $data as $field => $value ) { if ( isset( $basket['post'][ $post->ID ]['media-translation'] ) && ! in_array( $attachment_id, $basket['post'][ $post->ID ]['media-translation'] ) ) { $options = [ 'translate' => 0, 'data' => true, 'format' => '', ]; } else { $options = [ 'translate' => 1, 'data' => base64_encode( $value ), 'format' => 'base64', ]; } $package['contents'][ 'media_' . $attachment_id . '_' . $field ] = $options; } if ( isset( $basket['post'][ $post->ID ]['media-translation'] ) && in_array( $attachment_id, $basket['post'][ $post->ID ]['media-translation'] ) ) { $package['contents'][ 'should_translate_media_image_' . $attachment_id ] = [ 'translate' => 0, 'data' => true, 'format' => '', ]; } } $package = $this->add_placeholders_for_duplicate_fields( $package, $bundled_media_data ); } return $package; } private function get_bundled_media_to_translate( $post ) { $post_media = $this->post_media_factory->create( $post->ID ); $bundled_media_data = []; foreach ( $post_media->get_media_ids() as $attachment_id ) { $attachment = get_post( $attachment_id ); if ( $attachment->post_title ) { $bundled_media_data[ $attachment_id ]['title'] = $attachment->post_title; } if ( $attachment->post_excerpt ) { $bundled_media_data[ $attachment_id ]['caption'] = $attachment->post_excerpt; } if ( $attachment->post_content ) { $bundled_media_data[ $attachment_id ]['description'] = $attachment->post_content; } if ( $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) { $bundled_media_data[ $attachment_id ]['alt_text'] = $alt; } } return $bundled_media_data; } private function add_placeholders_for_duplicate_fields( $package, $bundled_media_data ) { $caption_parser = new MediaCaptionTagsParse(); foreach ( $package['contents'] as $field => $data ) { if ( $data['translate'] && 'base64' === $data['format'] ) { $original = $content = base64_decode( $data['data'] ); $captions = $caption_parser->get_captions( $content ); foreach ( $captions as $caption ) { $caption_id = $caption->get_id(); $caption_shortcode = $new_caption_shortcode = $caption->get_shortcode_string(); if ( isset( $bundled_media_data[ $caption_id ] ) ) { if ( isset( $bundled_media_data[ $caption_id ]['caption'] ) && $bundled_media_data[ $caption_id ]['caption'] === $caption->get_caption() ) { $new_caption_shortcode = $this->replace_caption_with_placeholder( $new_caption_shortcode, $caption ); } if ( isset( $bundled_media_data[ $caption_id ]['alt_text'] ) && $bundled_media_data[ $caption_id ]['alt_text'] === $caption->get_image_alt() ) { $new_caption_shortcode = $this->replace_alt_text_with_placeholder( $new_caption_shortcode, $caption ); } if ( $new_caption_shortcode !== $caption_shortcode ) { $content = str_replace( $caption_shortcode, $new_caption_shortcode, $content ); } } } if ( $content !== $original ) { $package['contents'][ $field ]['data'] = base64_encode( $content ); } } } return $package; } private function replace_caption_with_placeholder( $caption_shortcode, MediaCaption $caption ) { $caption_content = $caption->get_content(); $search_pattern = '/(>\s?)(' . preg_quote( $caption->get_caption(), '/' ) . ')/'; $new_caption_content = preg_replace( $search_pattern, '$1' . self::CAPTION_PLACEHOLDER, $caption_content, 1 ); return str_replace( $caption_content, $new_caption_content, $caption_shortcode ); } private function replace_alt_text_with_placeholder( $caption_shortcode, MediaCaption $caption ) { $alt_text = $caption->get_image_alt(); return str_replace( 'alt="' . $alt_text . '"', 'alt="' . self::ALT_PLACEHOLDER . '"', $caption_shortcode ); } } media-translation/MediaCaptionTagsParse.php 0000755 00000000711 14720342453 0015045 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaCaptionTagsParse { /** * @param string $text * * @return array */ public function get_captions( $text ) { $captions = []; if ( preg_match_all( '/\[caption (.+)\](.+)\[\/caption\]/sU', $text, $matches ) ) { for ( $i = 0; $i < count( $matches[0] ); $i ++ ) { $captions[] = new MediaCaption( $matches[0][ $i ], $matches[1][ $i ], $matches[2][ $i ] ); } } return $captions; } } media-translation/MediaTranslationEditorLayoutFactory.php 0000755 00000000312 14720342453 0020026 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaTranslationEditorLayoutFactory implements \IWPML_Backend_Action_Loader { public function create() { return new MediaTranslationEditorLayout(); } } media-translation/MediaTranslationEditorLayout.php 0000755 00000005043 14720342453 0016504 0 ustar 00 <?php namespace WPML\MediaTranslation; use WPML\Media\Option; class MediaTranslationEditorLayout implements \IWPML_Action { public function add_hooks() { if ( Option::getTranslateMediaLibraryTexts() ) { add_filter( 'wpml_tm_job_layout', [ $this, 'group_media_fields' ] ); add_filter( 'wpml_tm_adjust_translation_fields', [ $this, 'set_custom_labels' ] ); } } public function group_media_fields( $fields ) { $media_fields = []; foreach ( $fields as $k => $field ) { $media_field = $this->is_media_field( $field ); if ( $media_field ) { unset( $fields[ $k ] ); $media_fields[ $media_field['attachment_id'] ][] = $field; } } if ( $media_fields ) { $media_section_field = [ 'field_type' => 'tm-section', 'title' => __( 'Media', 'wpml-media' ), 'fields' => [], 'empty' => false, 'empty_message' => '', 'sub_title' => '', ]; foreach ( $media_fields as $attachment_id => $media_field ) { $media_group_field = [ 'title' => '', 'divider' => false, 'field_type' => 'tm-group', 'fields' => $media_field, ]; $image = wp_get_attachment_image_src( (int) $attachment_id, [ 100, 100 ] ); $image_field = [ 'field_type' => 'wcml-image', 'divider' => $media_field !== end( $media_fields ), 'image_src' => $image[0], 'fields' => [ $media_group_field ], ]; $media_section_field['fields'][] = $image_field; } $fields[] = $media_section_field; } return array_values( $fields ); } private function is_media_field( $field ) { $media_field = []; if ( is_string( $field ) && preg_match( '/^media_([0-9]+)_([a-z_]+)/', $field, $match ) ) { $media_field = [ 'attachment_id' => (int) $match[1], 'label' => $match[2], ]; } return $media_field; } public function set_custom_labels( $fields ) { foreach ( $fields as $k => $field ) { $media_field = $this->is_media_field( $field['field_type'] ); if ( $media_field ) { $fields[ $k ]['title'] = $this->get_field_label( $media_field ); } } return $fields; } private function get_field_label( $media_field ) { switch ( $media_field['label'] ) { case 'title': $label = __( 'Title', 'wpml-media' ); break; case 'caption': $label = __( 'Caption', 'wpml-media' ); break; case 'description': $label = __( 'Description', 'wpml-media' ); break; case 'alt_text': $label = __( 'Alt Text', 'wpml-media' ); break; default: $label = ''; } return $label; } } media-translation/MediaAttachmentByUrl.php 0000755 00000007123 14720342453 0014710 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaAttachmentByUrl { /** * @var wpdb */ private $wpdb; /** * @var string */ private $url; /** * @var string */ private $language; const SIZE_SUFFIX_REGEXP = '/-([0-9]+)x([0-9]+)\.([a-z]{3,4})$/'; const CACHE_KEY_PREFIX = 'attachment-id-from-guid-'; const CACHE_GROUP = 'wpml-media-setup'; const CACHE_EXPIRATION = 1800; public $cache_hit_flag = null; /** * WPML_Media_Attachment_By_URL constructor. * * @param \wpdb $wpdb * @param string $url * @param string $language */ public function __construct( \wpdb $wpdb, $url, $language ) { $this->url = $url; $this->language = $language; $this->wpdb = $wpdb; } public function get_id() { if ( ! $this->url ) { return 0; } $cache_key = self::CACHE_KEY_PREFIX . md5( $this->language . '#' . $this->url ); $attachment_id = wp_cache_get( $cache_key, self::CACHE_GROUP, false, $this->cache_hit_flag ); if ( ! $this->cache_hit_flag ) { $attachment_id = $this->get_id_from_guid(); if ( ! $attachment_id ) { $attachment_id = $this->get_id_from_meta(); } wp_cache_add( $cache_key, $attachment_id, self::CACHE_GROUP, self::CACHE_EXPIRATION ); } return $attachment_id; } private function get_id_from_guid() { $attachment_id = $this->wpdb->get_var( $this->wpdb->prepare( " SELECT ID FROM {$this->wpdb->posts} p JOIN {$this->wpdb->prefix}icl_translations t ON t.element_id = p.ID WHERE t.element_type='post_attachment' AND t.language_code=%s AND p.guid=%s ", $this->language, $this->url ) ); return $attachment_id; } private function get_id_from_meta() { $uploads_dir = wp_get_upload_dir(); $relative_path = ltrim( preg_replace( '@^' . $uploads_dir['baseurl'] . '@', '', $this->url ), '/' ); // using _wp_attached_file $attachment_id = $this->wpdb->get_var( $this->wpdb->prepare( " SELECT post_id FROM {$this->wpdb->postmeta} p JOIN {$this->wpdb->prefix}icl_translations t ON t.element_id = p.post_id WHERE p.meta_key='_wp_attached_file' AND p.meta_value=%s AND t.element_type='post_attachment' AND t.language_code=%s ", $relative_path, $this->language ) ); // using attachment meta (fallback) if ( ! $attachment_id && preg_match( self::SIZE_SUFFIX_REGEXP, $relative_path ) ) { $attachment_id = $this->get_attachment_image_from_meta_fallback( $relative_path ); } return $attachment_id; } private function get_attachment_image_from_meta_fallback( $relative_path ) { $attachment_id = null; $relative_path_original = preg_replace( self::SIZE_SUFFIX_REGEXP, '.$3', $relative_path ); $attachment_id_original = $this->wpdb->get_var( $this->wpdb->prepare( " SELECT p.post_id FROM {$this->wpdb->postmeta} p JOIN {$this->wpdb->prefix}icl_translations t ON t.element_id = p.post_id WHERE p.meta_key='_wp_attached_file' AND p.meta_value=%s AND t.element_type='post_attachment' AND t.language_code=%s ", $relative_path_original, $this->language ) ); // validate size if ( $attachment_id_original ) { $attachment_meta_data = wp_get_attachment_metadata( $attachment_id_original ); if ( $this->validate_image_size( $relative_path, $attachment_meta_data ) ) { $attachment_id = $attachment_id_original; } } return $attachment_id; } private function validate_image_size( $path, $attachment_meta_data ) { $valid = false; $file_name = basename( $path ); foreach ( $attachment_meta_data['sizes'] as $size ) { if ( $file_name === $size['file'] ) { $valid = true; break; } } return $valid; } } media-translation/AddMediaDataToTranslationPackageFactory.php 0000755 00000000363 14720342453 0020451 0 ustar 00 <?php namespace WPML\MediaTranslation; class AddMediaDataToTranslationPackageFactory implements \IWPML_Backend_Action_Loader { public function create() { return new AddMediaDataToTranslationPackage( new PostWithMediaFilesFactory() ); } } media-translation/PostWithMediaFilesFactory.php 0000755 00000000555 14720342453 0015740 0 ustar 00 <?php namespace WPML\MediaTranslation; class PostWithMediaFilesFactory { public function create( $post_id ) { global $sitepress, $iclTranslationManagement; return new PostWithMediaFiles( $post_id, new MediaImgParse(), new MediaAttachmentByUrlFactory(), $sitepress, new \WPML_Custom_Field_Setting_Factory( $iclTranslationManagement ) ); } } media-translation/MediaTranslationStatusFactory.php 0000755 00000000375 14720342453 0016676 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaTranslationStatusFactory implements \IWPML_Backend_Action_Loader, \IWPML_REST_Action_Loader { public function create() { global $sitepress; return new MediaTranslationStatus( $sitepress ); } } media-translation/PostWithMediaFiles.php 0000755 00000013041 14720342453 0014402 0 ustar 00 <?php namespace WPML\MediaTranslation; use WPML\FP\Fns; use WPML\LIB\WP\Post; use WPML_Post_Element; class PostWithMediaFiles { /** * @var int */ private $post_id; /** * @var MediaImgParse */ private $media_parser; /** * @var MediaAttachmentByUrlFactory */ private $attachment_by_url_factory; /** * @var \SitePress $sitepress */ private $sitepress; /** * @var \WPML_Custom_Field_Setting_Factory */ private $cf_settings_factory; /** * WPML_Media_Post_With_Media_Files constructor. * * @param $post_id * @param MediaImgParse $media_parser * @param MediaAttachmentByUrlFactory $attachment_by_url_factory * @param \SitePress $sitepress * @param \WPML_Custom_Field_Setting_Factory $cf_settings_factory */ public function __construct( $post_id, MediaImgParse $media_parser, MediaAttachmentByUrlFactory $attachment_by_url_factory, \SitePress $sitepress, \WPML_Custom_Field_Setting_Factory $cf_settings_factory ) { $this->post_id = $post_id; $this->media_parser = $media_parser; $this->attachment_by_url_factory = $attachment_by_url_factory; $this->sitepress = $sitepress; $this->cf_settings_factory = $cf_settings_factory; } public function get_media_ids() { $media_ids = []; if ( $post = get_post( $this->post_id ) ) { $content_to_parse = apply_filters( 'wpml_media_content_for_media_usage', $post->post_content, $post ); $post_content_media = $this->media_parser->get_imgs( $content_to_parse ); $media_ids = $this->_get_ids_from_media_array( $post_content_media ); if ( $featured_image = get_post_meta( $this->post_id, '_thumbnail_id', true ) ) { $media_ids[] = $featured_image; } $media_localization_settings = MediaSettings::get_setting( 'media_files_localization' ); if ( $media_localization_settings['custom_fields'] ) { $custom_fields_content = $this->get_content_in_translatable_custom_fields(); $custom_fields_media = $this->media_parser->get_imgs( $custom_fields_content ); $media_ids = array_merge( $media_ids, $this->_get_ids_from_media_array( $custom_fields_media ) ); } if ( $gallery_media_ids = $this->get_gallery_media_ids( $content_to_parse ) ) { $media_ids = array_unique( array_values( array_merge( $media_ids, $gallery_media_ids ) ) ); } if ( $attached_media_ids = $this->get_attached_media_ids( $this->post_id ) ) { $media_ids = array_unique( array_values( array_merge( $media_ids, $attached_media_ids ) ) ); } } return Fns::filter( Post::get(), apply_filters( 'wpml_ids_of_media_used_in_post', $media_ids, $this->post_id ) ); } /** * @param array $media_array * * @return array */ private function _get_ids_from_media_array( $media_array ) { $media_ids = []; foreach ( $media_array as $media ) { if ( isset( $media['attachment_id'] ) ) { $media_ids[] = $media['attachment_id']; } else { $attachment_by_url = $this->attachment_by_url_factory->create( $media['attributes']['src'], wpml_get_current_language() ); if ( $attachment_id = $attachment_by_url->get_id() ) { $media_ids[] = $attachment_id; } } } return $media_ids; } /** * @param string $post_content * * @return array */ private function get_gallery_media_ids( $post_content ) { $galleries_media_ids = []; $gallery_shortcode_regex = '/\[gallery [^[]*ids=["\']([0-9,\s]+)["\'][^[]*\]/m'; if ( preg_match_all( $gallery_shortcode_regex, $post_content, $matches ) ) { foreach ( $matches[1] as $gallery_ids_string ) { $media_ids_array = explode( ',', $gallery_ids_string ); $media_ids_array = Fns::map( Fns::unary( 'intval' ), $media_ids_array ); foreach ( $media_ids_array as $media_id ) { if ( 'attachment' === get_post_type( $media_id ) ) { $galleries_media_ids[] = $media_id; } } } } return $galleries_media_ids; } /** * @param $languages * * @return array */ public function get_untranslated_media( $languages ) { $untranslated_media = []; $post_media = $this->get_media_ids(); foreach ( $post_media as $attachment_id ) { $post_element = new WPML_Post_Element( $attachment_id, $this->sitepress ); foreach ( $languages as $language ) { $translation = $post_element->get_translation( $language ); if ( null === $translation || ! $this->media_file_is_translated( $attachment_id, $translation->get_id() ) ) { $untranslated_media[] = $attachment_id; break; } } } return $untranslated_media; } private function media_file_is_translated( $attachment_id, $translated_attachment_id ) { return get_post_meta( $attachment_id, '_wp_attached_file', true ) !== get_post_meta( $translated_attachment_id, '_wp_attached_file', true ); } private function get_content_in_translatable_custom_fields() { $content = ''; $post_meta = get_metadata( 'post', $this->post_id ); if ( is_array( $post_meta ) ) { foreach ( $post_meta as $meta_key => $meta_value ) { $setting = $this->cf_settings_factory->post_meta_setting( $meta_key ); $is_translatable = $this->sitepress->get_wp_api() ->constant( 'WPML_TRANSLATE_CUSTOM_FIELD' ) === $setting->status(); if ( is_string( $meta_value[0] ) && $is_translatable ) { $content .= $meta_value[0]; } } } return $content; } private function get_attached_media_ids( $post_id ) { $attachments = get_children( [ 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', ] ); return array_keys( $attachments ); } } media-translation/MediaAttachmentByUrlFactory.php 0000755 00000000314 14720342453 0016233 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaAttachmentByUrlFactory { public function create( $url, $language ) { global $wpdb; return new MediaAttachmentByUrl( $wpdb, $url, $language ); } } media-translation/MediaImgParse.php 0000755 00000006675 14720342453 0013364 0 ustar 00 <?php namespace WPML\MediaTranslation; use WPML\LIB\WP\Attachment; class MediaImgParse { /** * @param string $text * * @return array */ public function get_imgs( $text ) { $images = $this->get_from_img_tags( $text ); if ( $this->can_parse_blocks( $text ) ) { $blocks = parse_blocks( $text ); $images = array_merge( $images, $this->get_from_css_background_images_in_blocks( $blocks ) ); } else { $images = array_merge( $images, $this->get_from_css_background_images( $text ) ); } return $images; } /** * @param string $text * * @return array */ private function get_from_img_tags( $text ) { $media = wpml_collect( [] ); $media_elements = [ '/<img ([^>]+)>/s', '/<video ([^>]+)>/s', '/<audio ([^>]+)>/s', ]; foreach ( $media_elements as $element_expression ) { if ( preg_match_all( $element_expression, $text, $matches ) ) { $media = $media->merge( $this->getAttachments( $matches ) ); } } return $media->toArray(); } private function getAttachments( $matches ) { $attachments = []; foreach ( $matches[1] as $i => $match ) { if ( preg_match_all( '/(\S+)\\s*=\\s*["\']?((?:.(?!["\']?\s+(?:\S+)=|[>"\']))+.)["\']?/', $match, $attribute_matches ) ) { $attributes = []; foreach ( $attribute_matches[1] as $k => $key ) { $attributes[ $key ] = $attribute_matches[2][ $k ]; } if ( isset( $attributes['src'] ) ) { $attachments[ $i ]['attributes'] = $attributes; $attachments[ $i ]['attachment_id'] = Attachment::idFromUrl( $attributes['src'] ); } } } return $attachments; } /** * @param string $text * * @return array */ private function get_from_css_background_images( $text ) { $images = []; if ( preg_match_all( '/<\w+[^>]+style\s?=\s?"[^"]*?background-image:url\(\s?([^\s\)]+)\s?\)/', $text, $matches ) ) { foreach ( $matches[1] as $src ) { $images[] = [ 'attributes' => [ 'src' => $src ], 'attachment_id' => null, ]; } } return $images; } /** * @param array $blocks * * @return array */ private function get_from_css_background_images_in_blocks( $blocks ) { $images = []; foreach ( $blocks as $block ) { $block = $this->sanitize_block( $block ); if ( ! empty( $block->innerBlocks ) ) { $inner_images = $this->get_from_css_background_images_in_blocks( $block->innerBlocks ); $images = array_merge( $images, $inner_images ); continue; } if ( ! isset( $block->innerHTML, $block->attrs->id ) ) { continue; } $background_images = $this->get_from_css_background_images( $block->innerHTML ); $image = reset( $background_images ); if ( $image ) { $image['attachment_id'] = $block->attrs->id; $images[] = $image; } } return $images; } /** * `parse_blocks` does not specify which kind of collection it should return * (not always an array of `WP_Block_Parser_Block`) and the block parser can be filtered, * so we'll cast it to a standard object for now. * * @param mixed $block * * @return stdClass|WP_Block_Parser_Block */ private function sanitize_block( $block ) { $block = (object) $block; if ( isset( $block->attrs ) ) { /** Sometimes `$block->attrs` is an object or an array, so we'll use an object */ $block->attrs = (object) $block->attrs; } return $block; } function can_parse_blocks( $string ) { return false !== strpos( $string, '<!-- wp:' ) && function_exists( 'parse_blocks' ); } } media-translation/MediaSettings.php 0000755 00000001562 14720342453 0013443 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaSettings { private static $settings; private static $settings_option_key = '_wpml_media'; private static $default_settings = [ 'version' => false, 'media_files_localization' => [ 'posts' => true, 'custom_fields' => true, 'strings' => true, ], 'wpml_media_2_3_migration' => true, 'setup_run' => false, ]; public static function init_settings() { if ( ! self::$settings ) { self::$settings = get_option( self::$settings_option_key, [] ); } self::$settings = array_merge( self::$default_settings, self::$settings ); } public static function get_setting( $name, $default = false ) { self::init_settings(); if ( ! isset( self::$settings[ $name ] ) || ! self::$settings[ $name ] ) { return $default; } return self::$settings[ $name ]; } } media-translation/MediaCaption.php 0000755 00000004607 14720342453 0013243 0 ustar 00 <?php namespace WPML\MediaTranslation; class MediaCaption { private $shortcode; private $content_string; private $attributes; private $attachment_id; private $link; private $img; private $caption; public function __construct( $caption_shortcode, $attributes_data, $content_string ) { $this->shortcode = $caption_shortcode; $this->content_string = $content_string; $this->attributes = $this->find_attributes_array( $attributes_data ); $this->attachment_id = $this->find_attachment_id( $this->attributes ); $this->link = $this->find_link( $content_string ); $img_parser = new MediaImgParse(); $this->img = current( $img_parser->get_imgs( $content_string ) ); $this->caption = trim( strip_tags( $content_string ) ); } /** * @return int */ public function get_id() { return $this->attachment_id; } public function get_caption() { return $this->caption; } public function get_shortcode_string() { return $this->shortcode; } public function get_content() { return $this->content_string; } public function get_image_alt() { if ( isset( $this->img['attributes']['alt'] ) ) { return $this->img['attributes']['alt']; } else { return ''; } } public function get_link() { return $this->link; } /** * @param string $attributes_list * * @return array */ private function find_attributes_array( $attributes_list ) { $attributes = array(); if ( preg_match_all( '/(\S+)=["\']?((?:.(?!["\']?\s+(?:\S+)=|[>"\']))+.)["\']?/', $attributes_list, $attribute_matches ) ) { foreach ( $attribute_matches[1] as $k => $key ) { $attributes[ $key ] = $attribute_matches[2][ $k ]; } } return $attributes; } /** * @param array $attributes * * @return null|int */ private function find_attachment_id( $attributes ) { $attachment_id = null; if ( isset( $attributes['id'] ) ) { if ( preg_match( '/attachment_([0-9]+)\b/', $attributes['id'], $id_match ) ) { if ( 'attachment' === get_post_type( (int) $id_match[1] ) ) { $attachment_id = (int) $id_match[1]; } } } return $attachment_id; } /** * @param $string * * @return array */ private function find_link( $string ) { $link = array(); if ( preg_match( '/<a ([^>]+)>(.+)<\/a>/s', $string, $a_match ) ) { if ( preg_match( '/href=["\']([^"]+)["\']/', $a_match[1], $url_match ) ) { $link['url'] = $url_match[1]; } } return $link; } } full-site-editing/BlockTemplates.php 0000755 00000004250 14720342453 0013523 0 ustar 00 <?php namespace WPML\FullSiteEditing; use WPML\Element\API\PostTranslations; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Post; use function WPML\FP\spreadArgs; class BlockTemplates implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_REST_Action { public function add_hooks() { Hooks::onFilter( 'wpml_tm_translation_job_data', 10, 2 ) ->then( spreadArgs( [ self::class, 'doNotTranslateTitle' ] ) ); Hooks::onFilter( 'wpml_pre_save_pro_translation', 10, 2 ) ->then( spreadArgs( [ self::class, 'copyOriginalTitleToTranslation' ] ) ); Hooks::onAction( "rest_after_insert_wp_template", 10, 1 ) ->then( spreadArgs( [ self::class, 'syncPostName' ] ) ); Hooks::onAction( "rest_after_insert_wp_template_part", 10, 1 ) ->then( spreadArgs( [ self::class, 'syncPostName' ] ) ); } /** * @param array $package * @param object $post * * @return array */ public static function doNotTranslateTitle( array $package, $post ) { if ( Lst::includes( Obj::prop( 'post_type', $post ), [ 'wp_template', 'wp_template_part' ] ) ) { $package = Obj::assocPath( [ 'contents', 'title', 'translate' ], 0, $package ); } return $package; } /** * @param array $postData * @param object $job * * @return array */ public static function copyOriginalTitleToTranslation( $postData, $job ) { if ( Lst::includes( Obj::prop( 'original_post_type', $job ), [ 'post_wp_template', 'post_wp_template_part' ] ) ) { // WordPress looks up the post by 'name' so we need the translation to have the same name. $post = Post::get( $job->original_doc_id ); $postData['post_title'] = $post->post_title; $postData['post_name'] = $post->post_name; } return $postData; } /** * @param \WP_Post $post Inserted or updated post object. */ public static function syncPostName( \WP_Post $post ) { wpml_collect( PostTranslations::get( $post->ID ) ) ->reject( Obj::prop( 'original' ) ) ->pluck( 'element_id' ) ->map( function ( $postId ) use ( $post ) { global $wpdb; $wpdb->update( $wpdb->prefix . 'posts', [ 'post_name' => $post->post_name ], [ 'ID' => $postId ] ); } ); } } support/class-wpml-st-support-info.php 0000755 00000000250 14720402445 0014144 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ST_Support_Info { public function is_mbstring_extension_loaded() { return extension_loaded( 'mbstring' ); } } support/class-wpml-st-support-info-filter.php 0000755 00000002407 14720402445 0015435 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ST_Support_Info_Filter implements IWPML_Backend_Action, IWPML_DIC_Action { /** @var WPML_ST_Support_Info */ private $support_info; function __construct( WPML_ST_Support_Info $support_info ) { $this->support_info = $support_info; } public function add_hooks() { /** This filter is documented WPML Core in classes/support/class-wpml-support-info-ui.php */ add_filter( 'wpml_support_info_blocks', [ $this, 'filter_blocks' ] ); } /** * @param array $blocks * * @return array */ public function filter_blocks(array $blocks) { $is_mbstring_extension_loaded = $this->support_info->is_mbstring_extension_loaded(); $blocks['php']['data']['mbstring'] = array( 'label' => __( 'Multibyte String extension', 'wpml-string-translation' ), 'value' => $is_mbstring_extension_loaded ? __( 'Loaded', 'wpml-string-translation' ) : __( 'Not loaded', 'wpml-string-translation' ), 'url' => 'http://php.net/manual/book.mbstring.php', 'messages' => array( __( 'Multibyte String extension is required for WPML String Translation.', 'wpml-string-translation' ) => 'https://wpml.org/home/minimum-requirements/', ), 'is_error' => ! $is_mbstring_extension_loaded, ); return $blocks; } } admin-texts/class-wpml-st-admin-option-translation.php 0000755 00000004653 14720402445 0017165 0 ustar 00 <?php class WPML_ST_Admin_Option_Translation extends WPML_SP_User { /** @var WPML_String_Translation $st_instance */ private $st_instance; /** @var string $option_name */ private $option_name; /** @var string $option_name */ private $language; /** * WPML_ST_Admin_Option constructor. * * @param SitePress $sitepress * @param WPML_String_Translation $st_instance * @param string $option_name * @param string $language */ public function __construct( &$sitepress, &$st_instance, $option_name, $language = '' ) { if ( ! $option_name || ! is_scalar( $option_name ) ) { throw new InvalidArgumentException( 'Not a valid option name, received: ' . serialize( $option_name ) ); } parent::__construct( $sitepress ); $this->st_instance = &$st_instance; $this->option_name = $option_name; $this->language = $language ? $language : $this->st_instance->get_current_string_language( $option_name ); } /** * * @param string $option_name * @param string|array $new_value * @param int|bool $status * @param int $translator_id * @param int $rec_level * * @return boolean|mixed */ public function update_option( $option_name = '', $new_value = null, $status = false, $translator_id = null, $rec_level = 0 ) { $option_name = $option_name ? $option_name : $this->option_name; $new_value = (array) $new_value; $updated = array(); foreach ( $new_value as $index => $value ) { if ( is_array( $value ) ) { $name = '[' . $option_name . '][' . $index . ']'; $result = $this->update_option( $name, $value, $status, $translator_id, $rec_level + 1 ); $updated[] = array_sum( explode( ',', $result ) ); } else { if ( is_string( $index ) ) { $name = ( $rec_level == 0 ? '[' . $option_name . ']' : $option_name ) . $index; } else { $name = $option_name; } $string = $this->st_instance->string_factory()->find_admin_by_name( $name ); $string_id = $string->string_id(); if ( $string_id ) { if ( $this->language !== $string->get_language() ) { $updated[] = $string->set_translation( $this->language, $value, $status, $translator_id ); } else { $string->update_value( $value ); } } } } return array_sum( $updated ) > 0 ? join( ',', $updated ) : false; } } admin-texts/class-wpml-st-admin-blog-option.php 0000755 00000002757 14720402445 0015555 0 ustar 00 <?php class WPML_ST_Admin_Blog_Option extends WPML_SP_User { /** @var WPML_ST_Admin_Option_Translation $admin_option */ private $admin_option; /** * WPML_ST_Admin_Blog_Option constructor. * * @param SitePress $sitepress * @param WPML_String_Translation $st_instance * @param string $option_name */ public function __construct( &$sitepress, &$st_instance, $option_name ) { if ( ! WPML_ST_Blog_Name_And_Description_Hooks::is_string( $option_name ) ) { throw new InvalidArgumentException( $option_name . ' Is not a valid blog option that is handled by this class, allowed values are "Tagline" and "Blog Title"' ); } parent::__construct( $sitepress ); $this->admin_option = $st_instance->get_admin_option( $option_name ); } /** * @param string|array $old_value * @param string|array $new_value * * @return mixed */ public function pre_update_filter( $old_value, $new_value ) { $wp_api = $this->sitepress->get_wp_api(); if ( $wp_api->is_multisite() && $wp_api->ms_is_switched() && ! $this->sitepress->get_setting( 'setup_complete' ) ) { throw new RuntimeException( 'You cannot update blog option translations while switched to a blog on which the WPML setup is not complete! You are currently using blog ID:' . $this->sitepress->get_wp_api()->get_current_blog_id() ); } WPML_Config::load_config_run(); return $this->admin_option->update_option( '', $new_value, ICL_TM_COMPLETE ) ? $old_value : $new_value; } } strings-scanning/class-wpml-file-name-converter.php 0000755 00000001454 14720402445 0016477 0 ustar 00 <?php class WPML_File_Name_Converter { /** * @var string */ private $home_path; /** * @param string $file * * @return string */ public function transform_realpath_to_reference( $file ) { $home_path = $this->get_home_path(); return str_replace( $home_path, '', $file ); } /** * @param string $file * * @return string */ public function transform_reference_to_realpath( $file ) { $home_path = $this->get_home_path(); return trailingslashit( $home_path ) . ltrim( $file, '/\\' ); } /** * @return string */ private function get_home_path() { if ( null === $this->home_path ) { if ( ! function_exists( 'get_home_path' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $this->home_path = get_home_path(); } return $this->home_path; } } strings-scanning/class-wpml-themes-and-plugins-settings.php 0000755 00000003265 14720402445 0020201 0 ustar 00 <?php class WPML_ST_Themes_And_Plugins_Settings { const OPTION_NAME = 'wpml_st_display_strings_scan_notices'; const NOTICES_GROUP = 'wpml-st-string-scan'; public function init_hooks() { if ( $this->must_display_notices() ) { add_action( 'wp_ajax_hide_strings_scan_notices', array( $this, 'hide_strings_scan_notices' ) ); add_action( 'wpml-notices-scripts-enqueued', array( $this, 'enqueue_scripts' ) ); } } public function get_notices_group() { return self::NOTICES_GROUP; } public function must_display_notices() { return (bool) get_option( self::OPTION_NAME ); } public function set_strings_scan_notices( $value ) { update_option( self::OPTION_NAME, $value ); } public function hide_strings_scan_notices() { update_option( self::OPTION_NAME, false ); } public function display_notices_setting_is_missing() { return null === get_option( self::OPTION_NAME, null ); } public function create_display_notices_setting() { add_option( self::OPTION_NAME, true ); } public function enqueue_scripts() { $strings = array( 'title' => __( 'Dismiss all notices', 'wpml-string-translation' ), 'message' => __( 'Also prevent similar messages in the future?', 'wpml-string-translation' ), 'no' => __( 'No - keep showing these message', 'wpml-string-translation' ), 'yes' => __( 'Yes - disable these notifications completely', 'wpml-string-translation' ), ); wp_register_script( 'wpml-st-disable-notices', WPML_ST_URL . '/res/js/disable-string-scan-notices.js', array( 'jquery', 'jquery-ui-dialog' ) ); wp_localize_script( 'wpml-st-disable-notices', 'wpml_st_disable_notices_strings', $strings ); wp_enqueue_script( 'wpml-st-disable-notices' ); } } strings-scanning/class-wpml-st-update-file-hash-ajax.php 0000755 00000000727 14720402445 0017324 0 ustar 00 <?php class WPML_ST_Update_File_Hash_Ajax implements IWPML_Action { /** @var WPML_ST_File_Hashing */ private $file_hashing; /** * WPML_ST_Update_File_Hash_Ajax constructor. * * @param WPML_ST_File_Hashing $file_hashing */ public function __construct( WPML_ST_File_Hashing $file_hashing ) { $this->file_hashing = $file_hashing; } public function add_hooks() { add_action( 'wp_ajax_update_file_hash', array( $this->file_hashing, 'save_hash' ) ); } } strings-scanning/class-wpml-st-file-hashing.php 0000755 00000002161 14720402445 0015613 0 ustar 00 <?php class WPML_ST_File_Hashing { const OPTION_NAME = 'wpml-scanning-files-hashing'; /** @var array */ private $hashes; public function __construct() { $this->hashes = $this->get_hashes(); } /** * @param string $file */ private function set_hash( $file ) { $this->hashes[ $file ] = md5_file( $file ); } /** * @param string $file * * @return bool */ public function hash_changed( $file ) { return ! array_key_exists( $file, $this->hashes ) || md5_file( $file ) !== $this->hashes[ $file ]; } public function save_hash() { $needs_to_save = false; if ( array_key_exists( 'files', $_POST ) ) { foreach ( $_POST['files'] as $file_path ) { if ( realpath( $file_path ) ) { $this->set_hash( $file_path ); $needs_to_save = true; } } } if ( $needs_to_save ) { update_option( self::OPTION_NAME, $this->hashes ); } wp_send_json_success(); } /** * @return array */ private function get_hashes() { return get_option( self::OPTION_NAME ) ? get_option( self::OPTION_NAME ) : array(); } public function clean_hashes() { delete_option( self::OPTION_NAME ); } } strings-scanning/class-wpml-st-theme-plugin-hooks.php 0000755 00000000540 14720402445 0016773 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Hooks { /** * @var WPML_ST_File_Hashing */ private $file_hashing; public function __construct( WPML_ST_File_Hashing $file_hashing ) { $this->file_hashing = $file_hashing; } public function add_hooks() { add_action( 'icl_st_unregister_string_multi', array( $this->file_hashing, 'clean_hashes' ) ); } } strings-scanning/class-wpml-st-strings-stats.php 0000755 00000002316 14720402445 0016104 0 ustar 00 <?php class WPML_ST_Strings_Stats { /** * @var SitePress */ private $sitepress; /** * @var wpdb */ private $wpdb; /** * @var array */ private $stats; public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } /** * @param string $component_name * @param string $type * @param string $domain */ public function update( $component_name, $type, $domain ) { $count = $this->get_count( $domain ); $string_settings = $this->sitepress->get_setting( 'st' ); $string_settings[ $type . '_localization_domains' ][ $component_name ][ $domain ] = $count; $this->sitepress->set_setting( 'st', $string_settings, true ); $this->sitepress->save_settings(); } /** * @param string $domain * * @return int */ private function get_count( $domain ) { if ( ! $this->stats ) { $this->set_stats(); } return isset( $this->stats[ $domain ] ) ? (int) $this->stats[ $domain ]->count : 0; } private function set_stats() { $count_query = 'SELECT context, COUNT(id) count FROM ' . $this->wpdb->prefix . 'icl_strings GROUP BY context'; $this->stats = $this->wpdb->get_results( $count_query, OBJECT_K ); } } strings-scanning/factory/class-wpml-st-theme-string-scanner-factory.php 0000755 00000000405 14720402445 0022425 0 ustar 00 <?php class WPML_ST_Theme_String_Scanner_Factory { /** @return WPML_Theme_String_Scanner */ public function create() { $file_hashing = new WPML_ST_File_Hashing(); return new WPML_Theme_String_Scanner( wpml_get_filesystem_direct(), $file_hashing ); } } strings-scanning/factory/class-wpml-st-theme-plugin-scan-dir-ajax-factory.php 0000755 00000001117 14720402445 0023406 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory extends WPML_AJAX_Base_Factory implements IWPML_Backend_Action_Loader { const AJAX_ACTION = 'wpml_get_files_to_scan'; const NONCE = 'wpml-get-files-to-scan-nonce'; /** @return null|WPML_ST_Theme_Plugin_Scan_Dir_Ajax */ public function create() { $hooks = null; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { $scan_dir = new WPML_ST_Scan_Dir(); $file_hashing = new WPML_ST_File_Hashing(); $hooks = new WPML_ST_Theme_Plugin_Scan_Dir_Ajax( $scan_dir, $file_hashing ); } return $hooks; } } strings-scanning/factory/class-st-theme-plugin-hooks-factory.php 0000755 00000000363 14720402445 0021135 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Hooks_Factory implements IWPML_Backend_Action_Loader { /** * @return WPML_ST_Theme_Plugin_Hooks */ public function create() { return new WPML_ST_Theme_Plugin_Hooks( new WPML_ST_File_Hashing() ); } } strings-scanning/factory/class-st-update-file-hash-ajax-factory.php 0000755 00000000731 14720402445 0021456 0 ustar 00 <?php class WPML_ST_Update_File_Hash_Ajax_Factory extends WPML_AJAX_Base_Factory implements IWPML_Backend_Action_Loader { const AJAX_ACTION = 'update_file_hash'; const NONCE = 'wpml-update-file-hash-nonce'; /** @return null|WPML_ST_Update_File_Hash_Ajax */ public function create() { $hooks = null; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { $hooks = new WPML_ST_Update_File_Hash_Ajax( new WPML_ST_File_Hashing() ); } return $hooks; } } strings-scanning/factory/class-wpml-st-plugin-string-scanner-factory.php 0000755 00000000410 14720402445 0022615 0 ustar 00 <?php class WPML_ST_Plugin_String_Scanner_Factory { /** @return WPML_Plugin_String_Scanner */ public function create() { $file_hashing = new WPML_ST_File_Hashing(); return new WPML_Plugin_String_Scanner( wpml_get_filesystem_direct(), $file_hashing ); } } strings-scanning/factory/class-wpml-st-theme-plugin-scan-files-ajax-factory.php 0000755 00000001447 14720402445 0023740 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory extends WPML_AJAX_Base_Factory implements IWPML_Backend_Action_Loader { const AJAX_ACTION = 'wpml_st_scan_chunk'; const NONCE = 'wpml-scan-files-nonce'; /** @return null|WPML_ST_Theme_Plugin_Scan_Files_Ajax */ public function create() { $hooks = null; $scan_factory = ''; if ( $this->is_valid_action( self::AJAX_ACTION ) ) { if ( array_key_exists( 'theme', $_POST ) ) { $scan_factory = new WPML_ST_Theme_String_Scanner_Factory(); } elseif ( array_key_exists( 'plugin', $_POST ) ) { $scan_factory = new WPML_ST_Plugin_String_Scanner_Factory(); } if ( $scan_factory ) { $scan = $scan_factory->create(); $hooks = new WPML_ST_Theme_Plugin_Scan_Files_Ajax( $scan ); } } return $hooks; } } strings-scanning/iwpml-st-string-scanner.php 0000755 00000000107 14720402445 0015256 0 ustar 00 <?php interface IWPML_ST_String_Scanner { public function scan(); } strings-scanning/class-wpml-themes-and-plugins-updates.php 0000755 00000004176 14720402445 0020010 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ST_Themes_And_Plugins_Updates { const WPML_WP_UPDATED_MO_FILES = 'wpml_wp_updated_mo_files'; const WPML_ST_ITEMS_TO_SCAN = 'wpml_items_to_scan'; const WPML_ST_SCAN_NOTICE_ID = 'wpml_st_scan_items'; const WPML_ST_FASTER_SETTINGS_NOTICE_ID = 'wpml_st_faster_settings'; const WPML_ST_SCAN_ACTIVE_ITEMS_NOTICE_ID = 'wpml_st_scan_active_items'; /** @var WPML_Notices */ private $admin_notices; /** @var WPML_ST_Themes_And_Plugins_Settings */ private $settings; /** * WPML_ST_Admin_Notices constructor. * * @param WPML_Notices $admin_notices * @param WPML_ST_Themes_And_Plugins_Settings $settings */ public function __construct( WPML_Notices $admin_notices, WPML_ST_Themes_And_Plugins_Settings $settings ) { $this->admin_notices = $admin_notices; $this->settings = $settings; } public function init_hooks() { add_action( 'upgrader_process_complete', array( $this, 'store_mo_file_update' ), 10, 2 ); } public function data_is_valid( $thing ) { return $thing && ! is_wp_error( $thing ); } public function notices_count() { return $this->admin_notices->count(); } public function remove_notice( $id ) { $this->admin_notices->remove_notice( $this->settings->get_notices_group(), $id ); } /** * @param \WP_Upgrader $upgrader * @param array<string,string|array<string,string>> $language_translations */ public function store_mo_file_update( WP_Upgrader $upgrader, $language_translations ) { if ( is_wp_error( $upgrader->result ) ) { return; } $action = $language_translations['action']; if ( in_array( $action, array( 'update', 'install' ), true ) ) { if ( 'translation' === $language_translations['type'] ) { $last_update = get_option( self::WPML_WP_UPDATED_MO_FILES, array() ); $translations = $language_translations['translations']; foreach ( $translations as $translation ) { $last_update[ $translation['type'] ][ $translation['slug'] ] = time(); } update_option( self::WPML_WP_UPDATED_MO_FILES, $last_update, false ); } } } } strings-scanning/class-wpml-st-theme-plugin-scan-files-ajax.php 0000755 00000002047 14720402445 0020621 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Scan_Files_Ajax implements IWPML_Action { /** @var IWPML_ST_String_Scanner */ private $string_scanner; /** * WPML_ST_Theme_Scan_Files_Ajax constructor. * * @param IWPML_ST_String_Scanner $string_scanner */ public function __construct( IWPML_ST_String_Scanner $string_scanner ) { $this->string_scanner = $string_scanner; } public function add_hooks() { add_action( 'wp_ajax_wpml_st_scan_chunk', array( $this, 'scan' ) ); } public function scan() { wpml_get_admin_notices()->remove_notice( WPML_ST_Themes_And_Plugins_Settings::NOTICES_GROUP, WPML_ST_Themes_And_Plugins_Updates::WPML_ST_SCAN_NOTICE_ID ); wpml_get_admin_notices()->remove_notice( WPML_ST_Themes_And_Plugins_Settings::NOTICES_GROUP, WPML_ST_Themes_And_Plugins_Updates::WPML_ST_SCAN_ACTIVE_ITEMS_NOTICE_ID ); $this->clear_items_needs_scan_buffer(); $this->string_scanner->scan(); } public function clear_items_needs_scan_buffer() { delete_option( WPML_ST_Themes_And_Plugins_Updates::WPML_ST_ITEMS_TO_SCAN ); } } strings-scanning/wpml-st-theme-plugin-scan-dir-ajax.php 0000755 00000006076 14720402445 0017200 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Scan_Dir_Ajax { /** @var WPML_ST_Scan_Dir */ private $scan_dir; /** @var WPML_ST_File_Hashing */ private $file_hashing; /** * WPML_ST_Theme_Plugin_Scan_Dir_Ajax constructor. * * @param WPML_ST_Scan_Dir $scan_dir * @param WPML_ST_File_Hashing $file_hashing */ public function __construct( WPML_ST_Scan_Dir $scan_dir, WPML_ST_File_Hashing $file_hashing ) { $this->scan_dir = $scan_dir; $this->file_hashing = $file_hashing; } public function add_hooks() { add_action( 'wp_ajax_wpml_get_files_to_scan', array( $this, 'get_files' ) ); } public function get_files() { $folders = $this->get_folder(); $result = array(); if ( $folders ) { $file_type = array( 'php', 'inc' ); $files_found_chunks = array(); foreach ( $folders as $folder ) { $files_found_chunks[] = $this->scan_dir->scan( $folder, $file_type, $this->is_one_file_plugin(), $this->get_folders_to_ignore() ); } $files = call_user_func_array( 'array_merge', $files_found_chunks ); $files = $this->filter_modified_files( $files ); if ( ! $files ) { $this->clear_items_to_scan_buffer(); } $result = array( 'files' => $files, 'no_files_message' => __( 'Files already scanned.', 'wpml-string-translation' ), ); } wp_send_json_success( $result ); } private function clear_items_to_scan_buffer() { wpml_get_admin_notices()->remove_notice( WPML_ST_Themes_And_Plugins_Settings::NOTICES_GROUP, WPML_ST_Themes_And_Plugins_Updates::WPML_ST_SCAN_NOTICE_ID ); delete_option( WPML_ST_Themes_And_Plugins_Updates::WPML_ST_ITEMS_TO_SCAN ); } /** * @param array $files * * @return array */ private function filter_modified_files( $files ) { $modified_files = array(); foreach ( $files as $file ) { if ( $this->file_hashing->hash_changed( $file ) ) { $modified_files[] = $file; } } return $modified_files; } /** @return array */ private function get_folder() { $folder = array(); if ( array_key_exists( 'theme', $_POST ) ) { $folder[] = get_theme_root() . '/' . sanitize_text_field( $_POST['theme'] ); } elseif ( array_key_exists( 'plugin', $_POST ) ) { $plugin_folder = explode( '/', $_POST['plugin'] ); $folder[] = WPML_PLUGINS_DIR . '/' . sanitize_text_field( $plugin_folder[0] ); } elseif ( array_key_exists( 'mu-plugin', $_POST ) ) { $folder[] = WPMU_PLUGIN_DIR . '/' . sanitize_text_field( $_POST['mu-plugin'] ); } return $folder; } private function is_one_file_plugin() { $is_one_file_plugin = false; if ( array_key_exists( 'plugin', $_POST ) ) { $is_one_file_plugin = false === strpos( $_POST['plugin'], 'plugins/' ); } if ( array_key_exists( 'mu-plugin', $_POST ) ) { $is_one_file_plugin = false === strpos( $_POST['mu-plugin'], 'mu-plugins/' ); } return $is_one_file_plugin; } /** * @return array */ private function get_folders_to_ignore() { $folders = [ WPML_ST_Scan_Dir::PLACEHOLDERS_ROOT . '/node_modules', WPML_ST_Scan_Dir::PLACEHOLDERS_ROOT . '/tests', ]; return $folders; } } string-translation/class-wpml-st-string-translation-priority-ajax.php 0000755 00000002110 14720402445 0022264 0 ustar 00 <?php class WPML_ST_String_Translation_Priority_AJAX implements IWPML_Action { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function add_hooks() { add_action( 'wp_ajax_wpml_change_string_translation_priority', array( $this, 'change_string_translation_priority' ) ); } public function change_string_translation_priority() { if ( $this->verify_ajax( 'wpml_change_string_translation_priority_nonce' ) ) { $change_string_translation_priority_dialog = new WPML_Strings_Translation_Priority( $this->wpdb ); $string_ids = array_map( 'intval', $_POST['strings'] ); $priority = (string) filter_var( isset( $_POST['priority'] ) ? $_POST['priority'] : '', FILTER_SANITIZE_SPECIAL_CHARS ); $change_string_translation_priority_dialog->change_translation_priority_of_strings( $string_ids, $priority ); wp_send_json_success(); } } private function verify_ajax( $ajax_action ) { return isset( $_POST['wpnonce'] ) && wp_verify_nonce( $_POST['wpnonce'], $ajax_action ); } } string-translation/class-wpml-strings-translation-priority.php 0000755 00000001136 14720402445 0021111 0 ustar 00 <?php class WPML_Strings_Translation_Priority { /** * @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param int[] $strings * @param string $priority */ public function change_translation_priority_of_strings( $strings, $priority ) { $update_query = "UPDATE {$this->wpdb->prefix}icl_strings SET translation_priority=%s WHERE id IN (" . wpml_prepare_in( $strings, '%d' ) . ')'; $update_prepare = $this->wpdb->prepare( $update_query, $priority ); $this->wpdb->query( $update_prepare ); } } string-translation/class-wpml-st-string-translation-ajax-hooks-factory.php 0000755 00000000422 14720402445 0023177 0 ustar 00 <?php class WPML_ST_String_Translation_AJAX_Hooks_Factory implements IWPML_Backend_Action_Loader { public function create() { global $wpdb; $ajax_hooks = array(); $ajax_hooks[] = new WPML_ST_String_Translation_Priority_AJAX( $wpdb ); return $ajax_hooks; } } privacy/class-wpml-st-privacy-content.php 0000755 00000000734 14720402445 0014574 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ST_Privacy_Content extends WPML_Privacy_Content { /** * @return string */ protected function get_plugin_name() { return 'WPML String Translation'; } /** * @return string|array */ protected function get_privacy_policy() { return __( 'WPML String Translation will send all strings to WPML’s Advanced Translation Editor and to the translation services which are used.', 'wpml-string-translation' ); } } privacy/class-wpml-st-privacy-content-factory.php 0000755 00000000455 14720402445 0016241 0 ustar 00 <?php /** * @author OnTheGo Systems */ class WPML_ST_Privacy_Content_Factory implements IWPML_Backend_Action_Loader { /** * @return IWPML_Action */ public function create() { if ( class_exists( 'WPML_Privacy_Content' ) ) { return new WPML_ST_Privacy_Content(); } return null; } } Troubleshooting/BackendHooks.php 0000755 00000005604 14720402445 0013011 0 ustar 00 <?php namespace WPML\ST\Troubleshooting; use function WPML\Container\make; use WPML\ST\MO\Generate\DomainsAndLanguagesRepository; use WPML\ST\MO\Generate\Process\ProcessFactory; class BackendHooks implements \IWPML_Backend_Action, \IWPML_DIC_Action { const SCRIPT_HANDLE = 'wpml-st-troubleshooting'; const NONCE_KEY = 'wpml-st-troubleshooting'; /** @var DomainsAndLanguagesRepository $domainsAndLanguagesRepo */ private $domainsAndLanguagesRepo; public function __construct( DomainsAndLanguagesRepository $domainsAndLanguagesRepo ) { $this->domainsAndLanguagesRepo = $domainsAndLanguagesRepo; } public function add_hooks() { add_action( 'after_setup_complete_troubleshooting_functions', [ $this, 'displayButtons' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'loadJS' ] ); } public function displayButtons() { ?><div> <?php if ( ! $this->domainsAndLanguagesRepo->get()->isEmpty() ) { $this->displayButton( AjaxFactory::ACTION_SHOW_GENERATE_DIALOG, esc_attr__( 'Show custom MO Files Pre-generation dialog box', 'wpml-string-translation' ), false ); } $this->displayButton( AjaxFactory::ACTION_CLEANUP, esc_attr__( 'Cleanup and optimize string tables', 'wpml-string-translation' ), esc_attr__( 'Cleanup and optimization completed!', 'wpml-string-translation' ) ); $this->displayLinkButton( admin_url( sprintf( 'admin.php?page=%s', WPML_ST_FOLDER . '/menu/string-translation.php' ) ) . '&troubleshooting=1', esc_attr__( 'Clear invalid strings', 'wpml-string-translation' ) ); ?> </div> <?php } /** * @param string $action * @param string $buttonLabel * @param string|false $confirmationMessage A string to display or false if we want to immediately reload. */ private function displayButton( $action, $buttonLabel, $confirmationMessage ) { ?> <p> <input id="<?php echo $action; ?>" class="js-wpml-st-troubleshooting-action button-secondary" type="button" value="<?php echo $buttonLabel; ?>" data-action="<?php echo $action; ?>" data-success-message="<?php echo $confirmationMessage; ?>" data-nonce="<?php echo wp_create_nonce( self::NONCE_KEY ); ?>" data-reload="<?php echo ! (bool) $confirmationMessage; ?>" /> <br/> </p> <?php } /** * @param string $link * @param string $buttonLabel */ private function displayLinkButton( $link, $buttonLabel ) { ?> <p> <a class="button-secondary" href="<?php echo $link; ?>"><?php echo $buttonLabel; ?></a> <br/> </p> <?php } /** * @param string $hook */ public function loadJS( $hook ) { if ( WPML_PLUGIN_FOLDER . '/menu/troubleshooting.php' === $hook ) { wp_register_script( self::SCRIPT_HANDLE, WPML_ST_URL . '/res/js/troubleshooting.js', [ 'jquery', 'wp-util', 'jquery-ui-sortable', 'jquery-ui-dialog' ] ); wp_enqueue_script( self::SCRIPT_HANDLE ); } } } Troubleshooting/AjaxFactory.php 0000755 00000003125 14720402445 0012665 0 ustar 00 <?php namespace WPML\ST\Troubleshooting; use WPML\ST\MO\Generate\MultiSite\Executor; use WPML\ST\MO\Scan\UI\Factory; use function WPML\Container\make; use WPML\ST\MO\Generate\Process\Status; use WPML\ST\Troubleshooting\Cleanup\Database; class AjaxFactory implements \IWPML_AJAX_Action_Loader { const ACTION_SHOW_GENERATE_DIALOG = 'wpml_st_mo_generate_show_dialog'; const ACTION_CLEANUP = 'wpml_st_troubleshooting_cleanup'; public function create() { return self::getActions()->map( self::buildHandler() )->toArray(); } /** * @return \WPML\Collect\Support\Collection */ public static function getActions() { return wpml_collect( [ [ self::ACTION_SHOW_GENERATE_DIALOG, [ self::class, 'showGenerateDialog' ] ], [ self::ACTION_CLEANUP, [ self::class, 'cleanup' ] ], ] ); } /** * @return \Closure */ public static function buildHandler() { return function( array $action ) { return new RequestHandle( ...$action ); }; } /** * @throws \WPML\Auryn\InjectionException */ public static function showGenerateDialog() { if ( is_super_admin() && is_multisite() ) { ( new Executor() )->executeWith( Executor::MAIN_SITE_ID, function () { make( Status::class )->markIncompleteForAll(); } ); } else { make( Status::class )->markIncomplete(); } Factory::ignoreWpmlVersion(); } /** * @throws \WPML\Auryn\InjectionException */ public static function cleanup() { /** @var Database $database */ $database = make( Database::class ); $database->deleteStringsFromImportedMoFiles(); $database->truncatePagesAndUrls(); } } Troubleshooting/Cleanup/Database.php 0000755 00000004072 14720402445 0013547 0 ustar 00 <?php namespace WPML\ST\Troubleshooting\Cleanup; use wpdb; use WPML_ST_Translations_File_Dictionary; class Database { /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_ST_Translations_File_Dictionary $dictionary */ private $dictionary; public function __construct( wpdb $wpdb, WPML_ST_Translations_File_Dictionary $dictionary ) { $this->wpdb = $wpdb; $this->dictionary = $dictionary; } public function deleteStringsFromImportedMoFiles() { $moDomains = $this->dictionary->get_domains( 'mo' ); if ( ! $moDomains ) { return; } $this->deleteOnlyNativeMoStringTranslations( $moDomains ); $this->deleteMoStringsWithNoTranslation( $moDomains ); icl_update_string_status_all(); $this->optimizeStringTables(); } private function deleteOnlyNativeMoStringTranslations( array $moDomains ) { $this->wpdb->query( " DELETE st FROM {$this->wpdb->prefix}icl_string_translations AS st LEFT JOIN {$this->wpdb->prefix}icl_strings AS s ON st.string_id = s.id WHERE st.value IS NULL AND s.context IN(" . wpml_prepare_in( $moDomains ) . ') ' ); } private function deleteMoStringsWithNoTranslation( array $moDomains ) { $this->wpdb->query( " DELETE s FROM {$this->wpdb->prefix}icl_strings AS s LEFT JOIN {$this->wpdb->prefix}icl_string_translations AS st ON st.string_id = s.id WHERE st.string_id IS NULL AND s.context IN(" . wpml_prepare_in( $moDomains ) . ') ' ); } private function optimizeStringTables() { $this->wpdb->query( "OPTIMIZE TABLE {$this->wpdb->prefix}icl_strings, {$this->wpdb->prefix}icl_string_translations" ); } public function truncatePagesAndUrls() { foreach ( [ 'icl_string_pages', 'icl_string_urls' ] as $table ) { $table = $this->wpdb->prefix . $table; if ( $this->tableExists( $table ) ) { $this->wpdb->query( "TRUNCATE $table" ); } } } /** * @param string $table * * @return bool */ private function tableExists( $table ) { /** @var string $sql */ $sql = $this->wpdb->prepare( 'SHOW TABLES LIKE %s', $table ); return (bool) $this->wpdb->get_var( $sql ); } } Troubleshooting/RequestHandle.php 0000755 00000001205 14720402445 0013213 0 ustar 00 <?php namespace WPML\ST\Troubleshooting; class RequestHandle implements \IWPML_Action { /** @var string $action */ private $action; /** @var callable $callback */ private $callback; public function __construct( $action, $callback ) { $this->action = $action; $this->callback = $callback; } public function add_hooks() { add_action( 'wp_ajax_' . $this->action, [ $this, 'handle' ] ); } public function handle() { if ( wp_verify_nonce( $_POST['nonce'], BackendHooks::NONCE_KEY ) ) { call_user_func( $this->callback ); wp_send_json_success(); } else { wp_send_json_error( 'Invalid nonce value', 500 ); } } } MO/Plural.php 0000755 00000004044 14720402445 0007036 0 ustar 00 <?php namespace WPML\ST\MO; class Plural implements \IWPML_Backend_Action, \IWPML_Frontend_Action { public function add_hooks() { add_filter( 'ngettext', [ $this, 'handle_plural' ], 9, 5 ); add_filter( 'ngettext_with_context', [ $this, 'handle_plural_with_context' ], 9, 6 ); } /** * @param string $translation Translated text. * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param string $number The number to compare against to use either the singular or plural form. * @param string $domain Text domain. Unique identifier for retrieving translated strings. * * @return string */ public function handle_plural( $translation, $single, $plural, $number, $domain ) { return $this->get_translation( $translation, $single, $plural, $number, function ( $original ) use ( $domain ) { return __( $original, $domain ); } ); } /** * @param string $translation Translated text. * @param string $single The text to be used if the number is singular. * @param string $plural The text to be used if the number is plural. * @param string $number The number to compare against to use either the singular or plural form. * @param string $context Context information for the translators. * @param string $domain Text domain. Unique identifier for retrieving translated strings. * * @return string */ public function handle_plural_with_context( $translation, $single, $plural, $number, $context, $domain ) { return $this->get_translation( $translation, $single, $plural, $number, function ( $original ) use ( $domain, $context ) { return _x( $original, $context, $domain ); } ); } private function get_translation( $translation, $single, $plural, $number, $callback ) { $original = (int) $number === 1 ? $single : $plural; $possible_translation = $callback( $original ); if ( $possible_translation !== $original ) { return $possible_translation; } return $translation; } } MO/Notice/RegenerationInProgressNotice.php 0000755 00000001174 14720402445 0014621 0 ustar 00 <?php namespace WPML\ST\MO\Notice; class RegenerationInProgressNotice extends \WPML_Notice { const ID = 'mo-files-regeneration'; const GROUP = 'mo-files'; public function __construct() { $text = "WPML is updating the .mo files with the translation for strings. This will take a few more moments. During this process, translation for strings is not displaying on the front-end. You can refresh this page in a minute to see if it's done."; $text = __( $text, 'wpml-string-translation' ); parent::__construct( self::ID, $text, self::GROUP ); $this->set_dismissible( false ); $this->set_css_classes( 'warning' ); } } MO/File/Manager.php 0000755 00000001701 14720402445 0010025 0 ustar 00 <?php namespace WPML\ST\MO\File; use GlobIterator; use WPML\Collect\Support\Collection; use WPML\ST\TranslationFile\Domains; use WPML\ST\TranslationFile\StringsRetrieve; use WPML_Language_Records; class Manager extends \WPML\ST\TranslationFile\Manager { public function __construct( StringsRetrieve $strings, Builder $builder, \WP_Filesystem_Direct $filesystem, WPML_Language_Records $language_records, Domains $domains ) { parent::__construct( $strings, $builder, $filesystem, $language_records, $domains ); } /** * @return string */ protected function getFileExtension() { return 'mo'; } /** * @return bool */ public function isPartialFile() { return true; } /** * @return Collection */ protected function getDomains() { return $this->domains->getMODomains(); } /** * @return bool */ public static function hasFiles() { return (bool) ( new GlobIterator( self::getSubdir() . '/*.mo' ) )->count(); } } MO/File/MOFactory.php 0000755 00000000217 14720402445 0010317 0 ustar 00 <?php namespace WPML\ST\MO\File; class MOFactory { /** * @return \MO */ public function createNewInstance() { return new \MO(); } } MO/File/Generator.php 0000755 00000003455 14720402445 0010411 0 ustar 00 <?php namespace WPML\ST\MO\File; use WPML\Collect\Support\Collection; use WPML\ST\TranslateWpmlString; use WPML\ST\TranslationFile\StringEntity; use function wpml_collect; class Generator { /** @var MOFactory */ private $moFactory; public function __construct( MOFactory $moFactory ) { $this->moFactory = $moFactory; } /** * @param StringEntity[] $entries * * @return string */ public function getContent( array $entries ) { $mo = $this->moFactory->createNewInstance(); wpml_collect( $entries ) ->reduce( [ $this, 'createMOFormatEntities' ], wpml_collect( [] ) ) ->filter( function( array $entry ) { return ! empty($entry['singular']); } ) ->each( [ $mo, 'add_entry' ] ); $mem_file = fopen( 'php://memory', 'r+' ); if ( $mem_file === false ) { return ''; } $mo->export_to_file_handle( $mem_file ); rewind( $mem_file ); $mo_content = stream_get_contents( $mem_file ); fclose( $mem_file ); return $mo_content; } /** * @param Collection $carry * @param StringEntity $entry * * @return Collection */ public function createMOFormatEntities( $carry, StringEntity $entry ) { $carry->push( $this->mapStringEntityToMOFormatUsing( $entry, 'original' ) ); if ( TranslateWpmlString::canTranslateWithMO( $entry->get_original(), $entry->get_name() ) ) { $carry->push( $this->mapStringEntityToMOFormatUsing( $entry, 'name' ) ); } return $carry; } /** * @param StringEntity $entry * @param string $singularField * * @return array */ private function mapStringEntityToMOFormatUsing( StringEntity $entry, $singularField ) { return [ 'singular' => $entry->{'get_' . $singularField}(), 'translations' => $entry->get_translations(), 'context' => $entry->get_context(), 'plural' => $entry->get_original_plural(), ]; } } MO/File/FailureHooks.php 0000755 00000007310 14720402445 0011050 0 ustar 00 <?php namespace WPML\ST\MO\File; use WP_Filesystem_Direct; use WPML\ST\MO\Generate\Process\Status; use WPML\ST\MO\Generate\Process\SingleSiteProcess; use WPML\ST\MO\Notice\RegenerationInProgressNotice; use function wpml_get_admin_notices; class FailureHooks implements \IWPML_Backend_Action { use makeDir; const NOTICE_GROUP = 'mo-failure'; const NOTICE_ID_MISSING_FOLDER = 'missing-folder'; /** @var Status */ private $status; /** @var SingleSiteProcess $singleProcess */ private $singleProcess; public function __construct( WP_Filesystem_Direct $filesystem, Status $status, SingleSiteProcess $singleProcess ) { $this->filesystem = $filesystem; $this->status = $status; $this->singleProcess = $singleProcess; } public function add_hooks() { add_action( 'admin_init', [ $this, 'checkDirectories' ] ); } public function checkDirectories() { if ( $this->isDirectoryMissing( WP_LANG_DIR ) ) { $this->resetRegenerateStatus(); $this->displayMissingFolderNotice( WP_LANG_DIR ); return; } if ( $this->isDirectoryMissing( self::getSubdir() ) ) { $this->resetRegenerateStatus(); if ( ! $this->maybeCreateSubdir() ) { $this->displayMissingFolderNotice( self::getSubdir() ); return; } } $notices = wpml_get_admin_notices(); $notices->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID_MISSING_FOLDER ); if ( ! $this->status->isComplete() ) { $this->displayRegenerateInProgressNotice(); $this->singleProcess->runPage(); } if ( $this->status->isComplete() ) { wpml_get_admin_notices()->remove_notice( RegenerationInProgressNotice::GROUP, RegenerationInProgressNotice::ID ); } } /** * @param string $dir */ public function displayMissingFolderNotice( $dir ) { $notices = wpml_get_admin_notices(); $notice = $notices->get_new_notice( self::NOTICE_ID_MISSING_FOLDER, self::missingFolderNoticeContent( $dir ), self::NOTICE_GROUP ); $notice->set_css_classes( 'error' ); $notices->add_notice( $notice ); } /** * @param string $dir * * @return string */ public static function missingFolderNoticeContent( $dir ) { $text = '<p>' . esc_html__( 'WPML String Translation is attempting to write .mo files with translations to folder:', 'wpml-string-translation' ) . '<br/>' . str_replace( '\\', '/', $dir ) . '</p>'; $text .= '<p>' . esc_html__( 'This folder appears to be not writable. This is blocking translation for strings from appearing on the site.', 'wpml-string-translation' ) . '</p>'; $text .= '<p>' . esc_html__( 'To resolve this, please contact your hosting company and request that they make that folder writable.', 'wpml-string-translation' ) . '</p>'; $url = 'https://wpml.org/faq/cannot-write-mo-files/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlst'; $link = '<a href="' . $url . '" target="_blank" rel="noreferrer noopener" >' . esc_html__( "WPML's documentation on troubleshooting .mo files generation.", 'wpml-string-translation' ) . '</a>'; $text .= '<p>' . sprintf( esc_html__( 'For more details, see %s.', 'wpml-string-translation' ), $link ) . '</p>'; return $text; } private function displayRegenerateInProgressNotice() { $notices = wpml_get_admin_notices(); $notices->add_notice( new RegenerationInProgressNotice() ); } /** * @return string */ public static function getSubdir() { return WP_LANG_DIR . '/' . \WPML\ST\TranslationFile\Manager::SUB_DIRECTORY; } /** * @param string $dir * * @return bool */ private function isDirectoryMissing( $dir ) { return ! $this->filesystem->is_writable( $dir ); } private function resetRegenerateStatus() { $this->status->markIncomplete(); } } MO/File/makeDir.php 0000755 00000001175 14720402445 0010034 0 ustar 00 <?php namespace WPML\ST\MO\File; trait makeDir { /** * @var \WP_Filesystem_Direct */ protected $filesystem; /** @return bool */ public function maybeCreateSubdir() { $subdir = $this->getSubdir(); if ( $this->filesystem->is_dir( $subdir ) && $this->filesystem->is_writable( $subdir ) ) { return true; } $chmod = defined( 'FS_CHMOD_DIR' ) ? FS_CHMOD_DIR : 0755; return $this->filesystem->mkdir( $subdir, $chmod ); } /** * This declaration throws a "Strict standards" warning in PHP 5.6. * @todo: Remove the comment when we drop support for PHP 5.6. */ //abstract public static function getSubdir(); } MO/File/FailureHooksFactory.php 0000755 00000002044 14720402445 0012377 0 ustar 00 <?php namespace WPML\ST\MO\File; use SitePress; use WPML\ST\MO\Generate\Process\ProcessFactory; use function WPML\Container\make; use WPML\ST\MO\Scan\UI\Factory as UiFactory; class FailureHooksFactory implements \IWPML_Backend_Action_Loader { /** * @return FailureHooks|null * @throws \WPML\Auryn\InjectionException */ public function create() { /** @var SitePress $sitepress */ global $sitepress; if ( $sitepress->is_setup_complete() && $this->hasRanPreGenerateViaUi() ) { $inBackground = true; return make( FailureHooks::class, [ ':status' => ProcessFactory::createStatus( $inBackground ), ':singleProcess' => ProcessFactory::createSingle( $inBackground ), ] ); } return null; } /** * @return bool * @throws \WPML\Auryn\InjectionException */ private function hasRanPreGenerateViaUi() { $uiPreGenerateStatus = ProcessFactory::createStatus( false ); return $uiPreGenerateStatus->isComplete() || UiFactory::isDismissed() || ! ProcessFactory::createSingle()->getPagesCount(); } } MO/File/ManagerFactory.php 0000755 00000000432 14720402445 0011355 0 ustar 00 <?php namespace WPML\ST\MO\File; use function WPML\Container\make; class ManagerFactory { /** * @return Manager * @throws \WPML\Auryn\InjectionException */ public static function create() { return make( Manager::class, [ ':builder' => make( Builder::class ) ] ); } } MO/File/Builder.php 0000755 00000000673 14720402445 0010050 0 ustar 00 <?php namespace WPML\ST\MO\File; use WPML\ST\TranslationFile\StringEntity; class Builder extends \WPML\ST\TranslationFile\Builder { /** @var Generator */ private $generator; public function __construct( Generator $generator ) { $this->generator = $generator; } /** * @param StringEntity[] $strings * @return string */ public function get_content( array $strings ) { return $this->generator->getContent( $strings ); } } MO/Generate/StringsRetrieveMOOriginals.php 0000755 00000000542 14720402445 0014573 0 ustar 00 <?php namespace WPML\ST\MO\Generate; use WPML\ST\TranslationFile\StringsRetrieve; class StringsRetrieveMOOriginals extends StringsRetrieve { /** * @param array $row_data * * @return string|null */ public static function parseTranslation( array $row_data ) { return ! empty( $row_data['mo_string'] ) ? $row_data['mo_string'] : null; } } MO/Generate/DomainsAndLanguagesRepository.php 0000755 00000003266 14720402445 0015302 0 ustar 00 <?php namespace WPML\ST\MO\Generate; use wpdb; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\ST\TranslationFile\Domains; use function wpml_collect; use WPML_Locale; class DomainsAndLanguagesRepository { /** @var wpdb */ private $wpdb; /** @var Domains */ private $domains; /** @var WPML_Locale */ private $locale; /** * @param wpdb $wpdb * @param Domains $domains * @param WPML_Locale $wp_locale */ public function __construct( wpdb $wpdb, Domains $domains, WPML_Locale $wp_locale ) { $this->wpdb = $wpdb; $this->domains = $domains; $this->locale = $wp_locale; } /** * @return Collection */ public function get() { return $this->getAllDomains()->map( function ( $row ) { return (object) [ 'domain' => $row->domain, 'locale' => $this->locale->get_locale( $row->languageCode ) ]; } )->values(); } /** * @return Collection */ private function getAllDomains() { $moDomains = $this->domains->getMODomains()->toArray(); if ( ! $moDomains ) { return wpml_collect( [] ); } $sql = " SELECT DISTINCT (BINARY s.context) as `domain`, st.language as `languageCode` FROM {$this->wpdb->prefix}icl_string_translations st INNER JOIN {$this->wpdb->prefix}icl_strings s ON s.id = st.string_id WHERE st.`status` = 10 AND ( st.`value` != st.mo_string OR st.mo_string IS NULL) AND s.context IN(" . wpml_prepare_in( $moDomains ) . ") "; $result = $this->wpdb->get_results( $sql ); return wpml_collect( $result ); } /** * @return bool */ public static function hasTranslationFilesTable() { return make( \WPML_Upgrade_Schema::class )->does_table_exist( 'icl_mo_files_domains' ); } } MO/Generate/GenerateMissingMOFile.php 0000755 00000004717 14720402445 0013460 0 ustar 00 <?php namespace WPML\ST\MO\Generate; use WPML\ST\MO\File\Builder; use WPML\ST\MO\File\makeDir; use WPML\ST\MO\Hooks\LoadMissingMOFiles; use WPML\ST\TranslationFile\StringsRetrieve; use WPML\WP\OptionManager; use function WPML\Container\make; class MissingMOFile { use makeDir; const OPTION_GROUP = 'ST-MO'; const OPTION_NAME = 'missing-mo-processed'; /** * @var Builder */ private $builder; /** * @var StringsRetrieve */ private $stringsRetrieve; /** * @var \WPML_Language_Records */ private $languageRecords; /** * @var OptionManager */ private $optionManager; public function __construct( \WP_Filesystem_Direct $filesystem, Builder $builder, StringsRetrieveMOOriginals $stringsRetrieve, \WPML_Language_Records $languageRecords, OptionManager $optionManager ) { $this->filesystem = $filesystem; $this->builder = $builder; $this->stringsRetrieve = $stringsRetrieve; $this->languageRecords = $languageRecords; $this->optionManager = $optionManager; } /** * @param string $generateMoPath * @param string $domain */ public function run( $generateMoPath, $domain ) { $processed = $this->getProcessed(); if ( ! $processed->contains( basename( $generateMoPath ) ) && $this->maybeCreateSubdir() ) { $locale = make( \WPML_ST_Translations_File_Locale::class )->get( $generateMoPath, $domain ); $strings = $this->stringsRetrieve->get( $domain, $this->languageRecords->get_language_code( $locale ), false ); if ( ! empty( $strings ) ) { $fileContents = $this->builder ->set_language( $locale ) ->get_content( $strings ); $chmod = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644; $this->filesystem->put_contents( $generateMoPath, $fileContents, $chmod ); do_action( 'wpml_st_translation_file_updated', $generateMoPath, $domain, $locale ); } $processed->push( $generateMoPath ); $this->optionManager->set( self::OPTION_GROUP, self::OPTION_NAME, $processed->toArray() ); } } public function isNotProcessed( $generateMoPath ) { return ! $this->getProcessed()->contains( basename($generateMoPath) ); } public static function getSubdir() { return WP_LANG_DIR . LoadMissingMOFiles::MISSING_MO_FILES_DIR; } /** * @return \WPML\Collect\Support\Collection */ private function getProcessed() { return wpml_collect( $this->optionManager->get( self::OPTION_GROUP, self::OPTION_NAME, [] ) ) ->map( function ( $path ) { return basename( $path ); } ); } } MO/Generate/MultiSite/Condition.php 0000755 00000001012 14720402445 0013166 0 ustar 00 <?php namespace WPML\ST\MO\Generate\MultiSite; class Condition { /** * @return bool */ public function shouldRunWithAllSites() { return is_multisite() && ( $this->hasPostBodyParam() || is_super_admin() || defined( 'WP_CLI' ) ); } private function hasPostBodyParam() { $request_body = file_get_contents( 'php://input' ); if ( ! $request_body ) { return false; } $data = (array) json_decode( $request_body ); return isset( $data['runForAllSites'] ) && $data['runForAllSites']; } } MO/Generate/MultiSite/Executor.php 0000755 00000001660 14720402445 0013047 0 ustar 00 <?php namespace WPML\ST\MO\Generate\MultiSite; class Executor { const MAIN_SITE_ID = 1; /** * @param callable $callback * * @return \WPML\Collect\Support\Collection */ public function withEach( $callback ) { $applyCallback = function( $siteId ) use ( $callback ) { switch_to_blog( $siteId ); return [ $siteId, $callback() ]; }; $initialBlogId = get_current_blog_id(); $result = $this->getSiteIds()->map( $applyCallback ); switch_to_blog( $initialBlogId ); return $result; } /** * @return \WPML\Collect\Support\Collection */ public function getSiteIds() { return \wpml_collect( get_sites( [ 'number' => PHP_INT_MAX ] ) )->pluck( 'id' ); } /** * @param int $siteId * @param callable $callback * * @return mixed */ public function executeWith( $siteId, callable $callback ) { switch_to_blog( $siteId ); $result = $callback(); restore_current_blog(); return $result; } } MO/Generate/Process/ProcessFactory.php 0000755 00000003530 14720402445 0013714 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; use WPML\ST\MO\File\ManagerFactory; use WPML\ST\MO\Generate\MultiSite\Condition; use WPML\Utils\Pager; use function WPML\Container\make; class ProcessFactory { const FILES_PAGER = 'wpml-st-mo-generate-files-pager'; const FILES_PAGE_SIZE = 20; const SITES_PAGER = 'wpml-st-mo-generate-sites-pager'; /** @var Condition */ private $multiSiteCondition; /** * @param Condition $multiSiteCondition */ public function __construct( Condition $multiSiteCondition = null ) { $this->multiSiteCondition = $multiSiteCondition ?: new Condition(); } /** * @return Process * @throws \WPML\Auryn\InjectionException */ public function create() { $singleSiteProcess = self::createSingle(); if ( $this->multiSiteCondition->shouldRunWithAllSites() ) { return make( MultiSiteProcess::class, [ ':singleSiteProcess' => $singleSiteProcess, ':pager' => new Pager( self::SITES_PAGER, 1 ) ] ); } else { return $singleSiteProcess; } } /** * @param bool $isBackgroundProcess * * @return SingleSiteProcess * @throws \WPML\Auryn\InjectionException */ public static function createSingle( $isBackgroundProcess = false ) { return make( SingleSiteProcess::class, [ ':pager' => new Pager( self::FILES_PAGER, self::FILES_PAGE_SIZE ), ':manager' => ManagerFactory::create(), ':migrateAdminTexts' => \WPML_Admin_Texts::get_migrator(), ':status' => self::createStatus( $isBackgroundProcess ), ] ); } /** * @param bool $isBackgroundProcess * * @return mixed|\Mockery\MockInterface|Status * @throws \WPML\Auryn\InjectionException */ public static function createStatus( $isBackgroundProcess = false ) { return make( Status::class, [ ':optionPrefix' => $isBackgroundProcess ? Status::class . '_background' : null ] ); } } MO/Generate/Process/SingleSiteProcess.php 0000755 00000004655 14720402445 0014364 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; use WPML\ST\MO\File\Manager; use WPML\ST\MO\Generate\DomainsAndLanguagesRepository; use WPML\Utils\Pager; class SingleSiteProcess implements Process { CONST TIMEOUT = 5; /** @var DomainsAndLanguagesRepository */ private $domainsAndLanguagesRepository; /** @var Manager */ private $manager; /** @var Status */ private $status; /** @var Pager */ private $pager; /** @var callable */ private $migrateAdminTexts; /** * @param DomainsAndLanguagesRepository $domainsAndLanguagesRepository * @param Manager $manager * @param Status $status * @param Pager $pager * @param callable $migrateAdminTexts */ public function __construct( DomainsAndLanguagesRepository $domainsAndLanguagesRepository, Manager $manager, Status $status, Pager $pager, callable $migrateAdminTexts ) { $this->domainsAndLanguagesRepository = $domainsAndLanguagesRepository; $this->manager = $manager; $this->status = $status; $this->pager = $pager; $this->migrateAdminTexts = $migrateAdminTexts; } public function runAll() { call_user_func( $this->migrateAdminTexts ); $this->getDomainsAndLanguages()->each( function ( $row ) { $this->manager->add( $row->domain, $row->locale ); } ); $this->status->markComplete(); } /** * @return int Remaining */ public function runPage() { if ( $this->pager->getProcessedCount() === 0 ) { call_user_func( $this->migrateAdminTexts ); } $domains = $this->getDomainsAndLanguages();; $remaining = $this->pager->iterate( $domains, function ( $row ) { $this->manager->add( $row->domain, $row->locale ); return true; }, self::TIMEOUT ); if ( $remaining === 0 ) { $this->status->markComplete(); } return $remaining; } public function getPagesCount() { if ( $this->status->isComplete() ) { return 0; } $domains = $this->getDomainsAndLanguages(); if ( $domains->count() === 0 ) { $this->status->markComplete(); } return $domains->count(); } private function getDomainsAndLanguages() { return DomainsAndLanguagesRepository::hasTranslationFilesTable() ? $this->domainsAndLanguagesRepository->get() : wpml_collect(); } /** * @return bool */ public function isCompleted() { return $this->getPagesCount() === 0; } } MO/Generate/Process/Status.php 0000755 00000003074 14720402445 0012234 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; class Status { /** @var \SitePress */ private $sitepress; /** @var string */ private $optionPrefix; /** * @param \SitePress $sitepress * @param string|null $optionPrefix */ public function __construct( \SitePress $sitepress, $optionPrefix = null ) { $this->sitepress = $sitepress; $this->optionPrefix = $optionPrefix ?: self::class; } /** * @param bool $allSites */ public function markComplete( $allSites = false ) { $settings = $this->sitepress->get_setting( 'st', [] ); $settings[ $this->getOptionName( $allSites ) ] = true; $this->sitepress->set_setting( 'st', $settings, true ); } /** * @param bool $allSites */ public function markIncomplete( $allSites = false ) { $settings = $this->sitepress->get_setting( 'st', [] ); unset( $settings[ $this->getOptionName( $allSites ) ] ); $this->sitepress->set_setting( 'st', $settings, true ); } public function markIncompleteForAll() { $this->markIncomplete( true ); } /** * @return bool */ public function isComplete() { $st_settings = $this->sitepress->get_setting( 'st', [] ); return isset( $st_settings[ $this->getOptionName( false ) ] ); } /** * @return bool */ public function isCompleteForAllSites() { $st_settings = $this->sitepress->get_setting( 'st', [] ); return isset( $st_settings[ $this->getOptionName( true ) ] ); } private function getOptionName( $allSites ) { return $allSites ? $this->optionPrefix . '_has_run_all_sites' : $this->optionPrefix . '_has_run'; } } MO/Generate/Process/MultiSiteProcess.php 0000755 00000004611 14720402445 0014225 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; use WPML\Utils\Pager; use WPML\ST\MO\Generate\MultiSite\Executor; class MultiSiteProcess implements Process { /** @var Executor */ private $multiSiteExecutor; /** @var SingleSiteProcess */ private $singleSiteProcess; /** @var Status */ private $status; /** @var Pager */ private $pager; /** @var SubSiteValidator */ private $subSiteValidator; /** * @param Executor $multiSiteExecutor * @param SingleSiteProcess $singleSiteProcess * @param Status $status * @param Pager $pager * @param SubSiteValidator $subSiteValidator */ public function __construct( Executor $multiSiteExecutor, SingleSiteProcess $singleSiteProcess, Status $status, Pager $pager, SubSiteValidator $subSiteValidator ) { $this->multiSiteExecutor = $multiSiteExecutor; $this->singleSiteProcess = $singleSiteProcess; $this->status = $status; $this->pager = $pager; $this->subSiteValidator = $subSiteValidator; } public function runAll() { $this->multiSiteExecutor->withEach( $this->runIfSetupComplete( [ $this->singleSiteProcess, 'runAll' ] ) ); $this->status->markComplete( true ); } /** * @return int Is completed */ public function runPage() { $remaining = $this->pager->iterate( $this->multiSiteExecutor->getSiteIds(), function ( $siteId ) { return $this->multiSiteExecutor->executeWith( $siteId, $this->runIfSetupComplete( function () { // no more remaining pages which means that process is done return $this->singleSiteProcess->runPage() === 0; } ) ); } ); if ( $remaining === 0 ) { $this->multiSiteExecutor->executeWith( Executor::MAIN_SITE_ID, function () { $this->status->markComplete( true ); } ); } return $remaining; } /** * @return int */ public function getPagesCount() { $isCompletedForAllSites = $this->multiSiteExecutor->executeWith( Executor::MAIN_SITE_ID, [ $this->status, 'isCompleteForAllSites' ] ); if ( $isCompletedForAllSites ) { return 0; } return $this->multiSiteExecutor->getSiteIds()->count(); } /** * @return bool */ public function isCompleted() { return $this->getPagesCount() === 0; } private function runIfSetupComplete( $callback ) { return function () use ( $callback ) { if ( $this->subSiteValidator->isValid() ) { return $callback(); } return true; }; } } MO/Generate/Process/Process.php 0000755 00000000433 14720402445 0012363 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; interface Process { public function runAll(); /** * @return int Remaining */ public function runPage(); /** * @return int */ public function getPagesCount(); /** * @return bool */ public function isCompleted(); } MO/Generate/Process/SubSiteValidator.php 0000755 00000000664 14720402445 0014177 0 ustar 00 <?php namespace WPML\ST\MO\Generate\Process; use function WPML\Container\make; class SubSiteValidator { /** * @return bool */ public function isValid() { global $sitepress; return $sitepress->is_setup_complete() && $this->hasTranslationFilesTable(); } /** * @return bool */ private function hasTranslationFilesTable() { return make( \WPML_Upgrade_Schema::class )->does_table_exist( 'icl_mo_files_domains' ); } } MO/Hooks/PreloadThemeMoFile.php 0000755 00000003750 14720402445 0012332 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\Collect\Support\Collection; use WPML\ST\Gettext\AutoRegisterSettings; use function WPML\Container\make; class PreloadThemeMoFile implements \IWPML_Action { const SETTING_KEY = 'theme_localization_load_textdomain'; const SETTING_DISABLED = 0; const SETTING_ENABLED = 1; const SETTING_ENABLED_FOR_LOAD_TEXT_DOMAIN = 2; /** @var \SitePress */ private $sitepress; /** @var \wpdb */ private $wpdb; public function __construct( \SitePress $sitepress, \wpdb $wpdb ) { $this->sitepress = $sitepress; $this->wpdb = $wpdb; } public function add_hooks() { $domainsSetting = $this->sitepress->get_setting( 'gettext_theme_domain_name' ); $domains = empty( $domainsSetting ) ? [] : explode( ',', $domainsSetting ); $domains = \wpml_collect( array_map( 'trim', $domains ) ); $loadTextDomainSetting = (int) $this->sitepress->get_setting( static::SETTING_KEY ); $isEnabled = $loadTextDomainSetting === static::SETTING_ENABLED; if ( $loadTextDomainSetting === static::SETTING_ENABLED_FOR_LOAD_TEXT_DOMAIN ) { /** @var AutoRegisterSettings $autoStrings */ $autoStrings = make( AutoRegisterSettings::class ); $isEnabled = $autoStrings->isEnabled(); } if ( $isEnabled && $domains->count() ) { $this->getMOFilesByDomainsAndLocale( $domains, get_locale() )->map( function ( $fileResult ) { load_textdomain( $fileResult->domain, $fileResult->file_path ); } ); } } /** * @param Collection<string> $domains * @param string $locale * * @return Collection */ private function getMOFilesByDomainsAndLocale( $domains, $locale ) { $domainsClause = wpml_prepare_in( $domains->toArray(), '%s' ); $sql = " SELECT file_path, domain FROM {$this->wpdb->prefix}icl_mo_files_domains WHERE domain IN ({$domainsClause}) AND file_path REGEXP %s "; /** @var string $sql */ $sql = $this->wpdb->prepare( $sql, '((\\/|-)' . $locale . '(\\.|-))+' ); return \wpml_collect( $this->wpdb->get_results( $sql ) ); } } MO/Hooks/LoadTextDomain.php 0000755 00000007440 14720402445 0011541 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\ST\MO\File\Manager; use WPML\ST\MO\LoadedMODictionary; use WPML_ST_Translations_File_Locale; use function WPML\FP\partial; class LoadTextDomain implements \IWPML_Action { const PRIORITY_OVERRIDE = 10; /** @var Manager $file_manager */ private $file_manager; /** @var WPML_ST_Translations_File_Locale $file_locale */ private $file_locale; /** @var LoadedMODictionary $loaded_mo_dictionary */ private $loaded_mo_dictionary; /** @var array $loaded_domains */ private $loaded_domains = []; public function __construct( Manager $file_manager, WPML_ST_Translations_File_Locale $file_locale, LoadedMODictionary $loaded_mo_dictionary ) { $this->file_manager = $file_manager; $this->file_locale = $file_locale; $this->loaded_mo_dictionary = $loaded_mo_dictionary; } public function add_hooks() { $this->reloadAlreadyLoadedMOFiles(); add_filter( 'override_load_textdomain', [ $this, 'overrideLoadTextDomain' ], 10, 3 ); add_filter( 'override_unload_textdomain', [ $this, 'overrideUnloadTextDomain' ], 10, 2 ); add_action( 'wpml_language_has_switched', [ $this, 'languageHasSwitched' ] ); } /** * When a MO file is loaded, we override the process to load * the custom MO file before. * * That way, the custom MO file will be merged into the subsequent * native MO files and the custom MO translations will always * overwrite the native ones. * * This gives us the ability to build partial custom MO files * with only the modified translations. * * @param bool $override Whether to override the .mo file loading. Default false. * @param string $domain Text domain. Unique identifier for retrieving translated strings. * @param string $mofile Path to the MO file. * * @return bool */ public function overrideLoadTextDomain( $override, $domain, $mofile ) { if ( ! $mofile ) { return $override; } if ( ! $this->isCustomMOLoaded( $domain ) ) { remove_filter( 'override_load_textdomain', [ $this, 'overrideLoadTextDomain' ], 10 ); $locale = $this->file_locale->get( $mofile, $domain ); $this->loadCustomMOFile( $domain, $mofile, $locale ); add_filter( 'override_load_textdomain', [ $this, 'overrideLoadTextDomain' ], 10, 3 ); } $this->loaded_mo_dictionary->addFile( $domain, $mofile ); return $override; } /** * @param bool $override * @param string $domain * * @return bool */ public function overrideUnloadTextDomain( $override, $domain ) { $key = array_search( $domain, $this->loaded_domains ); if ( false !== $key ) { unset( $this->loaded_domains[ $key ] ); } return $override; } /** * @param string $domain * * @return bool */ private function isCustomMOLoaded( $domain ) { return in_array( $domain, $this->loaded_domains, true ); } private function loadCustomMOFile( $domain, $mofile, $locale ) { $wpml_mofile = $this->file_manager->get( $domain, $locale ); if ( $wpml_mofile && $wpml_mofile !== $mofile ) { load_textdomain( $domain, $wpml_mofile ); } $this->setCustomMOLoaded( $domain ); } private function reloadAlreadyLoadedMOFiles() { $this->loaded_mo_dictionary->getEntities()->each( function ( $entity ) { unload_textdomain( $entity->domain ); $locale = $this->file_locale->get( $entity->mofile, $entity->domain ); $this->loadCustomMOFile( $entity->domain, $entity->mofile, $locale ); if ( class_exists( '\WP_Translation_Controller' ) ) { // WP 6.5 - passing locale load_textdomain( $entity->domain, $entity->mofile, $locale ); } else { load_textdomain($entity->domain, $entity->mofile); } } ); } /** * @param string $domain */ private function setCustomMOLoaded( $domain ) { $this->loaded_domains[] = $domain; } public function languageHasSwitched() { $this->loaded_domains = []; } } MO/Hooks/StringsLanguageChanged.php 0000755 00000002602 14720402445 0013227 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\ST\MO\File\Manager; use WPML\ST\MO\Generate\DomainsAndLanguagesRepository; use function WPML\FP\pipe; class StringsLanguageChanged implements \IWPML_Action { private $domainsAndLanguageRepository; private $manager; private $getDomainsByStringIds; /** * @param DomainsAndLanguagesRepository $domainsAndLanguageRepository * @param Manager $manager * @param callable $getDomainsByStringIds */ public function __construct( DomainsAndLanguagesRepository $domainsAndLanguageRepository, Manager $manager, callable $getDomainsByStringIds ) { $this->domainsAndLanguageRepository = $domainsAndLanguageRepository; $this->manager = $manager; $this->getDomainsByStringIds = $getDomainsByStringIds; } public function add_hooks() { add_action( 'wpml_st_language_of_strings_changed', [ $this, 'regenerateMOFiles' ] ); } public function regenerateMOFiles( array $strings ) { $stringDomains = call_user_func( $this->getDomainsByStringIds, $strings ); $this->domainsAndLanguageRepository ->get() ->filter( pipe( Obj::prop( 'domain' ), Lst::includes( Fns::__, $stringDomains ) ) ) ->each( function ( $domainLangPair ) { $this->manager->add( $domainLangPair->domain, $domainLangPair->locale ); } ); } } MO/Hooks/DetectPrematurelyTranslatedStrings.php 0000755 00000006103 14720402445 0015716 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\ST\Gettext\Settings; class DetectPrematurelyTranslatedStrings implements \IWPML_Action { /** @var string[] */ private $domains = []; /** @var string[] */ private $preloadedDomains = []; /** @var \SitePress */ private $sitepress; /** @var Settings */ private $gettextHooksSettings; /** * @param \SitePress $sitepress */ public function __construct( \SitePress $sitepress, Settings $settings ) { $this->sitepress = $sitepress; $this->gettextHooksSettings = $settings; } /** * Init gettext hooks. */ public function add_hooks() { if ( $this->gettextHooksSettings->isAutoRegistrationEnabled() ) { $domains = $this->sitepress->get_setting( 'gettext_theme_domain_name' ); $this->preloadedDomains = array_filter( array_map( 'trim', explode( ',', $domains ) ) ); add_filter( 'gettext', [ $this, 'gettext_filter' ], 9, 3 ); add_filter( 'gettext_with_context', [ $this, 'gettext_with_context_filter' ], 1, 4 ); add_filter( 'ngettext', [ $this, 'ngettext_filter' ], 9, 5 ); add_filter( 'ngettext_with_context', [ $this, 'ngettext_with_context_filter' ], 9, 6 ); add_filter( 'override_load_textdomain', [ $this, 'registerDomainToPreloading' ], 10, 2 ); } } /** * @param string $translation * @param string $text * @param string|array $domain * * @return string */ public function gettext_filter( $translation, $text, $domain ) { $this->registerDomain( $domain ); return $translation; } /** * @param string $translation * @param string $text * @param string $context * @param string $domain * * @return string */ public function gettext_with_context_filter( $translation, $text, $context, $domain ) { $this->registerDomain( $domain ); return $translation; } /** * @param string $translation * @param string $single * @param string $plural * @param string $number * @param string|array $domain * * @return string */ public function ngettext_filter( $translation, $single, $plural, $number, $domain ) { $this->registerDomain( $domain ); return $translation; } /** * @param string $translation * @param string $single * @param string $plural * @param string $number * @param string $context * @param string $domain * * @return string * */ public function ngettext_with_context_filter( $translation, $single, $plural, $number, $context, $domain ) { $this->registerDomain( $domain ); return $translation; } private function registerDomain( $domain ) { if ( ! in_array( $domain, $this->preloadedDomains ) ) { $this->domains[ $domain ] = true; } } public function registerDomainToPreloading( $plugin_override, $domain ) { if ( array_key_exists( $domain, $this->domains ) && ! in_array( $domain, $this->preloadedDomains, true ) ) { $this->preloadedDomains[] = $domain; $this->sitepress->set_setting( 'gettext_theme_domain_name', implode( ',', array_unique( $this->preloadedDomains ) ) ); $this->sitepress->save_settings(); } return $plugin_override; } } MO/Hooks/Sync.php 0000755 00000002172 14720402445 0007576 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\ST\TranslationFile\Sync\FileSync; class Sync implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var FileSync */ private $fileSync; /** @var callable */ private $useFileSynchronization; public function __construct( FileSync $fileSync, callable $useFileSynchronization ) { $this->fileSync = $fileSync; $this->useFileSynchronization = $useFileSynchronization; } public function add_hooks() { if ( call_user_func( $this->useFileSynchronization ) ) { add_filter( 'override_load_textdomain', [ $this, 'syncCustomMoFileOnLoadTextDomain' ], LoadTextDomain::PRIORITY_OVERRIDE - 1, 3 ); } } public function syncFile( $domain, $moFile ) { if ( call_user_func( $this->useFileSynchronization ) ) { $this->fileSync->sync( $moFile, $domain ); } } /** * @param bool $override * @param string $domain * @param string $moFile * * @return bool */ public function syncCustomMoFileOnLoadTextDomain( $override, $domain, $moFile ) { $this->fileSync->sync( $moFile, $domain ); return $override; } } MO/Hooks/CustomTextDomains.php 0000755 00000011201 14720402445 0012305 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\FP\Lst; use WPML\ST\MO\File\Manager; use WPML\ST\MO\JustInTime\MO; use WPML\ST\MO\LoadedMODictionary; use WPML\ST\Storage\StoragePerLanguageInterface; use WPML\ST\TranslationFile\Domains; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; use WPML_Locale; class CustomTextDomains implements \IWPML_Action { const CACHE_ID = 'wpml-st-custom-mo-files'; const CACHE_ALL_LOCALES = 'locales'; const CACHE_KEY_DOMAINS = 'domains'; const CACHE_KEY_FILES = 'files'; /** @var Manager $manager */ private $manager; /** @var Domains $domains */ private $domains; /** @var LoadedMODictionary $loadedDictionary */ private $loadedDictionary; /** @var StoragePerLanguageInterface */ private $cache; /** @var WPML_Locale */ private $locale; /** @var callable */ private $syncMissingFile; /** @var string[] $loaded_custom_domains */ private $loaded_custom_domains = []; public function __construct( Manager $file_manager, Domains $domains, LoadedMODictionary $loadedDictionary, StoragePerLanguageInterface $cache, WPML_Locale $locale, callable $syncMissingFile = null ) { $this->manager = $file_manager; $this->domains = $domains; $this->loadedDictionary = $loadedDictionary; $this->cache = $cache; $this->locale = $locale; $this->syncMissingFile = $syncMissingFile ?: function () {}; // Flush cache when a custom MO file is written, removed or updated. add_action( 'wpml_st_translation_file_written', [ $this, 'clear_cache' ], 10, 0 ); add_action( 'wpml_st_translation_file_removed', [ $this, 'clear_cache' ], 10, 0 ); // The filename could be changed on update, that's why we need to clear the cache. add_action( 'wpml_st_translation_file_updated', [ $this, 'clear_cache' ], 10, 0 ); } public function clear_cache() { $locales = $this->cache->get( self::CACHE_ALL_LOCALES ); if ( ! is_array( $locales ) ) { // No cache. return; } // Clear cache for all locales, because the domains list will change // for all languages when a the first custom translation file is written // for a new domain (even if the other locales don't get that file). foreach ( $locales as $locale ) { $this->cache->delete( $locale ); } // Also flush the list of cached locales. $this->cache->delete( self::CACHE_ALL_LOCALES ); } public function add_hooks() { $this->init_custom_text_domains(); } public function init_custom_text_domains( $locale = null ) { $locale = $locale ?: get_locale(); $addJitMoToL10nGlobal = pipe( Lst::nth( 0 ), function ( $domain ) use ( $locale ) { // Following unset is important because WordPress is setting their // static $noop_translation variable by reference. Without unset it // would become our JustInTime/MO and is used for other domains. // @see wpmldev-2508 unset( $GLOBALS['l10n'][ $domain ] ); $this->loaded_custom_domains[] = $domain; $GLOBALS['l10n'][ $domain ] = new MO( $this->loadedDictionary, $locale, $domain ); } ); $getDomainPathTuple = function ( $domain ) use ( $locale ) { return [ $domain, $this->manager->getFilepath( $domain, $locale ) ]; }; $cache = $this->cache->get( $locale ); // Get domains. if ( isset( $cache[ self::CACHE_KEY_DOMAINS ] ) ) { // Cache hit. $domains = \wpml_collect( $cache[ self::CACHE_KEY_DOMAINS ] ); } else { // No cache for site domains. $cache_update_required = true; $domains = \wpml_collect( $this->domains->getCustomMODomains() ); } $files = $domains->map( $getDomainPathTuple ) ->each( spreadArgs( $this->syncMissingFile ) ) ->each( spreadArgs( [ $this->loadedDictionary, 'addFile' ] ) ); // Load local files. if ( isset( $cache[ self::CACHE_KEY_FILES ] ) ) { // Cache hit. $localeFiles = \wpml_collect( $cache[ self::CACHE_KEY_FILES ] ); } else { // No cache for this locale readable custom .mo files. $cache_update_required = true; $isReadableFile = function ( $domainAndFilePath ) { return is_readable( $domainAndFilePath[1] ); }; $localeFiles = $files->filter( $isReadableFile ); } if ( isset( $cache_update_required ) ) { $this->cache->save( $locale, [ self::CACHE_KEY_DOMAINS => $domains->toArray(), self::CACHE_KEY_FILES => $localeFiles->toArray(), ] ); $cache_locales = $this->cache->get( self::CACHE_ALL_LOCALES ); $cache_locales = is_array( $cache_locales ) ? $cache_locales : []; if ( ! in_array( $locale, $cache_locales, true ) ) { $cache_locales[] = $locale; $this->cache->save( self::CACHE_ALL_LOCALES, array_unique( $cache_locales ) ); } } $localeFiles->each( $addJitMoToL10nGlobal ); } } MO/Hooks/Factory.php 0000755 00000003013 14720402445 0010264 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use IWPML_Action; use WPML\ST\DB\Mappers\DomainsRepository; use WPML\ST\MO\File\ManagerFactory; use WPML\ST\Storage\WpTransientPerLanguage; use WPML\ST\TranslationFile\Sync\FileSync; use WPML\ST\TranslationFile\UpdateHooksFactory; use WPML\ST\TranslationFile\Hooks; use function WPML\Container\make; class Factory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { /** * Create hooks. * * @return IWPML_Action[] * @throws \WPML\Auryn\InjectionException Auryn Exception. */ public function create() { $manager = ManagerFactory::create(); $moFileSync = make( Sync::class, [ ':fileSync' => make( FileSync::class, [ ':manager' => ManagerFactory::create() ] ), ':useFileSynchronization' => [ Hooks::class, 'useFileSynchronization' ], ] ); return [ UpdateHooksFactory::create(), make( LoadTextDomain::class, [ ':file_manager' => $manager ] ), make( CustomTextDomains::class, [ ':file_manager' => $manager, ':cache' => make( WpTransientPerLanguage::class, [ ':id' => CustomTextDomains::CACHE_ID, ] ), ':syncMissingFile' => [ $moFileSync, 'syncFile' ], ] ), make( LanguageSwitch::class ), make( LoadMissingMOFiles::class ), make( PreloadThemeMoFile::class ), make( DetectPrematurelyTranslatedStrings::class ), $moFileSync, make( StringsLanguageChanged::class, [ ':manager' => $manager, ':getDomainsByStringIds' => DomainsRepository::getByStringIds(), ] ), ]; } } MO/Hooks/LoadMissingMOFiles.php 0000755 00000012726 14720402445 0012320 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\Collect\Support\Collection; use WPML\ST\MO\Generate\MissingMOFile; use WPML\WP\OptionManager; use function WPML\Container\make; class LoadMissingMOFiles implements \IWPML_Action { const MISSING_MO_FILES_DIR = '/wpml/missing/'; const OPTION_GROUP = 'ST-MO'; const MISSING_MO_OPTION = 'missing-mo'; const TIMEOUT = 10; const WPML_VERSION_INTRODUCING_ST_MO_FLOW = '4.3.0'; /** * @var MissingMOFile */ private $generateMissingMoFile; /** * @var OptionManager */ private $optionManager; /** @var \WPML_ST_Translations_File_Dictionary_Storage_Table */ private $moFilesDictionary; public function __construct( MissingMOFile $generateMissingMoFile, OptionManager $optionManager, \WPML_ST_Translations_File_Dictionary_Storage_Table $moFilesDictionary ) { $this->generateMissingMoFile = $generateMissingMoFile; $this->optionManager = $optionManager; $this->moFilesDictionary = $moFilesDictionary; } public function add_hooks() { if ( $this->wasWpmlInstalledPriorToMoFlowChanges() ) { add_filter( 'load_textdomain_mofile', [ $this, 'recordMissing' ], 10, 2 ); if ( $this->isThemeAndLocalizationPage() ) { // By now this complete feature is probably only required to load // generated files, but for the rare case that someone updates // from < 4.3.0 and has missing files, the genration will still // be triggered on the Themen and Plugins localization page. add_action( 'shutdown', [ $this, 'generateMissing' ] ); } } } /** * @param string $mofile * @param string $domain * * @return string */ public function recordMissing( $mofile, $domain ) { if ( strpos( $mofile, WP_LANG_DIR . '/themes/' ) === 0 ) { return $mofile; } if ( strpos( $mofile, WP_LANG_DIR . '/plugins/' ) === 0 ) { return $mofile; } $missing = $this->getMissing(); if ( self::isReadable( $mofile ) ) { if ( $missing->has( $domain ) ) { $this->saveMissing( $missing->forget( $domain ) ); } return $mofile; } // Light check to see if a file was already generated. $generatedFile = $this->getGeneratedFileName( $mofile, $domain ); if ( self::isReadable( $generatedFile ) && $this->moFilesDictionary->is_path_handled( $mofile, $domain ) ) { // The file exists AND the path is handled by ST. return $generatedFile; } if ( ! $this->isThemeAndLocalizationPage() ) { // All following code is for genarating missing files and that's // only happening on the Theme and Plugins localization page. // -> by now we should consider removing the generation completely. return $mofile; } // Heavy check to see if the file is handled by String Translation. if ( ! $this->moFilesDictionary->find( $mofile ) ) { // Not handled by String Translation. return $mofile; } if ( $this->generateMissingMoFile->isNotProcessed( $generatedFile ) ) { $this->saveMissing( $missing->put( $domain, $mofile ) ); } return $mofile; } public function generateMissing() { $lock = make( 'WPML\Utilities\Lock', [ ':name' => self::class ] ); $missing = $this->getMissing(); if ( $missing->count() && $lock->create() ) { $generate = function ( $pair ) { list( $domain, $mofile ) = $pair; $generatedFile = $this->getGeneratedFileName( $mofile, $domain ); $this->generateMissingMoFile->run( $generatedFile, $domain ); }; $unProcessed = $missing->assocToPair() ->eachWithTimeout( $generate, self::getTimeout() ) ->pairToAssoc(); $this->saveMissing( $unProcessed ); $lock->release(); } } public static function isReadable( $mofile ) { return is_readable( $mofile ); } /** * @return \WPML\Collect\Support\Collection */ private function getMissing() { return wpml_collect( $this->optionManager->get( self::OPTION_GROUP, self::MISSING_MO_OPTION, [] ) ); } /** * @param \WPML\Collect\Support\Collection $missing */ private function saveMissing( \WPML\Collect\Support\Collection $missing ) { $this->optionManager->set( self::OPTION_GROUP, self::MISSING_MO_OPTION, $missing->toArray() ); } public static function getTimeout() { return self::TIMEOUT; } /** * @return bool */ private function wasWpmlInstalledPriorToMoFlowChanges() { $wpml_start_version = \get_option( \WPML_Installation::WPML_START_VERSION_KEY, '0.0.0' ); return version_compare( $wpml_start_version, self::WPML_VERSION_INTRODUCING_ST_MO_FLOW, '<' ); } /** * @param string $mofile * @param string $domain * * @return string */ private function getGeneratedFileName( $mofile, $domain ) { $fileName = basename( $mofile ); if ( $this->isNonDefaultWithMissingDomain( $fileName, $domain ) ) { $fileName = $domain . '-' . $fileName; } return WP_LANG_DIR . self::MISSING_MO_FILES_DIR . $fileName; } /** * There's a fallback for theme that is looking for * this kind of file `wp-content/themes/hybrid/ru_RU.mo`. * We need to add the domain otherwise it collides with * the MO file for the default domain. * * @param string $fileName * @param string $domain * * @return bool */ private function isNonDefaultWithMissingDomain( $fileName, $domain ) { return 'default' !== $domain && preg_match( '/^[a-z]+_?[A-Z]*\.mo$/', $fileName ); } private function isThemeAndLocalizationPage() { global $sitepress; return $sitepress ->get_wp_api() ->is_core_page( 'theme-localization.php' ); } } MO/Hooks/LanguageSwitch.php 0000755 00000012617 14720402445 0011574 0 ustar 00 <?php namespace WPML\ST\MO\Hooks; use WPML\ST\MO\JustInTime\MOFactory; use WPML\ST\MO\WPLocaleProxy; use WPML\ST\Utils\LanguageResolution; class LanguageSwitch implements \IWPML_Action { /** @var MOFactory $jit_mo_factory */ private $jit_mo_factory; /** @var LanguageResolution $language_resolution */ private $language_resolution; /** @var null|string $current_locale */ private static $current_locale; /** @var array $globals_cache */ private static $globals_cache = []; public function __construct( LanguageResolution $language_resolution, MOFactory $jit_mo_factory ) { $this->language_resolution = $language_resolution; $this->jit_mo_factory = $jit_mo_factory; } public function add_hooks() { add_action( 'wpml_language_has_switched', [ $this, 'languageHasSwitched' ] ); } /** @param string $locale */ private function setCurrentLocale( $locale ) { self::$current_locale = $locale; } /** @return string */ public function getCurrentLocale() { return self::$current_locale; } public function languageHasSwitched() { $this->initCurrentLocale(); $new_locale = $this->language_resolution->getCurrentLocale(); $this->switchToLocale( $new_locale ); } public function initCurrentLocale() { if ( ! $this->getCurrentLocale() ) { add_filter( 'locale', [ $this, 'filterLocale' ], PHP_INT_MAX ); $this->setCurrentLocale( $this->language_resolution->getCurrentLocale() ); } } /** * This method will act as the WP Core function `switch_to_locale`, * but in a more efficient way. It will avoid to instantly load * the domains loaded in the previous locale. Instead, it will let * the domains be loaded via the "just in time" function. * * @param string $new_locale */ public function switchToLocale( $new_locale ) { if ( $new_locale === $this->getCurrentLocale() ) { return; } $this->updateCurrentGlobalsCache(); $this->changeWpLocale( $new_locale ); $this->changeMoObjects( $new_locale ); $this->setCurrentLocale( $new_locale ); } /** * @param string|null $locale */ public static function resetCache( $locale = null ) { self::$current_locale = $locale; self::$globals_cache = []; } /** * We need to take a new copy of the current locale globals * because some domains could have been added with the "just in time" * mechanism. */ private function updateCurrentGlobalsCache() { $cache = [ 'wp_locale' => isset( $GLOBALS['wp_locale'] ) ? $GLOBALS['wp_locale'] : null, 'l10n' => isset( $GLOBALS['l10n'] ) ? (array) $GLOBALS['l10n'] : [], ]; self::$globals_cache[ $this->getCurrentLocale() ] = $cache; } /** * @param string $new_locale */ private function changeWpLocale( $new_locale ) { if ( isset( self::$globals_cache[ $new_locale ]['wp_locale'] ) ) { $GLOBALS['wp_locale'] = self::$globals_cache[ $new_locale ]['wp_locale']; } else { /** * WPLocaleProxy is a wrapper of \WP_Locale with a kind of lazy initialization * to avoid loading the default domain for strings that * we don't use in this transitory language. */ $GLOBALS['wp_locale'] = new WPLocaleProxy(); } } /** * @param string $new_locale */ private function changeMoObjects( $new_locale ) { $this->resetTranslationAvailabilityInformation(); $cachedMoObjects = isset( self::$globals_cache[ $new_locale ]['l10n'] ) ? self::$globals_cache[ $new_locale ]['l10n'] : []; /** * The JustInTimeMO objects will replaced themselves on the fly * by the legacy default MO object if a string is translated. * This is because the function "_load_textdomain_just_in_time" * does not support the default domain and MO files outside the * "wp-content/languages" folder. */ $GLOBALS['l10n'] = $this->jit_mo_factory->get( $new_locale, $this->getUnloadedDomains(), $cachedMoObjects ); $this->setLocaleInWP65TranslationController( $new_locale ); } /** * @param string $new_locale * * @return void */ private function setLocaleInWP65TranslationController( $new_locale ) { if ( class_exists( \WP_Translation_Controller::class ) ) { \WP_Translation_Controller::get_instance()->set_locale( $new_locale ); } } private function resetTranslationAvailabilityInformation() { global $wp_textdomain_registry; if ( ! isset( $wp_textdomain_registry ) && function_exists( '_get_path_to_translation' ) ) { _get_path_to_translation( '', true ); } } /** * @param string $locale * * @return string */ public function filterLocale( $locale ) { $currentLocale = $this->getCurrentLocale(); if ( $currentLocale ) { return $currentLocale; } return $locale; } /** * @return array */ private function getUnloadedDomains() { $unloadedDomains = isset( $GLOBALS['l10n_unloaded'] ) ? array_keys( (array) $GLOBALS['l10n_unloaded'] ) : []; // WP 6.5 // When a text domain is unloaded, and later loaded again, WP will keep it in l10n_unloaded. // Prior to WP 6.5, there was a call ``unset( $l10n_unloaded[ $domain ] );`` just after // $l10n[ $domain ] was set. // This is a problem because the domain will be always excluded in our JITMO objects, // and will generate empty domains that should be loaded. if ( class_exists('\WP_Translation_Controller') ) { foreach ( $unloadedDomains as $key => $domain ) { if ( isset( $GLOBALS['l10n'][ $domain ] ) && ! $GLOBALS['l10n'][ $domain ] instanceof \NOOP_Translations ) { unset( $unloadedDomains[ $key ] ); } } } return $unloadedDomains; } } MO/WPLocaleProxy.php 0000755 00000002060 14720402445 0010303 0 ustar 00 <?php namespace WPML\ST\MO; use WP_Locale; class WPLocaleProxy { /** * @var WP_Locale|null $wp_locale */ private $wp_locale; /** * @param string $method * @param array $args * * @return mixed|null */ public function __call( $method, array $args ) { $callback = [ $this->getWPLocale(), $method ]; if ( method_exists( $this->getWPLocale(), $method ) && is_callable( $callback ) ) { return call_user_func_array( $callback , $args ); } return null; } /** * @param string $property * * @return bool */ public function __isset( $property ) { if ( property_exists( \WP_Locale::class, $property ) ) { return true; } return false; } /** * @param string $property * * @return mixed|null */ public function __get( $property ) { if ( $this->__isset( $property ) ) { return $this->getWPLocale()->{$property}; } return null; } /** * @return WP_Locale|null */ private function getWPLocale() { if ( ! $this->wp_locale ) { $this->wp_locale = new WP_Locale(); } return $this->wp_locale; } } MO/JustInTime/DefaultMO.php 0000755 00000000602 14720402445 0011446 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\MO\JustInTime; use WPML\ST\MO\LoadedMODictionary; class DefaultMO extends MO { public function __construct( LoadedMODictionary $loaded_mo_dictionary, $locale ) { parent::__construct( $loaded_mo_dictionary, $locale, 'default' ); } protected function loadTextDomain() { load_default_textdomain( $this->locale ); } } MO/JustInTime/MOFactory.php 0000755 00000002330 14720402445 0011471 0 ustar 00 <?php namespace WPML\ST\MO\JustInTime; use WPML\ST\MO\LoadedMODictionary; class MOFactory { /** @var LoadedMODictionary $loaded_mo_dictionary */ private $loaded_mo_dictionary; public function __construct( LoadedMODictionary $loaded_mo_dictionary ) { $this->loaded_mo_dictionary = $loaded_mo_dictionary; } /** * We need to rely on the loaded dictionary rather than `$GLOBALS['l10n]` * because a domain could have been loaded in a language that * does not have a MO file and so it won't be added to the `$GLOBALS['l10n]`. * * @param string $locale * @param array $excluded_domains * @param array $cachedMoObjects * * @return array */ public function get( $locale, array $excluded_domains, array $cachedMoObjects ) { $mo_objects = [ 'default' => isset( $cachedMoObjects['default'] ) ? $cachedMoObjects['default'] : new DefaultMO( $this->loaded_mo_dictionary, $locale ), ]; $excluded_domains[] = 'default'; foreach ( $this->loaded_mo_dictionary->getDomains( $excluded_domains ) as $domain ) { $mo_objects[ $domain ] = isset( $cachedMoObjects[ $domain ] ) ? $cachedMoObjects[ $domain ] : new MO( $this->loaded_mo_dictionary, $locale, $domain ); } return $mo_objects; } } MO/JustInTime/MO.php 0000755 00000005073 14720402445 0010150 0 ustar 00 <?php namespace WPML\ST\MO\JustInTime; use NOOP_Translations; use WPML\ST\MO\LoadedMODictionary; class MO extends \MO { /** @var LoadedMODictionary $loaded_mo_dictionary */ private $loaded_mo_dictionary; /** @var string $locale */ protected $locale; /** @var string $domain */ private $domain; /** @var bool $isLoading */ private $isLoading = false; /** * @param LoadedMODictionary $loaded_mo_dictionary * @param string $locale * @param string $domain */ public function __construct( LoadedMODictionary $loaded_mo_dictionary, $locale, $domain ) { $this->loaded_mo_dictionary = $loaded_mo_dictionary; $this->locale = $locale; $this->domain = $domain; } /** * @param string $singular * @param string $context * * @return string */ public function translate( $singular, $context = null ) { if ( $this->isLoading ) { return $singular; } $this->load(); return _x( $singular, $context, $this->domain ); } /** * @param string $singular * @param string $plural * @param int $count * @param string $context * * @return string */ public function translate_plural( $singular, $plural, $count, $context = null ) { if ( $this->isLoading ) { return $count > 1 ? $plural : $singular; } $this->load(); return _nx( $singular, $plural, $count, $context, $this->domain ); } private function load() { if ( $this->isLoaded() ) { // Abort as the domain just needs to be loaded once. return true; } $this->isLoading = true; $this->loadTextDomain(); if ( ! $this->isLoaded() ) { /** * If we could not load at least one MO file, * we need to assign the domain with a `NOOP_Translations` * object on the 'l10n' global. * This will prevent recursive loop on the current object. */ $GLOBALS['l10n'][ $this->domain ] = new NOOP_Translations(); } $this->isLoading = false; } protected function loadTextDomain() { $this->loaded_mo_dictionary ->getFiles( $this->domain, $this->locale ) ->each( function( $mofile ) { load_textdomain( $this->domain, $mofile, $this->locale ); } ); } /** * In some cases, themes or plugins are hooking on * `override_load_textdomain` so that the function * `load_textdomain` always returns `true` even * if the domain is not set on the global `$l10n`. * * That's why we need to check on the global `$l10n`. * * @return bool */ private function isLoaded() { return isset( $GLOBALS['l10n'][ $this->domain ] ) && ! $GLOBALS['l10n'][ $this->domain ] instanceof self; } } MO/LoadedMODictionary.php 0000755 00000007032 14720402445 0011251 0 ustar 00 <?php namespace WPML\ST\MO; use MO; use stdClass; use WPML\Collect\Support\Collection; class LoadedMODictionary { const PATTERN_SEARCH_LOCALE = '#([-]?)([a-z]+[_A-Z]*)(\.mo)$#i'; const LOCALE_PLACEHOLDER = '{LOCALE}'; /** @var array */ private $domainsCache = []; /** @var Collection $mo_files */ private $mo_files; public function __construct() { $this->mo_files = wpml_collect( [] ); $this->collectFilesAddedBeforeInstantiation(); } private function collectFilesAddedBeforeInstantiation() { if ( isset( $GLOBALS['l10n'] ) && is_array( $GLOBALS['l10n'] ) ) { wpml_collect( $GLOBALS['l10n'] )->each( function ( $mo, $domain ) { if ( $mo instanceof MO ) { $this->addFile( $domain, $mo->get_filename() ); } } ); } // WP 6.5 // We need to collect all files loaded in WP_Translation_Controller // at the moment of hooks registration. if ( class_exists('\WP_Translation_Controller') ) { $translationsController = \WP_Translation_Controller::get_instance(); $reflection = new \ReflectionClass( $translationsController ); $property = $reflection->getProperty( 'loaded_files' ); $property->setAccessible( true ); $loaded_files = $property->getValue( $translationsController ); foreach ( $loaded_files as $loaded_file => $loaded_file_data ) { $moFileName = str_replace('.l10n.php', '.mo', $loaded_file); $locale = array_keys($loaded_file_data)[0]; $locale_data = $loaded_file_data[$locale]; foreach ( $locale_data as $domain => $translationFile ) { $this->addFile( $domain, $moFileName ); } } } } /** * @param string $domain * @param string $mofile */ public function addFile( $domain, $mofile ) { $mofile_pattern = preg_replace( self::PATTERN_SEARCH_LOCALE, '$1' . self::LOCALE_PLACEHOLDER . '$3', $mofile, 1 ); $hash = md5( $domain . $mofile_pattern ); $entity = (object) [ 'domain' => $domain, 'mofile_pattern' => $mofile_pattern, 'mofile' => $mofile, ]; $this->mo_files->put( $hash, $entity ); $this->domainsCache = []; } /** * @param array $excluded * * @return array */ public function getDomains( array $excluded = [] ) { $key = md5( implode( $excluded ) ); if ( isset( $this->domainsCache[ $key ] ) ) { return $this->domainsCache[ $key ]; } $domains = $this->mo_files ->reject( $this->excluded( $excluded ) ) ->pluck( 'domain' ) ->unique()->values()->toArray(); $this->domainsCache[ $key ] = $domains; return $domains; } /** * @param string $domain * @param string $locale * * @return Collection */ public function getFiles( $domain, $locale ) { return $this->mo_files ->filter( $this->byDomain( $domain ) ) ->map( $this->getFile( $locale ) ) ->values(); } /** * @return Collection */ public function getEntities() { return $this->mo_files; } /** * @param array $excluded * * @return \Closure */ private function excluded( array $excluded ) { return function ( stdClass $entity ) use ( $excluded ) { return in_array( $entity->domain, $excluded, true ); }; } /** * @param string $domain * * @return \Closure */ private function byDomain( $domain ) { return function ( stdClass $entity ) use ( $domain ) { return $entity->domain === $domain; }; } /** * @param string $locale * * @return \Closure */ private function getFile( $locale ) { return function ( stdClass $entity ) use ( $locale ) { return str_replace( self::LOCALE_PLACEHOLDER, $locale, $entity->mofile_pattern ); }; } } class-wpml-st-verify-dependencies.php 0000755 00000002166 14720402445 0013723 0 ustar 00 <?php /** * Class WPML_ST_Verify_Dependencies * * Checks that the WPML Core plugin is installed and satisfies certain version * requirements */ class WPML_ST_Verify_Dependencies { /** * @param string|false $wpml_core_version */ function verify_wpml( $wpml_core_version ) { if ( false === $wpml_core_version ) { add_action( 'admin_notices', array( $this, 'notice_no_wpml', ) ); } elseif ( version_compare( $wpml_core_version, '3.5', '<' ) ) { add_action( 'admin_notices', array( $this, 'wpml_is_outdated' ) ); } } function notice_no_wpml() { ?> <div class="error wpml-admin-notice wpml-st-inactive wpml-inactive"> <p><?php esc_html_e( 'Please activate WPML Multilingual CMS to have WPML String Translation working.', 'wpml-string-translation' ); ?></p> </div> <?php } function wpml_is_outdated() { ?> <div class="message error wpml-admin-notice wpml-st-inactive wpml-outdated"> <p><?php esc_html_e( 'WPML String Translation is enabled but not effective, because WPML is outdated. Please update WPML first.', 'wpml-string-translation' ); ?></p> </div> <?php } } class-wpml-st-reset.php 0000755 00000003122 14720402445 0011106 0 ustar 00 <?php class WPML_ST_Reset { /** * @var wpdb */ private $wpdb; /** * @var WPML_ST_Settings */ private $settings; /** * @param wpdb $wpdb * @param WPML_ST_Settings $settings */ public function __construct( $wpdb, WPML_ST_Settings $settings = null ) { $this->wpdb = $wpdb; if ( ! $settings ) { $settings = new WPML_ST_Settings(); } $this->settings = $settings; } public function reset() { $this->settings->delete_settings(); // remove tables at the end to avoid errors in ST due to last actions invoked by hooks add_action( 'shutdown', array( $this, 'remove_db_tables' ), PHP_INT_MAX - 1 ); } public function remove_db_tables() { $blog_id = $this->retrieve_current_blog_id(); $is_multisite_reset = $blog_id && function_exists( 'is_multisite' ) && is_multisite(); if ( $is_multisite_reset ) { switch_to_blog( $blog_id ); } $table = $this->wpdb->prefix . 'icl_string_pages'; $this->wpdb->query( 'DROP TABLE IF EXISTS ' . $table ); $table = $this->wpdb->prefix . 'icl_string_urls'; $this->wpdb->query( 'DROP TABLE IF EXISTS ' . $table ); if ( $is_multisite_reset ) { restore_current_blog(); } } /** * @return int */ private function retrieve_current_blog_id() { $filtered_id = array_key_exists( 'id', $_POST ) ? filter_var( $_POST['id'], FILTER_SANITIZE_NUMBER_INT ) : false; $filtered_id = array_key_exists( 'id', $_GET ) && ! $filtered_id ? filter_var( $_GET['id'], FILTER_SANITIZE_NUMBER_INT ) : $filtered_id; $blog_id = false !== $filtered_id ? $filtered_id : $this->wpdb->blogid; return $blog_id; } } wpml-tm/class-wpml-st-tm-jobs.php 0000755 00000010437 14720402445 0012743 0 ustar 00 <?php class WPML_ST_TM_Jobs extends WPML_WPDB_User { /** * @param wpdb $wpdb * WPML_ST_TM_Jobs constructor. */ public function __construct( &$wpdb ) { parent::__construct( $wpdb ); add_filter( 'wpml_tm_jobs_union_table_sql', array( $this, 'jobs_union_table_sql_filter', ), 10, 2 ); add_filter( 'wpml_post_translation_original_table', array( $this, 'filter_tm_post_job_table', ), 10, 1 ); add_filter( 'wpml_st_job_state_pending', array( $this, 'tm_external_job_in_progress_filter', ), 10, 2 ); } /** * @param bool $in_progress_status * @param array|object $job_arr * * @return bool true if a job is in progress for the given arguments */ public function tm_external_job_in_progress_filter( $in_progress_status, $job_arr ) { $job_arr = (array) $job_arr; if ( isset( $job_arr['batch'] ) ) { $job_arr['batch'] = (array) $job_arr['batch']; } return isset( $job_arr['batch']['id'] ) && empty( $job_arr['cms_id'] ) && ! empty( $job_arr['id'] ) && ! empty( $job_arr['job_state'] ) && $job_arr['job_state'] === 'delivered' && $this->wpdb->get_var( $this->wpdb->prepare( " SELECT COUNT(*) FROM {$this->wpdb->prefix}icl_core_status ct JOIN {$this->wpdb->prefix}icl_string_status st ON ct.rid = st.rid JOIN {$this->wpdb->prefix}icl_string_translations t ON st.string_translation_id = t.id WHERE ct.rid = %d AND t.status < %d ", $job_arr['id'], ICL_TM_COMPLETE ) ) ? true : $in_progress_status; } public function jobs_union_table_sql_filter( $sql_statements, $args ) { return $this->get_jobs_table_sql_part( $sql_statements, $args ); } /** * @param string $table * * @return string */ public function filter_tm_post_job_table( $table ) { return " (SELECT ID, post_type FROM {$table} UNION ALL SELECT ID, NULL as post_type FROM {$this->wpdb->prefix}icl_string_packages) "; } private function get_jobs_table_sql_part( $sql_statements, $args ) { $sql_statements[] = "SELECT st.translator_id, st.id AS job_id, 'string' AS element_type_prefix, NULL AS post_type, st.batch_id FROM {$this->wpdb->prefix}icl_string_translations st JOIN {$this->wpdb->prefix}icl_strings s ON s.id = st.string_id " . $this->build_string_where( $args ); return $sql_statements; } /** * @param array $args * string_where * translator_id * from * to * status * service * * @return string */ private function build_string_where( $args ) { if ( isset( $args['overdue'] ) && $args['overdue'] ) { // We do not save "deadline" for string jobs so we just want to exclude them in such case return 'WHERE 1 = 0'; } $string_where = isset( $args['string_where'] ) ? $args['string_where'] : ''; $translator_id = isset( $args['translator_id'] ) ? $args['translator_id'] : ''; $from = isset( $args['from'] ) ? $args['from'] : ''; $to = isset( $args['to'] ) ? $args['to'] : ''; $status = isset( $args['status'] ) ? $args['status'] : ''; $service = isset( $args['service'] ) ? $args['service'] : false; $wheres = array(); $where_args = array(); if ( true === (bool) $from ) { $wheres[] = 's.language = %s'; $where_args[] = $from; } if ( true === (bool) $to ) { $wheres[] = 'st.language = %s'; $where_args[] = $to; } if ( $status ) { $wheres[] = 'st.status = %d'; $where_args[] = ICL_TM_IN_PROGRESS === (int) $status ? ICL_TM_WAITING_FOR_TRANSLATOR : $status; } $service = ! $service && is_numeric( $translator_id ) ? 'local' : $service; $service = 'local' !== $service && false !== strpos( $translator_id, 'ts-' ) ? substr( $translator_id, 3 ) : $service; if ( 'local' === $service ) { $wheres[] = 'st.translator_id = %s'; $where_args[] = $translator_id; } if ( false !== $service ) { $wheres[] = 'st.translation_service = %s'; $where_args[] = $service; } if ( count( $wheres ) > 0 && count( $wheres ) === count( $where_args ) ) { $where_sql = implode( ' AND ', $wheres ); /** @var string $sql */ $sql = $this->wpdb->prepare( $where_sql, $where_args ); $string_where = 'WHERE ' . $sql; } return $string_where; } } Storage/WpTransientPerLanguage.php 0000755 00000002565 14720402445 0013267 0 ustar 00 <?php namespace WPML\ST\Storage; class WpTransientPerLanguage implements StoragePerLanguageInterface { /** @var string $id */ private $id; /** @var int $lifetime Lifetime of storage in seconds.*/ private $lifetime = 86400; // 1 day. /** * @param string $id */ public function __construct( $id ) { $this->id = $id; } /** * @param string $lang * @return mixed Returns StorageInterface::NOTHING if there is no cache. */ public function get( $lang ) { $value = get_transient( $this->getName( $lang ) ); return false === $value ? StoragePerLanguageInterface::NOTHING : $value; } /** * @param string $lang * @param mixed $value */ public function save( $lang, $value ) { return set_transient( $this->getName( $lang ), $value, $this->lifetime ); } public function delete( $lang ) { return delete_transient( $this->getName( $lang ) ); } /** * Set the lifetime. * * @param int $lifetime */ public function setLifetime( $lifetime ) { $lifetime = (int) $lifetime; if ( $lifetime <= 0 ) { // Don't allow no expiration for lifetime. // Because WordPress sets 'autoload' to 'yes' for transients with // no expiration, which would be very bad for language based data. $lifetime = 86400 * 365; // 1 year. } $this->lifetime = $lifetime; } private function getName( $lang ) { return $this->id . '-' . $lang; } } Storage/StoragePerLanguageInterface.php 0000755 00000001076 14720402445 0014232 0 ustar 00 <?php namespace WPML\ST\Storage; interface StoragePerLanguageInterface { /* Defined value to allow for null/false values to be stored. */ const NOTHING = '___NOTHING___'; // Allow to store a value for all languages. const GLOBAL_GROUP = '__ALL_LANG__'; /** * @param string $lang * * @return mixed Returns self::NOTHING if there is nothing stored. */ public function get( $lang ); /** * @param string $lang * @param mixed $value */ public function save( $lang, $value ); /** * @param string $lang */ public function delete( $lang ); } class-wpml-st-admin-string.php 0000755 00000001360 14720402445 0012362 0 ustar 00 <?php /** * WPML_ST_Admin_String class */ class WPML_ST_Admin_String extends WPML_ST_String { /** * @var string $name */ private $name; /** * @var string $value */ private $value; /** * @param string $new_value */ public function update_value( $new_value ) { $this->fetch_name_and_value(); if ( md5( $this->value ) !== $this->name ) { $this->value = $new_value; $this->set_property( 'value', $new_value ); $this->update_status(); } } private function fetch_name_and_value() { if ( is_null( $this->name ) || is_null( $this->value ) ) { $res = $this->wpdb->get_row( 'SELECT name, value ' . $this->from_where_snippet() ); $this->name = $res->name; $this->value = $res->value; } } } package/class-domains.php 0000755 00000001446 14720402445 0011417 0 ustar 00 <?php namespace WPML\ST\Package; use WPML\Collect\Support\Collection; class Domains { /** @var \wpdb $wpdb */ private $wpdb; /** @var array $domains */ private $domains; public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string|null $domain * * @return bool */ public function isPackage( $domain ) { return $domain && $this->getDomains()->contains( $domain ); } /** * @see \WPML_Package::get_string_context_from_package for how the package domain is built * * @return Collection */ public function getDomains() { if ( ! $this->domains ) { $this->domains = wpml_collect( $this->wpdb->get_col( "SELECT CONCAT(kind_slug, '-', name) FROM {$this->wpdb->prefix}icl_string_packages" ) ); } return $this->domains; } } class-wpml-st-string.php 0000755 00000022241 14720402445 0011275 0 ustar 00 <?php /** * WPML_ST_String class * * Low level access to string in Database * * NOTE: Don't use this class to process a large amount of strings as it doesn't * do any caching, etc. */ class WPML_ST_String { protected $wpdb; private $string_id; /** @var string $language */ private $language; /** @var int $status */ private $status; /** @var array|null */ private $string_properties; /** * @param int $string_id * @param wpdb $wpdb */ public function __construct( $string_id, wpdb $wpdb ) { $this->wpdb = $wpdb; $this->string_id = $string_id; } /** * @return int */ public function string_id() { return $this->string_id; } /** * @return string|null */ public function get_language() { $this->language = $this->language ? $this->language : $this->wpdb->get_var( 'SELECT language ' . $this->from_where_snippet() . ' LIMIT 1' ); return $this->language; } /** * @return string */ public function get_value() { return $this->wpdb->get_var( 'SELECT value ' . $this->from_where_snippet() . ' LIMIT 1' ); } /** * @return int */ public function get_status() { $this->status = $this->status !== null ? $this->status : (int) $this->wpdb->get_var( 'SELECT status ' . $this->from_where_snippet() . ' LIMIT 1' ); return $this->status; } /** * @param string $language */ public function set_language( $language ) { if ( $language !== $this->get_language() ) { $this->language = $language; $this->set_property( 'language', $language ); $this->update_status(); } } /** * @return stdClass[] */ public function get_translation_statuses() { /** @var array<\stdClass> $statuses */ $statuses = $this->wpdb->get_results( 'SELECT language, status, mo_string ' . $this->from_where_snippet( true ) ); foreach ( $statuses as &$status ) { if ( ! empty( $status->mo_string ) ) { $status->status = ICL_TM_COMPLETE; } unset( $status->mo_string ); } return $statuses; } public function get_translations() { return $this->wpdb->get_results( 'SELECT * ' . $this->from_where_snippet( true ) ); } /** * For a bulk update of all strings: * * @see WPML_ST_Bulk_Update_Strings_Status::run */ public function update_status() { global $sitepress; /** * If the translation has a `mo_string`, the status of this * translation will be set to `WPML_TM_COMPLETE` */ $st = $this->get_translation_statuses(); if ( $st ) { $string_language = $this->get_language(); foreach ( $st as $t ) { if ( $string_language != $t->language ) { $translations[ $t->language ] = $t->status; } } $active_languages = $sitepress->get_active_languages(); // If has no translation or all translations are not translated if ( empty( $translations ) || max( $translations ) == ICL_TM_NOT_TRANSLATED ) { $status = ICL_TM_NOT_TRANSLATED; } elseif ( in_array( ICL_TM_WAITING_FOR_TRANSLATOR, $translations ) ) { $status = ICL_TM_WAITING_FOR_TRANSLATOR; } elseif ( in_array( ICL_TM_NEEDS_UPDATE, $translations ) ) { $status = ICL_TM_NEEDS_UPDATE; } elseif ( $this->has_less_translations_than_secondary_languages( $translations, $active_languages, $string_language ) ) { if ( in_array( ICL_TM_COMPLETE, $translations ) ) { $status = ICL_STRING_TRANSLATION_PARTIAL; } else { $status = ICL_TM_NOT_TRANSLATED; } } else { if ( in_array( ICL_TM_NOT_TRANSLATED, $translations ) ) { $status = ICL_STRING_TRANSLATION_PARTIAL; } else { $status = ICL_TM_COMPLETE; } } } else { $status = ICL_TM_NOT_TRANSLATED; } if ( $status !== $this->get_status() ) { $this->status = $status; $this->set_property( 'status', $status ); } return $status; } /** * @param array $translations * @param array $active_languages * @param string $string_language * * @return bool */ private function has_less_translations_than_secondary_languages( array $translations, array $active_languages, $string_language ) { $active_lang_codes = array_keys( $active_languages ); $translations_in_active_langs = array_intersect( $active_lang_codes, array_keys( $translations ) ); return count( $translations_in_active_langs ) < count( $active_languages ) - intval( in_array( $string_language, $active_lang_codes, true ) ); } /** * @param string $language * @param string|null|bool $value * @param int|bool|false $status * @param int|null $translator_id * @param string|int|null $translation_service * @param int|null $batch_id * * @return bool|int id of the translation */ public function set_translation( $language, $value = null, $status = false, $translator_id = null, $translation_service = null, $batch_id = null ) { if ( ! $this->exists() ) { return false; } /** @var $ICL_Pro_Translation WPML_Pro_Translation */ global $ICL_Pro_Translation; /** @var \stdClass $res */ $res = $this->wpdb->get_row( $this->wpdb->prepare( 'SELECT id, value, status ' . $this->from_where_snippet( true ) . ' AND language=%s', $language ) ); if ( isset( $res->status ) && $res->status == ICL_TM_WAITING_FOR_TRANSLATOR && is_null( $value ) && ! in_array( $status, [ ICL_TM_IN_PROGRESS, ICL_TM_NOT_TRANSLATED ] ) && ! $res->value ) { return false; } $translation_data = array(); if ( $translation_service ) { $translation_data['translation_service'] = $translation_service; } if ( $batch_id ) { $translation_data['batch_id'] = $batch_id; } if ( ! is_null( $value ) ) { $translation_data['value'] = $value; } if ( $translator_id ) { $translation_data['translator_id'] = $translator_id; } $translation_data = apply_filters( 'wpml_st_string_translation_before_save', $translation_data, $language, $this->string_id ); if ( $res ) { $st_id = $res->id; if ( $status ) { $translation_data['status'] = $status; } elseif ( $status === ICL_TM_NOT_TRANSLATED ) { $translation_data['status'] = ICL_TM_NOT_TRANSLATED; } if ( ! empty( $translation_data ) ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_string_translations', $translation_data, array( 'id' => $st_id ) ); $this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->prefix}icl_string_translations SET translation_date = NOW() WHERE id = %d", $st_id ) ); } } else { $translation_data = array_merge( $translation_data, array( 'string_id' => $this->string_id, 'language' => $language, 'status' => ( $status ? $status : ICL_TM_NOT_TRANSLATED ), ) ); $this->wpdb->insert( $this->wpdb->prefix . 'icl_string_translations', $translation_data ); $st_id = $this->wpdb->insert_id; } if ( $ICL_Pro_Translation ) { $ICL_Pro_Translation->fix_links_to_translated_content( $st_id, $language, 'string', [ 'value' => $value, 'string_id' => $this->string_id ] ); } icl_update_string_status( $this->string_id ); /** * @deprecated Use wpml_st_add_string_translation instead */ do_action( 'icl_st_add_string_translation', $st_id ); do_action( 'wpml_st_add_string_translation', $st_id ); return $st_id; } public function set_location( $location ) { $this->set_property( 'location', $location ); } /** * Set string wrap tag. * Used for SEO significance, can contain values as h1 ... h6, etc. * * @param string $wrap_tag Wrap tag. */ public function set_wrap_tag( $wrap_tag ) { $this->set_property( 'wrap_tag', $wrap_tag ); } /** * @param string $property * @param mixed $value */ protected function set_property( $property, $value ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', array( $property => $value ), array( 'id' => $this->string_id ) ); // Action called after string is updated. do_action( 'wpml_st_string_updated' ); } /** * @param bool $translations sets whether to use original or translations table * * @return string */ protected function from_where_snippet( $translations = false ) { if ( $translations ) { $id_column = 'string_id'; $table = 'icl_string_translations'; } else { $id_column = 'id'; $table = 'icl_strings'; } return $this->wpdb->prepare( "FROM {$this->wpdb->prefix}{$table} WHERE {$id_column}=%d", $this->string_id ); } public function exists() { $sql = $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE id = %d", $this->string_id ); return $this->wpdb->get_var( $sql ) > 0; } /** @return string|null */ public function get_context() { return $this->get_string_properties()->context; } /** @return string|null */ public function get_gettext_context() { return $this->get_string_properties()->gettext_context; } /** @return string|null */ public function get_name() { return $this->get_string_properties()->name; } private function get_string_properties() { if ( ! $this->string_properties ) { $row = $this->wpdb->get_row( 'SELECT name, context, gettext_context ' . $this->from_where_snippet() . ' LIMIT 1' ); $this->string_properties = $row ? $row : (object) [ 'name' => null, 'gettext_context' => null, 'context' => null, ]; } return $this->string_properties; } } package-translation/Hooks.php 0000755 00000001117 14720402445 0012274 0 ustar 00 <?php namespace WPML\ST\PackageTranslation; class Hooks implements \IWPML_Action, \IWPML_Backend_Action, \IWPML_Frontend_Action { public function add_hooks() { /** * @see Assign::stringsFromDomainToExistingPackage() */ add_action( 'wpml_st_assign_strings_from_domain_to_existing_package', Assign::class . '::stringsFromDomainToExistingPackage', 10, 2 ); /** * @see Assign::stringsFromDomainToNewPackage() */ add_action( 'wpml_st_assign_strings_from_domain_to_new_package', Assign::class . '::stringsFromDomainToNewPackage', 10, 2 ); } } package-translation/class-wpml-st-package-cleanup.php 0000755 00000001625 14720402445 0016741 0 ustar 00 <?php class WPML_ST_Package_Cleanup { private $existing_strings_in_package = array(); public function record_existing_strings( WPML_Package $package ) { $strings = $package->get_package_strings(); $this->existing_strings_in_package[ $package->ID ] = array(); if ( $strings ) { foreach ( $strings as $string ) { $this->existing_strings_in_package[ $package->ID ][ $string->id ] = $string; } } } public function record_register_string( WPML_Package $package, $string_id ) { unset( $this->existing_strings_in_package[ $package->ID ][ $string_id ] ); } public function delete_unused_strings( WPML_Package $package ) { if ( array_key_exists( $package->ID, $this->existing_strings_in_package ) ) { foreach ( $this->existing_strings_in_package[ $package->ID ] as $string_data ) { icl_unregister_string( $package->get_string_context_from_package(), $string_data->name ); } } } } package-translation/class-wpml-st-package-storage.php 0000755 00000005623 14720402445 0016760 0 ustar 00 <?php /** * Created by PhpStorm. * User: bruce * Date: 16/06/17 * Time: 10:57 AM */ class WPML_ST_Package_Storage { private $package_id; private $wpdb; /** * WPML_ST_Package_Storage constructor. * * @param int $package_id * @param \wpdb $wpdb */ public function __construct( $package_id, wpdb $wpdb ) { $this->package_id = $package_id; $this->wpdb = $wpdb; } /** * @param string $string_title * @param string $string_type * @param string $string_value * @param int $string_id * * @return bool */ public function update( $string_title, $string_type, $string_value, $string_id ) { $update_where = array( 'id' => $string_id ); $update_data = array( 'type' => $string_type, 'title' => $this->truncate_long_string( $string_title ), ); $type_or_title_updated = $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', $update_data, $update_where ); $update_data = array( 'string_package_id' => $this->package_id, 'value' => $string_value, ); $package_id_or_value_updated = $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', $update_data, $update_where ); if ( $package_id_or_value_updated ) { $this->set_string_status_to_needs_update_if_translated( $string_id ); $this->set_translations_to_needs_update(); } return $type_or_title_updated || $package_id_or_value_updated; } private function set_string_status_to_needs_update_if_translated( $string_id ) { $this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->prefix}icl_strings SET status=%d WHERE id=%d AND status<>%d", ICL_TM_NEEDS_UPDATE, $string_id, ICL_TM_NOT_TRANSLATED ) ); $this->wpdb->query( $this->wpdb->prepare( "UPDATE {$this->wpdb->prefix}icl_string_translations SET status=%d WHERE string_id=%d AND status<>%d", ICL_TM_NEEDS_UPDATE, $string_id, ICL_TM_NOT_TRANSLATED ) ); } private function set_translations_to_needs_update() { $translation_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT translation_id FROM {$this->wpdb->prefix}icl_translations WHERE trid = ( SELECT trid FROM {$this->wpdb->prefix}icl_translations WHERE element_id = %d AND element_type LIKE 'package%%' LIMIT 1 )", $this->package_id ) ); if ( ! empty( $translation_ids ) ) { $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_translation_status SET needs_update = 1 WHERE translation_id IN (" . wpml_prepare_in( $translation_ids, '%d' ) . ' ) ' ); } } private function truncate_long_string( $string ) { return strlen( $string ) > WPML_STRING_TABLE_NAME_CONTEXT_LENGTH ? mb_substr( $string, 0, WPML_STRING_TABLE_NAME_CONTEXT_LENGTH ) : $string; } } package-translation/Assign.php 0000755 00000002367 14720402445 0012445 0 ustar 00 <?php namespace WPML\ST\PackageTranslation; class Assign { /** * Assign all strings from specified domain to existing package. * * @param string $domainName * @param int $packageId * * @since 3.1.0 */ public static function stringsFromDomainToExistingPackage( $domainName, $packageId ) { global $wpdb; $wpdb->update( $wpdb->prefix . 'icl_strings', [ 'string_package_id' => $packageId ], [ 'context' => $domainName ] ); } /** * Assign all strings from specified domain to new package which is created on fly. * * @param string $domainName * @param array $packageData { * * @type string $kind_slug e.g. toolset_forms * @type string $kind e.g. "Toolset forms" * @type string $name e.g. "1" * @type string $title e.g. "Form 1" * @type string $edit_link URL to edit page of resource * @type string $view_link (Optional) Url to frontend view page of resource * @type int $page_id (optional) * } * @since 3.1.0 */ public static function stringsFromDomainToNewPackage( $domainName, array $packageData ) { $packageId = \WPML_Package_Helper::create_new_package( new \WPML_Package( $packageData ) ); if ( $packageId ) { self::stringsFromDomainToExistingPackage( $domainName, $packageId ); } } } class-wpml-st-strings.php 0000755 00000024537 14720402445 0011472 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_ST_Strings { const EMPTY_CONTEXT_LABEL = 'empty-context-domain'; /** * @var SitePress */ private $sitepress; /** * @var WP_Query */ private $wp_query; /** * @var wpdb */ private $wpdb; public function __construct( $sitepress, $wpdb, $wp_query ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->wp_query = $wp_query; } public function get_string_translations() { $string_translations = array(); $extra_cond = ''; $active_languages = $this->sitepress->get_active_languages(); // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification $status_filter = isset( $_GET['status'] ) ? (int) $_GET['status'] : false; $translation_priority = isset( $_GET['translation-priority'] ) ? $_GET['translation-priority'] : false; if ( false !== $status_filter ) { if ( ICL_TM_COMPLETE === $status_filter ) { $extra_cond .= ' AND s.status = ' . ICL_TM_COMPLETE; } elseif ( ICL_STRING_TRANSLATION_PARTIAL === $status_filter ) { $extra_cond .= ' AND s.status = ' . ICL_STRING_TRANSLATION_PARTIAL; } elseif ( ICL_TM_WAITING_FOR_TRANSLATOR !== $status_filter ) { $extra_cond .= ' AND s.status IN (' . ICL_STRING_TRANSLATION_PARTIAL . ',' . ICL_TM_NEEDS_UPDATE . ',' . ICL_TM_NOT_TRANSLATED . ',' . ICL_TM_WAITING_FOR_TRANSLATOR . ')'; } } if ( false !== $translation_priority ) { /** @var string $esc_translation_priority */ $esc_translation_priority = esc_sql( $translation_priority ); if ( __( 'Optional', 'sitepress' ) === $translation_priority ) { $extra_cond .= " AND s.translation_priority IN ( '" . $esc_translation_priority . "', '' ) "; } else { $extra_cond .= " AND s.translation_priority = '" . $esc_translation_priority . "' "; } } if ( array_key_exists( 'context', $_GET ) ) { $context = stripslashes( html_entity_decode( (string) Sanitize::stringProp( 'context', $_GET ), ENT_QUOTES ) ); if ( self::EMPTY_CONTEXT_LABEL === $context ) { $context = ''; } } if ( isset( $context ) ) { /** @phpstan-ignore-next-line */ $extra_cond .= $this->wpdb->prepare( ' AND s.context = %s ', $context ); } if ( $this->must_show_all_results() ) { $limit = 9999; $offset = 0; } else { $limit = $this->get_strings_per_page(); $_GET['paged'] = isset( $_GET['paged'] ) ? $_GET['paged'] : 1; $offset = ( $_GET['paged'] - 1 ) * $limit; } $search_filter = $this->get_search_filter(); $joins = []; $sql_query = ' WHERE 1 '; if ( ICL_TM_WAITING_FOR_TRANSLATOR === $status_filter ) { $sql_query .= ' AND s.status = ' . ICL_TM_WAITING_FOR_TRANSLATOR; } elseif ( $active_languages && $search_filter && ! $this->must_show_all_results() ) { $sql_query .= ' AND ' . $this->get_value_search_query(); $joins[] = "LEFT JOIN {$this->wpdb->prefix}icl_string_translations str ON str.string_id = s.id"; } if ( $this->is_troubleshooting_filter_enabled() ) { // This is a troubleshooting filter, it should display only String Translation elements that are in a wrong state. // @see wpmldev-1920 - Strings that are incorrectly duplicated when re-translating a post that was edited using native editor. $joins[] = ' LEFT JOIN ' . $this->wpdb->prefix . 'icl_string_packages sp ON sp.ID = s.string_package_id'; $joins[] = ' INNER JOIN ' . $this->wpdb->prefix . 'icl_translate it ON it.field_data_translated = sp.name'; $sql_query .=' AND it.field_type="original_id"'; } $res = $this->get_results( $sql_query, $extra_cond, $offset, $limit, $joins ); if ( $res ) { $extra_cond = ''; if ( isset( $_GET['translation_language'] ) ) { /** @var string $translation_language */ $translation_language = esc_sql( $_GET['translation_language'] ); $extra_cond .= " AND language='" . $translation_language . "'"; } foreach ( $res as $row ) { $string_translations[ $row['string_id'] ] = $row; // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared $tr = $this->wpdb->get_results( $this->wpdb->prepare( " SELECT id, language, status, value, mo_string, translator_id, translation_date FROM {$this->wpdb->prefix}icl_string_translations WHERE string_id=%d {$extra_cond} ", $row['string_id'] ), ARRAY_A ); if ( $tr ) { foreach ( $tr as $t ) { $string_translations[ $row['string_id'] ]['translations'][ $t['language'] ] = $t; } } } } return WPML\ST\Basket\Status::add( $string_translations, array_keys( $active_languages ) ); } /** * @return string */ private function get_value_search_query() { $language_where = wpml_collect( [ $this->get_original_value_filter_sql(), $this->get_name_filter_sql(), $this->get_context_filter_sql(), ] ); $search_context = $this->get_search_context_filter(); if ( $search_context['translation'] ) { $language_where->push( $this->get_translation_value_filter_sql() ); } if ( $search_context['mo_string'] ) { $language_where->push( $this->get_mo_file_value_filter_sql() ); } return sprintf( '((%s))', $language_where->implode( ') OR (' ) ); } /** * @return string */ private function get_original_value_filter_sql() { return $this->get_column_filter_sql( 's.value', $this->get_search_filter(), $this->is_exact_match() ); } /** * @return string */ private function get_name_filter_sql() { return $this->get_column_filter_sql( 's.name', $this->get_search_filter(), $this->is_exact_match() ); } /** * @return string */ private function get_context_filter_sql() { return $this->get_column_filter_sql( 's.gettext_context', $this->get_search_filter(), $this->is_exact_match() ); } /** * @return string */ private function get_translation_value_filter_sql() { return $this->get_column_filter_sql( 'str.value', $this->get_search_filter(), $this->is_exact_match() ); } /** * @return string */ private function get_mo_file_value_filter_sql() { return $this->get_column_filter_sql( 'str.mo_string', $this->get_search_filter(), $this->is_exact_match() ); } /** * @param string $column * @param string|null|false $search_filter * @param bool|null $exact_match * * @return string */ private function get_column_filter_sql( $column, $search_filter, $exact_match ) { /** @var string $search_filter_html */ $search_filter_html = esc_html( (string) $search_filter ); $column = esc_sql( $column ); $search_filter = esc_sql( (string) $search_filter ); $search_filter_html = esc_sql( $search_filter_html ); if ( $search_filter === $search_filter_html ) { // No special characters involved. return $exact_match ? "$column = '$search_filter'" : "$column LIKE '%$search_filter%'"; } /** @var string $replaced_search_filter */ $replaced_search_filter = $search_filter ? str_replace( "'", "'", $search_filter ) : ''; // Special characters involved - search also for HTML version. return $exact_match ? "($column = '$search_filter' OR $column = '$search_filter_html')" : "($column LIKE '%$search_filter%' OR $column LIKE '%$search_filter_html%')"; } public function get_per_domain_counts( $status ) { $extra_cond = ''; if ( false !== $status ) { if ( ICL_TM_COMPLETE === $status ) { $extra_cond .= ' AND s.status = ' . ICL_TM_COMPLETE; } else { $extra_cond .= ' AND s.status IN (' . ICL_STRING_TRANSLATION_PARTIAL . ',' . ICL_TM_NEEDS_UPDATE . ',' . ICL_TM_NOT_TRANSLATED . ')'; } } $results = $this->wpdb->get_results( " SELECT context, COUNT(context) AS c FROM {$this->wpdb->prefix}icl_strings s WHERE 1 {$extra_cond} AND TRIM(s.value) <> '' GROUP BY context ORDER BY context ASC" ); return $results; } private function get_strings_per_page() { $st_settings = $this->sitepress->get_setting( 'st' ); return isset( $st_settings['strings_per_page'] ) ? $st_settings['strings_per_page'] : WPML_ST_DEFAULT_STRINGS_PER_PAGE; } private function get_results( $where_snippet, $extra_cond, $offset, $limit, $joins = array(), $selects = array() ) { $query = $this->build_sql_start( $selects, $joins ); $query .= $where_snippet; $query .= " {$extra_cond} "; $query .= $this->filter_empty_order_snippet( $offset, $limit ); $res = $this->wpdb->get_results( $query, ARRAY_A ); $this->set_pagination_counts( $limit ); return $res; } private function filter_empty_order_snippet( $offset, $limit ) { return " AND TRIM(s.value) <> '' ORDER BY string_id DESC LIMIT {$offset},{$limit}"; } private function set_pagination_counts( $limit ) { if ( ! is_null( $this->wp_query ) ) { $this->wp_query->found_posts = $this->wpdb->get_var( 'SELECT FOUND_ROWS()' ); $this->wp_query->query_vars['posts_per_page'] = $limit; $this->wp_query->max_num_pages = ceil( $this->wp_query->found_posts / $limit ); } } private function build_sql_start( $selects = array(), $joins = array() ) { array_unshift( $selects, 'SQL_CALC_FOUND_ROWS DISTINCT(s.id) AS string_id, s.language AS string_language, s.string_package_id, s.context, s.gettext_context, s.name, s.value, s.status AS status, s.translation_priority' ); return 'SELECT ' . implode( ', ', $selects ) . " FROM {$this->wpdb->prefix}icl_strings s " . implode( PHP_EOL, $joins ) . ' '; } /** * @return string|false */ private function get_search_filter() { if ( array_key_exists( 'search', $_GET ) ) { return stripcslashes( $_GET['search'] ); } return false; } /** * @return bool */ private function is_exact_match() { if ( array_key_exists( 'em', $_GET ) ) { return 1 === (int) $_GET['em']; } return false; } /** * @return array */ private function get_search_context_filter() { $result = array( 'original' => true, 'translation' => false, 'mo_string' => false, ); if ( array_key_exists( 'search_translation', $_GET ) && ! $this->must_show_all_results() ) { $result['translation'] = (bool) $_GET['search_translation']; $result['mo_string'] = (bool) $_GET['search_translation']; } return $result; } /** * @return bool */ private function is_troubleshooting_filter_enabled() { return array_key_exists( 'troubleshooting', $_GET ) && $_GET['troubleshooting'] === '1'; } /** * @return bool */ private function must_show_all_results() { return isset( $_GET['show_results'] ) && 'all' === $_GET['show_results']; } } utilities/LanguageResolution.php 0000755 00000002602 14720402445 0013104 0 ustar 00 <?php namespace WPML\ST\Utils; use SitePress; use WPML_String_Translation; class LanguageResolution { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_String_Translation $string_translation */ private $string_translation; /** @var null|string $admin_language */ private $admin_language; public function __construct( SitePress $sitepress, WPML_String_Translation $string_translation ) { $this->sitepress = $sitepress; $this->string_translation = $string_translation; } /** @return bool|mixed|string|null */ public function getCurrentLanguage() { if ( $this->string_translation->should_use_admin_language() ) { $current_lang = $this->getAdminLanguage(); } else { $current_lang = $this->sitepress->get_current_language(); } if ( ! $current_lang ) { $current_lang = $this->sitepress->get_default_language(); if ( ! $current_lang ) { $current_lang = 'en'; } } return $current_lang; } /** */ public function getCurrentLocale() { return $this->sitepress->get_locale( $this->getCurrentLanguage() ); } /** @return string */ private function getAdminLanguage() { if ( $this->sitepress->is_wpml_switch_language_triggered() ) { return $this->sitepress->get_admin_language(); } if ( ! $this->admin_language ) { $this->admin_language = $this->sitepress->get_admin_language(); } return $this->admin_language; } } utilities/wpml-st-scan-dir.php 0000755 00000002061 14720402445 0012375 0 ustar 00 <?php class WPML_ST_Scan_Dir { const PLACEHOLDERS_ROOT = '<root>'; /** * @param string $folder * @param array $extensions * @param bool $single_file * @param array $ignore_folders * * @return array */ public function scan( $folder, array $extensions = array(), $single_file = false, $ignore_folders = array() ) { $files = array(); $scanned_files = array(); if ( is_dir( $folder ) ) { $scanned_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $folder ) ); } elseif ( $single_file ) { $scanned_files = array( new SplFileInfo( $folder ) ); } foreach ( $scanned_files as $file ) { $ignore_file = false; if ( in_array( $file->getExtension(), $extensions, true ) ) { foreach ( $ignore_folders as $ignore_folder ) { if ( false !== strpos( $file->getPathname(), str_replace( self::PLACEHOLDERS_ROOT, $folder, $ignore_folder ) ) ) { $ignore_file = true; } } if ( $ignore_file ) { continue; } $files[] = $file->getPathname(); } } return $files; } } utilities/string-dependencies/wpml-st-string-dependencies-builder.php 0000755 00000003567 14720402445 0022221 0 ustar 00 <?php class WPML_ST_String_Dependencies_Builder { /** @var WPML_ST_String_Dependencies_Records $records */ private $records; private $types_map = array( 'post' => 'package', 'package' => 'string', ); public function __construct( WPML_ST_String_Dependencies_Records $records ) { $this->records = $records; } /** * @param string|null $type * @param int $id * * @return WPML_ST_String_Dependencies_Node */ public function from( $type, $id ) { $parent_id = $type === null ? false : $this->records->get_parent_id_from( $type, $id ); if ( $parent_id ) { $parent = $this->from( $this->get_parent_type( $type ), $parent_id ); $initial_node = $parent->search( $id, $type ); if ( $initial_node ) { $initial_node->set_needs_refresh( true ); } return $parent; } $node = new WPML_ST_String_Dependencies_Node( $id, $type ); $node->set_needs_refresh( true ); return $this->populate_node( $node ); } /** * @param WPML_ST_String_Dependencies_Node $node * * @return WPML_ST_String_Dependencies_Node */ private function populate_node( WPML_ST_String_Dependencies_Node $node ) { $child_ids = $this->records->get_child_ids_from( $node->get_type(), $node->get_id() ); if ( $child_ids ) { $child_type = $this->get_child_type( $node->get_type() ); foreach ( $child_ids as $id ) { $child_node = new WPML_ST_String_Dependencies_Node( $id, $child_type ); $node->add_child( $child_node ); $this->populate_node( $child_node ); } } return $node; } /** * @param string $type * * @return null|string */ private function get_parent_type( $type ) { return array_search( $type, $this->types_map, true ) ?: null; } /** * @param string $type * * @return null|string */ private function get_child_type( $type ) { return isset( $this->types_map[ $type ] ) ? $this->types_map[ $type ] : null; } } utilities/string-dependencies/wpml-st-string-dependencies-node.php 0000755 00000007070 14720402445 0021511 0 ustar 00 <?php class WPML_ST_String_Dependencies_Node { /** @var WPML_ST_String_Dependencies_Node|null $parent */ private $parent; /** @var WPML_ST_String_Dependencies_Node[] $children */ private $children = array(); /** @var bool $iteration_completed */ private $iteration_completed = false; /** @var int|null $id */ private $id; /** @var string|null $type */ private $type; /** @var bool|null $needs_refresh */ private $needs_refresh; public function __construct( $id = null, $type = null ) { $this->id = $id; $this->type = $type; } public function get_id() { return $this->id; } public function get_type() { return $this->type; } public function set_needs_refresh( $needs_refresh ) { $this->needs_refresh = $needs_refresh; } public function get_needs_refresh() { return $this->needs_refresh; } public function set_parent( WPML_ST_String_Dependencies_Node $node ) { $node->add_child( $this ); $this->parent = $node; } public function get_parent() { return $this->parent; } public function add_child( WPML_ST_String_Dependencies_Node $node ) { if ( ! isset( $this->children[ $node->get_hash() ] ) ) { $this->children[ $node->get_hash() ] = $node; $node->set_parent( $this ); } } public function remove_child( WPML_ST_String_Dependencies_Node $node ) { unset( $this->children[ $node->get_hash() ] ); unset( $node ); } public function detach() { if ( $this->parent ) { $this->parent->remove_child( $this ); } } /** * Iteration DFS in post-order * * @return WPML_ST_String_Dependencies_Node */ public function get_next() { if ( $this->children ) { $first_child = reset( $this->children ); if ( $first_child ) { /** @var WPML_ST_String_Dependencies_Node $first_child */ return $first_child->get_next(); } } if ( ! $this->parent && ! $this->iteration_completed ) { $this->iteration_completed = true; } return $this; } /** * Search DFS in pre-order * * @param int $id * @param string $type * * @return false|WPML_ST_String_Dependencies_Node */ public function search( $id, $type ) { if ( $this->id === $id && $this->type === $type ) { return $this; } if ( $this->children ) { foreach ( $this->children as $child ) { $node = $child->search( $id, $type ); if ( $node ) { return $node; } } } return false; } public function iteration_completed() { return $this->iteration_completed; } /** * @return string|stdClass */ public function to_json() { $object = new stdClass(); foreach ( $this->get_item_properties() as $property ) { if ( null !== $this->{$property} ) { $object->{$property} = $this->{$property}; } } if ( $this->children ) { foreach ( $this->children as $child ) { $object->children[] = $child->to_json(); } } if ( ! $this->parent ) { return json_encode( $object ); } return $object; } /** * @param string|self $object */ public function from_json( $object ) { if ( is_string( $object ) ) { $object = json_decode( $object ); } foreach ( $this->get_item_properties() as $property ) { if ( isset( $object->{$property} ) ) { $this->{$property} = $object->{$property}; } } if ( isset( $object->children ) ) { foreach ( $object->children as $child ) { $child_node = new self( $child->id, $child->type ); $child_node->from_json( $child ); $child_node->set_parent( $this ); } } } private function get_item_properties() { return array( 'id', 'type', 'needs_refresh' ); } private function get_hash() { return spl_object_hash( $this ); } } utilities/string-dependencies/wpml-st-string-dependencies-records.php 0000755 00000002277 14720402445 0022231 0 ustar 00 <?php class WPML_ST_String_Dependencies_Records { /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string $type * @param int $id * * @return int */ public function get_parent_id_from( $type, $id ) { switch ( $type ) { case 'package': $query = "SELECT post_id FROM {$this->wpdb->prefix}icl_string_packages WHERE ID = %d"; break; case 'string': $query = "SELECT string_package_id FROM {$this->wpdb->prefix}icl_strings WHERE id = %d"; break; default: return 0; } return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $id ) ); } /** * @param string $type * @param int $id * * @return array */ public function get_child_ids_from( $type, $id ) { switch ( $type ) { case 'post': $query = "SELECT id FROM {$this->wpdb->prefix}icl_string_packages WHERE post_id = %d"; break; case 'package': $query = "SELECT ID FROM {$this->wpdb->prefix}icl_strings WHERE string_package_id = %d"; break; default: return array(); } $ids = $this->wpdb->get_col( $this->wpdb->prepare( $query, $id ) ); return array_map( 'intval', $ids ); } } translation-memory/FetchTranslationMemory.php 0000755 00000002330 14720402445 0015565 0 ustar 00 <?php namespace WPML\ST\Main\Ajax; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; class FetchTranslationMemory implements IHandler { /** @var \WPML_ST_Translation_Memory_Records $records */ private $records; public function __construct( \WPML_ST_Translation_Memory_Records $records ) { $this->records = $records; } public function run( Collection $data ) { $translations = Obj::prop( 'batch', $data ) ? $this->fetchBatchStrings( Obj::prop( 'strings', $data ) ) : $this->fetchSingleString( $data ); if ( $translations !== false ) { return Either::of( $translations ); } else { return Either::left( 'invalid data' ); } } private function fetchBatchStrings( $strings ) { $results = Fns::map( [ $this, 'fetchSingleString' ], $strings ); return Lst::includes( false, $results ) ? false : $results; } public function fetchSingleString( $data ) { $string = Obj::prop( 'string', $data ); $source = Obj::prop( 'source', $data ); $target = Obj::prop( 'target', $data ); if ( $string && $source && $target ) { return $this->records->get( [ $string ], $source, $target ); } else { return false; } } } translation-memory/class-wpml-st-translation-memory.php 0000755 00000002065 14720402445 0017501 0 ustar 00 <?php use \WPML\FP\Obj; use \WPML\FP\Fns; use \WPML\ST\Main\Ajax\FetchTranslationMemory; class WPML_ST_Translation_Memory implements IWPML_AJAX_Action, IWPML_Backend_Action, IWPML_DIC_Action { /** @var WPML_ST_Translation_Memory_Records $records */ private $records; public function __construct( WPML_ST_Translation_Memory_Records $records ) { $this->records = $records; } public function add_hooks() { add_filter( 'wpml_st_get_translation_memory', [ $this, 'get_translation_memory' ], 10, 2 ); add_filter( 'wpml_st_translation_memory_endpoint', Fns::always( FetchTranslationMemory::class ) ); } /** * @param array $empty_array * @param array $args with keys * - `strings` an array of strings * - `source_lang` * - `target_lang` * * @return stdClass[] */ public function get_translation_memory( $empty_array, $args ) { return $this->records->get( Obj::propOr( [], 'strings', $args ), Obj::propOr( '', 'source_lang', $args ), Obj::propOr( '', 'target_lang', $args ) ); } } translation-memory/class-wpml-st-translation-memory-records.php 0000755 00000005006 14720402445 0021136 0 ustar 00 <?php class WPML_ST_Translation_Memory_Records { /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param array $strings * @param string $source_lang * @param string $target_lang * * @return array */ public function get( $strings, $source_lang, $target_lang ) { if ( ! $strings ) { return []; } $strings = $this->also_match_alternative_line_breaks( $strings ); $prepared_strings = wpml_prepare_in( $strings ); $sql = " SELECT s.value as original, coalesce(st.value, st.mo_string) as translation, st.language as language FROM {$this->wpdb->prefix}icl_strings as s JOIN {$this->wpdb->prefix}icl_string_translations as st ON s.id = st.string_id WHERE s.value IN ({$prepared_strings}) AND s.language = '%s' AND ( (st.value IS NOT NULL AND st.status IN (" . ICL_STRING_TRANSLATION_COMPLETE . "," . ICL_STRING_TRANSLATION_NEEDS_UPDATE . ")) OR (st.value IS NULL AND st.mo_string IS NOT NULL) )"; $prepare_args = array( $source_lang ); if ( $target_lang ) { $sql .= " AND st.language = '%s'"; $prepare_args[] = $target_lang; } else { $sql .= " AND st.language <> '%s'"; $prepare_args[] = $source_lang; } $records = $this->wpdb->get_results( $this->wpdb->prepare( $sql, $prepare_args ) ); $records = $this->also_include_matches_for_alternative_line_breaks( $records ); return $records; } private function also_match_alternative_line_breaks( $strings ) { $new_strings = array(); foreach ( $strings as $string ) { if ( mb_strpos( $string, "\r\n" ) !== false ) { $new_strings[] = str_replace( "\r\n", "\n", $string ); } if ( mb_strpos( $string, "\n" ) !== false && mb_strpos( $string, "\r" ) === false ) { $new_strings[] = str_replace( "\n", "\r\n", $string ); } } return array_merge( $strings, $new_strings ); } private function also_include_matches_for_alternative_line_breaks( $records ) { $new_records = array(); foreach ( $records as $record ) { if ( mb_strpos( $record->original, "\r\n" ) !== false ) { $new_record = clone $record; $new_record->original = str_replace( "\r\n", "\n", $record->original ); $new_records[] = $new_record; } if ( mb_strpos( $record->original, "\n" ) !== false && mb_strpos( $record->original, "\r" ) === false ) { $new_record = clone $record; $new_record->original = str_replace( "\n", "\r\n", $record->original ); $new_records[] = $new_record; } } return array_merge( $records, $new_records ); } } TranslateWpmlString.php 0000755 00000012674 14720402445 0011260 0 ustar 00 <?php namespace WPML\ST; use WPML\ST\Gettext\Settings as GettextSettings; use WPML\ST\MO\Hooks\LanguageSwitch; use WPML\ST\MO\File\Manager; use WPML\ST\StringsFilter\Provider; use WPML_Locale; class TranslateWpmlString { /** @var array $loadedDomains */ private static $loadedDomains = []; /** @var Provider $filterProvider */ private $filterProvider; /** @var LanguageSwitch $languageSwitch */ private $languageSwitch; /** @var WPML_Locale $locale */ private $locale; /** @var GettextSettings $gettextSettings */ private $gettextSettings; /** @var Manager $fileManager */ private $fileManager; /** @var bool $isAutoRegisterDisabled */ private $isAutoRegisterDisabled; /** @var bool $lock */ private $lock = false; public function __construct( Provider $filterProvider, LanguageSwitch $languageSwitch, WPML_Locale $locale, GettextSettings $gettextSettings, Manager $fileManager ) { $this->filterProvider = $filterProvider; $this->languageSwitch = $languageSwitch; $this->locale = $locale; $this->gettextSettings = $gettextSettings; $this->fileManager = $fileManager; } public function init() { $this->languageSwitch->initCurrentLocale(); $this->isAutoRegisterDisabled = ! $this->gettextSettings->isAutoRegistrationEnabled(); } /** * @param string|array $wpmlContext * @param string $name * @param bool|string $value * @param bool $allowEmptyValue * @param null|bool $hasTranslation * @param null|string $targetLang * * @return bool|string */ public function translate( $wpmlContext, $name, $value = false, $allowEmptyValue = false, &$hasTranslation = null, $targetLang = null ) { if ( $this->lock ) { return $value; } $this->lock = true; if ( wpml_st_is_requested_blog() ) { if ( $this->isAutoRegisterDisabled && self::canTranslateWithMO( $value, $name ) ) { $value = $this->translateByMOFile( $wpmlContext, $name, $value, $hasTranslation, $targetLang ); } else { $value = $this->translateByDBQuery( $wpmlContext, $name, $value, $hasTranslation, $targetLang ); } } $this->lock = false; return $value; } /** * @param string|array $wpmlContext * @param string $name * @param bool|string $value * @param null|bool $hasTranslation * @param null|string $targetLang * * @return string */ private function translateByMOFile( $wpmlContext, $name, $value, &$hasTranslation, $targetLang ) { list ( $domain, $gettextContext ) = wpml_st_extract_context_parameters( $wpmlContext ); $translateByName = function ( $locale ) use ( $name, $domain, $gettextContext ) { $this->loadTextDomain( $domain, $locale ); if ( $gettextContext ) { return _x( $name, $gettextContext, $domain ); } else { return __( $name, $domain ); } }; $new_value = $this->withMOLocale( $targetLang, $translateByName ); $hasTranslation = $new_value !== $name; if ( $hasTranslation ) { $value = $new_value; } return $value; } /** * @param string|array $wpmlContext * @param string $name * @param bool|string $value * @param null|bool $hasTranslation * @param null|string $targetLang * * @return string */ private function translateByDBQuery( $wpmlContext, $name, $value, &$hasTranslation, $targetLang ) { $filter = $this->filterProvider->getFilter( $targetLang, $name ); if ( $filter ) { $value = $filter->translate_by_name_and_context( $value, $name, $wpmlContext, $hasTranslation ); } return $value; } /** * @param string $domain * @param string $locale */ private function loadTextDomain( $domain, $locale ) { if ( ! isset( $GLOBALS['l10n'][ $domain ] ) && ! isset( $GLOBALS['l10n_unloaded'][ $domain ] ) && ! isset( self::$loadedDomains[ $locale ][ $domain ] ) ) { load_textdomain( $domain, $this->fileManager->getFilepath( $domain, $locale ) ); self::$loadedDomains[ $locale ][ $domain ] = true; } } /** * @param string $targetLang * @param callable $function * * @return string */ private function withMOLocale( $targetLang, $function ) { $initialLocale = $this->languageSwitch->getCurrentLocale(); if ( $targetLang ) { /** @var string $targetLocale */ $targetLocale = $this->locale->get_locale( $targetLang ); $this->languageSwitch->switchToLocale( $targetLocale ); $result = $function( $targetLocale ); $this->languageSwitch->switchToLocale( $initialLocale ); } else { $result = $function( $initialLocale ); } return $result; } /** * We will allow MO translation only when * the original is not empty. * * We also need to make sure we deal with a * WPML registered string (not gettext). * * If those conditions are not fulfilled, * we will translate from the database. * * @param string|bool $original * @param string $name * * @return bool */ public static function canTranslateWithMO( $original, $name ) { return $original && self::isWpmlRegisteredString( $original, $name ); } /** * This allows to differentiate WPML registered strings * from gettext strings that have the default hash for * the name. * * But it's still possible that WPML registered strings * have a hash for the name. * * @param string|bool $original * @param string $name * * @return bool */ private static function isWpmlRegisteredString( $original, $name ) { return $name && md5( (string) $original ) !== $name; } public static function resetCache() { self::$loadedDomains = []; } } class-wpml-st-string-factory.php 0000755 00000005665 14720402445 0012755 0 ustar 00 <?php class WPML_ST_String_Factory { private $wpdb; /** * WPML_ST_String_Factory constructor. * * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** @var int[] $string_id_cache */ private $string_id_cache = array(); /** @var WPML_ST_String $string_cache */ private $string_cache = array(); /** * @param int $string_id * * @return WPML_ST_String */ public function find_by_id( $string_id ) { $this->string_cache[ $string_id ] = isset( $this->string_cache[ $string_id ] ) ? $this->string_cache[ $string_id ] : new WPML_ST_String( $string_id, $this->wpdb ); return $this->string_cache[ $string_id ]; } /** * @param string $name * * @return WPML_ST_String */ public function find_by_name( $name ) { /** @var string $sql */ $sql = $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE name=%s LIMIT 1", $name ); $cache_key = md5( $sql ); $this->string_id_cache[ $cache_key ] = isset( $this->string_id_cache[ $cache_key ] ) ? $this->string_id_cache[ $cache_key ] : (int) $this->wpdb->get_var( $sql ); $string_id = $this->string_id_cache[ $cache_key ]; $this->string_cache[ $string_id ] = isset( $this->string_cache[ $string_id ] ) ? $this->string_cache[ $string_id ] : new WPML_ST_String( $string_id, $this->wpdb ); return $this->string_cache[ $this->string_id_cache[ $cache_key ] ]; } /** * @param string $name * * @return WPML_ST_Admin_String */ public function find_admin_by_name( $name ) { $sql = $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE name=%s LIMIT 1", $name ); $string_id = (int) $this->wpdb->get_var( $sql ); return new WPML_ST_Admin_String( $string_id, $this->wpdb ); } /** * @param string $string * @param string|array $context * @param string|false $name * * @return mixed */ public function get_string_id( $string, $context, $name = false ) { list( $domain, $gettext_context ) = wpml_st_extract_context_parameters( $context ); $sql = "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE BINARY value=%s"; $prepare_args = array( $string ); if ( $gettext_context ) { $sql .= ' AND gettext_context=%s'; $prepare_args[] = $gettext_context; } if ( $domain ) { $sql .= ' AND context=%s'; $prepare_args[] = $domain; } if ( $name !== false ) { $sql .= ' AND name = %s '; $prepare_args[] = $name; } /** @var string $sql */ $sql = $this->wpdb->prepare( $sql . ' LIMIT 1', $prepare_args ); $cache_key = md5( $sql ); $this->string_id_cache[ $cache_key ] = isset( $this->string_id_cache[ $cache_key ] ) ? $this->string_id_cache[ $cache_key ] : (int) $this->wpdb->get_var( $sql ); return $this->string_id_cache[ $cache_key ]; } } API/rest/FactoryLoader.php 0000755 00000001463 14720402445 0011412 0 ustar 00 <?php namespace WPML\ST\Rest; use WPML\ST\MO\File\ManagerFactory; use WPML\ST\MO\File\Manager; use function WPML\Container\make; /** * @author OnTheGo Systems */ class FactoryLoader implements \IWPML_REST_Action_Loader, \IWPML_Deferred_Action_Loader { const REST_API_INIT_ACTION = 'rest_api_init'; /** * @return string */ public function get_load_action() { return self::REST_API_INIT_ACTION; } public function create() { return [ MO\Import::class => make( MO\Import::class ), MO\PreGenerate::class => $this->create_pre_generate(), Settings::class => make( Settings::class ), ]; } private function create_pre_generate() { /** @var Manager $manager */ $manager = ManagerFactory::create(); return make( MO\PreGenerate::class, [ ':manager' => $manager ] ); } } API/rest/Base.php 0000755 00000000253 14720402445 0007522 0 ustar 00 <?php namespace WPML\ST\Rest; abstract class Base extends \WPML\Rest\Base { /** * @return string */ public function get_namespace() { return 'wpml/st/v1'; } } API/rest/mo/PreGenerate.php 0000755 00000002134 14720402445 0011464 0 ustar 00 <?php namespace WPML\ST\Rest\MO; use WPML\ST\MO\File\Manager; use \WPML\Rest\Adaptor; use WPML\ST\MO\Generate\Process\ProcessFactory; use WPML\ST\MO\Scan\UI\Factory; class PreGenerate extends \WPML\ST\Rest\Base { /** @var Manager */ private $manager; /** @var ProcessFactory */ private $processFactory; public function __construct( Adaptor $adaptor, Manager $manager, ProcessFactory $processFactory ) { parent::__construct( $adaptor ); $this->manager = $manager; $this->processFactory = $processFactory; } /** * @return array */ function get_routes() { return [ [ 'route' => 'pre_generate_mo_files', 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'generate' ], 'args' => [], ] ] ]; } function get_allowed_capabilities( \WP_REST_Request $request ) { return [ 'manage_options' ]; } public function generate() { if ( ! $this->manager->maybeCreateSubdir() ) { return [ 'error' => 'no access' ]; } Factory::clearIgnoreWpmlVersion(); return [ 'remaining' => $this->processFactory->create()->runPage() ]; } } API/rest/mo/Import.php 0000755 00000002301 14720402445 0010531 0 ustar 00 <?php namespace WPML\ST\Rest\MO; use WPML\ST\TranslationFile\QueueFilter; use WPML_ST_Translations_File_Queue; class Import extends \WPML\ST\Rest\Base { /** * @return array */ function get_routes() { return [ [ 'route' => 'import_mo_strings', 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'import' ], 'args' => [ 'plugins' => [ 'type' => 'array', ], 'themes' => [ 'type' => 'array', ], 'other' => [ 'type' => 'array', ], ] ] ] ]; } /** * @param \WP_REST_Request $request * * @return array */ function get_allowed_capabilities( \WP_REST_Request $request ) { return [ 'manage_options' ]; } /** * @return array * @throws \WPML\Auryn\InjectionException */ public function import( \WP_REST_Request $request ) { /** @var WPML_ST_Translations_File_Queue $queue */ $queue = \WPML\Container\make( \WPML_ST_Translations_File_Scan_Factory::class )->create_queue(); $queue->import( new QueueFilter( $request->get_param( 'plugins' ), $request->get_param( 'themes' ), $request->get_param( 'other' ) ) ); return [ 'remaining' => $queue->get_pending() ]; } } API/rest/settings/Settings.php 0000755 00000001761 14720402445 0012315 0 ustar 00 <?php namespace WPML\ST\Rest; class Settings extends Base { /** @var \WPML\WP\OptionManager $option_manager */ private $option_manager; public function __construct( \WPML\Rest\Adaptor $adaptor, \WPML\WP\OptionManager $option_manager ) { parent::__construct( $adaptor ); $this->option_manager = $option_manager; } public function get_routes() { return [ [ 'route' => 'settings', 'args' => [ 'methods' => 'POST', 'callback' => [ $this, 'set' ], 'args' => [ 'group' => [ 'required' => true ], 'key' => [ 'required' => true ], 'data' => [ 'required' => true ], ], ], ], ]; } public function get_allowed_capabilities( \WP_REST_Request $request ) { return [ 'manage_options' ]; } public function set( \WP_REST_Request $request ) { $group = $request->get_param( 'group' ); $key = $request->get_param( 'key' ); $data = $request->get_param( 'data' ); $this->option_manager->set( 'ST-' . $group, $key, $data ); } } shortcode/Hooks.php 0000755 00000006022 14720402445 0010337 0 ustar 00 <?php namespace WPML\ST\Shortcode; use WPML\FP\Fns; use WPML\FP\Obj; use function WPML\FP\curryN; use function WPML\FP\invoke; use function WPML\FP\partial; class Hooks implements \IWPML_DIC_Action, \IWPML_Backend_Action, \IWPML_AJAX_Action, \IWPML_REST_Action { /** @var \WPML_ST_DB_Mappers_Strings */ private $stringMapper; public function __construct( \WPML_ST_DB_Mappers_Strings $stringMapper ) { $this->stringMapper = $stringMapper; } public function add_hooks() { /** * @see \WPML\ST\Shortcode\TranslationHandler::appendId */ add_filter( 'wpml_tm_xliff_unit_field_data', $this->appendId() ); $this->defineRetrievingATEJobHooks(); $this->defineRetrievingProxyJobHooks(); $this->defineCTEHooks(); } private function defineRetrievingATEJobHooks() { $lens = LensFactory::createLensForJobData(); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_tm_ate_job_data_from_xliff', TranslationHandler::registerStringTranslation( $lens ), 9, 2 ); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_tm_ate_job_data_from_xliff', $this->restoreOriginalShortcodes( $lens ), 10, 1 ); } private function defineRetrievingProxyJobHooks() { $lens = LensFactory::createLensForProxyTranslations(); $registerStringTranslation = $this->registerStringTranslation( $lens, invoke( 'get_target_language' ) ); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_tm_proxy_translations', $registerStringTranslation, 9, 1 ); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_tm_proxy_translations', $this->restoreOriginalShortcodes( $lens ), 10, 1 ); } private function defineCTEHooks() { $appendStringIdToFieldsData = Obj::over( LensFactory::createLensForAssignIdInCTE(), Fns::map( $this->appendId() ) ); add_filter( 'wpml_tm_adjust_translation_fields', $appendStringIdToFieldsData, 10, 1 ); $lens = LensFactory::createLensForJobData(); $registerStringTranslation = $this->registerStringTranslation( $lens, Obj::prop( 'target_lang' ) ); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_translation_editor_save_job_data', $registerStringTranslation, 9, 1 ); /** * @see \WPML\ST\Shortcode\TranslationHandler::restoreOriginalShortcodes */ add_filter( 'wpml_translation_editor_save_job_data', $this->restoreOriginalShortcodes( $lens ), 10, 1 ); } /** * @return callable */ private function appendId() { return TranslationHandler::appendId( [ $this->stringMapper, 'getByDomainAndValue' ] ); } private function restoreOriginalShortcodes( callable $lens ) { return TranslationHandler::restoreOriginalShortcodes( [ $this->stringMapper, 'getById' ], $lens ); } private function registerStringTranslation( callable $lens, callable $getTargetLang ) { return TranslationHandler::registerStringTranslation( $lens, Fns::__, $getTargetLang ); } } shortcode/LensFactory.php 0000755 00000004410 14720402445 0011504 0 ustar 00 <?php namespace WPML\ST\Shortcode; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use function WPML\Container\make; use function WPML\FP\gatherArgs; use function WPML\FP\invoke; use function WPML\FP\pipe; class LensFactory { public static function createLensForJobData() { // $get :: array->string[] $get = pipe( Obj::path( [ 'fields' ] ), Lst::pluck( 'data' ) ); // $set :: string[]->array->array $set = function ( $newValue, $jobData ) { /** @var array $newValue */ $newValue = Obj::objOf( 'fields', Fns::map( Obj::objOf( 'data' ), $newValue ) ); return Obj::replaceRecursive( $newValue, $jobData ); }; return Obj::lens( $get, $set ); } public static function createLensForProxyTranslations() { // $getTranslations :: \WPML_TP_Translation_Collection->\WPML_TP_Translation[] $getTranslations = pipe( invoke( 'to_array' ), Obj::prop( 'translations' ) ); // $get :: \WPML_TP_Translation_Collection->string[] $get = pipe( $getTranslations, Fns::map( Obj::prop( 'target' ) ) ); // $set :: string[]->\WPML_TP_Translation_Collection->\WPML_TP_Translation_Collection $set = function ( array $translations, \WPML_TP_Translation_Collection $tpTranslations ) use ( $getTranslations ) { $buildNewTranslations = pipe( $getTranslations, Fns::map( function ( $translation, $index ) use ( $translations ) { return make( '\WPML_TP_Translation', [ ':field' => $translation['field'], ':source' => $translation['source'], ':target' => $translations[ $index ], ] ); } ) ); return make( '\WPML_TP_Translation_Collection', [ ':translations' => $buildNewTranslations( $tpTranslations ), ':source_language' => $tpTranslations->get_source_language(), ':target_language' => $tpTranslations->get_target_language(), ] ); }; return Obj::lens( $get, $set ); } public static function createLensForAssignIdInCTE() { // $get :: array->string[] $get = Fns::map( Obj::prop( 'field_data' ) ); // $set :: string[]->array->array $set = function ( $updatedTranslations, $fields ) { $newValue = Fns::map( Obj::objOf( 'field_data' ), $updatedTranslations ); return Obj::replaceRecursive( $newValue, $fields ); }; return Obj::lens( $get, $set ); } } shortcode/TranslationHandler.php 0000755 00000015617 14720402445 0013062 0 ustar 00 <?php namespace WPML\ST\Shortcode; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Str; use function WPML\FP\curryN; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; /** * Class TranslationHandler * * @package WPML\ST\Shortcode * * @method static callable|mixed appendId(callable ...$getStringRowByItsDomainAndValue, mixed ...$fieldData) - Curried :: (string->string->array)->mixed->mixed * * It appends "id" attribute to [wpml-string] shortcode. * * $getStringRowByItsDomainAndValue :: string->string->array * * @method static callable|mixed registerStringTranslation(callable ...$lens, mixed ...$data, callable ...$getTargetLanguage) - Curried :: callable->mixed->(mixed->string)->mixed * * It detects all [wpml-string] shortcodes in $jobData and registers string translations * * $getTargetLanguage :: mixed->string * * @method static callable|mixed restoreOriginalShortcodes(callable ...$getStringById, callable ...$lens, mixed ...$data) - Curried :: (int->string)->callable->mixed->mixed * * It detects all [wpml-string] shortcodes in $jobData and * - removes "id" attribute * - replaces translated inner text by its original value * * $getStringById :: int->array */ class TranslationHandler { use Macroable; const SHORTCODE_PATTERN = '/\[wpml-string.*?\[\/wpml-string\]/'; public static function init() { self::macro( 'appendId', curryN( 2, function ( callable $getStringRowByItsDomainAndValue, $fieldData ) { // $getStringId :: [ domain, value ] → id $getStringId = spreadArgs( pipe( $getStringRowByItsDomainAndValue, Obj::prop( 'id' ) ) ); // appendStringId :: [ _, domain, value ] → [ _, domain, value, id ] $appendStringId = self::appendToData( pipe( Lst::takeLast( 2 ), $getStringId ) ); // $getShortcode :: [shortcode, domain, id] -> shortcode $getShortcode = Lst::nth( 0 ); // $getShortcode :: [shortcode, domain, id] -> id /** @var callable $getId */ $getId = Lst::last(); // $extractDomain :: string -> string $extractDomain = self::firstMatchingGroup( '/context="(.*?)"/', 'wpml-shortcode' ); // $forceRegisterShortcodeString :: string -> void $forceRegisterShortcodeString = pipe( $getShortcode, 'do_shortcode' ); // $newShortcode :: [shortcode, domain, id] -> string $newShortcode = function ( $data ) use ( $getId, $getShortcode ) { $pattern = '/\[wpml-string(.*)\]/'; $replace = '[wpml-string id="' . $getId( $data ) . '"${1}]'; return preg_replace( $pattern, $replace, $getShortcode( $data ) ); }; // $updateSingleShortcode :: [shortcode, domain, id] -> [shortcode, newShortcode] $updateSingleShortcode = Fns::converge( Lst::makePair(), [ $getShortcode, $newShortcode ] ); // $updateFieldData :: string, [shortcode, newShortcode] -> string $updateFieldData = function ( $fieldData, $shortCodePairs ) { list( $shortcode, $newShortcode ) = $shortCodePairs; return str_replace( $shortcode, $newShortcode, $fieldData ); }; return \wpml_collect( Str::matchAll( self::SHORTCODE_PATTERN, $fieldData ) ) ->map( self::appendToData( pipe( $getShortcode, $extractDomain ) ) ) ->map( self::appendToData( pipe( $getShortcode, self::extractInnerText() ) ) ) ->each( $forceRegisterShortcodeString ) ->map( $appendStringId ) ->filter( $getId ) ->map( $updateSingleShortcode ) ->reduce( $updateFieldData, $fieldData ); } ) ); self::macro( 'registerStringTranslation', curryN( 3, function ( callable $lens, $data, callable $getTargetLanguage ) { $targetLanguage = $getTargetLanguage( $data ); if ( ! $targetLanguage ) { return $data; } $registerStringTranslation = curryN( 4, 'icl_add_string_translation' ); // $registerStringTranslation :: stringId, translationValue -> translationId $registerStringTranslation = $registerStringTranslation( Fns::__, $targetLanguage, Fns::__, ICL_STRING_TRANSLATION_COMPLETE ); // $getStringIdAndTranslations :: [shortcode, id, translation] -> [id, translation] $getStringIdAndTranslations = Lst::drop( 1 ); // $registerTranslationOfSingleString :: [shortcode, id, translation] -> void $registerTranslationOfSingleString = pipe( $getStringIdAndTranslations, spreadArgs( $registerStringTranslation ) ); // $registerStringsFromFieldData :: string -> void $registerStringsFromFieldData = pipe( self::findShortcodesInJobData(), Fns::each( $registerTranslationOfSingleString ) ); Fns::each( $registerStringsFromFieldData, Obj::view( $lens, $data ) ); return $data; } ) ); self::macro( 'restoreOriginalShortcodes', curryN( 3, function ( callable $getStringById, callable $lens, $data ) { // $getOriginalStringValue :: int -> string $getOriginalStringValue = pipe( $getStringById, Obj::prop( 'value' ) ); // $restoreSingleShortcode :: string, [string, id] -> string $restoreSingleShortcode = function ( $fieldData, $shortcodeMatches ) use ( $getOriginalStringValue ) { list( , $stringId ) = $shortcodeMatches; $pattern = '/\[wpml-string id="' . $stringId . '"(.*?)\](.*?)\[\/wpml-string\]/'; $replacement = '[wpml-string${1}]' . $getOriginalStringValue( $stringId ) . '[/wpml-string]'; return preg_replace( $pattern, $replacement, $fieldData ); }; // $updateFieldData :: string -> string $updateFieldData = Fns::map( Fns::converge( Fns::reduce( $restoreSingleShortcode ), [ Fns::identity(), self::findShortcodesInJobData(), ] ) ); return Obj::over( $lens, $updateFieldData, $data ); } ) ); } // findShortcodesInJobData :: void → ( string → [shortcode, id, translation] ) private static function findShortcodesInJobData() { return function ( $str ) { /** @var callable $getId */ $getId = Lst::nth( 1 ); $extractId = self::firstMatchingGroup( '/id="(.*?)"/' ); return \wpml_collect( Str::matchAll( self::SHORTCODE_PATTERN, $str ) ) ->map( self::appendToData( pipe( Lst::nth( 0 ), $extractId ) ) ) ->map( self::appendToData( pipe( Lst::nth( 0 ), self::extractInnerText() ) ) ) ->filter( $getId ); }; } // appendToData :: callable -> ( array -> array ) private static function appendToData( callable $fn ) { return Fns::converge( Lst::append(), [ $fn, Fns::identity() ] ); } // firstMatchingGroup :: string, string|null -> ( string -> string ) private static function firstMatchingGroup( $pattern, $fallback = null ) { return function ( $str ) use ( $pattern, $fallback ) { return Lst::nth( 1, Str::match( $pattern, $str ) ) ?: $fallback; }; } // extractInnerText :: void -> ( string -> string ) private static function extractInnerText() { return self::firstMatchingGroup( '/\](.*)\[/' ); } } TranslationHandler::init(); string-translation-ui/class-wpml-change-string-domain-language-dialog.php 0000755 00000011730 14720402445 0022635 0 ustar 00 <?php class WPML_Change_String_Domain_Language_Dialog extends WPML_WPDB_And_SP_User { /** @var WPML_Language_Of_Domain $language_of_domain */ private $language_of_domain; /** @var WPML_ST_String_Factory $string_factory */ private $string_factory; public function __construct( &$wpdb, &$sitepress, &$string_factory ) { parent::__construct( $wpdb, $sitepress ); $this->string_factory = &$string_factory; $this->language_of_domain = new WPML_Language_Of_Domain( $sitepress ); } public function render( $domains ) { $all_languages = $this->sitepress->get_languages( $this->sitepress->get_admin_language() ); ?> <div id="wpml-change-domain-language-dialog" class="wpml-change-language-dialog" title="<?php _e( 'Language of domains', 'wpml-string-translation' ); ?>" style="display:none" data-button-text="<?php _e( 'Apply', 'wpml-string-translation' ); ?>" data-cancel-text="<?php _e( 'Cancel', 'wpml-string-translation' ); ?>" > <label for="wpml-domain-select"> <?php _e( 'Select for which domain to set the language: ', 'wpml-string-translation' ); ?> </label> <select id="wpml-domain-select"> <option value="" selected="selected"><?php _e( '-- Please select --', 'wpml-string-translation' ); ?></option> <?php foreach ( $domains as $domain ) { $results = $this->wpdb->get_results( $this->wpdb->prepare( " SELECT language, COUNT(language) AS count FROM {$this->wpdb->prefix}icl_strings s WHERE context = %s AND language IN (" . wpml_prepare_in( array_keys( $all_languages ) ) . ') GROUP BY language ', $domain->context ), ARRAY_A ); foreach ( $results as &$result ) { $result['display_name'] = $all_languages[ $result['language'] ]['display_name']; } $domain_lang = $this->language_of_domain->get_language( $domain->context ); if ( $domain_lang ) { $domain_data = 'data-domain_lang="' . $domain_lang . '" '; } else { $domain_data = 'data-domain_lang="" '; } echo '<option value="' . $domain->context . '" data-langs="' . esc_attr( (string) wp_json_encode( $results ) ) . '"' . $domain_data . '>' . $domain->context . '</option>'; } ?> </select> <div class="js-summary wpml-cdl-summary" style="display:none" > <p class="wpml-cdl-info"> <?php _e( 'This domain currently has the following strings:', 'wpml-string-translation' ); ?> </p> <table class="widefat striped wpml-cdl-table"> <thead> <tr> <td class="manage-column column-cb check-column"><input class="js-all-check" type="checkbox" value="all" /></td> <th><?php _e( 'Current source language', 'wpml-string-translation' ); ?></th> <th class="num"><?php _e( 'Number of strings', 'wpml-string-translation' ); ?></th> </tr> </thead> <tbody> </tbody> </table> <div class="js-lang-select-area wpml-cdl-info"> <label for="wpml-source-domain-language-change"><?php _e( 'Set the source language of these strings to:', 'wpml-string-translation' ); ?></label> <?php $lang_selector = new WPML_Simple_Language_Selector( $this->sitepress ); echo $lang_selector->render( array( 'id' => 'wpml-source-domain-language-change' ) ); ?> <label for="wpml-cdl-set-default"> <input id="wpml-cdl-set-default" type="checkbox" class="js-default" value="use-as-default" checked="checked" /> <?php _e( 'Use this language as the default language for new strings in this domain', 'wpml-string-translation' ); ?> </label> </div> </div> <span class="spinner"></span> <?php wp_nonce_field( 'wpml_change_string_domain_language_nonce', 'wpml_change_string_domain_language_nonce' ); ?> </div> <?php } public function change_language_of_strings( $domain, $langs, $to_lang, $set_as_default ) { $package_translation = new WPML_Package_Helper(); $package_translation->change_language_of_strings_in_domain( $domain, $langs, $to_lang ); if ( ! empty( $langs ) ) { foreach ( $langs as &$lang ) { $lang = "'" . $lang . "'"; } $langs = implode( ',', $langs ); $string_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE context=%s AND language IN ($langs)", $domain ) ); foreach ( $string_ids as $str_id ) { $this->string_factory->find_by_id( $str_id )->set_language( $to_lang ); } } if ( $set_as_default ) { $lang_of_domain = new WPML_Language_Of_Domain( $this->sitepress ); $lang_of_domain->set_language( $domain, $to_lang ); } $string_ids = $this->wpdb->get_col( $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE context = %s", $domain ) ); foreach ( $string_ids as $strid ) { $this->string_factory->find_by_id( $strid )->update_status(); } do_action( 'wpml_st_language_of_strings_changed', $string_ids ); return array( 'success' => true ); } } string-translation-ui/ajax/SaveTranslation.php 0000755 00000001117 14720402445 0015577 0 ustar 00 <?php namespace WPML\ST\Main\Ajax; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; use WPML\FP\Obj; use WPML\ST\API\Fns as STAPI; class SaveTranslation implements IHandler { public function run( Collection $data ) { $id = Obj::prop( 'id', $data ); $translation = Obj::prop( 'translation', $data ); $lang = Obj::prop( 'lang', $data ); if ( $id && $translation && $lang ) { return Either::of( STAPI::saveTranslation( $id, $lang, $translation, ICL_TM_COMPLETE ) ); } else { return Either::left( 'invalid data' ); } } } string-translation-ui/ajax/FetchCompletedStrings.php 0000755 00000001252 14720402445 0016722 0 ustar 00 <?php namespace WPML\ST\Main\Ajax; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use WPML\FP\Either; class FetchCompletedStrings implements IHandler { public function run( Collection $data ) { global $wpdb; $strings = $data->get( 'strings', [] ); if( count( $strings ) ) { $strings_in = wpml_prepare_in( $strings, '%d' ); $result = $wpdb->get_results( $wpdb->prepare( "SELECT string_id, language AS lang, value AS translation FROM {$wpdb->prefix}icl_string_translations WHERE string_id IN({$strings_in}) AND status=%d", ICL_TM_COMPLETE ) ); return Either::of( $result ); } else { return Either::of( [] ); } } } string-translation-ui/class-wpml-string-translation-table.php 0000755 00000021666 14720402445 0020561 0 ustar 00 <?php use WPML\Element\API\Languages; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\FP\Wrapper; use function WPML\FP\partialRight; class WPML_String_Translation_Table { private $strings; private $active_languages; private $additional_columns_to_render; private $strings_in_page; /** * WPML_String_Translation_Table constructor. * * @param array<string> $strings */ public function __construct( $strings ) { global $sitepress; $this->strings = $strings; if ( ! empty( $strings ) ) { $this->strings_in_page = icl_get_strings_tracked_in_pages( $strings ); } $this->additional_columns_to_render = wpml_collect(); $this->active_languages = $sitepress->get_active_languages(); } public function render() { ?> <table id="icl_string_translations" class="widefat" cellspacing="0"> <?php Fns::each( [ $this, 'updateColumnsForString' ], $this->strings ); $this->render_table_header_or_footer( 'thead' ); $this->render_table_header_or_footer( 'tfoot' ); ?> <tbody> <?php if ( empty( $this->strings ) ) { ?> <tr> <td colspan="6" align="center"> <?php esc_html_e( 'No strings found', 'wpml-string-translation' ); ?> </td> </tr> <?php } else { foreach ( $this->strings as $string_id => $icl_string ) { $this->render_string_row( $string_id, $icl_string ); } } ?> </tbody> </table> <?php } private function render_table_header_or_footer( $tag ) { $codes = Obj::keys( $this->active_languages ); $getFlagData = function ( $langData ) { return [ 'title' => $langData['display_name'], 'flagUrl' => Languages::getFlagUrl( $langData['code'] ), 'code' => $langData['code'], ]; }; $getFlagImg = function ($langData) { if ($langData['flagUrl'] !== '') { return '<img src="'.esc_attr( $langData['flagUrl'] ).'" alt="'.esc_attr( $langData['title'] ).'" >'; } else { return $langData['code']; } }; $makeFlag = function ( $langData ) use ($getFlagImg) { ob_start(); ?> <span data-code="<?php esc_attr_e( $langData['code'] ); ?>" title="<?php esc_attr_e( $langData['title'] ); ?>" > <?php echo $getFlagImg($langData) ?> </span> <?php return ob_get_clean(); }; $flags = Wrapper::of( $this->active_languages ) ->map( Fns::map( $getFlagData ) ) ->map( Fns::map( $makeFlag ) ) ->map( Fns::reduce( WPML\FP\Str::concat(), '' ) ); ?> <<?php echo $tag; ?>> <tr> <td scope="col" class="manage-column column-cb check-column"><input type="checkbox"/></td> <th scope="col"><?php esc_html_e( 'Domain', 'wpml-string-translation' ); ?></th> <?php if ( $this->additional_columns_to_render->contains( 'context' ) ) : ?> <th scope="col"><?php esc_html_e( 'Context', 'wpml-string-translation' ); ?></th> <?php endif; ?> <?php if ( $this->additional_columns_to_render->contains( 'name' ) ) : ?> <th scope="col"><?php esc_html_e( 'Name', 'wpml-string-translation' ); ?></th> <?php endif; ?> <?php if ( $this->additional_columns_to_render->contains( 'view' ) ) : ?> <th scope="col"><?php esc_html_e( 'View', 'wpml-string-translation' ); ?></th> <?php endif; ?> <th scope="col"><?php esc_html_e( 'String', 'wpml-string-translation' ); ?></th> <th scope="col" class="wpml-col-languages" data-langs="<?php echo esc_attr( (string) json_encode( $codes ) ); ?>"><?php echo $flags->get(); ?></th> </tr> <<?php echo $tag; ?>> <?php } public function render_string_row( $string_id, $icl_string ) { global $wpdb, $sitepress, $WPML_String_Translation; $icl_string = $this->decodeHtmlEntitiesForStringAndTranslations( $icl_string ); ?> <tr valign="top" data-string="<?php echo esc_attr( htmlentities( (string) json_encode( $icl_string ), ENT_QUOTES ) ); ?>"> <?php echo $this->render_checkbox_cell( $icl_string ); ?> <td class="wpml-st-col-domain"><?php echo esc_html( $icl_string['context'] ); ?></td> <?php if ( $this->additional_columns_to_render->contains( 'context' ) ) : ?> <td class="wpml-st-col-context"><?php echo esc_html( $icl_string['gettext_context'] ); ?></td> <?php endif; ?> <?php if ( $this->additional_columns_to_render->contains( 'name' ) ) : ?> <td class="wpml-st-col-name"><?php echo esc_html( $this->hide_if_md5( $icl_string['name'] ) ); ?></td> <?php endif; ?> <?php if ( $this->additional_columns_to_render->contains( 'view' ) ) : ?> <td class="wpml-st-col-view" nowrap="nowrap"> <?php $this->render_view_column( $string_id ); ?> </td> <?php endif; ?> <td class="wpml-st-col-string"> <div class="icl-st-original"<?php _icl_string_translation_rtl_div( $icl_string['string_language'] ); ?>> <img width="18" height="12" src="<?php echo esc_url( $sitepress->get_flag_url( $icl_string['string_language'] ) ); ?>"> <?php echo esc_html( $icl_string['value'] ); ?> </div> <input type="hidden" id="icl_st_wc_<?php echo esc_attr( $string_id ); ?>" value=" <?php echo $WPML_String_Translation->estimate_word_count( $icl_string['value'], $icl_string['string_language'] ) ?> "/> </td> <td class="languages-status wpml-col-languages"></td> </tr> <?php } private function decodeHtmlEntitiesForStringAndTranslations( $string ) { $decode = function( $string ) { return is_null( $string ) ? '' : partialRight( 'html_entity_decode', ENT_QUOTES )( $string ); }; $string['value'] = $decode( $string['value'] ); $string['name'] = $decode( $string['name'] ); if ( Obj::prop( 'translations', $string ) ) { $string['translations'] = Fns::map( Obj::over( Obj::lensProp( 'value' ), $decode ), $string['translations'] ); } return $string; } /** * @param array $string * * @return string html for the checkbox and the table cell it resides in */ private function render_checkbox_cell( $string ) { $class = 'icl_st_row_cb' . ( ! empty( $string['string_package_id'] ) ? ' icl_st_row_package' : '' ); return '<td><input class="' . esc_attr( $class ) . '" type="checkbox" value="' . esc_attr( $string['string_id'] ) . '" data-language="' . esc_attr( $string['string_language'] ) . '" /></td>'; } private function render_view_column( $string_id ) { if ( isset( $this->strings_in_page[ ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_SOURCE ][ $string_id ] ) ) { $thickbox_url = $this->get_thickbox_url( WPML_ST_String_Tracking_AJAX_Factory::ACTION_POSITION_IN_SOURCE, $string_id ); ?> <a class="thickbox" title="<?php esc_attr_e( 'view in source', 'wpml-string-translation' ); ?>" href="<?php echo esc_url( $thickbox_url ); ?>"> <img src="<?php echo WPML_ST_URL; ?>/res/img/view-in-source.png" width="16" height="16" alt="<?php esc_attr_e( 'view in page', 'wpml-string-translation' ); ?>"/> </a> <?php } if ( isset( $this->strings_in_page[ ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_PAGE ][ $string_id ] ) ) { $thickbox_url = $this->get_thickbox_url( WPML_ST_String_Tracking_AJAX_Factory::ACTION_POSITION_IN_PAGE, $string_id ); ?> <a class="thickbox" title="<?php esc_attr_e( 'view in page', 'wpml-string-translation' ); ?>" href="<?php echo esc_url( $thickbox_url ); ?>"> <img src="<?php echo WPML_ST_URL; ?>/res/img/view-in-page.png" width="16" height="16" alt="<?php esc_attr_e( 'view in page', 'wpml-string-translation' ); ?>"/> </a> <?php } } /** * @param string $action * @param int $string_id * * @return string */ private function get_thickbox_url( $action, $string_id ) { return add_query_arg( array( 'page' => WPML_ST_FOLDER . '/menu/string-translation.php', 'action' => $action, 'nonce' => wp_create_nonce( $action ), 'string_id' => $string_id, 'width' => 810, 'height' => 600, ), 'admin-ajax.php' ); } private function hide_if_md5( $str ) { return preg_replace( '#^((.+)( - ))?([a-z0-9]{32})$#', '$2', $str ); } /** * @param array<string,string|int> $string */ public function updateColumnsForString( $string ) { if ( ! $this->additional_columns_to_render->contains( 'context' ) && $string['gettext_context'] ) { $this->additional_columns_to_render->push( 'context' ); } if ( ! $this->additional_columns_to_render->contains( 'name' ) && $this->hide_if_md5( $string['name'] ) ) { $this->additional_columns_to_render->push( 'name' ); } if ( ! $this->additional_columns_to_render->contains( 'view' ) && $this->is_string_tracked( $string['string_id'] ) ) { $this->additional_columns_to_render->push( 'view' ); } } private function is_string_tracked( $string_id ) { $tracked_source = Obj::prop( ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_SOURCE, $this->strings_in_page ); $tracked_page = Obj::prop( ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_PAGE, $this->strings_in_page ); return ( $tracked_source && Obj::prop( $string_id, $tracked_source ) ) || ( $tracked_page && Obj::prop( $string_id, $tracked_page ) ); } } string-translation-ui/UI.php 0000755 00000002403 14720402445 0012053 0 ustar 00 <?php namespace WPML\ST\Main; use WPML\Element\API\Languages; use WPML\FP\Relation; use WPML\ST\Main\Ajax\FetchCompletedStrings; use WPML\ST\Main\Ajax\SaveTranslation; use WPML\ST\WP\App\Resources; use WPML\LIB\WP\Hooks as WPHooks; class UI implements \IWPML_Backend_Action_Loader { /** * @return callable|null */ public function create() { $isAdminTextsPage = isset( $_GET['trop'] ); if ( Relation::propEq( 'page', WPML_ST_FOLDER . '/menu/string-translation.php', $_GET ) && ! $isAdminTextsPage ) { return function () { WPHooks::onAction( 'admin_enqueue_scripts' ) ->then( [ self::class, 'localize' ] ) ->then( Resources::enqueueApp( 'main-ui' ) ); }; } else { return null; } } public static function localize() { /** @var array $languages */ $languages = Languages::withFlags( Languages::getAll() ); return [ 'name' => 'wpml_st_main_ui', 'data' => [ 'defaultLang' => Languages::getDefaultCode(), 'languageDetails' => Languages::withRtl( $languages ), 'endpoints' => [ 'saveTranslation' => SaveTranslation::class, 'translationMemory' => apply_filters( 'wpml_st_translation_memory_endpoint', '' ), 'fetchStrings' => FetchCompletedStrings::class, ], ], ]; } } string-translation-ui/class-wpml-translation-priority-select.php 0000755 00000002232 14720402445 0021310 0 ustar 00 <?php /** * Created by OnTheGoSystems */ class WPML_Translation_Priority_Select extends WPML_Templates_Factory { const NONCE = 'wpml_change_string_translation_priority_nonce'; public function get_model() { $model = array( 'translation_priorities' => get_terms( array( 'taxonomy' => 'translation_priority', 'hide_empty' => false, ) ), 'nonce' => wp_nonce_field( self::NONCE, self::NONCE, true, false ), 'strings' => array( 'empty_text' => __( 'Change translation priority of selected strings', 'wpml-string-translation' ), ), ); $this->enqueue_scripts(); return $model; } public function init_template_base_dir() { $this->template_paths = array( WPML_ST_PATH . '/templates/translation-priority/', ); } public function get_template() { return 'translation-priority-select.twig'; } private function enqueue_scripts() { if ( ! wp_script_is( 'wpml-select-2' ) ) { // Enqueue in the footer because this is usually called late. wp_enqueue_script( 'wpml-select-2', ICL_PLUGIN_URL . '/lib/select2/select2.min.js', array( 'jquery' ), ICL_SITEPRESS_VERSION, true ); } } } string-translation-ui/class-wpml-change-string-language-select.php 0000755 00000003020 14720402445 0021401 0 ustar 00 <?php class WPML_Change_String_Language_Select { /** * @var wpdb */ private $wpdb; /** * @var SitePress */ private $sitepress; /** * @param wpdb $wpdb * @param SitePress $sitepress */ public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } public function show() { $lang_selector = new WPML_Simple_Language_Selector( $this->sitepress ); echo $lang_selector->render( array( 'id' => 'icl_st_change_lang_selected', 'class' => 'wpml-select2-button', 'please_select_text' => __( 'Change the language of selected strings', 'wpml-string-translation' ), 'disabled' => true, ) ); } /** * @param int[] $strings * @param string $lang * * @return array */ public function change_language_of_strings( $strings, $lang ) { $package_translation = new WPML_Package_Helper(); $response = $package_translation->change_language_of_strings( $strings, $lang ); if ( $response['success'] ) { $strings_in = implode( ',', $strings ); $update_query = "UPDATE {$this->wpdb->prefix}icl_strings SET language=%s WHERE id IN ($strings_in)"; $update_prepare = $this->wpdb->prepare( $update_query, $lang ); $this->wpdb->query( $update_prepare ); $response['success'] = true; foreach ( $strings as $string ) { icl_update_string_status( $string ); } do_action( 'wpml_st_language_of_strings_changed', $strings ); } return $response; } } container/Config.php 0000755 00000002324 14720402445 0010452 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\Container; class Config { public static function getSharedClasses() { return [ \WPML\ST\StringsCleanup\UntranslatedStrings::class, \WPML\ST\Gettext\AutoRegisterSettings::class, \WPML\ST\Gettext\Hooks::class, \WPML\ST\Gettext\Settings::class, \WPML\ST\MO\LoadedMODictionary::class, \WPML\ST\MO\File\Manager::class, \WPML\ST\MO\File\Builder::class, \WPML\ST\Package\Domains::class, \WPML\ST\StringsFilter\Provider::class, \WPML\ST\TranslationFile\Domains::class, \WPML_String_Translation::class, \WPML_ST_Blog_Name_And_Description_Hooks::class, \WPML_ST_Settings::class, \WPML_ST_String_Factory::class, \WPML_ST_Upgrade::class, \WPML_Theme_Localization_Type::class, \WPML_ST_Translations_File_Dictionary_Storage_Table::class, \WPML\ST\TranslationFile\Sync\TranslationUpdates::class, ]; } public static function getAliases() { return [ \WPML_ST_Translations_File_Dictionary_Storage::class => \WPML_ST_Translations_File_Dictionary_Storage_Table::class, ]; } public static function getDelegated() { return [ \WPML_Admin_Texts::class => function() { return wpml_st_load_admin_texts(); }, ]; } } filters/strings-filter/Translations.php 0000755 00000001162 14720402445 0014367 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class Translations { /** @var \SplObjectStorage */ private $data; public function __construct() { $this->data = new TranslationsObjectStorage(); } /** * @param StringEntity $string * @param TranslationEntity $translation */ public function add( StringEntity $string, TranslationEntity $translation ) { $this->data->attach( $string, $translation ); } /** * @param StringEntity $string * * @return TranslationEntity|null */ public function get( StringEntity $string ) { return $this->data->contains( $string ) ? $this->data[ $string ] : null; } } filters/strings-filter/TranslationsObjectStorage.php 0000755 00000001026 14720402445 0017042 0 ustar 00 <?php namespace WPML\ST\StringsFilter; use WPML\ST\StringsFilter\StringEntity; /** * This storage in used internally in "Translations" class. Unfortunately, I cannot use anonymous classes due to PHP Version limitation. */ class TranslationsObjectStorage extends \SplObjectStorage { /** * @param StringEntity $o * * @return string */ #[\ReturnTypeWillChange] public function getHash( $o ) { return implode( '_', [ $o->getValue(), $o->getName(), $o->getDomain(), $o->getContext(), ] ); } } filters/strings-filter/StringEntity.php 0000755 00000002111 14720402445 0014344 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class StringEntity { /** @var string|bool */ private $value; /** @var string */ private $name; /** @var string */ private $domain; /** @var string */ private $context; /** * @param string|bool $value * @param string $name * @param string $domain * @param string $context */ public function __construct( $value, $name, $domain, $context = '' ) { $this->value = $value; $this->name = $name; $this->domain = $domain; $this->context = $context; } /** * @return string|bool */ public function getValue() { return $this->value; } /** * @return string */ public function getName() { return $this->name; } /** * @return string */ public function getDomain() { return $this->domain; } /** * @return string */ public function getContext() { return $this->context; } /** * @param array $data * * @return StringEntity */ public static function fromArray( array $data ) { return new self( $data['value'], $data['name'], $data['domain'], $data['context'] ); } } filters/strings-filter/TranslationEntity.php 0000755 00000001470 14720402445 0015403 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class TranslationEntity { /** @var string */ private $value; /** @var bool */ private $hasTranslation; /** @var bool */ private $stringRegistered; /** * @param bool|string $value * @param bool $hasTranslation * @param bool $stringRegistered */ public function __construct( $value, $hasTranslation, $stringRegistered = true ) { $this->value = $value; $this->hasTranslation = $hasTranslation; $this->stringRegistered = $stringRegistered; } /** * @return string */ public function getValue() { return $this->value; } /** * @return bool */ public function isStringRegistered() { return $this->stringRegistered; } /** * @return bool */ public function hasTranslation() { return $this->hasTranslation; } } filters/strings-filter/Translator.php 0000755 00000001717 14720402445 0014045 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class Translator { /** @var string */ private $language; /** @var TranslationReceiver */ private $translationReceiver; /** @var Translations */ private $translations; /** * @param string $language * @param TranslationReceiver $translationReceiver */ public function __construct( $language, TranslationReceiver $translationReceiver ) { $this->language = $language; $this->translationReceiver = $translationReceiver; } /** * @param StringEntity $string * * @return TranslationEntity */ public function translate( StringEntity $string ) { if ( $this->translations === null ) { $this->translations = new Translations(); } $translation = $this->translations->get( $string ); if ( ! $translation ) { $translation = $this->translationReceiver->get( $string, $this->language ); $this->translations->add( $string, $translation ); } return $translation; } } filters/strings-filter/QueryBuilder.php 0000755 00000003154 14720402445 0014325 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class QueryBuilder { /** @var \wpdb */ private $wpdb; /** @var string|null $language */ private $language; /** @var string */ private $where; public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string $language * * @return $this */ public function setLanguage( $language ) { $this->language = $language; return $this; } /** * @param array $domains * * @return $this */ public function filterByDomains( array $domains ) { $in = \wpml_prepare_in( $domains ); $this->where = "s.context IN({$in})"; return $this; } /** * @param StringEntity $string * * @return $this */ public function filterByString( StringEntity $string ) { $this->where = $this->wpdb->prepare( 's.name = %s AND s.context = %s AND s.gettext_context = %s', $string->getName(), $string->getDomain(), $string->getContext() ); return $this; } /** * @return string */ public function build() { $result = $this->getSQL(); if ( $this->where ) { $result .= ' WHERE ' . $this->where; } return $result; } /** * @return string */ private function getSQL() { return $this->wpdb->prepare( " SELECT s.value, s.name, s.context as domain, s.gettext_context as context, IF(st.status = %d AND st.value IS NOT NULL, st.`value`, st.mo_string) AS `translation` FROM {$this->wpdb->prefix}icl_strings s LEFT JOIN {$this->wpdb->prefix}icl_string_translations st ON st.string_id = s.id AND st.`language` = %s ", ICL_STRING_TRANSLATION_COMPLETE, $this->language ); } } filters/strings-filter/class-wpml-register-string-filter.php 0000755 00000026235 14720402445 0020411 0 ustar 00 <?php /** * WPML_Register_String_Filter class file. * * @package WPML\ST */ use WPML\ST\StringsFilter\Translator; /** * Class WPML_Register_String_Filter */ class WPML_Register_String_Filter extends WPML_Displayed_String_Filter { /** * WP DB instance. * * @var wpdb */ protected $wpdb; /** @var SitePress */ protected $sitepress; /** * @var array */ private $excluded_contexts = array(); /** * @var WPML_WP_Cache */ private $registered_string_cache; /** @var WPML_ST_String_Factory $string_factory */ private $string_factory; /** * @var WPML_Autoregister_Save_Strings */ private $save_strings; // Current string data. protected $name; protected $domain; protected $gettext_context; protected $name_and_gettext_context; protected $key; /** @var bool $block_save_strings */ private $block_save_strings = false; /** * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_ST_String_Factory $string_factory * @param Translator $translator * @param array $excluded_contexts * @param WPML_Autoregister_Save_Strings|null $save_strings */ public function __construct( $wpdb, SitePress $sitepress, &$string_factory, Translator $translator, array $excluded_contexts = array(), WPML_Autoregister_Save_Strings $save_strings = null ) { parent::__construct( $translator ); $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->string_factory = &$string_factory; $this->excluded_contexts = $excluded_contexts; $this->save_strings = $save_strings; $this->registered_string_cache = new WPML_WP_Cache( 'WPML_Register_String_Filter' ); } public function translate_by_name_and_context( $untranslated_text, $name, $context = '', &$has_translation = null ) { if ( is_array( $untranslated_text ) || is_object( $untranslated_text ) ) { return ''; } $translation = $this->get_translation( $untranslated_text, $name, $context ); $has_translation = $translation->hasTranslation(); if ( ! $translation->isStringRegistered() && $this->can_register_string( $untranslated_text, $name, $context ) ) { list ( $name, $domain, $gettext_content ) = $this->transform_parameters( $name, $context ); list( $name, $domain ) = array_map( array( $this, 'truncate_long_string' ), array( $name, $domain ) ); if ( ! in_array( $domain, $this->excluded_contexts ) ) { $save_strings = $this->get_save_strings(); $save_strings->save( $untranslated_text, $name, $domain, $gettext_content ); } } return $translation->getValue(); } private function can_register_string( $original_value, $name, $context ) { return $original_value || ( $name && md5( '' ) !== $name && $context ); } public function force_saving_of_autoregistered_strings() { $this->get_save_strings()->shutdown(); } public function register_string( $context, $name, $value, $allow_empty_value = false, $source_lang = '' ) { $name = trim( $name ) ? $name : md5( $value ); $this->initialize_current_string( $name, $context ); /* cpt slugs - do not register them when scanning themes and plugins * if name starting from 'URL slug: ' * and context is different from 'WordPress' */ if ( substr( $name, 0, 10 ) === 'URL slug: ' && WPML_Slug_Translation::STRING_DOMAIN !== $context ) { return false; } list( $domain, $context, $key ) = $this->key_by_name_and_context( $name, $context ); list( $name, $context ) = $this->truncate_name_and_context( $name, $context ); if ( $source_lang == '' ) { $source_lang = $this->get_save_strings()->get_source_lang( $name, $domain ); } $res = $this->get_registered_string( $domain, $context, $name ); if ( $res ) { $string_id = $res['id']; $update_string = array(); if ( $value != $res['value'] ) { $update_string['value'] = $value; } $existing_lang = $this->string_factory->find_by_id( $res['id'] )->get_language(); if ( ! empty( $update_string ) ) { if ( $existing_lang == $source_lang ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', $update_string, array( 'id' => $string_id ) ); $this->wpdb->update( $this->wpdb->prefix . 'icl_string_translations', array( 'status' => ICL_TM_NEEDS_UPDATE ), array( 'string_id' => $string_id ) ); icl_update_string_status( $string_id ); } else { $orig_data = array( 'string_id' => $string_id, 'language' => $source_lang, ); $update_string['status'] = ICL_TM_COMPLETE; if ( $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$this->wpdb->prefix}icl_string_translations WHERE string_id = %d AND language = %s", $string_id, $source_lang ) ) ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_string_translations', $update_string, $orig_data ); } else { $this->wpdb->insert( $this->wpdb->prefix . 'icl_string_translations', array_merge( $update_string, $orig_data ) ); } icl_update_string_status( $string_id ); } } } else { $string_id = $this->save_string( $value, $allow_empty_value, $source_lang, $domain, $context, $name ); } return $string_id; } /** * @param string $domain * @param string $context * @param string $name * * @return array|false */ private function get_registered_string( $domain, $context, $name ) { $key = md5( $domain . $name . $context ); $found = false; return $this->get_domain_cache( $domain )->get( $key, $found ); } private function save_string( $value, $allow_empty_value, $language, $domain, $context, $name ) { if ( ! $this->block_save_strings && ( $allow_empty_value || 0 !== strlen( $value ) ) ) { $args = array( 'language' => $language, 'context' => $domain, 'gettext_context' => $context, 'domain_name_context_md5' => md5( $domain . $name . $context ), 'name' => $name, 'value' => $value, 'status' => ICL_TM_NOT_TRANSLATED, ); $query_values = array( '%s', '%s', '%s', '%s', '%s', '%s', '%d' ); if ( class_exists( 'WPML_TM_Translation_Priorities' ) ) { $args['translation_priority'] = WPML_TM_Translation_Priorities::DEFAULT_TRANSLATION_PRIORITY_VALUE_SLUG; $query_values[] = '%s'; } $query_values = implode( ', ', $query_values ); $query_columns = implode( ', ', array_keys( $args ) ); $query = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_strings ({$query_columns}) VALUES ( {$query_values} )"; $this->wpdb->query( $this->wpdb->prepare( $query, $args ) ); $string_id = $this->wpdb->insert_id; if ( $string_id === 0 ) { if ( empty( $this->wpdb->last_error ) ) { $string_id = $this->get_string_id_registered_in_concurrent_request( $args ); } else { $input_args = $args; $input_args['allow_empty_value'] = $allow_empty_value; $string_id = $this->handle_db_error_and_resave_string( $input_args ); } } icl_update_string_status( $string_id ); $key = md5( $domain . $name . $context ); $cached_value = array( 'id' => $string_id, 'value' => $value, ); $this->get_domain_cache( $domain )->set( $key, $cached_value ); } else { $string_id = 0; } return $string_id; } /** * @param array $args * * @return int */ private function handle_db_error_and_resave_string( array $args ) { $repair_schema = new WPML_ST_Repair_Strings_Schema( wpml_get_admin_notices(), $args, $this->wpdb->last_error ); if ( false !== strpos( $this->wpdb->last_error, 'translation_priority' ) ) { $repair_schema->set_command( new WPML_ST_Upgrade_DB_Strings_Add_Translation_Priority_Field( $this->wpdb ) ); } if ( $repair_schema->run() ) { $string_id = $this->save_string( $args['value'], $args['allow_empty_value'], $args['language'], $args['context'], $args['gettext_context'], $args['name'] ); } else { $string_id = 0; $this->block_save_strings = true; } return $string_id; } /** * @param array $args * * @return int */ private function get_string_id_registered_in_concurrent_request( array $args ) { return (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE domain_name_context_md5 = %s", md5( $args['context'] . $args['name'] . $args['gettext_context'] ) ) ); } /** * @param string $name * @param string|string[] $context */ protected function initialize_current_string( $name, $context ) { list ( $this->domain, $this->gettext_context ) = wpml_st_extract_context_parameters( $context ); list( $this->name, $this->domain ) = array_map( array( $this, 'truncate_long_string', ), array( $name, $this->domain ) ); $this->name_and_gettext_context = $this->name . $this->gettext_context; $this->key = md5( $this->domain . $this->name_and_gettext_context ); } /** * @param string $name * @param string|string[] $context * * @return array */ protected function truncate_name_and_context( $name, $context ) { if ( is_array( $context ) ) { $domain = isset( $context['domain'] ) ? $context['domain'] : ''; $gettext_context = isset( $context['context'] ) ? $context['context'] : ''; } else { $domain = $context; $gettext_context = ''; } list( $name, $domain ) = array_map( array( $this, 'truncate_long_string', ), array( $name, $domain ) ); return array( $name . $gettext_context, $domain ); } protected function key_by_name_and_context( $name, $context ) { return array( $this->domain, $this->gettext_context, md5( $this->domain . $this->name_and_gettext_context ), ); } /** * @return WPML_Autoregister_Save_Strings */ private function get_save_strings() { if ( null === $this->save_strings ) { $this->save_strings = new WPML_Autoregister_Save_Strings( $this->wpdb, $this->sitepress ); } return $this->save_strings; } /** * @param string $domain * * @return WPML_WP_Cache */ private function get_domain_cache( $domain ) { $found = false; $this->registered_string_cache->get( $domain, $found ); if ( ! $found ) { // preload all the strings for this domain. $query = $this->wpdb->prepare( "SELECT id, value, gettext_context, name FROM {$this->wpdb->prefix}icl_strings WHERE context=%s", $domain ); $res = $this->wpdb->get_results( $query ); $domain_cache = new WPML_WP_Cache( 'WPML_Register_String_Filter::' . $domain ); foreach ( $res as $string ) { $key = md5( $domain . $string->name . $string->gettext_context ); $cached_value = array( 'id' => $string->id, 'value' => $string->value, ); $domain_cache->set( $key, $cached_value ); } $this->registered_string_cache->set( $domain, $domain_cache ); } return $this->registered_string_cache->get( $domain, $found ); } } filters/strings-filter/class-wpml-displayed-string-filter.php 0000755 00000006001 14720402445 0020530 0 ustar 00 <?php /** * WPML_Displayed_String_Filter class file. * * @package WPML\ST */ use WPML\ST\StringsFilter\Translator; use WPML\ST\StringsFilter\StringEntity; use WPML\ST\StringsFilter\TranslationEntity; /** * Class WPML_Displayed_String_Filter * * Handles all string translating when rendering translated strings to the user, unless auto-registering is * active for strings. */ class WPML_Displayed_String_Filter { /** @var Translator */ protected $translator; /** * @param Translator $translator */ public function __construct( Translator $translator ) { $this->translator = $translator; } /** * Translate by name and context. * * @param string|bool $untranslated_text Untranslated text. * @param string $name Name of the string. * @param string|array $context Context. * @param null|boolean $has_translation If string has translation. * * @return string */ public function translate_by_name_and_context( $untranslated_text, $name, $context = '', &$has_translation = null ) { if ( is_array( $untranslated_text ) || is_object( $untranslated_text ) ) { return ''; } $translation = $this->get_translation( $untranslated_text, $name, $context ); $has_translation = $translation->hasTranslation(); return $translation->getValue(); } /** * Transform translation parameters. * * @param string|bool $name Name of the string. * @param string|array $context Context. * * @return array */ protected function transform_parameters( $name, $context ) { list ( $domain, $gettext_context ) = wpml_st_extract_context_parameters( $context ); return array( $name, $domain, $gettext_context ); } /** * Truncates a string to the maximum string table column width. * * @param string $string String to translate. * * @return string */ public static function truncate_long_string( $string ) { return strlen( $string ) > WPML_STRING_TABLE_NAME_CONTEXT_LENGTH ? mb_substr( $string, 0, WPML_STRING_TABLE_NAME_CONTEXT_LENGTH ) : $string; } /** * Get translation of the string. * * @param string|bool $untranslated_text Untranslated text. * @param string|bool $name Name of the string. * @param string|array $context Context. * * @return TranslationEntity */ protected function get_translation( $untranslated_text, $name, $context ) { list ( $name, $domain, $gettext_context ) = $this->transform_parameters( $name, $context ); $untranslated_text = is_numeric( $untranslated_text ) ? (string) $untranslated_text : $untranslated_text; $translation = $this->translator->translate( new StringEntity( $untranslated_text, $name, $domain, $gettext_context ) ); if ( ! $translation->hasTranslation() ) { list( $name, $domain ) = array_map( array( $this, 'truncate_long_string' ), array( $name, $domain ) ); $translation = $this->translator->translate( new StringEntity( $untranslated_text, $name, $domain, $gettext_context ) ); } return $translation; } } filters/strings-filter/TranslationReceiver.php 0000755 00000001631 14720402445 0015672 0 ustar 00 <?php namespace WPML\ST\StringsFilter; class TranslationReceiver { /** @var \wpdb */ private $wpdb; /** @var QueryBuilder $query_builder */ private $query_builder; public function __construct( \wpdb $wpdb, QueryBuilder $query_builder ) { $this->wpdb = $wpdb; $this->query_builder = $query_builder; } /** * @param StringEntity $string * @param string $language * * @return TranslationEntity */ public function get( StringEntity $string, $language ) { $query = $this->query_builder->setLanguage( $language )->filterByString( $string )->build(); $record = $this->wpdb->get_row( $query, ARRAY_A ); if ( ! $record ) { return new TranslationEntity( $string->getValue(), false, false ); } if ( ! $record['translation'] ) { return new TranslationEntity( $record['value'], false, true ); } return new TranslationEntity( $record['translation'], true, true ); } } filters/strings-filter/Provider.php 0000755 00000002153 14720402445 0013501 0 ustar 00 <?php namespace WPML\ST\StringsFilter; use WPML_Displayed_String_Filter; use WPML_Register_String_Filter; use WPML_String_Translation; class Provider { /** @var WPML_String_Translation */ private $string_translation; /** @var WPML_Displayed_String_Filter[]|WPML_Register_String_Filter[] */ private $filters = []; public function __construct( WPML_String_Translation $string_translation ) { $this->string_translation = $string_translation; } /** * Get filter. * * @param string|null $lang Language. * @param string|null|bool $name Language name. * * @return WPML_Displayed_String_Filter|WPML_Register_String_Filter|null */ public function getFilter( $lang = null, $name = null ) { if ( ! $lang ) { $lang = $this->string_translation->get_current_string_language( $name ); } if ( ! $lang ) { return null; } if ( ! ( array_key_exists( $lang, $this->filters ) && $this->filters[ $lang ] ) ) { $this->filters[ $lang ] = $this->string_translation->get_string_filter( $lang ); } return $this->filters[ $lang ]; } public function clearFilters() { $this->filters = []; } } filters/taxonomy-strings/wpml-st-taxonomy-strings.php 0000755 00000012043 14720402445 0017245 0 ustar 00 <?php class WPML_ST_Taxonomy_Strings { const CONTEXT_GENERAL = 'taxonomy general name'; const CONTEXT_SINGULAR = 'taxonomy singular name'; const LEGACY_NAME_PREFIX_GENERAL = 'taxonomy general name: '; const LEGACY_NAME_PREFIX_SINGULAR = 'taxonomy singular name: '; const LEGACY_STRING_DOMAIN = 'WordPress'; /** @var WPML_Tax_Slug_Translation_Records $slug_translation_records */ private $slug_translation_records; /** @var WPML_ST_String_Factory $string_factory */ private $string_factory; private $translated_with_gettext_context = array(); public function __construct( WPML_Tax_Slug_Translation_Records $slug_translation_records, WPML_ST_String_Factory $string_factory ) { $this->slug_translation_records = $slug_translation_records; $this->string_factory = $string_factory; } /** * @param string $text * @param string $domain */ public function add_to_translated_with_gettext_context( $text, $domain ) { if ( ! in_array( $text, $this->translated_with_gettext_context, true ) ) { $this->translated_with_gettext_context[ $text ] = $domain; } } /** * @param string $text * @param string $gettext_context * @param string $domain * @param false|string $name * * @return int */ private function find_string_id( $text, $gettext_context = '', $domain = '', $name = false ) { $context = $this->get_context( $domain, $gettext_context ); return $this->string_factory->get_string_id( $text, $context, $name ); } /** * @param string $text * @param string $gettext_context * @param string $domain * @param false|string $name * * @return int */ public function create_string_if_not_exist( $text, $gettext_context = '', $domain = '', $name = false ) { $string_id = $this->find_string_id( $text, $gettext_context, $domain, $name ); if ( ! $string_id ) { $context = $this->get_context( $domain, $gettext_context ); $string_id = icl_register_string( $context, (string) $name, $text ); } return $string_id; } private function get_context( $domain, $gettext_context ) { return array( 'domain' => $domain, 'context' => $gettext_context, ); } /** * @param string $taxonomy_name * * @return WPML_ST_String[] */ public function get_taxonomy_strings( $taxonomy_name ) { $taxonomy = get_taxonomy( $taxonomy_name ); if ( $taxonomy && isset( $taxonomy->label ) && isset( $taxonomy->labels->singular_name ) ) { $general_string = $this->get_label_string( $taxonomy->label, 'general' ); $singular_string = $this->get_label_string( $taxonomy->labels->singular_name, 'singular' ); $slug_string = $this->get_slug_string( $taxonomy ); return array( $general_string, $singular_string, $slug_string ); } return null; } /** * @param string $value * @param string $general_or_singular * * @return WPML_ST_String|null */ private function get_label_string( $value, $general_or_singular ) { $string = $this->get_label_string_details( $value, $general_or_singular ); $string_id = $this->find_string_id( $value, $string['context'], $string['domain'], $string['name'] ); if ( ! $string_id ) { $string_id = $this->create_string_if_not_exist( $value, $string['context'], $string['domain'], $string['name'] ); } if ( $string_id ) { return $this->string_factory->find_by_id( $string_id ); } return null; } /** * @param WP_Taxonomy $taxonomy * * @return WPML_ST_String */ private function get_slug_string( $taxonomy ) { $string_id = $this->slug_translation_records->get_slug_id( $taxonomy->name ); if ( ! $string_id ) { $slug = isset( $taxonomy->rewrite['slug'] ) ? trim( $taxonomy->rewrite['slug'], '/' ) : $taxonomy->name; $string_id = $this->slug_translation_records->register_slug( $taxonomy->name, $slug ); } return $this->string_factory->find_by_id( $string_id ); } /** * @param string $value * @param string $general_or_singular * * @return array */ private function get_label_string_details( $value, $general_or_singular ) { $string_meta = array( 'context' => '', 'domain' => '', 'name' => false, ); if ( $this->is_string_translated_with_gettext_context( $value ) ) { $string_meta['domain'] = $this->get_domain_for_taxonomy( $value ); $string_meta['context'] = self::CONTEXT_SINGULAR; if ( 'general' === $general_or_singular ) { $string_meta['context'] = self::CONTEXT_GENERAL; } } else { $string_meta['domain'] = self::LEGACY_STRING_DOMAIN; $string_meta['name'] = self::LEGACY_NAME_PREFIX_SINGULAR . $value; if ( 'general' === $general_or_singular ) { $string_meta['name'] = self::LEGACY_NAME_PREFIX_GENERAL . $value; } } return $string_meta; } /** * @param string $value * * @return bool */ private function is_string_translated_with_gettext_context( $value ) { return array_key_exists( $value, $this->translated_with_gettext_context ); } /** * @param string $value * * @return string */ private function get_domain_for_taxonomy( $value ) { return $this->translated_with_gettext_context[ $value ]; } } filters/class-wpml-st-blog-name-and-description-hooks.php 0000755 00000005253 14720402445 0017506 0 ustar 00 <?php class WPML_ST_Blog_Name_And_Description_Hooks implements \IWPML_Action { const STRING_DOMAIN = 'WP'; const STRING_NAME_BLOGNAME = 'Blog Title'; const STRING_NAME_BLOGDESCRIPTION = 'Tagline'; /** @var array $cache */ private $cache = []; /** * Detect if ST is not installed on the current blog of multisite * * @var bool $is_active_on_current_blog */ private $is_active_on_current_blog = true; public function add_hooks() { if ( ! $this->is_customize_page() ) { add_filter( 'option_blogname', [ $this, 'option_blogname_filter' ] ); add_filter( 'option_blogdescription', [ $this, 'option_blogdescription_filter' ] ); add_action( 'wpml_language_has_switched', [ $this, 'clear_cache' ] ); add_action( 'switch_blog', [ $this, 'switch_blog_action' ] ); } } /** @return bool */ private function is_customize_page() { global $pagenow; return 'customize.php' === $pagenow; } /** * @param string $blogname * * @return string */ public function option_blogname_filter( $blogname ) { return $this->translate_option( self::STRING_NAME_BLOGNAME, $blogname ); } /** * @param string $blogdescription * * @return string */ public function option_blogdescription_filter( $blogdescription ) { return $this->translate_option( self::STRING_NAME_BLOGDESCRIPTION, $blogdescription ); } /** * @param string $name * @param string $value * * @return string */ private function translate_option( $name, $value ) { if ( ! $this->is_active_on_current_blog || ! wpml_st_is_requested_blog() ) { return $value; } if ( ! isset( $this->cache[ $name ] ) ) { $this->cache[ $name ] = wpml_get_string_current_translation( $value, self::STRING_DOMAIN, $name ); } return $this->cache[ $name ]; } /** * As the translation depends on `WPML_String_Translation::get_current_string_language`, * we added this clear cache callback on `wpml_language_has_switched` as done * in `WPML_String_Translation::wpml_language_has_switched`. */ public function clear_cache() { $this->cache = []; } public function switch_blog_action() { $this->is_active_on_current_blog = is_plugin_active( basename( WPML_ST_PATH ) . '/plugin.php' ); } /** * @param string $string_name * * Checks whether a given string is to be translated in the Admin back-end. * Currently only tagline and title of a site are to be translated. * All other admin strings are to always be displayed in the user admin language. * * @return bool */ public static function is_string( $string_name ) { return in_array( $string_name, [ self::STRING_NAME_BLOGDESCRIPTION, self::STRING_NAME_BLOGNAME, ], true ); } } filters/class-wpml-st-taxonomy-labels-translation.php 0000755 00000016364 14720402445 0017122 0 ustar 00 <?php class WPML_ST_Taxonomy_Labels_Translation implements IWPML_Action { const NONCE_TAXONOMY_TRANSLATION = 'wpml_taxonomy_translation_nonce'; const PRIORITY_GET_LABEL = 10; /** @var WPML_ST_Taxonomy_Strings $taxonomy_strings */ private $taxonomy_strings; /** @var WPML_ST_Tax_Slug_Translation_Settings $slug_translation_settings */ private $slug_translation_settings; /** @var WPML_Super_Globals_Validation $super_globals */ private $super_globals; /** @var array $active_languages */ private $active_languages; public function __construct( WPML_ST_Taxonomy_Strings $taxonomy_strings, WPML_ST_Tax_Slug_Translation_Settings $slug_translation_settings, WPML_Super_Globals_Validation $super_globals, array $active_languages ) { $this->taxonomy_strings = $taxonomy_strings; $this->slug_translation_settings = $slug_translation_settings; $this->super_globals = $super_globals; $this->active_languages = $active_languages; } public function add_hooks() { add_filter( 'gettext_with_context', array( $this, 'block_translation_and_init_strings' ), PHP_INT_MAX, 4 ); add_filter( 'wpml_label_translation_data', array( $this, 'get_label_translations' ), self::PRIORITY_GET_LABEL, 2 ); add_action( 'wp_ajax_wpml_tt_save_labels_translation', array( $this, 'save_label_translations' ) ); add_action( 'wp_ajax_wpml_tt_change_tax_strings_language', array( $this, 'change_taxonomy_strings_language' ) ); } /** * @param string $translation * @param string $text * @param string $gettext_context * @param string $domain * * @return mixed */ public function block_translation_and_init_strings( $translation, $text, $gettext_context, $domain ) { if ( WPML_ST_Taxonomy_Strings::CONTEXT_GENERAL === $gettext_context || WPML_ST_Taxonomy_Strings::CONTEXT_SINGULAR === $gettext_context ) { $this->taxonomy_strings->create_string_if_not_exist( $text, $gettext_context, $domain ); $this->taxonomy_strings->add_to_translated_with_gettext_context( $text, $domain ); // We need to return the original string here so the rest of // the label translation UI works. return $text; } return $translation; } /** * @param false $false * @param string $taxonomy * * @return array|null */ public function get_label_translations( $false, $taxonomy ) { list( $general, $singular, $slug ) = $this->taxonomy_strings->get_taxonomy_strings( $taxonomy ); if ( ! $general || ! $singular || ! $slug ) { return null; } $source_lang = $general->get_language(); $general_translations = $this->get_translations( $general ); $singular_translations = $this->get_translations( $singular ); $slug_translations = $this->get_translations( $slug ); $data = array( 'st_default_lang' => $source_lang, ); foreach ( array_keys( $this->active_languages ) as $lang ) { if ( $lang === $source_lang ) { continue; } $data[ $lang ]['general'] = $this->get_translation_value( $lang, $general_translations ); $data[ $lang ]['singular'] = $this->get_translation_value( $lang, $singular_translations ); $data[ $lang ]['slug'] = $this->get_translation_value( $lang, $slug_translations ); $data[ $lang ] = array_filter( $data[ $lang ] ); } $data[ $source_lang ] = array( 'general' => $general->get_value(), 'singular' => $singular->get_value(), 'slug' => $slug->get_value(), 'original' => true, 'globalSlugTranslationEnabled' => $this->slug_translation_settings->is_enabled(), 'showSlugTranslationField' => true, ); return $data; } /** * @param WPML_ST_String $string * * @return array */ private function get_translations( WPML_ST_String $string ) { $translations = array(); foreach ( $string->get_translations() as $translation ) { $translations[ $translation->language ] = $translation; } return $translations; } /** * @param string $lang * @param array $translations * * @return string|null */ private function get_translation_value( $lang, array $translations ) { $value = null; if ( isset( $translations[ $lang ] ) ) { if ( $translations[ $lang ]->value ) { $value = $translations[ $lang ]->value; } elseif ( $translations[ $lang ]->mo_string ) { $value = $translations[ $lang ]->mo_string; } } return $value; } public function save_label_translations() { if ( ! $this->check_nonce() ) { return; } $general_translation = $this->get_string_var_from_post( 'plural' ); $singular_translation = $this->get_string_var_from_post( 'singular' ); $slug_translation = $this->get_string_var_from_post( 'slug' ); $taxonomy_name = $this->get_string_var_from_post( 'taxonomy' ); $language = $this->get_string_var_from_post( 'taxonomy_language_code' ); if ( $general_translation && $singular_translation && $taxonomy_name && $language ) { list( $general, $singular, $slug ) = $this->taxonomy_strings->get_taxonomy_strings( $taxonomy_name ); if ( $general && $singular && $slug ) { $general->set_translation( $language, $general_translation, ICL_STRING_TRANSLATION_COMPLETE ); $singular->set_translation( $language, $singular_translation, ICL_STRING_TRANSLATION_COMPLETE ); $slug->set_translation( $language, $slug_translation, ICL_STRING_TRANSLATION_COMPLETE ); $slug_translation_enabled = $this->has_slug_translation( $slug ); $this->slug_translation_settings->set_type( $taxonomy_name, $slug_translation_enabled ); $this->slug_translation_settings->save(); $result = array( 'general' => $general_translation, 'singular' => $singular_translation, 'slug' => $slug_translation, 'lang' => $language, ); wp_send_json_success( $result ); return; } } wp_send_json_error(); } private function has_slug_translation( WPML_ST_String $slug ) { $translations = $slug->get_translations(); if ( $translations ) { foreach ( $translations as $translation ) { if ( trim( $translation->value ) && ICL_STRING_TRANSLATION_COMPLETE === (int) $translation->status ) { return true; } } } return false; } public function change_taxonomy_strings_language() { if ( ! $this->check_nonce() ) { return; } $taxonomy = $this->get_string_var_from_post( 'taxonomy' ); $source_lang = $this->get_string_var_from_post( 'source_lang' ); if ( ! $taxonomy || ! $source_lang ) { wp_send_json_error( __( 'Missing parameters', 'wpml-string-translation' ) ); return; } list( $general_string, $singular_string, $slug ) = $this->taxonomy_strings->get_taxonomy_strings( $taxonomy ); $general_string->set_language( $source_lang ); $singular_string->set_language( $source_lang ); $slug->set_language( $source_lang ); wp_send_json_success(); } /** * @param string $key * * @return false|string */ private function get_string_var_from_post( $key ) { $value = $this->super_globals->post( $key ); return null !== $value ? sanitize_text_field( $value ) : false; } private function check_nonce() { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], self::NONCE_TAXONOMY_TRANSLATION ) ) { wp_send_json_error( __( 'Invalid nonce', 'wpml-string-translation' ) ); return false; } return true; } } filters/class-wpml-tm-filters.php 0000755 00000003740 14720402445 0013104 0 ustar 00 <?php class WPML_TM_Filters { /** @var array */ private $string_lang_codes; /** @var wpdb */ private $wpdb; /** @var SitePress */ private $sitepress; /** * WPML_TM_Filters constructor. * * @param wpdb $wpdb * @param SitePress $sitepress */ public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } /** * Filters the active languages to include all languages in which strings exist. * * @param WPML_Language_Collection $source_langs * * @return array[] */ public function filter_tm_source_langs( WPML_Language_Collection $source_langs ) { foreach ( $this->get_string_lang_codes() as $lang_code ) { $source_langs->add( $lang_code ); } return $source_langs; } private function get_string_lang_codes() { if ( null === $this->string_lang_codes ) { $this->string_lang_codes = $this->wpdb->get_col( "SELECT DISTINCT(s.language) FROM {$this->wpdb->prefix}icl_strings s" ); } return $this->string_lang_codes; } /** * This filters the check whether or not a job is assigned to a specific translator for local string jobs. * It is to be used after assigning a job, as it will update the assignment for local string jobs itself. * * @param bool $assigned_correctly * @param string|int $string_translation_id * @param int $translator_id * @param string|int $service * * @return bool */ public function job_assigned_to_filter( $assigned_correctly, $string_translation_id, $translator_id, $service ) { if ( ( ! $service || $service === 'local' ) && strpos( (string) $string_translation_id, 'string|' ) !== false ) { $string_translation_id = preg_replace( '/[^0-9]/', '', (string) $string_translation_id ); $this->wpdb->update( $this->wpdb->prefix . 'icl_string_translations', array( 'translator_id' => $translator_id ), array( 'id' => $string_translation_id ) ); $assigned_correctly = true; } return $assigned_correctly; } } filters/class-wpml-st-taxonomy-labels-translation-factory.php 0000755 00000003157 14720402445 0020563 0 ustar 00 <?php class WPML_ST_Taxonomy_Labels_Translation_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { const AJAX_ACTION_BUILD = 'wpml_get_terms_and_labels_for_taxonomy_table'; const AJAX_ACTION_SAVE = 'wpml_tt_save_labels_translation'; const AJAX_ACTION_CHANGE_LANG = 'wpml_tt_change_tax_strings_language'; const AJAX_ACTION_SET_SLUG_TRANSLATION_ENABLE = 'wpml_tt_set_slug_translation_enabled'; public function create() { global $sitepress; if ( $this->is_taxonomy_translation_table_action() ) { $records_factory = new WPML_Slug_Translation_Records_Factory(); $taxonomy_strings = new WPML_ST_Taxonomy_Strings( $records_factory->createTaxRecords(), WPML\Container\make( WPML_ST_String_Factory::class ) ); $hooks[] = new WPML_ST_Taxonomy_Labels_Translation( $taxonomy_strings, new WPML_ST_Tax_Slug_Translation_Settings(), new WPML_Super_Globals_Validation(), $sitepress->get_active_languages( true ) ); if ( $this->is_wcml_active() ) { $hooks[] = new WPML_ST_WCML_Taxonomy_Labels_Translation(); } return $hooks; } return null; } private function is_taxonomy_translation_table_action() { $allowed_actions = array( self::AJAX_ACTION_BUILD, self::AJAX_ACTION_SAVE, self::AJAX_ACTION_CHANGE_LANG, self::AJAX_ACTION_SET_SLUG_TRANSLATION_ENABLE, ); return isset( $_POST['action'] ) && in_array( $_POST['action'], $allowed_actions, true ); } private function is_wcml_active() { return is_plugin_active( 'woocommerce-multilingual/wpml-woocommerce.php' ); } } filters/class-wpml-st-wcml-taxonomy-labels-translation.php 0000755 00000002030 14720402445 0020043 0 ustar 00 <?php class WPML_ST_WCML_Taxonomy_Labels_Translation implements IWPML_Action { public function add_hooks() { add_filter( 'wpml_label_translation_data', array( $this, 'alter_slug_translation_display' ), WPML_ST_Taxonomy_Labels_Translation::PRIORITY_GET_LABEL + 1, 2 ); } /** * @param array $data * @param string $taxonomy * * @return array */ public function alter_slug_translation_display( $data, $taxonomy ) { if ( ! empty( $data['st_default_lang'] ) ) { $source_lang = $data['st_default_lang']; if ( $this->is_product_attribute( $taxonomy ) || $this->is_shipping_class( $taxonomy ) ) { $data[ $source_lang ]['showSlugTranslationField'] = false; } } return $data; } /** * @param string $taxonomy * * @return bool */ private function is_product_attribute( $taxonomy ) { return 0 === strpos( $taxonomy, 'pa_' ); } /** * @param string $taxonomy * * @return bool */ private function is_shipping_class( $taxonomy ) { return 'product_shipping_class' === $taxonomy; } } filters/autoregister/class-wpml-autoregister-save-strings.php 0000755 00000005075 14720402445 0020676 0 ustar 00 <?php class WPML_Autoregister_Save_Strings { const INSERT_CHUNK_SIZE = 200; /** * @var wpdb */ private $wpdb; /** * @var SitePress $sitepress */ private $sitepress; /** * @var array */ private $data = array(); /** * @var WPML_Language_Of_Domain */ private $lang_of_domain; /** * @param wpdb $wpdb * @param SitePress $sitepress * @param WPML_Language_Of_Domain $language_of_domain */ public function __construct( wpdb $wpdb, SitePress $sitepress, WPML_Language_Of_Domain $language_of_domain = null ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $this->lang_of_domain = $language_of_domain ? $language_of_domain : new WPML_Language_Of_Domain( $this->sitepress ); add_action( 'shutdown', array( $this, 'shutdown' ) ); } /** * @param string|bool $value * @param string $name * @param string $domain * @param string $gettext_context */ public function save( $value, $name, $domain, $gettext_context = '' ) { $this->data[] = array( 'value' => $value, 'name' => $name, 'domain' => $domain, 'gettext_context' => $gettext_context, ); } /** * @param string $name * @param string $domain * * @return string */ public function get_source_lang( $name, $domain ) { $domain_lang = $this->lang_of_domain->get_language( $domain ); if ( ! $domain_lang ) { $flag = 0 === strpos( $domain, 'admin_texts_' ) || WPML_ST_Blog_Name_And_Description_Hooks::is_string( $name ); $domain_lang = $flag ? $this->sitepress->get_user_admin_language( get_current_user_id() ) : 'en'; } return $domain_lang; } private function persist() { foreach ( array_chunk( $this->data, self::INSERT_CHUNK_SIZE ) as $chunk ) { $query = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_strings " . '(`language`, `context`, `gettext_context`, `domain_name_context_md5`, `name`, `value`, `status`) VALUES '; $i = 0; foreach ( $chunk as $string ) { if ( $i > 0 ) { $query .= ','; } $query .= $this->wpdb->prepare( "('%s', '%s', '%s', '%s', '%s', '%s', %d)", $this->get_source_lang( $string['name'], $string['domain'] ), $string['domain'], $string['gettext_context'], md5( $string['domain'] . $string['name'] . $string['gettext_context'] ), $string['name'], $string['value'], ICL_TM_NOT_TRANSLATED ); $i ++; } $this->wpdb->query( $query ); } } public function shutdown() { if ( count( $this->data ) ) { $this->persist(); $this->data = array(); } } } string-tracking/class-wpml-st-string-tracking-ajax-factory.php 0000755 00000003510 14720402445 0020567 0 ustar 00 <?php use WPML\Core\Twig_Loader_Filesystem; use WPML\Core\Twig_Environment; class WPML_ST_String_Tracking_AJAX_Factory implements IWPML_AJAX_Action_Loader { const ACTION_POSITION_IN_SOURCE = 'view_string_in_source'; const ACTION_POSITION_IN_PAGE = 'view_string_in_page'; public function create() { if ( $this->is_string_position_view() ) { return new WPML_ST_String_Tracking_AJAX( $this->get_st_string_positions(), new WPML_Super_Globals_Validation(), (string) \WPML\API\Sanitize::string( $_GET['action'] ) ); } return null; } private function is_string_position_view() { return isset( $_GET['action'], $_GET['nonce'] ) && in_array( $_GET['action'], array( self::ACTION_POSITION_IN_SOURCE, self::ACTION_POSITION_IN_PAGE, ), true ) && wp_verify_nonce( $_GET['nonce'], $_GET['action'] ); } /** @return WPML_ST_String_Positions_In_Page|WPML_ST_String_Positions_In_Source */ private function get_st_string_positions() { global $sitepress, $wpdb; $string_positions_mapper = new WPML_ST_DB_Mappers_String_Positions( $wpdb ); if ( self::ACTION_POSITION_IN_PAGE === $_GET['action'] ) { return new WPML_ST_String_Positions_In_Page( new WPML_ST_String_Factory( $wpdb ), $string_positions_mapper, $this->get_template_service() ); } else { return new WPML_ST_String_Positions_In_Source( $sitepress, $string_positions_mapper, $this->get_template_service(), new WPML_WP_API() ); } } private function get_template_service() { $loader = new Twig_Loader_Filesystem( array( WPML_ST_PATH . WPML_ST_String_Positions::TEMPLATE_PATH ) ); $options = array(); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { $options['debug'] = true; } $twig_env = new Twig_Environment( $loader, $options ); return new WPML_Twig_Template( $twig_env ); } } string-tracking/class-wpml-st-string-positions.php 0000755 00000002276 14720402445 0016436 0 ustar 00 <?php abstract class WPML_ST_String_Positions { const TEMPLATE_PATH = '/templates/string-tracking/'; /** * @var WPML_ST_DB_Mappers_String_Positions $string_position_mapper */ protected $string_position_mapper; /** * @var IWPML_Template_Service $template_service */ protected $template_service; public function __construct( WPML_ST_DB_Mappers_String_Positions $string_position_mapper, IWPML_Template_Service $template_service ) { $this->string_position_mapper = $string_position_mapper; $this->template_service = $template_service; } /** * @param int $string_id * * @return array */ abstract protected function get_model( $string_id ); /** @return string */ abstract protected function get_template_name(); /** * @param int $string_id */ public function dialog_render( $string_id ) { echo $this->get_template_service()->show( $this->get_model( $string_id ), $this->get_template_name() ); } /** * @return WPML_ST_DB_Mappers_String_Positions */ protected function get_mapper() { return $this->string_position_mapper; } /** * @return IWPML_Template_Service */ protected function get_template_service() { return $this->template_service; } } string-tracking/class-wpml-st-string-tracking-ajax.php 0000755 00000002155 14720402445 0017126 0 ustar 00 <?php class WPML_ST_String_Tracking_AJAX implements IWPML_Action { /** @var WPML_ST_String_Positions $string_position */ private $string_position; /** @var WPML_Super_Globals_Validation $globals_validation */ private $globals_validation; /** @var string $action */ private $action; /** * WPML_ST_String_Tracking_AJAX constructor. * * @param WPML_ST_String_Positions $string_position * @param WPML_Super_Globals_Validation $globals_validation * @param string $action */ public function __construct( WPML_ST_String_Positions $string_position, WPML_Super_Globals_Validation $globals_validation, $action ) { $this->string_position = $string_position; $this->globals_validation = $globals_validation; $this->action = $action; } public function add_hooks() { add_action( 'wp_ajax_' . $this->action, array( $this, 'render_string_position' ) ); } public function render_string_position() { $string_id = $this->globals_validation->get( 'string_id', FILTER_SANITIZE_NUMBER_INT ); $this->string_position->dialog_render( $string_id ); wp_die(); } } string-tracking/class-wpml-st-string-positions-in-source.php 0000755 00000006104 14720402445 0020332 0 ustar 00 <?php /** * Class WPML_ST_String_Positions_In_Source */ class WPML_ST_String_Positions_In_Source extends WPML_ST_String_Positions { const KIND = ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_SOURCE; const TEMPLATE = 'positions-in-source.twig'; /** * @var SitePress $sitepress */ private $sitepress; /** * @var WP_Filesystem_Direct $filesystem */ private $filesystem; /** * @var WPML_File_Name_Converter $filename_converter */ private $filename_converter; /** * @var \WPML_WP_API */ private $wp_api; public function __construct( SitePress $sitePress, WPML_ST_DB_Mappers_String_Positions $string_position_mapper, IWPML_Template_Service $template_service, WPML_WP_API $wp_api ) { $this->sitepress = $sitePress; $this->wp_api = $wp_api; parent::__construct( $string_position_mapper, $template_service ); } protected function get_model( $string_id ) { $positions = $this->get_positions( $string_id ); $st_settings = $this->sitepress->get_setting( 'st' ); $highlight_color = '#FFFF00'; if ( array_key_exists( 'hl_color', $st_settings ) ) { $highlight_color = $st_settings['hl_color']; } return array( 'positions' => $positions, 'no_results_label' => __( 'No records found', 'wpml-string-translation' ), 'highlight_color' => $highlight_color, ); } protected function get_template_name() { return self::TEMPLATE; } /** * @param int $string_id * * @return array */ private function get_positions( $string_id ) { $positions = array(); $paths = $this->get_mapper()->get_positions_by_string_and_kind( $string_id, self::KIND ); foreach ( $paths as $path ) { $position = explode( '::', $path ); $path = isset( $position[0] ) ? $position[0] : null; if( ! $this->get_filesystem()->exists( $path ) ) { $path = $this->maybe_transform_from_relative_path_to_absolute_path( $path ); } if ( $path && $this->get_filesystem()->is_readable( $path ) ) { $positions[] = array( 'path' => $path, 'line' => isset( $position[1] ) ? $position[1] : null, 'content' => $this->get_filesystem()->get_contents_array( $path ), ); } } return $positions; } /** * @param string $path * * @return string|false */ private function maybe_transform_from_relative_path_to_absolute_path( $path ) { $path = $this->get_filename_converter()->transform_reference_to_realpath( $path ); if ( $this->get_filesystem()->exists( $path ) ) { return $path; } return false; } /** * @return WP_Filesystem_Direct */ private function get_filesystem() { if ( ! $this->filesystem ) { $this->filesystem = $this->get_wp_api()->get_wp_filesystem_direct(); } return $this->filesystem; } /** * @return WPML_WP_API */ private function get_wp_api() { if ( ! $this->wp_api ) { $this->wp_api = new WPML_WP_API(); } return $this->wp_api; } /** * @return WPML_File_Name_Converter */ private function get_filename_converter() { if ( ! $this->filename_converter ) { $this->filename_converter = new WPML_File_Name_Converter(); } return $this->filename_converter; } } string-tracking/class-wpml-st-string-positions-in-page.php 0000755 00000002443 14720402445 0017750 0 ustar 00 <?php class WPML_ST_String_Positions_In_Page extends WPML_ST_String_Positions { const KIND = ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_PAGE; const TEMPLATE = 'positions-in-page.twig'; /** @var WPML_ST_String_Factory $string_factory */ private $string_factory; public function __construct( WPML_ST_String_Factory $string_factory, WPML_ST_DB_Mappers_String_Positions $string_position_mapper, IWPML_Template_Service $template_service ) { $this->string_factory = $string_factory; parent::__construct( $string_position_mapper, $template_service ); } protected function get_model( $string_id ) { return array( 'pages' => $this->get_pages( $string_id ), ); } protected function get_template_name() { return self::TEMPLATE; } private function get_pages( $string_id ) { $pages = array(); $string = $this->string_factory->find_by_id( $string_id ); $value = $string->get_value(); $context = $string->get_context(); $urls = $this->get_mapper()->get_positions_by_string_and_kind( $string_id, self::KIND ); foreach ( $urls as $url ) { $pages[] = array( 'iframe_url' => add_query_arg( array( 'icl_string_track_value' => $value, 'icl_string_track_context' => $context, ), $url ), 'url' => $url, ); } return $pages; } } gettext-hooks/Hooks.php 0000755 00000010032 14720402445 0011146 0 ustar 00 <?php /** * WPML\ST\Gettext\Hooks class file. * * @package WPML\ST */ namespace WPML\ST\Gettext; use SitePress; use function WPML\Container\make; use WPML\ST\Gettext\Filters\IFilter; use WPML\ST\StringsFilter\Provider; /** * Class WPML\ST\Gettext\Hooks */ class Hooks implements \IWPML_Action { /** @var Filters\IFilter[] $filters */ private $filters = []; /** * @var SitePress SitePress */ private $sitepress; /** * @var array */ private $string_cache = []; /** * @var string|null */ private $lang; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; } /** * Init hooks. */ public function add_hooks() { add_action( 'plugins_loaded', array( $this, 'init_gettext_hooks' ), 2 ); } public function addFilter( IFilter $filter ) { $this->filters[] = $filter; } public function clearFilters() { $this->filters = []; } /** * @param string $lang */ public function switch_language_hook( $lang ) { $this->lang = $lang; } /** * @throws \WPML\Auryn\InjectionException * @deprecated since WPML ST 3.0.0 */ public function clear_filters() { // @todo: This is used in WCML tests, we will have to adjust there accordingly. /** @var Provider $filter_provider */ $filter_provider = make( Provider::class ); $filter_provider->clearFilters(); } /** * Init gettext hooks. */ public function init_gettext_hooks() { add_filter( 'gettext', [ $this, 'gettext_filter' ], 9, 3 ); add_filter( 'gettext_with_context', [ $this, 'gettext_with_context_filter' ], 1, 4 ); add_filter( 'ngettext', [ $this, 'ngettext_filter' ], 9, 5 ); add_filter( 'ngettext_with_context', [ $this, 'ngettext_with_context_filter' ], 9, 6 ); add_action( 'wpml_language_has_switched', [ $this, 'switch_language_hook' ] ); } /** * @param string $translation * @param string $text * @param string|array $domain * @param string|false $name Deprecated since WPML ST 3.0.0 (the name should be automatically created as a hash) * * @return string */ public function gettext_filter( $translation, $text, $domain, $name = false ) { if ( is_array( $domain ) ) { $domain_key = implode( '', $domain ); } else { $domain_key = $domain; } if ( ! $name ) { $name = md5( $text ); } if ( ! $this->lang ) { $this->lang = $this->sitepress->get_current_language(); } $key = $translation . $text . $domain_key . $name . $this->lang; if ( isset( $this->string_cache[ $key ] ) ) { return $this->string_cache[ $key ]; } foreach ( $this->filters as $filter ) { $translation = $filter->filter( $translation, $text, $domain, $name ); } $this->string_cache[ $key ] = $translation; return $translation; } /** * @param string $translation * @param string $text * @param string|false $context * @param string $domain * * @return string */ public function gettext_with_context_filter( $translation, $text, $context, $domain ) { if ( $context ) { $domain = [ 'domain' => $domain, 'context' => $context, ]; } return $this->gettext_filter( $translation, $text, $domain ); } /** * @param string $translation * @param string $single * @param string $plural * @param string $number * @param string $domain * @param string|false $context * * @return string */ public function ngettext_filter( $translation, $single, $plural, $number, $domain, $context = false ) { if ( $number == 1 ) { $string_to_translate = $single; } else { $string_to_translate = $plural; } return $this->gettext_with_context_filter( $translation, $string_to_translate, $context, $domain ); } /** * @param string $translation * @param string $single * @param string $plural * @param string $number * @param string $context * @param string $domain * * @return string */ public function ngettext_with_context_filter( $translation, $single, $plural, $number, $context, $domain ) { return $this->ngettext_filter( $translation, $single, $plural, $number, $domain, $context ); } } gettext-hooks/Settings.php 0000755 00000002637 14720402445 0011677 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\Gettext; use SitePress; class Settings { /** @var SitePress $sitepress */ private $sitepress; /** @var AutoRegisterSettings $auto_register_settings */ private $auto_register_settings; public function __construct( SitePress $sitepress, AutoRegisterSettings $auto_register_settings ) { $this->sitepress = $sitepress; $this->auto_register_settings = $auto_register_settings; } /** @return bool */ public function isTrackStringsEnabled() { return (bool) $this->getSTSetting( 'track_strings', false ); } /** @return string */ public function getTrackStringColor() { return (string) $this->getSTSetting( 'hl_color', '' ); } /** @return bool */ public function isAutoRegistrationEnabled() { return (bool) $this->auto_register_settings->isEnabled(); } /** * @param string|array $domain * * @return bool */ public function isDomainRegistrationExcluded( $domain ) { if ( is_array( $domain ) && array_key_exists( 'domain', $domain ) ) { $domain = $domain[ 'domain' ]; } return (bool) $this->auto_register_settings->isExcludedDomain( $domain ); } /** * @param string $key * @param mixed $default * * @return mixed|null */ private function getSTSetting( $key, $default = null ) { $settings = $this->sitepress->get_setting( 'st' ); return isset( $settings[ $key ] ) ? $settings[ $key ] : $default; } } gettext-hooks/filters/StringHighlighting.php 0000755 00000002344 14720402445 0015336 0 ustar 00 <?php namespace WPML\ST\Gettext\Filters; use WPML\ST\Gettext\HooksFactory; use WPML\ST\Gettext\Settings; class StringHighlighting implements IFilter { /** @var Settings $settings */ private $settings; public function __construct( Settings $settings ) { $this->settings = $settings; } /** * @param string $translation * @param string $text * @param string|array $domain * @param string|false $name * * @return string */ public function filter( $translation, $text, $domain, $name = false ) { if ( is_array( $domain ) ) { $domain = $domain['domain']; } if ( $this->isHighlighting( $domain, $text ) ) { $translation = '<span style="background-color:' . esc_attr( $this->settings->getTrackStringColor() ) . '">' . $translation . '</span>'; } return $translation; } /** * @param string $domain * @param string $text * * @return bool */ private function isHighlighting( $domain, $text ) { return isset( $_GET[ HooksFactory::TRACK_PARAM_TEXT ], $_GET[ HooksFactory::TRACK_PARAM_DOMAIN ] ) && stripslashes( $_GET[ HooksFactory::TRACK_PARAM_DOMAIN ] ) === $domain && stripslashes( $_GET[ HooksFactory::TRACK_PARAM_TEXT ] ) === $text; } } gettext-hooks/filters/StringTranslation.php 0000755 00000002076 14720402445 0015231 0 ustar 00 <?php namespace WPML\ST\Gettext\Filters; use WPML\ST\Gettext\Settings; class StringTranslation implements IFilter { /** @var Settings $settings */ private $settings; public function __construct( Settings $settings ) { $this->settings = $settings; } /** * @param string $translation * @param string $text * @param string|array $domain * @param string|false $name * * @return string */ public function filter( $translation, $text, $domain, $name = false ) { if ( $this->settings->isDomainRegistrationExcluded( $domain ) ) { return $translation; } if ( ! defined( 'ICL_STRING_TRANSLATION_DYNAMIC_CONTEXT' ) ) { define( 'ICL_STRING_TRANSLATION_DYNAMIC_CONTEXT', 'wpml_string' ); } if ( $domain === ICL_STRING_TRANSLATION_DYNAMIC_CONTEXT ) { icl_register_string( $domain, (string) $name, $text ); } $has_translation = null; $found_translation = icl_translate( $domain, (string) $name, $text, false, $has_translation ); if ( $has_translation ) { return $found_translation; } else { return $translation; } } } gettext-hooks/filters/StringTracking.php 0000755 00000001123 14720402445 0014465 0 ustar 00 <?php namespace WPML\ST\Gettext\Filters; class StringTracking implements IFilter { /** * @param string $translation * @param string $text * @param string|array $domain * @param string|false $name * * @return string */ public function filter( $translation, $text, $domain, $name = false ) { if ( $this->canTrackStrings() ) { icl_st_track_string( $text, $domain, ICL_STRING_TRANSLATION_STRING_TRACKING_TYPE_PAGE ); } return $translation; } /** * @return bool */ public function canTrackStrings() { return did_action( 'after_setup_theme' ); } } gettext-hooks/filters/IFilter.php 0000755 00000000517 14720402445 0013100 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\Gettext\Filters; interface IFilter { /** * @param string $translation * @param string $text * @param string|array $domain * @param string|false $name * * @return string */ public function filter( $translation, $text, $domain, $name = false ); } gettext-hooks/HooksFactory.php 0000755 00000003557 14720402445 0012514 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\Gettext; use function WPML\Container\make; use WPML_ST_Upgrade; class HooksFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader { const TRACK_PARAM_TEXT = 'icl_string_track_value'; const TRACK_PARAM_DOMAIN = 'icl_string_track_context'; /** * @return \IWPML_Action|Hooks|null * @throws \WPML\Auryn\InjectionException */ public function create() { /** * @deprecated this global should not be used anymore. * * @var Hooks $st_gettext_hooks */ global $st_gettext_hooks; $st_gettext_hooks = null; $filters = $this->getFilters(); if ( ! $filters ) { return $st_gettext_hooks; } /** @var Hooks $st_gettext_hooks */ $st_gettext_hooks = make( Hooks::class ); $st_gettext_hooks->clearFilters(); foreach ( $filters as $filter ) { $st_gettext_hooks->addFilter( $filter ); } return $st_gettext_hooks; } /** * @return Filters\IFilter[] * @throws \WPML\Auryn\InjectionException */ private function getFilters() { $filters = []; /** @var Settings $settings */ $settings = make( Settings::class ); if ( $settings->isAutoRegistrationEnabled() ) { $filters[] = make( Filters\StringTranslation::class ); } if ( $this->isTrackingStrings( $settings ) ) { $filters[] = make( Filters\StringTracking::class ); } if ( $this->isHighlightingStrings() ) { $filters[] = make( Filters\StringHighlighting::class ); } return $filters; } /** * @param Settings $settings * * @return bool */ private function isTrackingStrings( Settings $settings ) { return $settings->isTrackStringsEnabled() && current_user_can( 'edit_others_posts' ) && ! is_admin(); } /** * @return bool */ private function isHighlightingStrings() { return isset( $_GET[ self::TRACK_PARAM_TEXT ], $_GET[ self::TRACK_PARAM_DOMAIN ] ); } } gettext-hooks/AutoRegisterSettings.php 0000755 00000016757 14720402445 0014245 0 ustar 00 <?php namespace WPML\ST\Gettext; use wpdb; use WPML\FP\Obj; use WPML\ST\MO\Hooks\PreloadThemeMoFile; use WPML\ST\Package\Domains; use function wpml_collect; use WPML_ST_Settings; class AutoRegisterSettings { const KEY_EXCLUDED_DOMAINS = 'wpml_st_auto_reg_excluded_contexts'; const KEY_ENABLED = 'auto_register_enabled'; const RESET_AUTOLOAD_TIMEOUT = 2 * HOUR_IN_SECONDS; /** * @var wpdb $wpdb */ protected $wpdb; /** * @var WPML_ST_Settings */ private $settings; /** * @var Domains */ private $package_domains; /** @var \WPML_Localization */ private $localization; /** * @var array */ private $excluded_domains; public function __construct( wpdb $wpdb, WPML_ST_Settings $settings, Domains $package_domains, \WPML_Localization $localization ) { $this->wpdb = $wpdb; $this->settings = $settings; $this->package_domains = $package_domains; $this->localization = $localization; } /** @return bool */ public function isEnabled() { $setting = $this->getSetting( self::KEY_ENABLED, [ 'enabled' => false ] ); if ( $setting['enabled'] ) { $elapsed_time = time() - $setting['time']; $isStillEnabled = self::RESET_AUTOLOAD_TIMEOUT > $elapsed_time; $setting['enabled'] = $isStillEnabled; if ( ! $isStillEnabled ) { $this->setEnabled( false ); } } return $setting['enabled']; } /** * @param bool $isEnabled */ public function setEnabled( $isEnabled ) { $setting = $this->getSetting( self::KEY_ENABLED, [ 'enabled' => false ] ); $setting['enabled'] = ( $isEnabled && $this->getTimeToAutoDisable() > 0 ); $this->settings->update_setting( self::KEY_ENABLED, $setting, true ); $this->setWpmlSettingForThemeLocalization( $isEnabled ); } /** * @return int number of seconds before auto-disable */ public function getTimeToAutoDisable() { $setting = $this->getSetting( self::KEY_ENABLED, [ 'enabled' => false ] ); if ( isset( $setting['time'] ) ) { $elapsed_time = time() - $setting['time']; $time_to_auto_disable = self::RESET_AUTOLOAD_TIMEOUT - $elapsed_time; if ( $time_to_auto_disable > 0 ) { return $time_to_auto_disable; } } return 0; } /** * @param string $key * @param mixed $default * * @return mixed|null */ private function getSetting( $key, $default = null ) { $setting = $this->settings->get_setting( $key ); return null !== $setting ? $setting : $default; } /** * @return array */ public function getExcludedDomains() { if ( ! $this->excluded_domains ) { $excluded = $this->getSetting( self::KEY_EXCLUDED_DOMAINS, [] ); $this->excluded_domains = wpml_collect( $excluded ) ->reject( [ $this, 'isAdminOrPackageDomain' ] ) ->toArray(); } return $this->excluded_domains; } /** * @param string $domain * * @return bool */ public function isExcludedDomain( $domain ) { return in_array( $domain, $this->getExcludedDomains(), true ); } /** * @return array * @todo: Remove this method, looks like dead code. */ public function get_included_contexts() { return array_values( array_diff( $this->getAllDomains(), $this->getExcludedDomains() ) ); } /** * @return array */ public function getAllDomains() { $sql = " SELECT DISTINCT context FROM {$this->wpdb->prefix}icl_strings "; return wpml_collect( $this->wpdb->get_col( $sql ) ) ->reject( [ $this, 'isAdminOrPackageDomain' ] ) ->merge( wpml_collect( $this->getExcludedDomains() ) ) ->unique() ->toArray(); } /** * @param string $domain * * @return bool */ public function isAdminOrPackageDomain( $domain ) { return 0 === strpos( $domain, \WPML_Admin_Texts::DOMAIN_NAME_PREFIX ) || $this->package_domains->isPackage( $domain ); } /** * @return array */ public function getDomainsAndTheirExcludeStatus() { $contexts = $this->getAllDomains(); $excluded = $this->getExcludedDomains(); $result = array(); foreach ( $contexts as $context ) { $result[ $context ] = in_array( $context, $excluded ); } return $result; } public function saveExcludedContexts() { $nonce = isset( $_POST['nonce'] ) ? $_POST['nonce'] : ''; $is_valid = wp_verify_nonce( $nonce, 'wpml-st-cancel-button' ); if ( $is_valid ) { $is_enabled = [ 'enabled' => false ]; $excluded_contexts = []; if ( isset( $_POST[ self::KEY_EXCLUDED_DOMAINS ] ) && is_array( $_POST[ self::KEY_EXCLUDED_DOMAINS ] ) ) { $excluded_contexts = array_map( 'stripslashes', $_POST[ self::KEY_EXCLUDED_DOMAINS ] ); } $this->settings->update_setting( self::KEY_EXCLUDED_DOMAINS, $excluded_contexts, true ); if ( isset( $_POST[ self::KEY_ENABLED ] ) && 'true' === $_POST[ self::KEY_ENABLED ] ) { $is_enabled = [ 'enabled' => true, 'time' => time(), ]; } $this->settings->update_setting( self::KEY_ENABLED, $is_enabled, true ); $this->setWpmlSettingForThemeLocalization( $is_enabled[ 'enabled' ] ); wp_send_json_success(); } else { wp_send_json_error( __( 'Nonce value is invalid', 'wpml-string-translation' ) ); } } /** @return string */ public function getFeatureEnabledDescription() { return '<span class="icon otgs-ico-warning"></span> ' . __( "Automatic string registration will remain active for %s. Please visit the site's front-end to allow WPML to find strings for translation.", 'wpml-string-translation' ); } /** @return string */ public function getFeatureDisabledDescription() { return __( '* This feature is only intended for sites that are in development. It will significantly slow down the site, but help you find strings that WPML cannot detect in the PHP code.', 'wpml-string-translation' ); } public function getDomainsWithStringsTranslationData() { $excluded = $this->getExcludedDomains(); $domains = wpml_collect( $this->getAllDomains() )->merge( $excluded )->unique()->toArray(); $stats = $this->localization->get_domain_stats( $domains, 'default', false, true ); $result = []; foreach ( $domains as $domain ) { $completed_strings_count = (int) Obj::path( [ $domain, 'complete' ], $stats ); $incomplete_strings_count = (int) Obj::path( [ $domain, 'incomplete' ], $stats ); $result[ $domain ] = [ 'name' => $domain, 'translated_strings_count' => $completed_strings_count, 'total_strings_count' => $incomplete_strings_count + $completed_strings_count, 'is_blocked' => in_array( $domain, $excluded ), ]; } return $result; } private function setWpmlSettingForThemeLocalization( $isEnabled ) { if ( $isEnabled ) { $this->enableWpmlSettingForThemeLocalizationIfNecessary(); } else { $this->disableWpmlSettingForThemeLocalizationIfNecessary(); } } private function enableWpmlSettingForThemeLocalizationIfNecessary() { $previousLoadThemeSetting = (int) apply_filters( 'wpml_get_setting', 0, PreloadThemeMoFile::SETTING_KEY ); if ( $previousLoadThemeSetting === PreloadThemeMoFile::SETTING_DISABLED ) { do_action( 'wpml_set_setting', PreloadThemeMoFile::SETTING_KEY, PreloadThemeMoFile::SETTING_ENABLED_FOR_LOAD_TEXT_DOMAIN, true ); } } private function disableWpmlSettingForThemeLocalizationIfNecessary() { $previousLoadThemeSetting = (int) apply_filters( 'wpml_get_setting', 0, PreloadThemeMoFile::SETTING_KEY ); if ( $previousLoadThemeSetting === PreloadThemeMoFile::SETTING_ENABLED_FOR_LOAD_TEXT_DOMAIN ) { do_action( 'wpml_set_setting', PreloadThemeMoFile::SETTING_KEY, PreloadThemeMoFile::SETTING_DISABLED, true ); } } } slug-translation/wpml-rewrite-rule-filter-factory.php 0000755 00000002055 14720402445 0017125 0 ustar 00 <?php class WPML_Rewrite_Rule_Filter_Factory { /** * @param SitePress|null $sitepress * * @return WPML_Rewrite_Rule_Filter */ public function create( $sitepress = null ) { if ( ! $sitepress ) { global $sitepress; } $slug_records_factory = new WPML_Slug_Translation_Records_Factory(); $slug_translations = new WPML_ST_Slug_Translations(); $custom_types_repositories = array( new WPML_ST_Slug_Translation_Post_Custom_Types_Repository( $sitepress, new WPML_ST_Slug_Custom_Type_Factory( $sitepress, $slug_records_factory->create( WPML_Slug_Translation_Factory::POST ), $slug_translations ) ), new WPML_ST_Slug_Translation_Taxonomy_Custom_Types_Repository( $sitepress, new WPML_ST_Slug_Custom_Type_Factory( $sitepress, $slug_records_factory->create( WPML_Slug_Translation_Factory::TAX ), $slug_translations ), new WPML_ST_Tax_Slug_Translation_Settings() ), ); return new WPML_Rewrite_Rule_Filter( $custom_types_repositories, new WPML_ST_Slug_New_Match_Finder() ); } } slug-translation/wpml-st-element-slug-translation-ui-model.php 0000755 00000012531 14720402445 0020641 0 ustar 00 <?php class WPML_ST_Element_Slug_Translation_UI_Model { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_ST_Slug_Translation_Settings $settings */ private $settings; /** @var WPML_Slug_Translation_Records $slug_records */ private $slug_records; /** @var WPML_Element_Sync_Settings $sync_settings */ private $sync_settings; /** @var WPML_Simple_Language_Selector $lang_selector */ private $lang_selector; public function __construct( SitePress $sitepress, WPML_ST_Slug_Translation_Settings $settings, WPML_Slug_Translation_Records $slug_records, WPML_Element_Sync_Settings $sync_settings, WPML_Simple_Language_Selector $lang_selector ) { $this->sitepress = $sitepress; $this->settings = $settings; $this->slug_records = $slug_records; $this->sync_settings = $sync_settings; $this->lang_selector = $lang_selector; } /** * @param string $type_name * @param WP_Post_Type|WP_Taxonomy $custom_type * * @return null|array */ public function get( $type_name, $custom_type ) { $has_rewrite_slug = isset( $custom_type->rewrite['slug'] ) && $custom_type->rewrite['slug']; $is_translated_mode = $this->sync_settings->is_sync( $type_name ); $is_slug_translated = $this->settings->is_translated( $type_name ); if ( ! $has_rewrite_slug || ! $this->settings->is_enabled() ) { return null; } $original_slug_and_lang = $this->get_original_slug_and_lang( $type_name, $custom_type ); $slug_translations = $this->get_translations( $type_name ); $model = array( 'strings' => array( 'toggle_slugs_table' => sprintf( __( 'Set different slugs in different languages for %s.', 'wpml-string-translation' ), $custom_type->labels->name ), 'slug_status_incomplete' => __( "Not marked as 'complete'. Press 'Save' to enable.", 'wpml-string-translation' ), 'original_label' => __( '(original)', 'wpml-string-translation' ), ), 'css_class_wrapper' => $is_translated_mode ? '' : 'hidden', 'type_name' => $type_name, 'slugs' => array(), 'has_missing_translations_message' => '', ); if ( $is_slug_translated && ! $original_slug_and_lang->is_registered ) { $model['has_missing_translations_message'] = sprintf( esc_html__( '%s slugs are set to be translated, but they are missing their translation', 'wpml-string-translation' ), $custom_type->labels->name ); } $languages = $this->get_languages( $original_slug_and_lang->language ); foreach ( $languages as $code => $language ) { $slug = new stdClass(); $slug->value = ! empty( $slug_translations[ $code ]['value'] ) ? $slug_translations[ $code ]['value'] : ''; $slug->placeholder = $original_slug_and_lang->value . ' @' . $code; $slug->input_id = sprintf( 'translate_slugs[%s][langs][%s]', $type_name, $code ); $slug->language_flag = $this->sitepress->get_flag_img( $code ); $slug->language_name = $language['display_name']; $slug->language_code = $code; $slug->is_original = $code == $original_slug_and_lang->language; $slug->status_is_incomplete = isset( $slug_translations[ $code ] ) && ICL_TM_COMPLETE != $slug_translations[ $code ]['status']; if ( $slug->is_original ) { $slug->value = $original_slug_and_lang->value; $slug->language_selector = $this->lang_selector->render( array( 'name' => 'translate_slugs[' . $type_name . '][original]', 'selected' => $code, 'show_please_select' => false, 'echo' => false, 'class' => 'js-translate-slug-original', 'data' => array( 'slug' => $slug->value ), ) ); } $model['slugs'][ $slug->language_code ] = $slug; } return $model; } /** * @param string $type_name * @param WP_Post_Type|WP_Taxonomy $custom_type * * @return stdClass */ private function get_original_slug_and_lang( $type_name, $custom_type ) { $original_slug_and_lang = $this->slug_records->get_original_slug_and_lang( $type_name ); if ( $original_slug_and_lang ) { $original_slug_and_lang->is_registered = true; } else { $original_slug_and_lang = new stdClass(); $original_slug_and_lang->is_registered = false; $original_slug_and_lang->value = isset( $custom_type->slug ) ? $custom_type->slug : $custom_type->rewrite['slug']; $original_slug_and_lang->language = $this->sitepress->get_default_language(); } return $original_slug_and_lang; } /** * @param string $type_name * * @return array */ private function get_translations( $type_name ) { $translations = array(); $rows = $this->slug_records->get_element_slug_translations( $type_name, false ); foreach( $rows as $row ) { $translations[ $row->language ] = array( 'value' => $row->value, 'status' => $row->status ); } return $translations; } /** * @param string $string_lang * * @return array */ private function get_languages( $string_lang ) { $languages = $this->sitepress->get_active_languages(); if ( ! in_array( $string_lang, array_keys( $languages ) ) ) { $all_languages = $this->sitepress->get_languages(); $languages[ $string_lang ] = $all_languages[ $string_lang ]; } return $languages; } } slug-translation/class-wpml-slug-translation-records.php 0000755 00000013664 14720402445 0017627 0 ustar 00 <?php abstract class WPML_Slug_Translation_Records { const CONTEXT_DEFAULT = 'default'; const CONTEXT_WORDPRESS = 'WordPress'; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_WP_Cache_Factory $cache_factory*/ private $cache_factory; public function __construct( wpdb $wpdb, WPML_WP_Cache_Factory $cache_factory ) { $this->wpdb = $wpdb; $this->cache_factory = $cache_factory; } /** * @param string $type * * @return WPML_ST_Slug */ public function get_slug( $type ) { $cache_item = $this->cache_factory->create_cache_item( $this->get_cache_group(), $type ); if ( ! $cache_item->exists() ) { $slug = new WPML_ST_Slug(); /** @var \stdClass $original */ $original = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT id, value, language, context, name FROM {$this->wpdb->prefix}icl_strings WHERE name = %s AND (context = %s OR context = %s)", $this->get_string_name( $type ), self::CONTEXT_DEFAULT, self::CONTEXT_WORDPRESS ) ); if ( $original ) { $slug->set_lang_data( $original ); /** @var array<\stdClass> $translations */ $translations = $this->wpdb->get_results( $this->wpdb->prepare( "SELECT value, language, status FROM {$this->wpdb->prefix}icl_string_translations WHERE string_id = %d AND value <> '' AND language <> %s", $original->id, $original->language ) ); if ( $translations ) { foreach ( $translations as $translation ) { $slug->set_lang_data( $translation ); } } } $cache_item->set( $slug ); } return $cache_item->get(); } /** @return string */ private function get_cache_group() { return __CLASS__ . '::' . $this->get_element_type(); } private function flush_cache() { $cache_group = $this->cache_factory->create_cache_group( $this->get_cache_group() ); $cache_group->flush_group_cache(); } /** * @deprecated use `get_slug` instead. * * @param string $type * @param string $lang * * @return null|string */ public function get_translation( $type, $lang ) { $slug = $this->get_slug( $type ); if ( $slug->is_translation_complete( $lang ) ) { return $slug->get_value( $lang ); } return null; } /** * @deprecated use `get_slug` instead. * * @param string $type * @param string $lang * * @return null|string */ public function get_original( $type, $lang = '' ) { $slug = $this->get_slug( $type ); if ( ! $lang || $slug->get_original_lang() === $lang ) { return $slug->get_original_value(); } return null; } /** * @deprecated use `get_slug` instead. * * @param string $type * * @return int|null */ public function get_slug_id( $type ) { $slug = $this->get_slug( $type ); if ( $slug->get_original_id() ) { return $slug->get_original_id(); } return null; } /** * @param string $type * @param string $slug * * @return int|null */ public function register_slug( $type, $slug ) { $string_id = icl_register_string( self::CONTEXT_WORDPRESS, $this->get_string_name( $type ), $slug ); $this->flush_cache(); return $string_id; } /** * @param string $type * @param string $slug */ public function update_original_slug( $type, $slug ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', array( 'value' => $slug ), array( 'name' => $this->get_string_name( $type ) ) ); $this->flush_cache(); } /** * @deprecated use `get_slug` instead. * * @param string $type * * @return null|stdClass */ public function get_original_slug_and_lang( $type ) { $original_slug_and_lang = null; $slug = $this->get_slug( $type ); if ( $slug->get_original_id() ) { $original_slug_and_lang = (object) array( 'value' => $slug->get_original_value(), 'language' => $slug->get_original_lang(), ); } return $original_slug_and_lang; } /** * @deprecated use `get_slug` instead. * * @param string $type * @param bool $only_status_complete * * @return array */ public function get_element_slug_translations( $type, $only_status_complete = true ) { $slug = $this->get_slug( $type ); $rows = array(); foreach ( $slug->get_language_codes() as $lang ) { if ( $slug->get_original_lang() === $lang || ( $only_status_complete && ! $slug->is_translation_complete( $lang ) ) ) { continue; } $rows[] = (object) array( 'value' => $slug->get_value( $lang ), 'language' => $lang, 'status' => $slug->get_status( $lang ), ); } return $rows; } /** * @deprecated use `get_slug` instead. * * @param array $types * * @return array */ public function get_all_slug_translations( $types ) { $rows = array(); foreach ( $types as $type ) { $slug = $this->get_slug( $type ); foreach ( $slug->get_language_codes() as $lang ) { if ( $slug->get_original_lang() !== $lang ) { $rows[] = (object) array( 'value' => $slug->get_value( $lang ), 'name' => $slug->get_name(), ); } } } return $rows; } /** * @deprecated use `get_slug` instead. * * @param string $type * * @return array */ public function get_slug_translation_languages( $type ) { $languages = array(); $slug = $this->get_slug( $type ); foreach ( $slug->get_language_codes() as $lang ) { if ( $slug->is_translation_complete( $lang ) ) { $languages[] = $lang; } } return $languages; } /** * Use `WPML_ST_String` only for updating the values in the DB * because it does not have any caching feature. * * @param string $type * * @return null|WPML_ST_String */ public function get_slug_string( $type ) { $string_id = $this->get_slug_id( $type ); if ( $string_id ) { return new WPML_ST_String( $string_id, $this->wpdb ); } return null; } /** * @param string $slug * * @return string */ abstract protected function get_string_name( $slug ); /** @return string */ abstract protected function get_element_type(); } slug-translation/taxonomy/wpml-st-term-link-filter.php 0000755 00000004723 14720402445 0017242 0 ustar 00 <?php class WPML_ST_Term_Link_Filter { const CACHE_GROUP = 'WPML_ST_Term_Link_Filter::replace_base_in_permalink_structure'; /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Tax_Slug_Translation_Records $slug_records */ private $slug_records; /** @var WPML_WP_Cache_Factory $cache_factory */ private $cache_factory; /** @var WPML_ST_Tax_Slug_Translation_Settings $tax_settings */ private $tax_settings; public function __construct( WPML_Tax_Slug_Translation_Records $slug_records, SitePress $sitepress, WPML_WP_Cache_Factory $cache_factory, WPML_ST_Tax_Slug_Translation_Settings $tax_settings ) { $this->slug_records = $slug_records; $this->sitepress = $sitepress; $this->cache_factory = $cache_factory; $this->tax_settings = $tax_settings; } /** * Filters the permalink structure for a terms before token replacement occurs * with the hook filter `pre_term_link` available since WP 4.9.0 * * @see get_term_link * * @param false|string $termlink * @param WP_Term $term * * @return false|string */ public function replace_slug_in_termlink( $termlink, $term ) { if ( ! $termlink || ! $this->sitepress->is_translated_taxonomy( $term->taxonomy ) ) { return $termlink; } $term_lang = $this->sitepress->get_language_for_element( $term->term_taxonomy_id, 'tax_' . $term->taxonomy ); $cache_key = $termlink . $term_lang; $cache_item = $this->cache_factory->create_cache_item( self::CACHE_GROUP, $cache_key ); if ( $cache_item->exists() ) { $termlink = $cache_item->get(); } else { if ( $this->tax_settings->is_translated( $term->taxonomy ) ) { $original_slug = $this->slug_records->get_original( $term->taxonomy ); $translated_slug = $this->slug_records->get_translation( $term->taxonomy, $term_lang ); if ( $original_slug && $translated_slug && $original_slug !== $translated_slug ) { $termlink = $this->replace_slug( $termlink, $original_slug, $translated_slug ); } } $cache_item->set( $termlink ); } return $termlink; } /** * @param string $termlink * @param string $original_slug * @param string $translated_slug * * @return string */ private function replace_slug( $termlink, $original_slug, $translated_slug ) { if ( preg_match( '#/?' . preg_quote( $original_slug ) . '/#', $termlink ) ) { $termlink = preg_replace( '#^(/?)(' . addslashes( $original_slug ) . ')/#', "$1$translated_slug/", $termlink ); } return $termlink; } } slug-translation/taxonomy/wpml-tax-slug-translation-records.php 0000755 00000000565 14720402445 0021170 0 ustar 00 <?php class WPML_Tax_Slug_Translation_Records extends WPML_Slug_Translation_Records { const STRING_NAME = 'URL %s tax slug'; /** * @param string $slug * * @return string */ protected function get_string_name( $slug ) { return sprintf( self::STRING_NAME, $slug ); } /** @return string */ protected function get_element_type() { return 'taxonomy'; } } slug-translation/taxonomy/wpml-st-tax-slug-translation-settings.php 0000755 00000002540 14720402445 0022006 0 ustar 00 <?php class WPML_ST_Tax_Slug_Translation_Settings extends WPML_ST_Slug_Translation_Settings { const OPTION_NAME = 'wpml_tax_slug_translation_settings'; /** @var array $types */ private $types = array(); public function __construct() { $this->init(); } /** @param array $types */ public function set_types( array $types ) { $this->types = $types; } /** @return array */ public function get_types() { return $this->types; } /** * @param string $taxonomy_name * * @return bool */ public function is_translated( $taxonomy_name ) { return array_key_exists( $taxonomy_name, $this->types ) && (bool) $this->types[ $taxonomy_name ]; } /** * @param string $taxonomy_name * @param bool $is_enabled */ public function set_type( $taxonomy_name, $is_enabled ) { $this->types[ $taxonomy_name ] = (int) $is_enabled; } /** @return array */ private function get_properties() { return get_object_vars( $this ); } public function init() { $options = get_option( self::OPTION_NAME, array() ); foreach ( $this->get_properties() as $name => $value ) { $callback = array( $this, 'set_' . $name ); if ( array_key_exists( $name, $options ) && is_callable( $callback ) ) { call_user_func( $callback, $options[ $name ] ); } } } public function save() { update_option( self::OPTION_NAME, $this->get_properties() ); } } slug-translation/wpml-st-slug.php 0000755 00000005026 14720402445 0013146 0 ustar 00 <?php class WPML_ST_Slug { /** @var int $original_id */ private $original_id; /** @var string $original_lang */ private $original_lang; /** @var string $original_value */ private $original_value; /** @var array $langs */ private $langs = array(); /** @param stdClass $data */ public function set_lang_data( stdClass $data ) { if ( isset( $data->language ) ) { $this->langs[ $data->language ] = $data; } // Case of original string language if ( isset( $data->id ) ) { $this->original_id = $data->id; $this->original_lang = $data->language; $this->original_value = $data->value; } } /** @return string */ public function get_original_lang() { return $this->original_lang; } /** @return string */ public function get_original_value() { return $this->original_value; } /** @return int */ public function get_original_id() { return (int) $this->original_id; } /** * @param string $lang * * @return string */ public function get_value( $lang ) { if ( isset( $this->langs[ $lang ]->value ) ) { return $this->langs[ $lang ]->value; } return $this->get_original_value(); } /** * @param string $lang * * @return int */ public function get_status( $lang ) { if ( isset( $this->langs[ $lang ]->status ) ) { return (int) $this->langs[ $lang ]->status; } return ICL_TM_NOT_TRANSLATED; } /** * @param string $lang * * @return bool */ public function is_translation_complete( $lang ) { return ICL_TM_COMPLETE === $this->get_status( $lang ); } /** @return string|null */ public function get_context() { if ( isset( $this->langs[ $this->original_lang ]->context ) ) { return $this->langs[ $this->original_lang ]->context; } return null; } /** @return string|null */ public function get_name() { if ( isset( $this->langs[ $this->original_lang ]->name ) ) { return $this->langs[ $this->original_lang ]->name; } return null; } /** @return array */ public function get_language_codes() { return array_keys( $this->langs ); } /** * This method is used as a filter which returns the initial `$slug_value` * if no better value was found. * * @param string|false $slug_value * @param string $lang * * @return string */ public function filter_value( $slug_value, $lang ) { if ( $this->original_lang === $lang ) { return $this->original_value; } elseif ( in_array( $lang, $this->get_language_codes(), true ) && $this->is_translation_complete( $lang ) ) { return $this->get_value( $lang ); } return $slug_value; } } slug-translation/wpml-st-slug-translation-ui-save.php 0000755 00000010415 14720402445 0017047 0 ustar 00 <?php use WPML\API\Sanitize; class WPML_ST_Slug_Translation_UI_Save implements IWPML_Action { const ACTION_HOOK_FOR_POST = 'wpml_save_cpt_sync_settings'; const ACTION_HOOK_FOR_TAX = 'wpml_save_taxonomy_sync_settings'; /** @var WPML_ST_Slug_Translation_Settings $settings */ private $settings; /** @var WPML_Slug_Translation_Records $records */ private $records; /** @var SitePress $sitepress */ private $sitepress; /** @var IWPML_WP_Element_Type $wp_element_type */ private $wp_element_type; /** * @var string $action_hook either WPML_ST_Slug_Translation_UI_Save::ACTION_HOOK_FOR_POST * or WPML_ST_Slug_Translation_UI_Save::ACTION_HOOK_FOR_TAX */ private $action_hook; public function __construct( WPML_ST_Slug_Translation_Settings $settings, WPML_Slug_Translation_Records $records, SitePress $sitepress, IWPML_WP_Element_Type $wp_element_type, $action_hook ) { $this->settings = $settings; $this->records = $records; $this->sitepress = $sitepress; $this->wp_element_type = $wp_element_type; $this->action_hook = $action_hook; } public function add_hooks() { add_action( $this->action_hook, array( $this, 'save_element_type_slug_translation_options' ), 1 ); } public function save_element_type_slug_translation_options() { if ( $this->settings->is_enabled() && ! empty( $_POST['translate_slugs'] ) ) { foreach ( $_POST['translate_slugs'] as $type => $data ) { $type = (string) Sanitize::string( $type ); $data = $this->sanitize_translate_slug_data( $data ); $is_type_enabled = $this->has_translation( $data ); $this->settings->set_type( $type, $is_type_enabled ); $this->update_slug_translations( $type, $data ); } $this->settings->save(); } } /** * @param array $data * * @return array */ private function sanitize_translate_slug_data( array $data ) { $data['original'] = Sanitize::stringProp( 'original', $data ); foreach ( $data['langs'] as $lang => $translated_slug ) { $data['langs'][ $lang ] = Sanitize::string( $translated_slug ); } return $data; } private function has_translation( array $data ) { $slug_translations = $this->get_slug_translations( $data ); foreach ( $slug_translations as $slug ) { if ( trim( $slug ) ) { return true; } } return false; } /** * @param array $data * * @return array */ private function get_slug_translations( array $data ) { $slugs = $data['langs']; $original_slug_lang = $data['original']; unset( $slugs[ $original_slug_lang ] ); return $slugs; } /** * @param string $type * @param array $data */ private function update_slug_translations( $type, array $data ) { $string = $this->records->get_slug_string( $type ); if ( ! $string ) { $string = $this->register_string_if_not_exit( $type ); } if ( $string ) { $original_lang = $this->sitepress->get_default_language(); if ( isset( $data['original'] ) ) { $original_lang = $data['original']; } if ( $string->get_language() !== $original_lang ) { $string->set_language( $original_lang ); } if ( isset( $data['langs'] ) ) { foreach ( $this->sitepress->get_active_languages() as $code => $lang ) { if ( $code !== $original_lang ) { $translation_value = $this->sanitize_slug( $data['langs'][ $code ] ); $translation_value = urldecode( $translation_value ); $string->set_translation( $code, $translation_value, ICL_TM_COMPLETE ); } } } $string->update_status(); } } /** * @param string $type * * @return null|WPML_ST_String */ private function register_string_if_not_exit( $type ) { $slug = $this->get_registered_slug( $type ); $this->records->register_slug( $type, $slug ); return $this->records->get_slug_string( $type ); } /** * @param string $slug * * @return string */ private function sanitize_slug( $slug ) { return implode( '/', array_map( array( 'WPML_Slug_Translation', 'sanitize' ), explode( '/', $slug ) ) ); } /** * @param string $type_name * * @return string */ private function get_registered_slug( $type_name ) { $wp_element = $this->wp_element_type->get_wp_element_type_object( $type_name ); return $wp_element ? trim( $wp_element->rewrite['slug'], '/' ) : false; } } slug-translation/wpml-st-slug-translation-settings.php 0000755 00000001424 14720402445 0017336 0 ustar 00 <?php class WPML_ST_Slug_Translation_Settings { const KEY_ENABLED_GLOBALLY = 'wpml_base_slug_translation'; /** @param bool $enabled */ public function set_enabled( $enabled ) { update_option( self::KEY_ENABLED_GLOBALLY, (int) $enabled ); } /** @return bool */ public function is_enabled() { return (bool) get_option( self::KEY_ENABLED_GLOBALLY ); } public function is_translated( $type_name ) { throw new Exception( 'Use a child class with the proper element type: post or taxonomy.' ); } public function set_type( $type, $is_type_enabled ) { throw new Exception( 'Use a child class with the proper element type: post or taxonomy.' ); } public function save() { throw new Exception( 'Use a child class with the proper element type: post or taxonomy.' ); } } slug-translation/new-match-finder/wpml-st-slug-new-match-finder.php 0000755 00000006350 14720402445 0021425 0 ustar 00 <?php use WPML\FP\Fns; use function WPML\FP\partial; class WPML_ST_Slug_New_Match_Finder { /** * @param string $match * @param WPML_ST_Slug_Custom_Type[] $custom_types * * @return WPML_ST_Slug_New_Match */ public function get( $match, array $custom_types ) { $best_match = $this->find_the_best_match( $match, $this->map_to_new_matches( $match, $custom_types ) ); if ( ! $best_match ) { $best_match = new WPML_ST_Slug_New_Match( $match, false ); } return $best_match; } /** * @param string $match * @param WPML_ST_Slug_Custom_Type[] $custom_types * * @return WPML_ST_Slug_New_Match[] */ private function map_to_new_matches( $match, array $custom_types ) { return Fns::map( partial( [ $this, 'find_match_of_type' ], $match ), $custom_types ); } /** * @param string $match * @param WPML_ST_Slug_Custom_Type $custom_type * * @return WPML_ST_Slug_New_Match */ public function find_match_of_type( $match, WPML_ST_Slug_Custom_Type $custom_type ) { if ( $custom_type->is_using_tags() ) { $slug = $this->filter_slug_using_tag( $custom_type->get_slug() ); $slug_translation = $this->filter_slug_using_tag( $custom_type->get_slug_translation() ); $new_match = $this->adjust_match( $match, $slug, $slug_translation ); $result = new WPML_ST_Slug_New_Match( $new_match, $custom_type->is_display_as_translated() ); } else { $new_match = $this->adjust_match( $match, $custom_type->get_slug(), $custom_type->get_slug_translation() ); $result = new WPML_ST_Slug_New_Match( $new_match, $match !== $new_match && $custom_type->is_display_as_translated() ); } return $result; } private function filter_slug_using_tag( $slug ) { if ( preg_match( '#%([^/]+)%#', $slug ) ) { $slug = preg_replace( '#%[^/]+%#', '.+?', $slug ); } if ( preg_match( '#\.\+\?#', $slug ) ) { $slug = preg_replace( '#\.\+\?#', '(.+?)', $slug ); } return $slug; } /** * @param string $match * @param string $slug * @param string $slug_translation * * @return string */ private function adjust_match( $match, $slug, $slug_translation ) { if ( ! empty( $slug_translation ) && preg_match( '#^[^/]*/?\(?' . preg_quote( $slug ) . '\)?/#', $match ) && $slug !== $slug_translation ) { $replace = function( $match ) use ( $slug, $slug_translation ) { return str_replace( $slug, $slug_translation, $match[0]); }; $match = preg_replace_callback( '#^\(?' . preg_quote( addslashes( $slug ) ) . '\)?/#', $replace, $match ); } return $match; } /** * The best is that which differs the most from the original * * @param string $match * @param WPML_ST_Slug_New_Match[] $new_matches * * @return WPML_ST_Slug_New_Match */ private function find_the_best_match( $match, $new_matches ) { $similarities = array(); foreach ( $new_matches as $new_match ) { $percent = 0; similar_text( $match, $new_match->get_value(), $percent ); // Multiply $percent by 100 because floats as array keys are truncated to integers // This will allow for fractional percentages. $similarities[ intval( $percent * 100 ) ] = $new_match; } ksort( $similarities ); return reset( $similarities ); } } slug-translation/new-match-finder/wpml-st-slug-new-match.php 0000755 00000001043 14720402445 0020152 0 ustar 00 <?php class WPML_ST_Slug_New_Match { /** @var string */ private $value; /** @var bool */ private $preserve_original; /** * @param string $value * @param bool $preserve_original */ public function __construct( $value, $preserve_original ) { $this->value = $value; $this->preserve_original = $preserve_original; } /** * @return string */ public function get_value() { return $this->value; } /** * @return bool */ public function should_preserve_original() { return $this->preserve_original; } } slug-translation/class-wpml-rewrite-rule-filter.php 0000755 00000003051 14720402445 0016560 0 ustar 00 <?php class WPML_Rewrite_Rule_Filter implements IWPML_ST_Rewrite_Rule_Filter { /** @var WPML_ST_Slug_Translation_Custom_Types_Repository[] */ private $custom_types_repositories; /** @var WPML_ST_Slug_New_Match_Finder */ private $new_match_finder; /** * @param WPML_ST_Slug_Translation_Custom_Types_Repository[] $custom_types_repositories * @param WPML_ST_Slug_New_Match_Finder $new_match_finder */ public function __construct( array $custom_types_repositories, WPML_ST_Slug_New_Match_Finder $new_match_finder ) { $this->custom_types_repositories = $custom_types_repositories; $this->new_match_finder = $new_match_finder; } /** * @param array|false|null $rules * * @return array */ function rewrite_rules_filter( $rules ) { if ( ! is_array( $rules ) && empty( $rules ) ) { return $rules; } $custom_types = $this->get_custom_types(); if ( ! $custom_types ) { return $rules; } $result = array(); foreach ( $rules as $match => $query ) { $new_match = $this->new_match_finder->get( $match, $custom_types ); $result[ $new_match->get_value() ] = $query; if ( $new_match->should_preserve_original() ) { $result[ $match ] = $query; } } return $result; } private function get_custom_types() { if ( empty( $this->custom_types_repositories ) ) { return array(); } $types = array(); foreach ( $this->custom_types_repositories as $repository ) { $types[] = $repository->get(); } return call_user_func_array( 'array_merge', $types ); } } slug-translation/custom-types/wpml-st-slug-translation-custom-types-repository.php 0000755 00000000216 14720402445 0025041 0 ustar 00 <?php interface WPML_ST_Slug_Translation_Custom_Types_Repository { /** * @return WPML_ST_Slug_Custom_Type[] */ public function get(); } slug-translation/custom-types/wpml-st-slug-translation-taxonomy-custom-types-repository.php 0000755 00000003473 14720402445 0026725 0 ustar 00 <?php class WPML_ST_Slug_Translation_Taxonomy_Custom_Types_Repository implements WPML_ST_Slug_Translation_Custom_Types_Repository { /** @var SitePress */ private $sitepress; /** @var WPML_ST_Slug_Custom_Type_Factory */ private $custom_type_factory; /** @var WPML_ST_Tax_Slug_Translation_Settings $settings */ private $settings_repository; /** @var array */ private $settings; public function __construct( SitePress $sitepress, WPML_ST_Slug_Custom_Type_Factory $custom_type_factory, WPML_ST_Tax_Slug_Translation_Settings $settings_repository ) { $this->sitepress = $sitepress; $this->custom_type_factory = $custom_type_factory; $this->settings_repository = $settings_repository; } public function get() { return array_map( array( $this, 'build_object' ), array_values( array_filter( get_taxonomies( array( 'publicly_queryable' => true ) ), array( $this, 'filter' ) ) ) ); } /** * @param string $type * * @return bool */ private function filter( $type ) { $settings = $this->get_taxonomy_slug_translation_settings(); return isset( $settings[ $type ] ) && $settings[ $type ] && $this->sitepress->is_translated_taxonomy( $type ); } /** * @param string $type * * @return WPML_ST_Slug_Custom_Type */ private function build_object( $type ) { return $this->custom_type_factory->create( $type, $this->is_display_as_translated( $type ) ); } /** * @return array */ private function get_taxonomy_slug_translation_settings() { if ( null === $this->settings ) { $this->settings = $this->settings_repository->get_types(); } return $this->settings; } /** * @param string $type * * @return bool */ private function is_display_as_translated( $type ) { return $this->sitepress->is_display_as_translated_taxonomy( $type ); } } slug-translation/custom-types/wpml-st-slug-custom-type-factory.php 0000755 00000001701 14720402445 0021552 0 ustar 00 <?php class WPML_ST_Slug_Custom_Type_Factory { /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Slug_Translation_Records $slug_records */ private $slug_records; /** @var WPML_ST_Slug_Translations */ private $slug_translations; public function __construct( SitePress $sitepress, WPML_Slug_Translation_Records $slug_records, WPML_ST_Slug_Translations $slug_translations ) { $this->sitepress = $sitepress; $this->slug_records = $slug_records; $this->slug_translations = $slug_translations; } /** * @param string $name * @param bool $display_as_translated * * @return WPML_ST_Slug_Custom_Type */ public function create( $name, $display_as_translated ) { $slug = $this->slug_records->get_slug( $name ); return new WPML_ST_Slug_Custom_Type( $name, $display_as_translated, $slug->get_original_value(), $this->slug_translations->get( $slug, $display_as_translated ) ); } } slug-translation/custom-types/wpml-st-slug-custom-type.php 0000755 00000002761 14720402445 0020114 0 ustar 00 <?php /** * It may represent custom posts or custom taxonomies */ class WPML_ST_Slug_Custom_Type { /** @var string */ private $name; /** @var bool */ private $display_as_translated; /** @var string */ private $slug; /** @var string */ private $slug_translation; /** * WPML_ST_Slug_Custom_Type constructor. * * @param string $name * @param bool $display_as_translated * @param string $slug * @param string $slug_translation */ public function __construct( $name, $display_as_translated, $slug, $slug_translation ) { $this->name = $name; $this->display_as_translated = $display_as_translated; $this->slug = $slug; $this->slug_translation = $slug_translation; } /** * @return string */ public function get_name() { return $this->name; } /** * @return bool */ public function is_display_as_translated() { return $this->display_as_translated; } /** * @return string */ public function get_slug() { return $this->slug; } /** * @return string */ public function get_slug_translation() { return $this->slug_translation; } /** * @return bool */ public function is_using_tags() { $pattern = '#%([^/]+)%#'; $slug = isset($this->slug) && is_string($this->slug) ? $this->slug : ''; $slug_translation = isset($this->slug_translation) && is_string($this->slug_translation) ? $this->slug_translation : ''; return preg_match($pattern, $slug) || preg_match($pattern, $slug_translation); } } slug-translation/custom-types/wpml-st-slug-translations.php 0000755 00000002005 14720402445 0020333 0 ustar 00 <?php use WPML\Element\API\Languages; class WPML_ST_Slug_Translations { /** * @param WPML_ST_Slug $slug * @param bool $display_as_translated_mode * * @return string */ public function get( WPML_ST_Slug $slug, $display_as_translated_mode ) { $slug_translation = $this->get_slug_translation_to_lang( $slug, Languages::getCurrentCode() ); if ( ! $slug_translation ) { $default_language = Languages::getDefaultCode(); if ( $display_as_translated_mode && $default_language != 'en' ) { $slug_translation = $this->get_slug_translation_to_lang( $slug, $default_language ); } else { $slug_translation = $slug->get_original_value(); } } return $slug_translation ? trim( $slug_translation, '/' ) : ''; } /** * @param WPML_ST_Slug $slug * @param string $lang * * @return string|null */ private function get_slug_translation_to_lang( WPML_ST_Slug $slug, $lang ) { if ( $slug->is_translation_complete( $lang ) ) { return $slug->get_value( $lang ); } return null; } } slug-translation/custom-types/wpml-st-slug-translation-post-custom-types-repository.php 0000755 00000003443 14720402445 0026031 0 ustar 00 <?php class WPML_ST_Slug_Translation_Post_Custom_Types_Repository implements WPML_ST_Slug_Translation_Custom_Types_Repository { /** @var SitePress */ private $sitepress; /** @var WPML_ST_Slug_Custom_Type_Factory */ private $custom_type_factory; /** @var array */ private $post_slug_translation_settings; public function __construct( SitePress $sitepress, WPML_ST_Slug_Custom_Type_Factory $custom_type_factory ) { $this->sitepress = $sitepress; $this->custom_type_factory = $custom_type_factory; } public function get() { return array_map( array( $this, 'build_object' ), array_values( array_filter( get_post_types( array( 'publicly_queryable' => true ) ), array( $this, 'filter' ) ) ) ); } /** * @param string $type * * @return bool */ private function filter( $type ) { $post_slug_translation_settings = $this->get_post_slug_translation_settings(); return isset( $post_slug_translation_settings['types'][ $type ] ) && $post_slug_translation_settings['types'][ $type ] && $this->sitepress->is_translated_post_type( $type ); } /** * @param string $type * * @return WPML_ST_Slug_Custom_Type */ private function build_object( $type ) { return $this->custom_type_factory->create( $type, $this->is_display_as_translated( $type ) ); } /** * @return array */ private function get_post_slug_translation_settings() { if ( null === $this->post_slug_translation_settings ) { $this->post_slug_translation_settings = $this->sitepress->get_setting( 'posts_slug_translation', array() ); } return $this->post_slug_translation_settings; } /** * @param string $type * * @return bool */ private function is_display_as_translated( $type ) { return $this->sitepress->is_display_as_translated_post_type( $type ); } } slug-translation/post/wpml-post-slug-translation-records.php 0000755 00000000557 14720402445 0020471 0 ustar 00 <?php class WPML_Post_Slug_Translation_Records extends WPML_Slug_Translation_Records { const STRING_NAME = 'URL slug: %s'; /** * @param string $slug * * @return string */ protected function get_string_name( $slug ) { return sprintf( self::STRING_NAME, $slug ); } /** @return string */ protected function get_element_type() { return 'post'; } } slug-translation/post/wpml-st-post-slug-translation-settings.php 0000755 00000002577 14720402445 0021320 0 ustar 00 <?php /** * @todo: Move these settings to an independent option * like WPML_ST_Tax_Slug_Translation_Settings::OPTION_NAME */ class WPML_ST_Post_Slug_Translation_Settings extends WPML_ST_Slug_Translation_Settings { const KEY_IN_SITEPRESS_SETTINGS = 'posts_slug_translation'; /** @var SitePress $sitepress */ private $sitepress; private $settings; public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $this->settings = $sitepress->get_setting( self::KEY_IN_SITEPRESS_SETTINGS, array( ) ); } /** @param bool $enabled */ public function set_enabled( $enabled ) { parent::set_enabled( $enabled ); /** * Backward compatibility with 3rd part plugins * The `on` key has been replaced by an independent option * WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY */ $this->settings['on'] = (int) $enabled; } /** * @param string $type * * @return bool */ public function is_translated( $type ) { return ! empty( $this->settings['types'][ $type ] ); } /** * @param string $type * @param bool $is_enabled */ public function set_type( $type, $is_enabled ) { if ( $is_enabled ) { $this->settings['types'][ $type ] = 1; } else { unset( $this->settings['types'][ $type ] ); } } public function save() { $this->sitepress->set_setting( self::KEY_IN_SITEPRESS_SETTINGS, $this->settings, true ); } } slug-translation/wpml-st-slug-translation-strings-sync.php 0000755 00000004120 14720402445 0020135 0 ustar 00 <?php class WPML_ST_Slug_Translation_Strings_Sync implements IWPML_Action { /** @var WPML_Slug_Translation_Records_Factory $slug_records_factory */ private $slug_records_factory; /** @var WPML_ST_Slug_Translation_Settings_Factory $slug_settings_factory */ private $slug_settings_factory; public function __construct( WPML_Slug_Translation_Records_Factory $slug_records_factory, WPML_ST_Slug_Translation_Settings_Factory $slug_settings_factory ) { $this->slug_records_factory = $slug_records_factory; $this->slug_settings_factory = $slug_settings_factory; } public function add_hooks() { add_action( 'registered_taxonomy', array( $this, 'run_taxonomy_sync' ), 10, 3 ); add_action( 'registered_post_type', array( $this, 'run_post_type_sync' ), 10, 2 ); } /** * @param string $taxonomy * @param string|array $object_type * @param array $taxonomy_array */ public function run_taxonomy_sync( $taxonomy, $object_type, $taxonomy_array ) { if ( isset( $taxonomy_array['rewrite']['slug'] ) && $taxonomy_array['rewrite']['slug'] ) { $this->sync_element_slug( $taxonomy_array['rewrite']['slug'], $taxonomy, WPML_Slug_Translation_Factory::TAX ); } } /** * @param string $post_type * @param WP_Post_Type $post_type_object */ public function run_post_type_sync( $post_type, $post_type_object ) { if ( isset( $post_type_object->rewrite['slug'] ) && $post_type_object->rewrite['slug'] ) { $this->sync_element_slug( trim( $post_type_object->rewrite['slug'], '/' ), $post_type, WPML_Slug_Translation_Factory::POST ); } } /** * @param string $rewrite_slug * @param string $type * @param string $element_type */ public function sync_element_slug( $rewrite_slug, $type, $element_type ) { $settings = $this->slug_settings_factory->create( $element_type ); if ( ! $settings->is_translated( $type ) ) { return; } $records = $this->slug_records_factory->create( $element_type ); $slug = $records->get_slug( $type ); if ( $slug->get_original_value() !== $rewrite_slug ) { $records->update_original_slug( $type, $rewrite_slug ); } } } slug-translation/wpml-slug-translation-records-factory.php 0000755 00000001511 14720402445 0020155 0 ustar 00 <?php class WPML_Slug_Translation_Records_Factory { /** * @param string $type * * @return WPML_Post_Slug_Translation_Records|WPML_Tax_Slug_Translation_Records */ public function create( $type ) { /** @var wpdb */ global $wpdb; $cache_factory = new WPML_WP_Cache_Factory(); if ( WPML_Slug_Translation_Factory::POST === $type ) { return new WPML_Post_Slug_Translation_Records( $wpdb, $cache_factory ); } elseif ( WPML_Slug_Translation_Factory::TAX === $type ) { return new WPML_Tax_Slug_Translation_Records( $wpdb, $cache_factory ); } return null; } /** * @return WPML_Tax_Slug_Translation_Records */ public function createTaxRecords() { /** @var wpdb */ global $wpdb; $cache_factory = new WPML_WP_Cache_Factory(); return new WPML_Tax_Slug_Translation_Records( $wpdb, $cache_factory ); } } slug-translation/wpml-slug-translation-factory.php 0000755 00000003577 14720402445 0016534 0 ustar 00 <?php class WPML_Slug_Translation_Factory implements IWPML_Frontend_Action_Loader, IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader { const POST = 'post'; const TAX = 'taxonomy'; const INIT_PRIORITY = -1000; public function create() { global $sitepress; $hooks = array(); $records_factory = new WPML_Slug_Translation_Records_Factory(); $settings_factory = new WPML_ST_Slug_Translation_Settings_Factory(); $post_records = $records_factory->create( self::POST ); $tax_records = $records_factory->createTaxRecords(); $global_settings = $settings_factory->create(); $post_settings = $settings_factory->create( self::POST ); $tax_settings = $settings_factory->createTaxSettings(); $term_link_filter = new WPML_ST_Term_Link_Filter( $tax_records, $sitepress, new WPML_WP_Cache_Factory(), $tax_settings ); $hooks['legacy_class'] = new WPML_Slug_Translation( $sitepress, $records_factory, WPML_Get_LS_Languages_Status::get_instance(), $term_link_filter, $global_settings ); $hooks['rewrite_rules'] = ( new \WPML\ST\SlugTranslation\Hooks\HooksFactory() )->create(); if ( is_admin() ) { $hooks['ui_save_post'] = new WPML_ST_Slug_Translation_UI_Save( $post_settings, $post_records, $sitepress, new WPML_WP_Post_Type(), WPML_ST_Slug_Translation_UI_Save::ACTION_HOOK_FOR_POST ); $hooks['ui_save_tax'] = new WPML_ST_Slug_Translation_UI_Save( $tax_settings, $tax_records, $sitepress, new WPML_WP_Taxonomy(), WPML_ST_Slug_Translation_UI_Save::ACTION_HOOK_FOR_TAX ); if ( $global_settings->is_enabled() ) { $hooks['sync_strings'] = new WPML_ST_Slug_Translation_Strings_Sync( $records_factory, $settings_factory ); } } $hooks['public-api'] = new WPML_ST_Slug_Translation_API( $records_factory, $settings_factory, $sitepress, new WPML_WP_API() ); return $hooks; } } slug-translation/class-wpml-slug-translation.php 0000755 00000030641 14720402445 0016162 0 ustar 00 <?php class WPML_Slug_Translation implements IWPML_Action { const STRING_DOMAIN = 'WordPress'; /** @var array $post_link_cache */ private $post_link_cache = array(); /** @var SitePress $sitepress */ private $sitepress; /** @var WPML_Slug_Translation_Records_Factory $slug_records_factory */ private $slug_records_factory; /** @var WPML_ST_Term_Link_Filter $term_link_filter */ private $term_link_filter; /** @var WPML_Get_LS_Languages_Status $ls_languages_status */ private $ls_languages_status; /** @var WPML_ST_Slug_Translation_Settings $slug_translation_settings */ private $slug_translation_settings; private $ignore_post_type_link = false; public function __construct( SitePress $sitepress, WPML_Slug_Translation_Records_Factory $slug_records_factory, WPML_Get_LS_Languages_Status $ls_language_status, WPML_ST_Term_Link_Filter $term_link_filter, WPML_ST_Slug_Translation_Settings $slug_translation_settings ) { $this->sitepress = $sitepress; $this->slug_records_factory = $slug_records_factory; $this->ls_languages_status = $ls_language_status; $this->term_link_filter = $term_link_filter; $this->slug_translation_settings = $slug_translation_settings; } public function add_hooks() { add_action( 'init', array( $this, 'init' ), WPML_Slug_Translation_Factory::INIT_PRIORITY ); } public function init() { $this->migrate_global_enabled_setting(); if ( $this->slug_translation_settings->is_enabled() ) { add_filter( 'post_type_link', array( $this, 'post_type_link_filter' ), apply_filters( 'wpml_post_type_link_priority', 1 ), 4 ); add_filter( 'pre_term_link', array( $this->term_link_filter, 'replace_slug_in_termlink' ), 1, 2 ); // high priority add_filter( 'edit_post', array( $this, 'clear_post_link_cache' ), 1, 2 ); add_filter( 'query_vars', array( $this, 'add_cpt_names' ), 1, 1 ); add_filter( 'pre_get_posts', array( $this, 'filter_pre_get_posts' ), - 1000, 1 ); } if ( is_admin() ) { add_action( 'icl_ajx_custom_call', array( $this, 'gui_save_options' ), 10, 1 ); add_action( 'wp_loaded', array( $this, 'maybe_migrate_string_name' ), 10, 0 ); } } /** * @deprecated since 2.8.0, use the class `WPML_Post_Slug_Translation_Records` instead. * * @param string $type * * @return null|string */ public static function get_slug_by_type( $type ) { $slug_records_factory = new WPML_Slug_Translation_Records_Factory(); $slug_records = $slug_records_factory->create( WPML_Slug_Translation_Factory::POST ); return $slug_records->get_original( $type ); } /** * This method is only for CPT * * @deprecated use `WPML_ST_Slug::filter_value` directly of the filter hook `wpml_get_translated_slug` * * @param string|false $slug_value * @param string $post_type * @param string|bool $language * * @return string */ public function get_translated_slug( $slug_value, $post_type, $language = false ) { if ( $post_type ) { $language = $language ? $language : $this->sitepress->get_current_language(); $slug = $this->slug_records_factory->create( WPML_Slug_Translation_Factory::POST ) ->get_slug( $post_type ); return $slug->filter_value( $slug_value, $language ); } return $slug_value; } /** * @param array $value * * @return array * @deprecated Use WPML\ST\SlugTranslation\Hooks\Hooks::filter */ public static function rewrite_rules_filter( $value ) { return ( new \WPML\ST\SlugTranslation\Hooks\HooksFactory() )->create()->filter( $value ); } /** * @param string $post_link * @param WP_Post $post * @param bool $leavename * @param bool $sample * * @return mixed|string|WP_Error */ public function post_type_link_filter( $post_link, $post, $leavename, $sample ) { if ( $this->ignore_post_type_link ) { return $post_link; } if ( ! $this->sitepress->is_translated_post_type( $post->post_type ) || ! ( $ld = $this->sitepress->get_element_language_details( $post->ID, 'post_' . $post->post_type ) ) ) { return $post_link; } $ld = apply_filters( 'wpml_st_post_type_link_filter_language_details', $ld ); $cache_key = $leavename . '#' . $sample; $cache_key .= $this->ls_languages_status->is_getting_ls_languages() ? 'yes' : 'no'; $cache_key .= $ld->language_code; $blog_id = get_current_blog_id(); if ( isset( $this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ] ) ) { $post_link = $this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ]; } else { $slug_settings = $this->sitepress->get_setting( 'posts_slug_translation' ); $slug_settings = ! empty( $slug_settings['types'][ $post->post_type ] ) ? $slug_settings['types'][ $post->post_type ] : null; if ( (bool) $slug_settings === true ) { $post_type_obj = get_post_type_object( $post->post_type ); $slug_this = isset( $post_type_obj->rewrite['slug'] ) ? trim( $post_type_obj->rewrite['slug'], '/' ) : false; $slug_real = $this->get_translated_slug( $slug_this, $post->post_type, $ld->language_code ); if ( empty( $slug_real ) || empty( $slug_this ) || $slug_this == $slug_real ) { return $post_link; } global $wp_rewrite; if ( isset( $wp_rewrite->extra_permastructs[ $post->post_type ] ) ) { $struct_original = $wp_rewrite->extra_permastructs[ $post->post_type ]['struct']; /** * This hook allows to filter the slug we want to search and replace * in the permalink structure. This is required for 3rd party * plugins replacing the original slug with a placeholder. * * @since 3.1.0 * * @param string $slug_this The original slug. * @param string $post_link The initial link. * @param WP_Post $post The post. * @param bool $leavename Whether to keep the post name. * @param bool $sample Is it a sample permalink. */ $slug_this = apply_filters( 'wpml_st_post_type_link_filter_original_slug', $slug_this, $post_link, $post, $leavename, $sample ); $lslash = false !== strpos( $struct_original, '/' . $slug_this ) ? '/' : ''; $wp_rewrite->extra_permastructs[ $post->post_type ]['struct'] = preg_replace( '@' . $lslash . $slug_this . '/@', $lslash . $slug_real . '/', $struct_original ); $this->ignore_post_type_link = true; $post_link = get_post_permalink( $post->ID, $leavename, $sample ); $this->ignore_post_type_link = false; $wp_rewrite->extra_permastructs[ $post->post_type ]['struct'] = $struct_original; } else { $post_link = str_replace( $slug_this . '=', $slug_real . '=', $post_link ); } } $this->post_link_cache[ $blog_id ][ $post->ID ][ $cache_key ] = $post_link; } return $post_link; } /** * @param int $post_ID * @param \WP_Post $post */ public function clear_post_link_cache( $post_ID, $post ) { $blog_id = get_current_blog_id(); unset( $this->post_link_cache[ $blog_id ][ $post_ID ] ); } /** * @return array */ private function get_all_post_slug_translations() { $slug_translations = array(); $post_slug_translation_settings = $this->sitepress->get_setting( 'posts_slug_translation' ); if ( isset( $post_slug_translation_settings['types'] ) ) { $types = $post_slug_translation_settings['types']; $cache_key = 'WPML_Slug_Translation::get_all_slug_translations' . md5( (string) json_encode( $types ) ); $slug_translations = wp_cache_get( $cache_key ); if ( ! is_array( $slug_translations ) ) { $slug_translations = array(); $types_to_fetch = array(); foreach ( $types as $type => $state ) { if ( $state ) { $types_to_fetch[] = str_replace( '%', '%%', $type ); } } if ( $types_to_fetch ) { $data = $this->slug_records_factory ->create( WPML_Slug_Translation_Factory::POST ) ->get_all_slug_translations( $types_to_fetch ); foreach ( $data as $row ) { foreach ( $types_to_fetch as $type ) { if ( preg_match( '#\s' . $type . '$#', $row->name ) === 1 ) { $slug_translations[ $row->value ] = $type; } } } } wp_cache_set( $cache_key, $slug_translations ); } } return $slug_translations; } /** * Adds all translated custom post type slugs as valid query variables in addition to their original values * * @param array $qvars * * @return array */ public function add_cpt_names( $qvars ) { $all_slugs_translations = array_keys( $this->get_all_post_slug_translations() ); $qvars = array_merge( $qvars, $all_slugs_translations ); return $qvars; } /** * @param WP_Query $query * * @return WP_Query */ public function filter_pre_get_posts( $query ) { /** Do not alter the query if it has already resolved the post ID */ if ( ! empty( $query->query_vars['p'] ) ) { return $query; } $all_slugs_translations = $this->get_all_post_slug_translations(); foreach ( $query->query as $slug => $post_name ) { if ( isset( $all_slugs_translations[ $slug ] ) ) { $new_slug = isset( $all_slugs_translations[ $slug ] ) ? $all_slugs_translations[ $slug ] : $slug; unset( $query->query[ $slug ] ); $query->query[ $new_slug ] = $post_name; $query->query['name'] = $post_name; $query->query['post_type'] = $new_slug; unset( $query->query_vars[ $slug ] ); $query->query_vars[ $new_slug ] = $post_name; $query->query_vars['name'] = $post_name; $query->query_vars['post_type'] = $new_slug; } } return $query; } /** * @param string $action */ public static function gui_save_options( $action ) { switch ( $action ) { case 'icl_slug_translation': global $sitepress; $is_enabled = intval( ! empty( $_POST['icl_slug_translation_on'] ) ); $settings = new WPML_ST_Post_Slug_Translation_Settings( $sitepress ); $settings->set_enabled( (bool) $is_enabled ); echo '1|' . $is_enabled; break; } } /** * @param string $slug * * @return string */ public static function sanitize( $slug ) { // we need to preserve the % $slug = str_replace( '%', '%45', $slug ); $slug = sanitize_title_with_dashes( $slug ); $slug = str_replace( '%45', '%', $slug ); /** * Filters the sanitized post type or taxonomy slug translation * * @since 2.10.0 * * @param string $slug */ return apply_filters( 'wpml_st_slug_translation_sanitize', $slug ); } /** * @deprecated since 2.8.0, use the class `WPML_Post_Slug_Translation_Records` instead. */ public static function register_string_for_slug( $post_type, $slug ) { return icl_register_string( self::STRING_DOMAIN, 'URL slug: ' . $post_type, $slug ); } public function maybe_migrate_string_name() { global $wpdb; $slug_settings = $this->sitepress->get_setting( 'posts_slug_translation' ); if ( ! isset( $slug_settings['string_name_migrated'] ) ) { /** @var string[] $queryable_post_types */ $queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) ); foreach ( $queryable_post_types as $type ) { $post_type_obj = get_post_type_object( $type ); if ( null === $post_type_obj || ! isset( $post_type_obj->rewrite['slug'] ) ) { continue; } $slug = trim( $post_type_obj->rewrite['slug'], '/' ); if ( $slug ) { // First check if we should migrate from the old format URL slug: slug $string_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}icl_strings WHERE name = %s AND value = %s", 'URL slug: ' . $slug, $slug ) ); if ( $string_id ) { // migrate it to URL slug: post_type $st_update['name'] = 'URL slug: ' . $type; $wpdb->update( $wpdb->prefix . 'icl_strings', $st_update, array( 'id' => $string_id ) ); } } } $slug_settings['string_name_migrated'] = true; $this->sitepress->set_setting( 'posts_slug_translation', $slug_settings, true ); } } /** * Move global on/off setting to its own option WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY */ private function migrate_global_enabled_setting() { $enabled = get_option( WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY ); if ( false === $enabled ) { $old_setting = $this->sitepress->get_setting( WPML_ST_Post_Slug_Translation_Settings::KEY_IN_SITEPRESS_SETTINGS ); if ( array_key_exists( 'on', $old_setting ) ) { $enabled = (int) $old_setting['on']; } else { $enabled = 0; } update_option( WPML_ST_Slug_Translation_Settings::KEY_ENABLED_GLOBALLY, $enabled ); } } } slug-translation/CheckRedirect.php 0000755 00000002402 14720402445 0013265 0 ustar 00 <?php namespace WPML\ST\DisplayAsTranslated; use WPML\FP\Lst; use WPML\FP\Str; use WPML\FP\Obj; use WPML\LIB\WP\Hooks; use WPML\LIB\WP\Post; use function WPML\Container\make; use function WPML\FP\spreadArgs; class CheckRedirect implements \IWPML_Frontend_Action { public function add_hooks() { Hooks::onFilter( 'wpml_is_redirected', 10, 3 ) ->then( spreadArgs( [ self::class, 'checkForSlugTranslation' ] ) ); } public static function checkForSlugTranslation( $redirect, $post_id, $q ) { global $sitepress; if ( $redirect ) { $postType = Post::getType( $post_id ); if ( make( \WPML_ST_Post_Slug_Translation_Settings::class )->is_translated( $postType ) && $sitepress->is_display_as_translated_post_type( $postType ) ) { $adjustLanguageDetails = Obj::set( Obj::lensProp( 'language_code' ), $sitepress->get_current_language() ); add_filter( 'wpml_st_post_type_link_filter_language_details', $adjustLanguageDetails ); if ( \WPML_Query_Parser::is_permalink_part_of_request( get_permalink( $post_id ), explode( '?', $_SERVER['REQUEST_URI'] )[0] ) ) { $redirect = false; } remove_filter( 'wpml_st_post_type_link_filter_language_details', $adjustLanguageDetails ); } } return $redirect; } } slug-translation/wpml-st-slug-translation-api.php 0000755 00000011521 14720402445 0016246 0 ustar 00 <?php class WPML_ST_Slug_Translation_API implements IWPML_Action { /** * The section indexes are hardcoded in `sitepress-multilingual-cms/menu/_custom_types_translation.php` */ const SECTION_INDEX_POST = 7; const SECTION_INDEX_TAX = 8; /** @var WPML_Slug_Translation_Records_Factory $records_factory */ private $records_factory; /** @var WPML_ST_Slug_Translation_Settings_Factory $settings_factory */ private $settings_factory; /** @var IWPML_Current_Language $current_language */ private $current_language; /** @var WPML_WP_API $wp_api */ private $wp_api; public function __construct( WPML_Slug_Translation_Records_Factory $records_factory, WPML_ST_Slug_Translation_Settings_Factory $settings_factory, IWPML_Current_Language $current_language, WPML_WP_API $wp_api ) { $this->records_factory = $records_factory; $this->settings_factory = $settings_factory; $this->current_language = $current_language; $this->wp_api = $wp_api; } public function add_hooks() { add_action( 'init', array( $this, 'init' ), WPML_Slug_Translation_Factory::INIT_PRIORITY ); } public function init() { if ( $this->settings_factory->create()->is_enabled() ) { add_filter( 'wpml_get_translated_slug', array( $this, 'get_translated_slug_filter' ), 1, 4 ); add_filter( 'wpml_get_slug_translation_languages', array( $this, 'get_slug_translation_languages_filter' ), 1, 3 ); add_filter( 'wpml_type_slug_is_translated', array( $this, 'type_slug_is_translated_filter' ), 10, 3 ); } add_filter( 'wpml_slug_translation_available', '__return_true', 1 ); add_action( 'wpml_activate_slug_translation', array( $this, 'activate_slug_translation_action' ), 1, 3 ); add_filter( 'wpml_get_slug_translation_url', array( $this, 'get_slug_translation_url_filter' ), 1, 2 ); } /** * @param string $slug_value * @param string $type * @param string|bool $language * @param string $element_type WPML_Slug_Translation_Factory::POST|WPML_Slug_Translation_Factory::TAX * * @return string */ public function get_translated_slug_filter( $slug_value, $type, $language = false, $element_type = WPML_Slug_Translation_Factory::POST ) { if ( $type ) { $slug = $this->records_factory->create( $element_type )->get_slug( $type ); if ( ! $language ) { $language = $this->current_language->get_current_language(); } return $slug->filter_value( $slug_value, $language ); } return $slug_value; } /** * @param string $languages * @param string $type * @param string $element_type WPML_Slug_Translation_Factory::POST|WPML_Slug_Translation_Factory::TAX * * @return array */ public function get_slug_translation_languages_filter( $languages, $type, $element_type = WPML_Slug_Translation_Factory::POST ) { return $this->records_factory->create( $element_type )->get_slug_translation_languages( $type ); } /** * @param string $type * @param string|null $slug_value * @param string $element_type WPML_Slug_Translation_Factory::POST|WPML_Slug_Translation_Factory::TAX */ public function activate_slug_translation_action( $type, $slug_value = null, $element_type = WPML_Slug_Translation_Factory::POST ) { if ( ! $slug_value ) { $slug_value = $type; } $records = $this->records_factory->create( $element_type ); $settings = $this->settings_factory->create( $element_type ); $slug = $records->get_slug( $type ); if ( ! $slug->get_original_id() ) { $records->register_slug( $type, $slug_value ); } if ( ! $settings->is_enabled() || ! $settings->is_translated( $type ) ) { $settings->set_enabled( true ); $settings->set_type( $type, true ); $settings->save(); } } /** * @param string $url * @param string $element_type WPML_Slug_Translation_Factory::POST or WPML_Slug_Translation_Factory::TAX * * @return string */ public function get_slug_translation_url_filter( $url, $element_type = WPML_Slug_Translation_Factory::POST ) { $index = self::SECTION_INDEX_POST; if ( WPML_Slug_Translation_Factory::TAX === $element_type ) { $index = self::SECTION_INDEX_TAX; } $page = $this->wp_api->constant( 'WPML_PLUGIN_FOLDER' ) . '/menu/translation-options.php#ml-content-setup-sec-' . $index; if ( $this->wp_api->defined( 'WPML_TM_VERSION' ) ) { $page = $this->wp_api->constant( 'WPML_TM_FOLDER' ) . '/menu/settings&sm=mcsetup#ml-content-setup-sec-' . $index; } return admin_url( 'admin.php?page=' . $page ); } /** * @param bool $is_translated * @param string $type * @param string $element_type WPML_Slug_Translation_Factory::POST or WPML_Slug_Translation_Factory::TAX * * @return bool */ public function type_slug_is_translated_filter( $is_translated, $type, $element_type = WPML_Slug_Translation_Factory::POST ) { return $this->settings_factory->create( $element_type )->is_translated( $type ); } } slug-translation/wpml-st-element-slug-translation-ui.php 0000755 00000002431 14720402445 0017541 0 ustar 00 <?php class WPML_ST_Element_Slug_Translation_UI { const TEMPLATE_FILE = 'slug-translation-ui.twig'; /** @var WPML_ST_Element_Slug_Translation_UI_Model $model */ private $model; /** @var IWPML_Template_Service $template_service */ private $template_service; public function __construct( WPML_ST_Element_Slug_Translation_UI_Model $model, IWPML_Template_Service $template_service ) { $this->model = $model; $this->template_service = $template_service; } /** @return WPML_ST_Element_Slug_Translation_UI */ public function init() { wp_enqueue_script( 'wpml-custom-type-slug-ui', WPML_ST_URL . '/res/js/wpml-custom-type-slug-ui.js', array( 'jquery' ), WPML_ST_VERSION, true ); return $this; } /** * @param string $type_name * @param WP_Post_Type|WP_Taxonomy $custom_type * * @return string */ public function render( $type_name, $custom_type ) { $model = $this->model->get( $type_name, $custom_type ); if ( ! $model ) { return ''; } if ( ! empty( $model['has_missing_translations_message'] ) ) { ICL_AdminNotifier::displayInstantMessage( $model['has_missing_translations_message'], 'error', true, false ); } return $this->template_service->show( $model, self::TEMPLATE_FILE ); } } slug-translation/iwpml-st-rewrite-rule-filter.php 0000755 00000000145 14720402445 0016253 0 ustar 00 <?php interface IWPML_ST_Rewrite_Rule_Filter { public function rewrite_rules_filter( $rules ); } slug-translation/wpml-st-slug-translation-ui-factory.php 0000755 00000002334 14720402445 0017561 0 ustar 00 <?php class WPML_ST_Slug_Translation_UI_Factory { const POST = 'post'; const TAX = 'taxonomy'; const TEMPLATE_PATH = 'templates/slug-translation'; public function create( $type ) { global $sitepress; $sync_settings_factory = new WPML_Element_Sync_Settings_Factory(); $records_factory = new WPML_Slug_Translation_Records_Factory(); if ( WPML_Slug_Translation_Factory::POST === $type ) { $settings = new WPML_ST_Post_Slug_Translation_Settings( $sitepress ); } elseif ( WPML_Slug_Translation_Factory::TAX === $type ) { $settings = new WPML_ST_Tax_Slug_Translation_Settings(); } else { throw new Exception( 'Unknown element type.' ); } $records = $records_factory->create( $type ); $sync = $sync_settings_factory->create( $type ); $template_loader = new WPML_Twig_Template_Loader( array( WPML_ST_PATH . '/' . self::TEMPLATE_PATH ) ); $template_service = $template_loader->get_template(); $lang_selector = new WPML_Simple_Language_Selector( $sitepress ); $model = new WPML_ST_Element_Slug_Translation_UI_Model( $sitepress, $settings, $records, $sync, $lang_selector ); return new WPML_ST_Element_Slug_Translation_UI( $model, $template_service ); } } slug-translation/RewriteRules/Hooks.php 0000755 00000003275 14720402445 0014316 0 ustar 00 <?php namespace WPML\ST\SlugTranslation\Hooks; class Hooks { /** @var \WPML_Rewrite_Rule_Filter_Factory */ private $factory; /** @var \WPML_ST_Slug_Translation_Settings $slug_translation_settings */ private $slug_translation_settings; /** @var array|null */ private $cache; /** * @param \WPML_Rewrite_Rule_Filter_Factory $factory * @param \WPML_ST_Slug_Translation_Settings $slug_translation_settings */ public function __construct( \WPML_Rewrite_Rule_Filter_Factory $factory, \WPML_ST_Slug_Translation_Settings $slug_translation_settings ) { $this->factory = $factory; $this->slug_translation_settings = $slug_translation_settings; } public function add_hooks() { add_action( 'init', [ $this, 'init' ], \WPML_Slug_Translation_Factory::INIT_PRIORITY ); } public function init() { if ( $this->slug_translation_settings->is_enabled() ) { add_filter( 'option_rewrite_rules', [ $this, 'filter' ], 1, 1 ); add_filter( 'flush_rewrite_rules_hard', [ $this, 'flushRewriteRulesHard' ] ); add_action( 'registered_post_type', [ $this, 'clearCache' ] ); add_action( 'registered_taxonomy', [ $this, 'clearCache' ] ); } } /** * @param array $value * * @return array */ public function filter( $value ) { if ( empty( $value ) || apply_filters( 'wpml_st_disable_rewrite_rules', false ) ) { return $value; } if ( ! $this->cache ) { $this->cache = $this->factory->create()->rewrite_rules_filter( $value ); } return $this->cache; } public function clearCache() { $this->cache = null; } /** * @param bool $hard * * @return mixed */ public function flushRewriteRulesHard( $hard ) { $this->clearCache(); return $hard; } } slug-translation/RewriteRules/HooksFactory.php 0000755 00000001307 14720402445 0015640 0 ustar 00 <?php namespace WPML\ST\SlugTranslation\Hooks; use WPML_Rewrite_Rule_Filter_Factory; use WPML_ST_Slug_Translation_Settings_Factory; class HooksFactory { /** * We need a static property because there could be many instances of HooksFactory class but we need to guarantee * there is only a single instance of Hooks * * @var Hooks */ private static $instance; /** * @return Hooks */ public function create() { if ( ! self::$instance ) { $settings_factory = new WPML_ST_Slug_Translation_Settings_Factory(); $global_settings = $settings_factory->create(); self::$instance = new Hooks( new WPML_Rewrite_Rule_Filter_Factory(), $global_settings ); } return self::$instance; } } slug-translation/wpml-st-slug-translation-settings-factory.php 0000755 00000001614 14720402445 0021004 0 ustar 00 <?php class WPML_ST_Slug_Translation_Settings_Factory { /** * @throws InvalidArgumentException * @param string $element_type * * @return WPML_ST_Slug_Translation_Settings|WPML_ST_Tax_Slug_Translation_Settings|WPML_ST_Post_Slug_Translation_Settings */ public function create( $element_type = null ) { global $sitepress; if ( WPML_Slug_Translation_Factory::POST === $element_type ) { return new WPML_ST_Post_Slug_Translation_Settings( $sitepress ); } if ( WPML_Slug_Translation_Factory::TAX === $element_type ) { return new WPML_ST_Tax_Slug_Translation_Settings(); } if ( ! $element_type ) { return new WPML_ST_Slug_Translation_Settings(); } throw new InvalidArgumentException( 'Invalid element type.' ); } /** * @return WPML_ST_Tax_Slug_Translation_Settings */ public function createTaxSettings() { return new WPML_ST_Tax_Slug_Translation_Settings(); } } translation-files/Sync/TranslationUpdates.php 0000755 00000002667 14720402445 0015473 0 ustar 00 <?php namespace WPML\ST\TranslationFile\Sync; use WPML\Collect\Support\Collection; class TranslationUpdates { // The global constant is not defined yet. const ICL_STRING_TRANSLATION_COMPLETE = 10; /** @var \wpdb */ private $wpdb; /** @var \WPML_Language_Records */ private $languageRecords; /** @var null|Collection */ private $data; public function __construct( \wpdb $wpdb, \WPML_Language_Records $languageRecords ) { $this->wpdb = $wpdb; $this->languageRecords = $languageRecords; } /** * @param string $domain * @param string $locale * * @return int */ public function getTimestamp( $domain, $locale ) { $this->loadData(); $lang = $this->languageRecords->get_language_code( $locale ); return (int) $this->data->get( "$lang#$domain" ); } private function loadData() { if ( ! $this->data ) { $sql = " SELECT CONCAT(st.language,'#',s.context) AS lang_domain, UNIX_TIMESTAMP(MAX(st.translation_date)) as last_update FROM {$this->wpdb->prefix}icl_string_translations AS st INNER JOIN {$this->wpdb->prefix}icl_strings AS s ON st.string_id = s.id WHERE st.value IS NOT NULL AND st.status = %d GROUP BY s.context, st.language; "; $this->data = wpml_collect( $this->wpdb->get_results( $this->wpdb->prepare( $sql, self::ICL_STRING_TRANSLATION_COMPLETE ) ) )->pluck( 'last_update', 'lang_domain' ); } } public function reset() { $this->data = null; } } translation-files/Sync/FileSync.php 0000755 00000004037 14720402445 0013354 0 ustar 00 <?php namespace WPML\ST\TranslationFile\Sync; use WPML\ST\TranslationFile\Manager; use WPML_ST_Translations_File_Locale; class FileSync { /** @var Manager */ private $manager; /** @var TranslationUpdates */ private $translationUpdates; /** @var WPML_ST_Translations_File_Locale */ private $fileLocale; public function __construct( Manager $manager, TranslationUpdates $translationUpdates, WPML_ST_Translations_File_Locale $FileLocale ) { $this->manager = $manager; $this->translationUpdates = $translationUpdates; $this->fileLocale = $FileLocale; } /** * Before to load the custom translation file, we'll: * - Re-generate it if it's missing or outdated. * - Delete it if we don't have custom translations. * * We will also sync the custom file when a native file is passed * because the custom file might never be loaded if it's missing. * * @param string|false $filePath * @param string $domain */ public function sync( $filePath, $domain ) { if ( ! $filePath ) { return; } $locale = $this->fileLocale->get( $filePath, $domain ); $filePath = $this->getCustomFilePath( $filePath, $domain, $locale ); $lastDbUpdate = $this->translationUpdates->getTimestamp( $domain, $locale ); $fileTimestamp = file_exists( $filePath ) ? filemtime( $filePath ) : 0; if ( 0 === $lastDbUpdate ) { if ( $fileTimestamp ) { $this->manager->remove( $domain, $locale ); } } elseif ( $fileTimestamp < $lastDbUpdate ) { $this->manager->add( $domain, $locale ); } } /** * @param string $filePath * @param string $domain * @param string $locale * * @return string|null */ private function getCustomFilePath( $filePath, $domain, $locale ) { if ( self::isWpmlCustomFile( $filePath ) ) { return $filePath; } return $this->manager->getFilepath( $domain, $locale ); } /** * @param string $file * * @return bool */ private static function isWpmlCustomFile( $file ) { return 0 === strpos( $file, WP_LANG_DIR . '/' . Manager::SUB_DIRECTORY ); } } translation-files/DomainsLocalesMapper.php 0000755 00000004463 14720402445 0014771 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use wpdb; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Lst; use WPML\FP\Obj; use WPML_Locale; class DomainsLocalesMapper { const ALIAS_STRINGS = 's'; const ALIAS_STRING_TRANSLATIONS = 'st'; /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_Locale $locale */ private $locale; public function __construct( wpdb $wpdb, WPML_Locale $locale ) { $this->wpdb = $wpdb; $this->locale = $locale; } /** * @param array $string_translation_ids * * @return Collection of objects with properties `domain` and `locale` */ public function get_from_translation_ids( array $string_translation_ids ) { return $this->get_results_where( self::ALIAS_STRING_TRANSLATIONS, $string_translation_ids ); } /** * @param array $string_ids * * @return Collection of objects with properties `domain` and `locale` */ public function get_from_string_ids( array $string_ids ) { return $this->get_results_where( self::ALIAS_STRINGS, $string_ids ); } /** * @param callable $getActiveLanguages * @param string $domain * * @return array */ public function get_from_domain( callable $getActiveLanguages, $domain ) { $createEntity = function ( $locale ) use ( $domain ) { return (object) [ 'domain' => $domain, 'locale' => $locale, ]; }; /** @var array $defaultLocaleList */ $defaultLocaleList = Lst::pluck( 'default_locale', $getActiveLanguages() ); return Fns::map( $createEntity, Obj::values( $defaultLocaleList ) ); } /** * @param string $table_alias * @param array $ids * * @return Collection */ private function get_results_where( $table_alias, array $ids ) { $results = []; if ( array_filter( $ids ) ) { $results = $this->wpdb->get_results( " SELECT DISTINCT s.context AS domain, st.language FROM {$this->wpdb->prefix}icl_string_translations AS " . self::ALIAS_STRING_TRANSLATIONS . " JOIN {$this->wpdb->prefix}icl_strings AS " . self::ALIAS_STRINGS . " ON s.id = st.string_id WHERE $table_alias.id IN(" . wpml_prepare_in( $ids ) . ') ' ); } return wpml_collect( $results )->map( function( $row ) { return (object) [ 'domain' => $row->domain, 'locale' => $this->locale->get_locale( $row->language ), ]; } ); } } translation-files/Hooks.php 0000755 00000002341 14720402445 0012003 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use WPML\ST\MO\Hooks\Factory; use WPML\ST\MO\Plural; use WPML_Action_Filter_Loader; use WPML_Package_Translation_Schema; use WPML_ST_Upgrade; use WPML_ST_Upgrade_MO_Scanning; class Hooks { /** @var WPML_Action_Filter_Loader $action_loader */ private $action_loader; /** @var WPML_ST_Upgrade $upgrade */ private $upgrade; public function __construct( WPML_Action_Filter_Loader $action_loader, WPML_ST_Upgrade $upgrade ) { $this->action_loader = $action_loader; $this->upgrade = $upgrade; } public function install() { if ( $this->hasPackagesTable() && $this->hasTranslationFilesTables() ) { $this->action_loader->load( [ Factory::class, Plural::class ] ); } } private function hasPackagesTable() { $updates_run = get_option( WPML_Package_Translation_Schema::OPTION_NAME, [] ); return in_array( WPML_Package_Translation_Schema::REQUIRED_VERSION, $updates_run, true ); } private function hasTranslationFilesTables() { return $this->upgrade->has_command_been_executed( WPML_ST_Upgrade_MO_Scanning::class ); } public static function useFileSynchronization() { return defined( 'WPML_ST_SYNC_TRANSLATION_FILES' ) && constant( 'WPML_ST_SYNC_TRANSLATION_FILES' ); } } translation-files/UpdateHooks.php 0000755 00000013142 14720402445 0013147 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use stdClass; use WPML\Collect\Support\Collection; use WPML\Element\API\Languages; class UpdateHooks implements \IWPML_Action { /** @var Manager $file_manager */ private $file_manager; /** @var DomainsLocalesMapper $domains_locales_mapper */ private $domains_locales_mapper; /** @var array $updated_translation_ids */ private $updated_translation_ids = []; /** @var Collection $entities_to_update */ private $entities_to_update; /** @var callable */ private $resetDomainsCache; public function __construct( Manager $file_manager, DomainsLocalesMapper $domains_locales_mapper, callable $resetDomainsCache = null ) { $this->file_manager = $file_manager; $this->domains_locales_mapper = $domains_locales_mapper; $this->entities_to_update = wpml_collect( [] ); $this->resetDomainsCache = $resetDomainsCache ?: [ Domains::class, 'resetCache' ]; } public function add_hooks() { add_action( 'wpml_st_strings_table_altered', [ Domains::class, 'invalidateMODomainCache' ] ); add_action( 'wpml_st_string_registered', [ Domains::class, 'invalidateMODomainCache' ] ); add_action( 'wpml_st_string_unregistered', [ Domains::class, 'invalidateMODomainCache' ] ); add_action( 'wpml_st_string_updated', [ Domains::class, 'invalidateMODomainCache' ] ); add_action( 'wpml_st_add_string_translation', array( $this, 'add_to_update_queue' ) ); add_action( 'wpml_st_update_string', array( $this, 'refresh_after_update_original_string' ), 10, 6 ); add_action( 'wpml_st_before_remove_strings', array( $this, 'refresh_before_remove_strings' ) ); add_action( 'wpml_st_translation_files_process_queue', [ $this, 'process_update_queue_action' ] ); /** * @see UpdateHooks::refresh_domain * @since @3.1.0 */ add_action( 'wpml_st_refresh_domain', [ $this, 'refresh_domain' ] ); if ( ! $this->file_manager->isPartialFile() ) { add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_imported_file' ) ); } } /** @param int $string_translation_id */ public function add_to_update_queue( $string_translation_id ) { if ( ! in_array( $string_translation_id, $this->updated_translation_ids, true ) ) { $this->updated_translation_ids[] = $string_translation_id; $this->add_shutdown_action(); } } private function add_shutdown_action() { if ( ! has_action( 'shutdown', array( $this, 'process_update_queue_action' ) ) ) { add_action( 'shutdown', array( $this, 'process_update_queue_action' ) ); } } public function process_update_queue_action() { remove_action( 'shutdown', array( $this, 'process_update_queue_action' ) ); $this->process_update_queue(); } /** * @return array */ public function process_update_queue() { call_user_func( $this->resetDomainsCache ); $outdated_entities = $this->domains_locales_mapper->get_from_translation_ids( $this->updated_translation_ids ); $this->entities_to_update = $this->entities_to_update->merge( $outdated_entities ); $this->entities_to_update->each( function ( $entity ) { $updatedFilename = $this->update_file( $entity->domain, $entity->locale ); do_action( 'wpml_st_translation_file_updated', $updatedFilename, $entity->domain, $entity->locale ); } ); return $this->entities_to_update->toArray(); } /** * @param string $domain * @param string $name * @param string $old_value * @param string $new_value * @param bool|false $force_complete * @param stdClass $string */ public function refresh_after_update_original_string( $domain, $name, $old_value, $new_value, $force_complete, $string ) { $outdated_entities = $this->domains_locales_mapper->get_from_string_ids( [ $string->id ] ); $this->entities_to_update = $this->entities_to_update->merge( $outdated_entities ); $this->add_shutdown_action(); } public function update_imported_file( \WPML_ST_Translations_File_Entry $file_entry ) { if ( $file_entry->get_status() === \WPML_ST_Translations_File_Entry::IMPORTED ) { $updatedFilename = $this->update_file( $file_entry->get_domain(), $file_entry->get_file_locale() ); if ( $updatedFilename ) { do_action( 'wpml_st_translation_file_updated', $updatedFilename, $file_entry->get_domain(), $file_entry->get_file_locale() ); } } } /** * It dispatches the regeneration of MO files for a specific domain in all active languages. * * @param string $domain */ public function refresh_domain( $domain ) { $outdated_entities = $this->domains_locales_mapper->get_from_domain( [ Languages::class, 'getActive' ], $domain ); $this->entities_to_update = $this->entities_to_update->merge( $outdated_entities ); $this->add_shutdown_action(); } /** * We need to refresh before the strings are deleted, * otherwise we can't determine which domains to refresh. * * @param array $string_ids */ public function refresh_before_remove_strings( array $string_ids ) { $outdated_entities = $this->domains_locales_mapper->get_from_string_ids( $string_ids ); $this->entities_to_update = $this->entities_to_update->merge( $outdated_entities ); $this->add_shutdown_action(); } /** * @param string $domain * @param string $locale * * @return false|string */ private function update_file( $domain, $locale ) { /** * It does not matter whether MO/JED file can be handled or not, that's why `remove` method is placed before `handles` check. * If a file is not `handable` then it can still should be removed. */ $this->file_manager->remove( $domain, $locale ); if ( $this->file_manager->handles( $domain ) ) { return $this->file_manager->add( $domain, $locale ); } return false; } } translation-files/StringEntity.php 0000755 00000002621 14720402445 0013364 0 ustar 00 <?php namespace WPML\ST\TranslationFile; class StringEntity { /** @var string $original */ private $original; /** @var array $translations */ private $translations = array(); /** @var null|string $context */ private $context; /** @var string|null */ private $original_plural; /** @var string|null */ private $name; /** * @param string $original * @param array $translations * @param null|string $context * @param null|string $original_plural * @param null|string $name */ public function __construct( $original, array $translations, $context = null, $original_plural = null, $name = null ) { $this->original = $original; $this->translations = $translations; $this->context = $context ? $context : null; $this->original_plural = $original_plural; $this->name = $name; } /** @return string */ public function get_original() { return $this->original; } /** @return array */ public function get_translations() { return $this->translations; } /** @return null|string */ public function get_context() { return $this->context; } /** * @return string|null */ public function get_original_plural() { return $this->original_plural; } /** * @return string|null */ public function get_name() { return $this->name; } /** * @param string $name */ public function set_name( $name ) { $this->name = $name; } } translation-files/Manager.php 0000755 00000007121 14720402445 0012273 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use WP_Filesystem_Direct; use WPML\Collect\Support\Collection; use WPML\ST\MO\File\makeDir; use WPML_Language_Records; use WPML_ST_Translations_File_Dictionary; use function wpml_collect; use WPML_ST_Translations_File_Entry; abstract class Manager { use makeDir; const SUB_DIRECTORY = 'wpml'; /** @var StringsRetrieve $strings */ protected $strings; /** @var WPML_Language_Records $language_records */ protected $language_records; /** @var Builder $builder */ protected $builder; /** @var WPML_ST_Translations_File_Dictionary $file_dictionary */ protected $file_dictionary; /** @var Domains $domains */ protected $domains; public function __construct( StringsRetrieve $strings, Builder $builder, WP_Filesystem_Direct $filesystem, WPML_Language_Records $language_records, Domains $domains ) { $this->strings = $strings; $this->builder = $builder; $this->filesystem = $filesystem; $this->language_records = $language_records; $this->domains = $domains; } /** * @param string $domain * @param string $locale */ public function remove( $domain, $locale ) { $filepath = $this->getFilepath( $domain, $locale ); $this->filesystem->delete( $filepath ); do_action( 'wpml_st_translation_file_removed', $filepath, $domain, $locale ); } public function write( $domain, $locale, $content ) { $filepath = $this->getFilepath( $domain, $locale ); $chmod = defined( 'FS_CHMOD_FILE' ) ? FS_CHMOD_FILE : 0644; if ( ! $this->filesystem->put_contents( $filepath, $content, $chmod ) ) { return false; } do_action( 'wpml_st_translation_file_written', $filepath, $domain, $locale ); return $filepath; } /** * Builds and saves the .MO file. * Returns false if file doesn't exist, file path otherwise. * * @param string $domain * @param string $locale * * @return false|string */ public function add( $domain, $locale ) { if ( ! $this->maybeCreateSubdir() ) { return false; } $lang_code = $this->language_records->get_language_code( $locale ); $strings = $this->strings->get( $domain, $lang_code, $this->isPartialFile() ); if ( ! $strings && $this->isPartialFile() ) { $this->remove( $domain, $locale ); return false; } $file_content = $this->builder ->set_language( $locale ) ->get_content( $strings ); return $this->write( $domain, $locale, $file_content ); } /** * @param string $domain * @param string $locale * * @return string|null */ public function get( $domain, $locale ) { $filepath = $this->getFilepath( $domain, $locale ); if ( $this->filesystem->is_file( $filepath ) && $this->filesystem->is_readable( $filepath ) ) { return $filepath; } return null; } /** * @param string $domain * @param string $locale * * @return string */ public function getFilepath( $domain, $locale ) { return $this->getSubdir() . '/' . strtolower( $domain ) . '-' . $locale . '.' . $this->getFileExtension(); } /** * @param string $domain * * @return bool */ public function handles( $domain ) { return $this->getDomains()->contains( $domain ); } /** @return string */ public static function getSubdir() { $subdir = WP_LANG_DIR . '/' . self::SUB_DIRECTORY; $site_id = get_current_blog_id(); if ( $site_id > 1 ) { $subdir .= '/' . $site_id; } return $subdir; } /** * @return string */ abstract protected function getFileExtension(); /** * @return bool */ abstract public function isPartialFile(); /** * @return Collection */ abstract protected function getDomains(); } translation-files/UpdateHooksFactory.php 0000755 00000000562 14720402445 0014501 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use WPML\ST\MO\File\ManagerFactory; use function WPML\Container\make; class UpdateHooksFactory { /** @return UpdateHooks */ public static function create() { static $instance; if ( ! $instance ) { $instance = make( UpdateHooks::class, [ ':file_manager' => ManagerFactory::create() ] ); } return $instance; } } translation-files/jed/wpml-st-script-translations-hooks.php 0000755 00000003714 14720402445 0020234 0 ustar 00 <?php class WPML_ST_Script_Translations_Hooks implements IWPML_Action { const PRIORITY_OVERRIDE_JED_FILE = 10; /** @var WPML_ST_Translations_File_Dictionary $dictionary */ private $dictionary; /** @var WPML_ST_JED_File_Manager $jed_file_manager */ private $jed_file_manager; /** @var WPML_File $wpml_file */ private $wpml_file; public function __construct( WPML_ST_Translations_File_Dictionary $dictionary, WPML_ST_JED_File_Manager $jed_file_manager, WPML_File $wpml_file ) { $this->dictionary = $dictionary; $this->jed_file_manager = $jed_file_manager; $this->wpml_file = $wpml_file; } public function add_hooks() { add_filter( 'load_script_translation_file', array( $this, 'override_jed_file' ), self::PRIORITY_OVERRIDE_JED_FILE, 3 ); } /** * @param string $filepath * @param string $handler * @param string $domain * * @return string */ public function override_jed_file( $filepath, $handler, $domain ) { if ( ! $filepath ) { return $filepath; } $locale = $this->get_file_locale( $filepath, $domain ); $domain = WPML_ST_JED_Domain::get( $domain, $handler ); $wpml_filepath = $this->jed_file_manager->get( $domain, $locale ); if ( $wpml_filepath ) { return $wpml_filepath; } return $filepath; } /** * @param string $filepath * * @return bool */ private function is_file_imported( $filepath ) { $relative_path = $this->wpml_file->get_relative_path( $filepath ); $file = $this->dictionary->find_file_info_by_path( $relative_path ); $statuses = array( WPML_ST_Translations_File_Entry::IMPORTED, WPML_ST_Translations_File_Entry::FINISHED ); return $file && in_array( $file->get_status(), $statuses, true ); } /** * @param string $filepath * @param string $domain * * @return string */ private function get_file_locale( $filepath, $domain ) { return \WPML\Container\make( \WPML_ST_Translations_File_Locale::class )->get( $filepath, $domain ); } } translation-files/jed/wpml-st-jed-domain.php 0000755 00000000176 14720402445 0015076 0 ustar 00 <?php class WPML_ST_JED_Domain { public static function get( $domain, $handler ) { return $domain . '-' . $handler; } } translation-files/jed/wpml-st-jed-file-builder.php 0000755 00000003072 14720402445 0016170 0 ustar 00 <?php use WPML\ST\TranslationFile\StringEntity; class WPML_ST_JED_File_Builder extends WPML\ST\TranslationFile\Builder { /** @var string $decoded_eot */ private $decoded_eot; public function __construct() { $this->decoded_eot = json_decode( WPML_ST_Translations_File_JED::DECODED_EOT_CHAR ); } /** * @param StringEntity[] $strings * @return string */ public function get_content( array $strings ) { $data = new stdClass(); $data->{'translation-revision-date'} = date( 'Y-m-d H:i:sO' ); $data->generator = 'WPML String Translation ' . WPML_ST_VERSION; $data->domain = 'messages'; $data->locale_data = new stdClass(); $data->locale_data->messages = new stdClass(); $data->locale_data->messages->{WPML_ST_Translations_File_JED::EMPTY_PROPERTY_NAME} = (object) array( 'domain' => 'messages', 'plural-forms' => $this->plural_form, 'lang' => $this->language, ); foreach ( $strings as $string ) { $original = $this->get_original_with_context( $string ); $data->locale_data->messages->{$original} = $string->get_translations(); } $jed_content = (string) wp_json_encode( $data ); return preg_replace( '/"' . WPML_ST_Translations_File_JED::EMPTY_PROPERTY_NAME . '"/', '""', $jed_content, 1 ); } private function get_original_with_context( StringEntity $string ) { if ( $string->get_context() ) { return $string->get_context() . $this->decoded_eot . $string->get_original(); } return $string->get_original(); } } translation-files/jed/Hooks/Sync.php 0000755 00000002036 14720402445 0013462 0 ustar 00 <?php namespace WPML\ST\JED\Hooks; use WPML\ST\TranslationFile\Sync\FileSync; use WPML_ST_JED_Domain; use WPML_ST_JED_File_Manager; use WPML_ST_Script_Translations_Hooks; use WPML_ST_Translations_File_Locale; class Sync implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_DIC_Action { /** @var FileSync */ private $fileSync; public function __construct( FileSync $fileSync ) { $this->fileSync = $fileSync; } public function add_hooks() { add_filter( 'load_script_translation_file', [ $this, 'syncCustomJedFile' ], WPML_ST_Script_Translations_Hooks::PRIORITY_OVERRIDE_JED_FILE - 1, 3 ); } /** * @param string|false $jedFile Path to the translation file to load. False if there isn't one. * @param string $handler Name of the script to register a translation domain to. * @param string $domain The text domain. */ public function syncCustomJedFile( $jedFile, $handler, $domain ) { $this->fileSync->sync( $jedFile, WPML_ST_JED_Domain::get( $domain, $handler ) ); return $jedFile; } } translation-files/jed/wpml-st-jed-file-manager.php 0000755 00000000644 14720402445 0016156 0 ustar 00 <?php use WPML\Collect\Support\Collection; use WPML\ST\TranslationFile\Manager; class WPML_ST_JED_File_Manager extends Manager { /** * @return string */ protected function getFileExtension() { return 'json'; } /** * @return bool */ public function isPartialFile() { return false; } /** * @return Collection */ protected function getDomains() { return $this->domains->getJEDDomains(); } } translation-files/jed/wpml-st-script-translations-hooks-factory.php 0000755 00000003065 14720402445 0021700 0 ustar 00 <?php use WPML\ST\JED\Hooks\Sync; use WPML\ST\TranslationFile\Sync\FileSync; use function WPML\Container\make; use WPML\ST\TranslationFile\UpdateHooks; class WPML_ST_Script_Translations_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader { /** * Create hooks. * * @return array|IWPML_Action * @throws \WPML\Auryn\InjectionException Auryn Exception. */ public function create() { $hooks = array(); $jed_file_manager = make( WPML_ST_JED_File_Manager::class, [ ':builder' => make( WPML_ST_JED_File_Builder::class ) ] ); $hooks['update'] = $this->get_update_hooks( $jed_file_manager ); if ( ! wpml_is_ajax() && ! wpml_is_rest_request() ) { $hooks['filtering'] = $this->get_filtering_hooks( $jed_file_manager ); } if ( WPML\ST\TranslationFile\Hooks::useFileSynchronization() ) { $hooks['sync'] = make( Sync::class, [ ':fileSync' => make( FileSync::class, [ ':manager' => $jed_file_manager ] ), ':manager' => $jed_file_manager, ] ); } return $hooks; } /** * @param WPML_ST_JED_File_Manager $jed_file_manager * * @return UpdateHooks */ private function get_update_hooks( $jed_file_manager ) { return make( UpdateHooks::class, [ ':file_manager' => $jed_file_manager ] ); } /** * @param WPML_ST_JED_File_Manager $jed_file_manager * * @return WPML_ST_Script_Translations_Hooks */ private function get_filtering_hooks( $jed_file_manager ) { return make( WPML_ST_Script_Translations_Hooks::class, [ ':jed_file_manager' => $jed_file_manager ] ); } } translation-files/Domains.php 0000755 00000010726 14720402445 0012320 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use wpdb; use WPML\Collect\Support\Collection; use WPML\FP\Fns; use WPML\FP\Just; use WPML\FP\Maybe; use WPML\FP\Nothing; use WPML\LIB\WP\Cache; use WPML\ST\Package\Domains as PackageDomains; use WPML_Admin_Texts; use WPML_ST_Blog_Name_And_Description_Hooks; use WPML_ST_Translations_File_Dictionary; use WPML\ST\Shortcode; class Domains { const MO_DOMAINS_CACHE_GROUP = 'WPML_ST_CACHE'; const MO_DOMAINS_CACHE_KEY = 'wpml_string_translation_has_mo_domains'; /** @var wpdb $wpdb */ private $wpdb; /** @var PackageDomains $package_domains */ private $package_domains; /** @var WPML_ST_Translations_File_Dictionary $file_dictionary */ private $file_dictionary; /** @var null|Collection $jed_domains */ private static $jed_domains; /** * Domains constructor. * * @param PackageDomains $package_domains * @param WPML_ST_Translations_File_Dictionary $file_dictionary */ public function __construct( wpdb $wpdb, PackageDomains $package_domains, WPML_ST_Translations_File_Dictionary $file_dictionary ) { $this->wpdb = $wpdb; $this->package_domains = $package_domains; $this->file_dictionary = $file_dictionary; } /** * @return Collection */ public function getMODomains() { $getMODomainsFromDB = function () { $cacheLifeTime = HOUR_IN_SECONDS; $excluded_domains = self::getReservedDomains()->merge( $this->getJEDDomains() ); $sql = " SELECT DISTINCT context {$this->getCollateForContextColumn()} FROM {$this->wpdb->prefix}icl_strings "; $mo_domains = wpml_collect( $this->wpdb->get_col( $sql ) ) ->diff( $excluded_domains ) ->values(); if ( $mo_domains->count() <= 0 ) { // if we don't get any data from DB, we set cache expire time to be 15 minutes so that cache refreshes in lesser time. $cacheLifeTime = 15 * MINUTE_IN_SECONDS; } Cache::set( self::MO_DOMAINS_CACHE_GROUP, self::MO_DOMAINS_CACHE_KEY, $cacheLifeTime, $mo_domains ); return $mo_domains; }; /** @var Just|Nothing $cacheItem */ $cacheItem = Cache::get( self::MO_DOMAINS_CACHE_GROUP, self::MO_DOMAINS_CACHE_KEY ); return $cacheItem->getOrElse( $getMODomainsFromDB ); } public static function invalidateMODomainCache() { static $invalidationScheduled = false; if ( ! $invalidationScheduled ) { $invalidationScheduled = true; add_action( 'shutdown', function () { Cache::flushGroup( self::MO_DOMAINS_CACHE_GROUP ); } ); } } /** * @return string */ private function getCollateForContextColumn() { $sql = " SELECT COLLATION_NAME FROM information_schema.columns WHERE TABLE_SCHEMA = '" . DB_NAME . "' AND TABLE_NAME = '{$this->wpdb->prefix}icl_strings' AND COLUMN_NAME = 'context' "; $collation = $this->wpdb->get_var( $sql ); if ( ! $collation ) { return ''; } list( $type ) = explode( '_', $collation ); if ( in_array( $type, [ 'utf8', 'utf8mb4' ] ) ) { return 'COLLATE ' . $type . '_bin'; } return ''; } /** * Returns a collection of MO domains that * WPML needs to automatically load. * * @return Collection */ public function getCustomMODomains() { $all_mo_domains = $this->getMODomains(); $native_mo_domains = $this->file_dictionary->get_domains( 'mo', get_locale() ); return $all_mo_domains->reject( function ( $domain ) use ( $native_mo_domains ) { /** * Admin texts, packages, string shortcodes are handled separately, * so they are loaded on-demand. */ return null === $domain || 0 === strpos( $domain, WPML_Admin_Texts::DOMAIN_NAME_PREFIX ) || $this->package_domains->isPackage( $domain ) || Shortcode::STRING_DOMAIN === $domain || in_array( $domain, $native_mo_domains, true ); } )->values(); } /** * @return Collection */ public function getJEDDomains() { if ( ! self::$jed_domains instanceof Collection ) { self::$jed_domains = wpml_collect( $this->file_dictionary->get_domains( 'json' ) ); } return self::$jed_domains; } public static function resetCache() { self::invalidateMODomainCache(); self::$jed_domains = null; } /** * Domains that are not handled with MO files, * but have direct DB queries. * * @return Collection */ public static function getReservedDomains() { return wpml_collect( [ WPML_ST_Blog_Name_And_Description_Hooks::STRING_DOMAIN, ] ); } /** * @return Collection */ private function getPackageDomains() { return $this->package_domains->getDomains(); } } translation-files/StringsRetrieve.php 0000755 00000007732 14720402445 0014070 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use WPML\Collect\Support\Collection; use WPML\ST\TranslateWpmlString; class StringsRetrieve { // We need to store the strings by key that is a combination of original and gettext context // The join needs to be something that is unlikely to be in either so we can split later. const KEY_JOIN = '::JOIN::'; /** @var \WPML\ST\DB\Mappers\StringsRetrieve */ private $string_retrieve; /** * @param \WPML\ST\DB\Mappers\StringsRetrieve $string_retrieve */ public function __construct( \WPML\ST\DB\Mappers\StringsRetrieve $string_retrieve ) { $this->string_retrieve = $string_retrieve; } /** * @param string $domain * @param string $language * @param bool $modified_mo_only * * @return StringEntity[] */ public function get( $domain, $language, $modified_mo_only ) { return $this->loadFromDb( $language, $domain, $modified_mo_only ) ->filter( function ( $string ) { return (bool) $string['translation']; } ) ->mapToGroups( function ( array $string ) { return $this->groupPluralFormsOfSameString( $string ); } ) ->map( function ( Collection $strings, $key ) { return $this->buildStringEntity( $strings, $key ); } ) ->values() ->toArray(); } /** * @param string $language * @param string $domain * @param bool $modified_mo_only * * @return Collection */ private function loadFromDb( $language, $domain, $modified_mo_only = false ) { $result = \wpml_collect( $this->string_retrieve->get( $language, $domain, $modified_mo_only ) ); return $result->map( function ( $row ) { return $this->parseResult( $row ); } ); } /** * @param array $row_data * * @return array */ private function parseResult( array $row_data ) { return [ 'id' => $row_data['id'], 'original' => $row_data['original'], 'context' => $row_data['gettext_context'], 'translation' => self::parseTranslation( $row_data ), 'name' => $row_data['name'], ]; } /** * @param array $row_data * * @return string|null */ public static function parseTranslation( array $row_data ) { $value = null; $has_translation = ! empty( $row_data['translated'] ) && in_array( $row_data['status'], [ ICL_TM_COMPLETE, ICL_TM_NEEDS_UPDATE ] ); if ( $has_translation ) { $value = $row_data['translated']; } elseif ( ! empty( $row_data['mo_string'] ) ) { $value = $row_data['mo_string']; } return $value; } /** * @param array $string * * @return array */ private function groupPluralFormsOfSameString( array $string ) { $groupKey = $this->getPluralGroupKey( $string ); $pattern = '/^(.+) \[plural ([0-9]+)\]$/'; if ( preg_match( $pattern, $string['original'], $matches ) ) { $string['original'] = $matches[1]; $string['index'] = $matches[2]; } else { $string['index'] = null; } return [ $string['original'] . self::KEY_JOIN . $string['context'] . self::KEY_JOIN . $groupKey => $string, ]; } /** * Inside a domain, we can have several occurrences of strings * with the same original, but with different names. * In this situation, we should not try to group plurals. * * @param array $string * * @return mixed|string */ private function getPluralGroupKey( array $string ) { $cannotBelongToPluralGroup = TranslateWpmlString::canTranslateWithMO( $string['original'], $string['name'] ); if ( $cannotBelongToPluralGroup ) { return $string['name']; } return ''; } /** * @param Collection $strings * @param string $key * * @return StringEntity */ private function buildStringEntity( Collection $strings, $key ) { $translations = $strings->sortBy( 'index' )->pluck( 'translation' )->toArray(); list( $original, $context ) = explode( self::KEY_JOIN, $key ); $stringEntity = new StringEntity( $original, $translations, $context ); $stringEntity->set_name( $strings->first()['name'] ); return $stringEntity; } } translation-files/Builder.php 0000755 00000001232 14720402445 0012304 0 ustar 00 <?php namespace WPML\ST\TranslationFile; abstract class Builder { /** @var string $plural_form */ protected $plural_form = 'nplurals=2; plural=n != 1;'; /** @var string $language */ protected $language; /** * @param string $language * * @return Builder */ public function set_language( $language ) { $this->language = $language; return $this; } /** * @param string $plural_form * * @return Builder */ public function set_plural_form( $plural_form ) { $this->plural_form = $plural_form; return $this; } /** * @param StringEntity[] $strings * @return string */ abstract public function get_content( array $strings); } class-wpml-language-of-domain.php 0000755 00000001732 14720402445 0012777 0 ustar 00 <?php class WPML_Language_Of_Domain { /** * @var SitePress */ private $sitepress; /** * @var array */ private $language_of_domain = array(); /** * @param SitePress $sitepress */ public function __construct( SitePress $sitepress ) { $this->sitepress = $sitepress; $string_settings = $this->sitepress->get_setting( 'st' ); if ( isset( $string_settings['lang_of_domain'] ) ) { $this->language_of_domain = $string_settings['lang_of_domain']; } } public function get_language( $domain ) { $lang = null; if ( isset( $this->language_of_domain[ $domain ] ) ) { $lang = $this->language_of_domain[ $domain ]; } return $lang; } public function set_language( $domain, $lang ) { $this->language_of_domain[ $domain ] = $lang; $string_settings = $this->sitepress->get_setting( 'st' ); $string_settings['lang_of_domain'] = $this->language_of_domain; $this->sitepress->set_setting( 'st', $string_settings, true ); } } class-wpml-st-settings.php 0000755 00000002425 14720402445 0011631 0 ustar 00 <?php class WPML_ST_Settings { const SETTINGS_KEY = 'icl_st_settings'; /** * @var array */ private $settings = null; /** * @var array */ private $updated_settings = array(); /** * @return array */ public function get_settings() { if ( ! $this->settings ) { $options = get_option( self::SETTINGS_KEY ); $this->settings = is_array( $options ) ? $options : array(); } return array_merge( $this->settings, $this->updated_settings ); } /** * @param string $name * * @return mixed|null */ public function get_setting( $name ) { $this->get_settings(); return isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : null; } /** * @param string $key * @param mixed $value * @param bool $save */ public function update_setting( $key, $value, $save = false ) { $this->get_settings(); $this->updated_settings[ $key ] = $value; if ( $save ) { $this->save_settings(); } } public function delete_settings() { delete_option( self::SETTINGS_KEY ); } public function save_settings() { $settings = $this->get_settings(); update_option( self::SETTINGS_KEY, $settings ); do_action( 'icl_save_settings', $this->updated_settings ); $this->updated_settings = array(); $this->settings = $settings; } } basket/Status.php 0000755 00000002205 14720402445 0010015 0 ustar 00 <?php namespace WPML\ST\Basket; use WPML\FP\Obj; class Status { public static function add( array $translations, $languages ) { $statusProvider = [ 'TranslationProxy_Basket', 'is_in_basket' ]; if ( is_callable( $statusProvider ) ) { $translations = self::addWithProvider( $translations, $languages, $statusProvider ); } return $translations; } private static function addWithProvider( array $translations, $languages, callable $statusProvider ) { foreach ( $translations as $id => $string ) { foreach ( Obj::propOr( [], 'translations', $string ) as $lang => $data ) { $translations[ $id ]['translations'][ $lang ]['in_basket'] = $statusProvider( $id, $string['string_language'], $lang, 'string' ); } foreach ( $languages as $lang ) { if ( $lang !== $string['string_language'] && ! isset( $translations[ $id ]['translations'][ $lang ] ) && $statusProvider( $id, $string['string_language'], $lang, 'string' ) ) { $translations[ $id ]['translations'][ $lang ] = [ 'id' => 0, 'language' => $lang, 'in_basket' => true, ]; } } } return $translations; } } records/class-wpml-st-records.php 0000755 00000001372 14720402445 0013073 0 ustar 00 <?php class WPML_ST_Records { /** @var wpdb $wpdb */ public $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** @retur wpdb */ public function get_wpdb() { return $this->wpdb; } /** * @param int $string_id * * @return WPML_ST_ICL_Strings */ public function icl_strings_by_string_id( $string_id ) { return new WPML_ST_ICL_Strings( $this->wpdb, $string_id ); } /** * @param int $string_id * @param string $language_code * * @return WPML_ST_ICL_String_Translations */ public function icl_string_translations_by_string_id_and_language( $string_id, $language_code ) { return new WPML_ST_ICL_String_Translations( $this->wpdb, $string_id, $language_code ); } } records/class-wpml-st-icl-strings.php 0000755 00000002766 14720402445 0013700 0 ustar 00 <?php class WPML_ST_ICL_Strings extends WPML_WPDB_User { private $table = 'icl_strings'; private $string_id = 0; /** * WPML_TM_ICL_Strings constructor. * * @param wpdb $wpdb * @param int $string_id */ public function __construct( &$wpdb, $string_id ) { parent::__construct( $wpdb ); $string_id = (int) $string_id; if ( $string_id > 0 ) { $this->string_id = $string_id; } else { throw new InvalidArgumentException( 'Invalid String ID: ' . $string_id ); } } /** * @param array $args in the same format used by \wpdb::update() * * @return $this */ public function update( $args ) { $this->wpdb->update( $this->wpdb->prefix . $this->table, $args, array( 'id' => $this->string_id ) ); return $this; } /** * @return string */ public function value() { return $this->wpdb->get_var( $this->wpdb->prepare( " SELECT value FROM {$this->wpdb->prefix}{$this->table} WHERE id = %d LIMIT 1", $this->string_id ) ); } /** * @return string */ public function language() { return $this->wpdb->get_var( $this->wpdb->prepare( " SELECT language FROM {$this->wpdb->prefix}{$this->table} WHERE id = %d LIMIT 1", $this->string_id ) ); } /** * @return int */ public function status() { return (int) $this->wpdb->get_var( $this->wpdb->prepare( " SELECT status FROM {$this->wpdb->prefix}{$this->table} WHERE id = %d LIMIT 1", $this->string_id ) ); } } records/class-wpml-st-icl-string-translations.php 0000755 00000003042 14720402445 0016220 0 ustar 00 <?php class WPML_ST_ICL_String_Translations extends WPML_WPDB_User { private $table = 'icl_string_translations'; private $string_id = 0; private $lang_code; private $id; /** * WPML_ST_ICL_String_Translations constructor. * * @param wpdb $wpdb * @param int $string_id * @param string $lang_code */ public function __construct( &$wpdb, $string_id, $lang_code ) { parent::__construct( $wpdb ); $string_id = (int) $string_id; if ( $string_id > 0 && $lang_code ) { $this->string_id = $string_id; $this->lang_code = $lang_code; } else { throw new InvalidArgumentException( 'Invalid String ID: ' . $string_id . ' or language_code: ' . $lang_code ); } } /** * @return int|string */ public function translator_id() { return $this->wpdb->get_var( $this->wpdb->prepare( " SELECT translator_id FROM {$this->wpdb->prefix}{$this->table} WHERE id = %d LIMIT 1", $this->id() ) ); } /** * @return string */ public function value() { return $this->wpdb->get_var( $this->wpdb->prepare( " SELECT value FROM {$this->wpdb->prefix}{$this->table} WHERE id = %d LIMIT 1", $this->id() ) ); } /** * @return int */ public function id() { return (int) ( $this->id ? $this->id : $this->wpdb->get_var( $this->wpdb->prepare( " SELECT id FROM {$this->wpdb->prefix}{$this->table} WHERE string_id = %d AND language = %s LIMIT 1", $this->string_id, $this->lang_code ) ) ); } } class-wpml-admin-notifier.php 0000755 00000000416 14720402445 0012250 0 ustar 00 <?php class WPML_Admin_Notifier { public function display_instant_message( $message, $type = 'information', $class = false, $return = false, $fadeout = false ) { return ICL_AdminNotifier::display_instant_message( $message, $type, $class, $return, $fadeout ); } } Shortcode.php 0000755 00000004101 14720402445 0007210 0 ustar 00 <?php namespace WPML\ST; class Shortcode { const STRING_DOMAIN = 'wpml-shortcode'; private $context; private $name; /** * @var \wpdb */ private $wpdb; public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } function init_hooks() { add_shortcode( 'wpml-string', [ $this, 'render' ] ); } /** * @param array $attributes * @param string $value * * @return string */ function render( $attributes, $value ) { $this->parse_attributes( $attributes, $value ); $this->maybe_register_string( $value ); return do_shortcode( icl_t( $this->context, $this->name, $value ) ); } /** * @param string $value */ private function maybe_register_string( $value ) { $string = $this->get_registered_string(); if ( ! $string || $string->value !== $value ) { icl_register_string( $this->context, $this->name, $value ); } } /** * @param array $attributes * @param string $value */ private function parse_attributes( $attributes, $value ) { $pairs = array( 'context' => self::STRING_DOMAIN, 'name' => 'wpml-shortcode-' . md5( $value ), ); $attributes = shortcode_atts( $pairs, $attributes ); $this->context = $attributes['context']; $this->name = $attributes['name']; } /** * @return \stdClass */ private function get_registered_string() { $strings = $this->get_strings_registered_in_context(); if ( $strings && array_key_exists( $this->name, $strings ) ) { return $strings[ $this->name ]; } return null; } /** * @return \stdClass[] */ private function get_strings_registered_in_context() { $cache_key = $this->context; $cache_group = 'wpml-string-shortcode'; $cache_found = false; $string = wp_cache_get( $cache_key, $cache_group, false, $cache_found ); if ( ! $cache_found ) { $query = 'SELECT name, id, value, status FROM ' . $this->wpdb->prefix . 'icl_strings WHERE context=%s'; $sql = $this->wpdb->prepare( $query, $this->context ); $string = $this->wpdb->get_results( $sql, OBJECT_K ); wp_cache_set( $cache_key, $string, $cache_group ); } return $string; } } batch-translation/Hooks.php 0000755 00000004077 14720402445 0011772 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\LIB\WP\Hooks as WPHooks; use function WPML\FP\spreadArgs; class Hooks { public static function addHooks( callable $getBatchId, callable $setBatchRecord, callable $getBatchRecord, callable $getString ) { WPHooks::onFilter( 'wpml_tm_batch_factory_elements', 10, 2 ) ->then( spreadArgs( Convert::toBatchElements( $getBatchId, $setBatchRecord ) ) ); WPHooks::onFilter( 'wpml_tm_basket_items_types', 10, 1 ) ->then( spreadArgs( Obj::set( Obj::lensProp( 'st-batch' ), 'core' ) ) ); WPHooks::onFilter( 'wpml_is_external', 10, 2 ) ->then( spreadArgs( function ( $state, $type ) { return $state || ( is_object( $type ) && Obj::prop( 'post_type', $type ) === 'strings' ) || $type === 'st-batch'; } ) ); WPHooks::onFilter( 'wpml_get_translatable_item', 10, 3 ) ->then( spreadArgs( Strings::get( $getBatchRecord, $getString ) ) ); WPHooks::onAction( 'wpml_save_external', 10, 3 ) ->then( spreadArgs( StringTranslations::save() ) ); WPHooks::onFilter( 'wpml_tm_populate_prev_translation', 10, 3 ) ->then( spreadArgs( StringTranslations::addExisting() ) ); } public static function addStringTranslationStatusHooks( callable $updateTranslationStatus, callable $initializeTranslation ) { WPHooks::onAction( 'wpml_tm_added_translation_element', 10, 2 )->then( spreadArgs( $initializeTranslation ) ); WPHooks::onAction( 'wpml_tm_job_in_progress', 10, 2 )->then( spreadArgs( $updateTranslationStatus ) ); WPHooks::onAction( 'wpml_tm_job_cancelled', 10, 1 )->then( spreadArgs( StringTranslations::cancelTranslations() ) ); WPHooks::onAction( 'wpml_tm_jobs_cancelled', 10, 1 )->then( spreadArgs( function ( $jobs ) { /** * We need this check because if we pass only one job to the hook: * do_action( 'wpml_tm_jobs_cancelled', [ $job ] ) * then WordPress converts it to $job. */ if ( is_object( $jobs ) ) { $jobs = [ $jobs ]; } Fns::map( StringTranslations::cancelTranslations(), $jobs ); } ) ); } } batch-translation/Status.php 0000755 00000004661 14720402445 0012171 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\FP\Lst; use WPML\FP\Fns; use WPML\FP\Obj; class Status { public static function add( array $translations, $languages ) { global $wpdb; $batches = Records::findBatches( $wpdb, array_keys( $translations ) ); $statuses = self::getStatuses( $wpdb, $batches ); foreach ( $translations as $id => $string ) { foreach ( Obj::propOr( [], 'translations', $string ) as $lang => $data ) { $status = Obj::pathOr( null, [ $id, $lang ], $statuses ); if ( $status ) { $translations[ $id ]['translations'][ $lang ]['status'] = $status; } } } return $translations; } public static function getStatuses( \wpdb $wpdb, $batches ) { $batchIds = array_unique( array_values( $batches ) ); if ( $batchIds ) { $in = wpml_prepare_in( $batchIds, '%d' ); $trids = $wpdb->get_results( "SELECT element_id, trid FROM {$wpdb->prefix}icl_translations WHERE element_id IN ({$in}) AND element_type = 'st-batch_strings'" ); $keyByBatchId = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'element_id' ), Lst::pluck( 'trid' ) ] ); $trids = $keyByBatchId( $trids ); $in = wpml_prepare_in( $trids, '%d' ); /** @var array $transIds */ $transIds = $wpdb->get_results( "SELECT translation_id, trid, language_code FROM {$wpdb->prefix}icl_translations WHERE trid IN ({$in}) AND source_language_code IS NOT NULL" ); $in = wpml_prepare_in( Lst::pluck( 'translation_id', $transIds ), '%d' ); $statuses = $wpdb->get_results( "SELECT status, translation_id FROM {$wpdb->prefix}icl_translation_status WHERE translation_id IN ({$in})" ); $keyByTranslationId = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'translation_id' ), Lst::pluck( 'status' ), ] ); $statuses = $keyByTranslationId( $statuses ); $keyByTrid = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'trid' ), Fns::identity() ] ); return wpml_collect( $batches ) ->map( Obj::prop( Fns::__, $trids ) ) ->map( Obj::prop( Fns::__, $keyByTrid( $transIds ) ) ) ->map( function ( $item ) use ( $statuses ) { return [ $item->language_code => Obj::prop( $item->translation_id, $statuses ) ]; } ) ->toArray(); } else { return []; } } public static function getStatusesOfBatch( \wpdb $wpdb, $batchId ) { $statuses = self::getStatuses( $wpdb, [ $batchId ] ); return count( $statuses ) ? current( $statuses ) : []; } } batch-translation/Strings.php 0000755 00000002203 14720402445 0012325 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use function WPML\FP\curryN; /** * Class Strings * * @package WPML\ST\Batch\Translation * @method static callable|object get( ...$getBatchRecord, ...$getString, ...$item, ...$id, ...$type ) */ class Strings { use Macroable; public static function init() { self::macro( 'get', curryN( 5, function ( callable $getBatchRecord, callable $getString, $item, $id, $type ) { if ( $type === 'st-batch' || $type === Module::EXTERNAL_TYPE ) { $getBatchString = function ( $strings, $stringId ) use ( $getString ) { $strings[ Module::STRING_ID_PREFIX . $stringId ] = $getString( $stringId ); return $strings; }; return (object) [ 'post_id' => $id, 'ID' => $id, 'post_type' => 'strings', 'kind' => 'Strings', 'kind_slug' => 'Strings', 'external_type' => true, 'string_data' => Fns::reduce( $getBatchString, [], $getBatchRecord( $id ) ), ]; } return $item; } ) ); } } Strings::init(); batch-translation/Convert.php 0000755 00000003652 14720402445 0012325 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Relation; use function WPML\FP\curryN; use function WPML\FP\invoke; use function WPML\FP\pipe; /** * Class Convert * * @package WPML\ST\Batch\Translation * @method static callable|array toBatchElements( ...$getBatchId, ...$setBatchRecord, ...$elements, ...$basketName ) :: ( string → int ) → ( int → int → string ) → [WPML_TM_Translation_Batch_Element] → string → [WPML_TM_Translation_Batch_Element] */ class Convert { use Macroable; public static function init() { self::macro( 'toBatchElements', curryN( 4, function ( $getBatchId, $setBatchRecord, $elements, $basketName ) { // $isString :: WPML_TM_Translation_Batch_Element → bool $isString = pipe( invoke( 'get_element_type' ), Relation::equals( 'string' ) ); list( $stringElements, $otherElements ) = wpml_collect( $elements )->partition( $isString ); $makeBatchPerLanguage = function ( \WPML_TM_Translation_Batch_Element $element ) use ( $getBatchId, $setBatchRecord, $basketName ) { $makeBatchElement = function ( $action, $lang ) use ( $element, $getBatchId, $setBatchRecord, $basketName ) { $batchId = $getBatchId( $basketName . '-' . $lang ); $setBatchRecord( $batchId, $element->get_element_id(), $element->get_source_lang() ); return Fns::makeN( 5, 'WPML_TM_Translation_Batch_Element', $batchId, 'st-batch', $element->get_source_lang(), [ $lang => $action ], [] ); }; return Fns::map( $makeBatchElement, $element->get_target_langs() ); }; $stringElements = $stringElements->map( $makeBatchPerLanguage ) ->flatten() ->unique( invoke( 'get_target_langs' ) ); return $otherElements->merge( $stringElements ) ->toArray(); } ) ); } } Convert::init(); batch-translation/Records.php 0000755 00000005137 14720402445 0012306 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\FP\Curryable; use WPML\FP\Fns; use WPML\FP\Lst; use function WPML\Container\make; use function WPML\FP\curryN; /** * @phpstan-type curried '__CURRIED_PLACEHOLDER__' * * @method static callable|void installSchema( ...$wpdb ) :: wpdb → void * @method static callable|void set( ...$wpdb, ...$batchId, ...$stringId ) :: wpdb → int → int → void * @method static callable|int[] findBatches( ...$wpdb, ...$stringId ) :: wpdb → int → int[] */ class Records { use Curryable; /** @var string */ public static $string_batch_sql_prototype = ' CREATE TABLE IF NOT EXISTS `%sicl_string_batches` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `string_id` bigint(20) unsigned NOT NULL, `batch_id` bigint(20) unsigned NOT NULL, PRIMARY KEY (`id`) ) '; /** * @param \wpdb|null $wpdb * @param int|curried $batchId * @return int|callable * * @phpstan-return ($batchId is not null ? int : callable) */ public static function get( \wpdb $wpdb = null, $batchId = null ) { return call_user_func_array( curryN( 2, function ( \wpdb $wpdb, $batchId ) { /** @var string $sql */ $sql = $wpdb->prepare( "SELECT string_id FROM {$wpdb->prefix}icl_string_batches WHERE batch_id = %d", $batchId ); return $wpdb->get_col( $sql ); } ), func_get_args() ); } } Records::curryN( 'installSchema', 1, function ( \wpdb $wpdb ) { $option = make( 'WPML\WP\OptionManager' ); if ( ! $option->get( 'ST', Records::class . '_schema_installed' ) ) { $wpdb->query( sprintf( Records::$string_batch_sql_prototype, $wpdb->prefix ) ); $option->set( 'ST', Records::class . '_schema_installed', true ); } } ); Records::curryN( 'set', 3, function ( \wpdb $wpdb, $batchId, $stringId ) { // TODO: ignore duplicates $wpdb->insert( "{$wpdb->prefix}icl_string_batches", [ 'batch_id' => $batchId, 'string_id' => $stringId, ], [ '%d', '%d' ] ); } ); Records::curryN( 'findBatch', 2, function ( \wpdb $wpdb, $stringId ) { /** @var string $sql */ $sql = $wpdb->prepare( "SELECT batch_id FROM {$wpdb->prefix}icl_string_batches WHERE string_id = %d", $stringId ); return $wpdb->get_var( $sql ); } ); Records::curryN( 'findBatches', 2, function ( \wpdb $wpdb, $stringIds ) { $in = wpml_prepare_in( $stringIds, '%d' ); $data = $wpdb->get_results( "SELECT batch_id, string_id FROM {$wpdb->prefix}icl_string_batches WHERE string_id IN ({$in})" ); $keyByStringId = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'string_id' ), Lst::pluck( 'batch_id' ) ] ); return $keyByStringId( $data ); } ); batch-translation/Module.php 0000755 00000005157 14720402445 0012134 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use function WPML\Container\make; use function WPML\FP\curryN; use function WPML\FP\partial; /** * Class Module * @package WPML\ST\Batch\Translation * * @phpstan-type curried '__CURRIED_PLACEHOLDER__' * * @method static callable getBatchId() :: ( string → int ) * @method static callable|void setBatchLanguage( ...$batchId, ...$sourceLang ) :: int → string → void */ class Module { use Macroable; const EXTERNAL_TYPE = 'st-batch_strings'; const STRING_ID_PREFIX = 'batch-string-'; public static function init() { global $sitepress, $wpdb; Records::installSchema( $wpdb ); self::macro( 'getBatchId', curryN( 1, function ( $batch ) { return \TranslationProxy_Batch::update_translation_batch( $batch ); } ) ); $setLanguage = curryN( 4, [ $sitepress, 'set_element_language_details' ] ); self::macro( 'setBatchLanguage', $setLanguage( Fns::__, self::EXTERNAL_TYPE, null, Fns::__ ) ); /** @var callable $initializeTranslation */ $initializeTranslation = StringTranslations::markTranslationsAsInProgress( partial( [ Status::class, 'getStatusesOfBatch' ], $wpdb ) ); /** @var callable $recordSetter */ $recordSetter = Records::set( $wpdb ); Hooks::addHooks( self::getBatchId(), self::batchStringsStorage( $recordSetter ), Records::get( $wpdb ), self::getString() ); Hooks::addStringTranslationStatusHooks( StringTranslations::updateStatus(), $initializeTranslation ); } /** * @param int $id * @return string|callable * @phpstan-return ($id is not null ? string : callable ) */ public static function getString( $id = null ) { return call_user_func_array( curryN( 1, function ( $id ) { return make( '\WPML_ST_String', [ ':string_id' => $id ] )->get_value(); } ), func_get_args() ); } /** * @param callable|curried $saveBatch * @param int|curried $batchId * @param int|curried $stringId * @param string|curried $sourceLang * @return void|callable * * @phpstan-param ?callable $saveBatch * @phpstan-param ?int $batchId * @phpstan-param ?int $stringId * @phpstan-param ?string $sourceLang * * @phpstan-return ( $sourceLang is not null ? void : callable ) * */ public static function batchStringsStorage( callable $saveBatch = null, $batchId = null, $stringId = null, $sourceLang = null ) { return call_user_func_array( curryN( 4, function ( callable $saveBatch, $batchId, $stringId, $sourceLang ) { self::setBatchLanguage( $batchId, $sourceLang ); $saveBatch( $batchId, $stringId ); } ), func_get_args() ); } } batch-translation/StringTranslations.php 0000755 00000020433 14720402445 0014551 0 ustar 00 <?php namespace WPML\ST\Batch\Translation; use WPML\Collect\Support\Traits\Macroable; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Obj; use WPML\FP\Str; use WPML\FP\Wrapper; use WPML\Setup\Option; use WPML\ST\API\Fns as ST_API; use function WPML\Container\make; use function WPML\FP\curryN; use function WPML\FP\invoke; use function WPML\FP\pipe; use function WPML\FP\spreadArgs; /** * Class StringTranslations * * @package WPML\ST\Batch\Translation * * @phpstan-type curried '__CURRIED_PLACEHOLDER__' * * @method static callable|void save( ...$element_type_prefix, ...$job, ...$decoder ) :: string → object → ( string → string → string ) → void * @method static callable|void addExisting( ...$prevTranslations, ...$package, ...$lang ) :: [WPML_TM_Translated_Field] → object → string → [WPML_TM_Translated_Field] * @method static callable|bool isTranslated( ...$field ) :: object → bool * @method static callable|void markTranslationsAsInProgress( ...$getJobStatus, ...$hasTranslation, ...$addTranslation, ...$post, ...$element) :: callable -> callable -> callable -> WPML_TM_Translation_Batch_Element -> \stdClass -> void * @method static callable|void cancelTranslations(...$job) :: \WPML_TM_Job_Entity -> void */ class StringTranslations { use Macroable; public static function init() { self::macro( 'isTranslated', Obj::prop( 'field_translate' ) ); self::macro( 'isBatchField', curryN( 1, function( $field ) { return self::isBatchId( Obj::prop( 'field_type', $field ) ); } ) ); self::macro( 'save', curryN( 3, function ( $element_type_prefix, $job, callable $decoder ) { if ( $element_type_prefix === 'st-batch' ) { // $decodeField :: field → string $decodeField = pipe( Obj::props( [ 'field_data_translated', 'field_format' ] ), spreadArgs( $decoder ) ); // $getStringId :: field → int $getStringId = pipe( Obj::prop( 'field_type' ), self::decodeStringId() ); // $saveTranslation :: field → void $saveTranslation = Fns::converge( ST_API::saveTranslation( Fns::__, $job->language_code, Fns::__, ICL_TM_COMPLETE ), [ $getStringId, $decodeField ] ); /** @var callable $filterTranslatedAndBatchField */ $filterTranslatedAndBatchField = Logic::allPass( [ self::isTranslated(), self::isBatchField() ] ); Wrapper::of( $job->elements ) ->map( Fns::filter( $filterTranslatedAndBatchField ) ) ->map( Fns::each( $saveTranslation ) ); } } ) ); self::macro( 'cancelTranslations', curryN( 1, function ( $job ) { if ( $job instanceof \WPML_TM_Post_Job_Entity && $job->get_type() === 'st-batch_strings' ) { $language = $job->get_target_language(); // $getTranslations :: $stringId -> [stringId, translation] $getTranslations = function ( $stringId ) use ( $language ) { return [ 'string_id' => $stringId, 'translation' => Obj::pathOr( '', [ $language, 'value' ], ST_API::getTranslations( $stringId ) ), ]; }; // $cancelStatus :: [stringId, translation] -> int $cancelStatus = Logic::ifElse( Obj::prop( 'translation' ), Fns::always( ICL_TM_COMPLETE ), Fns::always( ICL_TM_NOT_TRANSLATED ) ); // $cancel :: [stringId, translation] -> void $cancel = Fns::converge( ST_API::updateStatus( Fns::__, $language, Fns::__ ), [ Obj::prop( 'string_id' ), $cancelStatus ] ); \wpml_collect( $job->get_elements() ) ->map( invoke( 'get_type' ) ) ->filter( Fns::unary( self::isBatchId() ) ) ->map( self::decodeStringId() ) ->map( $getTranslations ) ->map( Fns::tap( $cancel ) ); } } ) ); self::macro( 'addExisting', curryN( 3, function ( $prevTranslations, $package, $lang ) { // $getTranslation :: lang → { translate, ... } → int → { id, translation } | null $getTranslation = curryN( 3, function ( $lang, $data, $stringId ) { if ( $data['translate'] === 1 && self::isBatchId( $stringId ) ) { /** @var string $translation */ $translation = ST_API::getTranslation( self::decodeStringId( $stringId ), $lang ); return (object) [ 'id' => $stringId, 'translation' => base64_encode( is_null( $translation ) ? '' : $translation ), ]; } return null; } ); // $createField :: string → WPML_TM_Translated_Field $createField = function ( $translation ) { return make( 'WPML_TM_Translated_Field', [ '', '', $translation, false ] ); }; // $updatePrevious :: [a] → { id, translate } → [a] $updatePrevious = function ( $prev, $string ) { $prev[ $string->id ] = $string->translation; return $prev; }; // $hasTranslation :: { id, translation } | null → bool $hasTranslation = Obj::prop( 'translation' ); return Wrapper::of( $package['contents'] ) ->map( Fns::map( $getTranslation( $lang ) ) ) ->map( Fns::filter( $hasTranslation ) ) ->map( Fns::map( Obj::evolve( [ 'translation' => $createField ] ) ) ) ->map( Fns::reduce( $updatePrevious, $prevTranslations ) ) ->get(); } ) ); self::macro( 'markTranslationsAsInProgress', curryN( 3, function ( $getJobStatus, $element, $post ) { if ( $element instanceof \WPML_TM_Translation_Batch_Element && $element->get_element_type() === 'st-batch' ) { $statuses = \wpml_collect( $getJobStatus( $post->post_id ) ); $addTranslationWithStatus = function ( $stringId, $targetLanguage ) use ( $statuses ) { $status = Option::shouldTranslateEverything() ? ICL_TM_IN_PROGRESS : $statuses->get( $targetLanguage, ICL_STRING_TRANSLATION_NOT_TRANSLATED ); ST_API::updateStatus( $stringId, $targetLanguage, $status ); }; \wpml_collect( $post->string_data ) ->keys() ->map( Fns::unary( StringTranslations::decodeStringId() ) ) ->map( Fns::unary( 'intval' ) ) ->crossJoin( array_keys( $element->get_target_langs() ) ) ->map( Fns::tap( spreadArgs( $addTranslationWithStatus ) ) ); } } ) ); } /** * @param string $element_type_prefix * @param \stdClass $job * @return callable|void * @phpstan-return ( $job is not null ? void : callable ) */ public static function updateStatus( $element_type_prefix = null, $job = null ) { return call_user_func_array( curryN( 2, function ( $element_type_prefix, $job ) { if ( $element_type_prefix === 'st-batch' ) { // $getStringId :: field → int $getStringId = pipe( Obj::prop( 'field_type' ), self::decodeStringId() ); /** @var callable $updateStatus */ $updateStatus = ST_API::updateStatus( Fns::__, $job->language_code, ICL_TM_IN_PROGRESS ); \wpml_collect( $job->elements ) ->filter( self::isBatchField() ) ->map( $getStringId ) ->each( $updateStatus ); } } ), func_get_args() ); } /** * @param string $str * * @return callable|string * * @phpstan-template A1 of string|curried * @phpstan-param ?A1 $str * @phpstan-return ($str is not null ? string : callable(string=):string) */ public static function decodeStringId( $str = null ) { return call_user_func_array( curryN( 1, function( $str ) { return Str::replace( Module::STRING_ID_PREFIX, '', $str ); } ), func_get_args() ); } /** * @param string $str * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-param ?A1 $str * @phpstan-return ($str is not null ? bool : callable(string=):bool) */ public static function isBatchId( $str = null ) { return call_user_func_array( curryN( 1, function( $str ) { return Str::startsWith( Module::STRING_ID_PREFIX, $str ); } ), func_get_args() ); } /** * @param string $field * * @return callable|bool * * @phpstan-template A1 of string|curried * @phpstan-param ?A1 $field * @phpstan-return ($field is not null ? bool : callable(string):bool) */ public static function isBatchField( $field = null ) { return call_user_func_array( curryN( 1, function( $field ) { return self::isBatchId( Obj::prop( 'field_type', $field ) ); } ), func_get_args() ); } } StringTranslations::init(); actions/class-wpml-st-remote-string-translation-factory.php 0000755 00000001066 14720402445 0020231 0 ustar 00 <?php class WPML_ST_Remote_String_Translation_Factory implements IWPML_Backend_Action_Loader, IWPML_Action { public function create() { return $this; } public function add_hooks() { if ( did_action( 'wpml_tm_loaded' ) ) { $this->on_tm_loaded(); } else { add_action( 'wpml_tm_loaded', array( $this, 'on_tm_loaded' ) ); } } public function on_tm_loaded() { if ( current_user_can( \WPML\LIB\WP\User::CAP_MANAGE_TRANSLATIONS ) ) { add_action( 'wpml_st_below_menu', array( 'WPML_Remote_String_Translation', 'display_string_menu' ) ); } } } actions/class-wpml-st-wp-loaded-action.php 0000755 00000001670 14720402445 0014561 0 ustar 00 <?php class WPML_ST_WP_Loaded_Action extends WPML_SP_User { /** @var WPML_String_Translation $st_instance */ private $st_instance; /** @var string $pagenow */ private $pagenow; /** @var string $get_page */ private $get_page; public function __construct( &$sitepress, &$st_instance, &$pagenow, $get_page ) { parent::__construct( $sitepress ); $this->st_instance = &$st_instance; $this->pagenow = &$pagenow; $this->get_page = $get_page; } public function run() { $string_settings = $this->sitepress->get_setting( 'st', array() ); if ( ! isset( $string_settings['sw'] ) || ( $this->pagenow === 'admin.php' && strpos( $this->get_page, 'theme-localization.php' ) !== false ) ) { $string_settings['sw'] = isset( $string_settings['sw'] ) ? $string_settings['sw'] : array(); $this->sitepress->set_setting( 'st', $string_settings, true ); $this->st_instance->initialize_wp_and_widget_strings(); } } } actions/Actions.php 0000755 00000002515 14720402445 0010325 0 ustar 00 <?php namespace WPML\ST; class Actions { public static function get() { return array( 'WPML_ST_Theme_Plugin_Localization_Resources_Factory', 'WPML_ST_Theme_Plugin_Localization_Options_UI_Factory', 'WPML_ST_Theme_Plugin_Localization_Options_Settings_Factory', 'WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory', 'WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory', 'WPML_ST_Update_File_Hash_Ajax_Factory', 'WPML_ST_Theme_Plugin_Hooks_Factory', 'WPML_ST_Taxonomy_Labels_Translation_Factory', 'WPML_ST_String_Translation_AJAX_Hooks_Factory', 'WPML_ST_Remote_String_Translation_Factory', 'WPML_ST_Privacy_Content_Factory', 'WPML_ST_String_Tracking_AJAX_Factory', \WPML_ST_Translation_Memory::class, 'WPML_ST_Script_Translations_Hooks_Factory', \WPML\ST\MO\Scan\UI\Factory::class, 'WPML\ST\Rest\FactoryLoader', \WPML\ST\Gettext\HooksFactory::class, \WPML_ST_Support_Info_Filter::class, \WPML\ST\Troubleshooting\BackendHooks::class, \WPML\ST\Troubleshooting\AjaxFactory::class, \WPML\ST\MO\File\FailureHooksFactory::class, \WPML\ST\DB\Mappers\Hooks::class, \WPML\ST\Shortcode\Hooks::class, \WPML\ST\AdminTexts\UI::class, \WPML\ST\PackageTranslation\Hooks::class, \WPML\ST\Main\UI::class, \WPML\ST\StringsCleanup\UI::class, \WPML\ST\DisplayAsTranslated\CheckRedirect::class, ); } } class-wpml-st-string-statuses.php 0000755 00000001450 14720402445 0013145 0 ustar 00 <?php /** * WPML_ST_String_Statuses class * * Get the translation status text for the given status */ class WPML_ST_String_Statuses { public static function get_status( $status ) { switch ( $status ) { case ICL_STRING_TRANSLATION_COMPLETE: return __( 'Translation complete', 'wpml-string-translation' ); case ICL_STRING_TRANSLATION_PARTIAL: return __( 'Partial translation', 'wpml-string-translation' ); case ICL_STRING_TRANSLATION_NEEDS_UPDATE: return __( 'Translation needs update', 'wpml-string-translation' ); case ICL_STRING_TRANSLATION_NOT_TRANSLATED: return __( 'Not translated', 'wpml-string-translation' ); case ICL_STRING_TRANSLATION_WAITING_FOR_TRANSLATOR: return __( 'Waiting for translator', 'wpml-string-translation' ); } return ''; } } class-wpml-st-user-fields.php 0000755 00000012522 14720402445 0012212 0 ustar 00 <?php class WPML_ST_User_Fields { /** * @var string */ private $context = 'Authors'; /** @var SitePress */ private $sitepress; /** * @var mixed|WP_User|null */ private $authordata; /** @var bool */ private $lock_get_the_author_filter; public function __construct( SitePress $sitepress, &$authordata ) { $this->authordata = &$authordata; $this->sitepress = $sitepress; } public function init_hooks() { if ( ! is_admin() ) { add_action( 'init', array( $this, 'add_get_the_author_field_filters' ) ); add_filter( 'the_author', array( $this, 'the_author_filter' ), 10, 1 ); } add_action( 'profile_update', array( $this, 'profile_update_action' ), 10 ); add_action( 'user_register', array( $this, 'profile_update_action' ), 10 ); } public function add_get_the_author_field_filters() { $translatable_fields = $this->get_translatable_meta_fields(); foreach ( $translatable_fields as $field ) { add_filter( "get_the_author_{$field}", array( $this, 'get_the_author_field_filter' ), 10, 3 ); } } /** * @param int $user_id */ public function profile_update_action( $user_id ) { $this->register_user_strings( $user_id ); } /** * @param int $user_id */ private function register_user_strings( $user_id ) { if ( $this->is_user_role_translatable( $user_id ) ) { $fields = $this->get_translatable_meta_fields(); foreach ( $fields as $field ) { $name = $this->get_string_name( $field, $user_id ); $value = get_user_meta( $user_id, $field, true ); /** * Some fields like "display_name" are not part of user meta * so we have a fallback to get its value from `get_the_author_meta` */ if ( '' === $value ) { $value = get_the_author_meta( $field, $user_id ); } icl_register_string( $this->context, $name, $value, true ); } } } /** * @param string $value * @param int $user_id * @param int $original_user_id * * @return string */ public function get_the_author_field_filter( $value, $user_id, $original_user_id ) { if ( $this->lock_get_the_author_filter ) { return $value; } $field = preg_replace( '/get_the_author_/', '', current_filter(), 1 ); $value = $this->translate_user_meta_field( $field, $value, $user_id ); return $this->apply_filters_for_the_author_field_output( $value, $field, $user_id, $original_user_id ); } /** * @param string $value * @param string $field * @param int $user_id * @param int $original_user_id * * @return string */ private function apply_filters_for_the_author_field_output( $value, $field, $user_id, $original_user_id ) { $this->lock_get_the_author_filter = true; /** * WP hook described in wp-includes/author-template.php * * @see get_the_author_meta */ $value = apply_filters( "get_the_author_$field", $value, $user_id, $original_user_id ); $this->lock_get_the_author_filter = false; return $value; } /** * This filter will only replace the "display_name" of the current author (in global $authordata) * * @param mixed|string|null $value * * @return mixed|string|null */ public function the_author_filter( $value ) { if ( isset( $this->authordata->ID ) ) { $value = $this->translate_user_meta_field( 'display_name', $value, $this->authordata->ID ); } return $value; } /** * @param string $field * @param string $value * @param mixed|int|null $user_id * * @return string */ private function translate_user_meta_field( $field, $value, $user_id = null ) { if ( ! is_admin() && $this->is_user_role_translatable( $user_id ) ) { $name = $this->get_string_name( $field, $user_id ); $value = icl_translate( $this->context, $name, $value, true ); } return $value; } /** * @return array */ private function get_translatable_meta_fields() { $default_fields = array( 'first_name', 'last_name', 'nickname', 'description', 'display_name', ); return apply_filters( 'wpml_translatable_user_meta_fields', $default_fields ); } /** * @param int $user_id * * @return bool */ public function is_user_role_translatable( $user_id ) { $ret = false; $translated_roles = $this->get_translated_roles(); $user = new WP_User( $user_id ); if ( is_array( $user->roles ) && array_intersect( $user->roles, $translated_roles ) ) { $ret = true; } return $ret; } /** * @return array */ private function get_translated_roles() { $st_settings = $this->sitepress->get_setting( 'st' ); return isset( $st_settings['translated-users'] ) && is_array( $st_settings['translated-users'] ) ? $st_settings['translated-users'] : array(); } /** * @param string $field * @param int $user_id * * @return string */ private function get_string_name( $field, $user_id ) { return $field . '_' . $user_id; } /** * @return array */ public function init_register_strings() { $processed_ids = array(); $translated_roles = $this->get_translated_roles(); $blog_id = get_current_blog_id(); foreach ( $translated_roles as $role ) { $args = array( 'blog_id' => $blog_id, 'fields' => 'ID', 'exclude' => $processed_ids, 'role' => $role, ); $users = get_users( $args ); foreach ( $users as $user_id ) { $this->register_user_strings( $user_id ); $processed_ids[] = $user_id; } } return $processed_ids; } } po-import/class-wpml-po-import-strings.php 0000755 00000004523 14720402445 0014711 0 ustar 00 <?php class WPML_PO_Import_Strings { const NONCE_NAME = 'wpml-po-import-strings'; private $errors; public function maybe_import_po_add_strings() { if ( array_key_exists( 'icl_po_upload', $_POST ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'icl_po_form' ) ) { add_filter( 'wpml_st_get_po_importer', array( $this, 'import_po' ) ); return; } if ( array_key_exists( 'action', $_POST ) && 'icl_st_save_strings' === $_POST['action'] && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'add_po_strings' ) ) { $this->add_strings(); } } /** * @return null|WPML_PO_Import */ public function import_po() { if ( $_FILES[ 'icl_po_file' ][ 'size' ] === 0 ) { $this->errors = esc_html__( 'File upload error', 'wpml-string-translation' ); return null; } else { $po_importer = new WPML_PO_Import( $_FILES[ 'icl_po_file' ][ 'tmp_name' ] ); $this->errors = $po_importer->get_errors(); return $po_importer; } } /** * @return string */ public function get_errors() { return $this->errors; } private function add_strings() { $strings = json_decode( $_POST['strings_json'] ); foreach ( $strings as $k => $string ) { $original = wp_kses_post( $string->original ); $context = (string) \WPML\API\Sanitize::string( $string->context ); $string->original = str_replace( '\n', "\n", $original ); $name = isset( $string->name ) ? (string) \WPML\API\Sanitize::string( $string->name ) : md5( $original ); $string_id = icl_register_string( array( 'domain' => (string) \WPML\API\Sanitize::string( $_POST['icl_st_domain_name']), 'context' => $context ), $name, $original ); $this->maybe_add_translation( $string_id, $string ); } } /** * @param int|false|null $string_id * @param \stdClass $string */ private function maybe_add_translation( $string_id, $string ) { if ( $string_id && array_key_exists( 'icl_st_po_language', $_POST ) ) { if ( $string->translation !== '' ) { $status = ICL_TM_COMPLETE; if ( $string->fuzzy ) { $status = ICL_TM_NOT_TRANSLATED; } $translation = str_replace( '\n', "\n", wp_kses_post( $string->translation ) ); icl_add_string_translation( $string_id, $_POST[ 'icl_st_po_language' ], $translation, $status ); icl_update_string_status( $string_id ); } } } } po-import/class-wpml-po-import-strings-scripts.php 0000755 00000000743 14720402445 0016376 0 ustar 00 <?php class WPML_PO_Import_Strings_Scripts { public function __construct() { $this->init(); } public function init() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } public function enqueue_scripts( $page_hook ) { if ( WPML_ST_FOLDER . '/menu/string-translation.php' === $page_hook ) { wp_enqueue_script( 'wpml-st-strings-json-import-po', WPML_ST_URL . '/res/js/strings_json_import_po.js', array( 'jquery' ), WPML_ST_VERSION ); } } } upgrade/class-wpml-st-upgrade-migrate-originals.php 0000755 00000012235 14720402445 0016462 0 ustar 00 <?php class WPML_ST_Upgrade_Migrate_Originals implements IWPML_St_Upgrade_Command { /** @var wpdb $wpdb */ private $wpdb; /** @var SitePress sitepress */ private $sitepress; private $translations = array(); private $not_translated = array(); private $active_languages; public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; $active_languages = $this->sitepress->get_active_languages(); foreach ( $active_languages as $lang ) { $this->active_languages[] = $lang['code']; } } public static function get_command_id() { return __CLASS__; } public function run() { if ( $this->is_migration_required() ) { if ( current_user_can( 'manage_options' ) ) { $this->sitepress->get_wp_api()->add_action( 'admin_notices', array( $this, 'update_message' ) ); } return false; } else { return true; } } function update_message() { ?> <div id="wpml-st-upgrade-migrate-originals" class="update-nag notice notice-info" style="display:block"> <p> <?php esc_html_e( "WPML needs to update the database. This update will help improve WPML's performance when fetching translated strings.", 'wpml-string-translation' ); ?> <br /><br /> <button class="wpml-st-upgrade-migrate-originals"><?php esc_html_e( 'Update Now', 'wpml-string-translation' ); ?></button> <span class="spinner" style="float: none"></span> </p> <?php wp_nonce_field( 'wpml-st-upgrade-migrate-originals-nonce', 'wpml-st-upgrade-migrate-originals-nonce' ); ?> </div> <div id="wpml-st-upgrade-migrate-originals-complete" class="update-nag notice notice-info" style="display:none"> <p> <?php esc_html_e( 'The database has been updated.', 'wpml-string-translation' ); ?> <br /><br /> <button class="wpml-st-upgrade-migrate-originals-close"><?php esc_html_e( 'Close', 'wpml-string-translation' ); ?></button> </p> <?php wp_nonce_field( 'wpml-st-upgrade-migrate-originals-nonce', 'wpml-st-upgrade-migrate-originals-nonce' ); ?> </div> <script type="text/javascript"> jQuery( function( $ ) { jQuery( '.wpml-st-upgrade-migrate-originals' ).click( function() { jQuery( this ).prop( 'disabled', true ); jQuery( this ).parent().find( '.spinner' ).css( 'visibility', 'visible' ); jQuery.ajax({ url: ajaxurl, type: "POST", data: { action: 'wpml-st-upgrade-migrate-originals', nonce: jQuery( '#wpml-st-upgrade-migrate-originals-nonce' ).val() }, success: function ( response ) { jQuery( '#wpml-st-upgrade-migrate-originals' ).hide(); jQuery( '#wpml-st-upgrade-migrate-originals-complete' ).css( 'display', 'block' ); } }); }); jQuery( '.wpml-st-upgrade-migrate-originals-close' ).click( function() { jQuery( '#wpml-st-upgrade-migrate-originals-complete' ).hide(); }); }); </script> <?php } public function run_ajax() { if ( $this->is_migration_required() ) { $this->get_strings_without_translations(); $this->get_originals_with_translations(); $this->migrate_translations(); } return true; } public function run_frontend() {} private function is_migration_required() { $query = " SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE context LIKE 'plugin %' OR context LIKE 'theme %' LIMIT 1"; $found = $this->wpdb->get_var( $query ); return $found > 0; } private function get_strings_without_translations() { foreach ( $this->active_languages as $lang ) { $res_args = array( $lang, $lang ); $res_query = " SELECT s.value, s.id FROM {$this->wpdb->prefix}icl_strings s WHERE s.id NOT IN ( SELECT st.string_id FROM {$this->wpdb->prefix}icl_string_translations st WHERE st.language=%s ) AND s.language!=%s "; $res_prepare = $this->wpdb->prepare( $res_query, $res_args ); $this->not_translated[ $lang ] = $this->wpdb->get_results( $res_prepare, ARRAY_A ); } } private function get_originals_with_translations() { foreach ( $this->active_languages as $lang ) { $res_args = array( ICL_TM_COMPLETE, $lang ); $res_query = " SELECT st.value AS tra, s.value AS org FROM {$this->wpdb->prefix}icl_strings s LEFT JOIN {$this->wpdb->prefix}icl_string_translations st ON s.id=st.string_id WHERE st.status=%d AND st.language=%s "; $res_prepare = $this->wpdb->prepare( $res_query, $res_args ); $result = $this->wpdb->get_results( $res_prepare, ARRAY_A ); $strings = array(); foreach ( $result as $string ) { $strings[ $string['org'] ] = $string['tra']; } $this->translations[ $lang ] = $strings; } } private function migrate_translations() { foreach ( $this->active_languages as $lang ) { foreach ( $this->not_translated[ $lang ] as $not_translated ) { if ( isset( $this->translations[ $lang ][ $not_translated['value'] ] ) ) { icl_add_string_translation( $not_translated['id'], $lang, $this->translations[ $lang ][ $not_translated['value'] ], ICL_TM_COMPLETE ); break; } } } } } upgrade/class-wpml-st-upgrade-command-not-found-exception.php 0000755 00000000653 14720402445 0020367 0 ustar 00 <?php class WPML_ST_Upgrade_Command_Not_Found_Exception extends InvalidArgumentException { /** * @param string $class_name * @param int $code * @param Exception $previous */ public function __construct( $class_name, $code = 0, Exception $previous = null ) { $msg = sprintf( 'Class %s is not valid String Translation upgrade strategy', $class_name ); parent::__construct( $msg, $code, $previous ); } } upgrade/class-wpml-st-upgrade-db-longtext-string-value.php 0000755 00000003163 14720402445 0017712 0 ustar 00 <?php /** * WPML_ST_Upgrade_DB_Longtext_String_Value class file. * * @package wpml-string-translation */ /** * Class WPML_ST_Upgrade_DB_Longtext_String_Value */ class WPML_ST_Upgrade_DB_Longtext_String_Value implements IWPML_St_Upgrade_Command { /** * WP db instance. * * @var wpdb */ private $wpdb; /** * WPML_ST_Upgrade_DB_Longtext_String_Value constructor. * * @param wpdb $wpdb WP db instance. */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * Run upgrade. * * @return bool */ public function run() { $result = true; $table_name = $this->wpdb->prefix . 'icl_strings'; if ( count( (array) $this->wpdb->get_results( "SHOW TABLES LIKE '{$table_name}'" ) ) ) { $sql = " ALTER TABLE {$table_name} MODIFY COLUMN `value` LONGTEXT NOT NULL; "; $result = false !== $this->wpdb->query( $sql ); } $table_name = $this->wpdb->prefix . 'icl_string_translations'; if ( count( (array) $this->wpdb->get_results( "SHOW TABLES LIKE '{$table_name}'" ) ) ) { $sql = " ALTER TABLE {$table_name} MODIFY COLUMN `value` LONGTEXT NULL DEFAULT NULL, MODIFY COLUMN `mo_string` LONGTEXT NULL DEFAULT NULL; "; $result = ( false !== $this->wpdb->query( $sql ) ) && $result; } return $result; } /** * Run upgrade in ajax. * * @return bool */ public function run_ajax() { return $this->run(); } /** * Run upgrade on frontend. * * @return bool */ public function run_frontend() { return $this->run(); } /** * Get command id. * * @return string */ public static function get_command_id() { return __CLASS__; } } upgrade/class-wpml-st-upgrade.php 0000755 00000012660 14720402445 0013051 0 ustar 00 <?php /** * WPML_ST_Upgrade class file. * * @package wpml-string-translation */ /** * Class WPML_ST_Upgrade */ class WPML_ST_Upgrade { const TRANSIENT_UPGRADE_IN_PROGRESS = 'wpml_st_upgrade_in_progress'; /** * SitePress instance. * * @var SitePress $sitepress */ private $sitepress; /** * Upgrade Command Factory instance. * * @var WPML_ST_Upgrade_Command_Factory */ private $command_factory; /** * Upgrade in progress flag. * * @var bool $upgrade_in_progress */ private $upgrade_in_progress; /** * WPML_ST_Upgrade constructor. * * @param SitePress $sitepress SitePress instance. * @param WPML_ST_Upgrade_Command_Factory|null $command_factory Upgrade Command Factory instance. */ public function __construct( SitePress $sitepress, WPML_ST_Upgrade_Command_Factory $command_factory = null ) { $this->sitepress = $sitepress; $this->command_factory = $command_factory; } /** * Run upgrade. */ public function run() { if ( get_transient( self::TRANSIENT_UPGRADE_IN_PROGRESS ) ) { return; } if ( $this->sitepress->get_wp_api()->is_admin() ) { if ( $this->sitepress->get_wp_api()->constant( 'DOING_AJAX' ) ) { $this->run_ajax(); } else { $this->run_admin(); } } else { $this->run_front_end(); } $this->set_upgrade_completed(); } /** * Run admin. */ private function run_admin() { $this->maybe_run( 'WPML_ST_Upgrade_Migrate_Originals' ); $this->maybe_run( 'WPML_ST_Upgrade_Display_Strings_Scan_Notices' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_String_Packages' ); $this->maybe_run( 'WPML_ST_Upgrade_MO_Scanning' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_String_Name_Index' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_Longtext_String_Value' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_Strings_Add_Translation_Priority_Field' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_String_Packages_Word_Count' ); $this->maybe_run( '\WPML\ST\Upgrade\Command\RegenerateMoFilesWithStringNames' ); $this->maybe_run( \WPML\ST\Upgrade\Command\MigrateMultilingualWidgets::class ); } /** * Run ajax. */ private function run_ajax() { $this->maybe_run_ajax( 'WPML_ST_Upgrade_Migrate_Originals' ); // It has to be maybe_run. $this->maybe_run( 'WPML_ST_Upgrade_MO_Scanning' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_String_Packages_Word_Count' ); } /** * Run on frontend. */ private function run_front_end() { $this->maybe_run( 'WPML_ST_Upgrade_MO_Scanning' ); $this->maybe_run( 'WPML_ST_Upgrade_DB_String_Packages_Word_Count' ); } /** * Maybe run command. * * @param string $class Command class name. */ private function maybe_run( $class ) { if ( ! $this->has_command_been_executed( $class ) ) { $this->set_upgrade_in_progress(); $upgrade = $this->command_factory->create( $class ); if ( $upgrade->run() ) { $this->mark_command_as_executed( $class ); } } } /** * Maybe run command in ajax. * * @param string $class Command class name. */ private function maybe_run_ajax( $class ) { if ( ! $this->has_command_been_executed( $class ) ) { $this->run_ajax_command( $class ); } } /** * Run command in ajax. * * @param string $class Command class name. */ private function run_ajax_command( $class ) { if ( $this->nonce_ok( $class ) ) { $upgrade = $this->command_factory->create( $class ); if ( $upgrade->run_ajax() ) { $this->mark_command_as_executed( $class ); $this->sitepress->get_wp_api()->wp_send_json_success( '' ); } } } /** * Check nonce. * * @param string $class Command class name. * * @return bool */ private function nonce_ok( $class ) { $ok = false; $class = strtolower( $class ); $class = str_replace( '_', '-', $class ); if ( isset( $_POST['action'] ) && $_POST['action'] === $class ) { $nonce = $this->filter_nonce_parameter(); if ( $this->sitepress->get_wp_api()->wp_verify_nonce( $nonce, $class . '-nonce' ) ) { $ok = true; } } return $ok; } /** * Check if command was executed. * * @param string $class Command class name. * * @return bool */ public function has_command_been_executed( $class ) { /** @phpstan-ignore-next-line */ $id = call_user_func( [ $class, 'get_command_id' ] ); $settings = $this->sitepress->get_setting( 'st', [] ); return isset( $settings[ $id . '_has_run' ] ); } /** * Mark command as executed. * * @param string $class Command class name. */ public function mark_command_as_executed( $class ) { /** @phpstan-ignore-next-line */ $id = call_user_func( [ $class, 'get_command_id' ] ); $settings = $this->sitepress->get_setting( 'st', [] ); $settings[ $id . '_has_run' ] = true; $this->sitepress->set_setting( 'st', $settings, true ); wp_cache_flush(); } /** * Filter nonce. * * @return mixed */ protected function filter_nonce_parameter() { return filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); } /** * Set flag that upgrade is in process. */ private function set_upgrade_in_progress() { if ( ! $this->upgrade_in_progress ) { $this->upgrade_in_progress = true; set_transient( self::TRANSIENT_UPGRADE_IN_PROGRESS, true, MINUTE_IN_SECONDS ); } } /** * Mark upgrade as completed. */ private function set_upgrade_completed() { if ( $this->upgrade_in_progress ) { $this->upgrade_in_progress = false; delete_transient( self::TRANSIENT_UPGRADE_IN_PROGRESS ); } } } upgrade/repair-schema/wpml-st-repair-strings-schema.php 0000755 00000005221 14720402445 0017241 0 ustar 00 <?php class WPML_ST_Repair_Strings_Schema { const OPTION_HAS_RUN = 'wpml_st_repair_string_schema_has_run'; /** @var IWPML_St_Upgrade_Command $upgrade_command */ private $upgrade_command; /** @var WPML_Notices $notices */ private $notices; /** @var array $args */ private $args; /** @var string $db_error */ private $db_error; /** @var array $has_run */ private $has_run = array(); public function __construct( WPML_Notices $notices, array $args, $db_error ) { $this->notices = $notices; $this->args = $args; $this->db_error = $db_error; } public function set_command( IWPML_St_Upgrade_Command $upgrade_command ) { $this->upgrade_command = $upgrade_command; } /** @return bool */ public function run() { $this->has_run = get_option( self::OPTION_HAS_RUN, array() ); if ( ! $this->upgrade_command || array_key_exists( $this->get_command_id(), $this->has_run ) ) { $this->add_notice(); return false; } if ( $this->run_upgrade_command() ) { return true; } $this->add_notice(); return false; } /** @return bool */ private function run_upgrade_command() { if ( ! $this->acquire_lock() ) { return false; } $success = $this->upgrade_command->run(); $this->has_run[ $this->get_command_id() ] = true; update_option( self::OPTION_HAS_RUN, $this->has_run, false ); $this->release_lock(); return (bool) $success; } private function get_command_id() { return get_class( $this->upgrade_command ); } /** @return bool */ private function acquire_lock() { if ( get_transient( WPML_ST_Upgrade::TRANSIENT_UPGRADE_IN_PROGRESS ) ) { return false; } set_transient( WPML_ST_Upgrade::TRANSIENT_UPGRADE_IN_PROGRESS, true, MINUTE_IN_SECONDS ); return true; } private function release_lock() { delete_transient( WPML_ST_Upgrade::TRANSIENT_UPGRADE_IN_PROGRESS ); } private function add_notice() { $text = '<p>' . sprintf( esc_html__( 'We have detected a problem with some tables in the database. Please contact %1$sWPML support%2$s to get this fixed.', 'wpml-string-translation' ), '<a href="https://wpml.org/forums/forum/english-support/" class="otgs-external-link" rel="noopener" target="_blank">', '</a>' ) . '</p>'; $text .= '<pre>' . $this->db_error . '</pre>'; if ( $this->upgrade_command ) { $notice_id = $this->get_command_id(); } else { $notice_id = 'default'; $text .= '<pre>' . print_r( $this->args, true ) . '</pre>'; } $notice = $this->notices->create_notice( $notice_id, $text, __CLASS__ ); $notice->set_hideable( true ); $notice->set_css_class_types( array( 'notice-error' ) ); $this->notices->add_notice( $notice ); } } upgrade/class-wpml-st-upgrade-db-string-name-index.php 0000755 00000002047 14720402445 0016761 0 ustar 00 <?php class WPML_ST_Upgrade_DB_String_Name_Index implements IWPML_St_Upgrade_Command { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function run() { $result = true; $table_name = $this->wpdb->prefix . 'icl_strings'; /** @var array<int, object> $results */ $results = $this->wpdb->get_results( "SHOW TABLES LIKE '{$table_name}'" ); if ( 0 !== count( $results ) ) { $sql = "SHOW KEYS FROM {$table_name} WHERE Key_name='icl_strings_name'"; /** @var array<int, object> $results */ $results = $this->wpdb->get_results( $sql ); if ( 0 === count( $results ) ) { $sql = " ALTER TABLE {$this->wpdb->prefix}icl_strings ADD INDEX `icl_strings_name` (`name` ASC); "; $result = false !== $this->wpdb->query( $sql ); } } return $result; } public function run_ajax() { $this->run(); } public function run_frontend() { $this->run(); } public static function get_command_id() { return __CLASS__ . '_2'; } } upgrade/class-wpml-st-upgrade-string-index.php 0000755 00000001354 14720402445 0015460 0 ustar 00 <?php class WPML_ST_Upgrade_String_Index { /** @var wpdb */ private $wpdb; const OPTION_NAME = 'wpml_string_table_ok_for_mo_import'; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function is_uc_domain_name_context_index_unique() { $key_exists = get_option( self::OPTION_NAME ); if ( ! $key_exists ) { $sql = "SHOW KEYS FROM {$this->wpdb->prefix}icl_strings WHERE Key_name='uc_domain_name_context_md5' AND Non_unique = 0"; /** @var array<int, object> $results */ $results = $this->wpdb->get_results( $sql ); $key_exists = 0 < count( $results ) ? 'yes' : 'no'; update_option( self::OPTION_NAME, $key_exists, true ); } return 'yes' === $key_exists; } } upgrade/class-wpml-st-upgrade-command-factory.php 0000755 00000005475 14720402445 0016140 0 ustar 00 <?php /** * WPML_ST_Upgrade_Command_Factory class file. * * @package wpml-string-translation */ use function WPML\Container\make; use WPML\ST\Upgrade\Command\RegenerateMoFilesWithStringNames; use WPML\ST\Upgrade\Command\MigrateMultilingualWidgets; /** * Class WPML_ST_Upgrade_Command_Factory */ class WPML_ST_Upgrade_Command_Factory { /** * WP db instance. * * @var wpdb wpdb */ private $wpdb; /** * SitePress instance. * * @var SitePress */ private $sitepress; /** * WPML_ST_Upgrade_Command_Factory constructor. * * @param wpdb $wpdb WP db instance. * @param SitePress $sitepress SitePress instance. */ public function __construct( wpdb $wpdb, SitePress $sitepress ) { $this->wpdb = $wpdb; $this->sitepress = $sitepress; } /** * Create upgrade commands. * * @param string $class_name Name of upgrade command class. * * @throws WPML_ST_Upgrade_Command_Not_Found_Exception Exception when command not found. * @return IWPML_St_Upgrade_Command */ public function create( $class_name ) { switch ( $class_name ) { case 'WPML_ST_Upgrade_Migrate_Originals': $result = new WPML_ST_Upgrade_Migrate_Originals( $this->wpdb, $this->sitepress ); break; case 'WPML_ST_Upgrade_Display_Strings_Scan_Notices': $themes_and_plugins_settings = new WPML_ST_Themes_And_Plugins_Settings(); $result = new WPML_ST_Upgrade_Display_Strings_Scan_Notices( $themes_and_plugins_settings ); break; case 'WPML_ST_Upgrade_DB_String_Packages': $result = new WPML_ST_Upgrade_DB_String_Packages( $this->wpdb ); break; case 'WPML_ST_Upgrade_MO_Scanning': $result = new WPML_ST_Upgrade_MO_Scanning( $this->wpdb ); break; case 'WPML_ST_Upgrade_DB_String_Name_Index': $result = new WPML_ST_Upgrade_DB_String_Name_Index( $this->wpdb ); break; case 'WPML_ST_Upgrade_DB_Longtext_String_Value': $result = new WPML_ST_Upgrade_DB_Longtext_String_Value( $this->wpdb ); break; case 'WPML_ST_Upgrade_DB_Strings_Add_Translation_Priority_Field': $result = new WPML_ST_Upgrade_DB_Strings_Add_Translation_Priority_Field( $this->wpdb ); break; case 'WPML_ST_Upgrade_DB_String_Packages_Word_Count': $result = new WPML_ST_Upgrade_DB_String_Packages_Word_Count( wpml_get_upgrade_schema() ); break; case '\WPML\ST\Upgrade\Command\RegenerateMoFilesWithStringNames': $isBackground = true; $result = new RegenerateMoFilesWithStringNames( \WPML\ST\MO\Generate\Process\ProcessFactory::createStatus( $isBackground ), \WPML\ST\MO\Generate\Process\ProcessFactory::createSingle( $isBackground ) ); break; case MigrateMultilingualWidgets::class: $result = new MigrateMultilingualWidgets(); break; default: throw new WPML_ST_Upgrade_Command_Not_Found_Exception( $class_name ); } return $result; } } upgrade/interface-iwpml_st_upgrade_command.php 0000755 00000000262 14720402445 0015712 0 ustar 00 <?php interface IWPML_St_Upgrade_Command { public function run(); public function run_ajax(); public function run_frontend(); public static function get_command_id(); } upgrade/class-wpml-st-upgrade-db-strings-add-translation-priority-field.php 0000755 00000003124 14720402445 0023140 0 ustar 00 <?php class WPML_ST_Upgrade_DB_Strings_Add_Translation_Priority_Field implements IWPML_St_Upgrade_Command { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function run() { $result = null; $table_name = $this->wpdb->prefix . 'icl_strings'; /** @var array<int, object> $results */ $results = $this->wpdb->get_results( "SHOW TABLES LIKE '{$table_name}'" ); if ( 0 !== count( $results ) ) { $sql = "SHOW FIELDS FROM {$table_name} WHERE FIELD = 'translation_priority'"; /** @var array<int, object> $s_results */ $s_results = $this->wpdb->get_results( $sql ); if ( 0 === count( $s_results ) ) { $sql = "ALTER TABLE {$this->wpdb->prefix}icl_strings ADD COLUMN `translation_priority` varchar(160) NOT NULL"; $result = false !== $this->wpdb->query( $sql ); } if ( false !== $result ) { $sql = "SHOW KEYS FROM {$table_name} WHERE Key_name='icl_strings_translation_priority'"; /** @var array<int, object> $results */ $results = $this->wpdb->get_results( $sql ); if ( 0 === count( $results ) ) { $sql = " ALTER TABLE {$this->wpdb->prefix}icl_strings ADD INDEX `icl_strings_translation_priority` ( `translation_priority` ASC ) "; $result = false !== $this->wpdb->query( $sql ); } else { $result = true; } } } return (bool) $result; } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } public static function get_command_id() { return __CLASS__; } } upgrade/class-wpml-st-upgrade-db-string-packages-word-count.php 0000755 00000001554 14720402445 0020613 0 ustar 00 <?php class WPML_ST_Upgrade_DB_String_Packages_Word_Count implements IWPML_St_Upgrade_Command { /** @var WPML_Upgrade_Schema $upgrade_schema */ private $upgrade_schema; public function __construct( WPML_Upgrade_Schema $upgrade_schema ) { $this->upgrade_schema = $upgrade_schema; } public function run() { $table = 'icl_string_packages'; $column = 'word_count'; if ( ! $this->upgrade_schema->does_table_exist( $table ) ) { return false; } if ( ! $this->upgrade_schema->does_column_exist( $table, $column ) ) { return (bool) $this->upgrade_schema->add_column( $table, $column, 'VARCHAR(2000) DEFAULT NULL' ); } return true; } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } /** * @return string */ public static function get_command_id() { return __CLASS__; } } upgrade/Command/MigrateMultilingualWidgets.php 0000755 00000005250 14720402445 0015577 0 ustar 00 <?php namespace WPML\ST\Upgrade\Command; use WPML\Element\API\Languages; use WPML\FP\Cast; use WPML\FP\Fns; use WPML\FP\Logic; use WPML\FP\Lst; use WPML\FP\Obj; use WPML\FP\Relation; use WPML\FP\Str; use WPML\LIB\WP\Option; use WPML\ST\MO\File\ManagerFactory; use function WPML\FP\partial; use function WPML\FP\pipe; class MigrateMultilingualWidgets implements \IWPML_St_Upgrade_Command { public function run() { $multiLingualWidgets = Option::getOr( 'widget_text_icl', [] ); $multiLingualWidgets = array_filter( $multiLingualWidgets, Logic::complement( 'is_scalar' ) ); if ( ! $multiLingualWidgets ) { return true; } $textWidgets = Option::getOr( 'widget_text', [] ); if ( $textWidgets ) { /** @var array $textWidgetsKeys */ $textWidgetsKeys = Obj::keys( $textWidgets ); $theHighestTextWidgetId = max( $textWidgetsKeys ); } else { $theHighestTextWidgetId = 0; $textWidgets['_multiwidget'] = 1; } $transformWidget = pipe( Obj::renameProp( 'icl_language', 'wpml_language' ), Obj::over( Obj::lensProp( 'wpml_language' ), Logic::ifElse( Relation::equals( 'multilingual' ), Fns::always( 'all' ), Fns::identity() ) ) ); $oldToNewIdMap = []; foreach ( $multiLingualWidgets as $id => $widget ) { $newId = ++ $theHighestTextWidgetId; $oldToNewIdMap[ $id ] = $newId; $textWidgets = Obj::assoc( $newId, $transformWidget( $widget ), $textWidgets ); } Option::update( 'widget_text', $textWidgets ); Option::delete( 'widget_text_icl' ); $sidebars = wp_get_sidebars_widgets(); $sidebars = $this->convertSidebarsConfig( $sidebars, $oldToNewIdMap ); wp_set_sidebars_widgets( $sidebars ); $this->convertWidgetsContentStrings(); return true; } private function convertSidebarsConfig( $sidebars, array $oldToNewIdMap ) { $isMultilingualWidget = Str::startsWith( 'text_icl' ); $extractIdNumber = pipe( Str::split( '-' ), Lst::last(), Cast::toInt() ); $mapWidgetId = Logic::ifElse( $isMultilingualWidget, pipe( $extractIdNumber, Obj::prop( Fns::__, $oldToNewIdMap ), Str::concat( 'text-' ) ), Fns::identity() ); return Fns::map( Fns::map( $mapWidgetId ), $sidebars ); } private function convertWidgetsContentStrings() { global $wpdb; $wpdb->query(" UPDATE {$wpdb->prefix}icl_strings SET `name` = CONCAT( 'widget body - ', MD5(`value`)) WHERE `name` LIKE 'widget body - text_icl%' "); $locales = Fns::map( Languages::getWPLocale(), Languages::getSecondaries() ); Fns::map( partial( [ ManagerFactory::create(), 'add' ], 'Widgets' ), $locales ); } public function run_ajax() { } public function run_frontend() { } public static function get_command_id() { return __CLASS__; } } upgrade/Command/RegenerateMoFilesWithStringNames.php 0000755 00000003373 14720402445 0016644 0 ustar 00 <?php namespace WPML\ST\Upgrade\Command; use WPML\ST\MO\File\Manager; use WPML\ST\MO\Generate\Process\SingleSiteProcess; use WPML\ST\MO\Generate\Process\Status; use WPML\ST\MO\Notice\RegenerationInProgressNotice; use WPML_Installation; use function WPML\Container\make; class RegenerateMoFilesWithStringNames implements \IWPML_St_Upgrade_Command { const WPML_VERSION_FOR_THIS_COMMAND = '4.3.4'; /** @var Status $status */ private $status; /** @var SingleSiteProcess $singleProcess */ private $singleProcess; /** * @param Status $status * @param SingleSiteProcess $singleProcess We use run the single site process because * the migration command runs once per site. */ public function __construct( Status $status, SingleSiteProcess $singleProcess ) { $this->status = $status; $this->singleProcess = $singleProcess; } public function run() { if ( ! ( $this->hasWpmlStartedBeforeThisCommand() && Manager::hasFiles() ) ) { $this->status->markComplete(); return true; } $this->singleProcess->runPage(); if ( $this->singleProcess->isCompleted() ) { \wpml_get_admin_notices()->remove_notice( RegenerationInProgressNotice::GROUP, RegenerationInProgressNotice::ID ); return true; } \wpml_get_admin_notices()->add_notice( make( RegenerationInProgressNotice::class ) ); return false; } /** * @return bool */ private function hasWpmlStartedBeforeThisCommand() { return (bool) version_compare( get_option( WPML_Installation::WPML_START_VERSION_KEY, '0.0.0' ), self::WPML_VERSION_FOR_THIS_COMMAND, '<' ); } public function run_ajax() { } public function run_frontend() { } public static function get_command_id() { return __CLASS__; } } upgrade/class-wpml-st-upgrade-mo-scanning.php 0000755 00000004370 14720402445 0015257 0 ustar 00 <?php class WPML_ST_Upgrade_MO_Scanning implements IWPML_St_Upgrade_Command { /** @var wpdb $wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function run() { return $this->create_table() && $this->add_mo_value_field_if_does_not_exist(); } private function create_table() { $table_name = $this->wpdb->prefix . 'icl_mo_files_domains'; $this->wpdb->query( "DROP TABLE IF EXISTS `{$table_name}`" ); //@todo needs proper testing /** @var string $sql */ $sql = $this->wpdb->prepare( " CREATE TABLE `{$this->wpdb->prefix}icl_mo_files_domains` ( `id` int(11) PRIMARY KEY NOT NULL AUTO_INCREMENT, `file_path` varchar(250) NOT NULL, `file_path_md5` varchar(32) NOT NULL, `domain` varchar(160) NOT NULL, `status` varchar(20) NOT NULL DEFAULT %s, `num_of_strings` int(11) NOT NULL DEFAULT '0', `last_modified` int(11) NOT NULL, `component_type` enum('plugin','theme','other') NOT NULL DEFAULT 'other', `component_id` varchar(100) DEFAULT NULL, UNIQUE KEY `file_path_md5_UNIQUE` (`file_path_md5`) ) ", array( WPML_ST_Translations_File_Entry::NOT_IMPORTED ) ); $sql .= $this->get_charset_collate(); return false !== $this->wpdb->query( $sql ); } private function add_mo_value_field_if_does_not_exist() { $result = true; $table_name = $this->wpdb->prefix . 'icl_string_translations'; /** @var array $results */ $results = $this->wpdb->get_results( "SHOW COLUMNS FROM `{$table_name}` LIKE 'mo_string'" ); if ( 0 === count( $results ) ) { $sql = " ALTER TABLE {$table_name} ADD COLUMN `mo_string` TEXT NULL DEFAULT NULL AFTER `value`; "; $result = false !== $this->wpdb->query( $sql ); } return $result; } public function run_ajax() { return $this->run(); } public function run_frontend() { return $this->run(); } public static function get_command_id() { return __CLASS__ . '_4' ; } /** * @return string */ private function get_charset_collate() { $charset_collate = ''; if ( method_exists( $this->wpdb, 'has_cap' ) && $this->wpdb->has_cap( 'collation' ) ) { $charset_collate = $this->wpdb->get_charset_collate(); } return $charset_collate; } } upgrade/class-wpml-st-upgrade-display-strings-scan-notices.php 0000755 00000001577 14720402445 0020574 0 ustar 00 <?php class WPML_ST_Upgrade_Display_Strings_Scan_Notices implements IWPML_St_Upgrade_Command { /** @var WPML_ST_Themes_And_Plugins_Settings */ private $settings; /** * WPML_ST_Upgrade_Display_Strings_Scan_Notices constructor. * * @param WPML_ST_Themes_And_Plugins_Settings $settings */ public function __construct( WPML_ST_Themes_And_Plugins_Settings $settings ) { $this->settings = $settings; } public static function get_command_id() { return __CLASS__; } public function run() { $this->maybe_add_missing_setting(); return ! $this->settings->display_notices_setting_is_missing(); } public function run_ajax() { return false; } public function run_frontend() { return false; } private function maybe_add_missing_setting() { if ( $this->settings->display_notices_setting_is_missing() ) { $this->settings->create_display_notices_setting(); } } } upgrade/class-wpml-st-upgrade-db-string-packages.php 0000755 00000002451 14720402445 0016511 0 ustar 00 <?php /** * Class WPML_ST_Upgrade_DB_String_Packages */ class WPML_ST_Upgrade_DB_String_Packages implements IWPML_St_Upgrade_Command { private $wpdb; /** * WPML_ST_Upgrade_DB_String_Packages constructor. * * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function run() { $sql_get_st_package_table_name = "SHOW TABLES LIKE '{$this->wpdb->prefix}icl_string_packages'"; $st_packages_table_exist = $this->wpdb->get_var( $sql_get_st_package_table_name ) === "{$this->wpdb->prefix}icl_string_packages"; if ( ! $st_packages_table_exist ) { return false; } $sql_get_post_id_column_from_st_package = "SHOW COLUMNS FROM {$this->wpdb->prefix}icl_string_packages LIKE 'post_id'"; $post_id_column_exists = $st_packages_table_exist ? $this->wpdb->get_var( $sql_get_post_id_column_from_st_package ) === 'post_id' : false; if ( ! $post_id_column_exists ) { $sql = "ALTER TABLE {$this->wpdb->prefix}icl_string_packages ADD COLUMN `post_id` INTEGER"; return (bool) $this->wpdb->query( $sql ); } return true; } public function run_ajax() { return $this->run(); } public function run_frontend() { } /** * @return string */ public static function get_command_id() { return __CLASS__ . '_2.4.2'; } } translations-file-scan/wpml-st-translations-file-dictionary.php 0000755 00000004220 14720402445 0021042 0 ustar 00 <?php use WPML\ST\TranslationFile\EntryQueries; class WPML_ST_Translations_File_Dictionary { /** @var WPML_ST_Translations_File_Dictionary_Storage */ private $storage; /** * @param WPML_ST_Translations_File_Dictionary_Storage $storage */ public function __construct( WPML_ST_Translations_File_Dictionary_Storage $storage ) { $this->storage = $storage; } /** * @param string $file_path * * @return WPML_ST_Translations_File_Entry|null */ public function find_file_info_by_path( $file_path ) { $result = $this->storage->find( $file_path ); if ( $result ) { return current( $result ); } return null; } /** * @param WPML_ST_Translations_File_Entry $file */ public function save( WPML_ST_Translations_File_Entry $file ) { $this->storage->save( $file ); } /** * @return WPML_ST_Translations_File_Entry[] */ public function get_not_imported_files() { return $this->storage->find( null, [ WPML_ST_Translations_File_Entry::NOT_IMPORTED, WPML_ST_Translations_File_Entry::PARTLY_IMPORTED, ] ); } public function clear_skipped() { $skipped = wpml_collect( $this->storage->find( null, [ WPML_ST_Translations_File_Entry::SKIPPED ] ) ); $skipped->each( function ( WPML_ST_Translations_File_Entry $entry ) { $entry->set_status( WPML_ST_Translations_File_Entry::NOT_IMPORTED ); $this->storage->save( $entry ); } ); } /** * @return WPML_ST_Translations_File_Entry[] */ public function get_imported_files() { return $this->storage->find( null, WPML_ST_Translations_File_Entry::IMPORTED ); } /** * @param null|string $extension * @param null|string $locale * * @return array */ public function get_domains( $extension = null, $locale = null ) { $files = wpml_collect( $this->storage->find() ); if ( $extension ) { $files = $files->filter( EntryQueries::isExtension( $extension ) ); } if ( $locale ) { $files = $files->filter( function ( WPML_ST_Translations_File_Entry $file ) use ( $locale ) { return $file->get_file_locale() === $locale; } ); } return $files->map( EntryQueries::getDomain() ) ->unique() ->values() ->toArray(); } } translations-file-scan/wpml-st-translations-file-unicode-characters-filter.php 0000755 00000002525 14720402445 0023731 0 ustar 00 <?php class WPML_ST_Translations_File_Unicode_Characters_Filter { /** @var string */ private $pattern; public function __construct() { $parts = array( '([0-9|#][\x{20E3}])', '[\x{00ae}|\x{00a9}|\x{203C}|\x{2047}|\x{2048}|\x{2049}|\x{3030}|\x{303D}|\x{2139}|\x{2122}|\x{3297}|\x{3299}][\x{FE00}-\x{FEFF}]?', '[\x{2190}-\x{21FF}][\x{FE00}-\x{FEFF}]?', '[\x{2300}-\x{23FF}][\x{FE00}-\x{FEFF}]?', '[\x{2460}-\x{24FF}][\x{FE00}-\x{FEFF}]?', '[\x{25A0}-\x{25FF}][\x{FE00}-\x{FEFF}]?', '[\x{2600}-\x{27BF}][\x{FE00}-\x{FEFF}]?', '[\x{2900}-\x{297F}][\x{FE00}-\x{FEFF}]?', '[\x{2B00}-\x{2BF0}][\x{FE00}-\x{FEFF}]?', '[\x{1F000}-\x{1F6FF}][\x{FE00}-\x{FEFF}]?', ); $this->pattern = '/' . implode( '|', $parts ) . '/u'; } /** * @param WPML_ST_Translations_File_Translation[] $translations * * @return WPML_ST_Translations_File_Translation[] */ public function filter( array $translations ) { return array_filter( $translations, array( $this, 'is_valid' ) ); } /** * @param \WPML_ST_Translations_File_Translation $translation * * @return bool */ public function is_valid( WPML_ST_Translations_File_Translation $translation ) { if ( preg_match( $this->pattern, $translation->get_original() ) || preg_match( $this->pattern, $translation->get_translation() ) ) { return false; } return true; } } translations-file-scan/wpml-st-translations-file-string-status-update.php 0000755 00000002575 14720402445 0023017 0 ustar 00 <?php class WPML_ST_Translations_File_String_Status_Update { /** @var int */ private $number_of_secondary_languages; /** @var wpdb */ private $wpdb; /** * @param int $number_of_secondary_languages * @param wpdb $wpdb */ public function __construct( $number_of_secondary_languages, wpdb $wpdb ) { $this->number_of_secondary_languages = $number_of_secondary_languages; $this->wpdb = $wpdb; } public function add_hooks() { add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_string_statuses' ), 10, 1 ); } public function update_string_statuses( WPML_ST_Translations_File_Entry $file ) { if ( ! in_array( $file->get_status(), array( WPML_ST_Translations_File_Entry::IMPORTED, WPML_ST_Translations_File_Entry::FINISHED ), true ) ) { return; } $sql = " UPDATE {$this->wpdb->prefix}icl_strings s SET s.status = CASE ( SELECT COUNT(t.id) FROM {$this->wpdb->prefix}icl_string_translations t WHERE t.string_id = s.id AND (t.status = %d OR t.mo_string IS NOT NULL) ) WHEN %d THEN %d WHEN 0 THEN %d ELSE %d END WHERE s.context = %s "; $sql = $this->wpdb->prepare( $sql, ICL_TM_COMPLETE, $this->number_of_secondary_languages, ICL_TM_COMPLETE, ICL_TM_NOT_TRANSLATED, ICL_TM_IN_PROGRESS, $file->get_domain() ); $this->wpdb->query( $sql ); } } translations-file-scan/dictionary/class-st-translations-file-dictionary-storage.php 0000755 00000000477 14720402445 0025011 0 ustar 00 <?php interface WPML_ST_Translations_File_Dictionary_Storage { public function save( WPML_ST_Translations_File_Entry $file ); /** * @param null|string $path * @param null|string|array $status * * @return WPML_ST_Translations_File_Entry[] */ public function find( $path = null, $status = null ); } translations-file-scan/dictionary/class-st-translations-file-dicionary-storage-table.php 0000755 00000010205 14720402445 0025700 0 ustar 00 <?php class WPML_ST_Translations_File_Dictionary_Storage_Table implements WPML_ST_Translations_File_Dictionary_Storage { /** @var wpdb */ private $wpdb; /** @var null|array */ private $data; /** @var WPML_ST_Translations_File_Entry[] */ private $new_data = array(); /** @var WPML_ST_Translations_File_Entry[] */ private $updated_data = array(); /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function add_hooks() { add_action( 'shutdown', array( $this, 'persist' ), 11, 0 ); } public function save( WPML_ST_Translations_File_Entry $file ) { $this->load_data(); $is_new = ! isset( $this->data[ $file->get_path() ] ); $this->data[ $file->get_path() ] = $file; if ( $is_new ) { $this->new_data[] = $file; } else { $this->updated_data[] = $file; } } /** * We have to postpone saving of real data because target table may not be created yet by migration process */ public function persist() { foreach ( $this->new_data as $file ) { $sql = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_mo_files_domains ( file_path, file_path_md5, domain, status, num_of_strings, last_modified, component_type, component_id ) VALUES ( %s, %s, %s, %s, %d, %d, %s, %s )"; $this->wpdb->query( $this->wpdb->prepare( $sql, array( $file->get_path(), $file->get_path_hash(), $file->get_domain(), $file->get_status(), $file->get_imported_strings_count(), $file->get_last_modified(), $file->get_component_type(), $file->get_component_id(), ) ) ); } foreach ( $this->updated_data as $file ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_mo_files_domains', $this->file_to_array( $file ), array( 'file_path_md5' => $file->get_path_hash(), ), array( '%s', '%s', '%d', '%d' ) ); } } /** * @param WPML_ST_Translations_File_Entry $file * @param array $data * * @return array */ private function file_to_array( WPML_ST_Translations_File_Entry $file, array $data = array() ) { $data['domain'] = $file->get_domain(); $data['status'] = $file->get_status(); $data['num_of_strings'] = $file->get_imported_strings_count(); $data['last_modified'] = $file->get_last_modified(); return $data; } public function find( $path = null, $status = null ) { $this->load_data(); if ( null !== $path ) { return isset( $this->data[ $path ] ) ? array( $this->data[ $path ] ) : array(); } if ( null === $status ) { return array_values( $this->data ); } if ( ! is_array( $status ) ) { $status = array( $status ); } $result = array(); foreach ( $this->data as $file ) { if ( in_array( $file->get_status(), $status, true ) ) { $result[] = $file; } } return $result; } /** * Checks if the given $path is on the wp_icl_mo_files_domains table. * It's only there when ST has handled the file. * * @param string $path * @param string $domain * * @return bool */ public function is_path_handled( $path, $domain ) { $file = new WPML_ST_Translations_File_Entry( $path, $domain ); // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared $id = $this->wpdb->get_var( $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_mo_files_domains WHERE file_path_md5 = %s LIMIT 1", $file->get_path_hash() ) ); // phpcs:enable WordPress.WP.PreparedSQL.NotPrepared return null !== $id; } private function load_data() { if ( null === $this->data ) { $this->data = array(); $sql = "SELECT * FROM {$this->wpdb->prefix}icl_mo_files_domains"; $rowset = $this->wpdb->get_results( $sql ); foreach ( $rowset as $row ) { $file = new WPML_ST_Translations_File_Entry( $row->file_path, $row->domain, $row->status ); $file->set_imported_strings_count( $row->num_of_strings ); $file->set_last_modified( $row->last_modified ); $file->set_component_type( $row->component_type ); $file->set_component_id( $row->component_id ); $this->data[ $file->get_path() ] = $file; } } } public function reset() { $this->data = null; } } translations-file-scan/wpml-st-translations-file-scan-factory.php 0000755 00000013110 14720402445 0021264 0 ustar 00 <?php use function WPML\Container\make; class WPML_ST_Translations_File_Scan_Factory { private $dictionary; private $queue; private $storage; private $wpml_file; private $find_aggregate; public function check_core_dependencies() { global $wpdb; $string_index_check = new WPML_ST_Upgrade_String_Index( $wpdb ); $scan_ui_block = new WPML_ST_Translations_File_Scan_UI_Block( wpml_get_admin_notices() ); if ( ! $string_index_check->is_uc_domain_name_context_index_unique() ) { $scan_ui_block->block_ui(); return false; } $scan_ui_block->unblock_ui(); if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'get_plugins' ) ) { $file = ABSPATH . 'wp-admin/includes/plugin.php'; if ( file_exists( $file ) ) { require_once $file; } else { return false; } } $wpml_file = $this->get_wpml_file(); return method_exists( $wpml_file, 'get_relative_path' ); } /** * @return array */ public function create_hooks() { $st_upgrade = WPML\Container\make( 'WPML_ST_Upgrade' ); if ( ! $st_upgrade->has_command_been_executed( 'WPML_ST_Upgrade_MO_Scanning' ) ) { return []; } $load = [ // Listener of 'wpml_st_translations_file_post_import' hook. 'stats-update' => $this->get_stats_update(), 'string-status-update' => $this->get_string_status_update(), ]; if ( current_user_can( 'manage_options' ) && ( ! is_admin() || $this->isThemeAndLocalizationPage() ) ) { // Only register / update .mo files when the user is an admin. // NOTE: it's tending to only load this on the Themen and Plugins // localization page, but some .mo files are only loaded on the // frontend (plugins which separating frontend and backend strings). $load['mo-file-registration'] = $this->store_translation_files_info_on_db(); } return $load; } private function isThemeAndLocalizationPage() { global $sitepress; return $sitepress ->get_wp_api() ->is_core_page( 'theme-localization.php' ); } /** * @return WPML_ST_Translations_File_Queue */ public function create_queue() { if ( ! $this->queue ) { global $wpdb; $charset_filter_factory = new WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory( $wpdb ); $this->queue = new WPML_ST_Translations_File_Queue( $this->create_dictionary(), new WPML_ST_Translations_File_Scan( $charset_filter_factory ), $this->create_storage(), new WPML_Language_Records( $wpdb ), $this->get_scan_limit(), new WPML_Transient() ); } return $this->queue; } /** * @return WPML_ST_Translations_File_Scan_Storage */ private function create_storage() { if ( ! $this->storage ) { global $wpdb; $this->storage = new WPML_ST_Translations_File_Scan_Storage( $wpdb, new WPML_ST_Bulk_Strings_Insert( $wpdb ) ); } return $this->storage; } /** * @return WPML_ST_Translations_File_Dictionary */ private function create_dictionary() { if ( ! $this->dictionary ) { global $sitepress; /** @var WPML_ST_Translations_File_Dictionary_Storage_Table $table_storage */ $table_storage = make( WPML_ST_Translations_File_Dictionary_Storage_Table::class ); $st_upgrade = new WPML_ST_Upgrade( $sitepress ); if ( $st_upgrade->has_command_been_executed( 'WPML_ST_Upgrade_MO_Scanning' ) ) { $table_storage->add_hooks(); } $this->dictionary = new WPML_ST_Translations_File_Dictionary( $table_storage ); } return $this->dictionary; } /** * @return int */ private function get_scan_limit() { $limit = WPML_ST_Translations_File_Queue::DEFAULT_LIMIT; if ( defined( 'WPML_ST_MO_SCANNING_LIMIT' ) ) { $limit = WPML_ST_MO_SCANNING_LIMIT; } return $limit; } private function get_sitepress() { global $sitepress; return $sitepress; } private function get_wpml_wp_api() { $sitepress = $this->get_sitepress(); if ( ! $sitepress ) { return new WPML_WP_API(); } return $sitepress->get_wp_api(); } private function get_wpml_file() { if ( ! $this->wpml_file ) { $this->wpml_file = new WPML_File( $this->get_wpml_wp_api(), new WP_Filesystem_Direct( null ) ); } return $this->wpml_file; } /** * @return WPML_ST_Translations_File_Registration */ private function store_translation_files_info_on_db() { return new WPML_ST_Translations_File_Registration( $this->create_dictionary(), $this->get_wpml_file(), $this->get_aggregate_find_component(), $this->get_sitepress()->get_active_languages() ); } /** * @return WPML_ST_Translations_File_Component_Stats_Update_Hooks */ private function get_stats_update() { global $wpdb; return new WPML_ST_Translations_File_Component_Stats_Update_Hooks( new WPML_ST_Strings_Stats( $wpdb, $this->get_sitepress() ) ); } /** * @return WPML_ST_Translations_File_Component_Details */ private function get_aggregate_find_component() { if ( null === $this->find_aggregate ) { $debug_backtrace = new WPML_Debug_BackTrace(); $this->find_aggregate = new WPML_ST_Translations_File_Component_Details( new WPML_ST_Translations_File_Components_Find_Plugin( $debug_backtrace ), new WPML_ST_Translations_File_Components_Find_Theme( $debug_backtrace, $this->get_wpml_file() ), $this->get_wpml_file() ); } return $this->find_aggregate; } /** * @return WPML_ST_Translations_File_String_Status_Update */ private function get_string_status_update() { global $wpdb; $num_of_secondary_languages = count( $this->get_sitepress()->get_active_languages() ) - 1; $status_update = new WPML_ST_Translations_File_String_Status_Update( $num_of_secondary_languages, $wpdb ); $status_update->add_hooks(); return $status_update; } } translations-file-scan/wpml-st-translations-file-entry.php 0000755 00000011103 14720402445 0020034 0 ustar 00 <?php class WPML_ST_Translations_File_Entry { const NOT_IMPORTED = 'not_imported'; const IMPORTED = 'imported'; const PARTLY_IMPORTED = 'partly_imported'; const FINISHED = 'finished'; const SKIPPED = 'skipped'; const PATTERN_SEARCH_LANG_MO = '#[-]?([a-z]+[_A-Z]*)\.mo$#i'; const PATTERN_SEARCH_LANG_JSON = '#([a-z]+[_A-Z]*)-[-a-z0-9]+\.json$#i'; /** @var string */ private $path; /** @var string */ private $domain; /** @var int */ private $status; /** @var int */ private $imported_strings_count = 0; /** @var int */ private $last_modified; /** @var string */ private $component_type; /** @var string */ private $component_id; /** * @param string $path * @param string $domain * @param string $status */ public function __construct( $path, $domain, $status = self::NOT_IMPORTED ) { if ( ! is_string( $path ) ) { throw new InvalidArgumentException( 'MO File path must be string type' ); } if ( ! is_string( $domain ) ) { throw new InvalidArgumentException( 'MO File domain must be string type' ); } $this->path = $this->convert_to_relative_path( $path ); $this->domain = $domain; $this->validate_status( $status ); $this->status = $status; } /** * We can't rely on ABSPATH in out tests * * @param string $path * * @return string */ private function convert_to_relative_path( $path ) { $parts = explode( DIRECTORY_SEPARATOR, $this->fix_dir_separator( WP_CONTENT_DIR ) ); return str_replace( WP_CONTENT_DIR, end( $parts ), $path ); } /** * @return string */ public function get_path() { return $this->path; } public function get_full_path() { $wp_content_dir = $this->fix_dir_separator( WP_CONTENT_DIR ); $parts = explode( DIRECTORY_SEPARATOR, $wp_content_dir ); return str_replace( end( $parts ), $wp_content_dir, $this->path ); } /** * @return string */ public function get_path_hash() { return md5( $this->path ); } /** * @return string */ public function get_domain() { return $this->domain; } /** * @return int */ public function get_status() { return $this->status; } /** * @param string $status */ public function set_status( $status ) { $this->validate_status( $status ); $this->status = $status; } /** * @return int */ public function get_imported_strings_count() { return $this->imported_strings_count; } /** * @param int $imported_strings_count */ public function set_imported_strings_count( $imported_strings_count ) { $this->imported_strings_count = (int) $imported_strings_count; } /** * @return int */ public function get_last_modified() { return $this->last_modified; } /** * @param int $last_modified */ public function set_last_modified( $last_modified ) { $this->last_modified = (int) $last_modified; } public function __get( $name ) { if ( in_array( $name, array( 'path', 'domain', 'status', 'imported_strings_count', 'last_modified' ), true ) ) { return $this->$name; } if ( $name === 'path_md5' ) { return $this->get_path_hash(); } return null; } /** * It extracts locale from mo file path, examples * '/wp-content/languages/admin-pl_PL.mo' => 'pl' * '/wp-content/plugins/sitepress/sitepress-hr.mo' => 'hr' * * @return null|string * @throws RuntimeException */ public function get_file_locale() { return \WPML\Container\make( WPML_ST_Translations_File_Locale::class )->get( $this->get_path(), $this->get_domain() ); } /** * @return string */ public function get_component_type() { return $this->component_type; } /** * @param string $component_type */ public function set_component_type( $component_type ) { $this->component_type = $component_type; } /** * @return string */ public function get_component_id() { return $this->component_id; } /** * @param string $component_id */ public function set_component_id( $component_id ) { $this->component_id = $component_id; } /** * @param string $status */ private function validate_status( $status ) { $allowed_statuses = array( self::NOT_IMPORTED, self::IMPORTED, self::PARTLY_IMPORTED, self::FINISHED, self::SKIPPED, ); if ( ! in_array( $status, $allowed_statuses, true ) ) { throw new InvalidArgumentException( 'Status of MO file is invalid' ); } } /** * @param string $path * * @return string */ private function fix_dir_separator( $path ) { return ( '\\' === DIRECTORY_SEPARATOR ) ? str_replace( '/', '\\', $path ) : str_replace( '\\', '/', $path ); } public function get_extension() { return pathinfo( $this->path, PATHINFO_EXTENSION ); } } translations-file-scan/wpml-st-translations-file-scan-storage.php 0000755 00000004720 14720402445 0021270 0 ustar 00 <?php class WPML_ST_Translations_File_Scan_Storage { /** @var wpdb */ private $wpdb; /** @var WPML_ST_Bulk_Strings_Insert */ private $bulk_insert; /** * @param wpdb $wpdb * @param WPML_ST_Bulk_Strings_Insert $bulk_insert */ public function __construct( wpdb $wpdb, WPML_ST_Bulk_Strings_Insert $bulk_insert ) { $this->wpdb = $wpdb; $this->bulk_insert = $bulk_insert; } public function save( array $translations, $domain, $lang ) { $this->bulk_insert->insert_strings( $this->build_string_collection( $translations, $domain ) ); $string_translations = $this->build_string_translation_collection( $translations, $lang, $this->get_string_maps( $domain ) ); $this->bulk_insert->insert_string_translations( $string_translations ); } /** * @param WPML_ST_Translations_File_Translation[] $translations * @param string $domain * * @return WPML_ST_Models_String[] */ private function build_string_collection( array $translations, $domain ) { $result = array(); foreach ( $translations as $translation ) { $result[] = new WPML_ST_Models_String( 'en', $domain, $translation->get_context(), $translation->get_original(), ICL_TM_NOT_TRANSLATED ); } return $result; } /** * @param string $domain * * @return array */ private function get_string_maps( $domain ) { $sql = " SELECT id, value, gettext_context FROM {$this->wpdb->prefix}icl_strings WHERE context = %s "; $rowset = $this->wpdb->get_results( $this->wpdb->prepare( $sql, $domain ) ); $result = array(); foreach ( $rowset as $row ) { $result[ $row->value ][ $row->gettext_context ] = $row->id; } return $result; } /** * @param WPML_ST_Translations_File_Translation[] $translations * @param string $lang * @param array $value_id_map * * @return WPML_ST_Models_String_Translation[] */ private function build_string_translation_collection( array $translations, $lang, $value_id_map ) { $result = array(); foreach ( $translations as $translation ) { if ( ! isset( $value_id_map[ $translation->get_original() ] ) ) { continue; } $result[] = new WPML_ST_Models_String_Translation( $value_id_map[ $translation->get_original() ][ $translation->get_context() ], $lang, ICL_TM_NOT_TRANSLATED, null, $translation->get_translation() ); } return $result; } } translations-file-scan/charset-validation/wpml-st-translations-file-scan-charset-validation.php 0000755 00000000177 14720402445 0027170 0 ustar 00 <?php interface WPML_ST_Translations_File_Scan_Charset_Validation { /** * @return bool */ public function is_valid(); } translations-file-scan/charset-validation/wpml-st-translations-file-scan-db-table-list.php 0000755 00000000571 14720402445 0026030 0 ustar 00 <?php class WPML_ST_Translations_File_Scan_Db_Table_List { /** @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @return array */ public function get_tables() { return array( $this->wpdb->prefix . 'icl_strings', $this->wpdb->prefix . 'icl_string_translations', ); } } charset-validation/wpml-st-translations-file-scan-db-charset-validation-factory.php 0000755 00000000752 14720402445 0031140 0 ustar 00 translations-file-scan <?php class WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory { /** @var wpdb $wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } public function create() { $charset_validator = new WPML_ST_Translations_File_Scan_Db_Charset_Validation( $this->wpdb, new WPML_ST_Translations_File_Scan_Db_Table_List( $this->wpdb ) ); return $charset_validator->is_valid() ? null : new WPML_ST_Translations_File_Unicode_Characters_Filter(); } } translations-file-scan/charset-validation/wpml-st-translations-file-scan-db-charset-validation.php 0000755 00000002452 14720402445 0027551 0 ustar 00 <?php class WPML_ST_Translations_File_Scan_Db_Charset_Validation implements WPML_ST_Translations_File_Scan_Charset_Validation { /** @var wpdb */ private $wpdb; /** @var WPML_ST_Translations_File_Scan_Db_Table_List */ private $table_list; /** * @param wpdb $wpdb * @param WPML_ST_Translations_File_Scan_Db_Table_List $table_list */ public function __construct( wpdb $wpdb, WPML_ST_Translations_File_Scan_Db_Table_List $table_list ) { $this->wpdb = $wpdb; $this->table_list = $table_list; } /** * @return bool */ public function is_valid() { if ( ! $this->wpdb->has_cap( 'utf8mb4' ) ) { return false; } $chunks = array(); foreach ( $this->table_list->get_tables() as $table ) { $chunks[] = $this->get_unique_collation_list_from_table( $table ); } $collations = call_user_func_array( 'array_merge', $chunks ); $collations = array_unique( $collations ); return count( $collations ) < 2; } /** * @param string $table * * @return array */ private function get_unique_collation_list_from_table( $table ) { /** @var array $columns */ $columns = $this->wpdb->get_results( "SHOW FULL COLUMNS FROM `{$table}` WHERE Collation LIKE 'utf8mb4%'" ); return array_unique( wp_list_pluck( $columns, 'Collation' ) ); } } translations-file-scan/components/wpml-st-translations-file-components-find-plugin.php 0000755 00000004203 14720402445 0025462 0 ustar 00 <?php class WPML_ST_Translations_File_Components_Find_Plugin implements WPML_ST_Translations_File_Components_Find { /** @var WPML_Debug_BackTrace */ private $debug_backtrace; /** @var string */ private $plugin_dir; /** @var array */ private $plugin_ids; /** * @param WPML_Debug_BackTrace $debug_backtrace */ public function __construct( WPML_Debug_BackTrace $debug_backtrace ) { $this->debug_backtrace = $debug_backtrace; $this->plugin_dir = realpath( WPML_PLUGINS_DIR ); } public function find_id( $file ) { $directory = $this->find_plugin_directory( $file ); if ( ! $directory ) { return null; } return $this->get_plugin_id_by_directory( $directory ); } private function find_plugin_directory( $file ) { if ( false !== strpos( $file, $this->plugin_dir ) ) { return $this->extract_plugin_directory( $file ); } return $this->find_plugin_directory_in_backtrace(); } private function find_plugin_directory_in_backtrace() { $file = $this->find_file_in_backtrace(); if ( ! $file ) { return null; } return $this->extract_plugin_directory( $file ); } private function find_file_in_backtrace() { $stack = $this->debug_backtrace->get_backtrace(); foreach ( $stack as $call ) { if ( isset( $call['function'] ) && 'load_plugin_textdomain' === $call['function'] ) { return $call['file']; } } return null; } /** * @param string $file_path * * @return string */ private function extract_plugin_directory( $file_path ) { $dir = ltrim( str_replace( $this->plugin_dir, '', $file_path ), DIRECTORY_SEPARATOR ); $dir = explode( DIRECTORY_SEPARATOR, $dir ); return trim( $dir[0], DIRECTORY_SEPARATOR ); } /** * @param string $directory * * @return string|null */ private function get_plugin_id_by_directory( $directory ) { foreach ( $this->get_plugin_ids() as $plugin_id ) { if ( 0 === strpos( $plugin_id, $directory . '/' ) ) { return $plugin_id; } } return null; } /** * @return string[] */ private function get_plugin_ids() { if ( null === $this->plugin_ids ) { $this->plugin_ids = array_keys( get_plugins() ); } return $this->plugin_ids; } } translations-file-scan/components/wpml-st-translations-file-components-find-theme.php 0000755 00000003340 14720402445 0025267 0 ustar 00 <?php class WPML_ST_Translations_File_Components_Find_Theme implements WPML_ST_Translations_File_Components_Find { /** @var WPML_Debug_BackTrace */ private $debug_backtrace; /** @var WPML_File $file */ private $file; /** @var string */ private $theme_dir; /** * @param WPML_Debug_BackTrace $debug_backtrace * @param WPML_File $file */ public function __construct( WPML_Debug_BackTrace $debug_backtrace, WPML_File $file ) { $this->debug_backtrace = $debug_backtrace; $this->file = $file; $this->theme_dir = $this->file->fix_dir_separator( get_theme_root() ); } public function find_id( $file ) { return $this->find_theme_directory( $file ); } private function find_theme_directory( $file ) { if ( false !== strpos( $file, $this->theme_dir ) ) { return $this->extract_theme_directory( $file ); } return $this->find_theme_directory_in_backtrace(); } private function find_theme_directory_in_backtrace() { $file = $this->find_file_in_backtrace(); if ( ! $file ) { return null; } return $this->extract_theme_directory( $file ); } private function find_file_in_backtrace() { $stack = $this->debug_backtrace->get_backtrace(); foreach ( $stack as $call ) { if ( isset( $call['function'] ) && 'load_theme_textdomain' === $call['function'] ) { return $call['file']; } } return null; } /** * @param string $file_path * * @return string */ private function extract_theme_directory( $file_path ) { $file_path = $this->file->fix_dir_separator( $file_path ); $dir = ltrim( str_replace( $this->theme_dir, '', $file_path ), DIRECTORY_SEPARATOR ); $dir = explode( DIRECTORY_SEPARATOR, $dir ); return trim( $dir[0], DIRECTORY_SEPARATOR ); } } translations-file-scan/components/wpml-st-translations-file-component-details.php 0000755 00000006404 14720402445 0024515 0 ustar 00 <?php class WPML_ST_Translations_File_Component_Details { /** @var WPML_ST_Translations_File_Components_Find[] */ private $finders; /** @var WPML_File $file */ private $file; /** @var string */ private $plugin_dir; /** @var string */ private $theme_dir; /** @var string */ private $languages_plugin_dir; /** @var string */ private $languages_theme_dir; /** @var array */ private $cache = array(); /** * @param WPML_ST_Translations_File_Components_Find_Plugin $plugin_id_finder * @param WPML_ST_Translations_File_Components_Find_Theme $theme_id_finder * @param WPML_File $wpml_file */ public function __construct( WPML_ST_Translations_File_Components_Find_Plugin $plugin_id_finder, WPML_ST_Translations_File_Components_Find_Theme $theme_id_finder, WPML_File $wpml_file ) { $this->finders['plugin'] = $plugin_id_finder; $this->finders['theme'] = $theme_id_finder; $this->file = $wpml_file; $this->theme_dir = $this->file->fix_dir_separator( get_theme_root() ); $this->plugin_dir = $this->file->fix_dir_separator( (string) realpath( WPML_PLUGINS_DIR ) ); $wp_content_dir = realpath( WP_CONTENT_DIR ); $this->languages_plugin_dir = $this->file->fix_dir_separator( $wp_content_dir . '/languages/plugins' ); $this->languages_theme_dir = $this->file->fix_dir_separator( $wp_content_dir . '/languages/themes' ); } /** * @param string $file_full_path * * @return array */ public function find_details( $file_full_path ) { $file_full_path = $this->file->fix_dir_separator( $file_full_path ); if ( ! isset( $this->cache[ $file_full_path ] ) ) { $type = $this->find_type( $file_full_path ); if ( 'other' === $type ) { $this->cache[ $file_full_path ] = array( $type, null ); } else { $this->cache[ $file_full_path ] = array( $type, $this->find_id( $type, $file_full_path ) ); } } return $this->cache[ $file_full_path ]; } /** * @param string $component_type * @param string $file_full_path * * @return null|string */ public function find_id( $component_type, $file_full_path ) { if ( ! isset( $this->finders[ $component_type ] ) ) { return null; } return $this->finders[ $component_type ]->find_id( $file_full_path ); } /** * @param string $file_full_path * * @return string */ public function find_type( $file_full_path ) { if ( $this->theme_dir && ( $this->string_contains( $file_full_path, $this->theme_dir ) || $this->string_contains( $file_full_path, $this->languages_theme_dir ) ) ) { return 'theme'; } if ( $this->string_contains( $file_full_path, $this->plugin_dir ) || $this->string_contains( $file_full_path, $this->languages_plugin_dir ) ) { return 'plugin'; } return 'other'; } /** * @param string $file_full_path * * @return bool */ public function is_component_active( $file_full_path ) { list( $type, $id ) = $this->find_details( $file_full_path ); if ( 'other' === $type ) { return true; } if ( ! $id ) { return false; } if ( 'plugin' === $type ) { return is_plugin_active( $id ); } else { return get_stylesheet_directory() === get_theme_root() . '/' . $id; } } private function string_contains( $haystack, $needle ) { return false !== strpos( $haystack, $needle ); } } translations-file-scan/components/wpml-st-translations-file-components-find.php 0000755 00000000240 14720402445 0024163 0 ustar 00 <?php interface WPML_ST_Translations_File_Components_Find { /** * @param string $file * * @return string|null */ public function find_id( $file ); } translations-file-scan/wpml-st-translations-file-queue.php 0000755 00000012255 14720402445 0020030 0 ustar 00 <?php use WPML\ST\TranslationFile\EntryQueries; use WPML\ST\TranslationFile\QueueFilter; class WPML_ST_Translations_File_Queue { const DEFAULT_LIMIT = 20000; const TIME_LIMIT = 10; // seconds const LOCK_FIELD = '_wpml_st_file_scan_in_progress'; /** @var WPML_ST_Translations_File_Dictionary */ private $file_dictionary; /** @var WPML_ST_Translations_File_Scan */ private $file_scan; /** @var WPML_ST_Translations_File_Scan_Storage */ private $file_scan_storage; /** @var WPML_Language_Records */ private $language_records; /** @var int */ private $limit; /** @var WPML_Transient */ private $transient; /** * @param WPML_ST_Translations_File_Dictionary $file_dictionary * @param WPML_ST_Translations_File_Scan $file_scan * @param WPML_ST_Translations_File_Scan_Storage $file_scan_storage * @param WPML_Language_Records $language_records * @param int $limit * @param WPML_Transient $transient */ public function __construct( WPML_ST_Translations_File_Dictionary $file_dictionary, WPML_ST_Translations_File_Scan $file_scan, WPML_ST_Translations_File_Scan_Storage $file_scan_storage, WPML_Language_Records $language_records, $limit, WPML_Transient $transient ) { $this->file_dictionary = $file_dictionary; $this->file_scan = $file_scan; $this->file_scan_storage = $file_scan_storage; $this->language_records = $language_records; $this->limit = $limit; $this->transient = $transient; } /** * @param QueueFilter|null $queueFilter */ public function import( QueueFilter $queueFilter = null ) { $this->file_dictionary->clear_skipped(); $files = $this->file_dictionary->get_not_imported_files(); if ( count( $files ) ) { $this->lock(); $start_time = time(); $imported = 0; foreach ( $files as $file ) { if ( $imported >= $this->limit || time() - $start_time > self::TIME_LIMIT ) { break; } if ( ! $queueFilter || $queueFilter->isSelected( $file ) ) { $translations = $this->file_scan->load_translations( $file->get_full_path() ); try { $number_of_translations = count( $translations ); if ( ! $number_of_translations ) { throw new RuntimeException( 'File is empty' ); } $translations = $this->constrain_translations_number( $translations, $file->get_imported_strings_count(), $this->limit - $imported ); $imported += $imported_in_file = count( $translations ); $this->file_scan_storage->save( $translations, $file->get_domain(), $this->map_language_code( $file->get_file_locale() ) ); $file->set_imported_strings_count( $file->get_imported_strings_count() + $imported_in_file ); if ( $file->get_imported_strings_count() >= $number_of_translations ) { $file->set_status( WPML_ST_Translations_File_Entry::IMPORTED ); } else { $file->set_status( WPML_ST_Translations_File_Entry::PARTLY_IMPORTED ); } } catch ( WPML_ST_Bulk_Strings_Insert_Exception $e ) { $file->set_status( WPML_ST_Translations_File_Entry::PARTLY_IMPORTED ); break; } catch ( Exception $e ) { $file->set_status( WPML_ST_Translations_File_Entry::IMPORTED ); } } else { $file->set_status( WPML_ST_Translations_File_Entry::SKIPPED ); } $this->file_dictionary->save( $file ); do_action( 'wpml_st_translations_file_post_import', $file ); } $this->unlock(); } } /** * @param string $locale * * @return string */ private function map_language_code( $locale ) { $language_code = $this->language_records->get_language_code( $locale ); if ( $language_code ) { return $language_code; } return $locale; } /** * @return bool */ public function is_completed() { return 0 === count( $this->file_dictionary->get_not_imported_files() ) && 0 < count( $this->file_dictionary->get_imported_files() ); } /** * @return string[] */ public function get_processed() { return wp_list_pluck( $this->file_dictionary->get_imported_files(), 'path' ); } /** * @return bool */ public function is_processing() { return 0 !== count( $this->file_dictionary->get_not_imported_files() ); } /** * @return int */ public function get_pending() { return count( $this->file_dictionary->get_not_imported_files() ); } public function mark_as_finished() { foreach ( $this->file_dictionary->get_imported_files() as $file ) { $file->set_status( WPML_ST_Translations_File_Entry::FINISHED ); $this->file_dictionary->save( $file ); } } /** * @param array $translations * @param int $offset * @param int $limit * * @return array */ private function constrain_translations_number( array $translations, $offset, $limit ) { if ( $limit > count( $translations ) ) { return $translations; } return array_slice( $translations, $offset, $limit ); } public function is_locked() { return (bool) $this->transient->get( self::LOCK_FIELD ); } private function lock() { $this->transient->set( self::LOCK_FIELD, 1, MINUTE_IN_SECONDS * 5 ); } private function unlock() { $this->transient->delete( self::LOCK_FIELD ); } } translations-file-scan/EntryQueries.php 0000755 00000003235 14720402445 0014304 0 ustar 00 <?php namespace WPML\ST\TranslationFile; class EntryQueries { /** * @param string $type * * @return \Closure */ public static function isType( $type ) { return function ( \WPML_ST_Translations_File_Entry $entry ) use ( $type ) { return $entry->get_component_type() === $type; }; } /** * @param string $extension * * @return \Closure */ public static function isExtension( $extension ) { return function ( \WPML_ST_Translations_File_Entry $file ) use ( $extension ) { return $file->get_extension() === $extension; }; } /** * @return \Closure */ public static function getResourceName() { return function ( \WPML_ST_Translations_File_Entry $entry ) { $function = 'get' . ucfirst( $entry->get_component_type() ) . 'Name'; return self::$function( $entry ); }; } /** * @return \Closure */ public static function getDomain() { return function( \WPML_ST_Translations_File_Entry $file ) { return $file->get_domain(); }; } /** * @param \WPML_ST_Translations_File_Entry $entry * * @return string */ private static function getPluginName( \WPML_ST_Translations_File_Entry $entry ) { $data = get_plugin_data( WPML_PLUGINS_DIR . '/' . $entry->get_component_id(), false, false ); return $data['Name']; } /** * @param \WPML_ST_Translations_File_Entry $entry * * @return string */ private static function getThemeName( \WPML_ST_Translations_File_Entry $entry ) { return $entry->get_component_id(); } /** * @param \WPML_ST_Translations_File_Entry $entry * * @return mixed|string|void */ private static function getOtherName( \WPML_ST_Translations_File_Entry $entry ) { return 'WordPress'; } } translations-file-scan/QueueFilter.php 0000755 00000001752 14720402445 0014101 0 ustar 00 <?php namespace WPML\ST\TranslationFile; use WPML_ST_Translations_File_Entry; class QueueFilter { /** @var array */ private $plugins; /** @var array */ private $themes; /** @var array */ private $other; /** * @param array $plugins * @param array $themes * @param array $other */ public function __construct( array $plugins, array $themes, array $other ) { $this->plugins = $plugins; $this->themes = $themes; $this->other = $other; } /** * @param WPML_ST_Translations_File_Entry $file * * @return bool */ public function isSelected( WPML_ST_Translations_File_Entry $file ) { $getResourceName = EntryQueries::getResourceName(); $resourceName = $getResourceName( $file ); switch ( $file->get_component_type() ) { case 'plugin': return in_array( $resourceName, $this->plugins ); case 'theme': return in_array( $resourceName, $this->themes ); case 'other': return in_array( $resourceName, $this->other ); } return false; } } translations-file-scan/UI/InstalledComponents.php 0000755 00000002124 14720402445 0016143 0 ustar 00 <?php /** * @author OnTheGo Systems */ namespace WPML\ST\MO\Scan\UI; use WPML\Collect\Support\Collection; use WPML_ST_Translations_File_Entry; class InstalledComponents { /** * @param Collection $components Collection of WPML_ST_Translations_File_Entry objects. * * @return Collection */ public static function filter( Collection $components ) { return $components ->reject( self::isPluginMissing() ) ->reject( self::isThemeMissing() ); } /** * WPML_ST_Translations_File_Entry -> bool * * @return \Closure */ public static function isPluginMissing() { return function( WPML_ST_Translations_File_Entry $entry ) { return 'plugin' === $entry->get_component_type() && ! is_readable( WPML_PLUGINS_DIR . '/' . $entry->get_component_id() ); }; } /** * WPML_ST_Translations_File_Entry -> bool * * @return \Closure */ public static function isThemeMissing() { return function( WPML_ST_Translations_File_Entry $entry ) { return 'theme' === $entry->get_component_type() && ! wp_get_theme( $entry->get_component_id() )->exists(); }; } } translations-file-scan/UI/Factory.php 0000755 00000007041 14720402445 0013570 0 ustar 00 <?php namespace WPML\ST\MO\Scan\UI; use WPML\Collect\Support\Collection; use WPML\ST\MO\Generate\DomainsAndLanguagesRepository; use WPML\ST\MO\Generate\Process\ProcessFactory; use WPML_ST_Translations_File_Dictionary_Storage_Table; use WPML_ST_Translations_File_Dictionary; use WPML\WP\OptionManager; use function WPML\Container\make; use function WPML\FP\partial; class Factory implements \IWPML_Backend_Action_Loader, \IWPML_Deferred_Action_Loader { const WPML_VERSION_INTRODUCING_ST_MO_FLOW = '4.3.0'; const OPTION_GROUP = 'ST-MO'; const IGNORE_WPML_VERSION = 'ignore-wpml-version'; /** * @return callable|null * @throws \WPML\Auryn\InjectionException */ public function create() { if ( current_user_can( 'manage_options' ) && function_exists( 'wpml_is_rest_enabled' ) && wpml_is_rest_enabled() ) { global $sitepress; $wp_api = $sitepress->get_wp_api(); $pre_gen_dissmissed = self::isDismissed(); $st_page = $wp_api->is_core_page( 'theme-localization.php' ) || $wp_api->is_string_translation_page(); if ( ( $st_page || ( ! $st_page && ! $pre_gen_dissmissed ) ) && ( DomainsAndLanguagesRepository::hasTranslationFilesTable() || is_network_admin() ) ) { $files_to_import = $st_page ? $this->getFilesToImport() : wpml_collect( [] ); $domains_to_pre_generate_count = self::getDomainsToPreGenerateCount(); if ( $files_to_import->count() || $domains_to_pre_generate_count || isset( $_GET['search'] ) ) { return partial( [ UI::class, 'add_hooks' ], Model::provider( $files_to_import, $domains_to_pre_generate_count, $st_page, is_network_admin() ), $st_page ); } } } return null; } public function get_load_action() { return 'init'; } /** * @return bool * @throws \WPML\Auryn\InjectionException */ public static function isDismissed() { return make( OptionManager::class )->get( self::OPTION_GROUP, 'pregen-dismissed', false ); } /** * @return Collection * @throws \WPML\Auryn\InjectionException */ private function getFilesToImport() { /** @var WPML_ST_Translations_File_Dictionary $file_dictionary */ $file_dictionary = make( WPML_ST_Translations_File_Dictionary::class, [ 'storage' => WPML_ST_Translations_File_Dictionary_Storage_Table::class ] ); $file_dictionary->clear_skipped(); return InstalledComponents::filter( wpml_collect( $file_dictionary->get_not_imported_files() ) ); } /** * @return bool */ private static function isPreGenerationRequired() { return self::shouldIgnoreWpmlVersion() || self::wpmlStartVersionBeforeMOFlow(); } /** * @return bool */ private static function wpmlStartVersionBeforeMOFlow() { return version_compare( get_option( \WPML_Installation::WPML_START_VERSION_KEY, '0.0.0' ), self::WPML_VERSION_INTRODUCING_ST_MO_FLOW, '<' ); } /** * @return int * @throws \WPML\Auryn\InjectionException */ public static function getDomainsToPreGenerateCount() { return self::isPreGenerationRequired() ? make( ProcessFactory::class )->create()->getPagesCount() : 0; } /** * @return bool */ public static function shouldIgnoreWpmlVersion() { return make( OptionManager::class )->get( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, false ); } public static function ignoreWpmlVersion() { make( OptionManager::class )->set( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, true ); } public static function clearIgnoreWpmlVersion() { make( OptionManager::class )->set( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, false ); } } translations-file-scan/UI/Model.php 0000755 00000003411 14720402445 0013216 0 ustar 00 <?php namespace WPML\ST\MO\Scan\UI; use WPML\Collect\Support\Collection; use WPML\ST\MO\Generate\MultiSite\Condition; use WPML\ST\TranslationFile\EntryQueries; class Model { /** * @param Collection $files_to_scan * @param int $domains_to_pre_generate_count * @param bool $is_st_page * @param bool $is_network_admin * * @return \Closure */ public static function provider( Collection $files_to_scan, $domains_to_pre_generate_count, $is_st_page, $is_network_admin ) { return function () use ( $files_to_scan, $domains_to_pre_generate_count, $is_st_page ) { return [ 'files_to_scan' => [ 'count' => $files_to_scan->count(), 'plugins' => self::filterFilesByType( $files_to_scan, 'plugin' ), 'themes' => self::filterFilesByType( $files_to_scan, 'theme' ), 'other' => self::filterFilesByType( $files_to_scan, 'other' ), ], 'domains_to_pre_generate_count' => $domains_to_pre_generate_count, 'file_path' => WP_LANG_DIR . '/wpml', 'is_st_page' => $is_st_page, 'admin_texts_url' => 'admin.php?page=' . WPML_ST_FOLDER . '/menu/string-translation.php&trop=1', 'is_search' => isset( $_GET['search'] ), 'run_ror_all_sites' => ( new Condition() )->shouldRunWithAllSites(), ]; }; } /** * @param Collection $files_to_scan * @param string $type * * @return array */ private static function filterFilesByType( Collection $files_to_scan, $type ) { return $files_to_scan->filter( EntryQueries::isType( $type ) ) ->map( EntryQueries::getResourceName() ) ->unique() ->values() ->toArray(); } } translations-file-scan/UI/UI.php 0000755 00000001324 14720402445 0012474 0 ustar 00 <?php namespace WPML\ST\MO\Scan\UI; use \WPML\ST\WP\App\Resources; use WPML\LIB\WP\Hooks as WPHooks; class UI { public static function add_hooks( callable $getModel, $isSTPage ) { WPHooks::onAction( 'admin_enqueue_scripts' ) ->then( $getModel ) ->then( [ self::class, 'localize' ] ) ->then( Resources::enqueueApp( 'mo-scan' ) ); if ( ! $isSTPage ) { WPHooks::onAction( [ 'admin_notices', 'network_admin_notices' ] ) ->then( [ self::class, 'add_admin_notice' ] ); } } public static function add_admin_notice() { ?> <div id="wpml-mo-scan-st-page"></div> <?php } public static function localize( $model ) { return [ 'name' => 'wpml_mo_scan_ui_files', 'data' => $model, ]; } } translations-file-scan/wpml-st-translations-file-translation.php 0000755 00000001343 14720402445 0021236 0 ustar 00 <?php class WPML_ST_Translations_File_Translation { /** @var string */ private $original; /** @var string */ private $translation; /** @var string */ private $context; /** * @param string $original * @param string $translation * @param string $context */ public function __construct( $original, $translation, $context = '' ) { $this->original = $original; $this->translation = $translation; $this->context = $context; } /** * @return string */ public function get_original() { return $this->original; } /** * @return string */ public function get_translation() { return $this->translation; } /** * @return string */ public function get_context() { return $this->context; } } translations-file-scan/wpml-st-translations-file-component-stats-update-hooks.php 0000755 00000001317 14720402445 0024440 0 ustar 00 <?php class WPML_ST_Translations_File_Component_Stats_Update_Hooks { /** @var WPML_ST_Strings_Stats */ private $string_stats; /** * @param WPML_ST_Strings_Stats $string_stats */ public function __construct( WPML_ST_Strings_Stats $string_stats ) { $this->string_stats = $string_stats; } public function add_hooks() { add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_stats' ), 10, 1 ); } /** * @param WPML_ST_Translations_File_Entry $file */ public function update_stats( WPML_ST_Translations_File_Entry $file ) { if ( $file->get_component_id() ) { $this->string_stats->update( $file->get_component_id(), $file->get_component_type(), $file->get_domain() ); } } } translations-file-scan/translations-file/wpml-st-translations-file-locale.php 0000755 00000007370 14720402445 0023603 0 ustar 00 <?php class WPML_ST_Translations_File_Locale { const PATTERN_SEARCH_LANG_JSON = '#DOMAIN_PLACEHOLDER(LOCALES_PLACEHOLDER)-[-_a-z0-9]+\.json$#i'; /** @var \SitePress */ private $sitepress; /** @var \WPML_Locale */ private $locale; /** * @param SitePress $sitepress * @param WPML_Locale $locale */ public function __construct( SitePress $sitepress, WPML_Locale $locale ) { $this->sitepress = $sitepress; $this->locale = $locale; } /** * It extracts language code from mo file path, examples * '/wp-content/languages/admin-pl_PL.mo' => 'pl' * '/wp-content/plugins/sitepress/sitepress-hr.mo' => 'hr' * '/wp-content/languages/fr_FR-4gh5e6d3g5s33d6gg51zas2.json' => 'fr_FR' * '/wp-content/plugins/my-plugin/languages/-my-plugin-fr_FR-my-handler.json' => 'fr_FR' * * @param string $filepath * @param string $domain * * @return string */ public function get( $filepath, $domain ) { switch ( $this->get_extension( $filepath ) ) { case 'mo': return $this->get_from_mo_file( $filepath ); case 'json': return $this->get_from_json_file( $filepath, $domain ); default: return ''; } } /** * @param string $filepath * * @return string|null */ private function get_extension( $filepath ) { return wpml_collect( pathinfo( $filepath ) )->get( 'extension', null ); } /** * @param string $filepath * * @return string */ private function get_from_mo_file( $filepath ) { return $this->get_locales() ->first( function ( $locale ) use ( $filepath ) { return strpos( $filepath, $locale . '.mo' ); }, '' ); } /** * @param string $filepath * @param string $domain * * @return string */ private function get_from_json_file( $filepath, $domain ) { $original_domain = $this->get_original_domain_for_json( $filepath, $domain ); $domain_replace = 'default' === $original_domain ? '' : $original_domain . '-'; $locales = $this->get_locales()->implode( '|' ); $searches['native-file'] = '#' . $domain_replace . '(' . $locales . ')-[-_a-z0-9]+\.json$#i'; $searches['wpml-file'] = '#' . $domain . '-(' . $locales . ').json#i'; foreach ( $searches as $search ) { if ( preg_match( $search, $filepath, $matches ) && isset( $matches[1] ) ) { return $matches[1]; } } return ''; } /** * We need the original domain name to refine the regex pattern. * Unfortunately, the domain is concatenated with the script handler * in the import queue table. That's why we need to retrieve the original * domain from the registration domain and the filepath. * * @param string $filepath * @param string $domain * * @return string */ private function get_original_domain_for_json( $filepath, $domain ) { $filename = basename( $filepath ); /** * Case of WP JED files: * - filename: de_DE-73f9977556584a369800e775b48f3dbe.json * - domain: default-some-script * => original_domain: default */ if ( 0 === strpos( $domain, 'default' ) && 0 !== strpos( $filename, 'default' ) ) { return 'default'; } /** * Case of 3rd part JED files: * - filename: plugin-domain-de_DE-script-handler.json * - domain: plugin-domain-script-handler * => original_domain: plugin-domain */ $filename_parts = explode( '-', $filename ); $domain_parts = explode( '-', $domain ); $original_domain_parts = array(); foreach ( $domain_parts as $i => $part ) { if ( $filename_parts[ $i ] !== $part ) { break; } $original_domain_parts[] = $part; } return implode( '-', $original_domain_parts ); } /** * @return \WPML\Collect\Support\Collection */ private function get_locales() { return \wpml_collect( $this->sitepress->get_active_languages() ) ->keys() ->map( [ $this->locale, 'get_locale' ] ); } } translations-file-scan/translations-file/wpml-st-translations-file-mo.php 0000755 00000002125 14720402445 0022750 0 ustar 00 <?php class WPML_ST_Translations_File_MO implements IWPML_ST_Translations_File { /** @var string $filepath */ private $filepath; /** * @param string $filepath */ public function __construct( $filepath ) { $this->filepath = $filepath; } /** * @return WPML_ST_Translations_File_Translation[] */ public function get_translations() { $translations = array(); $mo = new MO(); $pomo_reader = new POMO_CachedFileReader( $this->filepath ); // @see wpmldev-1856 /** @phpstan-ignore-next-line */ $mo->import_from_reader( $pomo_reader ); foreach ( $mo->entries as $str => $v ) { $str = str_replace( "\n", '\n', $v->singular ); $translations[] = new WPML_ST_Translations_File_Translation( $str, $v->translations[0], $v->context ); if ( $v->is_plural ) { $str = str_replace( "\n", '\n', $v->plural ); $translation = ! empty( $v->translations[1] ) ? $v->translations[1] : $v->translations[0]; $translations[] = new WPML_ST_Translations_File_Translation( $str, $translation, $v->context ); } } return $translations; } } translations-file-scan/translations-file/iwpml-st-translations-file.php 0000755 00000000224 14720402445 0022506 0 ustar 00 <?php interface IWPML_ST_Translations_File { /** * @return WPML_ST_Translations_File_Translation[] */ public function get_translations(); } translations-file-scan/translations-file/wpml-st-translations-file-jed.php 0000755 00000004230 14720402445 0023076 0 ustar 00 <?php class WPML_ST_Translations_File_JED implements IWPML_ST_Translations_File { const EMPTY_PROPERTY_NAME = '_empty_'; const DECODED_EOT_CHAR = '"\u0004"'; const PLURAL_SUFFIX_PATTERN = ' [plural %d]'; /** @var string $filepath */ private $filepath; /** @var string $decoded_eot_char */ private $decoded_eot_char; public function __construct( $filepath ) { $this->filepath = $filepath; $this->decoded_eot_char = json_decode( self::DECODED_EOT_CHAR ); } /** * @return WPML_ST_Translations_File_Translation[] */ public function get_translations() { $translations = array(); $data = json_decode( (string) file_get_contents( $this->filepath ) ); if ( isset( $data->locale_data->messages ) ) { $entries = (array) $data->locale_data->messages; unset( $entries[ self::EMPTY_PROPERTY_NAME ] ); foreach ( $entries as $str => $str_data ) { $str_data = (array) $str_data; if ( ! isset( $str_data[0] ) ) { continue; } list( $str, $context ) = $this->get_string_and_context( $str ); $count_translations = count( $str_data ); $translations[] = new WPML_ST_Translations_File_Translation( $str, $str_data[0], $context ); if ( $count_translations > 1 ) { /** * The strings coming after the first element are the plural translations. * As we don't have the information about the original plural in the JED file, * we will add a suffix to the original singular string. */ for ( $i = 1; $i < $count_translations; $i++ ) { $plural_str = $str . sprintf( self::PLURAL_SUFFIX_PATTERN, $i ); $translations[] = new WPML_ST_Translations_File_Translation( $plural_str, $str_data[ $i ], $context ); } } } } return $translations; } /** * The context is the first part of the string separated with the EOT char (\u0004) * * @param string $string * * @return array */ private function get_string_and_context( $string ) { $context = ''; $parts = explode( $this->decoded_eot_char, $string ); if ( $parts && count( $parts ) > 1 ) { $context = $parts[0]; $string = $parts[1]; } return array( $string, $context ); } } translations-file-scan/wpml-st-translations-file-registration.php 0000755 00000013127 14720402445 0021415 0 ustar 00 <?php use WPML\Element\API\Languages; use WPML\FP\Fns; class WPML_ST_Translations_File_Registration { const PATH_PATTERN_SEARCH_MO = '#(-)?([a-z]+)([_A-Z]*)\.mo$#i'; const PATH_PATTERN_REPLACE_MO = '${1}%s.mo'; const PATH_PATTERN_SEARCH_JSON = '#(DOMAIN_PLACEHOLDER)([a-z]+)([_A-Z]*)(-[-_a-z0-9]+)\.json$#i'; const PATH_PATTERN_REPLACE_JSON = '${1}%s${4}.json'; /** @var WPML_ST_Translations_File_Dictionary */ private $file_dictionary; /** @var WPML_File */ private $wpml_file; /** @var WPML_ST_Translations_File_Component_Details */ private $components_find; /** @var array */ private $active_languages; /** @var array */ private $cache = array(); /** @var callable - string->string */ private $getWPLocale; /** * @param WPML_ST_Translations_File_Dictionary $file_dictionary * @param WPML_File $wpml_file * @param WPML_ST_Translations_File_Component_Details $components_find * @param array $active_languages */ public function __construct( WPML_ST_Translations_File_Dictionary $file_dictionary, WPML_File $wpml_file, WPML_ST_Translations_File_Component_Details $components_find, array $active_languages ) { $this->file_dictionary = $file_dictionary; $this->wpml_file = $wpml_file; $this->components_find = $components_find; $this->active_languages = $active_languages; $this->getWPLocale = Fns::memorize( Languages::getWPLocale() ); } public function add_hooks() { add_filter( 'override_load_textdomain', array( $this, 'cached_save_mo_file_info' ), 11, 3 ); add_filter( 'pre_load_script_translations', array( $this, 'add_json_translations_to_import_queue' ), 10, 4 ); } /** * @param bool $override * @param string $domain * @param string $mo_file_path * * @return bool */ public function cached_save_mo_file_info( $override, $domain, $mo_file_path ) { if ( ! isset( $this->cache[ $mo_file_path ] ) ) { $this->cache[ $mo_file_path ] = $this->save_file_info( $domain, $domain, $mo_file_path ); } return $override; } /** * @param string|false $translations translations in the JED format * @param string|false $file * @param string $handle * @param string $original_domain * * @return string|false */ public function add_json_translations_to_import_queue( $translations, $file, $handle, $original_domain ) { if ( $file && ! isset( $this->cache[ $file ] ) ) { $registration_domain = WPML_ST_JED_Domain::get( $original_domain, $handle ); $this->cache[ $file ] = $this->save_file_info( $original_domain, $registration_domain, $file ); } return $translations; } /** * @param string $original_domain * @param string $registration_domain which can be composed with the script-handle for JED files * @param string $file_path * * @return true */ private function save_file_info( $original_domain, $registration_domain, $file_path ) { try { $file_path_pattern = $this->get_file_path_pattern( $file_path, $original_domain ); foreach ( $this->active_languages as $lang_data ) { $file_path_in_lang = sprintf( (string) $file_path_pattern, $lang_data['default_locale'] ); $this->register_single_file( $registration_domain, $file_path_in_lang ); } } catch ( Exception $e ) { } return true; } /** * @param string $file_path * @param string $original_domain * * @return string|null * @throws InvalidArgumentException */ private function get_file_path_pattern( $file_path, $original_domain ) { $pathinfo = pathinfo( $file_path ); $file_type = isset( $pathinfo['extension'] ) ? $pathinfo['extension'] : null; switch ( $file_type ) { case 'mo': return preg_replace( self::PATH_PATTERN_SEARCH_MO, self::PATH_PATTERN_REPLACE_MO, $file_path ); case 'json': $domain_replace = 'default' === $original_domain ? '' : $original_domain . '-'; $search_pattern = str_replace( 'DOMAIN_PLACEHOLDER', $domain_replace, self::PATH_PATTERN_SEARCH_JSON ); return preg_replace( $search_pattern, self::PATH_PATTERN_REPLACE_JSON, $file_path ); } throw new RuntimeException( 'The "' . $file_type . '" file type is not supported for registration' ); } /** * @param string $registration_domain * @param string $file_path */ private function register_single_file( $registration_domain, $file_path ) { if ( ! $this->wpml_file->file_exists( $file_path ) || $this->isGeneratedFile( $file_path ) ) { return; } $relative_path = $this->wpml_file->get_relative_path( $file_path ); $last_modified = $this->wpml_file->get_file_modified_timestamp( $file_path ); $file = $this->file_dictionary->find_file_info_by_path( $relative_path ); if ( ! $file ) { if ( ! $this->components_find->is_component_active( $file_path ) ) { return; } $file = new WPML_ST_Translations_File_Entry( $relative_path, $registration_domain ); $file->set_last_modified( $last_modified ); list( $component_type, $component_id ) = $this->components_find->find_details( $file_path ); $file->set_component_type( $component_type ); $file->set_component_id( $component_id ); $this->file_dictionary->save( $file ); } elseif ( $file->get_last_modified() !== $last_modified ) { $file->set_status( WPML_ST_Translations_File_Entry::NOT_IMPORTED ); $file->set_last_modified( $last_modified ); $file->set_imported_strings_count( 0 ); $this->file_dictionary->save( $file ); } } private function isGeneratedFile( $path ) { return strpos( $this->wpml_file->fix_dir_separator( $path ), $this->wpml_file->fix_dir_separator( WPML\ST\TranslationFile\Manager::getSubdir() ) ) === 0; } } translations-file-scan/wpml-st-translations-file-scan-ui-block.php 0000755 00000007231 14720402445 0021331 0 ustar 00 <?php class WPML_ST_Translations_File_Scan_UI_Block { const NOTICES_GROUP = 'wpml-st-mo-scan'; const NOTICES_MO_SCANNING_BLOCKED = 'mo-scanning-blocked'; /** @var WPML_Notices */ private $notices; /** @var string */ private $link = 'https://wpml.org/faq/how-to-deal-with-error-messages-about-a-broken-table-that-needs-fixing/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlst'; /** * @param WPML_Notices $notices */ public function __construct( WPML_Notices $notices ) { $this->notices = $notices; } public function block_ui() { $this->disable_option(); $this->remove_default_notice(); $this->display_notice(); } public function unblock_ui() { if ( is_admin() ) { $this->notices->remove_notice( self::NOTICES_GROUP, self::NOTICES_MO_SCANNING_BLOCKED ); } } private function disable_option() { add_filter( 'wpml_localization_options_ui_model', array( $this, 'disable_option_handler' ) ); } public function disable_option_handler( $model ) { if ( ! isset( $model['top_options'][0] ) ) { return $model; } $model['top_options'][0]['disabled'] = true; $model['top_options'][0]['message'] = $this->get_short_notice_message(); return $model; } private function get_short_notice_message() { $message = _x( 'WPML cannot replace .mo files because of technical problems in the String Translation table.', 'MO Import blocked short 1/3', 'wpml-string-translation' ); $message .= ' ' . _x( 'WPML support team knows how to fix it.', 'MO Import blocked short 2/3', 'wpml-string-translation' ); $message .= ' ' . sprintf( _x( 'Please add a message in the relevant <a href="%s" target="_blank" >support thread</a> and we\'ll fix it for you.', 'MO Import blocked short 3/3', 'wpml-string-translation' ), $this->link ); return '<span class="icl_error_text" >' . $message . '</span>'; } private function display_notice() { $message = _x( 'There is a problem with the String Translation table in your site.', 'MO Import blocked 1/4', 'wpml-string-translation' ); $message .= ' ' . _x( 'This problem is not causing a problem running the site right now, but can become a critical issue in the future.', 'MO Import blocked 2/4', 'wpml-string-translation' ); $message .= ' ' . _x( 'WPML support team knows how to fix it.', 'MO Import blocked 3/4', 'wpml-string-translation' ); $message .= ' ' . sprintf( _x( 'Please add a message in the relevant <a href="%s" target="_blank">support thread</a> and we\'ll fix it for you.', 'MO Import blocked 4/4', 'wpml-string-translation' ), $this->link ); $notice = $this->notices->create_notice( self::NOTICES_MO_SCANNING_BLOCKED, $message, self::NOTICES_GROUP ); $notice->set_css_class_types( 'error' ); $notice->set_dismissible( false ); $restricted_pages = array( 'sitepress-multilingual-cms/menu/languages.php', 'sitepress-multilingual-cms/menu/menu-sync/menus-sync.php', 'sitepress-multilingual-cms/menu/support.php', 'sitepress-multilingual-cms/menu/taxonomy-translation.php', 'sitepress-multilingual-cms/menu/theme-localization.php', 'wpml-media', 'wpml-package-management', 'wpml-string-translation/menu/string-translation.php', 'wpml-sticky-links', 'wpml-translation-management/menu/translations-queue.php', 'wpml-translation-management/menu/main.php', ); $notice->set_restrict_to_pages( $restricted_pages ); $this->notices->add_notice( $notice ); } private function remove_default_notice() { $this->notices->remove_notice( WPML_ST_Themes_And_Plugins_Settings::NOTICES_GROUP, WPML_ST_Themes_And_Plugins_Updates::WPML_ST_FASTER_SETTINGS_NOTICE_ID ); } } translations-file-scan/wpml-st-translations-file-scan.php 0000755 00000002404 14720402445 0017623 0 ustar 00 <?php class WPML_ST_Translations_File_Scan { /** * @var WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory */ private $charset_filter_factory; /** * @param WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory $charset_filter_factory */ public function __construct( WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory $charset_filter_factory ) { $this->charset_filter_factory = $charset_filter_factory; } /** * @param string $file * * @return WPML_ST_Translations_File_Translation[] */ public function load_translations( $file ) { if ( ! file_exists( $file ) ) { return array(); } $translations = array(); $file_type = pathinfo( $file, PATHINFO_EXTENSION ); switch ( $file_type ) { case 'mo': $translations_file = new WPML_ST_Translations_File_MO( $file ); $translations = $translations_file->get_translations(); break; case 'json': $translations_file = new WPML_ST_Translations_File_JED( $file ); $translations = $translations_file->get_translations(); break; } $unicode_characters_filter = $this->charset_filter_factory->create(); if ( $unicode_characters_filter ) { $translations = $unicode_characters_filter->filter( $translations ); } return $translations; } } class-wpml-st-initialize.php 0000755 00000002233 14720402445 0012127 0 ustar 00 <?php class WPML_ST_Initialize { public function load() { add_action( 'plugins_loaded', array( $this, 'run' ), - PHP_INT_MAX ); } public function run() { if ( ! $this->hasMinimalCoreRequirements() ) { return; } $this->includeAutoloader(); $this->configureDIC(); $this->loadEarlyHooks(); } private function hasMinimalCoreRequirements() { if ( ! class_exists( 'WPML_Core_Version_Check' ) ) { require_once WPML_ST_PATH . '/vendor/wpml-shared/wpml-lib-dependencies/src/dependencies/class-wpml-core-version-check.php'; } return WPML_Core_Version_Check::is_ok( WPML_ST_PATH . '/wpml-dependencies.json' ); } private function includeAutoloader() { require_once WPML_ST_PATH . '/vendor/autoload.php'; } private function configureDIC() { \WPML\Container\share( \WPML\ST\Container\Config::getSharedClasses() ); \WPML\Container\alias( \WPML\ST\Container\Config::getAliases() ); \WPML\Container\delegate( \WPML\ST\Container\Config::getDelegated() ); } private function loadEarlyHooks() { /** @var \WPML\ST\TranslationFile\Hooks $hooks */ $hooks = \WPML\Container\make( \WPML\ST\TranslationFile\Hooks::class ); $hooks->install(); } } strings-cleanup/ajax/RemoveStringsFromDomains.php 0000755 00000001744 14720402445 0016277 0 ustar 00 <?php namespace WPML\ST\StringsCleanup\Ajax; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\FP\Either; use WPML\FP\Fns; use WPML\FP\Obj; use WPML\ST\StringsCleanup\UntranslatedStrings; class RemoveStringsFromDomains implements IHandler { const REMOVE_STRINGS_BATCH_SIZE = 500; public function run( Collection $data ) { $domains = $data->get( 'domains', false ); if ( $domains !== false ) { /** @var UntranslatedStrings $untranslated_strings */ $untranslated_strings = make( UntranslatedStrings::class ); return Either::of( [ 'total_strings' => $untranslated_strings->getCountInDomains( $domains ), 'removed_strings' => $untranslated_strings->remove( Fns::map( Obj::prop( 'id' ), $untranslated_strings->getFromDomains( $domains, self::REMOVE_STRINGS_BATCH_SIZE ) ) ) ] ); } else { return Either::left( __( 'Error: please try again', 'wpml-string-translation' ) ); } } } strings-cleanup/ajax/InitStringsRemoving.php 0000755 00000001722 14720402445 0015311 0 ustar 00 <?php namespace WPML\ST\StringsCleanup\Ajax; use WPML\Ajax\IHandler; use WPML\Collect\Support\Collection; use function WPML\Container\make; use WPML\FP\Either; use WPML\ST\Gettext\AutoRegisterSettings; use WPML\ST\StringsCleanup\UntranslatedStrings; class InitStringsRemoving implements IHandler { public function run( Collection $data ) { $domains = $data->get( 'domains', false ); if ( $domains !== false ) { /** @var UntranslatedStrings $untranslatedStrings */ $untranslatedStrings = make( UntranslatedStrings::class ); /** @var AutoRegisterSettings $autoRegsiterSettings */ $autoRegsiterSettings = make( AutoRegisterSettings::class ); if ( $autoRegsiterSettings->isEnabled() ) { $autoRegsiterSettings->setEnabled( false ); } return Either::of( [ 'total_strings' => $untranslatedStrings->getCountInDomains( $domains ) ] ); } else { return Either::left( __( 'Error: please try again', 'wpml-string-translation' ) ); } } } strings-cleanup/UntranslatedStrings.php 0000755 00000002323 14720402445 0014416 0 ustar 00 <?php namespace WPML\ST\StringsCleanup; class UntranslatedStrings { /** @var \wpdb */ private $wpdb; public function __construct( \wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string[] $domains * * @return int */ public function getCountInDomains( $domains ) { if ( ! $domains ) { return 0; } return (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT count(id) FROM {$this->wpdb->prefix}icl_strings WHERE status = %d AND context IN (" . wpml_prepare_in( $domains, '%s' ) . ')', ICL_TM_NOT_TRANSLATED ) ); } /** * @param string[] $domains * @param int $batchSize * * @return array */ public function getFromDomains( $domains, $batchSize ) { if ( ! $domains ) { return []; } return $this->wpdb->get_results( $this->wpdb->prepare( "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE status = %d AND context IN (" . wpml_prepare_in( $domains, '%s' ) . ') LIMIT 0, %d', ICL_TM_NOT_TRANSLATED, $batchSize ) ); } /** * @param int[] $stringIds * * @return int */ public function remove( $stringIds ) { if ( $stringIds ) { wpml_unregister_string_multi( $stringIds ); } return count( $stringIds ); } } strings-cleanup/UI.php 0000755 00000002220 14720402445 0010711 0 ustar 00 <?php namespace WPML\ST\StringsCleanup; use WPML\FP\Relation; use WPML\ST\Gettext\AutoRegisterSettings; use WPML\ST\StringsCleanup\Ajax\InitStringsRemoving; use WPML\ST\StringsCleanup\Ajax\RemoveStringsFromDomains; use WPML\ST\WP\App\Resources; use WPML\LIB\WP\Hooks as WPHooks; class UI implements \IWPML_Backend_Action_Loader { /** * @return callable|null */ public function create() { if ( Relation::propEq( 'page', WPML_ST_FOLDER . '/menu/string-translation.php', $_GET ) ) { return function () { WPHooks::onAction( 'admin_enqueue_scripts' ) ->then( [ self::class, 'localize' ] ) ->then( Resources::enqueueApp( 'strings-cleanup' ) ); }; } else { return null; } } public static function localize() { $settings = \WPML\Container\make( AutoRegisterSettings::class ); $domains_data = $settings->getDomainsWithStringsTranslationData(); return [ 'name' => 'wpml_strings_cleanup_ui', 'data' => [ 'domains' => $domains_data, 'endpoints' => [ 'removeStringsFromDomains' => RemoveStringsFromDomains::class, 'initStringsRemoving' => InitStringsRemoving::class, ], ], ]; } } menus/theme-plugin-localization-ui/class-st-plugin-localization-ui-utils.php 0000755 00000002356 14720402445 0023402 0 ustar 00 <?php class WPML_ST_Plugin_Localization_Utils { /** @return array */ public function get_plugins() { $plugins = get_plugins(); $mu_plugins = wp_get_mu_plugins(); if ( $mu_plugins ) { foreach ( $mu_plugins as $p ) { $plugin_file = basename( $p ); $plugins[ $plugin_file ] = array( 'Name' => 'MU :: ' . $plugin_file ); } } return $plugins; } /** * @param string $plugin_file * * @return bool */ public function is_plugin_active( $plugin_file ) { $active_plugins = get_option( 'active_plugins', array() ); if ( in_array( $plugin_file, $active_plugins, true ) ) { $is_active = true; } else { $wpmu_sitewide_plugins = (array) maybe_unserialize( get_site_option( 'active_sitewide_plugins' ) ); $is_active = array_key_exists( $plugin_file, $wpmu_sitewide_plugins ); } return $is_active; } public function get_plugins_by_status( $active ) { $plugins = $this->get_plugins(); foreach ( $plugins as $plugin_file => $plugin ) { if ( $active && ! $this->is_plugin_active( $plugin_file ) ) { unset( $plugins[ $plugin_file ] ); } elseif ( ! $active && $this->is_plugin_active( $plugin_file ) ) { unset( $plugins[ $plugin_file ] ); } } return $plugins; } } menus/theme-plugin-localization-ui/class-st-theme-localization-ui-utils.php 0000755 00000000723 14720402445 0023202 0 ustar 00 <?php class WPML_ST_Theme_Localization_Utils { /** @return array */ public function get_theme_data() { $themes = wp_get_themes(); $theme_data = array(); foreach ( $themes as $theme_folder => $theme ) { $theme_data[ $theme_folder ] = array( 'path' => $theme->get_theme_root() . '/' . $theme->get_stylesheet(), 'name' => $theme->get( 'Name' ), 'TextDomain' => $theme->get( 'TextDomain' ), ); } return $theme_data; } } menus/theme-plugin-localization-ui/strategy/class-wpml-st-theme-localization-ui.php 0000755 00000011425 14720402445 0024664 0 ustar 00 <?php class WPML_ST_Theme_Localization_UI implements IWPML_Theme_Plugin_Localization_UI_Strategy { private $utils; private $template_path; private $localization; /** * WPML_ST_Theme_Localization_UI constructor. * * @param \WPML_Localization $localization * @param \WPML_ST_Theme_Localization_Utils $utils * @param string $template_path */ public function __construct( WPML_Localization $localization, WPML_ST_Theme_Localization_Utils $utils, $template_path ) { $this->localization = $localization; $this->utils = $utils; $this->template_path = $template_path; } /** @return array */ public function get_model() { $model = array( 'section_label' => __( 'Strings in the themes', 'wpml-string-translation' ), 'scan_button_label' => __( 'Scan selected themes for strings', 'wpml-string-translation' ), 'completed_title' => __( 'Completely translated strings', 'wpml-string-translation' ), 'needs_update_title' => __( 'Strings in need of translation', 'wpml-string-translation' ), 'component' => __( 'Theme', 'wpml-string-translation' ), 'domain' => __( 'Textdomain', 'wpml-string-translation' ), 'all_text' => __( 'All', 'wpml-string-translation' ), 'active_text' => __( 'Active', 'wpml-string-translation' ), 'inactive_text' => __( 'Inactive', 'wpml-string-translation' ), 'type' => 'theme', 'components' => $this->get_components(), 'stats_id' => 'wpml_theme_scan_stats', 'scan_button_id' => 'wpml_theme_localization_scan', 'section_class' => 'wpml_theme_localization', 'nonces' => array( 'scan_folder' => array( 'action' => WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory::AJAX_ACTION ), ), 'scan_files' => array( 'action' => WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory::AJAX_ACTION ), ), 'update_hash' => array( 'action' => WPML_ST_Update_File_Hash_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Update_File_Hash_Ajax_Factory::AJAX_ACTION ), ), ), 'status_count' => array( 'active' => 1, 'inactive' => count( $this->utils->get_theme_data() ) - 1, ), ); return $model; } /** @return array */ private function get_components() { $components = array(); $theme_localization_status = $this->localization->get_localization_stats( 'theme' ); $status_counters = array( 'complete' => 0, 'incomplete' => 0, ); foreach ( $this->utils->get_theme_data() as $theme_folder => $theme_data ) { $domains = array_key_exists( $theme_folder, $theme_localization_status ) ? $theme_localization_status[ $theme_folder ] : false; $components[ $theme_folder ] = array( 'id' => md5( $theme_data['path'] ), 'component_name' => $theme_data['name'], 'active' => wp_get_theme()->get( 'Name' ) === $theme_data['name'], ); $components[ $theme_folder ]['domains'] = array(); if ( $domains ) { foreach ( $domains as $domain => $stats ) { $components[ $theme_folder ]['domains'][ $domain ] = $this->get_component( $domain, $stats ); } } else { if ( ! array_key_exists( 'TextDomain', $theme_data ) ) { $theme_data['TextDomain'] = __( 'No TextDomain', 'wpml-string-translation' ); } $components[ $theme_folder ]['domains'][ $theme_data['TextDomain'] ] = $this->get_component( $theme_data['TextDomain'], $status_counters ); } } return $components; } /** * @param string $domain * @param array $stats * * @return array */ private function get_component( $domain, array $stats ) { $base_st_url = admin_url( 'admin.php?page=' . WPML_ST_FOLDER . '/menu/string-translation.php' ); return array( 'translated' => $stats['complete'], 'needs_update' => $stats['incomplete'], 'needs_update_link' => add_query_arg( array( 'context' => $domain, 'status' => ICL_STRING_TRANSLATION_NOT_TRANSLATED, ), $base_st_url ), 'translated_link' => add_query_arg( array( 'context' => $domain, 'status' => ICL_STRING_TRANSLATION_COMPLETE, ), $base_st_url ), 'domain_link' => add_query_arg( array( 'context' => $domain ), $base_st_url ), 'title_needs_translation' => sprintf( __( 'Translate strings in %s', 'wpml-string-translation' ), $domain ), 'title_all_strings' => sprintf( __( 'All strings in %s', 'wpml-string-translation' ), $domain ), ); } /** @return string */ public function get_template() { return 'theme-plugin-localization-ui.twig'; } } menus/theme-plugin-localization-ui/strategy/class-wpml-st-plugin-localization-ui.php 0000755 00000011720 14720402445 0025056 0 ustar 00 <?php class WPML_ST_Plugin_Localization_UI implements IWPML_Theme_Plugin_Localization_UI_Strategy { /** @var WPML_ST_Plugin_Localization_Utils */ private $utils; /** @var WPML_Localization */ private $localization; /** @var string */ private $base_st_url; /** * WPML_ST_Plugin_Localization_UI constructor. * * @param WPML_Localization $localization * @param WPML_ST_Plugin_Localization_Utils $utils */ public function __construct( WPML_Localization $localization, WPML_ST_Plugin_Localization_Utils $utils ) { $this->localization = $localization; $this->utils = $utils; $this->base_st_url = admin_url( 'admin.php?page=' . WPML_ST_FOLDER . '/menu/string-translation.php' ); } /** * @return array */ public function get_model() { $model = array( 'section_label' => __( 'Strings in the plugins', 'wpml-string-translation' ), 'scan_button_label' => __( 'Scan selected plugins for strings', 'wpml-string-translation' ), 'completed_title' => __( 'Completely translated strings', 'wpml-string-translation' ), 'needs_update_title' => __( 'Strings in need of translation', 'wpml-string-translation' ), 'component' => __( 'Plugin', 'wpml-string-translation' ), 'domain' => __( 'Textdomain', 'wpml-string-translation' ), 'all_text' => __( 'All', 'wpml-string-translation' ), 'active_text' => __( 'Active', 'wpml-string-translation' ), 'inactive_text' => __( 'Inactive', 'wpml-string-translation' ), 'type' => 'plugin', 'components' => $this->get_components( $this->utils->get_plugins(), $this->localization->get_localization_stats( 'plugin' ) ), 'stats_id' => 'wpml_plugin_scan_stats', 'scan_button_id' => 'wpml_plugin_localization_scan', 'section_class' => 'wpml_plugin_localization', 'nonces' => array( 'scan_folder' => array( 'action' => WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Theme_Plugin_Scan_Dir_Ajax_Factory::AJAX_ACTION ), ), 'scan_files' => array( 'action' => WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Theme_Plugin_Scan_Files_Ajax_Factory::AJAX_ACTION ), ), 'update_hash' => array( 'action' => WPML_ST_Update_File_Hash_Ajax_Factory::AJAX_ACTION, 'nonce' => wp_create_nonce( WPML_ST_Update_File_Hash_Ajax_Factory::AJAX_ACTION ), ), ), 'status_count' => array( 'active' => count( $this->utils->get_plugins_by_status( true ) ), 'inactive' => count( $this->utils->get_plugins_by_status( false ) ), ), ); return $model; } /** * @param array $plugins * @param array $plugin_stats * * @return array */ private function get_components( $plugins, $plugin_stats ) { $components = array(); $no_domain_stats = array( 'complete' => 0, 'incomplete' => 0, ); foreach ( $plugins as $plugin_file => $plugin_data ) { $domains = array_key_exists( $plugin_file, $plugin_stats ) ? $plugin_stats[ $plugin_file ] : false; $components[ $plugin_file ] = array( 'id' => md5( plugin_dir_path( WP_PLUGIN_URL . '/' . $plugin_file ) ), 'file' => basename( $plugin_file ), 'component_name' => $plugin_data['Name'], 'active' => $this->utils->is_plugin_active( $plugin_file ), ); $components[ $plugin_file ]['domains'] = array(); if ( $domains ) { foreach ( $domains as $domain => $stats ) { $components[ $plugin_file ]['domains'][ $domain ] = $this->get_component( $domain, $stats ); } } else { if ( ! array_key_exists( 'TextDomain', $plugin_data ) ) { $plugin_data['TextDomain'] = __( 'No TextDomain', 'wpml-string-translation' ); } $components[ $plugin_file ]['domains'][ $plugin_data['TextDomain'] ] = $this->get_component( $plugin_data['TextDomain'], $no_domain_stats ); } } return $components; } /** * @param string $domain * @param array $stats * * @return array */ private function get_component( $domain, array $stats ) { return array( 'translated' => $stats['complete'], 'needs_update' => $stats['incomplete'], 'needs_update_link' => add_query_arg( array( 'context' => $domain, 'status' => ICL_STRING_TRANSLATION_NOT_TRANSLATED, ), $this->base_st_url ), 'translated_link' => add_query_arg( array( 'context' => $domain, 'status' => ICL_STRING_TRANSLATION_COMPLETE, ), $this->base_st_url ), 'domain_link' => add_query_arg( array( 'context' => $domain ), $this->base_st_url ), 'title_needs_translation' => sprintf( __( 'Translate strings in %s', 'wpml-string-translation' ), $domain ), 'title_all_strings' => sprintf( __( 'All strings in %s', 'wpml-string-translation' ), $domain ), ); } /** @return string */ public function get_template() { return 'theme-plugin-localization-ui.twig'; } } theme-plugin-localization-ui/factory/class-wpml-st-theme-plugin-localization-resources-factory.php 0000755 00000000404 14720402445 0030743 0 ustar 00 menus <?php class WPML_ST_Theme_Plugin_Localization_Resources_Factory implements IWPML_Backend_Action_Loader { /** @return WPML_ST_Theme_Plugin_Localization_Resources */ public function create() { return new WPML_ST_Theme_Plugin_Localization_Resources(); } } menus/theme-plugin-localization-ui/factory/class-wpml-st-theme-localization-ui-factory.php 0000755 00000000644 14720402445 0026137 0 ustar 00 <?php class WPML_ST_Theme_Localization_UI_Factory { const TEMPLATE_PATH = '/templates/theme-plugin-localization/'; /** * @return WPML_ST_Theme_Localization_UI */ public function create() { global $wpdb; $localization = new WPML_Localization( $wpdb ); $utils = new WPML_ST_Theme_Localization_Utils(); return new WPML_ST_Theme_Localization_UI( $localization, $utils, self::TEMPLATE_PATH ); } } menus/theme-plugin-localization-ui/factory/class-wpml-st-plugin-localization-ui-factory.php 0000755 00000000522 14720402445 0026326 0 ustar 00 <?php class WPML_ST_Plugin_Localization_UI_Factory { /** * @return WPML_ST_Plugin_Localization_UI */ public function create() { global $wpdb; $localization = new WPML_Localization( $wpdb ); $utils = new WPML_ST_Plugin_Localization_Utils(); return new WPML_ST_Plugin_Localization_UI( $localization, $utils ); } } theme-plugin-localization-ui/factory/class-wpml-st-theme-plugin-localization-options-ui-factory.php 0000755 00000001210 14720402445 0031033 0 ustar 00 menus <?php class WPML_ST_Theme_Plugin_Localization_Options_UI_Factory implements IWPML_Backend_Action_Loader, IWPML_Deferred_Action_Loader { /** @return WPML_ST_Theme_Plugin_Localization_Options_UI */ public function create() { global $sitepress; $hooks = null; $current_screen = get_current_screen(); if ( isset( $current_screen->id ) && WPML_PLUGIN_FOLDER . '/menu/theme-localization' === $current_screen->id ) { $hooks = new WPML_ST_Theme_Plugin_Localization_Options_UI( $sitepress->get_setting( 'st' ) ); } return $hooks; } /** @return string */ public function get_load_action() { return 'current_screen'; } } factory/class-wpml-st-theme-plugin-localization-options-settings-factory.php 0000755 00000000326 14720402445 0032265 0 ustar 00 menus/theme-plugin-localization-ui <?php class WPML_ST_Theme_Plugin_Localization_Options_Settings_Factory implements IWPML_Backend_Action_Loader { public function create() { return new WPML_ST_Theme_Plugin_Localization_Options_Settings(); } } menus/theme-plugin-localization-ui/class-st-theme-plugin-localization-options-ui.php 0000755 00000002336 14720402445 0025033 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Localization_Options_UI { /** @var array */ private $st_settings; /** * WPML_ST_Theme_Plugin_Localization_Options_UI constructor. * * @param array $st_settings */ public function __construct( $st_settings ) { $this->st_settings = $st_settings; } public function add_hooks() { add_filter( 'wpml_localization_options_ui_model', array( $this, 'add_st_options' ) ); } /** * @param array $model * * @return array */ public function add_st_options( $model ) { $model['top_options'][] = array( 'name' => 'use_theme_plugin_domain', 'type' => 'checkbox', 'value' => 1, 'label' => __( 'Use theme or plugin text domains when gettext calls do not use a string literal', 'wpml-string-translation' ), 'tooltip' => __( "Some themes and plugins don't properly set the textdomain (second argument) in GetText calls. When you select this option, WPML will assume that the strings found in GetText calls in the PHP files of the theme and plugin should have the textdomain with the theme/plugin's name.", 'wpml-string-translation' ), 'checked' => checked( true, ! empty( $this->st_settings['use_header_text_domains_when_missing'] ), false ), ); return $model; } } menus/theme-plugin-localization-ui/class-wpml-st-theme-plugin-localization-options-settings.php 0000755 00000001524 14720402445 0027231 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Localization_Options_Settings implements IWPML_Action { public function add_hooks() { add_filter( 'wpml_localization_options_settings', array( $this, 'add_st_settings' ) ); } /** * @param array $settings * * @return array */ public function add_st_settings( $settings ) { $settings[ WPML_ST_Themes_And_Plugins_Settings::OPTION_NAME ] = array( 'settings_var' => WPML_ST_Themes_And_Plugins_Settings::OPTION_NAME, 'filter' => FILTER_SANITIZE_NUMBER_INT, 'type' => 'option', 'st_setting' => false, ); $settings['use_theme_plugin_domain'] = array( 'settings_var' => 'use_header_text_domains_when_missing', 'filter' => FILTER_SANITIZE_NUMBER_INT, 'type' => 'setting', 'st_setting' => true, ); return $settings; } } menus/theme-plugin-localization-ui/class-wpml-st-theme-plugin-localization-resources.php 0000755 00000002167 14720402445 0025716 0 ustar 00 <?php class WPML_ST_Theme_Plugin_Localization_Resources { public function add_hooks() { add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } public function enqueue_scripts() { wp_enqueue_script( 'wpml-theme-plugin-localization-scan', WPML_ST_URL . '/res/js/theme-plugin-localization/theme-plugin-localization.js', array( 'jquery-ui-dialog' ), WPML_ST_VERSION ); wp_enqueue_style( 'wpml-theme-plugin-localization-scan', WPML_ST_URL . '/res/css/theme-plugin-localization.css', array(), WPML_ST_VERSION ); wp_localize_script( 'wpml-theme-plugin-localization-scan', 'wpml_groups_to_scan', get_option( WPML_ST_Themes_And_Plugins_Updates::WPML_ST_ITEMS_TO_SCAN, [] ) ); wp_localize_script( 'wpml-theme-plugin-localization-scan', 'wpml_active_plugins_themes', $this->get_active_items() ); } private function get_active_items() { $items = array(); foreach ( get_plugins() as $key => $plugin ) { if ( is_plugin_active( $key ) ) { $items['plugin'][] = $key; } } $items['theme'][] = wp_get_theme()->get_template(); return $items; } } db-mappers/Hooks.php 0000755 00000001117 14720402445 0010377 0 ustar 00 <?php namespace WPML\ST\DB\Mappers; use function WPML\Container\make; use function WPML\FP\partial; class Hooks implements \IWPML_Action, \IWPML_Backend_Action, \IWPML_Frontend_Action { public function add_hooks() { $getStringById = [ make( \WPML_ST_DB_Mappers_Strings::class ), 'getById' ]; $moveStringToDomain = partial( [ Update::class, 'moveStringToDomain' ], $getStringById ); add_action( 'wpml_st_move_string_to_domain', $moveStringToDomain, 10, 2 ); add_action( 'wpml_st_move_all_strings_to_new_domain', [ Update::class, 'moveAllStringsToNewDomain' ], 10, 2 ); } } db-mappers/wpml-st-word-count-string-records.php 0000755 00000005554 14720402445 0015772 0 ustar 00 <?php class WPML_ST_Word_Count_String_Records { const CACHE_GROUP = __CLASS__; /** @var wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** @return int */ public function get_total_words() { return (int) $this->wpdb->get_var( "SELECT SUM(word_count) FROM {$this->wpdb->prefix}icl_strings" ); } /** @return array */ public function get_all_values_without_word_count() { $query = " SELECT id, value FROM {$this->wpdb->prefix}icl_strings WHERE word_count IS NULL "; return $this->wpdb->get_results( $query ); } /** * @param string $lang * @param null|string $package_id * * @return int */ public function get_words_to_translate_per_lang( $lang, $package_id = null ) { $key = $lang . ':' . $package_id; $found = false; $words = WPML_Non_Persistent_Cache::get( $key, self::CACHE_GROUP, $found ); if ( ! $found ) { $query = " SELECT SUM(word_count) FROM {$this->wpdb->prefix}icl_strings AS s LEFT JOIN {$this->wpdb->prefix}icl_string_translations AS st ON st.string_id = s.id AND st.language = %s WHERE (st.status <> %d OR st.status IS NULL) "; $prepare_args = [ $lang, ICL_STRING_TRANSLATION_COMPLETE, ]; if ( $package_id ) { $query .= ' AND s.string_package_id = %d'; $prepare_args[] = $package_id; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $words = (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $prepare_args ) ); WPML_Non_Persistent_Cache::set( $key, $words, self::CACHE_GROUP ); } return $words; } /** * @param int $string_id * * @return stdClass */ public function get_value_and_language( $string_id ) { return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT value, language FROM {$this->wpdb->prefix}icl_strings WHERE id = %d", $string_id ) ); } /** * @param int $string_id * @param int $word_count */ public function set_word_count( $string_id, $word_count ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_strings', array( 'word_count' => $word_count ), array( 'id' => $string_id ) ); } /** * @param int $string_id * * @return int */ public function get_word_count( $string_id ) { return (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT word_count FROM {$this->wpdb->prefix}icl_strings WHERE ID = %d", $string_id ) ); } public function reset_all() { $this->wpdb->query( "UPDATE {$this->wpdb->prefix}icl_strings SET word_count = NULL" ); } /** * @param array $package_ids * * @return array */ public function get_ids_from_package_ids( array $package_ids ) { if ( ! $package_ids ) { return array(); } $query = "SELECT id FROM {$this->wpdb->prefix}icl_strings WHERE string_package_id IN(" . wpml_prepare_in( $package_ids ) . ')'; return array_map( 'intval', $this->wpdb->get_col( $query ) ); } } db-mappers/class-wpml-st-bulk-update-strings-status.php 0000755 00000016644 14720402445 0017260 0 ustar 00 <?php class WPML_ST_Bulk_Update_Strings_Status { /** @var wpdb $wpdb */ private $wpdb; /** @var array $active_lang_codes */ private $active_lang_codes; public function __construct( wpdb $wpdb, array $active_lang_codes ) { $this->wpdb = $wpdb; $this->active_lang_codes = $active_lang_codes; } /** * This bulk process was transposed from PHP code * * @see WPML_ST_String::update_status * * Important: The order we call each method is important because it reflects * the order of the conditions in WPML_ST_String::update_status. The updated IDs * will not be updated anymore in the subsequent calls. * * @return array updated IDs */ public function run() { $updated_ids = $this->update_strings_with_no_translation(); $updated_ids = $this->update_strings_with_all_translations_not_translated( $updated_ids ); $updated_ids = $this->update_strings_with_one_translation_waiting_for_translator( $updated_ids ); $updated_ids = $this->update_strings_with_one_translation_needs_update( $updated_ids ); $updated_ids = $this->update_strings_with_less_translations_than_langs_and_one_translation_completed( $updated_ids ); $updated_ids = $this->update_strings_with_less_translations_than_langs_and_no_translation_completed( $updated_ids ); $updated_ids = $this->update_remaining_strings_with_one_not_translated( $updated_ids ); $updated_ids = $this->update_remaining_strings( $updated_ids ); return $updated_ids; } /** * @return array */ private function update_strings_with_no_translation() { $ids = $this->wpdb->get_col( "SELECT DISTINCT s.id FROM {$this->wpdb->prefix}icl_strings AS s LEFT JOIN {$this->wpdb->prefix}icl_string_translations AS st ON st.string_id = s.id WHERE st.string_id IS NULL" ); $this->update_strings_status( $ids, ICL_TM_NOT_TRANSLATED ); return $ids; } /** * @param array $updated_ids * * @return array */ private function update_strings_with_all_translations_not_translated( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( " AND (st.status != %d OR (st.mo_string != '' AND st.mo_string IS NOT NULL))", ICL_TM_NOT_TRANSLATED ); $subquery_not_exists = $this->get_translations_snippet() . $sql; $ids = $this->wpdb->get_col( "SELECT DISTINCT s.id FROM {$this->wpdb->prefix}icl_strings AS s WHERE NOT EXISTS(" . $subquery_not_exists . ')' . $this->get_and_not_in_updated_snippet( $updated_ids ) ); $this->update_strings_status( $ids, ICL_TM_NOT_TRANSLATED ); return array_merge( $updated_ids, $ids ); } /** * @param array $updated_ids * * @return array */ private function update_strings_with_one_translation_waiting_for_translator( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( ' AND st.status = %d', ICL_TM_WAITING_FOR_TRANSLATOR ); $subquery = $this->get_translations_snippet() . $sql; return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_TM_WAITING_FOR_TRANSLATOR ); } /** * @param array $updated_ids * * @return array */ private function update_strings_with_one_translation_needs_update( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( ' AND st.status = %d', ICL_TM_NEEDS_UPDATE ); $subquery = $this->get_translations_snippet() . $sql; return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_TM_NEEDS_UPDATE ); } /** * @param array $updated_ids * * @return array */ private function update_strings_with_less_translations_than_langs_and_one_translation_completed( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( " AND (st.status = %d OR (st.mo_string != '' AND st.mo_string IS NOT NULL))", ICL_TM_COMPLETE ); $subquery = $this->get_translations_snippet() . $sql . $this->get_and_translations_less_than_secondary_languages_snippet(); return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_STRING_TRANSLATION_PARTIAL ); } /** * @param array $updated_ids * * @return array */ private function update_strings_with_less_translations_than_langs_and_no_translation_completed( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( ' AND st.status != %d', ICL_TM_COMPLETE ); $subquery = $this->get_translations_snippet() . $sql . $this->get_and_translations_less_than_secondary_languages_snippet(); return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_TM_NOT_TRANSLATED ); } /** * Defaults to ICL_STRING_TRANSLATION_PARTIAL if not caught before * * @param array $updated_ids * * @return array */ private function update_remaining_strings_with_one_not_translated( array $updated_ids ) { /** @var string $sql */ $sql = $this->wpdb->prepare( " AND st.status = %d AND (st.mo_string = '' OR st.mo_string IS NULL)", ICL_TM_NOT_TRANSLATED ); $subquery = $this->get_translations_snippet() . $sql; return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_STRING_TRANSLATION_PARTIAL ); } /** * Defaults to ICL_TM_COMPLETE if not caught before * * @param array $updated_ids * * @return array */ private function update_remaining_strings( array $updated_ids ) { $subquery = $this->get_translations_snippet(); return $this->update_string_ids_if_subquery_exists( $subquery, $updated_ids, ICL_TM_COMPLETE ); } /** * @param string $subquery * @param array $updated_ids * @param int $new_status * * @return array */ private function update_string_ids_if_subquery_exists( $subquery, array $updated_ids, $new_status ) { $ids = $this->wpdb->get_col( "SELECT DISTINCT s.id FROM {$this->wpdb->prefix}icl_strings AS s WHERE EXISTS(" . $subquery . ')' . $this->get_and_not_in_updated_snippet( $updated_ids ) ); $this->update_strings_status( $ids, $new_status ); return array_merge( $updated_ids, $ids ); } /** * Subquery for the string translations * * @return string */ private function get_translations_snippet() { return "SELECT DISTINCT st.string_id FROM {$this->wpdb->prefix}icl_string_translations AS st WHERE st.string_id = s.id AND st.language != s.language"; } /** * Subquery where translations are less than the number of secondary languages: * - the string translation language must be different than the string language * - the string translation language must be part of the active languages * * @return string */ private function get_and_translations_less_than_secondary_languages_snippet() { $secondary_languages_count = count( $this->active_lang_codes ) - 1; return $this->wpdb->prepare( " AND ( SELECT COUNT( st2.id ) FROM {$this->wpdb->prefix}icl_string_translations AS st2 WHERE st2.string_id = s.id AND st2.language != s.language AND st2.language IN(" . wpml_prepare_in( $this->active_lang_codes ) . ') ) < %d', $secondary_languages_count ); } /** * @param array $updated_ids * * @return string */ private function get_and_not_in_updated_snippet( array $updated_ids ) { return ' AND s.id NOT IN(' . wpml_prepare_in( $updated_ids ) . ')'; } /** * @param array $ids * @param int $status */ private function update_strings_status( array $ids, $status ) { if ( ! $ids ) { return; } /** @var string $sql */ $sql = $this->wpdb->prepare( "UPDATE {$this->wpdb->prefix}icl_strings SET status = %d WHERE id IN(" . wpml_prepare_in( $ids ) . ')', $status ); $this->wpdb->query( $sql ); } } db-mappers/class-wpml-st-db-mappers-string-positions.php 0000755 00000003557 14720402445 0017415 0 ustar 00 <?php class WPML_ST_DB_Mappers_String_Positions { /** * @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param int $string_id * @param int $kind * * @return int */ public function get_count_of_positions_by_string_and_kind( $string_id, $kind ) { $query = " SELECT COUNT(id) FROM {$this->wpdb->prefix}icl_string_positions WHERE string_id = %d AND kind = %d "; /** @var string $sql */ $sql = $this->wpdb->prepare( $query, $string_id, $kind ); return (int) $this->wpdb->get_var( $sql ); } /** * @param int $string_id * @param int $kind * * @return array */ public function get_positions_by_string_and_kind( $string_id, $kind ) { $query = " SELECT position_in_page FROM {$this->wpdb->prefix}icl_string_positions WHERE string_id = %d AND kind = %d "; /** @var string $sql */ $sql = $this->wpdb->prepare( $query, $string_id, $kind ); return $this->wpdb->get_col( $sql ); } /** * @param int $string_id * @param string $position * @param int $kind * * @return bool */ public function is_string_tracked( $string_id, $position, $kind ) { $query = " SELECT id FROM {$this->wpdb->prefix}icl_string_positions WHERE string_id=%d AND position_in_page=%s AND kind=%s "; /** @var string $sql */ $sql = $this->wpdb->prepare( $query, $string_id, $position, $kind ); return (bool) $this->wpdb->get_var( $sql ); } /** * @param int $string_id * @param string $position * @param int $kind */ public function insert( $string_id, $position, $kind ) { $this->wpdb->insert( $this->wpdb->prefix . 'icl_string_positions', array( 'string_id' => $string_id, 'kind' => $kind, 'position_in_page' => $position, ) ); } } db-mappers/Update.php 0000755 00000002132 14720402445 0010534 0 ustar 00 <?php namespace WPML\ST\DB\Mappers; class Update { /** * @param callable $getStringById * @param int $stringId * @param string $domain * * @return bool */ public static function moveStringToDomain( callable $getStringById, $stringId, $domain ) { global $wpdb; $string = $getStringById( $stringId ); if ( $string ) { $wpdb->update( $wpdb->prefix . 'icl_strings', [ 'context' => $domain ], [ 'id' => $stringId ] ); self::regenerateMOFiles( $string->context, $domain ); return true; } return false; } /** * @param string $oldDomain * @param string $newDomain */ public static function moveAllStringsToNewDomain( $oldDomain, $newDomain ) { global $wpdb; $affected = (int) $wpdb->update( $wpdb->prefix . 'icl_strings', [ 'context' => $newDomain ], [ 'context' => $oldDomain ] ); if ( $affected ) { self::regenerateMOFiles( $oldDomain, $newDomain ); } } private static function regenerateMOFiles( $oldDomain, $newDomain ) { do_action( 'wpml_st_refresh_domain', $oldDomain ); do_action( 'wpml_st_refresh_domain', $newDomain ); } } db-mappers/StringsRetrieve.php 0000755 00000004703 14720402445 0012457 0 ustar 00 <?php namespace WPML\ST\DB\Mappers; use \wpdb; use \WPML_DB_Chunk; class StringsRetrieve { /** @var wpdb $wpdb */ private $wpdb; /** @var WPML_DB_Chunk $chunk_retrieve */ private $chunk_retrieve; public function __construct( wpdb $wpdb, WPML_DB_Chunk $chunk_retrieve ) { $this->wpdb = $wpdb; $this->chunk_retrieve = $chunk_retrieve; } /** * @param string $language * @param string $domain * @param bool $modified_mo_only * * @return array */ public function get( $language, $domain, $modified_mo_only = false ) { $args = [ $language, $language, $domain ]; $query = " SELECT s.id, st.status, s.domain_name_context_md5 AS ctx , st.value AS translated, st.mo_string AS mo_string, s.value AS original, s.gettext_context, s.name FROM {$this->wpdb->prefix}icl_strings s " . $this->getStringTranslationJoin() . ' ' . $this->getDomainWhere(); if ( $modified_mo_only ) { $query .= $this->getModifiedMOOnlyWhere(); } $total_strings = $this->get_number_of_strings_in_domain( $language, $domain, $modified_mo_only ); return $this->chunk_retrieve->retrieve( $query, $args, $total_strings ); } /** * @param string $language * @param string $domain * @param bool $modified_mo_only * * @return int */ private function get_number_of_strings_in_domain( $language, $domain, $modified_mo_only ) { $tables = "SELECT COUNT(s.id) FROM {$this->wpdb->prefix}icl_strings AS s"; /** @var string $where */ /** @phpstan-ignore-next-line */ $where = $this->wpdb->prepare( $this->getDomainWhere(), [ $domain ] ); if ( $modified_mo_only ) { /** @var string $sql */ /** @phpstan-ignore-next-line */ $sql = $this->wpdb->prepare( $this->getStringTranslationJoin(), [ $language, $language ] ); $tables .= $sql; $where .= $this->getModifiedMOOnlyWhere(); } return (int) $this->wpdb->get_var( $tables . $where ); } /** * @return string */ private function getStringTranslationJoin() { return " LEFT JOIN {$this->wpdb->prefix}icl_string_translations AS st ON s.id = st.string_id AND st.language = %s AND s.language != %s"; } /** @return string */ private function getDomainWhere() { return ' WHERE UPPER(context) = UPPER(%s)'; } /** @return string */ private function getModifiedMOOnlyWhere() { return ' AND st.status IN (' . wpml_prepare_in( [ ICL_TM_COMPLETE, ICL_TM_NEEDS_UPDATE ], '%d' ) . ') AND st.value IS NOT NULL'; } } db-mappers/DomainsRepository.php 0000755 00000001147 14720402445 0013011 0 ustar 00 <?php namespace WPML\ST\DB\Mappers; use WPML\FP\Curryable; /** * Class DomainsRepository * @package WPML\ST\DB\Mappers * * @method static callable|array getByStringIds( ...$stringIds ) - Curried :: int[]->string[] * */ class DomainsRepository { use Curryable; public static function init() { self::curryN( 'getByStringIds', 1, function ( array $stringIds ) { global $wpdb; $sql = "SELECT DISTINCT `context` FROM {$wpdb->prefix}icl_strings WHERE id IN (" . wpml_prepare_in( $stringIds ) . ")"; return $wpdb->get_col( $sql ); } ); } } DomainsRepository::init(); db-mappers/StringTranslations.php 0000755 00000001254 14720402445 0013166 0 ustar 00 <?php namespace WPML\ST\DB\Mappers; use function WPML\FP\curryN; class StringTranslations { /** * @param \wpdb $wpdb * @param int $stringId * @param string $language * * @return callable|bool */ public static function hasTranslation( $wpdb = null, $stringId = null, $language = null ) { $has = function ( \wpdb $wpdb, $stringId, $language ) { $sql = "SELECT COUNT(id) FROM {$wpdb->prefix}icl_string_translations WHERE string_id = %d AND language = %s"; /** @var string $sql */ $sql = $wpdb->prepare( $sql, $stringId, $language ); return $wpdb->get_var( $sql ) > 0; }; return call_user_func_array( curryN( 3, $has ), func_get_args() ); } } db-mappers/wpml-st-word-count-package-records.php 0000755 00000006667 14720402445 0016065 0 ustar 00 <?php class WPML_ST_Word_Count_Package_Records { /** @var wpdb */ private $wpdb; public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** @return array */ public function get_all_package_ids() { return array_map( 'intval', $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->prefix}icl_string_packages" ) ); } /** @return array */ public function get_packages_ids_without_word_count() { return array_map( 'intval', $this->wpdb->get_col( "SELECT ID FROM {$this->wpdb->prefix}icl_string_packages WHERE word_count IS NULL" ) ); } /** @return array */ public function get_word_counts( $post_id ) { return $this->wpdb->get_col( $this->wpdb->prepare( "SELECT word_count FROM {$this->wpdb->prefix}icl_string_packages WHERE post_id = %d", $post_id ) ); } /** * @param int $package_id * @param string $word_count */ public function set_word_count( $package_id, $word_count ) { $this->wpdb->update( $this->wpdb->prefix . 'icl_string_packages', array( 'word_count' => $word_count ), array( 'ID' => $package_id ) ); } /** * @param int $package_id * * @return null|string */ public function get_word_count( $package_id ) { return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT word_count FROM {$this->wpdb->prefix}icl_string_packages WHERE ID = %d", $package_id ) ); } public function reset_all( array $package_kinds ) { if ( ! $package_kinds ) { return; } $query = "UPDATE {$this->wpdb->prefix}icl_string_packages SET word_count = NULL WHERE kind_slug IN(" . wpml_prepare_in( $package_kinds ) . ')'; $this->wpdb->query( $query ); } /** * @param array $kinds * * @return array */ public function get_ids_from_kind_slugs( array $kinds ) { if ( ! $kinds ) { return array(); } $query = "SELECT ID FROM {$this->wpdb->prefix}icl_string_packages WHERE kind_slug IN(" . wpml_prepare_in( $kinds ) . ')'; return array_map( 'intval', $this->wpdb->get_col( $query ) ); } /** * @param array $post_types * * @return array */ public function get_ids_from_post_types( array $post_types ) { if ( ! $post_types ) { return array(); } $query = "SELECT sp.ID FROM {$this->wpdb->prefix}icl_string_packages AS sp LEFT JOIN {$this->wpdb->posts} AS p ON p.ID = sp.post_id WHERE p.post_type IN(" . wpml_prepare_in( $post_types ) . ')'; return array_map( 'intval', $this->wpdb->get_col( $query ) ); } /** * @param string $kind_slug * * @return int */ public function count_items_by_kind_not_part_of_posts( $kind_slug ) { $query = "SELECT COUNT(*) FROM {$this->wpdb->prefix}icl_string_packages WHERE kind_slug = %s AND post_id IS NULL"; return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $kind_slug ) ); } /** * @param string $kind_slug * * @return int */ public function count_word_counts_by_kind( $kind_slug ) { $query = "SELECT COUNT(*) FROM {$this->wpdb->prefix}icl_string_packages WHERE kind_slug = %s AND word_count IS NOT NULL AND post_id IS NULL"; return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $kind_slug ) ); } /** * @param string $kind_slug * * @return array */ public function get_word_counts_by_kind( $kind_slug ) { $query = "SELECT word_count FROM {$this->wpdb->prefix}icl_string_packages WHERE kind_slug = %s"; return $this->wpdb->get_col( $this->wpdb->prepare( $query, $kind_slug ) ); } } db-mappers/class-wpml-st-db-mappers-strings.php 0000755 00000002325 14720402445 0015543 0 ustar 00 <?php class WPML_ST_DB_Mappers_Strings { /** * @var wpdb */ private $wpdb; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param string $context * * @return array */ public function get_all_by_context( $context ) { $where = strpos( $context, '%' ) === false ? '=' : 'LIKE'; $query = " SELECT * FROM {$this->wpdb->prefix}icl_strings WHERE context {$where} %s "; $query = $this->wpdb->prepare( $query, esc_sql( $context ) ); return $this->wpdb->get_results( $query, ARRAY_A ); } /** * Get a single string row by its domain and value * * @param string $domain * @param string $value * * @return array */ public function getByDomainAndValue( $domain, $value ) { $sql = "SELECT * FROM {$this->wpdb->prefix}icl_strings WHERE `context` = %s and `value` = %s"; return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $domain, $value ) ); } /** * Get a single string row by its id * * @param int $id * * @return array */ public function getById( $id ) { $sql = "SELECT * FROM {$this->wpdb->prefix}icl_strings WHERE id = %d"; return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $id ) ); } } db-mappers/class-wpml-st-models-string.php 0000755 00000003066 14720402445 0014614 0 ustar 00 <?php class WPML_ST_Models_String { /** @var string */ private $language; /** @var string */ private $domain; /** @var string */ private $context; /** @var string */ private $value; /** @var int */ private $status; /** @var string */ private $name; /** @var string */ private $domain_name_context_md5; /** * @param string $language * @param string $domain * @param string $context * @param string $value * @param int $status * @param string|null $name */ public function __construct( $language, $domain, $context, $value, $status, $name = null ) { $this->language = (string) $language; $this->domain = (string) $domain; $this->context = (string) $context; $this->value = (string) $value; $this->status = (int) $status; if ( ! $name ) { $name = md5( $value ); } $this->name = (string) $name; $this->domain_name_context_md5 = md5( $domain . $name . $context ); } /** * @return string */ public function get_language() { return $this->language; } /** * @return string */ public function get_domain() { return $this->domain; } /** * @return string */ public function get_context() { return $this->context; } /** * @return string */ public function get_value() { return $this->value; } /** * @return int */ public function get_status() { return $this->status; } /** * @return string */ public function get_name() { return $this->name; } /** * @return string */ public function get_domain_name_context_md5() { return $this->domain_name_context_md5; } } db-mappers/class-wpml-st-bulk-strings-insert.php 0000755 00000005207 14720402445 0015752 0 ustar 00 <?php class WPML_ST_Bulk_Strings_Insert_Exception extends Exception { } class WPML_ST_Bulk_Strings_Insert { /** @var wpdb */ private $wpdb; /** @var int<1,max> */ private $chunk_size = 1000; /** * @param wpdb $wpdb */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * @param int $chunk_size */ public function set_chunk_size( $chunk_size ) { $this->chunk_size = $chunk_size; } /** * @param WPML_ST_Models_String[] $strings */ public function insert_strings( array $strings ) { foreach ( array_chunk( $strings, $this->chunk_size ) as $chunk ) { $query = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_strings " . '(`language`, `context`, `gettext_context`, `domain_name_context_md5`, `name`, `value`, `status`) VALUES '; $query .= implode( ',', array_map( array( $this, 'build_string_row' ), $chunk ) ); $this->wpdb->suppress_errors = true; $this->wpdb->query( $query ); $this->wpdb->suppress_errors = false; if ( $this->wpdb->last_error ) { throw new WPML_ST_Bulk_Strings_Insert_Exception( 'Deadlock with bulk insert' ); } } } /** * @param WPML_ST_Models_String_Translation[] $translations */ public function insert_string_translations( array $translations ) { foreach ( array_chunk( $translations, $this->chunk_size ) as $chunk ) { $query = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_string_translations " . '(`string_id`, `language`, `status`, `mo_string`) VALUES '; $query .= implode( ',', array_map( array( $this, 'build_translation_row' ), $chunk ) ); $query .= ' ON DUPLICATE KEY UPDATE `mo_string`=VALUES(`mo_string`)'; $this->wpdb->suppress_errors = true; $this->wpdb->query( $query ); $this->wpdb->suppress_errors = false; if ( $this->wpdb->last_error ) { throw new WPML_ST_Bulk_Strings_Insert_Exception( 'Deadlock with bulk insert' ); } } } /** * @param WPML_ST_Models_String $string * * @return string */ private function build_string_row( WPML_ST_Models_String $string ) { return $this->wpdb->prepare( '(%s, %s, %s, %s, %s, %s, %d)', $string->get_language(), $string->get_domain(), $string->get_context(), $string->get_domain_name_context_md5(), $string->get_name(), $string->get_value(), $string->get_status() ); } /** * @param WPML_ST_Models_String_Translation $translation * * @return string */ private function build_translation_row( WPML_ST_Models_String_Translation $translation ) { return $this->wpdb->prepare( '(%d, %s, %d, %s)', $translation->get_string_id(), $translation->get_language(), $translation->get_status(), $translation->get_mo_string() ); } } db-mappers/class-wpml-st-models-string-translation.php 0000755 00000002114 14720402445 0017141 0 ustar 00 <?php class WPML_ST_Models_String_Translation { /** @var int */ private $string_id; /** @var string */ private $language; /** @var int */ private $status; /** @var string */ private $value; /** @var string */ private $mo_string; /** * @param int $string_id * @param string $language * @param int $status * @param string|null $value */ public function __construct( $string_id, $language, $status, $value, $mo_string ) { $this->string_id = (int) $string_id; $this->language = (string) $language; $this->status = (int) $status; $this->value = (string) $value; $this->mo_string = (string) $mo_string; } /** * @return int */ public function get_string_id() { return $this->string_id; } /** * @return string */ public function get_language() { return $this->language; } /** * @return int */ public function get_status() { return $this->status; } /** * @return string */ public function get_value() { return $this->value; } /** * @return string */ public function get_mo_string() { return $this->mo_string; } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.3-4ubuntu2.24 | Генерация страницы: 0.95 |
proxy
|
phpinfo
|
Настройка