first commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\Rest;
|
||||
|
||||
abstract class Base extends \WPML\Rest\Base {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace() {
|
||||
return 'wpml/st/v1';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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 ]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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() ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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() ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
|
||||
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->remove_notice( self::NOTICE_GROUP, self::NOTICE_ID_MISSING_FOLDER );
|
||||
$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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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+' );
|
||||
$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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\MO\File;
|
||||
|
||||
class MOFactory {
|
||||
/**
|
||||
* @return \MO
|
||||
*/
|
||||
public function createNewInstance() {
|
||||
return new \MO();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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 ) ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
return $this->filesystem->mkdir( $subdir, 0755 & ~ umask() );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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 );
|
||||
|
||||
$this->filesystem->put_contents( $generateMoPath, $fileContents, 0755 & ~umask() );
|
||||
}
|
||||
$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 );
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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' );
|
||||
$data = filter_var_array( (array)json_decode( $request_body ), FILTER_SANITIZE_STRING );
|
||||
|
||||
return isset( $data['runForAllSites'] ) && $data['runForAllSites'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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\TranslationFile\Domains;
|
||||
use function WPML\FP\curryN;
|
||||
use function WPML\FP\partial;
|
||||
use function WPML\FP\pipe;
|
||||
use function WPML\FP\spreadArgs;
|
||||
|
||||
class CustomTextDomains implements \IWPML_Action {
|
||||
|
||||
/** @var Manager $manager */
|
||||
private $manager;
|
||||
|
||||
/** @var Domains $domains */
|
||||
private $domains;
|
||||
|
||||
/** @var LoadedMODictionary $loadedDictionary */
|
||||
private $loadedDictionary;
|
||||
|
||||
/** @var callable */
|
||||
private $syncMissingFile;
|
||||
|
||||
public function __construct(
|
||||
Manager $file_manager,
|
||||
Domains $domains,
|
||||
LoadedMODictionary $loadedDictionary,
|
||||
callable $syncMissingFile = null
|
||||
) {
|
||||
$this->manager = $file_manager;
|
||||
$this->domains = $domains;
|
||||
$this->loadedDictionary = $loadedDictionary;
|
||||
$this->syncMissingFile = $syncMissingFile ?: function () {
|
||||
};
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
$locale = get_locale();
|
||||
|
||||
$getDomainPathTuple = function ( $domain ) use ( $locale ) {
|
||||
return [ $domain, $this->manager->getFilepath( $domain, $locale ) ];
|
||||
};
|
||||
|
||||
$isReadableFile = function ( $domainAndFilePath ) {
|
||||
return is_readable( $domainAndFilePath[1] );
|
||||
};
|
||||
|
||||
$addJitMoToL10nGlobal = pipe( Lst::nth( 0 ), function ( $domain ) use ( $locale ) {
|
||||
$GLOBALS['l10n'][ $domain ] = new MO( $this->loadedDictionary, $locale, $domain );
|
||||
} );
|
||||
|
||||
\wpml_collect( $this->domains->getCustomMODomains() )
|
||||
->map( $getDomainPathTuple )
|
||||
->each( spreadArgs( $this->syncMissingFile ) )
|
||||
->each( spreadArgs( [ $this->loadedDictionary, 'addFile' ] ) )
|
||||
->filter( $isReadableFile )
|
||||
->each( $addJitMoToL10nGlobal );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\MO\Hooks;
|
||||
|
||||
use IWPML_Action;
|
||||
use WPML\ST\DB\Mappers\DomainsRepository;
|
||||
use WPML\ST\MO\File\ManagerFactory;
|
||||
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,
|
||||
':syncMissingFile' => [ $moFileSync, 'syncFile' ],
|
||||
] ),
|
||||
make( LanguageSwitch::class ),
|
||||
make( LoadMissingMOFiles::class ),
|
||||
make( PreloadThemeMoFile::class ),
|
||||
make( DetectPrematurelyTranslatedStrings::class ),
|
||||
$moFileSync,
|
||||
make( StringsLanguageChanged::class, [
|
||||
':manager' => $manager,
|
||||
':getDomainsByStringIds' => DomainsRepository::getByStringIds(),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
private function resetTranslationAvailabilityInformation() {
|
||||
global $wp_textdomain_registry;
|
||||
if ( ! isset( $wp_textdomain_registry ) && function_exists( '_get_path_to_translation' ) ) {
|
||||
_get_path_to_translation( null, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filterLocale( $locale ) {
|
||||
$currentLocale = $this->getCurrentLocale();
|
||||
|
||||
if ( $currentLocale ) {
|
||||
return $currentLocale;
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getUnloadedDomains() {
|
||||
return isset( $GLOBALS['l10n_unloaded'] ) ? array_keys( (array) $GLOBALS['l10n_unloaded'] ) : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?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 );
|
||||
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;
|
||||
}
|
||||
|
||||
if ( ! $this->moFilesDictionary->find( $mofile ) ) {
|
||||
return $mofile;
|
||||
}
|
||||
|
||||
$generatedFile = $this->getGeneratedFileName( $mofile, $domain );
|
||||
if ( self::isReadable( $generatedFile ) ) {
|
||||
return $generatedFile;
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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 );
|
||||
load_textdomain( $entity->domain, $entity->mofile );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $domain
|
||||
*/
|
||||
private function setCustomMOLoaded( $domain ) {
|
||||
$this->loaded_domains[] = $domain;
|
||||
}
|
||||
|
||||
public function languageHasSwitched() {
|
||||
$this->loaded_domains = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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
|
||||
";
|
||||
|
||||
$sql = $this->wpdb->prepare(
|
||||
$sql,
|
||||
'((\\/|-)' . $locale . '(\\.|-))+'
|
||||
);
|
||||
|
||||
return \wpml_collect( $this->wpdb->get_results( $sql ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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 );
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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() {
|
||||
$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 );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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() );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?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 ) {
|
||||
if ( method_exists( $this->getWPLocale(), $method ) ) {
|
||||
return call_user_func_array( [ $this->getWPLocale(), $method ], $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?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 $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 $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 $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 ) {
|
||||
$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 $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 $original
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isWpmlRegisteredString( $original, $name ) {
|
||||
return $name && md5( $original ) !== $name;
|
||||
}
|
||||
|
||||
public static function resetCache() {
|
||||
self::$loadedDomains = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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' )
|
||||
);
|
||||
|
||||
?>
|
||||
</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 $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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?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 ) {
|
||||
return (bool) $this->wpdb->get_var( $this->wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?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 ( ! class_exists( 'WPML_Manage_Translations_Role' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( current_user_can( WPML_Manage_Translations_Role::CAPABILITY ) ) {
|
||||
add_action( 'wpml_st_below_menu', array( 'WPML_Remote_String_Translation', 'display_string_menu' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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 $old_value
|
||||
* @param string $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?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 $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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();
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 );
|
||||
} ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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
|
||||
* @method static callable|string getString( ...$id ) :: int → string
|
||||
* @method static callable getBatchId() :: ( string → int )
|
||||
* @method static callable|void batchStringsStorage( ...$records, ...$batchId, ...$stringId, ...$sourceLang ) :: Records → int → int → string → void
|
||||
* @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( 'getString', curryN( 1, function ( $id ) {
|
||||
return make( '\WPML_ST_String', [ ':string_id' => $id ] )->get_value();
|
||||
} ) );
|
||||
|
||||
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::__ ) );
|
||||
|
||||
self::macro( 'batchStringsStorage', curryN( 4, function ( callable $saveBatch, $batchId, $stringId, $sourceLang ) {
|
||||
self::setBatchLanguage( $batchId, $sourceLang );
|
||||
|
||||
$saveBatch( $batchId, $stringId );
|
||||
} ) );
|
||||
|
||||
$initializeTranslation = StringTranslations::markTranslationsAsInProgress(
|
||||
partial( [ Status::class, 'getStatusesOfBatch' ], $wpdb )
|
||||
);
|
||||
|
||||
Hooks::addHooks(
|
||||
self::getBatchId(),
|
||||
self::batchStringsStorage( Records::set( $wpdb ) ),
|
||||
Records::get( $wpdb ),
|
||||
self::getString()
|
||||
);
|
||||
|
||||
Hooks::addStringTranslationStatusHooks( StringTranslations::updateStatus(), $initializeTranslation );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\Batch\Translation;
|
||||
|
||||
use WPML\FP\Curryable;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use function WPML\Container\make;
|
||||
|
||||
/**
|
||||
* @method static callable|void installSchema( ...$wpdb ) :: wpdb → void
|
||||
* @method static callable|int get( ...$wpdb, ...$batchId ) :: wpdb → int → [int]
|
||||
* @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`)
|
||||
)
|
||||
';
|
||||
|
||||
}
|
||||
|
||||
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(
|
||||
'get',
|
||||
2,
|
||||
function ( \wpdb $wpdb, $batchId ) {
|
||||
return $wpdb->get_col(
|
||||
$wpdb->prepare( "SELECT string_id FROM {$wpdb->prefix}icl_string_batches WHERE batch_id = %d", $batchId )
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 ) {
|
||||
return $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT batch_id FROM {$wpdb->prefix}icl_string_batches WHERE string_id = %d", $stringId )
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
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 );
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
<?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' );
|
||||
$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 ) : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?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
|
||||
* @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|bool isBatchId( ...$str ) :: string → bool
|
||||
* @method static callable|bool isBatchField( ...$field ) :: object → bool
|
||||
* @method static callable|string decodeStringId( ...$str ) :: string → string
|
||||
* @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
|
||||
* @method static callable|void updateStatus(...$element_type_prefix, ...$job) :: string -> \stdClass -> void
|
||||
*/
|
||||
class StringTranslations {
|
||||
|
||||
use Macroable;
|
||||
|
||||
public static function init() {
|
||||
|
||||
self::macro( 'isBatchId', Str::startsWith( Module::STRING_ID_PREFIX ) );
|
||||
|
||||
self::macro( 'isTranslated', Obj::prop( 'field_translate' ) );
|
||||
|
||||
self::macro(
|
||||
'isBatchField',
|
||||
curryN(
|
||||
1,
|
||||
function( $field ) {
|
||||
return self::isBatchId( Obj::prop( 'field_type', $field ) );
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
self::macro( 'decodeStringId', Str::replace( Module::STRING_ID_PREFIX, '' ) );
|
||||
|
||||
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 ]
|
||||
);
|
||||
|
||||
Wrapper::of( $job->elements )
|
||||
->map( Fns::filter( Logic::allPass( [ self::isTranslated(), self::isBatchField() ] ) ) )
|
||||
->map( Fns::each( $saveTranslation ) );
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
self::macro(
|
||||
'updateStatus',
|
||||
curryN(
|
||||
2,
|
||||
function ( $element_type_prefix, $job ) {
|
||||
if ( $element_type_prefix === 'st-batch' ) {
|
||||
// $getStringId :: field → int
|
||||
$getStringId = pipe( Obj::prop( 'field_type' ), self::decodeStringId() );
|
||||
|
||||
$updateStatus = ST_API::updateStatus( Fns::__, $job->language_code, ICL_TM_IN_PROGRESS );
|
||||
|
||||
\wpml_collect( $job->elements )
|
||||
->filter( self::isBatchField() )
|
||||
->map( $getStringId )
|
||||
->each( $updateStatus );
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
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 ) ) {
|
||||
return (object) [
|
||||
'id' => $stringId,
|
||||
'translation' => base64_encode( ST_API::getTranslation( self::decodeStringId( $stringId ), $lang ) ),
|
||||
];
|
||||
}
|
||||
|
||||
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 ) ) );
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StringTranslations::init();
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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();
|
||||
@@ -0,0 +1,9 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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 ) {
|
||||
$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 bool|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;
|
||||
}
|
||||
|
||||
$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 ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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 '';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?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 $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' );
|
||||
}
|
||||
|
||||
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 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
<?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();
|
||||
|
||||
$status_filter = isset( $_GET['status'] ) ? (int) $_GET['status'] : false;
|
||||
|
||||
$translation_priority = isset( $_GET['translation-priority'] ) ? $_GET['translation-priority'] : false;
|
||||
|
||||
if ( $status_filter !== false ) {
|
||||
if ( $status_filter == ICL_TM_COMPLETE ) {
|
||||
$extra_cond .= ' AND s.status = ' . ICL_TM_COMPLETE;
|
||||
} elseif ( $status_filter == ICL_STRING_TRANSLATION_PARTIAL ) {
|
||||
$extra_cond .= ' AND s.status = ' . ICL_STRING_TRANSLATION_PARTIAL;
|
||||
} elseif ( $status_filter != ICL_TM_WAITING_FOR_TRANSLATOR ) {
|
||||
$extra_cond .= ' AND s.status IN (' . ICL_STRING_TRANSLATION_PARTIAL . ',' . ICL_TM_NEEDS_UPDATE . ',' . ICL_TM_NOT_TRANSLATED . ',' . ICL_TM_WAITING_FOR_TRANSLATOR . ')';
|
||||
}
|
||||
}
|
||||
|
||||
if ( $translation_priority != false ) {
|
||||
if ( $translation_priority === __( 'Optional', 'sitepress' ) ) {
|
||||
$extra_cond .= " AND s.translation_priority IN ( '" . esc_sql( $translation_priority ) . "', '' ) ";
|
||||
} else {
|
||||
$extra_cond .= " AND s.translation_priority = '" . esc_sql( $translation_priority ) . "' ";
|
||||
}
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'context', $_GET ) ) {
|
||||
$context = Sanitize::stringProp( 'context', $_GET );
|
||||
|
||||
if ( self::EMPTY_CONTEXT_LABEL === $context ) {
|
||||
$context = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $context ) ) {
|
||||
$extra_cond .= " AND s.context = '" . esc_sql( $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 ( $status_filter === ICL_TM_WAITING_FOR_TRANSLATOR ) {
|
||||
$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";
|
||||
}
|
||||
$res = $this->get_results( $sql_query, $extra_cond, $offset, $limit, $joins );
|
||||
|
||||
if ( $res ) {
|
||||
$extra_cond = '';
|
||||
if ( isset( $_GET['translation_language'] ) ) {
|
||||
$extra_cond .= " AND language='" . esc_sql( $_GET['translation_language'] ) . "'";
|
||||
}
|
||||
|
||||
foreach ( $res as $row ) {
|
||||
$string_translations[ $row['string_id'] ] = $row;
|
||||
|
||||
$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 $search_filter
|
||||
* @param bool|null $exact_match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_column_filter_sql( $column, $search_filter, $exact_match ) {
|
||||
$pattern = '{column} LIKE \'%{value}%\'';
|
||||
if ( $exact_match ) {
|
||||
$pattern = '{column} = \'{value}\'';
|
||||
}
|
||||
|
||||
return str_replace(
|
||||
array( '{column}', '{value}' ),
|
||||
array(
|
||||
esc_sql( $column ),
|
||||
esc_sql( str_replace( "'", "'", $search_filter ) ),
|
||||
),
|
||||
$pattern
|
||||
);
|
||||
}
|
||||
|
||||
public function get_per_domain_counts( $status ) {
|
||||
$extra_cond = '';
|
||||
|
||||
if ( $status !== false ) {
|
||||
if ( $status == ICL_TM_COMPLETE ) {
|
||||
$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|bool
|
||||
*/
|
||||
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 (int) $_GET['em'] === 1;
|
||||
}
|
||||
|
||||
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 must_show_all_results() {
|
||||
return isset( $_GET['show_results'] ) && $_GET['show_results'] === 'all';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?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, 2 );
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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 $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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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(); },
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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();
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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";
|
||||
|
||||
return $wpdb->get_var( $wpdb->prepare( $sql, $stringId, $language ) ) > 0;
|
||||
};
|
||||
|
||||
return call_user_func_array( curryN( 3, $has ), func_get_args() );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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";
|
||||
$where = $this->wpdb->prepare( $this->getDomainWhere(), [ $domain ] );
|
||||
|
||||
if ( $modified_mo_only ) {
|
||||
$tables .= $this->wpdb->prepare( $this->getStringTranslationJoin(), [ $language, $language ] );
|
||||
$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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
return $affected;
|
||||
}
|
||||
|
||||
private static function regenerateMOFiles( $oldDomain, $newDomain ) {
|
||||
do_action( 'wpml_st_refresh_domain', $oldDomain );
|
||||
do_action( 'wpml_st_refresh_domain', $newDomain );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Bulk_Strings_Insert_Exception extends Exception {
|
||||
|
||||
}
|
||||
|
||||
class WPML_ST_Bulk_Strings_Insert {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/** @var int */
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?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 ) {
|
||||
$subquery_not_exists = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( " AND (st.status != %d OR (st.mo_string != '' AND st.mo_string IS NOT NULL))", ICL_TM_NOT_TRANSLATED );
|
||||
|
||||
$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 ) {
|
||||
$subquery = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( ' AND st.status = %d', ICL_TM_WAITING_FOR_TRANSLATOR );
|
||||
|
||||
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 ) {
|
||||
$subquery = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( ' AND st.status = %d', ICL_TM_NEEDS_UPDATE );
|
||||
|
||||
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 ) {
|
||||
$subquery = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( " AND (st.status = %d OR (st.mo_string != '' AND st.mo_string IS NOT NULL))", ICL_TM_COMPLETE )
|
||||
. $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 ) {
|
||||
$subquery = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( ' AND st.status != %d', ICL_TM_COMPLETE )
|
||||
. $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 ) {
|
||||
$subquery = $this->get_translations_snippet()
|
||||
. $this->wpdb->prepare( " AND st.status = %d AND (st.mo_string = '' OR st.mo_string IS NULL)", ICL_TM_NOT_TRANSLATED );
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$this->wpdb->query(
|
||||
$this->wpdb->prepare(
|
||||
"UPDATE {$this->wpdb->prefix}icl_strings SET status = %d WHERE id IN(" . wpml_prepare_in( $ids ) . ')',
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?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
|
||||
";
|
||||
|
||||
return (int) $this->wpdb->get_var( $this->wpdb->prepare( $query, $string_id, $kind ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
";
|
||||
|
||||
return $this->wpdb->get_col( $this->wpdb->prepare( $query, $string_id, $kind ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
";
|
||||
|
||||
return (bool) $this->wpdb->get_var( $this->wpdb->prepare( $query, $string_id, $position, $kind ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,
|
||||
) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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 $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?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 ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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 $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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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->create( WPML_Slug_Translation_Factory::TAX ),
|
||||
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' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?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_translation_id, 'string|' ) !== false ) {
|
||||
$string_translation_id = preg_replace( '/[^0-9]/', '', $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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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 $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 = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\StringsFilter;
|
||||
|
||||
class StringEntity {
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var string */
|
||||
private $domain;
|
||||
|
||||
/** @var string */
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param string $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
|
||||
*/
|
||||
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'] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\StringsFilter;
|
||||
|
||||
class TranslationEntity {
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
/** @var bool */
|
||||
private $hasTranslation;
|
||||
|
||||
/** @var bool */
|
||||
private $stringRegistered;
|
||||
|
||||
/**
|
||||
* @param 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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 $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 $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 $untranslated_text Untranslated text.
|
||||
* @param string $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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user