first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user