first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\TranslationFile;
|
||||
|
||||
class EntryQueries {
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function isType( $type ) {
|
||||
return function ( \WPML_ST_Translations_File_Entry $entry ) use ( $type ) {
|
||||
return $entry->get_component_type() === $type;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function isExtension( $extension ) {
|
||||
return function ( \WPML_ST_Translations_File_Entry $file ) use ( $extension ) {
|
||||
return $file->get_extension() === $extension;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function getResourceName() {
|
||||
return function ( \WPML_ST_Translations_File_Entry $entry ) {
|
||||
$function = 'get' . ucfirst( $entry->get_component_type() ) . 'Name';
|
||||
return self::$function( $entry );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function getDomain() {
|
||||
return function( \WPML_ST_Translations_File_Entry $file ) {
|
||||
return $file->get_domain();
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @param \WPML_ST_Translations_File_Entry $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getPluginName( \WPML_ST_Translations_File_Entry $entry ) {
|
||||
$data = get_plugin_data( WPML_PLUGINS_DIR . '/' . $entry->get_component_id(), false, false );
|
||||
return $data['Name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_ST_Translations_File_Entry $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getThemeName( \WPML_ST_Translations_File_Entry $entry ) {
|
||||
return $entry->get_component_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_ST_Translations_File_Entry $entry
|
||||
*
|
||||
* @return mixed|string|void
|
||||
*/
|
||||
private static function getOtherName( \WPML_ST_Translations_File_Entry $entry ) {
|
||||
return 'WordPress';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\TranslationFile;
|
||||
|
||||
use WPML_ST_Translations_File_Entry;
|
||||
|
||||
class QueueFilter {
|
||||
/** @var array */
|
||||
private $plugins;
|
||||
|
||||
/** @var array */
|
||||
private $themes;
|
||||
|
||||
/** @var array */
|
||||
private $other;
|
||||
|
||||
/**
|
||||
* @param array $plugins
|
||||
* @param array $themes
|
||||
* @param array $other
|
||||
*/
|
||||
public function __construct( array $plugins, array $themes, array $other ) {
|
||||
$this->plugins = $plugins;
|
||||
$this->themes = $themes;
|
||||
$this->other = $other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Entry $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelected( WPML_ST_Translations_File_Entry $file ) {
|
||||
$getResourceName = EntryQueries::getResourceName();
|
||||
$resourceName = $getResourceName( $file );
|
||||
|
||||
switch ( $file->get_component_type() ) {
|
||||
case 'plugin':
|
||||
return in_array( $resourceName, $this->plugins );
|
||||
|
||||
case 'theme':
|
||||
return in_array( $resourceName, $this->themes );
|
||||
|
||||
case 'other':
|
||||
return in_array( $resourceName, $this->other );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\MO\Scan\UI;
|
||||
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\ST\MO\Generate\DomainsAndLanguagesRepository;
|
||||
use WPML\ST\MO\Generate\Process\ProcessFactory;
|
||||
use WPML_ST_Translations_File_Dictionary_Storage_Table;
|
||||
use WPML_ST_Translations_File_Dictionary;
|
||||
use WPML\WP\OptionManager;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\partial;
|
||||
|
||||
class Factory implements \IWPML_Backend_Action_Loader, \IWPML_Deferred_Action_Loader {
|
||||
|
||||
const WPML_VERSION_INTRODUCING_ST_MO_FLOW = '4.3.0';
|
||||
const OPTION_GROUP = 'ST-MO';
|
||||
const IGNORE_WPML_VERSION = 'ignore-wpml-version';
|
||||
|
||||
/**
|
||||
* @return callable|null
|
||||
* @throws \WPML\Auryn\InjectionException
|
||||
*/
|
||||
public function create() {
|
||||
if (
|
||||
current_user_can( 'manage_options' ) &&
|
||||
function_exists( 'wpml_is_rest_enabled' ) && wpml_is_rest_enabled()
|
||||
) {
|
||||
global $sitepress;
|
||||
$wp_api = $sitepress->get_wp_api();
|
||||
|
||||
$pre_gen_dissmissed = self::isDismissed();
|
||||
$st_page = $wp_api->is_core_page( 'theme-localization.php' ) || $wp_api->is_string_translation_page();
|
||||
|
||||
if (
|
||||
( $st_page || ( ! $st_page && ! $pre_gen_dissmissed ) )
|
||||
&& ( DomainsAndLanguagesRepository::hasTranslationFilesTable() || is_network_admin() )
|
||||
) {
|
||||
$files_to_import = $st_page ? $this->getFilesToImport() : wpml_collect( [] );
|
||||
$domains_to_pre_generate_count = self::getDomainsToPreGenerateCount();
|
||||
if ( $files_to_import->count() || $domains_to_pre_generate_count || isset( $_GET['search'] ) ) {
|
||||
return partial(
|
||||
[ UI::class, 'add_hooks' ],
|
||||
Model::provider( $files_to_import, $domains_to_pre_generate_count, $st_page, is_network_admin() ),
|
||||
$st_page
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_load_action() {
|
||||
return 'init';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \WPML\Auryn\InjectionException
|
||||
*/
|
||||
public static function isDismissed() {
|
||||
return make( OptionManager::class )->get( self::OPTION_GROUP, 'pregen-dismissed', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
* @throws \WPML\Auryn\InjectionException
|
||||
*/
|
||||
private function getFilesToImport() {
|
||||
/** @var WPML_ST_Translations_File_Dictionary $file_dictionary */
|
||||
$file_dictionary = make(
|
||||
WPML_ST_Translations_File_Dictionary::class,
|
||||
[ 'storage' => WPML_ST_Translations_File_Dictionary_Storage_Table::class ]
|
||||
);
|
||||
|
||||
$file_dictionary->clear_skipped();
|
||||
|
||||
return InstalledComponents::filter( wpml_collect( $file_dictionary->get_not_imported_files() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private static function isPreGenerationRequired() {
|
||||
return self::shouldIgnoreWpmlVersion() || self::wpmlStartVersionBeforeMOFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private static function wpmlStartVersionBeforeMOFlow() {
|
||||
return version_compare(
|
||||
get_option( \WPML_Installation::WPML_START_VERSION_KEY, '0.0.0' ),
|
||||
self::WPML_VERSION_INTRODUCING_ST_MO_FLOW,
|
||||
'<'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws \WPML\Auryn\InjectionException
|
||||
*/
|
||||
public static function getDomainsToPreGenerateCount() {
|
||||
return self::isPreGenerationRequired() ? make( ProcessFactory::class )->create()->getPagesCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function shouldIgnoreWpmlVersion() {
|
||||
return make( OptionManager::class )->get( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, false );
|
||||
}
|
||||
|
||||
public static function ignoreWpmlVersion() {
|
||||
make( OptionManager::class )->set( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, true );
|
||||
}
|
||||
|
||||
public static function clearIgnoreWpmlVersion() {
|
||||
make( OptionManager::class )->set( self::OPTION_GROUP, self::IGNORE_WPML_VERSION, false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* @author OnTheGo Systems
|
||||
*/
|
||||
|
||||
namespace WPML\ST\MO\Scan\UI;
|
||||
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML_ST_Translations_File_Entry;
|
||||
|
||||
class InstalledComponents {
|
||||
|
||||
/**
|
||||
* @param Collection $components Collection of WPML_ST_Translations_File_Entry objects.
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function filter( Collection $components ) {
|
||||
return $components
|
||||
->reject( self::isPluginMissing() )
|
||||
->reject( self::isThemeMissing() );
|
||||
}
|
||||
|
||||
/**
|
||||
* WPML_ST_Translations_File_Entry -> bool
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function isPluginMissing() {
|
||||
return function( WPML_ST_Translations_File_Entry $entry ) {
|
||||
return 'plugin' === $entry->get_component_type()
|
||||
&& ! is_readable( WPML_PLUGINS_DIR . '/' . $entry->get_component_id() );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WPML_ST_Translations_File_Entry -> bool
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function isThemeMissing() {
|
||||
return function( WPML_ST_Translations_File_Entry $entry ) {
|
||||
return 'theme' === $entry->get_component_type()
|
||||
&& ! wp_get_theme( $entry->get_component_id() )->exists();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\MO\Scan\UI;
|
||||
|
||||
use WPML\Collect\Support\Collection;
|
||||
use WPML\ST\MO\Generate\MultiSite\Condition;
|
||||
use WPML\ST\TranslationFile\EntryQueries;
|
||||
|
||||
class Model {
|
||||
|
||||
/**
|
||||
* @param Collection $files_to_scan
|
||||
* @param int $domains_to_pre_generate_count
|
||||
* @param bool $is_st_page
|
||||
* @param bool $is_network_admin
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function provider(
|
||||
Collection $files_to_scan,
|
||||
$domains_to_pre_generate_count,
|
||||
$is_st_page,
|
||||
$is_network_admin
|
||||
) {
|
||||
return function () use ( $files_to_scan, $domains_to_pre_generate_count, $is_st_page ) {
|
||||
return [
|
||||
'files_to_scan' => [
|
||||
'count' => $files_to_scan->count(),
|
||||
'plugins' => self::filterFilesByType( $files_to_scan, 'plugin' ),
|
||||
'themes' => self::filterFilesByType( $files_to_scan, 'theme' ),
|
||||
'other' => self::filterFilesByType( $files_to_scan, 'other' ),
|
||||
],
|
||||
'domains_to_pre_generate_count' => $domains_to_pre_generate_count,
|
||||
'file_path' => WP_LANG_DIR . '/wpml',
|
||||
'is_st_page' => $is_st_page,
|
||||
'admin_texts_url' => 'admin.php?page=' . WPML_ST_FOLDER . '/menu/string-translation.php&trop=1',
|
||||
'is_search' => isset( $_GET['search'] ),
|
||||
'run_ror_all_sites' => ( new Condition() )->shouldRunWithAllSites(),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $files_to_scan
|
||||
* @param string $type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function filterFilesByType( Collection $files_to_scan, $type ) {
|
||||
return $files_to_scan->filter( EntryQueries::isType( $type ) )
|
||||
->map( EntryQueries::getResourceName() )
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\ST\MO\Scan\UI;
|
||||
|
||||
use \WPML\ST\WP\App\Resources;
|
||||
use WPML\LIB\WP\Hooks as WPHooks;
|
||||
|
||||
class UI {
|
||||
|
||||
public static function add_hooks( callable $getModel, $isSTPage ) {
|
||||
WPHooks::onAction( 'admin_enqueue_scripts' )
|
||||
->then( $getModel )
|
||||
->then( [ self::class, 'localize' ] )
|
||||
->then( Resources::enqueueApp( 'mo-scan' ) );
|
||||
|
||||
if ( ! $isSTPage ) {
|
||||
WPHooks::onAction( [ 'admin_notices', 'network_admin_notices' ] )
|
||||
->then( [ self::class, 'add_admin_notice' ] );
|
||||
}
|
||||
}
|
||||
|
||||
public static function add_admin_notice() {
|
||||
?>
|
||||
<div id="wpml-mo-scan-st-page"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function localize( $model ) {
|
||||
return [
|
||||
'name' => 'wpml_mo_scan_ui_files',
|
||||
'data' => $model,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
interface WPML_ST_Translations_File_Scan_Charset_Validation {
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory {
|
||||
|
||||
/** @var wpdb $wpdb */
|
||||
private $wpdb;
|
||||
|
||||
public function __construct( wpdb $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$charset_validator = new WPML_ST_Translations_File_Scan_Db_Charset_Validation( $this->wpdb, new WPML_ST_Translations_File_Scan_Db_Table_List( $this->wpdb ) );
|
||||
|
||||
return $charset_validator->is_valid() ? null : new WPML_ST_Translations_File_Unicode_Characters_Filter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan_Db_Charset_Validation implements WPML_ST_Translations_File_Scan_Charset_Validation {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Scan_Db_Table_List */
|
||||
private $table_list;
|
||||
|
||||
/**
|
||||
* @param wpdb $wpdb
|
||||
* @param WPML_ST_Translations_File_Scan_Db_Table_List $table_list
|
||||
*/
|
||||
public function __construct( wpdb $wpdb, WPML_ST_Translations_File_Scan_Db_Table_List $table_list ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->table_list = $table_list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid() {
|
||||
if ( ! $this->wpdb->has_cap( 'utf8mb4' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chunks = array();
|
||||
foreach ( $this->table_list->get_tables() as $table ) {
|
||||
$chunks[] = $this->get_unique_collation_list_from_table( $table );
|
||||
}
|
||||
|
||||
$collations = call_user_func_array( 'array_merge', $chunks );
|
||||
$collations = array_unique( $collations );
|
||||
|
||||
return count( $collations ) < 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_unique_collation_list_from_table( $table ) {
|
||||
$columns = $this->wpdb->get_results( "SHOW FULL COLUMNS FROM `{$table}` WHERE Collation LIKE 'utf8mb4%'" );
|
||||
|
||||
return array_unique( wp_list_pluck( $columns, 'Collation' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan_Db_Table_List {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/**
|
||||
* @param wpdb $wpdb
|
||||
*/
|
||||
public function __construct( wpdb $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_tables() {
|
||||
return array(
|
||||
$this->wpdb->prefix . 'icl_strings',
|
||||
$this->wpdb->prefix . 'icl_string_translations',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Component_Details {
|
||||
/** @var WPML_ST_Translations_File_Components_Find[] */
|
||||
private $finders;
|
||||
|
||||
/** @var WPML_File $file */
|
||||
private $file;
|
||||
|
||||
/** @var string */
|
||||
private $plugin_dir;
|
||||
|
||||
/** @var string */
|
||||
private $theme_dir;
|
||||
|
||||
/** @var string */
|
||||
private $languages_plugin_dir;
|
||||
|
||||
/** @var string */
|
||||
private $languages_theme_dir;
|
||||
|
||||
/** @var array */
|
||||
private $cache = array();
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Components_Find_Plugin $plugin_id_finder
|
||||
* @param WPML_ST_Translations_File_Components_Find_Theme $theme_id_finder
|
||||
* @param WPML_File $wpml_file
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_ST_Translations_File_Components_Find_Plugin $plugin_id_finder,
|
||||
WPML_ST_Translations_File_Components_Find_Theme $theme_id_finder,
|
||||
WPML_File $wpml_file
|
||||
) {
|
||||
$this->finders['plugin'] = $plugin_id_finder;
|
||||
$this->finders['theme'] = $theme_id_finder;
|
||||
$this->file = $wpml_file;
|
||||
|
||||
$this->theme_dir = $this->file->fix_dir_separator( get_theme_root() );
|
||||
$this->plugin_dir = $this->file->fix_dir_separator( realpath( WPML_PLUGINS_DIR ) );
|
||||
|
||||
$wp_content_dir = realpath( WP_CONTENT_DIR );
|
||||
|
||||
$this->languages_plugin_dir = $this->file->fix_dir_separator( $wp_content_dir . '/languages/plugins' );
|
||||
$this->languages_theme_dir = $this->file->fix_dir_separator( $wp_content_dir . '/languages/themes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_full_path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function find_details( $file_full_path ) {
|
||||
$file_full_path = $this->file->fix_dir_separator( $file_full_path );
|
||||
|
||||
if ( ! isset( $this->cache[ $file_full_path ] ) ) {
|
||||
$type = $this->find_type( $file_full_path );
|
||||
if ( 'other' === $type ) {
|
||||
$this->cache[ $file_full_path ] = array( $type, null );
|
||||
} else {
|
||||
$this->cache[ $file_full_path ] = array( $type, $this->find_id( $type, $file_full_path ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cache[ $file_full_path ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $component_type
|
||||
* @param string $file_full_path
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function find_id( $component_type, $file_full_path ) {
|
||||
if ( ! isset( $this->finders[ $component_type ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->finders[ $component_type ]->find_id( $file_full_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_full_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function find_type( $file_full_path ) {
|
||||
if ( $this->theme_dir && ( $this->string_contains( $file_full_path, $this->theme_dir ) || $this->string_contains( $file_full_path, $this->languages_theme_dir ) ) ) {
|
||||
return 'theme';
|
||||
}
|
||||
|
||||
if ( $this->string_contains( $file_full_path, $this->plugin_dir ) || $this->string_contains( $file_full_path, $this->languages_plugin_dir ) ) {
|
||||
return 'plugin';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_full_path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_component_active( $file_full_path ) {
|
||||
list( $type, $id ) = $this->find_details( $file_full_path );
|
||||
|
||||
if ( 'other' === $type ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! $id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'plugin' === $type ) {
|
||||
return is_plugin_active( $id );
|
||||
} else {
|
||||
return get_stylesheet_directory() === get_theme_root() . '/' . $id;
|
||||
}
|
||||
}
|
||||
|
||||
private function string_contains( $haystack, $needle ) {
|
||||
return false !== strpos( $haystack, $needle );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Components_Find_Plugin implements WPML_ST_Translations_File_Components_Find {
|
||||
/** @var WPML_Debug_BackTrace */
|
||||
private $debug_backtrace;
|
||||
|
||||
/** @var string */
|
||||
private $plugin_dir;
|
||||
|
||||
/** @var array */
|
||||
private $plugin_ids;
|
||||
|
||||
/**
|
||||
* @param WPML_Debug_BackTrace $debug_backtrace
|
||||
*/
|
||||
public function __construct( WPML_Debug_BackTrace $debug_backtrace ) {
|
||||
$this->debug_backtrace = $debug_backtrace;
|
||||
$this->plugin_dir = realpath( WPML_PLUGINS_DIR );
|
||||
}
|
||||
|
||||
public function find_id( $file ) {
|
||||
$directory = $this->find_plugin_directory( $file );
|
||||
if ( ! $directory ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->get_plugin_id_by_directory( $directory );
|
||||
}
|
||||
|
||||
private function find_plugin_directory( $file ) {
|
||||
if ( false !== strpos( $file, $this->plugin_dir ) ) {
|
||||
return $this->extract_plugin_directory( $file );
|
||||
}
|
||||
|
||||
return $this->find_plugin_directory_in_backtrace();
|
||||
}
|
||||
|
||||
private function find_plugin_directory_in_backtrace() {
|
||||
$file = $this->find_file_in_backtrace();
|
||||
if ( ! $file ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->extract_plugin_directory( $file );
|
||||
}
|
||||
|
||||
private function find_file_in_backtrace() {
|
||||
$stack = $this->debug_backtrace->get_backtrace();
|
||||
|
||||
foreach ( $stack as $call ) {
|
||||
if ( isset( $call['function'] ) && 'load_plugin_textdomain' === $call['function'] ) {
|
||||
return $call['file'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extract_plugin_directory( $file_path ) {
|
||||
$dir = ltrim( str_replace( $this->plugin_dir, '', $file_path ), DIRECTORY_SEPARATOR );
|
||||
$dir = explode( DIRECTORY_SEPARATOR, $dir );
|
||||
|
||||
return trim( $dir[0], DIRECTORY_SEPARATOR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $directory
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_plugin_id_by_directory( $directory ) {
|
||||
foreach ( $this->get_plugin_ids() as $plugin_id ) {
|
||||
if ( 0 === strpos( $plugin_id, $directory . '/' ) ) {
|
||||
return $plugin_id;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function get_plugin_ids() {
|
||||
if ( null === $this->plugin_ids ) {
|
||||
$this->plugin_ids = array_keys( get_plugins() );
|
||||
}
|
||||
|
||||
return $this->plugin_ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Components_Find_Theme implements WPML_ST_Translations_File_Components_Find {
|
||||
/** @var WPML_Debug_BackTrace */
|
||||
private $debug_backtrace;
|
||||
|
||||
/** @var WPML_File $file */
|
||||
private $file;
|
||||
|
||||
/** @var string */
|
||||
private $theme_dir;
|
||||
|
||||
/**
|
||||
* @param WPML_Debug_BackTrace $debug_backtrace
|
||||
* @param WPML_File $file
|
||||
*/
|
||||
public function __construct( WPML_Debug_BackTrace $debug_backtrace, WPML_File $file ) {
|
||||
$this->debug_backtrace = $debug_backtrace;
|
||||
$this->file = $file;
|
||||
|
||||
$this->theme_dir = $this->file->fix_dir_separator( get_theme_root() );
|
||||
}
|
||||
|
||||
public function find_id( $file ) {
|
||||
return $this->find_theme_directory( $file );
|
||||
}
|
||||
|
||||
private function find_theme_directory( $file ) {
|
||||
if ( false !== strpos( $file, $this->theme_dir ) ) {
|
||||
return $this->extract_theme_directory( $file );
|
||||
}
|
||||
|
||||
return $this->find_theme_directory_in_backtrace();
|
||||
}
|
||||
|
||||
private function find_theme_directory_in_backtrace() {
|
||||
$file = $this->find_file_in_backtrace();
|
||||
if ( ! $file ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->extract_theme_directory( $file );
|
||||
}
|
||||
|
||||
private function find_file_in_backtrace() {
|
||||
$stack = $this->debug_backtrace->get_backtrace();
|
||||
|
||||
foreach ( $stack as $call ) {
|
||||
if ( isset( $call['function'] ) && 'load_theme_textdomain' === $call['function'] ) {
|
||||
return $call['file'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extract_theme_directory( $file_path ) {
|
||||
$file_path = $this->file->fix_dir_separator( $file_path );
|
||||
$dir = ltrim( str_replace( $this->theme_dir, '', $file_path ), DIRECTORY_SEPARATOR );
|
||||
$dir = explode( DIRECTORY_SEPARATOR, $dir );
|
||||
|
||||
return trim( $dir[0], DIRECTORY_SEPARATOR );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
interface WPML_ST_Translations_File_Components_Find {
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function find_id( $file );
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Dictionary_Storage_Table implements WPML_ST_Translations_File_Dictionary_Storage {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/** @var null|array */
|
||||
private $data;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Entry[] */
|
||||
private $new_data = array();
|
||||
|
||||
/** @var WPML_ST_Translations_File_Entry[] */
|
||||
private $updated_data = array();
|
||||
|
||||
/**
|
||||
* @param wpdb $wpdb
|
||||
*/
|
||||
public function __construct( wpdb $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'shutdown', array( $this, 'persist' ), 11, 0 );
|
||||
}
|
||||
|
||||
public function save( WPML_ST_Translations_File_Entry $file ) {
|
||||
$this->load_data();
|
||||
|
||||
$is_new = ! isset( $this->data[ $file->get_path() ] );
|
||||
$this->data[ $file->get_path() ] = $file;
|
||||
|
||||
if ( $is_new ) {
|
||||
$this->new_data[] = $file;
|
||||
} else {
|
||||
$this->updated_data[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to postpone saving of real data because target table may not be created yet by migration process
|
||||
*/
|
||||
public function persist() {
|
||||
foreach ( $this->new_data as $file ) {
|
||||
$sql = "INSERT IGNORE INTO {$this->wpdb->prefix}icl_mo_files_domains ( file_path, file_path_md5, domain, status, num_of_strings, last_modified, component_type, component_id ) VALUES ( %s, %s, %s, %s, %d, %d, %s, %s )";
|
||||
$this->wpdb->query(
|
||||
$this->wpdb->prepare(
|
||||
$sql,
|
||||
array(
|
||||
$file->get_path(),
|
||||
$file->get_path_hash(),
|
||||
$file->get_domain(),
|
||||
$file->get_status(),
|
||||
$file->get_imported_strings_count(),
|
||||
$file->get_last_modified(),
|
||||
$file->get_component_type(),
|
||||
$file->get_component_id(),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $this->updated_data as $file ) {
|
||||
$this->wpdb->update(
|
||||
$this->wpdb->prefix . 'icl_mo_files_domains',
|
||||
$this->file_to_array( $file ),
|
||||
array(
|
||||
'file_path_md5' => $file->get_path_hash(),
|
||||
),
|
||||
array( '%s', '%s', '%d', '%d' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Entry $file
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function file_to_array( WPML_ST_Translations_File_Entry $file, array $data = array() ) {
|
||||
$data['domain'] = $file->get_domain();
|
||||
$data['status'] = $file->get_status();
|
||||
$data['num_of_strings'] = $file->get_imported_strings_count();
|
||||
$data['last_modified'] = $file->get_last_modified();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function find( $path = null, $status = null ) {
|
||||
$this->load_data();
|
||||
|
||||
if ( null !== $path ) {
|
||||
return isset( $this->data[ $path ] ) ? array( $this->data[ $path ] ) : array();
|
||||
}
|
||||
|
||||
if ( null === $status ) {
|
||||
return array_values( $this->data );
|
||||
}
|
||||
|
||||
if ( ! is_array( $status ) ) {
|
||||
$status = array( $status );
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ( $this->data as $file ) {
|
||||
if ( in_array( $file->get_status(), $status, true ) ) {
|
||||
$result[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function load_data() {
|
||||
if ( null === $this->data ) {
|
||||
$this->data = array();
|
||||
$sql = "SELECT * FROM {$this->wpdb->prefix}icl_mo_files_domains";
|
||||
$rowset = $this->wpdb->get_results( $sql );
|
||||
|
||||
foreach ( $rowset as $row ) {
|
||||
$file = new WPML_ST_Translations_File_Entry( $row->file_path, $row->domain, $row->status );
|
||||
$file->set_imported_strings_count( $row->num_of_strings );
|
||||
$file->set_last_modified( $row->last_modified );
|
||||
$file->set_component_type( $row->component_type );
|
||||
$file->set_component_id( $row->component_id );
|
||||
|
||||
$this->data[ $file->get_path() ] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function reset() {
|
||||
$this->data = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
interface WPML_ST_Translations_File_Dictionary_Storage {
|
||||
|
||||
public function save( WPML_ST_Translations_File_Entry $file );
|
||||
|
||||
/**
|
||||
* @param null|string $path
|
||||
* @param null|string $status
|
||||
*
|
||||
* @return WPML_ST_Translations_File_Entry[]
|
||||
*/
|
||||
public function find( $path = null, $status = null );
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_ST_Translations_File {
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Translation[]
|
||||
*/
|
||||
public function get_translations();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_JED implements IWPML_ST_Translations_File {
|
||||
|
||||
const EMPTY_PROPERTY_NAME = '_empty_';
|
||||
const DECODED_EOT_CHAR = '"\u0004"';
|
||||
const PLURAL_SUFFIX_PATTERN = ' [plural %d]';
|
||||
|
||||
/** @var string $filepath */
|
||||
private $filepath;
|
||||
|
||||
/** @var string $decoded_eot_char */
|
||||
private $decoded_eot_char;
|
||||
|
||||
public function __construct( $filepath ) {
|
||||
$this->filepath = $filepath;
|
||||
$this->decoded_eot_char = json_decode( self::DECODED_EOT_CHAR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Translation[]
|
||||
*/
|
||||
public function get_translations() {
|
||||
$translations = array();
|
||||
$data = json_decode( file_get_contents( $this->filepath ) );
|
||||
|
||||
if ( isset( $data->locale_data->messages ) ) {
|
||||
|
||||
$entries = (array) $data->locale_data->messages;
|
||||
unset( $entries[ self::EMPTY_PROPERTY_NAME ] );
|
||||
|
||||
foreach ( $entries as $str => $str_data ) {
|
||||
$str_data = (array) $str_data;
|
||||
|
||||
if ( ! isset( $str_data[0] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list( $str, $context ) = $this->get_string_and_context( $str );
|
||||
$count_translations = count( $str_data );
|
||||
$translations[] = new WPML_ST_Translations_File_Translation( $str, $str_data[0], $context );
|
||||
|
||||
if ( $count_translations > 1 ) {
|
||||
/**
|
||||
* The strings coming after the first element are the plural translations.
|
||||
* As we don't have the information about the original plural in the JED file,
|
||||
* we will add a suffix to the original singular string.
|
||||
*/
|
||||
for ( $i = 1; $i < $count_translations; $i++ ) {
|
||||
$plural_str = $str . sprintf( self::PLURAL_SUFFIX_PATTERN, $i );
|
||||
$translations[] = new WPML_ST_Translations_File_Translation( $plural_str, $str_data[ $i ], $context );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* The context is the first part of the string separated with the EOT char (\u0004)
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_string_and_context( $string ) {
|
||||
$context = '';
|
||||
$parts = explode( $this->decoded_eot_char, $string );
|
||||
|
||||
if ( count( $parts ) > 1 ) {
|
||||
$context = $parts[0];
|
||||
$string = $parts[1];
|
||||
}
|
||||
|
||||
return array( $string, $context );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Locale {
|
||||
|
||||
const PATTERN_SEARCH_LANG_JSON = '#DOMAIN_PLACEHOLDER(LOCALES_PLACEHOLDER)-[-_a-z0-9]+\.json$#i';
|
||||
|
||||
/** @var \SitePress */
|
||||
private $sitepress;
|
||||
|
||||
/** @var \WPML_Locale */
|
||||
private $locale;
|
||||
|
||||
/**
|
||||
* @param SitePress $sitepress
|
||||
* @param WPML_Locale $locale
|
||||
*/
|
||||
public function __construct( SitePress $sitepress, WPML_Locale $locale ) {
|
||||
$this->sitepress = $sitepress;
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* It extracts language code from mo file path, examples
|
||||
* '/wp-content/languages/admin-pl_PL.mo' => 'pl'
|
||||
* '/wp-content/plugins/sitepress/sitepress-hr.mo' => 'hr'
|
||||
* '/wp-content/languages/fr_FR-4gh5e6d3g5s33d6gg51zas2.json' => 'fr_FR'
|
||||
* '/wp-content/plugins/my-plugin/languages/-my-plugin-fr_FR-my-handler.json' => 'fr_FR'
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string $domain
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get( $filepath, $domain ) {
|
||||
switch ( $this->get_extension( $filepath ) ) {
|
||||
case 'mo':
|
||||
return $this->get_from_mo_file( $filepath );
|
||||
case 'json':
|
||||
return $this->get_from_json_file( $filepath, $domain );
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_extension( $filepath ) {
|
||||
return wpml_collect( pathinfo( $filepath ) )->get( 'extension', null );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_from_mo_file( $filepath ) {
|
||||
return $this->get_locales()
|
||||
->first(
|
||||
function ( $locale ) use ( $filepath ) {
|
||||
return strpos( $filepath, $locale . '.mo' );
|
||||
},
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
* @param string $domain
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_from_json_file( $filepath, $domain ) {
|
||||
$original_domain = $this->get_original_domain_for_json( $filepath, $domain );
|
||||
$domain_replace = 'default' === $original_domain ? '' : $original_domain . '-';
|
||||
$locales = $this->get_locales()->implode( '|' );
|
||||
|
||||
$searches['native-file'] = '#' . $domain_replace . '(' . $locales . ')-[-_a-z0-9]+\.json$#i';
|
||||
$searches['wpml-file'] = '#' . $domain . '-(' . $locales . ').json#i';
|
||||
|
||||
foreach ( $searches as $search ) {
|
||||
if ( preg_match( $search, $filepath, $matches ) && isset( $matches[1] ) ) {
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* We need the original domain name to refine the regex pattern.
|
||||
* Unfortunately, the domain is concatenated with the script handler
|
||||
* in the import queue table. That's why we need to retrieve the original
|
||||
* domain from the registration domain and the filepath.
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string $domain
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_original_domain_for_json( $filepath, $domain ) {
|
||||
$filename = basename( $filepath );
|
||||
|
||||
/**
|
||||
* Case of WP JED files:
|
||||
* - filename: de_DE-73f9977556584a369800e775b48f3dbe.json
|
||||
* - domain: default-some-script
|
||||
* => original_domain: default
|
||||
*/
|
||||
if ( 0 === strpos( $domain, 'default' ) && 0 !== strpos( $filename, 'default' ) ) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Case of 3rd part JED files:
|
||||
* - filename: plugin-domain-de_DE-script-handler.json
|
||||
* - domain: plugin-domain-script-handler
|
||||
* => original_domain: plugin-domain
|
||||
*/
|
||||
$filename_parts = explode( '-', $filename );
|
||||
$domain_parts = explode( '-', $domain );
|
||||
$original_domain_parts = array();
|
||||
|
||||
foreach ( $domain_parts as $i => $part ) {
|
||||
if ( $filename_parts[ $i ] !== $part ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$original_domain_parts[] = $part;
|
||||
}
|
||||
|
||||
return implode( '-', $original_domain_parts );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WPML\Collect\Support\Collection
|
||||
*/
|
||||
private function get_locales() {
|
||||
return \wpml_collect( $this->sitepress->get_active_languages() )
|
||||
->keys()
|
||||
->map( [ $this->locale, 'get_locale' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_MO implements IWPML_ST_Translations_File {
|
||||
|
||||
/** @var string $filepath */
|
||||
private $filepath;
|
||||
|
||||
/**
|
||||
* @param string $filepath
|
||||
*/
|
||||
public function __construct( $filepath ) {
|
||||
$this->filepath = $filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Translation[]
|
||||
*/
|
||||
public function get_translations() {
|
||||
$translations = array();
|
||||
$mo = new MO();
|
||||
$pomo_reader = new POMO_CachedFileReader( $this->filepath );
|
||||
|
||||
$mo->import_from_reader( $pomo_reader );
|
||||
|
||||
foreach ( $mo->entries as $str => $v ) {
|
||||
$str = str_replace( "\n", '\n', $v->singular );
|
||||
$translations[] = new WPML_ST_Translations_File_Translation( $str, $v->translations[0], $v->context );
|
||||
|
||||
if ( $v->is_plural ) {
|
||||
$str = str_replace( "\n", '\n', $v->plural );
|
||||
$translation = ! empty( $v->translations[1] ) ? $v->translations[1] : $v->translations[0];
|
||||
$translations[] = new WPML_ST_Translations_File_Translation( $str, $translation, $v->context );
|
||||
}
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Component_Stats_Update_Hooks {
|
||||
/** @var WPML_ST_Strings_Stats */
|
||||
private $string_stats;
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Strings_Stats $string_stats
|
||||
*/
|
||||
public function __construct( WPML_ST_Strings_Stats $string_stats ) {
|
||||
$this->string_stats = $string_stats;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_stats' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Entry $file
|
||||
*/
|
||||
public function update_stats( WPML_ST_Translations_File_Entry $file ) {
|
||||
if ( $file->get_component_id() ) {
|
||||
$this->string_stats->update( $file->get_component_id(), $file->get_component_type(), $file->get_domain() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
use WPML\ST\TranslationFile\EntryQueries;
|
||||
|
||||
class WPML_ST_Translations_File_Dictionary {
|
||||
/** @var WPML_ST_Translations_File_Dictionary_Storage */
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Dictionary_Storage $storage
|
||||
*/
|
||||
public function __construct( WPML_ST_Translations_File_Dictionary_Storage $storage ) {
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_path
|
||||
*
|
||||
* @return WPML_ST_Translations_File_Entry|null
|
||||
*/
|
||||
public function find_file_info_by_path( $file_path ) {
|
||||
$result = $this->storage->find( $file_path );
|
||||
if ( $result ) {
|
||||
return current( $result );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Entry $file
|
||||
*/
|
||||
public function save( WPML_ST_Translations_File_Entry $file ) {
|
||||
$this->storage->save( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Entry[]
|
||||
*/
|
||||
public function get_not_imported_files() {
|
||||
return $this->storage->find(
|
||||
null,
|
||||
[
|
||||
WPML_ST_Translations_File_Entry::NOT_IMPORTED,
|
||||
WPML_ST_Translations_File_Entry::PARTLY_IMPORTED,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function clear_skipped() {
|
||||
$skipped = wpml_collect( $this->storage->find( null, [ WPML_ST_Translations_File_Entry::SKIPPED ] ) );
|
||||
$skipped->each(
|
||||
function ( WPML_ST_Translations_File_Entry $entry ) {
|
||||
$entry->set_status( WPML_ST_Translations_File_Entry::NOT_IMPORTED );
|
||||
$this->storage->save( $entry );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Entry[]
|
||||
*/
|
||||
public function get_imported_files() {
|
||||
return $this->storage->find( null, WPML_ST_Translations_File_Entry::IMPORTED );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $extension
|
||||
* @param null|string $locale
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_domains( $extension = null, $locale = null ) {
|
||||
$files = wpml_collect( $this->storage->find() );
|
||||
|
||||
if ( $extension ) {
|
||||
$files = $files->filter( EntryQueries::isExtension( $extension ) );
|
||||
}
|
||||
if ( $locale ) {
|
||||
$files = $files->filter(
|
||||
function ( WPML_ST_Translations_File_Entry $file ) use ( $locale ) {
|
||||
return $file->get_file_locale() === $locale;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $files->map( EntryQueries::getDomain() )
|
||||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Entry {
|
||||
|
||||
const NOT_IMPORTED = 'not_imported';
|
||||
const IMPORTED = 'imported';
|
||||
const PARTLY_IMPORTED = 'partly_imported';
|
||||
const FINISHED = 'finished';
|
||||
const SKIPPED = 'skipped';
|
||||
|
||||
const PATTERN_SEARCH_LANG_MO = '#[-]?([a-z]+[_A-Z]*)\.mo$#i';
|
||||
const PATTERN_SEARCH_LANG_JSON = '#([a-z]+[_A-Z]*)-[-a-z0-9]+\.json$#i';
|
||||
|
||||
/** @var string */
|
||||
private $path;
|
||||
|
||||
/** @var string */
|
||||
private $domain;
|
||||
|
||||
/** @var int */
|
||||
private $status;
|
||||
|
||||
/** @var int */
|
||||
private $imported_strings_count = 0;
|
||||
|
||||
/** @var int */
|
||||
private $last_modified;
|
||||
|
||||
/** @var string */
|
||||
private $component_type;
|
||||
|
||||
/** @var string */
|
||||
private $component_id;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $domain
|
||||
* @param string $status
|
||||
*/
|
||||
public function __construct( $path, $domain, $status = self::NOT_IMPORTED ) {
|
||||
if ( ! is_string( $path ) ) {
|
||||
throw new InvalidArgumentException( 'MO File path must be string type' );
|
||||
}
|
||||
if ( ! is_string( $domain ) ) {
|
||||
throw new InvalidArgumentException( 'MO File domain must be string type' );
|
||||
}
|
||||
|
||||
$this->path = $this->convert_to_relative_path( $path );
|
||||
$this->domain = $domain;
|
||||
|
||||
$this->validate_status( $status );
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* We can't rely on ABSPATH in out tests
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function convert_to_relative_path( $path ) {
|
||||
$parts = explode( DIRECTORY_SEPARATOR, $this->fix_dir_separator( WP_CONTENT_DIR ) );
|
||||
|
||||
return str_replace( WP_CONTENT_DIR, end( $parts ), $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function get_full_path() {
|
||||
$wp_content_dir = $this->fix_dir_separator( WP_CONTENT_DIR );
|
||||
$parts = explode( DIRECTORY_SEPARATOR, $wp_content_dir );
|
||||
|
||||
return str_replace( end( $parts ), $wp_content_dir, $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_path_hash() {
|
||||
return md5( $this->path );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_domain() {
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_status() {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*/
|
||||
public function set_status( $status ) {
|
||||
$this->validate_status( $status );
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_imported_strings_count() {
|
||||
return $this->imported_strings_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $imported_strings_count
|
||||
*/
|
||||
public function set_imported_strings_count( $imported_strings_count ) {
|
||||
$this->imported_strings_count = (int) $imported_strings_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_last_modified() {
|
||||
return $this->last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $last_modified
|
||||
*/
|
||||
public function set_last_modified( $last_modified ) {
|
||||
$this->last_modified = (int) $last_modified;
|
||||
}
|
||||
|
||||
public function __get( $name ) {
|
||||
if ( in_array( $name, array( 'path', 'domain', 'status', 'imported_strings_count', 'last_modified' ), true ) ) {
|
||||
return $this->$name;
|
||||
}
|
||||
if ( $name === 'path_md5' ) {
|
||||
return $this->get_path_hash();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* It extracts locale from mo file path, examples
|
||||
* '/wp-content/languages/admin-pl_PL.mo' => 'pl'
|
||||
* '/wp-content/plugins/sitepress/sitepress-hr.mo' => 'hr'
|
||||
*
|
||||
* @return null|string
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function get_file_locale() {
|
||||
return \WPML\Container\make( WPML_ST_Translations_File_Locale::class )->get( $this->get_path(), $this->get_domain() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_component_type() {
|
||||
return $this->component_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $component_type
|
||||
*/
|
||||
public function set_component_type( $component_type ) {
|
||||
$this->component_type = $component_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_component_id() {
|
||||
return $this->component_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $component_id
|
||||
*/
|
||||
public function set_component_id( $component_id ) {
|
||||
$this->component_id = $component_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $status
|
||||
*/
|
||||
private function validate_status( $status ) {
|
||||
$allowed_statuses = array(
|
||||
self::NOT_IMPORTED,
|
||||
self::IMPORTED,
|
||||
self::PARTLY_IMPORTED,
|
||||
self::FINISHED,
|
||||
self::SKIPPED,
|
||||
);
|
||||
|
||||
if ( ! in_array( $status, $allowed_statuses, true ) ) {
|
||||
throw new InvalidArgumentException( 'Status of MO file is invalid' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function fix_dir_separator( $path ) {
|
||||
return ( '\\' === DIRECTORY_SEPARATOR ) ? str_replace( '/', '\\', $path ) : str_replace( '\\', '/', $path );
|
||||
}
|
||||
|
||||
public function get_extension() {
|
||||
return pathinfo( $this->path, PATHINFO_EXTENSION );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
use WPML\ST\TranslationFile\EntryQueries;
|
||||
use WPML\ST\TranslationFile\QueueFilter;
|
||||
|
||||
class WPML_ST_Translations_File_Queue {
|
||||
const DEFAULT_LIMIT = 20000;
|
||||
const TIME_LIMIT = 10; // seconds
|
||||
const LOCK_FIELD = '_wpml_st_file_scan_in_progress';
|
||||
|
||||
/** @var WPML_ST_Translations_File_Dictionary */
|
||||
private $file_dictionary;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Scan */
|
||||
private $file_scan;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Scan_Storage */
|
||||
private $file_scan_storage;
|
||||
|
||||
/** @var WPML_Language_Records */
|
||||
private $language_records;
|
||||
|
||||
/** @var int */
|
||||
private $limit;
|
||||
|
||||
private $transient;
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Dictionary $file_dictionary
|
||||
* @param WPML_ST_Translations_File_Scan $file_scan
|
||||
* @param WPML_ST_Translations_File_Scan_Storage $file_scan_storage
|
||||
* @param WPML_Language_Records $language_records
|
||||
* @param int $limit
|
||||
* @param WPML_Transient $transient
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_ST_Translations_File_Dictionary $file_dictionary,
|
||||
WPML_ST_Translations_File_Scan $file_scan,
|
||||
WPML_ST_Translations_File_Scan_Storage $file_scan_storage,
|
||||
WPML_Language_Records $language_records,
|
||||
$limit,
|
||||
WPML_Transient $transient
|
||||
) {
|
||||
$this->file_dictionary = $file_dictionary;
|
||||
$this->file_scan = $file_scan;
|
||||
$this->file_scan_storage = $file_scan_storage;
|
||||
$this->language_records = $language_records;
|
||||
$this->limit = $limit;
|
||||
$this->transient = $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueueFilter|null $queueFilter
|
||||
*/
|
||||
public function import( QueueFilter $queueFilter = null ) {
|
||||
$this->file_dictionary->clear_skipped();
|
||||
$files = $this->file_dictionary->get_not_imported_files();
|
||||
|
||||
if ( count( $files ) ) {
|
||||
$this->lock();
|
||||
|
||||
$start_time = time();
|
||||
$imported = 0;
|
||||
foreach ( $files as $file ) {
|
||||
if ( $imported >= $this->limit || time() - $start_time > self::TIME_LIMIT ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! $queueFilter || $queueFilter->isSelected( $file ) ) {
|
||||
|
||||
$translations = $this->file_scan->load_translations( $file->get_full_path() );
|
||||
|
||||
try {
|
||||
$number_of_translations = count( $translations );
|
||||
if ( ! $number_of_translations ) {
|
||||
throw new RuntimeException( 'File is empty' );
|
||||
}
|
||||
|
||||
$translations = $this->constrain_translations_number(
|
||||
$translations,
|
||||
$file->get_imported_strings_count(),
|
||||
$this->limit - $imported
|
||||
);
|
||||
|
||||
$imported += $imported_in_file = count( $translations );
|
||||
|
||||
$this->file_scan_storage->save(
|
||||
$translations,
|
||||
$file->get_domain(),
|
||||
$this->map_language_code( $file->get_file_locale() )
|
||||
);
|
||||
|
||||
$file->set_imported_strings_count( $file->get_imported_strings_count() + $imported_in_file );
|
||||
|
||||
if ( $file->get_imported_strings_count() >= $number_of_translations ) {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::IMPORTED );
|
||||
} else {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::PARTLY_IMPORTED );
|
||||
}
|
||||
} catch ( WPML_ST_Bulk_Strings_Insert_Exception $e ) {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::PARTLY_IMPORTED );
|
||||
break;
|
||||
} catch ( Exception $e ) {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::IMPORTED );
|
||||
}
|
||||
} else {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::SKIPPED );
|
||||
}
|
||||
$this->file_dictionary->save( $file );
|
||||
|
||||
do_action( 'wpml_st_translations_file_post_import', $file );
|
||||
}
|
||||
|
||||
$this->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function map_language_code( $locale ) {
|
||||
$language_code = $this->language_records->get_language_code( $locale );
|
||||
|
||||
if ( $language_code ) {
|
||||
return $language_code;
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_completed() {
|
||||
return 0 === count( $this->file_dictionary->get_not_imported_files() ) &&
|
||||
0 < count( $this->file_dictionary->get_imported_files() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_processed() {
|
||||
return wp_list_pluck( $this->file_dictionary->get_imported_files(), 'path' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_processing() {
|
||||
return 0 !== count( $this->file_dictionary->get_not_imported_files() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function get_pending() {
|
||||
return count( $this->file_dictionary->get_not_imported_files() );
|
||||
}
|
||||
|
||||
public function mark_as_finished() {
|
||||
foreach ( $this->file_dictionary->get_imported_files() as $file ) {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::FINISHED );
|
||||
$this->file_dictionary->save( $file );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $translations
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function constrain_translations_number( array $translations, $offset, $limit ) {
|
||||
if ( $limit > count( $translations ) ) {
|
||||
return $translations;
|
||||
}
|
||||
|
||||
return array_slice( $translations, $offset, $limit );
|
||||
}
|
||||
|
||||
public function is_locked() {
|
||||
return (bool) $this->transient->get( self::LOCK_FIELD );
|
||||
}
|
||||
|
||||
private function lock() {
|
||||
$this->transient->set( self::LOCK_FIELD, 1, MINUTE_IN_SECONDS * 5 );
|
||||
}
|
||||
|
||||
private function unlock() {
|
||||
$this->transient->delete( self::LOCK_FIELD );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
use WPML\Element\API\Languages;
|
||||
use WPML\FP\Fns;
|
||||
|
||||
class WPML_ST_Translations_File_Registration {
|
||||
|
||||
const PATH_PATTERN_SEARCH_MO = '#(-)?([a-z]+)([_A-Z]*)\.mo$#i';
|
||||
const PATH_PATTERN_REPLACE_MO = '${1}%s.mo';
|
||||
|
||||
const PATH_PATTERN_SEARCH_JSON = '#(DOMAIN_PLACEHOLDER)([a-z]+)([_A-Z]*)(-[-_a-z0-9]+)\.json$#i';
|
||||
const PATH_PATTERN_REPLACE_JSON = '${1}%s${4}.json';
|
||||
|
||||
/** @var WPML_ST_Translations_File_Dictionary */
|
||||
private $file_dictionary;
|
||||
|
||||
/** @var WPML_File */
|
||||
private $wpml_file;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Component_Details */
|
||||
private $components_find;
|
||||
|
||||
/** @var array */
|
||||
private $active_languages;
|
||||
|
||||
/** @var array */
|
||||
private $cache = array();
|
||||
|
||||
/** @var callable - string->string */
|
||||
private $getWPLocale;
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Dictionary $file_dictionary
|
||||
* @param WPML_File $wpml_file
|
||||
* @param WPML_ST_Translations_File_Component_Details $components_find
|
||||
* @param array $active_languages
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_ST_Translations_File_Dictionary $file_dictionary,
|
||||
WPML_File $wpml_file,
|
||||
WPML_ST_Translations_File_Component_Details $components_find,
|
||||
array $active_languages
|
||||
) {
|
||||
$this->file_dictionary = $file_dictionary;
|
||||
$this->wpml_file = $wpml_file;
|
||||
$this->components_find = $components_find;
|
||||
$this->active_languages = $active_languages;
|
||||
$this->getWPLocale = Fns::memorize( Languages::getWPLocale() );
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'override_load_textdomain', array( $this, 'cached_save_mo_file_info' ), 11, 3 );
|
||||
add_filter( 'pre_load_script_translations', array( $this, 'add_json_translations_to_import_queue' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $override
|
||||
* @param string $domain
|
||||
* @param string $mo_file_path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function cached_save_mo_file_info( $override, $domain, $mo_file_path ) {
|
||||
if ( ! isset( $this->cache[ $mo_file_path ] ) ) {
|
||||
$this->cache[ $mo_file_path ] = $this->save_file_info( $domain, $domain, $mo_file_path );
|
||||
}
|
||||
|
||||
return $override;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|false $translations translations in the JED format
|
||||
* @param string|false $file
|
||||
* @param string $handle
|
||||
* @param string $original_domain
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public function add_json_translations_to_import_queue( $translations, $file, $handle, $original_domain ) {
|
||||
if ( $file && ! isset( $this->cache[ $file ] ) ) {
|
||||
$registration_domain = WPML_ST_JED_Domain::get( $original_domain, $handle );
|
||||
$this->cache[ $file ] = $this->save_file_info( $original_domain, $registration_domain, $file );
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $original_domain
|
||||
* @param string $registration_domain which can be composed with the script-handle for JED files
|
||||
* @param string $file_path
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
private function save_file_info( $original_domain, $registration_domain, $file_path ) {
|
||||
try {
|
||||
$file_path_pattern = $this->get_file_path_pattern( $file_path, $original_domain );
|
||||
|
||||
foreach ( $this->active_languages as $lang_data ) {
|
||||
$file_path_in_lang = sprintf( $file_path_pattern, $lang_data['default_locale'] );
|
||||
$this->register_single_file( $registration_domain, $file_path_in_lang );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file_path
|
||||
* @param string $original_domain
|
||||
*
|
||||
* @return string|string[]|null
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function get_file_path_pattern( $file_path, $original_domain ) {
|
||||
$pathinfo = pathinfo( $file_path );
|
||||
$file_type = isset( $pathinfo['extension'] ) ? $pathinfo['extension'] : null;
|
||||
|
||||
switch ( $file_type ) {
|
||||
case 'mo':
|
||||
return preg_replace( self::PATH_PATTERN_SEARCH_MO, self::PATH_PATTERN_REPLACE_MO, $file_path );
|
||||
|
||||
case 'json':
|
||||
$domain_replace = 'default' === $original_domain ? '' : $original_domain . '-';
|
||||
$search_pattern = str_replace( 'DOMAIN_PLACEHOLDER', $domain_replace, self::PATH_PATTERN_SEARCH_JSON );
|
||||
return preg_replace( $search_pattern, self::PATH_PATTERN_REPLACE_JSON, $file_path );
|
||||
}
|
||||
|
||||
throw new RuntimeException( 'The "' . $file_type . '" file type is not supported for registration' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $registration_domain
|
||||
* @param string $file_path
|
||||
*/
|
||||
private function register_single_file( $registration_domain, $file_path ) {
|
||||
if (
|
||||
! $this->wpml_file->file_exists( $file_path ) ||
|
||||
$this->isGeneratedFile( $file_path )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relative_path = $this->wpml_file->get_relative_path( $file_path );
|
||||
$last_modified = $this->wpml_file->get_file_modified_timestamp( $file_path );
|
||||
$file = $this->file_dictionary->find_file_info_by_path( $relative_path );
|
||||
|
||||
if ( ! $file ) {
|
||||
if ( ! $this->components_find->is_component_active( $file_path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = new WPML_ST_Translations_File_Entry( $relative_path, $registration_domain );
|
||||
$file->set_last_modified( $last_modified );
|
||||
|
||||
list( $component_type, $component_id ) = $this->components_find->find_details( $file_path );
|
||||
$file->set_component_type( $component_type );
|
||||
$file->set_component_id( $component_id );
|
||||
|
||||
$this->file_dictionary->save( $file );
|
||||
} elseif ( $file->get_last_modified() !== $last_modified ) {
|
||||
$file->set_status( WPML_ST_Translations_File_Entry::NOT_IMPORTED );
|
||||
$file->set_last_modified( $last_modified );
|
||||
$file->set_imported_strings_count( 0 );
|
||||
|
||||
$this->file_dictionary->save( $file );
|
||||
}
|
||||
}
|
||||
|
||||
private function isGeneratedFile( $path ) {
|
||||
return strpos(
|
||||
$this->wpml_file->fix_dir_separator( $path ),
|
||||
$this->wpml_file->fix_dir_separator( WPML\ST\TranslationFile\Manager::getSubdir() )
|
||||
) === 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
use function WPML\Container\make;
|
||||
|
||||
class WPML_ST_Translations_File_Scan_Factory {
|
||||
private $dictionary;
|
||||
private $queue;
|
||||
private $storage;
|
||||
private $wpml_file;
|
||||
private $find_aggregate;
|
||||
|
||||
public function check_core_dependencies() {
|
||||
global $wpdb;
|
||||
$string_index_check = new WPML_ST_Upgrade_String_Index( $wpdb );
|
||||
$scan_ui_block = new WPML_ST_Translations_File_Scan_UI_Block( wpml_get_admin_notices() );
|
||||
if ( ! $string_index_check->is_uc_domain_name_context_index_unique() ) {
|
||||
$scan_ui_block->block_ui();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$scan_ui_block->unblock_ui();
|
||||
|
||||
if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'get_plugins' ) ) {
|
||||
$file = ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
if ( file_exists( $file ) ) {
|
||||
require_once $file;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$wpml_file = $this->get_wpml_file();
|
||||
|
||||
return method_exists( $wpml_file, 'get_relative_path' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function create_hooks() {
|
||||
$st_upgrade = WPML\Container\make( 'WPML_ST_Upgrade' );
|
||||
if ( $st_upgrade->has_command_been_executed( 'WPML_ST_Upgrade_MO_Scanning' ) ) {
|
||||
return [
|
||||
'stats-update' => $this->get_stats_update(),
|
||||
'string-status-update' => $this->get_string_status_update(),
|
||||
'mo-file-registration' => $this->get_translations_file_registration(),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Queue
|
||||
*/
|
||||
public function create_queue() {
|
||||
if ( ! $this->queue ) {
|
||||
global $wpdb;
|
||||
|
||||
$charset_filter_factory = new WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory( $wpdb );
|
||||
|
||||
$this->queue = new WPML_ST_Translations_File_Queue(
|
||||
$this->create_dictionary(),
|
||||
new WPML_ST_Translations_File_Scan( $charset_filter_factory ),
|
||||
$this->create_storage(),
|
||||
new WPML_Language_Records( $wpdb ),
|
||||
$this->get_scan_limit(),
|
||||
new WPML_Transient()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Scan_Storage
|
||||
*/
|
||||
private function create_storage() {
|
||||
if ( ! $this->storage ) {
|
||||
global $wpdb;
|
||||
|
||||
$this->storage = new WPML_ST_Translations_File_Scan_Storage( $wpdb, new WPML_ST_Bulk_Strings_Insert( $wpdb ) );
|
||||
}
|
||||
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Dictionary
|
||||
*/
|
||||
private function create_dictionary() {
|
||||
if ( ! $this->dictionary ) {
|
||||
global $sitepress;
|
||||
|
||||
/** @var WPML_ST_Translations_File_Dictionary_Storage_Table $table_storage */
|
||||
$table_storage = make( WPML_ST_Translations_File_Dictionary_Storage_Table::class );
|
||||
|
||||
$st_upgrade = new WPML_ST_Upgrade( $sitepress );
|
||||
if ( $st_upgrade->has_command_been_executed( 'WPML_ST_Upgrade_MO_Scanning' ) ) {
|
||||
$table_storage->add_hooks();
|
||||
}
|
||||
|
||||
$this->dictionary = new WPML_ST_Translations_File_Dictionary( $table_storage );
|
||||
}
|
||||
|
||||
return $this->dictionary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function get_scan_limit() {
|
||||
$limit = WPML_ST_Translations_File_Queue::DEFAULT_LIMIT;
|
||||
if ( defined( 'WPML_ST_MO_SCANNING_LIMIT' ) ) {
|
||||
$limit = WPML_ST_MO_SCANNING_LIMIT;
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
private function get_sitepress() {
|
||||
global $sitepress;
|
||||
|
||||
return $sitepress;
|
||||
}
|
||||
|
||||
private function get_wpml_wp_api() {
|
||||
$sitepress = $this->get_sitepress();
|
||||
if ( ! $sitepress ) {
|
||||
return new WPML_WP_API();
|
||||
}
|
||||
|
||||
return $sitepress->get_wp_api();
|
||||
}
|
||||
|
||||
private function get_wpml_file() {
|
||||
if ( ! $this->wpml_file ) {
|
||||
$this->wpml_file = new WPML_File( $this->get_wpml_wp_api(), new WP_Filesystem_Direct( null ) );
|
||||
}
|
||||
|
||||
return $this->wpml_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Registration
|
||||
*/
|
||||
private function get_translations_file_registration() {
|
||||
return new WPML_ST_Translations_File_Registration(
|
||||
$this->create_dictionary(),
|
||||
$this->get_wpml_file(),
|
||||
$this->get_aggregate_find_component(),
|
||||
$this->get_sitepress()->get_active_languages()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Component_Stats_Update_Hooks
|
||||
*/
|
||||
private function get_stats_update() {
|
||||
global $wpdb;
|
||||
|
||||
return new WPML_ST_Translations_File_Component_Stats_Update_Hooks(
|
||||
new WPML_ST_Strings_Stats( $wpdb, $this->get_sitepress() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_Component_Details
|
||||
*/
|
||||
private function get_aggregate_find_component() {
|
||||
if ( null === $this->find_aggregate ) {
|
||||
$debug_backtrace = new WPML_Debug_BackTrace();
|
||||
|
||||
$this->find_aggregate = new WPML_ST_Translations_File_Component_Details(
|
||||
new WPML_ST_Translations_File_Components_Find_Plugin( $debug_backtrace ),
|
||||
new WPML_ST_Translations_File_Components_Find_Theme( $debug_backtrace, $this->get_wpml_file() ),
|
||||
$this->get_wpml_file()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->find_aggregate;
|
||||
}
|
||||
/**
|
||||
* @return WPML_ST_Translations_File_String_Status_Update
|
||||
*/
|
||||
private function get_string_status_update() {
|
||||
global $wpdb;
|
||||
$num_of_secondary_languages = count( $this->get_sitepress()->get_active_languages() ) - 1;
|
||||
$status_update = new WPML_ST_Translations_File_String_Status_Update( $num_of_secondary_languages, $wpdb );
|
||||
$status_update->add_hooks();
|
||||
|
||||
return $status_update;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan_Storage {
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/** @var WPML_ST_Bulk_Strings_Insert */
|
||||
private $bulk_insert;
|
||||
|
||||
/**
|
||||
* @param wpdb $wpdb
|
||||
* @param WPML_ST_Bulk_Strings_Insert $bulk_insert
|
||||
*/
|
||||
public function __construct( wpdb $wpdb, WPML_ST_Bulk_Strings_Insert $bulk_insert ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->bulk_insert = $bulk_insert;
|
||||
}
|
||||
|
||||
|
||||
public function save( array $translations, $domain, $lang ) {
|
||||
$this->bulk_insert->insert_strings( $this->build_string_collection( $translations, $domain ) );
|
||||
|
||||
$string_translations = $this->build_string_translation_collection(
|
||||
$translations,
|
||||
$lang,
|
||||
$this->get_string_maps( $domain )
|
||||
);
|
||||
|
||||
$this->bulk_insert->insert_string_translations( $string_translations );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Translation[] $translations
|
||||
* @param string $domain
|
||||
*
|
||||
* @return WPML_ST_Models_String[]
|
||||
*/
|
||||
private function build_string_collection( array $translations, $domain ) {
|
||||
$result = array();
|
||||
|
||||
foreach ( $translations as $translation ) {
|
||||
$result[] = new WPML_ST_Models_String(
|
||||
'en',
|
||||
$domain,
|
||||
$translation->get_context(),
|
||||
$translation->get_original(),
|
||||
ICL_TM_NOT_TRANSLATED
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $domain
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_string_maps( $domain ) {
|
||||
$sql = "
|
||||
SELECT id, value, gettext_context FROM {$this->wpdb->prefix}icl_strings
|
||||
WHERE context = %s
|
||||
";
|
||||
|
||||
$rowset = $this->wpdb->get_results( $this->wpdb->prepare( $sql, $domain ) );
|
||||
$result = array();
|
||||
|
||||
foreach ( $rowset as $row ) {
|
||||
$result[ $row->value ][ $row->gettext_context ] = $row->id;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Translation[] $translations
|
||||
* @param string $lang
|
||||
* @param array $value_id_map
|
||||
*
|
||||
* @return WPML_ST_Models_String_Translation[]
|
||||
*/
|
||||
private function build_string_translation_collection( array $translations, $lang, $value_id_map ) {
|
||||
$result = array();
|
||||
|
||||
foreach ( $translations as $translation ) {
|
||||
if ( ! isset( $value_id_map[ $translation->get_original() ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = new WPML_ST_Models_String_Translation(
|
||||
$value_id_map[ $translation->get_original() ][ $translation->get_context() ],
|
||||
$lang,
|
||||
ICL_TM_NOT_TRANSLATED,
|
||||
null,
|
||||
$translation->get_translation()
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan_UI_Block {
|
||||
const NOTICES_GROUP = 'wpml-st-mo-scan';
|
||||
const NOTICES_MO_SCANNING_BLOCKED = 'mo-scanning-blocked';
|
||||
|
||||
/** @var WPML_Notices */
|
||||
private $notices;
|
||||
|
||||
/** @var string */
|
||||
private $link = 'https://wpml.org/faq/how-to-deal-with-error-messages-about-a-broken-table-that-needs-fixing/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlst';
|
||||
|
||||
/**
|
||||
* @param WPML_Notices $notices
|
||||
*/
|
||||
public function __construct( WPML_Notices $notices ) {
|
||||
$this->notices = $notices;
|
||||
}
|
||||
|
||||
public function block_ui() {
|
||||
$this->disable_option();
|
||||
$this->remove_default_notice();
|
||||
$this->display_notice();
|
||||
}
|
||||
|
||||
public function unblock_ui() {
|
||||
if ( is_admin() ) {
|
||||
$this->notices->remove_notice( self::NOTICES_GROUP, self::NOTICES_MO_SCANNING_BLOCKED );
|
||||
}
|
||||
}
|
||||
|
||||
private function disable_option() {
|
||||
add_filter( 'wpml_localization_options_ui_model', array( $this, 'disable_option_handler' ) );
|
||||
}
|
||||
|
||||
public function disable_option_handler( $model ) {
|
||||
if ( ! isset( $model['top_options'][0] ) ) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
$model['top_options'][0]['disabled'] = true;
|
||||
$model['top_options'][0]['message'] = $this->get_short_notice_message();
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
private function get_short_notice_message() {
|
||||
$message = _x(
|
||||
'WPML cannot replace .mo files because of technical problems in the String Translation table.',
|
||||
'MO Import blocked short 1/3',
|
||||
'wpml-string-translation'
|
||||
);
|
||||
|
||||
$message .= ' ' . _x(
|
||||
'WPML support team knows how to fix it.',
|
||||
'MO Import blocked short 2/3',
|
||||
'wpml-string-translation'
|
||||
);
|
||||
|
||||
$message .= ' ' . sprintf(
|
||||
_x(
|
||||
'Please add a message in the relevant <a href="%s" target="_blank" >support thread</a> and we\'ll fix it for you.',
|
||||
'MO Import blocked short 3/3',
|
||||
'wpml-string-translation'
|
||||
),
|
||||
$this->link
|
||||
);
|
||||
|
||||
return '<span class="icl_error_text" >' . $message . '</span>';
|
||||
}
|
||||
|
||||
private function display_notice() {
|
||||
$message = _x(
|
||||
'There is a problem with the String Translation table in your site.',
|
||||
'MO Import blocked 1/4',
|
||||
'wpml-string-translation'
|
||||
);
|
||||
|
||||
$message .= ' ' . _x(
|
||||
'This problem is not causing a problem running the site right now, but can become a critical issue in the future.',
|
||||
'MO Import blocked 2/4',
|
||||
'wpml-string-translation'
|
||||
);
|
||||
|
||||
$message .= ' ' . _x(
|
||||
'WPML support team knows how to fix it.',
|
||||
'MO Import blocked 3/4',
|
||||
'wpml-string-translation'
|
||||
);
|
||||
|
||||
$message .= ' ' . sprintf(
|
||||
_x(
|
||||
'Please add a message in the relevant <a href="%s" target="_blank">support thread</a> and we\'ll fix it for you.',
|
||||
'MO Import blocked 4/4',
|
||||
'wpml-string-translation'
|
||||
),
|
||||
$this->link
|
||||
);
|
||||
|
||||
$notice = $this->notices->create_notice( self::NOTICES_MO_SCANNING_BLOCKED, $message, self::NOTICES_GROUP );
|
||||
|
||||
$notice->set_css_class_types( 'error' );
|
||||
$notice->set_dismissible( false );
|
||||
$restricted_pages = array(
|
||||
'sitepress-multilingual-cms/menu/languages.php',
|
||||
'sitepress-multilingual-cms/menu/menu-sync/menus-sync.php',
|
||||
'sitepress-multilingual-cms/menu/support.php',
|
||||
'sitepress-multilingual-cms/menu/taxonomy-translation.php',
|
||||
'sitepress-multilingual-cms/menu/theme-localization.php',
|
||||
'wpml-media',
|
||||
'wpml-package-management',
|
||||
'wpml-string-translation/menu/string-translation.php',
|
||||
'wpml-sticky-links',
|
||||
'wpml-translation-management/menu/translations-queue.php',
|
||||
'wpml-translation-management/menu/main.php',
|
||||
);
|
||||
$notice->set_restrict_to_pages( $restricted_pages );
|
||||
|
||||
$this->notices->add_notice( $notice );
|
||||
}
|
||||
|
||||
private function remove_default_notice() {
|
||||
$this->notices->remove_notice( WPML_ST_Themes_And_Plugins_Settings::NOTICES_GROUP, WPML_ST_Themes_And_Plugins_Updates::WPML_ST_FASTER_SETTINGS_NOTICE_ID );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Scan {
|
||||
|
||||
/**
|
||||
* @var WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory
|
||||
*/
|
||||
private $charset_filter_factory;
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory $charset_filter_factory
|
||||
*/
|
||||
public function __construct( WPML_ST_Translations_File_Scan_Db_Charset_Filter_Factory $charset_filter_factory ) {
|
||||
$this->charset_filter_factory = $charset_filter_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return WPML_ST_Translations_File_Translation[]
|
||||
*/
|
||||
public function load_translations( $file ) {
|
||||
if ( ! file_exists( $file ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$translations = array();
|
||||
$file_type = pathinfo( $file, PATHINFO_EXTENSION );
|
||||
|
||||
switch ( $file_type ) {
|
||||
case 'mo':
|
||||
$translations_file = new WPML_ST_Translations_File_MO( $file );
|
||||
$translations = $translations_file->get_translations();
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
$translations_file = new WPML_ST_Translations_File_JED( $file );
|
||||
$translations = $translations_file->get_translations();
|
||||
break;
|
||||
}
|
||||
|
||||
$unicode_characters_filter = $this->charset_filter_factory->create();
|
||||
if ( $unicode_characters_filter ) {
|
||||
$translations = $unicode_characters_filter->filter( $translations );
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_String_Status_Update {
|
||||
/** @var int */
|
||||
private $number_of_secondary_languages;
|
||||
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
|
||||
/**
|
||||
* @param int $number_of_secondary_languages
|
||||
* @param wpdb $wpdb
|
||||
*/
|
||||
public function __construct( $number_of_secondary_languages, wpdb $wpdb ) {
|
||||
$this->number_of_secondary_languages = $number_of_secondary_languages;
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_string_statuses' ), 10, 1 );
|
||||
}
|
||||
|
||||
public function update_string_statuses( WPML_ST_Translations_File_Entry $file ) {
|
||||
if ( ! in_array( $file->get_status(), array( WPML_ST_Translations_File_Entry::IMPORTED, WPML_ST_Translations_File_Entry::FINISHED ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = "
|
||||
UPDATE {$this->wpdb->prefix}icl_strings s
|
||||
SET s.status = CASE (
|
||||
SELECT COUNT(t.id) FROM {$this->wpdb->prefix}icl_string_translations t
|
||||
WHERE t.string_id = s.id AND (t.status = %d OR t.mo_string IS NOT NULL)
|
||||
)
|
||||
WHEN %d THEN %d
|
||||
WHEN 0 THEN %d
|
||||
ELSE %d
|
||||
END
|
||||
|
||||
WHERE s.context = %s
|
||||
";
|
||||
|
||||
$sql = $this->wpdb->prepare(
|
||||
$sql,
|
||||
ICL_TM_COMPLETE,
|
||||
$this->number_of_secondary_languages,
|
||||
ICL_TM_COMPLETE,
|
||||
ICL_TM_NOT_TRANSLATED,
|
||||
ICL_TM_IN_PROGRESS,
|
||||
$file->get_domain()
|
||||
);
|
||||
|
||||
$this->wpdb->query( $sql );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Translation {
|
||||
/** @var string */
|
||||
private $original;
|
||||
|
||||
/** @var string */
|
||||
private $translation;
|
||||
|
||||
/** @var string */
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @param string $original
|
||||
* @param string $translation
|
||||
* @param string $context
|
||||
*/
|
||||
public function __construct( $original, $translation, $context = '' ) {
|
||||
$this->original = $original;
|
||||
$this->translation = $translation;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_original() {
|
||||
return $this->original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_translation() {
|
||||
return $this->translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_context() {
|
||||
return $this->context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_Translations_File_Unicode_Characters_Filter {
|
||||
/** @var string */
|
||||
private $pattern;
|
||||
|
||||
public function __construct() {
|
||||
$parts = array(
|
||||
'([0-9|#][\x{20E3}])',
|
||||
'[\x{00ae}|\x{00a9}|\x{203C}|\x{2047}|\x{2048}|\x{2049}|\x{3030}|\x{303D}|\x{2139}|\x{2122}|\x{3297}|\x{3299}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2190}-\x{21FF}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2300}-\x{23FF}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2460}-\x{24FF}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{25A0}-\x{25FF}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2600}-\x{27BF}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2900}-\x{297F}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{2B00}-\x{2BF0}][\x{FE00}-\x{FEFF}]?',
|
||||
'[\x{1F000}-\x{1F6FF}][\x{FE00}-\x{FEFF}]?',
|
||||
);
|
||||
|
||||
$this->pattern = '/' . implode( '|', $parts ) . '/u';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_ST_Translations_File_Translation[] $translations
|
||||
*
|
||||
* @return WPML_ST_Translations_File_Translation[]
|
||||
*/
|
||||
public function filter( array $translations ) {
|
||||
return array_filter( $translations, array( $this, 'is_valid' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_ST_Translations_File_Translation $translation
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid( WPML_ST_Translations_File_Translation $translation ) {
|
||||
if ( preg_match( $this->pattern, $translation->get_original() ) ||
|
||||
preg_match( $this->pattern, $translation->get_translation() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user