first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
<?php
namespace WPML\ST\TranslationFile;
abstract class Builder {
/** @var string $plural_form */
protected $plural_form = 'nplurals=2; plural=n != 1;';
/** @var string $language */
protected $language;
/**
* @param string $language
*
* @return Builder
*/
public function set_language( $language ) {
$this->language = $language;
return $this;
}
/**
* @param string $plural_form
*
* @return Builder
*/
public function set_plural_form( $plural_form ) {
$this->plural_form = $plural_form;
return $this;
}
/**
* @param StringEntity[] $strings
* @return string
*/
abstract public function get_content( array $strings);
}

View File

@@ -0,0 +1,154 @@
<?php
namespace WPML\ST\TranslationFile;
use wpdb;
use WPML\Collect\Support\Collection;
use WPML\ST\Package\Domains as PackageDomains;
use WPML_Admin_Texts;
use function wpml_prepare_in;
use WPML_Slug_Translation;
use WPML_ST_Blog_Name_And_Description_Hooks;
use WPML_ST_Translations_File_Dictionary;
use WPML\ST\Shortcode;
class Domains {
/** @var wpdb $wpdb */
private $wpdb;
/** @var PackageDomains $package_domains */
private $package_domains;
/** @var WPML_ST_Translations_File_Dictionary $file_dictionary */
private $file_dictionary;
/** @var null|Collection $mo_domains */
private static $mo_domains;
/** @var null|Collection $jed_domains */
private static $jed_domains;
/**
* Domains constructor.
*
* @param PackageDomains $package_domains
* @param WPML_ST_Translations_File_Dictionary $file_dictionary
*/
public function __construct(
wpdb $wpdb,
PackageDomains $package_domains,
WPML_ST_Translations_File_Dictionary $file_dictionary
) {
$this->wpdb = $wpdb;
$this->package_domains = $package_domains;
$this->file_dictionary = $file_dictionary;
}
/**
* @return Collection
*/
public function getMODomains() {
if ( ! self::$mo_domains instanceof Collection ) {
$excluded_domains = self::getReservedDomains()
->merge( $this->getJEDDomains() );
$sql = "
SELECT DISTINCT context {$this->getCollateForContextColumn()}
FROM {$this->wpdb->prefix}icl_strings
";
self::$mo_domains = wpml_collect( $this->wpdb->get_col( $sql ) )
->diff( $excluded_domains )
->values();
}
return self::$mo_domains;
}
/**
* @return string
*/
private function getCollateForContextColumn() {
$sql = "
SELECT COLLATION_NAME
FROM information_schema.columns
WHERE TABLE_SCHEMA = '" . DB_NAME . "' AND TABLE_NAME = '{$this->wpdb->prefix}icl_strings' AND COLUMN_NAME = 'context'
";
$collation = $this->wpdb->get_var( $sql );
if ( ! $collation ) {
return '';
}
list( $type ) = explode( '_', $collation );
if ( in_array( $type, [ 'utf8', 'utf8mb4' ] ) ) {
return 'COLLATE ' . $type . '_bin';
}
return '';
}
/**
* Returns a collection of MO domains that
* WPML needs to automatically load.
*
* @return Collection
*/
public function getCustomMODomains() {
$all_mo_domains = $this->getMODomains();
$native_mo_domains = $this->file_dictionary->get_domains( 'mo', get_locale() );
return $all_mo_domains->reject(
function( $domain ) use ( $native_mo_domains ) {
/**
* Admin texts, packages, string shortcodes are handled separately,
* so they are loaded on-demand.
*/
return null === $domain
|| 0 === strpos( $domain, WPML_Admin_Texts::DOMAIN_NAME_PREFIX )
|| $this->package_domains->isPackage( $domain )
|| Shortcode::STRING_DOMAIN === $domain
|| in_array( $domain, $native_mo_domains, true );
}
)->values();
}
/**
* @return Collection
*/
public function getJEDDomains() {
if ( ! self::$jed_domains instanceof Collection ) {
self::$jed_domains = wpml_collect( $this->file_dictionary->get_domains( 'json' ) );
}
return self::$jed_domains;
}
public static function resetCache() {
self::$mo_domains = null;
self::$jed_domains = null;
}
/**
* Domains that are not handled with MO files,
* but have direct DB queries.
*
* @return Collection
*/
public static function getReservedDomains() {
return wpml_collect(
[
WPML_ST_Blog_Name_And_Description_Hooks::STRING_DOMAIN,
]
);
}
/**
* @return Collection
*/
private function getPackageDomains() {
return $this->package_domains->getDomains();
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace WPML\ST\TranslationFile;
use wpdb;
use WPML\Collect\Support\Collection;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
use WPML_Locale;
class DomainsLocalesMapper {
const ALIAS_STRINGS = 's';
const ALIAS_STRING_TRANSLATIONS = 'st';
/** @var wpdb $wpdb */
private $wpdb;
/** @var WPML_Locale $locale */
private $locale;
public function __construct( wpdb $wpdb, WPML_Locale $locale ) {
$this->wpdb = $wpdb;
$this->locale = $locale;
}
/**
* @param array $string_translation_ids
*
* @return Collection of objects with properties `domain` and `locale`
*/
public function get_from_translation_ids( array $string_translation_ids ) {
return $this->get_results_where( self::ALIAS_STRING_TRANSLATIONS, $string_translation_ids );
}
/**
* @param array $string_ids
*
* @return Collection of objects with properties `domain` and `locale`
*/
public function get_from_string_ids( array $string_ids ) {
return $this->get_results_where( self::ALIAS_STRINGS, $string_ids );
}
/**
* @param callable $getActiveLanguages
* @param string $domain
*
* @return array
*/
public function get_from_domain( callable $getActiveLanguages, $domain ) {
$createEntity = function ( $locale ) use ( $domain ) {
return (object) [
'domain' => $domain,
'locale' => $locale,
];
};
return Fns::map( $createEntity, Obj::values( Lst::pluck( 'default_locale', $getActiveLanguages() ) ) );
}
/**
* @param string $table_alias
* @param array $ids
*
* @return Collection
*/
private function get_results_where( $table_alias, array $ids ) {
$results = [];
if ( array_filter( $ids ) ) {
$results = $this->wpdb->get_results(
"
SELECT DISTINCT
s.context AS domain,
st.language
FROM {$this->wpdb->prefix}icl_string_translations AS " . self::ALIAS_STRING_TRANSLATIONS . "
JOIN {$this->wpdb->prefix}icl_strings AS " . self::ALIAS_STRINGS . " ON s.id = st.string_id
WHERE $table_alias.id IN(" . wpml_prepare_in( $ids ) . ')
'
);
}
return wpml_collect( $results )->map(
function( $row ) {
return (object) [
'domain' => $row->domain,
'locale' => $this->locale->get_locale( $row->language ),
];
}
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace WPML\ST\TranslationFile;
use WPML\ST\MO\Hooks\Factory;
use WPML\ST\MO\Plural;
use WPML_Action_Filter_Loader;
use WPML_Package_Translation_Schema;
use WPML_ST_Upgrade;
use WPML_ST_Upgrade_MO_Scanning;
class Hooks {
/** @var WPML_Action_Filter_Loader $action_loader */
private $action_loader;
/** @var WPML_ST_Upgrade $upgrade */
private $upgrade;
public function __construct( WPML_Action_Filter_Loader $action_loader, WPML_ST_Upgrade $upgrade ) {
$this->action_loader = $action_loader;
$this->upgrade = $upgrade;
}
public function install() {
if ( $this->hasPackagesTable() && $this->hasTranslationFilesTables() ) {
$this->action_loader->load( [ Factory::class, Plural::class ] );
}
}
private function hasPackagesTable() {
$updates_run = get_option( WPML_Package_Translation_Schema::OPTION_NAME, [] );
return in_array( WPML_Package_Translation_Schema::REQUIRED_VERSION, $updates_run, true );
}
private function hasTranslationFilesTables() {
return $this->upgrade->has_command_been_executed( WPML_ST_Upgrade_MO_Scanning::class );
}
public static function useFileSynchronization() {
return defined( 'WPML_ST_SYNC_TRANSLATION_FILES' ) && constant( 'WPML_ST_SYNC_TRANSLATION_FILES' );
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace WPML\ST\TranslationFile;
use WP_Filesystem_Direct;
use WPML\Collect\Support\Collection;
use WPML\ST\MO\File\makeDir;
use WPML_Language_Records;
use WPML_ST_Translations_File_Dictionary;
use function wpml_collect;
use WPML_ST_Translations_File_Entry;
abstract class Manager {
use makeDir;
const SUB_DIRECTORY = 'wpml';
/** @var StringsRetrieve $strings */
protected $strings;
/** @var WPML_Language_Records $language_records */
protected $language_records;
/** @var Builder $builder */
protected $builder;
/** @var WPML_ST_Translations_File_Dictionary $file_dictionary */
protected $file_dictionary;
/** @var Domains $domains */
protected $domains;
public function __construct(
StringsRetrieve $strings,
Builder $builder,
WP_Filesystem_Direct $filesystem,
WPML_Language_Records $language_records,
Domains $domains
) {
$this->strings = $strings;
$this->builder = $builder;
$this->filesystem = $filesystem;
$this->language_records = $language_records;
$this->domains = $domains;
}
/**
* @param string $domain
* @param string $locale
*/
public function remove( $domain, $locale ) {
$this->filesystem->delete( $this->getFilepath( $domain, $locale ) );
}
/**
* @param string $domain
* @param string $locale
*
* @return bool
*/
public function add( $domain, $locale ) {
if ( ! $this->maybeCreateSubdir() ) {
return false;
}
$lang_code = $this->language_records->get_language_code( $locale );
$strings = $this->strings->get( $domain, $lang_code, $this->isPartialFile() );
if ( ! $strings && $this->isPartialFile() ) {
$this->remove( $domain, $locale );
return false;
}
$file_content = $this->builder
->set_language( $locale )
->get_content( $strings );
$filepath = $this->getFilepath( $domain, $locale );
return $this->filesystem->put_contents( $filepath, $file_content, 0755 & ~ umask() );
}
/**
* @param string $domain
* @param string $locale
*
* @return string|null
*/
public function get( $domain, $locale ) {
$filepath = $this->getFilepath( $domain, $locale );
if ( $this->filesystem->is_file( $filepath ) && $this->filesystem->is_readable( $filepath ) ) {
return $filepath;
}
return null;
}
/**
* @param string $domain
* @param string $locale
*
* @return string
*/
public function getFilepath( $domain, $locale ) {
return $this->getSubdir() . '/' . strtolower( $domain ) . '-' . $locale . '.' . $this->getFileExtension();
}
/**
* @param string $domain
*
* @return bool
*/
public function handles( $domain ) {
return $this->getDomains()->contains( $domain );
}
/** @return string */
public static function getSubdir() {
$subdir = WP_LANG_DIR . '/' . self::SUB_DIRECTORY;
$siteId = get_current_blog_id();
if ( $siteId > 1 ) {
$subdir .= '/' . $siteId;
}
return $subdir;
}
/**
* @return string
*/
abstract protected function getFileExtension();
/**
* @return bool
*/
abstract public function isPartialFile();
/**
* @return Collection
*/
abstract protected function getDomains();
}

View File

@@ -0,0 +1,72 @@
<?php
namespace WPML\ST\TranslationFile;
class StringEntity {
/** @var string $original */
private $original;
/** @var array $translations */
private $translations = array();
/** @var null|string $context */
private $context;
/** @var string|null */
private $original_plural;
/** @var string|null */
private $name;
/**
* @param string $original
* @param array $translations
* @param null|string $context
* @param null|string $original_plural
* @param null|string $name
*/
public function __construct( $original, array $translations, $context = null, $original_plural = null, $name = null ) {
$this->original = $original;
$this->translations = $translations;
$this->context = $context ? $context : null;
$this->original_plural = $original_plural;
$this->name = $name;
}
/** @return string */
public function get_original() {
return $this->original;
}
/** @return array */
public function get_translations() {
return $this->translations;
}
/** @return null|string */
public function get_context() {
return $this->context;
}
/**
* @return string|null
*/
public function get_original_plural() {
return $this->original_plural;
}
/**
* @return string|null
*/
public function get_name() {
return $this->name;
}
/**
* @param string $name
*/
public function set_name( $name ) {
$this->name = $name;
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace WPML\ST\TranslationFile;
use WPML\Collect\Support\Collection;
use WPML\ST\TranslateWpmlString;
class StringsRetrieve {
// We need to store the strings by key that is a combination of original and gettext context
// The join needs to be something that is unlikely to be in either so we can split later.
const KEY_JOIN = '::JOIN::';
/** @var \WPML\ST\DB\Mappers\StringsRetrieve */
private $string_retrieve;
/**
* @param \WPML\ST\DB\Mappers\StringsRetrieve $string_retrieve
*/
public function __construct( \WPML\ST\DB\Mappers\StringsRetrieve $string_retrieve ) {
$this->string_retrieve = $string_retrieve;
}
/**
* @param string $domain
* @param string $language
* @param bool $modified_mo_only
*
* @return StringEntity[]
*/
public function get( $domain, $language, $modified_mo_only ) {
return $this->loadFromDb( $language, $domain, $modified_mo_only )
->filter(
function ( $string ) {
return (bool) $string['translation'];
}
)
->mapToGroups(
function ( array $string ) {
return $this->groupPluralFormsOfSameString( $string );
}
)
->map(
function ( Collection $strings, $key ) {
return $this->buildStringEntity( $strings, $key );
}
)
->values()
->toArray();
}
/**
* @param string $language
* @param string $domain
* @param bool $modified_mo_only
*
* @return Collection
*/
private function loadFromDb( $language, $domain, $modified_mo_only = false ) {
$result = \wpml_collect( $this->string_retrieve->get( $language, $domain, $modified_mo_only ) );
return $result->map(
function ( $row ) {
return $this->parseResult( $row );
}
);
}
/**
* @param array $row_data
*
* @return array
*/
private function parseResult( array $row_data ) {
return [
'id' => $row_data['id'],
'original' => $row_data['original'],
'context' => $row_data['gettext_context'],
'translation' => self::parseTranslation( $row_data ),
'name' => $row_data['name'],
];
}
/**
* @param array $row_data
*
* @return string|null
*/
public static function parseTranslation( array $row_data ) {
$value = null;
$has_translation = ! empty( $row_data['translated'] ) && in_array( $row_data['status'], [ ICL_TM_COMPLETE, ICL_TM_NEEDS_UPDATE ] );
if ( $has_translation ) {
$value = $row_data['translated'];
} elseif ( ! empty( $row_data['mo_string'] ) ) {
$value = $row_data['mo_string'];
}
return $value;
}
/**
* @param array $string
*
* @return array
*/
private function groupPluralFormsOfSameString( array $string ) {
$groupKey = $this->getPluralGroupKey( $string );
$pattern = '/^(.+) \[plural ([0-9]+)\]$/';
if ( preg_match( $pattern, $string['original'], $matches ) ) {
$string['original'] = $matches[1];
$string['index'] = $matches[2];
} else {
$string['index'] = null;
}
return [
$string['original'] . self::KEY_JOIN . $string['context'] . self::KEY_JOIN . $groupKey => $string,
];
}
/**
* Inside a domain, we can have several occurrences of strings
* with the same original, but with different names.
* In this situation, we should not try to group plurals.
*
* @param array $string
*
* @return mixed|string
*/
private function getPluralGroupKey( array $string ) {
$cannotBelongToPluralGroup = TranslateWpmlString::canTranslateWithMO( $string['original'], $string['name'] );
if ( $cannotBelongToPluralGroup ) {
return $string['name'];
}
return '';
}
/**
* @param Collection $strings
* @param string $key
*
* @return StringEntity
*/
private function buildStringEntity( Collection $strings, $key ) {
$translations = $strings->sortBy( 'index' )->pluck( 'translation' )->toArray();
list( $original, $context ) = explode( self::KEY_JOIN, $key );
$stringEntity = new StringEntity( $original, $translations, $context );
$stringEntity->set_name( $strings->first()['name'] );
return $stringEntity;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace WPML\ST\TranslationFile\Sync;
use WPML\ST\TranslationFile\Manager;
use WPML_ST_Translations_File_Locale;
class FileSync {
/** @var Manager */
private $manager;
/** @var TranslationUpdates */
private $translationUpdates;
/** @var WPML_ST_Translations_File_Locale */
private $fileLocale;
public function __construct(
Manager $manager,
TranslationUpdates $translationUpdates,
WPML_ST_Translations_File_Locale $FileLocale
) {
$this->manager = $manager;
$this->translationUpdates = $translationUpdates;
$this->fileLocale = $FileLocale;
}
/**
* Before to load the custom translation file, we'll:
* - Re-generate it if it's missing or outdated.
* - Delete it if we don't have custom translations.
*
* We will also sync the custom file when a native file is passed
* because the custom file might never be loaded if it's missing.
*
* @param string|false $filePath
* @param string $domain
*/
public function sync( $filePath, $domain ) {
if ( ! $filePath ) {
return;
}
$locale = $this->fileLocale->get( $filePath, $domain );
$filePath = $this->getCustomFilePath( $filePath, $domain, $locale );
$lastDbUpdate = $this->translationUpdates->getTimestamp( $domain, $locale );
$fileTimestamp = file_exists( $filePath ) ? filemtime( $filePath ) : 0;
if ( 0 === $lastDbUpdate ) {
if ( $fileTimestamp ) {
$this->manager->remove( $domain, $locale );
}
} elseif ( $fileTimestamp < $lastDbUpdate ) {
$this->manager->add( $domain, $locale );
}
}
/**
* @param string $filePath
* @param string $domain
* @param string $locale
*
* @return string|null
*/
private function getCustomFilePath( $filePath, $domain, $locale ) {
if ( self::isWpmlCustomFile( $filePath ) ) {
return $filePath;
}
return $this->manager->getFilepath( $domain, $locale );
}
/**
* @param string $file
*
* @return bool
*/
private static function isWpmlCustomFile( $file ) {
return 0 === strpos( $file, WP_LANG_DIR . '/' . Manager::SUB_DIRECTORY );
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace WPML\ST\TranslationFile\Sync;
use WPML\Collect\Support\Collection;
class TranslationUpdates {
// The global constant is not defined yet.
const ICL_STRING_TRANSLATION_COMPLETE = 10;
/** @var \wpdb */
private $wpdb;
/** @var \WPML_Language_Records */
private $languageRecords;
/** @var null|Collection */
private $data;
public function __construct( \wpdb $wpdb, \WPML_Language_Records $languageRecords ) {
$this->wpdb = $wpdb;
$this->languageRecords = $languageRecords;
}
/**
* @param string $domain
* @param string $locale
*
* @return int
*/
public function getTimestamp( $domain, $locale ) {
$this->loadData();
$lang = $this->languageRecords->get_language_code( $locale );
return (int) $this->data->get( "$lang#$domain" );
}
private function loadData() {
if ( ! $this->data ) {
$sql = "
SELECT
CONCAT(st.language,'#',s.context) AS lang_domain,
UNIX_TIMESTAMP(MAX(st.translation_date)) as last_update
FROM {$this->wpdb->prefix}icl_string_translations AS st
INNER JOIN {$this->wpdb->prefix}icl_strings AS s
ON st.string_id = s.id
WHERE st.value IS NOT NULL AND st.status = %d
GROUP BY s.context, st.language;
";
$this->data = wpml_collect(
$this->wpdb->get_results( $this->wpdb->prepare( $sql, self::ICL_STRING_TRANSLATION_COMPLETE ) )
)->pluck( 'last_update', 'lang_domain' );
}
}
public function reset() {
$this->data = null;
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace WPML\ST\TranslationFile;
use stdClass;
use WPML\Collect\Support\Collection;
use WPML\Element\API\Languages;
class UpdateHooks implements \IWPML_Action {
/** @var Manager $file_manager */
private $file_manager;
/** @var DomainsLocalesMapper $domains_locales_mapper */
private $domains_locales_mapper;
/** @var array $updated_translation_ids */
private $updated_translation_ids = [];
/** @var Collection $entities_to_update */
private $entities_to_update;
/** @var callable */
private $resetDomainsCache;
public function __construct(
Manager $file_manager,
DomainsLocalesMapper $domains_locales_mapper,
callable $resetDomainsCache = null
) {
$this->file_manager = $file_manager;
$this->domains_locales_mapper = $domains_locales_mapper;
$this->entities_to_update = wpml_collect( [] );
$this->resetDomainsCache = $resetDomainsCache ?: [ Domains::class, 'resetCache' ];
}
public function add_hooks() {
add_action( 'wpml_st_add_string_translation', array( $this, 'add_to_update_queue' ) );
add_action( 'wpml_st_update_string', array( $this, 'refresh_after_update_original_string' ), 10, 6 );
add_action( 'wpml_st_before_remove_strings', array( $this, 'refresh_before_remove_strings' ) );
/**
* @see UpdateHooks::refresh_domain
* @since @3.1.0
*/
add_action( 'wpml_st_refresh_domain', [ $this, 'refresh_domain' ] );
if ( ! $this->file_manager->isPartialFile() ) {
add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_imported_file' ) );
}
}
/** @param int $string_translation_id */
public function add_to_update_queue( $string_translation_id ) {
if ( ! in_array( $string_translation_id, $this->updated_translation_ids, true ) ) {
$this->updated_translation_ids[] = $string_translation_id;
$this->add_shutdown_action();
}
}
private function add_shutdown_action() {
if ( ! has_action( 'shutdown', array( $this, 'process_update_queue' ) ) ) {
add_action( 'shutdown', array( $this, 'process_update_queue' ) );
}
}
/**
* @return array
*/
public function process_update_queue() {
call_user_func( $this->resetDomainsCache );
$outdated_entities = $this->domains_locales_mapper->get_from_translation_ids( $this->updated_translation_ids );
$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
$this->entities_to_update->each(
function( $entity ) {
$this->update_file( $entity->domain, $entity->locale );
}
);
return $this->entities_to_update->toArray();
}
/**
* @param string $domain
* @param string $name
* @param string $old_value
* @param string $new_value
* @param bool|false $force_complete
* @param stdClass $string
*/
public function refresh_after_update_original_string( $domain, $name, $old_value, $new_value, $force_complete, $string ) {
$outdated_entities = $this->domains_locales_mapper->get_from_string_ids( [ $string->id ] );
$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
$this->add_shutdown_action();
}
public function update_imported_file( \WPML_ST_Translations_File_Entry $file_entry ) {
if ( $file_entry->get_status() === \WPML_ST_Translations_File_Entry::IMPORTED ) {
$this->update_file( $file_entry->get_domain(), $file_entry->get_file_locale() );
}
}
/**
* It dispatches the regeneration of MO files for a specific domain in all active languages.
*
* @param string $domain
*/
public function refresh_domain( $domain ) {
$outdated_entities = $this->domains_locales_mapper->get_from_domain(
[ Languages::class, 'getActive' ],
$domain
);
$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
$this->add_shutdown_action();
}
/**
* We need to refresh before the strings are deleted,
* otherwise we can't determine which domains to refresh.
*
* @param array $string_ids
*/
public function refresh_before_remove_strings( array $string_ids ) {
$outdated_entities = $this->domains_locales_mapper->get_from_string_ids( $string_ids );
$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
$this->add_shutdown_action();
}
/**
* @param string $domain
* @param string $locale
*/
private function update_file( $domain, $locale ) {
/**
* It does not matter whether MO/JED file can be handled or not, that's why `remove` method is placed before `handles` check.
* If a file is not `handable` then it can still should be removed.
*/
$this->file_manager->remove( $domain, $locale );
if ( $this->file_manager->handles( $domain ) ) {
$this->file_manager->add( $domain, $locale );
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace WPML\ST\TranslationFile;
use WPML\ST\MO\File\ManagerFactory;
use function WPML\Container\make;
class UpdateHooksFactory {
/** @return UpdateHooks */
public static function create() {
static $instance;
if ( ! $instance ) {
$instance = make( UpdateHooks::class, [ ':file_manager' => ManagerFactory::create() ] );
}
return $instance;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace WPML\ST\JED\Hooks;
use WPML\ST\TranslationFile\Sync\FileSync;
use WPML_ST_JED_Domain;
use WPML_ST_JED_File_Manager;
use WPML_ST_Script_Translations_Hooks;
use WPML_ST_Translations_File_Locale;
class Sync implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_DIC_Action {
/** @var FileSync */
private $fileSync;
public function __construct( FileSync $fileSync ) {
$this->fileSync = $fileSync;
}
public function add_hooks() {
add_filter(
'load_script_translation_file',
[ $this, 'syncCustomJedFile' ],
WPML_ST_Script_Translations_Hooks::PRIORITY_OVERRIDE_JED_FILE - 1,
3
);
}
/**
* @param string|false $jedFile Path to the translation file to load. False if there isn't one.
* @param string $handler Name of the script to register a translation domain to.
* @param string $domain The text domain.
*/
public function syncCustomJedFile( $jedFile, $handler, $domain ) {
$this->fileSync->sync( $jedFile, WPML_ST_JED_Domain::get( $domain, $handler ) );
return $jedFile;
}
}

View File

@@ -0,0 +1,8 @@
<?php
class WPML_ST_JED_Domain {
public static function get( $domain, $handler ) {
return $domain . '-' . $handler;
}
}

View File

@@ -0,0 +1,50 @@
<?php
use WPML\ST\TranslationFile\StringEntity;
class WPML_ST_JED_File_Builder extends WPML\ST\TranslationFile\Builder {
/** @var string $decoded_eot */
private $decoded_eot;
public function __construct() {
$this->decoded_eot = json_decode( WPML_ST_Translations_File_JED::DECODED_EOT_CHAR );
}
/**
* @param StringEntity[] $strings
* @return string
*/
public function get_content( array $strings ) {
$data = new stdClass();
$data->{'translation-revision-date'} = date( 'Y-m-d H:i:sO' );
$data->generator = 'WPML String Translation ' . WPML_ST_VERSION;
$data->domain = 'messages';
$data->locale_data = new stdClass();
$data->locale_data->messages = new stdClass();
$data->locale_data->messages->{WPML_ST_Translations_File_JED::EMPTY_PROPERTY_NAME} = (object) array(
'domain' => 'messages',
'plural-forms' => $this->plural_form,
'lang' => $this->language,
);
foreach ( $strings as $string ) {
$original = $this->get_original_with_context( $string );
$data->locale_data->messages->{$original} = $string->get_translations();
}
$jed_content = wp_json_encode( $data );
return preg_replace( '/"' . WPML_ST_Translations_File_JED::EMPTY_PROPERTY_NAME . '"/', '""', $jed_content, 1 );
}
private function get_original_with_context( StringEntity $string ) {
if ( $string->get_context() ) {
return $string->get_context() . $this->decoded_eot . $string->get_original();
}
return $string->get_original();
}
}

View File

@@ -0,0 +1,28 @@
<?php
use WPML\Collect\Support\Collection;
use WPML\ST\TranslationFile\Manager;
class WPML_ST_JED_File_Manager extends Manager {
/**
* @return string
*/
protected function getFileExtension() {
return 'json';
}
/**
* @return bool
*/
public function isPartialFile() {
return false;
}
/**
* @return Collection
*/
protected function getDomains() {
return $this->domains->getJEDDomains();
}
}

View File

@@ -0,0 +1,66 @@
<?php
use WPML\ST\JED\Hooks\Sync;
use WPML\ST\TranslationFile\Sync\FileSync;
use function WPML\Container\make;
use WPML\ST\TranslationFile\UpdateHooks;
class WPML_ST_Script_Translations_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
/**
* Create hooks.
*
* @return array|IWPML_Action
* @throws \WPML\Auryn\InjectionException Auryn Exception.
*/
public function create() {
$hooks = array();
$jed_file_manager = make(
WPML_ST_JED_File_Manager::class,
[ ':builder' => make( WPML_ST_JED_File_Builder::class ) ]
);
$hooks['update'] = $this->get_update_hooks( $jed_file_manager );
if ( ! wpml_is_ajax() && ! wpml_is_rest_request() ) {
$hooks['filtering'] = $this->get_filtering_hooks( $jed_file_manager );
}
if ( WPML\ST\TranslationFile\Hooks::useFileSynchronization() ) {
$hooks['sync'] = make(
Sync::class,
[
':fileSync' => make( FileSync::class, [ ':manager' => $jed_file_manager ] ),
':manager' => $jed_file_manager,
]
);
}
return $hooks;
}
/**
* @param WPML_ST_JED_File_Manager $jed_file_manager
*
* @return UpdateHooks
*/
private function get_update_hooks( $jed_file_manager ) {
return make(
UpdateHooks::class,
[ ':file_manager' => $jed_file_manager ]
);
}
/**
* @param WPML_ST_JED_File_Manager $jed_file_manager
*
* @return WPML_ST_Script_Translations_Hooks
*/
private function get_filtering_hooks( $jed_file_manager ) {
return make(
WPML_ST_Script_Translations_Hooks::class,
[ ':jed_file_manager' => $jed_file_manager ]
);
}
}

View File

@@ -0,0 +1,75 @@
<?php
class WPML_ST_Script_Translations_Hooks implements IWPML_Action {
const PRIORITY_OVERRIDE_JED_FILE = 10;
/** @var WPML_ST_Translations_File_Dictionary $dictionary */
private $dictionary;
/** @var WPML_ST_JED_File_Manager $jed_file_manager */
private $jed_file_manager;
/** @var WPML_File $wpml_file */
private $wpml_file;
public function __construct(
WPML_ST_Translations_File_Dictionary $dictionary,
WPML_ST_JED_File_Manager $jed_file_manager,
WPML_File $wpml_file
) {
$this->dictionary = $dictionary;
$this->jed_file_manager = $jed_file_manager;
$this->wpml_file = $wpml_file;
}
public function add_hooks() {
add_filter( 'load_script_translation_file', array( $this, 'override_jed_file' ), self::PRIORITY_OVERRIDE_JED_FILE, 3 );
}
/**
* @param string $filepath
* @param string $handler
* @param string $domain
*
* @return string
*/
public function override_jed_file( $filepath, $handler, $domain ) {
if ( ! $filepath ) {
return $filepath;
}
$locale = $this->get_file_locale( $filepath, $domain );
$domain = WPML_ST_JED_Domain::get( $domain, $handler );
$wpml_filepath = $this->jed_file_manager->get( $domain, $locale );
if ( $wpml_filepath ) {
return $wpml_filepath;
}
return $filepath;
}
/**
* @param string $filepath
*
* @return bool
*/
private function is_file_imported( $filepath ) {
$relative_path = $this->wpml_file->get_relative_path( $filepath );
$file = $this->dictionary->find_file_info_by_path( $relative_path );
$statuses = array( WPML_ST_Translations_File_Entry::IMPORTED, WPML_ST_Translations_File_Entry::FINISHED );
return $file && in_array( $file->get_status(), $statuses, true );
}
/**
* @param string $filepath
* @param string $domain
*
* @return string
*/
private function get_file_locale( $filepath, $domain ) {
return \WPML\Container\make( \WPML_ST_Translations_File_Locale::class )->get( $filepath, $domain );
}
}