first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\Compatibility;
|
||||
|
||||
use SitePress;
|
||||
|
||||
abstract class BaseDynamicContent implements \IWPML_DIC_Action, \IWPML_Backend_Action, \IWPML_Frontend_Action {
|
||||
|
||||
/** @var SitePress */
|
||||
private $sitepress;
|
||||
|
||||
/**
|
||||
* @param SitePress $sitepress
|
||||
*/
|
||||
public function __construct( SitePress $sitepress ) {
|
||||
$this->sitepress = $sitepress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters and actions.
|
||||
*/
|
||||
public function add_hooks() {
|
||||
if ( $this->sitepress->is_setup_complete() ) {
|
||||
add_filter( 'wpml_pb_shortcode_decode', [ $this, 'decode_dynamic_content' ], 10, 2 );
|
||||
add_filter( 'wpml_pb_shortcode_encode', [ $this, 'encode_dynamic_content' ], 10, 2 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets dynamic content to be translatable.
|
||||
*
|
||||
* @param string $string The decoded string so far.
|
||||
* @param string $encoding The encoding used.
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
abstract public function decode_dynamic_content( $string, $encoding );
|
||||
|
||||
/**
|
||||
* Rebuilds dynamic content with translated strings.
|
||||
*
|
||||
* @param string|array $string The field array or string.
|
||||
* @param string $encoding The encoding used.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function encode_dynamic_content( $string, $encoding );
|
||||
|
||||
/**
|
||||
* Check if a certain field contains dynamic content.
|
||||
*
|
||||
* @param string $string The string to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function is_dynamic_content( $string );
|
||||
|
||||
/**
|
||||
* Decode a dynamic-content field.
|
||||
*
|
||||
* @param string $string The string to decode.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function decode_field( $string );
|
||||
|
||||
/**
|
||||
* Encode a dynamic-content field.
|
||||
*
|
||||
* @param array $field The field to encode.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function encode_field( $field );
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\AutoUpdate;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\FP\Relation;
|
||||
use WPML\PB\Shutdown\Hooks as ShutdownHooks;
|
||||
use function WPML\FP\invoke;
|
||||
use function WPML\FP\partialRight;
|
||||
use function WPML\FP\pipe;
|
||||
|
||||
class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
|
||||
|
||||
const HASH_SEP = '-';
|
||||
|
||||
/** @var \WPML_PB_Integration $pbIntegration */
|
||||
private $pbIntegration;
|
||||
|
||||
/** @var \WPML_Translation_Element_Factory $elementFactory */
|
||||
private $elementFactory;
|
||||
|
||||
/** @var \WPML_Page_Builders_Page_Built $pageBuilt */
|
||||
private $pageBuilt;
|
||||
|
||||
/** @var array $translationStatusesUpdaters */
|
||||
private $translationStatusesUpdaters = [];
|
||||
|
||||
public function __construct(
|
||||
\WPML_PB_Integration $pbIntegration,
|
||||
\WPML_Translation_Element_Factory $elementFactory,
|
||||
\WPML_Page_Builders_Page_Built $pageBuilt
|
||||
) {
|
||||
$this->pbIntegration = $pbIntegration;
|
||||
$this->elementFactory = $elementFactory;
|
||||
$this->pageBuilt = $pageBuilt;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
if ( Settings::isEnabled() && $this->isTmLoaded() ) {
|
||||
add_filter( 'wpml_tm_delegate_translation_statuses_update', [ $this, 'enqueueTranslationStatusUpdate'], 10, 3 );
|
||||
add_filter( 'wpml_tm_post_md5_content', [ $this, 'getMd5ContentFromPackageStrings' ], 10, 2 );
|
||||
add_action( 'shutdown', [ $this, 'afterRegisterAllStringsInShutdown' ], ShutdownHooks::PRIORITY_REGISTER_STRINGS + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
public function isTmLoaded() {
|
||||
return defined( 'WPML_TM_VERSION' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isDelegated
|
||||
* @param int $originalPostId
|
||||
* @param callable $statusesUpdater
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function enqueueTranslationStatusUpdate( $isDelegated, $originalPostId, $statusesUpdater ) {
|
||||
$this->translationStatusesUpdaters[ $originalPostId ] = $statusesUpdater;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param \WP_Post $post
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMd5ContentFromPackageStrings( $content, $post ) {
|
||||
// $joinPackageStringHashes :: \WPML_Package → string
|
||||
$joinPackageStringHashes = pipe(
|
||||
invoke( 'get_package_strings' )->with( true ),
|
||||
Lst::pluck( 'value' ),
|
||||
Lst::sort( Relation::gt() ),
|
||||
Lst::join( self::HASH_SEP )
|
||||
);
|
||||
|
||||
return Maybe::of( $post->ID )
|
||||
->map( [ self::class, 'getPackages' ] )
|
||||
->map( Fns::map( $joinPackageStringHashes ) )
|
||||
->filter()
|
||||
->map( Lst::join( self::HASH_SEP ) )
|
||||
->getOrElse( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*
|
||||
* @return \WPML_Package[]
|
||||
*/
|
||||
public static function getPackages( $postId ) {
|
||||
return apply_filters( 'wpml_st_get_post_string_packages', [], $postId );
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to update translation statuses after string registration
|
||||
* to make sure we build the content hash with the new strings.
|
||||
*/
|
||||
public function afterRegisterAllStringsInShutdown() {
|
||||
if ( $this->translationStatusesUpdaters ) {
|
||||
do_action( 'wpml_cache_clear' );
|
||||
|
||||
foreach ( $this->translationStatusesUpdaters as $originalPostId => $translationStatusesUpdater ) {
|
||||
if ( \get_post( $originalPostId ) ) {
|
||||
call_user_func( $translationStatusesUpdater );
|
||||
$this->resaveTranslations( $originalPostId );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*/
|
||||
private function resaveTranslations( $postId ) {
|
||||
if ( ! $this->isPageBuilder( $postId ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// $ifOriginal :: \WPML_Post_Element → bool
|
||||
$ifOriginal = pipe( invoke( 'get_source_language_code' ), Logic::not() );
|
||||
|
||||
// $ifCompleted :: \WPML_Post_Element → bool
|
||||
$ifCompleted = pipe( [ TranslationStatus::class, 'get' ], Relation::equals( ICL_TM_COMPLETE ) );
|
||||
|
||||
// $resaveElement :: \WPML_Post_Element → null
|
||||
$resaveElement = \WPML\FP\Fns::unary( partialRight( [ $this->pbIntegration, 'resave_post_translation_in_shutdown' ], false ) );
|
||||
|
||||
wpml_collect( $this->elementFactory->create_post( $postId )->get_translations() )
|
||||
->reject( $ifOriginal )
|
||||
->filter( $ifCompleted )
|
||||
->each( $resaveElement );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isPageBuilder( $postId ) {
|
||||
$isPbPostWithoutStrings = function( $postId ) {
|
||||
$post = get_post( $postId );
|
||||
|
||||
return $post instanceof \WP_Post
|
||||
&& $this->pageBuilt->is_page_builder_page( $post );
|
||||
};
|
||||
|
||||
return self::getPackages( $postId )
|
||||
|| $isPbPostWithoutStrings( $postId );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\AutoUpdate;
|
||||
|
||||
class Settings {
|
||||
|
||||
/**
|
||||
* This is part of the "Translation Auto-Update" feature
|
||||
* The "Translation Auto-Update" feature will be released in the next major version.
|
||||
* We need a way for users to allow disabling it quickly, if necessary.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEnabled() {
|
||||
if ( defined( 'WPML_TRANSLATION_AUTO_UPDATE_ENABLED' ) ) {
|
||||
return (bool) constant( 'WPML_TRANSLATION_AUTO_UPDATE_ENABLED' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\AutoUpdate;
|
||||
|
||||
use WPML\FP\Maybe;
|
||||
use WPML_Post_Element;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\FP\invoke;
|
||||
|
||||
class TranslationStatus {
|
||||
|
||||
/**
|
||||
* @param WPML_Post_Element $element
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public static function get( WPML_Post_Element $element ) {
|
||||
return Maybe::fromNullable( make( '\WPML_TM_Translation_Status' ) )
|
||||
->map( invoke( 'filter_translation_status' )->with( null, $element->get_trid(), $element->get_language_code() ) )
|
||||
->getOrElse( null );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Config;
|
||||
|
||||
use WPML\WP\OptionManager;
|
||||
|
||||
abstract class Factory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader {
|
||||
|
||||
/**
|
||||
* @return \IWPML_Action
|
||||
* @throws \Auryn\InjectionException
|
||||
*/
|
||||
public function create() {
|
||||
return new Hooks(
|
||||
new Parser(
|
||||
$this->getPbData( 'configRoot' ),
|
||||
$this->getPbData( 'defaultConditionKey' )
|
||||
),
|
||||
new Storage(
|
||||
new OptionManager(),
|
||||
$this->getPbData( 'pbKey' )
|
||||
),
|
||||
$this->getPbData( 'translatableWidgetsHook' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function getPbData( $key );
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Config;
|
||||
|
||||
use function WPML\FP\tap as tap;
|
||||
|
||||
class Hooks implements \IWPML_Action {
|
||||
|
||||
const PRIORITY_AFTER_DEFAULT = 20;
|
||||
|
||||
/** @var Parser $parser */
|
||||
private $parser;
|
||||
|
||||
/** @var Storage $storage */
|
||||
private $storage;
|
||||
|
||||
/** @var string $translatableWidgetsHook */
|
||||
private $translatableWidgetsHook;
|
||||
|
||||
public function __construct(
|
||||
Parser $parser,
|
||||
Storage $storage,
|
||||
$translatableWidgetsHook
|
||||
) {
|
||||
$this->parser = $parser;
|
||||
$this->storage = $storage;
|
||||
$this->translatableWidgetsHook = $translatableWidgetsHook;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_config_array', tap( [ $this, 'extractConfig' ] ) );
|
||||
add_filter( $this->translatableWidgetsHook , [ $this, 'extendTranslatableWidgets' ], self::PRIORITY_AFTER_DEFAULT );
|
||||
}
|
||||
|
||||
public function extractConfig( array $allConfig ) {
|
||||
$this->storage->update( $this->parser->extract( $allConfig ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $widgets
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extendTranslatableWidgets( array $widgets ) {
|
||||
return array_merge( $widgets, $this->storage->get() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Config;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Lst;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\FP\Obj;
|
||||
|
||||
class Parser {
|
||||
|
||||
/** @var string $configRoot */
|
||||
private $configRoot;
|
||||
|
||||
/** @var string $defaultConditionKey */
|
||||
private $defaultConditionKey;
|
||||
|
||||
public function __construct( $configRoot, $defaultConditionKey ) {
|
||||
$this->configRoot = $configRoot;
|
||||
$this->defaultConditionKey = $defaultConditionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a raw config array (from XML) and convert it into
|
||||
* a page builder configuration array.
|
||||
*
|
||||
* @see WPML_Elementor_Translatable_Nodes::get_nodes_to_translate()
|
||||
*
|
||||
* @param array $allConfig
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extract( array $allConfig ) {
|
||||
$pbConfig = [];
|
||||
$allWidgets = Obj::pathOr( [], [ 'wpml-config', $this->configRoot, 'widget' ], $allConfig );
|
||||
|
||||
foreach ( $allWidgets as $widget ) {
|
||||
$widgetName = Obj::path( ['attr', 'name'], $widget );
|
||||
|
||||
$pbConfig[ $widgetName ] = [
|
||||
'conditions' => $this->parseConditions( $widget, $widgetName ),
|
||||
'fields' => $this->parseFields( Obj::pathOr( [], ['fields', 'field'], $widget ) ),
|
||||
];
|
||||
|
||||
$fieldsInItems = Obj::prop( 'fields-in-item', $widget );
|
||||
|
||||
if ( $fieldsInItems ) {
|
||||
$pbConfig[ $widgetName ]['fields_in_item'] = [];
|
||||
$fieldsInItems = $this->normalize( $fieldsInItems );
|
||||
|
||||
foreach ( $fieldsInItems as $fieldsInItem ) {
|
||||
$itemOf = Obj::path( [ 'attr', 'items_of' ], $fieldsInItem );
|
||||
$pbConfig[ $widgetName ]['fields_in_item'][ $itemOf ] = $this->parseFields( Obj::propOr( [], 'field', $fieldsInItem ) );
|
||||
}
|
||||
}
|
||||
|
||||
$integrationClasses = $this->parseIntegrationClasses( $widget );
|
||||
|
||||
if ( $integrationClasses ) {
|
||||
$pbConfig[ $widgetName ]['integration-class'] = $integrationClasses;
|
||||
}
|
||||
}
|
||||
|
||||
return $pbConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $widget
|
||||
* @param string $widgetName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseConditions( array $widget, $widgetName ) {
|
||||
$makePair = function( $condition ) {
|
||||
return [ Obj::pathOr( $this->defaultConditionKey, ['attr', 'key'], $condition ), $condition['value'] ];
|
||||
};
|
||||
|
||||
return Maybe::fromNullable( Obj::path( ['conditions', 'condition'], $widget ) )
|
||||
->map( [ $this, 'normalize' ] )
|
||||
->map( Fns::map( $makePair ) )
|
||||
->map( Lst::fromPairs() )
|
||||
->getOrElse( [ $this->defaultConditionKey => $widgetName ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawFields
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseFields( array $rawFields ) {
|
||||
$parsedFields = [];
|
||||
|
||||
foreach ( $this->normalize( $rawFields ) as $field ) {
|
||||
$key = Obj::path( [ 'attr', 'key_of' ], $field );
|
||||
$fieldId = Obj::path( [ 'attr', 'field_id' ], $field );
|
||||
|
||||
$parsedField = [
|
||||
'field' => $field['value'],
|
||||
'type' => Obj::pathOr( $field['value'], [ 'attr', 'type' ], $field ),
|
||||
'editor_type' => Obj::pathOr( 'LINE', [ 'attr', 'editor_type' ], $field ),
|
||||
];
|
||||
|
||||
if ( $fieldId ) {
|
||||
$parsedField['field_id'] = $fieldId;
|
||||
}
|
||||
|
||||
if ( $key ) {
|
||||
$parsedFields[ $key ] = $parsedField;
|
||||
} else {
|
||||
$parsedFields[] = $parsedField;
|
||||
}
|
||||
}
|
||||
|
||||
return $parsedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $widget
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseIntegrationClasses( array $widget ) {
|
||||
return Maybe::fromNullable( Obj::path( [ 'integration-classes', 'integration-class' ], $widget ) )
|
||||
->map( [ $this, 'normalize' ] )
|
||||
->map( Lst::pluck( 'value' ) )
|
||||
->getOrElse( [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* If a sequence has only one element, we will wrap it
|
||||
* in order to have the same data shape as for multiple elements.
|
||||
*
|
||||
* @param array $partialConfig
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function normalize( array $partialConfig ) {
|
||||
$isAssocArray = count( array_filter( array_keys( $partialConfig ), 'is_string' ) ) > 0;
|
||||
|
||||
return $isAssocArray ? [ $partialConfig ] : $partialConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Config;
|
||||
|
||||
use WPML\WP\OptionManager;
|
||||
|
||||
class Storage {
|
||||
|
||||
const OPTION_GROUP = 'api-pb-config';
|
||||
|
||||
/** @var OptionManager $optionManager */
|
||||
private $optionManager;
|
||||
|
||||
/** @var string $pbKey */
|
||||
private $pbKey;
|
||||
|
||||
public function __construct(
|
||||
OptionManager $optionManager,
|
||||
$pbKey
|
||||
) {
|
||||
$this->optionManager = $optionManager;
|
||||
$this->pbKey = $pbKey;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return $this->optionManager->get( self::OPTION_GROUP, $this->pbKey, [] );
|
||||
}
|
||||
|
||||
public function update( array $pbConfig ) {
|
||||
$this->optionManager->set( self::OPTION_GROUP, $this->pbKey, $pbConfig, false );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Container;
|
||||
|
||||
class Config {
|
||||
|
||||
public static function getSharedClasses() {
|
||||
return [
|
||||
'\WPML_PB_Factory',
|
||||
'\WPML_PB_Integration',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\ConvertIds;
|
||||
|
||||
use WPML\Convert\Ids;
|
||||
|
||||
class Helper {
|
||||
|
||||
const TYPE_POST_IDS = 'post-ids';
|
||||
const TYPE_TAXONOMY_IDS = 'taxonomy-ids';
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidType( $type ) {
|
||||
return in_array( $type, [ self::TYPE_POST_IDS, self::TYPE_TAXONOMY_IDS ], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $subtype
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function selectElementType( $subtype, $type ) {
|
||||
return $subtype ?: wpml_collect( [
|
||||
self::TYPE_POST_IDS => Ids::ANY_POST,
|
||||
self::TYPE_TAXONOMY_IDS => Ids::ANY_TERM,
|
||||
] )->get( (string) $type, $type );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\GutenbergCleanup;
|
||||
|
||||
use WPML\FP\Relation;
|
||||
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*
|
||||
* @return \WPML_Package|null
|
||||
*/
|
||||
public static function get( $postId ) {
|
||||
// $isGbPackage :: \WPML_Package -> bool
|
||||
$isGbPackage = Relation::propEq( 'kind_slug', 'gutenberg' );
|
||||
|
||||
return wpml_collect( apply_filters( 'wpml_st_get_post_string_packages', [], $postId ) )
|
||||
->filter( $isGbPackage )
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WPML_Package|null $package
|
||||
*/
|
||||
public static function delete( $package ) {
|
||||
$package && do_action( 'wpml_delete_package', $package->name, $package->kind );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\GutenbergCleanup;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\LIB\WP\Gutenberg;
|
||||
use function WPML\FP\partialRight;
|
||||
use function WPML\FP\pipe;
|
||||
use function WPML\FP\tap as tap;
|
||||
|
||||
class ShortcodeHooks implements \IWPML_Backend_Action {
|
||||
|
||||
public function add_hooks() {
|
||||
add_action(
|
||||
'wp_insert_post',
|
||||
Fns::withoutRecursion( Fns::noop(), [ $this, 'removeGutenbergFootprint' ] ),
|
||||
10, 2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_ID
|
||||
* @param \WP_Post|mixed $post
|
||||
*/
|
||||
public function removeGutenbergFootprint( $post_ID, $post ) {
|
||||
// $isWpPost :: mixed -> bool (wpmlcore-8575)
|
||||
$isWpPost = partialRight( 'is_a', \WP_Post::class );
|
||||
|
||||
// $isBuiltWithShortcodes :: \WP_Post -> bool
|
||||
$isBuiltWithShortcodes = function( \WP_Post $post ) {
|
||||
/**
|
||||
* @since WPML 4.4.9
|
||||
*
|
||||
* @param bool false
|
||||
* @param \WP_Post $post
|
||||
*/
|
||||
return apply_filters( 'wpml_pb_is_post_built_with_shortcodes', false, $post );
|
||||
};
|
||||
|
||||
// $hasGutenbergMetaData :: \WP_Post -> bool
|
||||
$hasGutenbergMetaData = pipe(
|
||||
Obj::prop( 'post_content' ),
|
||||
Gutenberg::hasBlock()
|
||||
);
|
||||
|
||||
// $removeHtmlComments :: \WP_Post -> \WP_Post
|
||||
$removeHtmlComments = tap( pipe(
|
||||
Obj::over( Obj::lensProp( 'post_content' ), Gutenberg::stripBlockData() ),
|
||||
'wp_update_post'
|
||||
) );
|
||||
|
||||
// $deleteGutenbergPackage :: \WP_Post -> void
|
||||
$deleteGutenbergPackage = pipe(
|
||||
Obj::prop( 'ID' ),
|
||||
[ Package::class, 'get' ],
|
||||
[ Package::class, 'delete' ]
|
||||
);
|
||||
|
||||
Maybe::of( $post )
|
||||
->filter( $isWpPost )
|
||||
->filter( $isBuiltWithShortcodes )
|
||||
->filter( $hasGutenbergMetaData )
|
||||
->map( $removeHtmlComments )
|
||||
->map( $deleteGutenbergPackage );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Helper;
|
||||
|
||||
class LanguageNegotiation {
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsingDomains() {
|
||||
return apply_filters( 'wpml_setting', [], 'language_domains' )
|
||||
&& constant( 'WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN' ) === (int) apply_filters( 'wpml_setting', 1, 'language_negotiation_type' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $languageCode Language code.
|
||||
*
|
||||
* @retun string|null
|
||||
*/
|
||||
public static function getDomainByLanguage( $languageCode ) {
|
||||
return wpml_collect( self::getMappedDomains() )->first( function( $domain, $code ) use ( $languageCode ) {
|
||||
return $languageCode === $code;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private static function getMappedDomains() {
|
||||
$defaultLanguage = apply_filters( 'wpml_default_language', null );
|
||||
$homeUrl = apply_filters( 'wpml_permalink', get_home_url(), $defaultLanguage );
|
||||
$defaultDomain = wp_parse_url( $homeUrl, PHP_URL_HOST );
|
||||
$domains = apply_filters( 'wpml_setting', [], 'language_domains' );
|
||||
|
||||
$domains[ $defaultLanguage ] = $defaultDomain;
|
||||
|
||||
return $domains;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Shortcode;
|
||||
|
||||
use WPML\Convert\Ids;
|
||||
use WPML\FP\Fns;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\LIB\WP\Hooks;
|
||||
use WPML_PB_Config_Import_Shortcode;
|
||||
use function WPML\FP\curryN;
|
||||
use function WPML\FP\spreadArgs;
|
||||
|
||||
class AdjustIdsHooks implements \IWPML_Frontend_Action, \IWPML_DIC_Action {
|
||||
|
||||
/**
|
||||
* @var WPML_PB_Config_Import_Shortcode $config
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct( WPML_PB_Config_Import_Shortcode $config ) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
Hooks::onFilter( 'pre_do_shortcode_tag', - PHP_INT_MAX, 4 )
|
||||
->then( spreadArgs( Fns::withoutRecursion( Fns::identity(), [ $this, 'convertAttributeIds' ] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param false|string $bool
|
||||
* @param string $tag
|
||||
* @param array $attr
|
||||
* @param array $m
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function convertAttributeIds( $bool, $tag, $attr, $m ) {
|
||||
$tagConfig = $this->getConfig( $tag );
|
||||
|
||||
if ( $tagConfig ) {
|
||||
$convert = curryN( 2, function( $type, $arr ) {
|
||||
return $arr[1] . Ids::convert( $arr[2], $type, true );
|
||||
} );
|
||||
|
||||
foreach ( $tagConfig as $attribute => $type ) {
|
||||
$convertTypeIds = $convert( $type );
|
||||
|
||||
$m = preg_replace_callback( '/(' . $attribute . '=[\"\']{1})([^\"\']+)/', $convertTypeIds, $m );
|
||||
}
|
||||
|
||||
return do_shortcode_tag( $m );
|
||||
}
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tag
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function getConfig( $tag ) {
|
||||
return Obj::prop( $tag, $this->config->get_id_settings() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Shutdown;
|
||||
|
||||
class Hooks implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_DIC_Action {
|
||||
|
||||
const PRIORITY_REGISTER_STRINGS = 10;
|
||||
const PRIORITY_SAVE_TRANSLATIONS_TO_POST = 20;
|
||||
const PRIORITY_TRANSLATE_MEDIA = 30;
|
||||
|
||||
/** @var \WPML_PB_Integration $pbIntegration */
|
||||
private $pbIntegration;
|
||||
|
||||
public function __construct( \WPML_PB_Integration $pbIntegration ) {
|
||||
$this->pbIntegration = $pbIntegration;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'shutdown', [ $this, 'registerStrings' ], self::PRIORITY_REGISTER_STRINGS );
|
||||
add_action( 'shutdown', [ $this->pbIntegration, 'save_translations_to_post' ], self::PRIORITY_SAVE_TRANSLATIONS_TO_POST );
|
||||
add_action( 'shutdown', [ $this, 'translateMedias' ], self::PRIORITY_TRANSLATE_MEDIA );
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies only on original posts.
|
||||
*/
|
||||
public function registerStrings() {
|
||||
foreach( $this->pbIntegration->get_save_post_queue() as $post ) {
|
||||
$this->pbIntegration->register_all_strings_for_translation( $post );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This applies only on post translations.
|
||||
*/
|
||||
public function translateMedias() {
|
||||
if ( defined( 'WPML_MEDIA_VERSION' ) ) {
|
||||
foreach( $this->pbIntegration->get_save_post_queue() as $post ) {
|
||||
$this->pbIntegration->translate_media( $post );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Hooks implements IWPML_Action {
|
||||
|
||||
/** @var IWPML_PB_Media_Update_Factory $media_update_factory */
|
||||
private $media_update_factory;
|
||||
|
||||
/** @var string $page_builder_slug */
|
||||
private $page_builder_slug;
|
||||
|
||||
/**
|
||||
* WPML_Page_Builders_Media_Hooks constructor.
|
||||
*
|
||||
* @param IWPML_PB_Media_Update_Factory $media_update_factory
|
||||
* @param string $page_builder_slug
|
||||
*/
|
||||
public function __construct( IWPML_PB_Media_Update_Factory $media_update_factory, $page_builder_slug ) {
|
||||
$this->media_update_factory = $media_update_factory;
|
||||
$this->page_builder_slug = $page_builder_slug;
|
||||
}
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_pb_get_media_updaters', array( $this, 'add_media_updater' ) );
|
||||
add_filter( 'wpml_media_content_for_media_usage', array( $this, 'add_package_strings_content' ), 10, 2 );
|
||||
}
|
||||
/**
|
||||
* @param IWPML_PB_Media_Update[] $updaters
|
||||
*
|
||||
* @return IWPML_PB_Media_Update[]
|
||||
*/
|
||||
public function add_media_updater( $updaters ) {
|
||||
if ( ! array_key_exists( $this->page_builder_slug, $updaters ) ) {
|
||||
$updaters[ $this->page_builder_slug ] = $this->media_update_factory->create();
|
||||
}
|
||||
return $updaters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function add_package_strings_content( $content, $post ) {
|
||||
$packages = apply_filters( 'wpml_st_get_post_string_packages', array(), $post->ID );
|
||||
|
||||
/** @var WPML_Package[] $packages */
|
||||
foreach ( $packages as $package ) {
|
||||
$strings = $package->get_package_strings();
|
||||
|
||||
foreach ( $strings as $string ) {
|
||||
$content .= PHP_EOL . $string->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Translate {
|
||||
|
||||
/** @var WPML_Translation_Element_Factory $element_factory */
|
||||
private $element_factory;
|
||||
|
||||
/** @var WPML_Media_Image_Translate $image_translate */
|
||||
protected $image_translate;
|
||||
|
||||
/** @var array $translated_urls */
|
||||
protected $translated_urls = array();
|
||||
|
||||
/** @var (WP_Post|null)[] $translated_posts */
|
||||
protected $translated_posts = array();
|
||||
|
||||
/** @var array $translated_ids */
|
||||
private $translated_ids = array();
|
||||
|
||||
public function __construct(
|
||||
WPML_Translation_Element_Factory $element_factory,
|
||||
WPML_Media_Image_Translate $image_translate
|
||||
) {
|
||||
$this->element_factory = $element_factory;
|
||||
$this->image_translate = $image_translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $lang
|
||||
* @param string $source_lang
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function translate_image_url( $url, $lang, $source_lang ) {
|
||||
$key = $url . $lang . $source_lang;
|
||||
|
||||
if ( ! array_key_exists( $key, $this->translated_urls ) ) {
|
||||
$this->translated_urls[ $key ] = $url;
|
||||
|
||||
$attachment_id = $this->image_translate->get_attachment_id_by_url( $url, $source_lang );
|
||||
|
||||
if ( $attachment_id ) {
|
||||
$this->add_translated_id( $attachment_id );
|
||||
$translated_url = $this->image_translate->get_translated_image_by_url( $url, $source_lang, $lang );
|
||||
$this->translated_urls[ $key ] = $url;
|
||||
|
||||
if ( $translated_url ) {
|
||||
$this->translated_urls[ $key ] = $translated_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->translated_urls[ $key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $lang
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function translate_id( $id, $lang ) {
|
||||
if ( (int) $id < 1 ) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
$this->add_translated_id( $id );
|
||||
$translated_attachment = $this->get_translated_attachment( $id, $lang );
|
||||
|
||||
if ( isset( $translated_attachment->ID ) ) {
|
||||
return $translated_attachment->ID;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $lang
|
||||
*
|
||||
* @return WP_Post|null
|
||||
*/
|
||||
private function get_translated_attachment( $id, $lang ) {
|
||||
$key = $id . $lang;
|
||||
|
||||
if ( ! array_key_exists( $key, $this->translated_posts ) ) {
|
||||
$this->translated_posts[ $key ] = null;
|
||||
$element = $this->element_factory->create_post( $id );
|
||||
$translation = $element->get_translation( $lang );
|
||||
|
||||
if ( $translation ) {
|
||||
$this->translated_posts[ $key ] = $translation->get_wp_object();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->translated_posts[ $key ];
|
||||
}
|
||||
|
||||
/** @param int $id */
|
||||
private function add_translated_id( $id ) {
|
||||
if ( ! in_array( $id, $this->translated_ids, true ) ) {
|
||||
$this->translated_ids[] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
public function reset_translated_ids() {
|
||||
$this->translated_ids = array();
|
||||
}
|
||||
|
||||
/** @return array */
|
||||
public function get_translated_ids() {
|
||||
return $this->translated_ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Usage {
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Translate $media_translate */
|
||||
private $media_translate;
|
||||
|
||||
/** @var WPML_Media_Usage_Factory $media_usage_factory */
|
||||
private $media_usage_factory;
|
||||
|
||||
public function __construct(
|
||||
WPML_Page_Builders_Media_Translate $media_translate,
|
||||
WPML_Media_Usage_Factory $media_usage_factory
|
||||
) {
|
||||
$this->media_translate = $media_translate;
|
||||
$this->media_usage_factory = $media_usage_factory;
|
||||
}
|
||||
|
||||
/** @param int $post_id */
|
||||
public function update( $post_id ) {
|
||||
$media_ids = $this->media_translate->get_translated_ids();
|
||||
|
||||
foreach ( $media_ids as $media_id ) {
|
||||
$this->media_usage_factory->create( $media_id )->add_post( $post_id );
|
||||
}
|
||||
|
||||
$this->media_translate->reset_translated_ids();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Update_Media implements IWPML_PB_Media_Update {
|
||||
|
||||
/** @var WPML_Page_Builders_Update $pb_update */
|
||||
private $pb_update;
|
||||
|
||||
/** @var WPML_Translation_Element_Factory $element_factory */
|
||||
private $element_factory;
|
||||
|
||||
/** @var IWPML_PB_Media_Nodes_Iterator $node_iterator */
|
||||
protected $node_iterator;
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Usage|null $media_usage */
|
||||
protected $media_usage;
|
||||
|
||||
public function __construct(
|
||||
WPML_Page_Builders_Update $pb_update,
|
||||
WPML_Translation_Element_Factory $element_factory,
|
||||
IWPML_PB_Media_Nodes_Iterator $node_iterator,
|
||||
WPML_Page_Builders_Media_Usage $media_usage = null
|
||||
) {
|
||||
$this->pb_update = $pb_update;
|
||||
$this->element_factory = $element_factory;
|
||||
$this->node_iterator = $node_iterator;
|
||||
$this->media_usage = $media_usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function translate( $post ) {
|
||||
$element = $this->element_factory->create_post( $post->ID );
|
||||
$source_element = $element->get_source_element();
|
||||
|
||||
if ( ! $source_element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lang = $element->get_language_code();
|
||||
$source_lang = $source_element->get_language_code();
|
||||
$original_post_id = $source_element->get_id();
|
||||
$converted_data = $this->pb_update->get_converted_data( $post->ID );
|
||||
|
||||
if ( ! $converted_data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$converted_data = $this->node_iterator->translate( $converted_data, $lang, $source_lang );
|
||||
|
||||
$this->pb_update->save( $post->ID, $original_post_id, $converted_data );
|
||||
|
||||
if ( $this->media_usage ) {
|
||||
$this->media_usage->update( $original_post_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_PB_Media_Nodes_Iterator {
|
||||
|
||||
public function translate( $data, $lang, $source_lang );
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_PB_Media_Update_Factory {
|
||||
|
||||
/** @return IWPML_PB_Media_Update */
|
||||
public function create();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_PB_Media_Update {
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function translate( $post );
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Shortcodes_Update_Factory implements IWPML_PB_Media_Update_Factory {
|
||||
|
||||
/** @var WPML_PB_Config_Import_Shortcode WPML_PB_Config_Import_Shortcode */
|
||||
private $page_builder_config_import;
|
||||
|
||||
/** @var WPML_Translation_Element_Factory|null $element_factory */
|
||||
private $element_factory;
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Translate|null $media_translate */
|
||||
private $media_translate;
|
||||
|
||||
public function __construct( WPML_PB_Config_Import_Shortcode $page_builder_config_import ) {
|
||||
$this->page_builder_config_import = $page_builder_config_import;
|
||||
}
|
||||
|
||||
public function create() {
|
||||
$media_shortcodes = new WPML_Page_Builders_Media_Shortcodes(
|
||||
$this->get_media_translate(),
|
||||
$this->page_builder_config_import->get_media_settings()
|
||||
);
|
||||
|
||||
return new WPML_Page_Builders_Media_Shortcodes_Update(
|
||||
$this->get_element_factory(),
|
||||
$media_shortcodes,
|
||||
new WPML_Page_Builders_Media_Usage( $this->get_media_translate(), new WPML_Media_Usage_Factory() )
|
||||
);
|
||||
}
|
||||
|
||||
/** @return WPML_Translation_Element_Factory */
|
||||
private function get_element_factory() {
|
||||
global $sitepress;
|
||||
|
||||
if ( ! $this->element_factory ) {
|
||||
$this->element_factory = new WPML_Translation_Element_Factory( $sitepress );
|
||||
}
|
||||
|
||||
return $this->element_factory;
|
||||
}
|
||||
|
||||
/** @return WPML_Page_Builders_Media_Translate */
|
||||
private function get_media_translate() {
|
||||
global $sitepress;
|
||||
|
||||
if ( ! $this->media_translate ) {
|
||||
$this->media_translate = new WPML_Page_Builders_Media_Translate(
|
||||
$this->get_element_factory(),
|
||||
new WPML_Media_Image_Translate( $sitepress, new WPML_Media_Attachment_By_URL_Factory() )
|
||||
);
|
||||
}
|
||||
|
||||
return $this->media_translate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Shortcodes_Update implements IWPML_PB_Media_Update {
|
||||
|
||||
/** @var WPML_Translation_Element_Factory $element_factory */
|
||||
private $element_factory;
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Shortcodes $media_shortcodes*/
|
||||
private $media_shortcodes;
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Usage $media_usage */
|
||||
private $media_usage;
|
||||
|
||||
public function __construct(
|
||||
WPML_Translation_Element_Factory $element_factory,
|
||||
WPML_Page_Builders_Media_Shortcodes $media_shortcodes,
|
||||
WPML_Page_Builders_Media_Usage $media_usage
|
||||
) {
|
||||
$this->element_factory = $element_factory;
|
||||
$this->media_shortcodes = $media_shortcodes;
|
||||
$this->media_usage = $media_usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function translate( $post ) {
|
||||
if ( ! $this->media_shortcodes->has_media_shortcode( $post->post_content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$element = $this->element_factory->create_post( $post->ID );
|
||||
|
||||
if ( ! $element->get_source_language_code() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post->post_content = $this->media_shortcodes->set_target_lang( $element->get_language_code() )
|
||||
->set_source_lang( $element->get_source_language_code() )
|
||||
->translate( $post->post_content );
|
||||
|
||||
$this->media_usage->update( $element->get_source_element()->get_id() );
|
||||
|
||||
/**
|
||||
* The function wp_update_post() can modify post tag.
|
||||
* The code below sends tags by IDs to prevent this.
|
||||
*
|
||||
* @see wpmlcore-5947
|
||||
* @see https://core.trac.wordpress.org/ticket/45121
|
||||
*/
|
||||
$tag_ids = wp_get_post_tags( $post->ID, array( 'fields' => 'ids' ) );
|
||||
$postarr = array(
|
||||
'ID' => $post->ID,
|
||||
'post_content' => $post->post_content,
|
||||
'tags_input' => $tag_ids,
|
||||
);
|
||||
kses_remove_filters();
|
||||
wpml_update_escaped_post( $postarr );
|
||||
kses_init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Media_Shortcodes {
|
||||
|
||||
const TYPE_URL = 'media-url';
|
||||
const TYPE_IDS = 'media-ids';
|
||||
|
||||
/** @var WPML_Page_Builders_Media_Translate $media_translate */
|
||||
private $media_translate;
|
||||
|
||||
/** @var string $target_lang */
|
||||
private $target_lang;
|
||||
|
||||
/** @var string $source_lang */
|
||||
private $source_lang;
|
||||
|
||||
/** @var array $config */
|
||||
private $config;
|
||||
|
||||
public function __construct( WPML_Page_Builders_Media_Translate $media_translate, array $config ) {
|
||||
$this->media_translate = $media_translate;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function translate( $content ) {
|
||||
foreach ( $this->config as $shortcode ) {
|
||||
$shortcode = $this->sanitize_shortcode( $shortcode );
|
||||
$tag_name = isset( $shortcode['tag']['name'] ) ? $shortcode['tag']['name'] : '';
|
||||
|
||||
if ( ! empty( $shortcode['attributes'] ) ) {
|
||||
$content = $this->translate_attributes( $content, $tag_name, $shortcode['attributes'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $shortcode['content'] ) ) {
|
||||
$content = $this->translate_content( $content, $tag_name, $shortcode['content'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_media_shortcode( $content ) {
|
||||
if ( false === strpos( $content, '[' ) ) {
|
||||
return false;
|
||||
};
|
||||
|
||||
foreach ( $this->config as $shortcode ) {
|
||||
$shortcode = $this->sanitize_shortcode( $shortcode );
|
||||
$tag_name = isset( $shortcode['tag']['name'] ) ? $shortcode['tag']['name'] : '';
|
||||
|
||||
if ( $tag_name && false !== strpos( $content, '[' . $tag_name ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $shortcode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function sanitize_shortcode( array $shortcode ) {
|
||||
$defaults = array(
|
||||
'attributes' => null,
|
||||
);
|
||||
|
||||
return array_merge( $defaults, $shortcode );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $tag
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function translate_attributes( $content, $tag, array $attributes ) {
|
||||
foreach ( $attributes as $attribute => $data ) {
|
||||
$pattern = '/(\[' . $tag . '(?: [^\]]* | )' . $attribute . '=(?:"|\'))([^"\']*)/';
|
||||
$type = isset( $data['type'] ) ? $data['type'] : '';
|
||||
$content = preg_replace_callback( $pattern, array( $this, $this->get_callback( $type ) ), $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $tag
|
||||
* @param array $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function translate_content( $content, $tag, array $data ) {
|
||||
$pattern = '/(\[(?:' . $tag . ')[^\]]*\])([^\[]+)/';
|
||||
$type = isset( $data['type'] ) ? $data['type'] : '';
|
||||
return preg_replace_callback( $pattern, array( $this, $this->get_callback( $type ) ), $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_callback( $type ) {
|
||||
if ( self::TYPE_URL === $type ) {
|
||||
return 'replace_url_callback';
|
||||
}
|
||||
|
||||
return 'replace_ids_callback';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function replace_url_callback( array $matches ) {
|
||||
$translated_url = $this->media_translate->translate_image_url( $matches[2], $this->target_lang, $this->source_lang );
|
||||
|
||||
return $matches[1] . $translated_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function replace_ids_callback( array $matches ) {
|
||||
$ids = explode( ',', $matches[2] );
|
||||
|
||||
foreach ( $ids as &$id ) {
|
||||
$id = $this->media_translate->translate_id( (int) $id, $this->target_lang );
|
||||
}
|
||||
|
||||
return $matches[1] . implode( ',', $ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $target_lang
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function set_target_lang( $target_lang ) {
|
||||
$this->target_lang = $target_lang;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $source_lang
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function set_source_lang( $source_lang ) {
|
||||
$this->source_lang = $source_lang;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
|
||||
/**
|
||||
* Class ShortCodesInGutenbergBlocks
|
||||
* @package WPML\PB
|
||||
*
|
||||
* This class is to handle an edge case when there is only one Gutenberg block
|
||||
* that contains one or more shortcodes.
|
||||
* In this case we need to force the Gutenberg processing as there will be
|
||||
* no Gutenberg strings and only shortcode strings.
|
||||
*
|
||||
*/
|
||||
class ShortCodesInGutenbergBlocks {
|
||||
|
||||
const FORCED_GUTENBERG = 'Forced-Gutenberg';
|
||||
|
||||
public static function recordPackage(
|
||||
\WPML_PB_String_Translation_By_Strategy $strategy,
|
||||
$strategyKind,
|
||||
\WPML_Package $package,
|
||||
$language
|
||||
) {
|
||||
if ( $strategyKind === 'Gutenberg' && $package->kind === 'Page Builder ShortCode Strings' ) {
|
||||
$package->kind = self::FORCED_GUTENBERG;
|
||||
$strategy->add_package_to_update_list( $package, $language );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function fixupPackage( $package_data ) {
|
||||
if ( $package_data['package']->kind === self::FORCED_GUTENBERG ) {
|
||||
$package_data['package']->kind = 'Gutenberg';
|
||||
}
|
||||
|
||||
return $package_data;
|
||||
}
|
||||
|
||||
public static function normalizePackages( array $packagesToUpdate ) {
|
||||
if ( count( $packagesToUpdate ) > 1 ) {
|
||||
// If we have more than one package then we don't need to 'Force' it.
|
||||
// The normal Gutenberg package will update all translations correctly.
|
||||
$isForced = function ( $package ) { return $package['package']->kind !== self::FORCED_GUTENBERG; };
|
||||
$packagesToUpdate = array_filter( $packagesToUpdate, $isForced );
|
||||
}
|
||||
|
||||
return $packagesToUpdate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB;
|
||||
|
||||
class TranslateLinks {
|
||||
|
||||
/**
|
||||
* @param \WPML_ST_String_Factory $stringFactory
|
||||
* @param array $activeLanguages
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public static function getTranslatorForString( \WPML_ST_String_Factory $stringFactory, $activeLanguages ) {
|
||||
return function ( $string_id ) use ( $stringFactory, $activeLanguages ) {
|
||||
$string = $stringFactory->find_by_id( $string_id );
|
||||
|
||||
$sameStringLanguage = function ( $language ) use ( $string ) {
|
||||
return $language === $string->get_language();
|
||||
};
|
||||
|
||||
$setTranslation = function ( $language ) use ( $string ) {
|
||||
$string->set_translation( $language, $string->get_value() );
|
||||
};
|
||||
|
||||
\wpml_collect( $activeLanguages )->pluck( 'code' )
|
||||
->reject( $sameStringLanguage )
|
||||
->each( $setTranslation );
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_Page_Builders_Integration
|
||||
*/
|
||||
class WPML_Page_Builders_Integration {
|
||||
|
||||
const STRINGS_TRANSLATED_PRIORITY = 10;
|
||||
|
||||
/** @var WPML_Page_Builders_Register_Strings */
|
||||
private $register_strings;
|
||||
|
||||
/** @var WPML_Page_Builders_Update_Translation */
|
||||
private $update_translation;
|
||||
|
||||
/** @var IWPML_Page_Builders_Data_Settings */
|
||||
private $data_settings;
|
||||
|
||||
/**
|
||||
* WPML_Page_Builders_Integration constructor.
|
||||
*
|
||||
* @param WPML_Page_Builders_Register_Strings $register_strings
|
||||
* @param WPML_Page_Builders_Update_Translation $update_translation
|
||||
* @param IWPML_Page_Builders_Data_Settings $data_settings
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_Page_Builders_Register_Strings $register_strings,
|
||||
WPML_Page_Builders_Update_Translation $update_translation,
|
||||
IWPML_Page_Builders_Data_Settings $data_settings
|
||||
) {
|
||||
$this->register_strings = $register_strings;
|
||||
$this->update_translation = $update_translation;
|
||||
$this->data_settings = $data_settings;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_page_builder_support_required', array( $this, 'support_required' ) );
|
||||
add_action( 'wpml_page_builder_register_strings', array( $this, 'register_pb_strings' ), 10, 2 );
|
||||
add_action( 'wpml_page_builder_string_translated', array( $this, 'update_translated_post' ), self::STRINGS_TRANSLATED_PRIORITY, 5 );
|
||||
add_filter( 'wpml_get_translatable_types', array( $this, 'remove_shortcode_strings_type_filter' ), 12, 1 );
|
||||
|
||||
$this->data_settings->add_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $page_builder_plugins
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function support_required( array $page_builder_plugins ) {
|
||||
$page_builder_plugins[] = $this->data_settings->get_pb_name();
|
||||
return $page_builder_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post $post
|
||||
* @param array $package_key
|
||||
*/
|
||||
public function register_pb_strings( $post, $package_key ) {
|
||||
if ( $this->data_settings->get_pb_name() === $package_key['kind'] ) {
|
||||
$this->register_strings->register_strings( $post, $package_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $kind
|
||||
* @param int $translated_post_id
|
||||
* @param WP_Post $original_post
|
||||
* @param array $string_translations
|
||||
* @param string $lang
|
||||
*/
|
||||
public function update_translated_post( $kind, $translated_post_id, WP_Post $original_post, $string_translations, $lang ) {
|
||||
if ( $this->data_settings->get_pb_name() === $kind ) {
|
||||
$this->update_translation->update( $translated_post_id, $original_post, $string_translations, $lang );
|
||||
}
|
||||
}
|
||||
|
||||
public function remove_shortcode_strings_type_filter( $types ) {
|
||||
unset( $types[ sanitize_title_with_dashes( $this->data_settings->get_pb_name() ) ] );
|
||||
return $types;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_Page_Builders_App
|
||||
*/
|
||||
class WPML_Page_Builders_App {
|
||||
|
||||
/**
|
||||
* @var WPML_Page_Builders_Defined
|
||||
*/
|
||||
private $page_builder_plugins;
|
||||
|
||||
/**
|
||||
* WPML_Page_Builders_App constructor.
|
||||
*
|
||||
* @param WPML_Page_Builders_Defined $page_builder_plugins
|
||||
*/
|
||||
public function __construct( WPML_Page_Builders_Defined $page_builder_plugins ) {
|
||||
$this->page_builder_plugins = $page_builder_plugins;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_action( 'wpml_load_page_builders_integration', array( $this, 'load_integration' ) );
|
||||
add_filter( 'wpml_integrations_components', array( $this, 'add_components' ), 10, 1 );
|
||||
}
|
||||
|
||||
public function load_integration() {
|
||||
if ( ! class_exists( 'WPML_ST_Package_Factory' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$factories = array();
|
||||
|
||||
foreach ( $this->page_builder_plugins->get_settings() as $page_builder_id => $page_builder ) {
|
||||
if ( $this->page_builder_plugins->has( $page_builder_id ) ) {
|
||||
$current_factory = $page_builder['factory'];
|
||||
$factories[] = new $current_factory();
|
||||
}
|
||||
}
|
||||
|
||||
if ( $factories ) {
|
||||
foreach ( $factories as $factory ) {
|
||||
$integration = $factory->create();
|
||||
$integration->add_hooks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function add_components( $components ) {
|
||||
return $this->page_builder_plugins->add_components( $components );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
use function WPML\Container\make;
|
||||
|
||||
class WPML_PB_Factory {
|
||||
|
||||
/** @var wpdb */
|
||||
private $wpdb;
|
||||
/** @var SitePress */
|
||||
private $sitepress;
|
||||
private $string_translations = array();
|
||||
|
||||
public function __construct( wpdb $wpdb, SitePress $sitepress ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->sitepress = $sitepress;
|
||||
}
|
||||
|
||||
public function get_wpml_package( $package_id ) {
|
||||
return new WPML_Package( $package_id );
|
||||
}
|
||||
|
||||
public function get_string_translations( IWPML_PB_Strategy $strategy ) {
|
||||
$kind = $strategy->get_package_kind();
|
||||
if ( ! array_key_exists( $kind, $this->string_translations ) ) {
|
||||
$this->string_translations[ $kind ] = new WPML_PB_String_Translation_By_Strategy( $this->wpdb, $this, $strategy );
|
||||
}
|
||||
|
||||
return $this->string_translations[ $kind ];
|
||||
}
|
||||
|
||||
public function get_shortcode_parser( WPML_PB_Shortcode_Strategy $strategy ) {
|
||||
return new WPML_PB_Shortcodes( $strategy );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_PB_Shortcode_Strategy $strategy
|
||||
* @param bool $migration_mode
|
||||
*
|
||||
* @return WPML_PB_Register_Shortcodes
|
||||
*/
|
||||
public function get_register_shortcodes( WPML_PB_Shortcode_Strategy $strategy, $migration_mode = false ) {
|
||||
$string_factory = new WPML_ST_String_Factory( $this->wpdb );
|
||||
|
||||
$string_registration = new WPML_PB_String_Registration(
|
||||
$strategy,
|
||||
$string_factory,
|
||||
new WPML_ST_Package_Factory(),
|
||||
make( 'WPML_Translate_Link_Targets' ),
|
||||
WPML\PB\TranslateLinks::getTranslatorForString( $string_factory, $this->sitepress->get_active_languages() ),
|
||||
$migration_mode
|
||||
);
|
||||
|
||||
return new WPML_PB_Register_Shortcodes(
|
||||
$string_registration,
|
||||
$strategy,
|
||||
new WPML_PB_Shortcode_Encoding(),
|
||||
$migration_mode ? null : new WPML_PB_Reuse_Translations_By_Strategy( $strategy, $string_factory )
|
||||
);
|
||||
}
|
||||
|
||||
public function get_update_post( $package_data, IWPML_PB_Strategy $strategy ) {
|
||||
return new WPML_PB_Update_Post( $this->wpdb, $this->sitepress, $package_data, $strategy );
|
||||
}
|
||||
|
||||
public function get_shortcode_content_updater( IWPML_PB_Strategy $strategy ) {
|
||||
return new WPML_PB_Update_Shortcodes_In_Content( $strategy, new WPML_PB_Shortcode_Encoding() );
|
||||
}
|
||||
|
||||
public function get_api_hooks_content_updater( IWPML_PB_Strategy $strategy ) {
|
||||
return new WPML_PB_Update_API_Hooks_In_Content( $strategy );
|
||||
}
|
||||
|
||||
public function get_package_strings_resave() {
|
||||
return new WPML_PB_Package_Strings_Resave( new WPML_ST_String_Factory( $this->wpdb ) );
|
||||
}
|
||||
|
||||
public function get_handle_post_body() {
|
||||
return new WPML_PB_Handle_Post_Body(
|
||||
new WPML_Page_Builders_Page_Built(
|
||||
new WPML_Config_Built_With_Page_Builders()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depecated Use the static methods instead of the instance.
|
||||
*/
|
||||
public function get_last_translation_edit_mode() {
|
||||
return new WPML_PB_Last_Translation_Edit_Mode();
|
||||
}
|
||||
|
||||
public function get_post_element( $post_id ) {
|
||||
$factory = new WPML_Translation_Element_Factory( $this->sitepress );
|
||||
return $factory->create_post( $post_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
|
||||
use \WPML\FP\Fns;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\PB\Shortcode\StringCleanUp;
|
||||
use function WPML\FP\invoke;
|
||||
use function WPML\Container\make;
|
||||
|
||||
/**
|
||||
* Class WPML_PB_Integration
|
||||
*/
|
||||
class WPML_PB_Integration {
|
||||
|
||||
const MIGRATION_DONE_POST_META = '_wpml_location_migration_done';
|
||||
|
||||
private $sitepress;
|
||||
private $factory;
|
||||
private $new_translations_recieved = false;
|
||||
private $save_post_queue = array();
|
||||
private $is_registering_string = false;
|
||||
|
||||
private $strategies = array();
|
||||
|
||||
/** @var StringCleanUp[] */
|
||||
private $stringCleanUp = [];
|
||||
|
||||
/**
|
||||
* @var WPML_PB_Integration_Rescan
|
||||
*/
|
||||
private $rescan;
|
||||
|
||||
/** @var IWPML_PB_Media_Update[]|null $media_updaters */
|
||||
private $media_updaters;
|
||||
|
||||
/**
|
||||
* WPML_PB_Integration constructor.
|
||||
*
|
||||
* @param SitePress $sitepress
|
||||
* @param WPML_PB_Factory $factory
|
||||
*/
|
||||
public function __construct( SitePress $sitepress, WPML_PB_Factory $factory ) {
|
||||
$this->sitepress = $sitepress;
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IWPML_PB_Strategy $strategy
|
||||
*/
|
||||
public function add_strategy( IWPML_PB_Strategy $strategy ) {
|
||||
$this->strategies[] = $strategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_PB_Integration_Rescan
|
||||
*/
|
||||
public function get_rescan() {
|
||||
if ( null === $this->rescan ) {
|
||||
$this->rescan = new WPML_PB_Integration_Rescan( $this );
|
||||
}
|
||||
|
||||
return $this->rescan;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_PB_Integration_Rescan $rescan
|
||||
*/
|
||||
public function set_rescan( WPML_PB_Integration_Rescan $rescan ) {
|
||||
$this->rescan = $rescan;
|
||||
}
|
||||
|
||||
public function resave_post_translation_in_shutdown( WPML_Post_Element $post_element, $disallowed_in_shutdown = true ) {
|
||||
if ( ! $post_element->get_source_element()
|
||||
|| ( did_action( 'shutdown' ) && $disallowed_in_shutdown )
|
||||
|| array_key_exists( $post_element->get_id(), $this->save_post_queue )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( WPML_PB_Last_Translation_Edit_Mode::is_native_editor( $post_element->get_id() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$updated_packages = $this->factory->get_package_strings_resave()->from_element( $post_element );
|
||||
|
||||
if ( ! $updated_packages ) {
|
||||
$this->factory->get_handle_post_body()->copy(
|
||||
$post_element->get_id(),
|
||||
$post_element->get_source_element()->get_id(),
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
$this->with_strategies( function( IWPML_PB_Strategy $strategy ) use ( $updated_packages, $post_element ) {
|
||||
foreach ( $updated_packages as $package ) {
|
||||
$this->factory->get_string_translations( $strategy )
|
||||
->add_package_to_update_list( $package, $post_element->get_language_code() );
|
||||
}
|
||||
} );
|
||||
|
||||
$this->new_translations_recieved = true;
|
||||
$this->queue_save_post_actions( $post_element->get_id(), $post_element->get_wp_object() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $post_id
|
||||
* @param \WP_Post $post
|
||||
*/
|
||||
public function queue_save_post_actions( $post_id, $post ) {
|
||||
$this->update_last_editor_mode( (int) $post_id );
|
||||
$this->save_post_queue[ $post_id ] = $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WP_Post[]
|
||||
*/
|
||||
public function get_save_post_queue() {
|
||||
return $this->save_post_queue;
|
||||
}
|
||||
|
||||
/** @param int $post_id */
|
||||
private function update_last_editor_mode( $post_id ) {
|
||||
if ( ! $this->is_translation( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_editing_translation_with_native_editor( $post_id ) ) {
|
||||
WPML_PB_Last_Translation_Edit_Mode::set_native_editor( $post_id );
|
||||
} else {
|
||||
WPML_PB_Last_Translation_Edit_Mode::set_translation_editor( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the "translation auto-update" feature, an original update
|
||||
* can also trigger an update on the translations.
|
||||
* We need to make sure the globally edited post is matching with
|
||||
* the local one.
|
||||
*
|
||||
* @param int $translatedPostId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_editing_translation_with_native_editor( $translatedPostId ) {
|
||||
// $getPOST :: string -> mixed
|
||||
$getPOST = Obj::prop( Fns::__, $_POST ); // phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification
|
||||
|
||||
// $isQuickEditAction :: int -> bool
|
||||
$isQuickEditAction = function( $id ) use ( $getPOST ) {
|
||||
return wp_doing_ajax()
|
||||
&& 'inline-save' === $getPOST( 'action' )
|
||||
&& $id === (int) $getPOST( 'post_ID' );
|
||||
};
|
||||
|
||||
$isTranslationWithNativeEditor = ( 'editpost' === $getPOST( 'action' )
|
||||
&& (int) $getPOST( 'ID' ) === $translatedPostId )
|
||||
|| ( $isQuickEditAction( $translatedPostId ) && WPML_PB_Last_Translation_Edit_Mode::is_native_editor( $translatedPostId ) );
|
||||
|
||||
/**
|
||||
* This filter allows to override the result if a translation
|
||||
* is edited with a native editor, but not the WP one.
|
||||
*
|
||||
* @since WPML 4.5.0
|
||||
*
|
||||
* @param bool $isTranslationWithNativeEditor
|
||||
* @param int $translatedPostId
|
||||
*/
|
||||
return apply_filters( 'wpml_pb_is_editing_translation_with_native_editor', $isTranslationWithNativeEditor, $translatedPostId );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_translation( $postId ) {
|
||||
return (bool) $this->factory->get_post_element( $postId )->get_source_language_code();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
* @param bool $allowRegisteringPostTranslation Specifies if the string registration must be allowed for posts that are not original.
|
||||
*/
|
||||
public function register_all_strings_for_translation( $post, $allowRegisteringPostTranslation = false ) {
|
||||
if ( $post instanceof \WP_Post && $this->is_post_status_ok( $post ) && ( $allowRegisteringPostTranslation || $this->is_original_post( $post ) ) ) {
|
||||
$this->is_registering_string = true;
|
||||
$this->with_strategies( invoke( 'register_strings' )->with( $post ) );
|
||||
$this->is_registering_string = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post|\stdClass $post
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_original_post( $post ) {
|
||||
return $post->ID == $this->sitepress->get_original_element_id( $post->ID, 'post_' . $post->post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post|\stdClass $post
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_post_status_ok( $post ) {
|
||||
return ! in_array( $post->post_status, array( 'trash', 'auto-draft', 'inherit' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all actions filters.
|
||||
*/
|
||||
public function add_hooks() {
|
||||
add_action( 'pre_post_update', array( $this, 'migrate_location' ) );
|
||||
add_action( 'save_post', array( $this, 'queue_save_post_actions' ), PHP_INT_MAX, 2 );
|
||||
add_action( 'wpml_pb_resave_post_translation', array( $this, 'resave_post_translation_in_shutdown' ), 10, 1 );
|
||||
add_action( 'icl_st_add_string_translation', array( $this, 'new_translation' ), 10, 1 );
|
||||
add_action( 'wpml_pb_finished_adding_string_translations', array( $this, 'process_pb_content_with_hidden_strings_only' ), 9, 2 );
|
||||
add_action( 'wpml_pb_finished_adding_string_translations', array( $this, 'save_translations_to_post' ), 10 );
|
||||
add_action( 'wpml_pro_translation_completed', array( $this, 'cleanup_strings_after_translation_completed' ), 10, 3 );
|
||||
|
||||
add_filter( 'wpml_tm_translation_job_data', array( $this, 'rescan' ), 9, 2 );
|
||||
|
||||
add_action( 'wpml_pb_register_all_strings_for_translation', [ $this, 'register_all_strings_for_translation' ] );
|
||||
add_filter( 'wpml_pb_register_strings_in_content', [ $this, 'register_strings_in_content' ], 10, 3 );
|
||||
add_filter( 'wpml_pb_update_translations_in_content', [ $this, 'update_translations_in_content'], 10, 2 );
|
||||
|
||||
add_action( 'wpml_start_GB_register_strings', [ $this, 'initialize_string_clean_up' ], 10, 1 );
|
||||
add_action( 'wpml_end_GB_register_strings', [ $this, 'clean_up_strings' ], 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_post_id
|
||||
* @param array $fields
|
||||
* @param stdClass $job
|
||||
*/
|
||||
public function cleanup_strings_after_translation_completed( $new_post_id, array $fields, stdClass $job ) {
|
||||
if ( 'post' === $job->element_type_prefix ) {
|
||||
$original_post = get_post( $job->original_doc_id );
|
||||
$this->register_all_strings_for_translation( $original_post );
|
||||
}
|
||||
}
|
||||
|
||||
public function new_translation( $translated_string_id ) {
|
||||
if ( ! $this->is_registering_string ) {
|
||||
$this->with_strategies( function( $strategy ) use ( $translated_string_id ) {
|
||||
$this->factory->get_string_translations( $strategy )->new_translation( $translated_string_id );
|
||||
} );
|
||||
$this->new_translations_recieved = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
*/
|
||||
private function with_strategies( callable $callable ) {
|
||||
Fns::each( $callable, $this->strategies );
|
||||
}
|
||||
|
||||
/**
|
||||
* When a Page Builder content has only a "LINK" string, it's won't be part
|
||||
* of the translation job as it's automatically converted.
|
||||
* We need to add the package to the update list (by strategies).
|
||||
*
|
||||
* @param int $new_post_id
|
||||
* @param int $original_doc_id
|
||||
*/
|
||||
public function process_pb_content_with_hidden_strings_only( $new_post_id, $original_doc_id ) {
|
||||
if (
|
||||
! did_action( 'wpml_add_string_translation' )
|
||||
&& apply_filters( 'wpml_pb_is_page_builder_page', false, get_post( $new_post_id ) )
|
||||
) {
|
||||
$targetLang = $this->sitepress->get_language_for_element( $new_post_id, 'post_' . get_post_type( $new_post_id ) );
|
||||
|
||||
$addPackageToUpdateList = function( WPML_Package $package ) use ( $targetLang ) {
|
||||
$this->with_strategies( function( IWPML_PB_Strategy $strategy ) use ( $package, $targetLang ) {
|
||||
$this->factory
|
||||
->get_string_translations( $strategy )
|
||||
->add_package_to_update_list( $package, $targetLang );
|
||||
} );
|
||||
};
|
||||
|
||||
$this->new_translations_recieved = wpml_collect( apply_filters( 'wpml_st_get_post_string_packages', [], $original_doc_id ) )
|
||||
->each( $addPackageToUpdateList )
|
||||
->isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public function save_translations_to_post() {
|
||||
if ( $this->new_translations_recieved ) {
|
||||
$this->with_strategies( function( IWPML_PB_Strategy $strategy ) {
|
||||
$this->factory->get_string_translations( $strategy )->save_translations_to_post();
|
||||
} );
|
||||
$this->new_translations_recieved = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $lang
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_translations_in_content( $content, $lang ) {
|
||||
$this->with_strategies( function ( IWPML_PB_Strategy $strategy ) use ( &$content, $lang ) {
|
||||
$content = $this->factory->get_string_translations( $strategy )->update_translations_in_content( $content, $lang );
|
||||
} );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlst-958
|
||||
* @param array $translation_package
|
||||
* @param WP_Post|WPML_Package $post
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rescan( array $translation_package, $post ) {
|
||||
if ( $post instanceof WP_Post ) {
|
||||
$translation_package = $this->get_rescan()->rescan( $translation_package, $post );
|
||||
}
|
||||
|
||||
return $translation_package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function migrate_location( $post_id ) {
|
||||
if ( $this->post_has_strings( $post_id ) && ! $this->is_migrate_location_done( $post_id ) ) {
|
||||
$wpdb = $this->sitepress->get_wpdb();
|
||||
$post = $wpdb->get_row( $wpdb->prepare( "SELECT ID, post_type, post_status, post_content FROM {$wpdb->posts} WHERE ID = %d", $post_id ) );
|
||||
if ( $this->is_post_status_ok( $post ) && $this->is_original_post( $post ) ) {
|
||||
$this->with_strategies( invoke( 'migrate_location' )->with( $post_id, $post->post_content ) );
|
||||
}
|
||||
|
||||
$this->mark_migrate_location_done( $post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $registered
|
||||
* @param string|int $post_id
|
||||
* @param string $content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function register_strings_in_content( $registered, $post_id, $content ) {
|
||||
foreach ( $this->strategies as $strategy ) {
|
||||
$registered = $strategy->register_strings_in_content( $post_id, $content, $this->stringCleanUp[ $post_id ] ) || $registered;
|
||||
}
|
||||
return $registered;
|
||||
}
|
||||
|
||||
public function get_factory() {
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
public function initialize_string_clean_up( WP_Post $post ) {
|
||||
$shortcodeStrategy = make( WPML_PB_Shortcode_Strategy::class );
|
||||
$shortcodeStrategy->set_factory( $this->factory );
|
||||
$this->stringCleanUp[ $post->ID ] = new StringCleanUp( $post->ID, $shortcodeStrategy );
|
||||
}
|
||||
|
||||
public function clean_up_strings( WP_Post $post ) {
|
||||
$this->stringCleanUp[ $post->ID ]->cleanUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function post_has_strings( $post_id ) {
|
||||
$wpdb = $this->sitepress->get_wpdb();
|
||||
$string_packages_table = $wpdb->prefix . 'icl_string_packages';
|
||||
|
||||
if ( $wpdb->get_var( "SHOW TABLES LIKE '$string_packages_table'" ) !== $string_packages_table ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$string_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(ID) FROM {$string_packages_table} WHERE post_id = %d", $post_id) );
|
||||
return $string_count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_migrate_location_done( $post_id ) {
|
||||
return get_post_meta( $post_id, self::MIGRATION_DONE_POST_META, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*/
|
||||
private function mark_migrate_location_done( $post_id ) {
|
||||
update_post_meta( $post_id, WPML_PB_Integration::MIGRATION_DONE_POST_META, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function translate_media( $post ) {
|
||||
if ( $this->is_post_status_ok( $post ) && ! $this->is_original_post( $post ) ) {
|
||||
|
||||
foreach ( $this->get_media_updaters() as $updater ) {
|
||||
$updater->translate( $post );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return IWPML_PB_Media_Update[] $media_updaters */
|
||||
private function get_media_updaters() {
|
||||
if ( ! $this->media_updaters ) {
|
||||
$this->media_updaters = apply_filters( 'wpml_pb_get_media_updaters', array() );
|
||||
}
|
||||
|
||||
return $this->media_updaters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Last_Translation_Edit_Mode {
|
||||
|
||||
const POST_META_KEY = '_last_translation_edit_mode';
|
||||
const NATIVE_EDITOR = 'native-editor';
|
||||
const TRANSLATION_EDITOR = 'translation-editor';
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_native_editor( $post_id ) {
|
||||
return self::get_last_mode( $post_id ) === self::NATIVE_EDITOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_translation_editor( $post_id ) {
|
||||
return self::get_last_mode( $post_id ) === self::TRANSLATION_EDITOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function get_last_mode( $post_id ) {
|
||||
return get_post_meta( $post_id, self::POST_META_KEY, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function set_native_editor( $post_id ) {
|
||||
self::set_mode( $post_id, self::NATIVE_EDITOR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*/
|
||||
public static function set_translation_editor( $post_id ) {
|
||||
self::set_mode( $post_id, self::TRANSLATION_EDITOR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param string $mode
|
||||
*/
|
||||
private static function set_mode( $post_id, $mode ) {
|
||||
update_post_meta( $post_id, self::POST_META_KEY, $mode );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use WPML\PB\Container\Config;
|
||||
use function WPML\Container\make;
|
||||
use function WPML\Container\share;
|
||||
|
||||
class WPML_PB_Loader {
|
||||
|
||||
public function __construct(
|
||||
WPML_ST_Settings $st_settings,
|
||||
$pb_integration = null // Only needed for testing
|
||||
) {
|
||||
share( Config::getSharedClasses() );
|
||||
|
||||
do_action( 'wpml_load_page_builders_integration' );
|
||||
|
||||
$page_builder_strategies = array();
|
||||
|
||||
/**
|
||||
* This filter hook provide the API page builders names that need to be supported.
|
||||
*
|
||||
* For each PB name, we will create a dedicated strategy and a proper string package namespace.
|
||||
*
|
||||
* It's called in 2 places:
|
||||
* - `WPML_Page_Builders_Integration` for external plugins
|
||||
* - `WPML_Gutenberg_Integration` for WordPress Core block editor
|
||||
*
|
||||
* @param string[] $array Required plugin names (e.g. `Beaver Builder`, `Gutenberg`)
|
||||
*/
|
||||
$required = apply_filters( 'wpml_page_builder_support_required', array() );
|
||||
foreach ( $required as $plugin ) {
|
||||
$page_builder_strategies[] = new WPML_PB_API_Hooks_Strategy( $plugin );
|
||||
}
|
||||
|
||||
$page_builder_config_import = new WPML_PB_Config_Import_Shortcode( $st_settings );
|
||||
$page_builder_config_import->add_hooks();
|
||||
if ( $page_builder_config_import->has_settings() ) {
|
||||
$strategy = new WPML_PB_Shortcode_Strategy( new WPML_Page_Builder_Settings() );
|
||||
$strategy->add_shortcodes( $page_builder_config_import->get_settings() );
|
||||
$page_builder_strategies[] = $strategy;
|
||||
|
||||
if ( defined( 'WPML_MEDIA_VERSION' ) && $page_builder_config_import->get_media_settings() ) {
|
||||
$shortcodes_media_hooks = new WPML_Page_Builders_Media_Hooks(
|
||||
new WPML_Page_Builders_Media_Shortcodes_Update_Factory( $page_builder_config_import ),
|
||||
'shortcodes'
|
||||
);
|
||||
$shortcodes_media_hooks->add_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
self::load_hooks();
|
||||
|
||||
if ( $page_builder_strategies ) {
|
||||
if ( $pb_integration ) {
|
||||
$factory = $pb_integration->get_factory();
|
||||
} else {
|
||||
$factory = make( 'WPML_PB_Factory' );
|
||||
$pb_integration = make( 'WPML_PB_Integration' );
|
||||
}
|
||||
$pb_integration->add_hooks();
|
||||
foreach ( $page_builder_strategies as $strategy ) {
|
||||
$strategy->set_factory( $factory );
|
||||
$pb_integration->add_strategy( $strategy );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static function load_hooks() {
|
||||
$hooks = [
|
||||
WPML_PB_Handle_Post_Body::class,
|
||||
WPML\PB\AutoUpdate\Hooks::class,
|
||||
WPML\PB\Shutdown\Hooks::class,
|
||||
WPML\PB\GutenbergCleanup\ShortcodeHooks::class,
|
||||
WPML\PB\Shortcode\AdjustIdsHooks::class,
|
||||
];
|
||||
|
||||
make( WPML_Action_Filter_Loader::class )->load( $hooks );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Package_Strings_Resave {
|
||||
|
||||
/** @var WPML_ST_String_Factory $string_factory */
|
||||
private $string_factory;
|
||||
|
||||
public function __construct( WPML_ST_String_Factory $string_factory ) {
|
||||
$this->string_factory = $string_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_Post_Element $post_element
|
||||
*
|
||||
* @return WPML_Package[]
|
||||
*/
|
||||
public function from_element( WPML_Post_Element $post_element ) {
|
||||
if ( ! $post_element->get_source_element() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$target_lang = $post_element->get_language_code();
|
||||
$original_post_id = $post_element->get_source_element()->get_id();
|
||||
|
||||
/** @var WPML_Package[] $string_packages */
|
||||
$string_packages = apply_filters( 'wpml_st_get_post_string_packages', array(), $original_post_id );
|
||||
|
||||
foreach ( $string_packages as $string_package ) {
|
||||
|
||||
/** @var stdClass[] $strings */
|
||||
$strings = $string_package->get_package_strings();
|
||||
|
||||
foreach ( $strings as $string ) {
|
||||
$this->resave_string_translation( $string->id, $target_lang );
|
||||
}
|
||||
}
|
||||
|
||||
return $string_packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $string_id
|
||||
* @param string $target_lang
|
||||
*/
|
||||
private function resave_string_translation( $string_id, $target_lang ) {
|
||||
$string = $this->string_factory->find_by_id( $string_id );
|
||||
$translations = wp_list_filter( $string->get_translations(), array( 'language' => $target_lang ) );
|
||||
|
||||
if ( $translations ) {
|
||||
$translation = reset( $translations );
|
||||
|
||||
$string->set_translation(
|
||||
$target_lang,
|
||||
$translation->value,
|
||||
$translation->status,
|
||||
$translation->translator_id,
|
||||
$translation->translation_service,
|
||||
$translation->batch_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Integration_Rescan {
|
||||
/**
|
||||
* @var WPML_PB_Integration
|
||||
*/
|
||||
private $integrator;
|
||||
|
||||
/**
|
||||
* @param WPML_PB_Integration $integrator
|
||||
*/
|
||||
public function __construct( WPML_PB_Integration $integrator ) {
|
||||
$this->integrator = $integrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rescan post content if it does not contain packages
|
||||
*
|
||||
* @see https://onthegosystems.myjetbrains.com/youtrack/issue/wpmlst-958
|
||||
*
|
||||
* @param array $translation_package
|
||||
* @param \WP_Post $post
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rescan( array $translation_package, $post ) {
|
||||
$string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $post->ID );
|
||||
if ( ! $string_packages ) {
|
||||
$this->integrator->register_all_strings_for_translation( $post );
|
||||
}
|
||||
|
||||
return $translation_package;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Reuse_Translations_By_Strategy extends WPML_PB_Reuse_Translations {
|
||||
|
||||
/** @var IWPML_PB_Strategy $strategy */
|
||||
private $strategy;
|
||||
|
||||
/** @var array $original_strings */
|
||||
private $original_strings_by_strategy;
|
||||
|
||||
public function __construct( IWPML_PB_Strategy $strategy, WPML_ST_String_Factory $string_factory ) {
|
||||
$this->strategy = $strategy;
|
||||
parent::__construct( $string_factory );
|
||||
}
|
||||
|
||||
/** @param array $strings */
|
||||
public function set_original_strings( array $strings ) {
|
||||
$this->original_strings_by_strategy = $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param array $leftover_strings
|
||||
*/
|
||||
public function find_and_reuse( $post_id, array $leftover_strings ) {
|
||||
$current_strings = $this->get_strings( $post_id );
|
||||
$this->find_and_reuse_translations( $this->original_strings_by_strategy, $current_strings, $leftover_strings );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_strings( $post_id ) {
|
||||
return $this->strategy->get_package_strings( $this->strategy->get_package_key( $post_id ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Reuse_Translations {
|
||||
|
||||
/** @var WPML_ST_String_Factory $string_factory */
|
||||
private $string_factory;
|
||||
|
||||
/** @var array $original_strings */
|
||||
private $original_strings;
|
||||
|
||||
/** @var array $current_strings */
|
||||
private $current_strings;
|
||||
|
||||
public function __construct( WPML_ST_String_Factory $string_factory ) {
|
||||
$this->string_factory = $string_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* We receive arrays of strings with this structure:
|
||||
*
|
||||
* array(
|
||||
* 'gf4544ds454sds542122sd' => array(
|
||||
* 'value' => 'The string value',
|
||||
* 'context' => 'the-string-context',
|
||||
* 'name' => 'the-string-name',
|
||||
* 'id' => 123,
|
||||
* 'package_id' => 123,
|
||||
* 'location' => 123,
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
* The key is the string hash.
|
||||
*
|
||||
* @param array[] $original_strings
|
||||
* @param array[] $current_strings
|
||||
* @param array[] $leftover_strings
|
||||
*/
|
||||
public function find_and_reuse_translations( array $original_strings, array $current_strings, array $leftover_strings ) {
|
||||
$this->original_strings = $original_strings;
|
||||
$this->current_strings = $current_strings;
|
||||
$new_strings = $this->find_new_strings();
|
||||
$new_strings_to_update = $this->find_existing_strings_for_new_strings( $new_strings, $leftover_strings );
|
||||
$this->reuse_translations( $new_strings_to_update );
|
||||
}
|
||||
|
||||
/** @return array */
|
||||
private function find_new_strings() {
|
||||
$new_strings = array();
|
||||
|
||||
foreach ( $this->current_strings as $current_string ) {
|
||||
$found = false;
|
||||
foreach ( $this->original_strings as $original_string ) {
|
||||
if ( $current_string['id'] == $original_string['id'] ) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( ! $found ) {
|
||||
$new_strings[ $current_string['id'] ] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $new_strings
|
||||
* @param array[] $leftover_strings
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function find_existing_strings_for_new_strings( array $new_strings, array $leftover_strings ) {
|
||||
|
||||
list( $new_strings, $leftover_strings ) = $this->find_by_location( $new_strings, $leftover_strings );
|
||||
$new_strings = $this->find_by_similar_text( $new_strings, $leftover_strings );
|
||||
|
||||
return $new_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $new_strings
|
||||
* @param array[] $leftover_strings
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
private function find_by_location( array $new_strings, array $leftover_strings ) {
|
||||
if ( ! $leftover_strings ) {
|
||||
return array( $new_strings, $leftover_strings );
|
||||
}
|
||||
|
||||
if ( ( count( $this->current_strings ) - count( $leftover_strings ) ) !== count( $this->original_strings ) ) {
|
||||
return array( $new_strings, $leftover_strings );
|
||||
}
|
||||
|
||||
foreach ( $leftover_strings as $key => $leftover_string ) {
|
||||
foreach ( $this->current_strings as $current_string ) {
|
||||
if ( isset( $new_strings[ $current_string['id'] ] ) ) {
|
||||
if ( $this->is_same_location_and_different_ids( $current_string, $leftover_string )
|
||||
&& $this->is_similar_text( $leftover_string['value'], $current_string['value'] ) ) {
|
||||
$new_strings[ $current_string['id'] ] = $leftover_string['id'];
|
||||
unset( $leftover_strings[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array( $new_strings, $leftover_strings );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $new_strings
|
||||
* @param array[] $leftover_strings
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function find_by_similar_text( array $new_strings, array $leftover_strings ) {
|
||||
|
||||
if ( $leftover_strings ) {
|
||||
foreach ( $new_strings as $new_string_id => $old_string_id ) {
|
||||
if ( ! $old_string_id ) {
|
||||
$new_string = $this->string_factory->find_by_id( $new_string_id );
|
||||
$new_string_value = $new_string->get_value();
|
||||
foreach ( $leftover_strings as $key => $leftover_string ) {
|
||||
$leftover_string_id = $leftover_string['id'];
|
||||
$leftover_string = $this->string_factory->find_by_id( $leftover_string_id );
|
||||
$leftover_string_value = $leftover_string->get_value();
|
||||
|
||||
if ( $this->is_similar_text( $leftover_string_value, $new_string_value ) ) {
|
||||
$new_strings[ $new_string_id ] = $leftover_string_id;
|
||||
unset( $leftover_strings[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $new_strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $current_string
|
||||
* @param array $leftover_string
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_same_location_and_different_ids( array $current_string, array $leftover_string ) {
|
||||
return $current_string['location'] === $leftover_string['location']
|
||||
&& $current_string['id'] !== $leftover_string['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $old_text
|
||||
* @param string $new_text
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_similar_text( $old_text, $new_text ) {
|
||||
return WPML_ST_Diff::get_sameness_percent( $old_text, $new_text ) > 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $strings
|
||||
*/
|
||||
private function reuse_translations( array $strings ) {
|
||||
foreach ( $strings as $new_string_id => $old_string_id ) {
|
||||
|
||||
if ( $old_string_id ) {
|
||||
$new_string = $this->string_factory->find_by_id( $new_string_id );
|
||||
$old_string = $this->string_factory->find_by_id( $old_string_id );
|
||||
$translations = $old_string->get_translations();
|
||||
|
||||
foreach ( $translations as $translation ) {
|
||||
$status = $translation->status == ICL_TM_COMPLETE ? ICL_TM_NEEDS_UPDATE : $translation->status;
|
||||
$new_string->set_translation(
|
||||
$translation->language,
|
||||
$translation->value,
|
||||
$status,
|
||||
$translation->translator_id,
|
||||
$translation->translation_service,
|
||||
$translation->batch_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_PB_String_Registration
|
||||
*/
|
||||
class WPML_PB_String_Registration {
|
||||
|
||||
/** @var IWPML_PB_Strategy $strategy */
|
||||
private $strategy;
|
||||
/** @var WPML_ST_String_Factory $string_factory */
|
||||
private $string_factory;
|
||||
/** @var WPML_ST_Package_Factory $package_factory */
|
||||
private $package_factory;
|
||||
/** @var WPML_Translate_Link_Targets $translate_link_targets */
|
||||
private $translate_link_targets;
|
||||
|
||||
/** @var callable $set_link_translations */
|
||||
private $set_link_translations;
|
||||
|
||||
/** @var bool $migration_mode */
|
||||
private $migration_mode;
|
||||
|
||||
/**
|
||||
* WPML_PB_String_Registration constructor.
|
||||
*
|
||||
* @param IWPML_PB_Strategy $strategy
|
||||
* @param WPML_ST_String_Factory $string_factory
|
||||
* @param WPML_ST_Package_Factory $package_factory
|
||||
* @param WPML_Translate_Link_Targets $translate_link_targets
|
||||
* @param callable $set_link_translations
|
||||
* @param bool $migration_mode
|
||||
*/
|
||||
public function __construct(
|
||||
IWPML_PB_Strategy $strategy,
|
||||
WPML_ST_String_Factory $string_factory,
|
||||
WPML_ST_Package_Factory $package_factory,
|
||||
WPML_Translate_Link_Targets $translate_link_targets,
|
||||
callable $set_link_translations,
|
||||
$migration_mode = false
|
||||
) {
|
||||
$this->strategy = $strategy;
|
||||
$this->string_factory = $string_factory;
|
||||
$this->package_factory = $package_factory;
|
||||
$this->translate_link_targets = $translate_link_targets;
|
||||
$this->set_link_translations = $set_link_translations;
|
||||
$this->migration_mode = $migration_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param string $content
|
||||
* @param string $name
|
||||
*
|
||||
* @return null|int
|
||||
*/
|
||||
public function get_string_id_from_package( $post_id, $content, $name = '' ) {
|
||||
$package_data = $this->strategy->get_package_key( $post_id );
|
||||
$package = $this->package_factory->create( $package_data );
|
||||
$string_name = $name ? $name : md5( $content );
|
||||
$string_name = $package->sanitize_string_name( $string_name );
|
||||
$string_value = $content;
|
||||
|
||||
return apply_filters( 'wpml_string_id_from_package', null, $package, $string_name, $string_value );
|
||||
}
|
||||
|
||||
public function get_string_title( $string_id ) {
|
||||
return apply_filters( 'wpml_string_title_from_id', null, $string_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register string.
|
||||
*
|
||||
* @param int $post_id Post Id.
|
||||
* @param string $content String content.
|
||||
* @param string $type String editor type.
|
||||
* @param string $title String title.
|
||||
* @param string $name String name.
|
||||
* @param int $location String location.
|
||||
* @param string $wrap_tag String wrap tag.
|
||||
*
|
||||
* @return null|integer $string_id
|
||||
*/
|
||||
public function register_string(
|
||||
$post_id,
|
||||
$content = '',
|
||||
$type = 'LINE',
|
||||
$title = '',
|
||||
$name = '',
|
||||
$location = 0,
|
||||
$wrap_tag = ''
|
||||
) {
|
||||
|
||||
$string_id = 0;
|
||||
|
||||
if ( trim( $content ) ) {
|
||||
|
||||
$string_name = $name ? $name : md5( $content );
|
||||
|
||||
if ( $this->migration_mode ) {
|
||||
|
||||
$string_id = $this->get_string_id_from_package( $post_id, $content, $string_name );
|
||||
$this->update_string_data( $string_id, $location, $wrap_tag );
|
||||
|
||||
} else {
|
||||
|
||||
if ( 'LINK' === $type && ! $this->translate_link_targets->is_internal_url( $content ) ) {
|
||||
$type = 'LINE';
|
||||
}
|
||||
|
||||
$string_value = $content;
|
||||
$package = $this->strategy->get_package_key( $post_id );
|
||||
$string_title = $title ? $title : $string_value;
|
||||
do_action( 'wpml_register_string', $string_value, $string_name, $package, $string_title, $type );
|
||||
|
||||
$string_id = $this->get_string_id_from_package( $post_id, $content, $string_name );
|
||||
$this->update_string_data( $string_id, $location, $wrap_tag );
|
||||
|
||||
if ( 'LINK' === $type ) {
|
||||
call_user_func( $this->set_link_translations, $string_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $string_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update string data: location and wrap tag.
|
||||
* Wrap tag is used for SEO significance, can contain values as h1 ... h6, etc.
|
||||
*
|
||||
* @param int $string_id String id.
|
||||
* @param string $location String location inside of the page builder content.
|
||||
* @param string $wrap_tag String wrap tag for SEO significance.
|
||||
*/
|
||||
private function update_string_data( $string_id, $location, $wrap_tag ) {
|
||||
$string = $this->string_factory->find_by_id( $string_id );
|
||||
$string->set_location( $location );
|
||||
$string->set_wrap_tag( $wrap_tag );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
use WPML\PB\ShortCodesInGutenbergBlocks;
|
||||
|
||||
class WPML_PB_String_Translation_By_Strategy extends WPML_PB_String_Translation {
|
||||
|
||||
/** @var WPML_PB_Factory $factory */
|
||||
private $factory;
|
||||
|
||||
/** @var IWPML_PB_Strategy $strategy */
|
||||
private $strategy;
|
||||
|
||||
/** @var array $packages_to_update */
|
||||
private $packages_to_update = array();
|
||||
|
||||
public function __construct( wpdb $wpdb, WPML_PB_Factory $factory, IWPML_PB_Strategy $strategy ) {
|
||||
$this->factory = $factory;
|
||||
$this->strategy = $strategy;
|
||||
parent::__construct( $wpdb );
|
||||
}
|
||||
|
||||
/** @param int $translated_string_id */
|
||||
public function new_translation( $translated_string_id ) {
|
||||
list( $package_id, $string_id, $language ) = $this->get_package_for_translated_string( $translated_string_id );
|
||||
if ( $package_id ) {
|
||||
$package = $this->factory->get_wpml_package( $package_id );
|
||||
if ( $package->post_id ) {
|
||||
$strategyKind = $this->strategy->get_package_kind();
|
||||
if ( $strategyKind === $package->kind ) {
|
||||
$this->add_package_to_update_list( $package, $language );
|
||||
}
|
||||
ShortCodesInGutenbergBlocks::recordPackage(
|
||||
$this,
|
||||
$strategyKind,
|
||||
$package,
|
||||
$language
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function save_translations_to_post() {
|
||||
foreach ( $this->packages_to_update as $package_data ) {
|
||||
$package_data = ShortCodesInGutenbergBlocks::fixupPackage( $package_data );
|
||||
if ( $package_data['package']->kind == $this->strategy->get_package_kind() ) {
|
||||
$update_post = $this->strategy->get_update_post( $package_data );
|
||||
$update_post->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $lang
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_translations_in_content( $content, $lang ) {
|
||||
foreach ( $this->packages_to_update as $package_data ) {
|
||||
if ( $package_data['package']->kind == $this->strategy->get_package_kind() ) {
|
||||
$update_post = $this->strategy->get_update_post( $package_data );
|
||||
$content = $update_post->update_content( $content, $lang );
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $translated_string_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_package_for_translated_string( $translated_string_id ) {
|
||||
$sql = $this->wpdb->prepare(
|
||||
"SELECT s.string_package_id, s.id, t.language
|
||||
FROM {$this->wpdb->prefix}icl_strings s
|
||||
LEFT JOIN {$this->wpdb->prefix}icl_string_translations t
|
||||
ON s.id = t.string_id
|
||||
WHERE t.id = %d", $translated_string_id );
|
||||
$result = $this->wpdb->get_row( $sql );
|
||||
|
||||
if ( $result ) {
|
||||
// @phpstan-ignore-next-line
|
||||
return array( $result->string_package_id, $result->id, $result->language );
|
||||
} else {
|
||||
return array( null, null, null );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_Package $package
|
||||
* @param string $language
|
||||
*/
|
||||
public function add_package_to_update_list( WPML_Package $package, $language ) {
|
||||
if ( ! isset( $this->packages_to_update[ $package->ID ] ) ) {
|
||||
$this->packages_to_update[ $package->ID ] = array( 'package' => $package,
|
||||
'languages' => array( $language )
|
||||
);
|
||||
} else {
|
||||
if ( ! in_array( $language, $this->packages_to_update[ $package->ID ]['languages'] ) ) {
|
||||
$this->packages_to_update[ $package->ID ]['languages'][] = $language;
|
||||
}
|
||||
}
|
||||
|
||||
$this->packages_to_update = ShortCodesInGutenbergBlocks::normalizePackages( $this->packages_to_update );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_String_Translation {
|
||||
|
||||
/** @var wpdb $wpdb */
|
||||
protected $wpdb;
|
||||
|
||||
public function __construct( wpdb $wpdb ) {
|
||||
$this->wpdb = $wpdb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $package_data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_package_strings( array $package_data ) {
|
||||
$strings = array();
|
||||
$package_id = $this->get_package_id( $package_data );
|
||||
if ( $package_id ) {
|
||||
$sql_to_get_strings_with_package_id = $this->wpdb->prepare( "SELECT *
|
||||
FROM {$this->wpdb->prefix}icl_strings s
|
||||
WHERE s.string_package_id=%d",
|
||||
$package_id );
|
||||
|
||||
$package_strings = $this->wpdb->get_results( $sql_to_get_strings_with_package_id );
|
||||
|
||||
if ( ! empty( $package_strings ) ) {
|
||||
foreach ( $package_strings as $string ) {
|
||||
$strings[ $this->get_string_hash( $string->value ) ] = array(
|
||||
'value' => $string->value,
|
||||
'context' => $string->context,
|
||||
'name' => $string->name,
|
||||
'id' => $string->id,
|
||||
'package_id' => $package_id,
|
||||
'location' => $string->location,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $strings;
|
||||
}
|
||||
|
||||
public function remove_string( array $string_data ) {
|
||||
icl_unregister_string( $string_data['context'], $string_data['name'] );
|
||||
|
||||
$field_type = 'package-string-' . $string_data['package_id'] . '-' . $string_data['id'];
|
||||
$job_id = $this->get_job_id( $field_type );
|
||||
if ( ! $job_id || ! $this->is_job_in_progress( $job_id ) ) {
|
||||
$this->wpdb->delete( $this->wpdb->prefix . 'icl_translate', array( 'field_type' => $field_type ), array( '%s' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field_type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function get_job_id( $field_type ) {
|
||||
return $this->wpdb->get_var( $this->wpdb->prepare( "SELECT MAX(job_id) FROM {$this->wpdb->prefix}icl_translate WHERE field_type = %s", $field_type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $job_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_job_in_progress( $job_id ) {
|
||||
return ! (bool) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT translated FROM {$this->wpdb->prefix}icl_translate_job WHERE job_id = %d", $job_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $package_data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function get_package_id( array $package_data ) {
|
||||
$package_id = false;
|
||||
$sql_to_get_package_id = $this->wpdb->prepare( "SELECT s.ID
|
||||
FROM {$this->wpdb->prefix}icl_string_packages s
|
||||
WHERE s.kind=%s AND s.name=%s AND s.title=%s AND s.post_id=%s",
|
||||
$package_data['kind'], $package_data['name'], $package_data['title'], $package_data['post_id'] );
|
||||
|
||||
$result = $this->wpdb->get_row( $sql_to_get_package_id );
|
||||
|
||||
if ( isset( $result->ID ) ) {
|
||||
$package_id = $result->ID;
|
||||
}
|
||||
|
||||
return $package_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string_value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_string_hash( $string_value ) {
|
||||
return md5( $string_value );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_String {
|
||||
|
||||
/** @var string $value */
|
||||
private $value;
|
||||
|
||||
/** @var string $name */
|
||||
private $name;
|
||||
/** @var string $title */
|
||||
private $title;
|
||||
/** @var string $editor_type */
|
||||
private $editor_type;
|
||||
|
||||
/**
|
||||
* String wrap tag.
|
||||
*
|
||||
* @var string $wrap_tag
|
||||
*/
|
||||
private $wrap_tag;
|
||||
|
||||
/**
|
||||
* WPML_PB_String constructor.
|
||||
*
|
||||
* @param string $value String value.
|
||||
* @param string $name String name.
|
||||
* @param string $title String title.
|
||||
* @param string $editor_type Editor type used.
|
||||
* @param string $wrap_tag String wrap tag.
|
||||
*/
|
||||
public function __construct( $value, $name, $title, $editor_type, $wrap_tag = '' ) {
|
||||
$this->value = $value;
|
||||
$this->name = $name;
|
||||
$this->title = $title;
|
||||
$this->editor_type = $editor_type;
|
||||
$this->wrap_tag = $wrap_tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_value() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function set_value( $value ) {
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_editor_type() {
|
||||
return $this->editor_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string wrap tag.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_wrap_tag() {
|
||||
return $this->wrap_tag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use \WPML\FP\Wrapper;
|
||||
use function \WPML\FP\invoke;
|
||||
|
||||
class WPML_PB_Update_Post {
|
||||
|
||||
private $package_data;
|
||||
/** @var IWPML_PB_Strategy $strategy */
|
||||
private $strategy;
|
||||
/** @var wpdb $wpdb */
|
||||
private $wpdb;
|
||||
/** @var SitePress $sitepress */
|
||||
private $sitepress;
|
||||
|
||||
public function __construct( $wpdb, $sitepress, $package_data, IWPML_PB_Strategy $strategy ) {
|
||||
$this->wpdb = $wpdb;
|
||||
$this->sitepress = $sitepress;
|
||||
$this->package_data = $package_data;
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
|
||||
public function update() {
|
||||
|
||||
$package = $this->package_data['package'];
|
||||
$original_post_id = $package->post_id;
|
||||
$post = get_post( $original_post_id );
|
||||
$element_type = 'post_' . $post->post_type;
|
||||
$trid = $this->sitepress->get_element_trid( $original_post_id, $element_type );
|
||||
$post_translations = $this->sitepress->get_element_translations( $trid, $element_type, false, true );
|
||||
|
||||
$languages = $this->package_data['languages'];
|
||||
|
||||
$string_translations = $package->get_translated_strings( array() );
|
||||
|
||||
foreach ( $languages as $lang ) {
|
||||
if ( isset( $post_translations[ $lang ] ) ) {
|
||||
$this->update_post( $post_translations[ $lang ]->element_id, $post, $string_translations, $lang );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param string $lang
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_content( $content, $lang ) {
|
||||
return Wrapper::of( $this->strategy )
|
||||
->map( invoke( 'get_content_updater' ) )
|
||||
->map( invoke( 'update_content' )->with(
|
||||
$content,
|
||||
$this->package_data['package']->get_translated_strings( [] ),
|
||||
$lang
|
||||
) )
|
||||
->get();
|
||||
}
|
||||
|
||||
private function update_post( $translated_post_id, $original_post, $string_translations, $lang ) {
|
||||
$content_updater = $this->strategy->get_content_updater();
|
||||
$content_updater->update( $translated_post_id, $original_post, $string_translations, $lang );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class WPML_ST_PB_Plugin {
|
||||
function check_requirements() {
|
||||
if ( defined( 'WPML_PAGE_BUILDERS_VERSION' ) ) {
|
||||
add_action( 'admin_notices', array( $this, 'disable_old_pb_notice' ) );
|
||||
}
|
||||
}
|
||||
|
||||
function is_active() {
|
||||
return defined( 'WPML_PAGE_BUILDERS_VERSION' );
|
||||
}
|
||||
|
||||
function ask_to_deactivate() {
|
||||
add_action( 'admin_notices', array( $this, 'disable_old_pb_notice' ) );
|
||||
}
|
||||
|
||||
function disable_old_pb_notice() {
|
||||
$plugin_name = plugin_basename( WPML_PAGE_BUILDERS_PATH . '/plugin.php' );
|
||||
$plugins_url = admin_url( '/plugins.php' );
|
||||
$plugins_url = add_query_arg(
|
||||
array(
|
||||
'action' => 'deactivate',
|
||||
'plugin_status' => 'inactive',
|
||||
'_wpnonce' => urlencode( wp_create_nonce( 'deactivate-plugin_' . $plugin_name ) ),
|
||||
'plugin' => urlencode( $plugin_name ),
|
||||
),
|
||||
$plugins_url
|
||||
);
|
||||
?>
|
||||
<div class="message error">
|
||||
<p>
|
||||
<?php esc_html_e( "The WPML Page Builders plugin that you're using is now part of WPML.", 'sitepress' ); ?>
|
||||
</p>
|
||||
<p>
|
||||
<?php esc_html_e( 'You need to deactivate the separate plugin.', 'sitepress' ); ?>
|
||||
</p>
|
||||
<p>
|
||||
<?php esc_html_e( 'No worries, the full functionality is preserved in WPML String Translation.', 'sitepress' ); ?>
|
||||
</p>
|
||||
<p>
|
||||
<a class="button-primary" href="<?php echo esc_url( $plugins_url ); ?>"><?php esc_html_e( 'Deactivate WPML Page Builders', 'sitepress' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use function WPML\Container\make;
|
||||
|
||||
/**
|
||||
* Class WPML_String_Registration_Factory
|
||||
*/
|
||||
class WPML_String_Registration_Factory {
|
||||
|
||||
private $pb_plugin_name;
|
||||
|
||||
public function __construct( $pb_plugin_name ) {
|
||||
$this->pb_plugin_name = $pb_plugin_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_PB_String_Registration
|
||||
*/
|
||||
public function create() {
|
||||
global $sitepress;
|
||||
|
||||
$string_factory = make( 'WPML_ST_String_Factory' );
|
||||
|
||||
return new WPML_PB_String_Registration(
|
||||
new WPML_PB_API_Hooks_Strategy( $this->pb_plugin_name ),
|
||||
$string_factory,
|
||||
new WPML_ST_Package_Factory(),
|
||||
make( 'WPML_Translate_Link_Targets' ),
|
||||
WPML\PB\TranslateLinks::getTranslatorForString( $string_factory, $sitepress->get_active_languages() )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_Page_Builders_Defined
|
||||
*/
|
||||
class WPML_Page_Builders_Defined {
|
||||
|
||||
private $settings;
|
||||
|
||||
public function __construct() {
|
||||
$this->init_settings();
|
||||
}
|
||||
|
||||
public function has( $page_builder ) {
|
||||
global $wp_version;
|
||||
if ( 'gutenberg' === $page_builder ) {
|
||||
if ( version_compare( $wp_version, '5.0-beta1', '>=' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->settings[ $page_builder ]['constant'] ) ) {
|
||||
return defined( $this->settings[ $page_builder ]['constant'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->settings[ $page_builder ]['function'] ) ) {
|
||||
return function_exists( $this->settings[ $page_builder ]['function'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $components
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_components( $components ) {
|
||||
if ( isset( $components['page-builders'] ) ) {
|
||||
foreach (
|
||||
array(
|
||||
'beaver-builder' => 'Beaver Builder',
|
||||
'elementor' => 'Elementor',
|
||||
'gutenberg' => 'Gutenberg',
|
||||
'cornerstone' => 'Cornerstone',
|
||||
'siteorigin' => 'SiteOrigin',
|
||||
) as $key => $name
|
||||
) {
|
||||
$components['page-builders'][ $key ] = array(
|
||||
'name' => $name,
|
||||
'constant' => isset( $this->settings[ $key ]['constant'] ) ? $this->settings[ $key ]['constant'] : null,
|
||||
'function' => isset( $this->settings[ $key ]['function'] ) ? $this->settings[ $key ]['function'] : null,
|
||||
'notices-display' => array(
|
||||
'wpml-translation-editor',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $components;
|
||||
}
|
||||
|
||||
public function init_settings() {
|
||||
$this->settings = array(
|
||||
'beaver-builder' => array(
|
||||
'constant' => 'FL_BUILDER_VERSION',
|
||||
'factory' => 'WPML_Beaver_Builder_Integration_Factory',
|
||||
),
|
||||
'elementor' => array(
|
||||
'constant' => 'ELEMENTOR_VERSION',
|
||||
'factory' => 'WPML_Elementor_Integration_Factory',
|
||||
),
|
||||
'gutenberg' => array(
|
||||
'constant' => 'GUTENBERG_VERSION',
|
||||
'factory' => 'WPML_Gutenberg_Integration_Factory',
|
||||
),
|
||||
'cornerstone' => array(
|
||||
'constant' => 'CS_VERSION',
|
||||
'factory' => 'WPML_Cornerstone_Integration_Factory',
|
||||
),
|
||||
'siteorigin' => array(
|
||||
'constant' => 'SITEORIGIN_PANELS_VERSION',
|
||||
'factory' => \WPML\PB\SiteOrigin\Factory::class,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_settings() {
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_Page_Builders_Register_Strings
|
||||
*/
|
||||
abstract class WPML_Page_Builders_Register_Strings {
|
||||
|
||||
/**
|
||||
* @var IWPML_Page_Builders_Translatable_Nodes
|
||||
*/
|
||||
private $translatable_nodes;
|
||||
|
||||
/**
|
||||
* @var IWPML_Page_Builders_Data_Settings
|
||||
*/
|
||||
protected $data_settings;
|
||||
|
||||
/**
|
||||
* @var WPML_PB_String_Registration
|
||||
*/
|
||||
private $string_registration;
|
||||
|
||||
/** @var WPML_PB_Reuse_Translations_By_Strategy|null $reuse_translations */
|
||||
private $reuse_translations;
|
||||
|
||||
/** @var int $string_location */
|
||||
private $string_location;
|
||||
|
||||
public function __construct(
|
||||
IWPML_Page_Builders_Translatable_Nodes $translatable_nodes,
|
||||
IWPML_Page_Builders_Data_Settings $data_settings,
|
||||
WPML_PB_String_Registration $string_registration,
|
||||
WPML_PB_Reuse_Translations_By_Strategy $reuse_translations = null
|
||||
) {
|
||||
|
||||
$this->data_settings = $data_settings;
|
||||
$this->translatable_nodes = $translatable_nodes;
|
||||
$this->string_registration = $string_registration;
|
||||
$this->reuse_translations = $reuse_translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
* @param array $package
|
||||
*/
|
||||
public function register_strings( WP_Post $post, array $package ) {
|
||||
|
||||
do_action( 'wpml_start_string_package_registration', $package );
|
||||
|
||||
$this->string_location = 1;
|
||||
|
||||
if ( $this->data_settings->is_handling_post( $post->ID ) ) {
|
||||
|
||||
if ( $this->reuse_translations ) {
|
||||
$existing_strings = $this->reuse_translations->get_strings( $post->ID );
|
||||
$this->reuse_translations->set_original_strings( $existing_strings );
|
||||
}
|
||||
|
||||
$data = get_post_meta( $post->ID, $this->data_settings->get_meta_field(), false );
|
||||
|
||||
if ( $data ) {
|
||||
$converted = $this->data_settings->convert_data_to_array( $data );
|
||||
if ( is_array( $converted ) ) {
|
||||
$this->register_strings_for_modules(
|
||||
$converted,
|
||||
$package
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->reuse_translations ) {
|
||||
// @phpstan-ignore-next-line
|
||||
$this->reuse_translations->find_and_reuse( $post->ID, $existing_strings );
|
||||
}
|
||||
}
|
||||
|
||||
do_action( 'wpml_delete_unused_package_strings', $package );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $node_id
|
||||
* @param mixed $element
|
||||
* @param array $package
|
||||
*/
|
||||
protected function register_strings_for_node( $node_id, $element, array $package ) {
|
||||
$strings = $this->translatable_nodes->get( $node_id, $element );
|
||||
foreach ( $strings as $string ) {
|
||||
$this->string_registration->register_string(
|
||||
$package['post_id'],
|
||||
$string->get_value(),
|
||||
$string->get_editor_type(),
|
||||
$string->get_title(),
|
||||
$string->get_name(),
|
||||
$this->string_location,
|
||||
$string->get_wrap_tag()
|
||||
);
|
||||
|
||||
$this->string_location++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data_array
|
||||
* @param array $package
|
||||
*/
|
||||
abstract protected function register_strings_for_modules( array $data_array, array $package );
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_Page_Builders_Update_Translation
|
||||
*/
|
||||
abstract class WPML_Page_Builders_Update_Translation extends WPML_Page_Builders_Update {
|
||||
|
||||
const TRANSLATION_COMPLETE = 10;
|
||||
|
||||
/**
|
||||
* @var IWPML_Page_Builders_Translatable_Nodes
|
||||
*/
|
||||
protected $translatable_nodes;
|
||||
|
||||
private $string_translations;
|
||||
private $lang;
|
||||
|
||||
public function __construct(
|
||||
IWPML_Page_Builders_Translatable_Nodes $translatable_nodes,
|
||||
IWPML_Page_Builders_Data_Settings $data_settings
|
||||
) {
|
||||
$this->translatable_nodes = $translatable_nodes;
|
||||
parent::__construct( $data_settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $translated_post_id
|
||||
* @param \WP_Post|stdClass $original_post
|
||||
* @param array $string_translations
|
||||
* @param string $lang
|
||||
*/
|
||||
public function update( $translated_post_id, $original_post, $string_translations, $lang ) {
|
||||
$this->string_translations = $string_translations;
|
||||
$this->lang = $lang;
|
||||
|
||||
$converted_data = $this->get_converted_data( $original_post->ID );
|
||||
$this->update_strings_in_modules( $converted_data );
|
||||
$this->save( $translated_post_id, $original_post->ID, $converted_data );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WPML_PB_String $string
|
||||
*
|
||||
* @return WPML_PB_String
|
||||
*/
|
||||
protected function get_translation( WPML_PB_String $string ) {
|
||||
if ( array_key_exists( $string->get_name(), $this->string_translations ) &&
|
||||
array_key_exists( $this->lang, $this->string_translations[ $string->get_name() ] ) ) {
|
||||
$translation = $this->string_translations[ $string->get_name() ][ $this->lang ];
|
||||
$string->set_value( $translation['value'] );
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
abstract protected function update_strings_in_modules( array &$data_array );
|
||||
abstract protected function update_strings_in_node( $node_id, $settings );
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Update {
|
||||
|
||||
/** @var IWPML_Page_Builders_Data_Settings */
|
||||
protected $data_settings;
|
||||
|
||||
public function __construct( IWPML_Page_Builders_Data_Settings $data_settings ) {
|
||||
$this->data_settings = $data_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_converted_data( $post_id ) {
|
||||
$data = get_post_meta( $post_id, $this->data_settings->get_meta_field(), true );
|
||||
return $this->data_settings->convert_data_to_array( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param int $original_post_id
|
||||
* @param array $converted_data
|
||||
*/
|
||||
public function save( $post_id, $original_post_id, $converted_data ) {
|
||||
$this->save_data( $post_id, $this->data_settings->get_fields_to_save(), $this->data_settings->prepare_data_for_saving( $converted_data ) );
|
||||
$this->copy_meta_fields( $post_id, $original_post_id, $this->data_settings->get_fields_to_copy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param array $fields
|
||||
* @param mixed $data
|
||||
*/
|
||||
private function save_data( $post_id, $fields, $data ) {
|
||||
foreach ( $fields as $field ) {
|
||||
update_post_meta( $post_id, $field, $data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $translated_post_id
|
||||
* @param int $original_post_id
|
||||
* @param array $meta_fields
|
||||
*/
|
||||
private function copy_meta_fields( $translated_post_id, $original_post_id, $meta_fields ) {
|
||||
foreach ( $meta_fields as $meta_key ) {
|
||||
if ( 'post_content' === $meta_key ) {
|
||||
$original_post = get_post( $original_post_id );
|
||||
wpml_update_escaped_post(
|
||||
[
|
||||
'ID' => $translated_post_id,
|
||||
'post_content' => $original_post->post_content,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$value = get_post_meta( $original_post_id, $meta_key, true );
|
||||
update_post_meta(
|
||||
$translated_post_id,
|
||||
$meta_key,
|
||||
apply_filters( 'wpml_pb_copy_meta_field', $value, $translated_post_id, $original_post_id, $meta_key )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Interface IWPML_Page_Builders_Data_Settings
|
||||
*/
|
||||
interface IWPML_Page_Builders_Data_Settings {
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_meta_field();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_node_id_field();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_fields_to_copy();
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_fields_to_save();
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function convert_data_to_array( $data );
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function prepare_data_for_saving( array $data );
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_pb_name();
|
||||
|
||||
public function add_hooks();
|
||||
|
||||
/**
|
||||
* @param int $postId
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_handling_post( $postId );
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class IWPML_Page_Builders_Module
|
||||
*/
|
||||
interface IWPML_Page_Builders_Module {
|
||||
/**
|
||||
* @param string|int $node_id
|
||||
* @param mixed $element
|
||||
* @param WPML_PB_String[] $strings
|
||||
*
|
||||
* @return WPML_PB_String[]
|
||||
*/
|
||||
public function get( $node_id, $element, $strings );
|
||||
|
||||
/**
|
||||
* @param string|int $node_id
|
||||
* @param mixed $element
|
||||
* @param WPML_PB_String $string
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function update( $node_id, $element, WPML_PB_String $string );
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_Page_Builders_Translatable_Nodes {
|
||||
|
||||
/**
|
||||
* @param string|int $node_id
|
||||
* @param array|stdClass $element
|
||||
*
|
||||
* @return WPML_PB_String[]
|
||||
*/
|
||||
public function get( $node_id, $element );
|
||||
|
||||
/**
|
||||
* @param string|int $node_id
|
||||
* @param array|stdClass $element
|
||||
* @param WPML_PB_String $string
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function update( $node_id, $element, WPML_PB_String $string );
|
||||
|
||||
/**
|
||||
* @param string $node_id
|
||||
* @param array $field
|
||||
* @param mixed $settings
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_string_name( $node_id, $field, $settings );
|
||||
|
||||
public function initialize_nodes_to_translate();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_API_Hooks_Strategy implements IWPML_PB_Strategy {
|
||||
|
||||
/** @var WPML_PB_Factory $factory */
|
||||
private $factory;
|
||||
private $name;
|
||||
|
||||
public function __construct( $name ) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post|stdClass $post
|
||||
*/
|
||||
public function register_strings( $post ) {
|
||||
do_action( 'wpml_page_builder_register_strings', $post, $this->get_package_key( $post->ID ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $post_id
|
||||
* @param string $content
|
||||
* @param WPML\PB\Shortcode\StringCleanUp $stringCleanUp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function register_strings_in_content( $post_id, $content, WPML\PB\Shortcode\StringCleanUp $stringCleanUp ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set_factory( $factory ) {
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_package_key( $page_id ) {
|
||||
return array(
|
||||
'kind' => $this->get_package_kind(),
|
||||
'name' => $page_id,
|
||||
'title' => 'Page Builder Page ' . $page_id,
|
||||
'post_id' => $page_id,
|
||||
);
|
||||
}
|
||||
|
||||
public function get_package_kind() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function get_update_post( $package_data) {
|
||||
return $this->factory->get_update_post( $package_data, $this );
|
||||
}
|
||||
|
||||
public function get_content_updater() {
|
||||
return $this->factory->get_api_hooks_content_updater( $this );
|
||||
}
|
||||
|
||||
public function get_package_strings( $package_data ) {
|
||||
return $this->factory->get_string_translations( $this )->get_package_strings( $package_data );
|
||||
}
|
||||
|
||||
public function remove_string( $string_data ) {
|
||||
$this->factory->get_string_translations( $this )->remove_string( $string_data );
|
||||
}
|
||||
|
||||
public function migrate_location( $post_id, $post_content ) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Update_API_Hooks_In_Content {
|
||||
|
||||
/** @var WPML_PB_API_Hooks_Strategy $strategy */
|
||||
private $strategy;
|
||||
|
||||
public function __construct( WPML_PB_API_Hooks_Strategy $strategy ) {
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
|
||||
public function update( $translated_post_id, $original_post, $string_translations, $lang ) {
|
||||
do_action(
|
||||
'wpml_page_builder_string_translated',
|
||||
$this->strategy->get_package_kind(),
|
||||
$translated_post_id,
|
||||
$original_post,
|
||||
$string_translations,
|
||||
$lang
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $original_content
|
||||
* @param array $string_translations
|
||||
* @param string $lang
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function update_content( $original_content, $string_translations, $lang ) {
|
||||
return $original_content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
interface IWPML_PB_Strategy {
|
||||
|
||||
/**
|
||||
* @param \WP_Post|stdClass $post
|
||||
*/
|
||||
public function register_strings( $post );
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param string $content
|
||||
* @param WPML\PB\Shortcode\StringCleanUp $stringCleanUp
|
||||
*
|
||||
* @return bool - true if strings were added.
|
||||
*/
|
||||
public function register_strings_in_content( $post_id, $content, WPML\PB\Shortcode\StringCleanUp $stringCleanUp );
|
||||
|
||||
/**
|
||||
* @param WPML_PB_Factory $factory
|
||||
*
|
||||
*/
|
||||
public function set_factory( $factory );
|
||||
|
||||
public function get_package_key( $page_id );
|
||||
public function get_package_kind();
|
||||
public function get_update_post( $package_data );
|
||||
public function get_content_updater();
|
||||
public function get_package_strings( $package_data );
|
||||
public function remove_string( $string_data );
|
||||
public function migrate_location( $post_id, $post_content );
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace WPML\PB\Shortcode;
|
||||
|
||||
use WPML\FP\Fns;
|
||||
|
||||
class StringCleanUp {
|
||||
|
||||
/* @var array */
|
||||
private $existingStrings;
|
||||
|
||||
/* @var \WPML_PB_Shortcode_Strategy */
|
||||
private $shortcodeStrategy;
|
||||
|
||||
/**
|
||||
* StringCleanUp constructor.
|
||||
*
|
||||
* @param int $postId
|
||||
* @param \WPML_PB_Shortcode_Strategy $shortcodeStrategy
|
||||
*/
|
||||
public function __construct( $postId, \WPML_PB_Shortcode_Strategy $shortcodeStrategy ) {
|
||||
$this->shortcodeStrategy = $shortcodeStrategy;
|
||||
$this->existingStrings = $shortcodeStrategy->get_package_strings( $shortcodeStrategy->get_package_key( $postId ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get() {
|
||||
return $this->existingStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function remove( $value ) {
|
||||
unset( $this->existingStrings[ md5( $value ) ] );
|
||||
}
|
||||
|
||||
public function cleanUp() {
|
||||
Fns::each( [ $this->shortcodeStrategy, 'remove_string' ], $this->existingStrings );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
use WPML\Convert\Ids;
|
||||
use WPML\FP\Obj;
|
||||
use WPML\PB\ConvertIds\Helper as ConvertIdsHelper;
|
||||
|
||||
class WPML_PB_Config_Import_Shortcode {
|
||||
|
||||
const PB_SHORTCODE_SETTING = 'pb_shortcode';
|
||||
const PB_MEDIA_SHORTCODE_SETTING = 'wpml_pb_media_shortcode';
|
||||
const PB_IDS_SHORTCODE_SETTING = 'wpml_pb_ids_shortcode';
|
||||
|
||||
const TYPE_POST_IDS = 'post-ids';
|
||||
const TYPE_TAXONOMY_IDS = 'taxonomy-ids';
|
||||
|
||||
/** @var WPML_ST_Settings $st_settings */
|
||||
private $st_settings;
|
||||
|
||||
public function __construct( WPML_ST_Settings $st_settings ) {
|
||||
$this->st_settings = $st_settings;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_config_array', array( $this, 'wpml_config_filter' ) );
|
||||
}
|
||||
|
||||
public function wpml_config_filter( $config_data ) {
|
||||
$this->update_shortcodes_config( $config_data );
|
||||
$this->update_ids_shortcodes_config( $config_data );
|
||||
$this->update_media_shortcodes_config( $config_data );
|
||||
|
||||
return $config_data;
|
||||
}
|
||||
|
||||
/** @param array $config_data */
|
||||
private function update_shortcodes_config( $config_data ) {
|
||||
$old_shortcode_data = $this->get_settings();
|
||||
|
||||
$shortcode_data = array();
|
||||
if ( isset ( $config_data['wpml-config']['shortcodes']['shortcode'] ) ) {
|
||||
foreach ( $config_data['wpml-config']['shortcodes']['shortcode'] as $data ) {
|
||||
$ignore_content = isset( $data['tag']['attr']['ignore-content'] )
|
||||
&& $data['tag']['attr']['ignore-content'];
|
||||
|
||||
$attributes = array();
|
||||
if ( isset( $data['attributes']['attribute'] ) ) {
|
||||
|
||||
$data['attributes']['attribute'] = $this->convert_single_attribute_to_multiple_format( $data['attributes']['attribute'] );
|
||||
|
||||
foreach ( $data['attributes']['attribute'] as $attribute ) {
|
||||
|
||||
if ( ! $this->is_string_attribute( $attribute ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $attribute['value'] ) ) {
|
||||
$attribute_encoding = isset( $attribute['attr']['encoding'] ) ? $attribute['attr']['encoding'] : '';
|
||||
$attribute_type = isset( $attribute['attr']['type'] ) ? $attribute['attr']['type'] : '';
|
||||
$attribute_label = isset( $attribute['attr']['label'] ) ? $attribute['attr']['label'] : '';
|
||||
$attributes[] = [
|
||||
'value' => $attribute['value'],
|
||||
'encoding' => $attribute_encoding,
|
||||
'type' => $attribute_type,
|
||||
'label' => $attribute_label,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! ( $ignore_content && empty( $attributes ) ) ) {
|
||||
$shortcode_data[] = [
|
||||
'tag' => [
|
||||
'value' => $data['tag']['value'],
|
||||
'encoding' => isset( $data['tag']['attr']['encoding'] ) ? $data['tag']['attr']['encoding'] : '',
|
||||
'encoding-condition' => isset( $data['tag']['attr']['encoding-condition'] ) ? $data['tag']['attr']['encoding-condition'] : '',
|
||||
'type' => isset( $data['tag']['attr']['type'] ) ? $data['tag']['attr']['type'] : '',
|
||||
'raw-html' => isset( $data['tag']['attr']['raw-html'] ) ? $data['tag']['attr']['raw-html'] : '',
|
||||
'ignore-content' => $ignore_content,
|
||||
'label' => isset( $data['tag']['attr']['label'] ) ? $data['tag']['attr']['label'] : '',
|
||||
],
|
||||
'attributes' => $attributes,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $shortcode_data != $old_shortcode_data ) {
|
||||
$this->st_settings->update_setting( self::PB_SHORTCODE_SETTING, $shortcode_data, true );
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array $config_data */
|
||||
private function update_media_shortcodes_config( $config_data ) {
|
||||
$old_shortcodes_data = $this->get_media_settings();
|
||||
$shortcodes_data = array();
|
||||
|
||||
if ( isset ( $config_data['wpml-config']['shortcodes']['shortcode'] ) ) {
|
||||
|
||||
foreach ( $config_data['wpml-config']['shortcodes']['shortcode'] as $data ) {
|
||||
$shortcode_data = array();
|
||||
|
||||
if ( isset( $data['attributes']['attribute'] ) ) {
|
||||
$attributes = array();
|
||||
|
||||
$data['attributes']['attribute'] = $this->convert_single_attribute_to_multiple_format( $data['attributes']['attribute'] );
|
||||
|
||||
foreach ( $data['attributes']['attribute'] as $attribute ) {
|
||||
|
||||
if ( ! $this->is_media_attribute( $attribute ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $attribute['value'] ) ) {
|
||||
$attribute_type = isset( $attribute['attr']['type'] ) ? $attribute['attr']['type'] : '';
|
||||
$attributes[ $attribute['value'] ] = array( 'type' => $attribute_type );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $attributes ) {
|
||||
$shortcode_data['attributes'] = $attributes;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $data['tag']['attr']['type'] )
|
||||
&& $data['tag']['attr']['type'] === WPML_Page_Builders_Media_Shortcodes::TYPE_URL
|
||||
) {
|
||||
$shortcode_data['content'] = array( 'type' => WPML_Page_Builders_Media_Shortcodes::TYPE_URL );
|
||||
}
|
||||
|
||||
if ( $shortcode_data ) {
|
||||
$shortcode_data['tag'] = array( 'name' => $data['tag']['value'] );
|
||||
$shortcodes_data[] = $shortcode_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $shortcodes_data != $old_shortcodes_data ) {
|
||||
update_option( self::PB_MEDIA_SHORTCODE_SETTING, $shortcodes_data, true );
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array $config_data */
|
||||
private function update_ids_shortcodes_config( $config_data ) {
|
||||
$old_shortcodes_data = $this->get_id_settings();
|
||||
$shortcodes_data = [];
|
||||
|
||||
if ( isset ( $config_data['wpml-config']['shortcodes']['shortcode'] ) ) {
|
||||
|
||||
foreach ( $config_data['wpml-config']['shortcodes']['shortcode'] as $data ) {
|
||||
$attributes = [];
|
||||
|
||||
if ( isset( $data['attributes']['attribute'] ) ) {
|
||||
$data['attributes']['attribute'] = $this->convert_single_attribute_to_multiple_format( $data['attributes']['attribute'] );
|
||||
|
||||
foreach ( $data['attributes']['attribute'] as $attribute ) {
|
||||
|
||||
if ( ! $this->is_id_attribute( $attribute ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $attribute['value'] ) ) {
|
||||
$attributes[ $attribute['value'] ] = ConvertIdsHelper::selectElementType(
|
||||
Obj::path( [ 'attr','sub-type' ], $attribute ),
|
||||
Obj::path( [ 'attr','type' ], $attribute )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $attributes ) {
|
||||
$tag_name = $data['tag']['value'];
|
||||
$shortcodes_data[ $tag_name ] = $attributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $shortcodes_data != $old_shortcodes_data ) {
|
||||
update_option( self::PB_IDS_SHORTCODE_SETTING, $shortcodes_data, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $attribute
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_string_attribute( array $attribute ) {
|
||||
return ! $this->is_id_attribute( $attribute )
|
||||
&& ! $this->is_media_attribute( $attribute );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $attribute
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_id_attribute( array $attribute ) {
|
||||
return ConvertIdsHelper::isValidType( Obj::path( [ 'attr', 'type' ], $attribute ) );
|
||||
}
|
||||
|
||||
private function is_media_attribute( array $attribute ) {
|
||||
$media_attribute_types = array(
|
||||
WPML_Page_Builders_Media_Shortcodes::TYPE_URL,
|
||||
WPML_Page_Builders_Media_Shortcodes::TYPE_IDS,
|
||||
);
|
||||
|
||||
return isset( $attribute['attr']['type'] )
|
||||
&& in_array( $attribute['attr']['type'], $media_attribute_types, true );
|
||||
}
|
||||
|
||||
private function convert_single_attribute_to_multiple_format( array $attribute ) {
|
||||
if ( ! is_numeric( key( $attribute ) ) ) {
|
||||
$attribute = array( $attribute );
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
public function get_settings() {
|
||||
return $this->st_settings->get_setting( self::PB_SHORTCODE_SETTING );
|
||||
}
|
||||
|
||||
public function get_media_settings() {
|
||||
return get_option( self::PB_MEDIA_SHORTCODE_SETTING, array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_id_settings() {
|
||||
return get_option( self::PB_IDS_SHORTCODE_SETTING, [] );
|
||||
}
|
||||
|
||||
public function has_settings() {
|
||||
$settings = $this->get_settings();
|
||||
|
||||
return ! empty( $settings );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
use WPML\PB\Shortcode\StringCleanUp;
|
||||
|
||||
/**
|
||||
* Class WPML_PB_Register_Shortcodes
|
||||
*/
|
||||
class WPML_PB_Register_Shortcodes {
|
||||
|
||||
private $handle_strings;
|
||||
/** @var WPML_PB_Shortcode_Strategy $shortcode_strategy */
|
||||
private $shortcode_strategy;
|
||||
/** @var WPML_PB_Shortcode_Encoding $encoding */
|
||||
private $encoding;
|
||||
/** @var WPML_PB_Reuse_Translations_By_Strategy|null $reuse_translations */
|
||||
private $reuse_translations;
|
||||
|
||||
/** @var StringCleanUp|null */
|
||||
private $existingStrings;
|
||||
|
||||
/** @var int $location_index */
|
||||
private $location_index;
|
||||
|
||||
/**
|
||||
* @param WPML_PB_String_Registration $handle_strings
|
||||
* @param WPML_PB_Shortcode_Strategy $shortcode_strategy
|
||||
* @param WPML_PB_Shortcode_Encoding $encoding
|
||||
* @param WPML_PB_Reuse_Translations_By_Strategy|null $reuse_translations
|
||||
*/
|
||||
public function __construct(
|
||||
WPML_PB_String_Registration $handle_strings,
|
||||
WPML_PB_Shortcode_Strategy $shortcode_strategy,
|
||||
WPML_PB_Shortcode_Encoding $encoding,
|
||||
WPML_PB_Reuse_Translations_By_Strategy $reuse_translations = null
|
||||
) {
|
||||
$this->handle_strings = $handle_strings;
|
||||
$this->shortcode_strategy = $shortcode_strategy;
|
||||
$this->encoding = $encoding;
|
||||
$this->reuse_translations = $reuse_translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $post_id
|
||||
* @param string $content
|
||||
* @param StringCleanUp $externalStringCleanUp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function register_shortcode_strings(
|
||||
$post_id,
|
||||
$content,
|
||||
StringCleanUp $externalStringCleanUp = null
|
||||
) {
|
||||
|
||||
$any_registered = false;
|
||||
|
||||
$this->location_index = 1;
|
||||
|
||||
$content = apply_filters( 'wpml_pb_shortcode_content_for_translation', $content, $post_id );
|
||||
$content = WPML_PB_Shortcode_Content_Wrapper::maybeWrap( $content, $this->shortcode_strategy->get_shortcodes() );
|
||||
|
||||
$shortcode_parser = $this->shortcode_strategy->get_shortcode_parser();
|
||||
$shortcodes = $shortcode_parser->get_shortcodes( $content );
|
||||
$this->existingStrings = $externalStringCleanUp ?: new StringCleanUp( $post_id, $this->shortcode_strategy );
|
||||
|
||||
if ( $this->reuse_translations ) {
|
||||
$this->reuse_translations->set_original_strings( $this->existingStrings->get() );
|
||||
}
|
||||
|
||||
foreach ( $shortcodes as $shortcode ) {
|
||||
|
||||
if ( $this->should_handle_content( $shortcode ) ) {
|
||||
$shortcode_content = $shortcode['content'];
|
||||
$encoding = $this->shortcode_strategy->get_shortcode_tag_encoding( $shortcode['tag'] );
|
||||
$encoding_condition = $this->shortcode_strategy->get_shortcode_tag_encoding_condition( $shortcode['tag'] );
|
||||
$type = $this->shortcode_strategy->get_shortcode_tag_type( $shortcode['tag'] );
|
||||
$shortcode_content = $this->encoding->decode( $shortcode_content, $encoding, $encoding_condition );
|
||||
$any_registered = $this->register_string( $post_id, $shortcode_content, $shortcode, 'content', $type ) || $any_registered;
|
||||
}
|
||||
|
||||
$attributes = (array) shortcode_parse_atts( $shortcode['attributes'] );
|
||||
$translatable_attributes = $this->shortcode_strategy->get_shortcode_attributes( $shortcode['tag'] );
|
||||
if ( ! empty( $attributes ) ) {
|
||||
foreach ( $attributes as $attr => $attr_value ) {
|
||||
if ( in_array( $attr, $translatable_attributes, true ) ) {
|
||||
$encoding = $this->shortcode_strategy->get_shortcode_attribute_encoding( $shortcode['tag'], $attr );
|
||||
$type = $this->shortcode_strategy->get_shortcode_attribute_type( $shortcode['tag'], $attr );
|
||||
$attr_value = $this->encoding->decode( $attr_value, $encoding );
|
||||
|
||||
$any_registered = $this->register_string( $post_id, $attr_value, $shortcode, $attr, $type ) || $any_registered;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->reuse_translations ) {
|
||||
$this->reuse_translations->find_and_reuse( $post_id, $this->existingStrings->get() );
|
||||
}
|
||||
|
||||
if( ! $externalStringCleanUp ) {
|
||||
$this->existingStrings->cleanUp();
|
||||
$this->mark_post_as_migrate_location_done( $post_id );
|
||||
}
|
||||
|
||||
return $any_registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $shortcode
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_handle_content( $shortcode ) {
|
||||
$tag = $shortcode['tag'];
|
||||
|
||||
$handle_content = ! (
|
||||
$this->shortcode_strategy->get_shortcode_ignore_content( $tag )
|
||||
|| in_array(
|
||||
$this->shortcode_strategy->get_shortcode_tag_type( $tag ),
|
||||
array(
|
||||
'media-url',
|
||||
'media-ids',
|
||||
),
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Allow page builders to override if the shortcode should be handled as a translatable string.
|
||||
*
|
||||
* @since 4.2
|
||||
* @param bool $handle_content.
|
||||
* @param array $shortcode {
|
||||
*
|
||||
* @type string $tag.
|
||||
* @type string $content.
|
||||
* @type string $attributes.
|
||||
* }
|
||||
*/
|
||||
return apply_filters( 'wpml_pb_should_handle_content', $handle_content, $shortcode );
|
||||
}
|
||||
|
||||
function get_updated_shortcode_string_title( $string_id, $shortcode, $attribute ) {
|
||||
$title = $this->shortcode_strategy->get_shortcode_attribute_label( $shortcode['tag'], $attribute );
|
||||
if ( $title ) {
|
||||
return $title;
|
||||
}
|
||||
|
||||
$current_title = $this->get_shortcode_string_title( $string_id );
|
||||
|
||||
$current_title_parts = explode( ':', $current_title );
|
||||
$current_title_parts = array_map( 'trim', $current_title_parts );
|
||||
|
||||
$shortcode_tag = $shortcode['tag'];
|
||||
if ( isset( $current_title_parts[1] ) ) {
|
||||
$shortcode_attributes = explode( ',', $current_title_parts[1] );
|
||||
$shortcode_attributes = array_map( 'trim', $shortcode_attributes );
|
||||
}
|
||||
$shortcode_attributes[] = $attribute;
|
||||
sort( $shortcode_attributes );
|
||||
$shortcode_attributes = array_unique( $shortcode_attributes );
|
||||
|
||||
return $shortcode_tag . ': ' . implode( ', ', $shortcode_attributes );
|
||||
}
|
||||
|
||||
function get_shortcode_string_title( $string_id ) {
|
||||
return $this->handle_strings->get_string_title( $string_id );
|
||||
}
|
||||
|
||||
public function register_string( $post_id, $content, $shortcode, $attribute, $editor_type ) {
|
||||
$string_id = 0;
|
||||
|
||||
if ( is_array( $content ) ) {
|
||||
foreach ( $content as $key => $data ) {
|
||||
if ( $data['translate'] ) {
|
||||
$this->register_string( $post_id, $data['value'], $shortcode, $attribute . ' ' . $key, $editor_type );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( $this->existingStrings ) {
|
||||
$this->existingStrings->remove( $content );
|
||||
}
|
||||
try {
|
||||
$string_id = $this->handle_strings->get_string_id_from_package( $post_id, $content );
|
||||
$string_title = $this->get_updated_shortcode_string_title( $string_id, $shortcode, $attribute );
|
||||
$string_id = $this->handle_strings->register_string( $post_id, $content, $editor_type, $string_title, '', $this->location_index );
|
||||
if ( $string_id ) {
|
||||
$this->location_index ++;
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
$string_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $string_id !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
*/
|
||||
private function mark_post_as_migrate_location_done( $post_id ) {
|
||||
update_post_meta( $post_id, WPML_PB_Integration::MIGRATION_DONE_POST_META, true );
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<?php
|
||||
|
||||
use WPML\FP\Logic;
|
||||
use WPML\FP\Maybe;
|
||||
use WPML\FP\Str;
|
||||
use WPML\LIB\WP\Gutenberg;
|
||||
use function WPML\FP\pipe;
|
||||
|
||||
|
||||
class WPML_PB_Shortcode_Content_Wrapper {
|
||||
|
||||
const WRAPPER_SHORTCODE_NAME = 'wpml_string_wrapper';
|
||||
|
||||
/** @var string $content */
|
||||
private $content;
|
||||
|
||||
/** @var array $valid_shortcodes */
|
||||
private $valid_shortcodes;
|
||||
|
||||
/** @var array $shortcodes */
|
||||
private $shortcodes = array();
|
||||
|
||||
/** @var array $content_array */
|
||||
private $content_array;
|
||||
|
||||
/** @var array $insert_wrapper */
|
||||
private $insert_wrapper = array();
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param array $valid_shortcodes
|
||||
*/
|
||||
public function __construct( $content, array $valid_shortcodes ) {
|
||||
$this->content = $content;
|
||||
$this->valid_shortcodes = $valid_shortcodes;
|
||||
}
|
||||
|
||||
public function get_wrapped_content() {
|
||||
$this->split_content();
|
||||
$this->parse_shortcodes();
|
||||
$this->analyze_unwrapped_text();
|
||||
$this->insert_wrappers();
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a multibyte safe version of `str_split`
|
||||
*/
|
||||
private function split_content() {
|
||||
$length = mb_strlen( $this->content );
|
||||
|
||||
for ( $i = 0; $i < $length; $i++ ) {
|
||||
$this->content_array[] = mb_substr( $this->content, $i, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
private function parse_shortcodes() {
|
||||
$close_bracket_position = false;
|
||||
$content_length = count( $this->content_array );
|
||||
|
||||
for ( $i = 0; $i < $content_length; $i++ ) {
|
||||
if ( false !== $close_bracket_position && $close_bracket_position >= $i ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( '[' === $this->content_array[ $i ] ) {
|
||||
$close_bracket_position = $this->parse_shortcode( $i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $open_bracket_position
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function parse_shortcode( $open_bracket_position ) {
|
||||
$shortcode_name = $this->get_shortcode_name( $open_bracket_position );
|
||||
$close_bracket_position = $this->get_shortcode_end( $open_bracket_position, $shortcode_name );
|
||||
$is_closing = isset( $this->content_array[ $open_bracket_position + 1 ] )
|
||||
&& '/' === $this->content_array[ $open_bracket_position + 1 ];
|
||||
|
||||
if ( ! in_array( $shortcode_name, $this->valid_shortcodes, true ) ) {
|
||||
return $close_bracket_position;
|
||||
}
|
||||
|
||||
if ( $is_closing ) {
|
||||
$shortcode_index = $this->find_last_opened_shortcode( $shortcode_name );
|
||||
|
||||
if ( null !== $shortcode_index ) {
|
||||
$this->shortcodes[ $shortcode_index ]['end'] = $close_bracket_position;
|
||||
$this->remove_nested_shortcodes_between(
|
||||
$this->shortcodes[ $shortcode_index ]['start'],
|
||||
$close_bracket_position
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->shortcodes[] = array(
|
||||
'name' => $shortcode_name,
|
||||
'start' => $open_bracket_position,
|
||||
'end' => $close_bracket_position,
|
||||
);
|
||||
}
|
||||
|
||||
return $close_bracket_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
*/
|
||||
private function remove_nested_shortcodes_between( $start, $end ) {
|
||||
foreach ( $this->shortcodes as $key => $shortcode ) {
|
||||
|
||||
if ( $start < $shortcode['start'] && $end > $shortcode['end'] ) {
|
||||
unset( $this->shortcodes[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function analyze_unwrapped_text() {
|
||||
$next_unwrapped_text_start = 0;
|
||||
|
||||
foreach ( $this->shortcodes as $shortcode ) {
|
||||
$unwrapped_text_start = $next_unwrapped_text_start;
|
||||
$unwrapped_text_end = $shortcode['start'] - 1;
|
||||
$next_unwrapped_text_start = $shortcode['end'] + 1;
|
||||
|
||||
if ( $unwrapped_text_start < $unwrapped_text_end ) {
|
||||
$this->set_wrapper_positions( $unwrapped_text_start, $unwrapped_text_end );
|
||||
}
|
||||
}
|
||||
|
||||
$max_content_char_position = mb_strlen( $this->content ) - 1;
|
||||
|
||||
// For unwrapped text closing the content.
|
||||
if ( $next_unwrapped_text_start < $max_content_char_position ) {
|
||||
$this->set_wrapper_positions( $next_unwrapped_text_start, $max_content_char_position );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $start
|
||||
* @param int $end
|
||||
*/
|
||||
private function set_wrapper_positions( $start, $end ) {
|
||||
$raw_chunk = mb_substr( $this->content, $start, $end - $start );
|
||||
|
||||
if ( '' === trim( $raw_chunk ) ) {
|
||||
// the chunk is an empty string, we don't need to wrap it.
|
||||
return;
|
||||
}
|
||||
|
||||
$chunk_start = $this->get_wrapper_insert_position( $start, 'open' );
|
||||
$unwrapped_text_end = $this->get_wrapper_insert_position( $end, 'close' );
|
||||
|
||||
$this->insert_wrapper[ $chunk_start ] = '[' . self::WRAPPER_SHORTCODE_NAME . ']';
|
||||
$this->insert_wrapper[ $unwrapped_text_end ] = '[/' . self::WRAPPER_SHORTCODE_NAME . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
* @param string $type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_wrapper_insert_position( $position, $type ) {
|
||||
if ( 'close' === $type ) {
|
||||
$increment = - 1;
|
||||
} else {
|
||||
$increment = 1;
|
||||
}
|
||||
|
||||
while ( isset( $this->content_array[ $position ] )
|
||||
&& in_array( $this->content_array[ $position ], array( "\n", "\r" ), true )
|
||||
) {
|
||||
$position = $position + $increment;
|
||||
}
|
||||
|
||||
if ( 'close' === $type ) {
|
||||
$position++;
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $open_bracket_position
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_shortcode_name( $open_bracket_position ) {
|
||||
$char_position = $open_bracket_position + 1;
|
||||
$name = '';
|
||||
|
||||
while ( isset( $this->content_array[ $char_position ] )
|
||||
&& ( '' === $name || ! in_array( $this->content_array[ $char_position ], array( ' ', ']' ), true ) )
|
||||
) {
|
||||
if ( '/' !== $this->content_array[ $char_position ] ) {
|
||||
$name .= $this->content_array[ $char_position ];
|
||||
}
|
||||
|
||||
$char_position++;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $open_bracket_position
|
||||
* @param string $shortcode_name
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function get_shortcode_end( $open_bracket_position, $shortcode_name ) {
|
||||
$char_position = $open_bracket_position + mb_strlen( $shortcode_name );
|
||||
$is_in_quotes = false;
|
||||
|
||||
while ( isset( $this->content_array[ $char_position ] )
|
||||
&& ( ']' !== $this->content_array[ $char_position ] || $is_in_quotes )
|
||||
) {
|
||||
if ( in_array( $this->content_array[ $char_position ], array( '"', "'" ), true ) ) {
|
||||
$is_in_quotes = ! $is_in_quotes;
|
||||
}
|
||||
|
||||
$char_position++;
|
||||
}
|
||||
|
||||
return $char_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $shortcode_name
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
private function find_last_opened_shortcode( $shortcode_name ) {
|
||||
$last_matching_index = null;
|
||||
|
||||
foreach ( $this->shortcodes as $shortcode_index => $shortcode ) {
|
||||
if ( $shortcode['name'] === $shortcode_name ) {
|
||||
$last_matching_index = (int) $shortcode_index;
|
||||
}
|
||||
}
|
||||
|
||||
return $last_matching_index;
|
||||
}
|
||||
|
||||
private function insert_wrappers() {
|
||||
$offset = 0;
|
||||
|
||||
foreach ( $this->insert_wrapper as $wrapper_position => $wrapper ) {
|
||||
$insert_position = $wrapper_position + $offset;
|
||||
$before = mb_substr( $this->content, 0, $insert_position );
|
||||
$after = mb_substr( $this->content, $insert_position );
|
||||
$this->content = $before . $wrapper . $after;
|
||||
$offset = $offset + mb_strlen( $wrapper );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param array $shortcodes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function maybeWrap( $content, array $shortcodes ) {
|
||||
$containsOneShortcode = pipe( Str::match( '/' . get_shortcode_regex( $shortcodes ) . '/s' ), Logic::isEmpty(), Logic::not() );
|
||||
|
||||
return Maybe::of( $content )
|
||||
->filter( Gutenberg::doesNotHaveBlock() )
|
||||
->filter( $containsOneShortcode )
|
||||
->filter( [ self::class, 'isStrippedContentDifferent' ] )
|
||||
->map( [ self::class, 'wrap' ] )
|
||||
->getOrElse( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* This will flag some regular text not wrapped in a shortcode.
|
||||
* e.g. "[foo] Some text not wrapped [bar]"
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isStrippedContentDifferent( $content ) {
|
||||
$content_with_stripped_shortcode = preg_replace( '/\[([\S]*)[^\]]*\][\s\S]*\[\/(\1)\]|\[[^\]]*\]/', '', $content );
|
||||
$content_with_stripped_shortcode = trim( $content_with_stripped_shortcode );
|
||||
return ! empty( $content_with_stripped_shortcode ) && trim( $content ) !== $content_with_stripped_shortcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wrap( $content ) {
|
||||
return '[' . self::WRAPPER_SHORTCODE_NAME . ']' . $content . '[/' . self::WRAPPER_SHORTCODE_NAME . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function unwrap( $content ) {
|
||||
return str_replace(
|
||||
[
|
||||
'[' . self::WRAPPER_SHORTCODE_NAME . ']',
|
||||
'[/' . self::WRAPPER_SHORTCODE_NAME . ']'
|
||||
],
|
||||
'',
|
||||
$content
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class WPML_PB_Register_Shortcodes
|
||||
*/
|
||||
class WPML_PB_Shortcode_Encoding {
|
||||
const ENCODE_TYPES_BASE64 = 'base64';
|
||||
const ENCODE_TYPES_VISUAL_COMPOSER_LINK = 'vc_link';
|
||||
const ENCODE_TYPES_ENFOLD_LINK = 'av_link';
|
||||
|
||||
public function decode( $string, $encoding, $encoding_condition = '' ) {
|
||||
$encoded_string = $string;
|
||||
|
||||
if ( $encoding_condition && ! $this->should_decode( $encoding_condition ) ) {
|
||||
return html_entity_decode( $string );
|
||||
}
|
||||
|
||||
switch ( $encoding ) {
|
||||
case self::ENCODE_TYPES_BASE64:
|
||||
$string = html_entity_decode( rawurldecode( base64_decode( strip_tags( $string ) ) ) );
|
||||
break;
|
||||
|
||||
case self::ENCODE_TYPES_VISUAL_COMPOSER_LINK:
|
||||
$parts = explode( '|', $string );
|
||||
$string = array();
|
||||
foreach ( $parts as $part ) {
|
||||
$data = explode( ':', $part );
|
||||
if ( count( $data ) === 2 ) {
|
||||
if ( in_array( $data[0], array( 'url', 'title' ), true ) ) {
|
||||
$string[ $data[0] ] = array( 'value' => urldecode( $data[1] ), 'translate' => true );
|
||||
} else {
|
||||
$string[ $data[0] ] = array( 'value' => urldecode( $data[1] ), 'translate' => false );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case self::ENCODE_TYPES_ENFOLD_LINK:
|
||||
// Note: We can't handle 'lightbox' mode because we don't know how to re-encode it
|
||||
$link = explode( ',', $string, 2 );
|
||||
if ( 'manually' === $link[0] ) {
|
||||
$string = $link[1];
|
||||
} elseif ( post_type_exists( $link[0] ) ) {
|
||||
$string = get_permalink( $link[1] );
|
||||
} elseif ( taxonomy_exists( $link[0] ) ) {
|
||||
$term_link = get_term_link( get_term( $link[1], $link[0] ) );
|
||||
if ( ! is_wp_error( $term_link ) ) {
|
||||
$string = $term_link;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return apply_filters( 'wpml_pb_shortcode_decode', $string, $encoding, $encoded_string );
|
||||
}
|
||||
|
||||
public function encode( $string, $encoding ) {
|
||||
$decoded_string = $string;
|
||||
switch ( $encoding ) {
|
||||
case self::ENCODE_TYPES_BASE64:
|
||||
$string = base64_encode( $string );
|
||||
break;
|
||||
|
||||
case self::ENCODE_TYPES_VISUAL_COMPOSER_LINK:
|
||||
$output = '';
|
||||
if ( is_array( $string ) ) {
|
||||
foreach ( $string as $key => $value ) {
|
||||
$output .= $key . ':' . rawurlencode( $value ) . '|';
|
||||
}
|
||||
}
|
||||
$string = $output;
|
||||
break;
|
||||
|
||||
case self::ENCODE_TYPES_ENFOLD_LINK:
|
||||
$link = explode( ',', $string, 2 );
|
||||
if ( $link[0] !== 'lightbox' ) {
|
||||
$string = 'manually,' . $string;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return apply_filters( 'wpml_pb_shortcode_encode', $string, $encoding, $decoded_string );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $condition
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_decode( $condition ) {
|
||||
preg_match( '/(?P<type>\w+):(?P<field>\w+)=(?P<value>\w+)/', $condition, $matches );
|
||||
|
||||
return 'option' === $matches['type'] && get_option( $matches['field'] ) === $matches['value'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
use \WPML\LIB\WP\Gutenberg;
|
||||
use WPML\FP\Obj;
|
||||
|
||||
class WPML_PB_Shortcode_Strategy implements IWPML_PB_Strategy {
|
||||
|
||||
private $shortcodes = array(
|
||||
WPML_PB_Shortcode_Content_Wrapper::WRAPPER_SHORTCODE_NAME => array(
|
||||
'encoding' => '',
|
||||
'encoding-condition' => '',
|
||||
'type' => '',
|
||||
'ignore-content' => false,
|
||||
'attributes' => array(),
|
||||
),
|
||||
);
|
||||
/** @var WPML_PB_Factory $factory */
|
||||
private $factory;
|
||||
|
||||
/** @var WPML_Page_Builder_Settings $page_builder_settings */
|
||||
private $page_builder_settings;
|
||||
|
||||
public function __construct( WPML_Page_Builder_Settings $page_builder_settings ) {
|
||||
$this->page_builder_settings = $page_builder_settings;
|
||||
}
|
||||
|
||||
public function add_shortcodes( $shortcode_data ) {
|
||||
|
||||
foreach ( $shortcode_data as $shortcode ) {
|
||||
$tag = $shortcode['tag']['value'];
|
||||
$is_raw_html = isset( $shortcode['tag']['raw-html'] ) && $shortcode['tag']['raw-html'];
|
||||
|
||||
if ( $is_raw_html && ! $this->page_builder_settings->is_raw_html_translatable() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! in_array( $tag, $this->shortcodes ) ) {
|
||||
$this->shortcodes[ $tag ] = [
|
||||
'encoding' => $shortcode['tag']['encoding'],
|
||||
'encoding-condition' => isset( $shortcode['tag']['encoding-condition'] ) ? $shortcode['tag']['encoding-condition'] : '',
|
||||
'type' => isset( $shortcode['tag']['type'] ) ? $shortcode['tag']['type'] : '',
|
||||
'ignore-content' => isset( $shortcode['tag']['ignore-content'] ) ? (bool) $shortcode['tag']['ignore-content'] : false,
|
||||
'label' => isset( $shortcode['tag']['label'] ) ? $shortcode['tag']['label'] : '',
|
||||
'attributes' => [],
|
||||
];
|
||||
}
|
||||
if ( isset( $shortcode['attributes'] ) ) {
|
||||
foreach ( $shortcode['attributes'] as $attribute ) {
|
||||
$this->shortcodes[ $tag ]['attributes'][ $attribute['value'] ] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_shortcodes() {
|
||||
return array_keys( $this->shortcodes );
|
||||
}
|
||||
|
||||
public function get_shortcode_attributes( $tag ) {
|
||||
return array_keys( $this->shortcodes[ $tag ]['attributes'] );
|
||||
}
|
||||
|
||||
public function get_shortcode_tag_encoding( $tag ) {
|
||||
return $this->shortcodes[ $tag ]['encoding'];
|
||||
}
|
||||
|
||||
public function get_shortcode_tag_encoding_condition( $tag ) {
|
||||
return $this->shortcodes[ $tag ]['encoding-condition'];
|
||||
}
|
||||
|
||||
public function get_shortcode_tag_type( $tag ) {
|
||||
if ( $this->shortcodes[ $tag ]['type'] ) {
|
||||
return strtoupper( $this->shortcodes[ $tag ]['type'] );
|
||||
}
|
||||
return 'VISUAL';
|
||||
}
|
||||
|
||||
public function get_shortcode_ignore_content( $tag ) {
|
||||
return $this->shortcodes[ $tag ]['ignore-content'];
|
||||
}
|
||||
|
||||
public function get_shortcode_attribute_encoding( $tag, $attribute ) {
|
||||
return $this->shortcodes[ $tag ]['attributes'][ $attribute ]['encoding'];
|
||||
}
|
||||
|
||||
public function get_shortcode_attribute_type( $tag, $attribute ) {
|
||||
if ( $this->shortcodes[ $tag ]['attributes'][ $attribute ]['type'] ) {
|
||||
return strtoupper( $this->shortcodes[ $tag ]['attributes'][ $attribute ]['type'] );
|
||||
}
|
||||
return 'LINE';
|
||||
}
|
||||
|
||||
public function get_shortcode_attribute_label( $tag, $attribute ) {
|
||||
$labelPath = 'content' === $attribute ? [ $tag, 'label' ] : [ $tag, 'attributes', $attribute, 'label' ];
|
||||
|
||||
return Obj::pathOr( '', $labelPath, $this->shortcodes );
|
||||
}
|
||||
|
||||
public function get_shortcode_parser() {
|
||||
return $this->factory->get_shortcode_parser( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Post|stdClass $post
|
||||
*/
|
||||
public function register_strings( $post ) {
|
||||
if ( Gutenberg::doesNotHaveBlock( $post->post_content ) ) {
|
||||
$this->register_strings_in_content( $post->ID, $post->post_content, null );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int $post_id
|
||||
* @param string $content
|
||||
* @param WPML\PB\Shortcode\StringCleanUp $stringCleanUp
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function register_strings_in_content( $post_id, $content, WPML\PB\Shortcode\StringCleanUp $stringCleanUp = null ) {
|
||||
$register_shortcodes = $this->factory->get_register_shortcodes( $this );
|
||||
|
||||
return $register_shortcodes->register_shortcode_strings( $post_id, $content, $stringCleanUp );
|
||||
}
|
||||
|
||||
public function set_factory( $factory ) {
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
public function get_package_key( $page_id ) {
|
||||
return array(
|
||||
'kind' => $this->get_package_kind(),
|
||||
'name' => $page_id,
|
||||
'title' => 'Page Builder Page ' . $page_id,
|
||||
'post_id' => $page_id,
|
||||
);
|
||||
}
|
||||
|
||||
public function get_package_kind() {
|
||||
return 'Page Builder ShortCode Strings';
|
||||
}
|
||||
|
||||
public function get_update_post( $package_data ) {
|
||||
return $this->factory->get_update_post( $package_data, $this );
|
||||
}
|
||||
|
||||
public function get_content_updater() {
|
||||
return $this->factory->get_shortcode_content_updater( $this );
|
||||
}
|
||||
|
||||
public function get_package_strings( $package_data ) {
|
||||
return $this->factory->get_string_translations( $this )->get_package_strings( $package_data );
|
||||
}
|
||||
|
||||
public function remove_string( $string_data ) {
|
||||
return $this->factory->get_string_translations( $this )->remove_string( $string_data );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $post_id
|
||||
* @param object $post_content
|
||||
*/
|
||||
public function migrate_location( $post_id, $post_content ) {
|
||||
$migrate_locations = $this->factory->get_register_shortcodes( $this, true );
|
||||
$migrate_locations->register_shortcode_strings( $post_id, $post_content, null );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Shortcodes {
|
||||
|
||||
/** @var WPML_PB_Shortcode_Strategy $shortcode_strategy */
|
||||
private $shortcode_strategy;
|
||||
|
||||
/** @var bool $is_wrapping_regular_text */
|
||||
private $is_wrapping_regular_text = false;
|
||||
|
||||
public function __construct( WPML_PB_Shortcode_Strategy $shortcode_strategy ) {
|
||||
$this->shortcode_strategy = $shortcode_strategy;
|
||||
}
|
||||
|
||||
public function get_shortcodes( $content ) {
|
||||
|
||||
$shortcodes = array();
|
||||
$pattern = get_shortcode_regex( $this->shortcode_strategy->get_shortcodes() );
|
||||
|
||||
if ( preg_match_all( '/' . $pattern . '/s', $content, $matches ) && isset( $matches[5] ) && ! empty( $matches[5] ) ) {
|
||||
for ( $index = 0; $index < sizeof( $matches[0] ); $index ++ ) {
|
||||
$shortcode = array(
|
||||
'block' => $matches[0][ $index ],
|
||||
'tag' => $matches[2][ $index ],
|
||||
'attributes' => $matches[3][ $index ],
|
||||
'content' => $matches[5][ $index ],
|
||||
);
|
||||
|
||||
$nested_shortcodes = array();
|
||||
if ( $shortcode['content'] ) {
|
||||
if ( $this->needs_wrapping_regular_text( $shortcode['content'] ) ) {
|
||||
$this->is_wrapping_regular_text = true;
|
||||
$shortcode['content'] = $this->wrap_regular_text( $shortcode['content'] );
|
||||
}
|
||||
|
||||
$nested_shortcodes = $this->get_shortcodes( $shortcode['content'] );
|
||||
$this->is_wrapping_regular_text = false;
|
||||
if ( count( $nested_shortcodes ) ) {
|
||||
$shortcode['content'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $nested_shortcodes ) ) {
|
||||
$shortcodes = array_merge( $shortcodes, $nested_shortcodes );
|
||||
}
|
||||
$shortcodes[] = $shortcode;
|
||||
}
|
||||
}
|
||||
|
||||
return $shortcodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function wrap_regular_text( $content ) {
|
||||
$wrapper = new WPML_PB_Shortcode_Content_Wrapper( $content, $this->shortcode_strategy->get_shortcodes() );
|
||||
return $wrapper->get_wrapped_content();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function needs_wrapping_regular_text( $content ) {
|
||||
if ( $this->is_wrapping_regular_text ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WPML_PB_Shortcode_Content_Wrapper::isStrippedContentDifferent( $content );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
use WPML\LIB\WP\Gutenberg;
|
||||
|
||||
class WPML_PB_Update_Shortcodes_In_Content {
|
||||
|
||||
const LONG_STRING_THRESHOLD = 5000;
|
||||
|
||||
/** @var WPML_PB_Shortcode_Strategy $strategy */
|
||||
private $strategy;
|
||||
/** @var WPML_PB_Shortcode_Encoding $encoding */
|
||||
private $encoding;
|
||||
|
||||
private $new_content;
|
||||
private $string_translations;
|
||||
private $lang;
|
||||
|
||||
public function __construct( WPML_PB_Shortcode_Strategy $strategy, WPML_PB_Shortcode_Encoding $encoding ) {
|
||||
$this->strategy = $strategy;
|
||||
$this->encoding = $encoding;
|
||||
}
|
||||
|
||||
public function update( $translated_post_id, $original_post, $string_translations, $lang ) {
|
||||
if ( Gutenberg::hasBlock( $original_post->post_content ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$original_content = $original_post->post_content;
|
||||
$original_content = apply_filters( 'wpml_pb_shortcode_content_for_translation', $original_content, $original_post->ID );
|
||||
|
||||
$new_translation = $this->update_content( $original_content, $string_translations, $lang );
|
||||
|
||||
$translated_post = get_post( $translated_post_id );
|
||||
$current_translation = isset( $translated_post->post_content ) ? $translated_post->post_content : '';
|
||||
$current_translation = apply_filters( 'wpml_pb_shortcode_content_for_translation', $current_translation, $translated_post_id );
|
||||
|
||||
$translation_saved = apply_filters( 'wpml_pb_shortcodes_save_translation', false, $translated_post_id, $new_translation );
|
||||
|
||||
if ( ! $translation_saved ) {
|
||||
if ( $new_translation != $original_content || '' === $current_translation ) {
|
||||
wpml_update_escaped_post(
|
||||
[
|
||||
'ID' => $translated_post_id,
|
||||
'post_content' => $new_translation,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update_content( $original_content, $string_translations, $lang ) {
|
||||
$original_content = WPML_PB_Shortcode_Content_Wrapper::maybeWrap( $original_content, $this->strategy->get_shortcodes() );
|
||||
$this->new_content = $original_content;
|
||||
$this->string_translations = $string_translations;
|
||||
$this->lang = $lang;
|
||||
|
||||
$shortcode_parser = $this->strategy->get_shortcode_parser();
|
||||
$shortcodes = $shortcode_parser->get_shortcodes( $original_content );
|
||||
|
||||
foreach ( $shortcodes as $shortcode ) {
|
||||
$this->update_shortcodes( $shortcode );
|
||||
$this->update_shortcode_attributes( $shortcode );
|
||||
}
|
||||
|
||||
return WPML_PB_Shortcode_Content_Wrapper::unwrap( $this->new_content );
|
||||
}
|
||||
|
||||
private function update_shortcodes( $shortcode_data ) {
|
||||
$encoding = $this->strategy->get_shortcode_tag_encoding( $shortcode_data['tag'] );
|
||||
$translation = $this->get_translation( $shortcode_data['content'], $encoding );
|
||||
$this->replace_string_with_translation( $shortcode_data['block'], $shortcode_data['content'], $translation );
|
||||
}
|
||||
|
||||
private function update_shortcode_attributes( $shortcode_data ) {
|
||||
|
||||
$shortcode_attribute = $shortcode_data['attributes'];
|
||||
|
||||
$attributes = (array) shortcode_parse_atts( $shortcode_attribute );
|
||||
$translatable_attributes = $this->strategy->get_shortcode_attributes( $shortcode_data['tag'] );
|
||||
if ( ! empty( $attributes ) ) {
|
||||
foreach ( $attributes as $attr => $attr_value ) {
|
||||
if ( in_array( $attr, $translatable_attributes, true ) ) {
|
||||
$encoding = $this->strategy->get_shortcode_attribute_encoding( $shortcode_data['tag'], $attr );
|
||||
$translation = $this->get_translation( $attr_value, $encoding );
|
||||
$translation = $this->filter_attribute_translation( $translation, $encoding );
|
||||
$shortcode_attribute = $this->replace_string_with_translation( $shortcode_attribute, $attr_value, $translation, true, $attr );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function replace_string_with_translation( $block, $original, $translation, $is_attribute = false, $attr = '' ) {
|
||||
$translation = apply_filters( 'wpml_pb_before_replace_string_with_translation', $translation, $is_attribute );
|
||||
$used_wrapper = false;
|
||||
$new_block = $block;
|
||||
|
||||
if ( $translation ) {
|
||||
|
||||
if ( $this->is_string_too_long_for_regex( $original ) ) {
|
||||
$block = WPML_PB_Shortcode_Content_Wrapper::unwrap( $block );
|
||||
$new_block = str_replace( $original, $translation, $block );
|
||||
$this->new_content = str_replace( $block, $new_block, $this->new_content );
|
||||
} else {
|
||||
if ( $is_attribute && $attr ) {
|
||||
$pattern = '/' . $attr . '=(["\'])' . preg_quote( $original, '/' ) . '(["\'])/';
|
||||
$replacement = $attr . '=${1}' . $this->escape_backward_reference_on_replacement_string( $translation ) . '${2}';
|
||||
} else {
|
||||
$used_wrapper = false !== strpos( $new_block, '[' . WPML_PB_Shortcode_Content_Wrapper::WRAPPER_SHORTCODE_NAME . ']' );
|
||||
$pattern = '/(]\s*)' . preg_quote( trim( $original ), '/' ) . '(\s*\[)/';
|
||||
$replacement = '${1}' . $this->escape_backward_reference_on_replacement_string( trim( $translation ) ) . '${2}';
|
||||
}
|
||||
|
||||
$new_block = preg_replace( $pattern, $replacement, $block );
|
||||
$block = WPML_PB_Shortcode_Content_Wrapper::unwrap( $block );
|
||||
$new_block = WPML_PB_Shortcode_Content_Wrapper::unwrap( $new_block );
|
||||
$replacement = $this->escape_backward_reference_on_replacement_string( $new_block );
|
||||
|
||||
if ( $used_wrapper ) {
|
||||
$this->new_content = $this->replace_content_without_delimiters( $block, $replacement );
|
||||
} else {
|
||||
$this->new_content = preg_replace( '/' . preg_quote( $block, '/' ) . '/', $replacement, $this->new_content, 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $new_block;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to escape backward references that could be included in the replacement text
|
||||
* e.g. '$1999.each' => '$19' is considered as a backward reference
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function escape_backward_reference_on_replacement_string( $string ) {
|
||||
return preg_replace( '/\$([\d]{1,2})/', '\\\$' . '${1}', $string );
|
||||
}
|
||||
|
||||
private function replace_content_without_delimiters( $block, $replacement ) {
|
||||
return preg_replace( '/(]\s*)' . preg_quote( $block, '/' ) . '(\s*\[)/', '${1}' . $replacement . '${2}', $this->new_content, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_string_too_long_for_regex( $string ) {
|
||||
return mb_strlen( $string ) > self::LONG_STRING_THRESHOLD;
|
||||
}
|
||||
|
||||
private function get_translation( $original, $encoding = false ) {
|
||||
$decoded_original = $this->encoding->decode( $original, $encoding );
|
||||
|
||||
$translation = null;
|
||||
|
||||
if ( is_array( $decoded_original ) ) {
|
||||
|
||||
$translation = array();
|
||||
foreach ( $decoded_original as $key => $data ) {
|
||||
if ( $data['translate'] ) {
|
||||
$translated_data = $this->get_translation( $data['value'], '' );
|
||||
if ( $translated_data ) {
|
||||
$translation[ $key ] = $translated_data;
|
||||
} else {
|
||||
$translation[ $key ] = $data['value'];
|
||||
}
|
||||
} else {
|
||||
$translation[ $key ] = $data['value'];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$string_name = md5( $decoded_original );
|
||||
if ( isset( $this->string_translations[ $string_name ][ $this->lang ] ) && $this->string_translations[ $string_name ][ $this->lang ]['status'] == ICL_TM_COMPLETE ) {
|
||||
$translation = $this->string_translations[ $string_name ][ $this->lang ]['value'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $translation ) {
|
||||
$translation = $this->encoding->encode( $translation, $encoding );
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $translation
|
||||
* @param string $encoding
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function filter_attribute_translation( $translation, $encoding ) {
|
||||
if ( 'allow_html_tags' !== $encoding ) {
|
||||
$translation = htmlspecialchars( htmlspecialchars_decode( $translation ) );
|
||||
}
|
||||
|
||||
$translation = str_replace( array( '[', ']' ), array( '[', ']' ), $translation );
|
||||
|
||||
return $translation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Handle_Custom_Fields {
|
||||
|
||||
protected $data_settings;
|
||||
|
||||
public function __construct( IWPML_Page_Builders_Data_Settings $data_settings ) {
|
||||
$this->data_settings = $data_settings;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_pb_is_page_builder_page', array( $this, 'is_page_builder_page_filter' ), 10, 2 );
|
||||
add_action( 'wpml_pb_after_page_without_elements_post_content_copy', array(
|
||||
$this,
|
||||
'copy_custom_fields'
|
||||
), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $is_page_builder_page
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_page_builder_page_filter( $is_page_builder_page, WP_Post $post ) {
|
||||
if ( $this->data_settings->is_handling_post( $post->ID ) ) {
|
||||
$is_page_builder_page = true;
|
||||
}
|
||||
|
||||
return $is_page_builder_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_post_id
|
||||
* @param int $original_post_id
|
||||
*/
|
||||
public function copy_custom_fields( $new_post_id, $original_post_id ) {
|
||||
$fields = array_merge( $this->data_settings->get_fields_to_copy(), $this->data_settings->get_fields_to_save() );
|
||||
|
||||
foreach ( $fields as $field ) {
|
||||
self::copy_field( $new_post_id, $original_post_id, $field );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_post_id
|
||||
* @param int $original_post_id
|
||||
* @param string $field
|
||||
*/
|
||||
public static function copy_field( $new_post_id, $original_post_id, $field ) {
|
||||
$original_field = get_post_meta( $original_post_id, $field, true );
|
||||
if ( $original_field ) {
|
||||
update_post_meta( $new_post_id, $field, self::slash_json( $original_field ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return mixed string
|
||||
*/
|
||||
public static function slash_json( $data ) {
|
||||
if ( ! is_string( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
json_decode( $data );
|
||||
if ( json_last_error() === JSON_ERROR_NONE ) {
|
||||
return wp_slash( $data );
|
||||
}
|
||||
return $data;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
class WPML_PB_Handle_Post_Body implements IWPML_Backend_Action, IWPML_Frontend_Action, IWPML_DIC_Action {
|
||||
|
||||
private $page_builders_built;
|
||||
|
||||
public function __construct( WPML_Page_Builders_Page_Built $page_builders_built ) {
|
||||
$this->page_builders_built = $page_builders_built;
|
||||
}
|
||||
|
||||
public function add_hooks() {
|
||||
add_filter( 'wpml_pb_should_body_be_translated', array( $this, 'should_translate' ), 10, 3 );
|
||||
add_action( 'wpml_pb_finished_adding_string_translations', array( $this, 'copy' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $translate
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function should_translate( $translate, WP_Post $post ) {
|
||||
return $this->page_builders_built->is_page_builder_page( $post ) ? 0 : $translate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_post_id
|
||||
* @param int $original_post_id
|
||||
* @param array $fields
|
||||
*/
|
||||
public function copy( $new_post_id, $original_post_id, array $fields ) {
|
||||
$original_post = get_post( $original_post_id );
|
||||
if ( $original_post && $this->page_builders_built->is_page_builder_page( $original_post ) && ! $this->job_has_packages( $fields ) ) {
|
||||
wpml_update_escaped_post( [ 'ID' => $new_post_id, 'post_content' => $original_post->post_content ] );
|
||||
do_action( 'wpml_pb_after_page_without_elements_post_content_copy', $new_post_id, $original_post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $fields
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function job_has_packages( array $fields ) {
|
||||
foreach ( $fields as $key => $field ) {
|
||||
if ( 0 === strpos( $key, 'package' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* WPML_TM_Page_Builders_Field_Wrapper class file.
|
||||
*
|
||||
* @package wpml-page-builders
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WPML_TM_Page_Builders_Field_Wrapper
|
||||
*/
|
||||
class WPML_TM_Page_Builders_Field_Wrapper {
|
||||
const SLUG_BASE = 'package-string-';
|
||||
|
||||
/**
|
||||
* Field slug.
|
||||
*
|
||||
* @var false|string
|
||||
*/
|
||||
private $field_slug;
|
||||
|
||||
/**
|
||||
* Package id.
|
||||
*
|
||||
* @var string|false|null
|
||||
*/
|
||||
private $package_id;
|
||||
|
||||
/**
|
||||
* String id.
|
||||
*
|
||||
* @var string|false|null
|
||||
*/
|
||||
private $string_id;
|
||||
|
||||
/**
|
||||
* WPML_TM_Page_Builders_Field_Wrapper constructor.
|
||||
*
|
||||
* @param string $field_slug Field slug.
|
||||
*/
|
||||
public function __construct( $field_slug ) {
|
||||
$this->field_slug = $field_slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if package is valid.
|
||||
*
|
||||
* @param bool $package_must_exist Demand existence of the package.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid( $package_must_exist = false ) {
|
||||
$result = $this->get_package_id() && $this->get_string_id();
|
||||
if ( $result && $package_must_exist ) {
|
||||
$result = $this->get_package() !== null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package id.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function get_package_id() {
|
||||
if ( null === $this->package_id ) {
|
||||
$this->package_id = $this->extract_string_package_id( $this->field_slug );
|
||||
}
|
||||
|
||||
return $this->package_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package.
|
||||
*
|
||||
* @return WPML_Package|null
|
||||
*/
|
||||
public function get_package() {
|
||||
if ( ! $this->get_package_id() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return apply_filters( 'wpml_st_get_string_package', false, $this->get_package_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string id.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function get_string_id() {
|
||||
if ( null === $this->string_id ) {
|
||||
$this->string_id = $this->extract_string_id( $this->field_slug );
|
||||
}
|
||||
|
||||
return $this->string_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_field_slug() {
|
||||
return $this->field_slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string type.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function get_string_type() {
|
||||
$result = false;
|
||||
if ( $this->is_valid( true ) ) {
|
||||
$package_strings = $this->get_package()->get_package_strings();
|
||||
if ( ! $package_strings ) {
|
||||
return false;
|
||||
}
|
||||
$package_strings = wp_list_pluck( $package_strings, 'type', 'id' );
|
||||
$result = $package_strings[ $this->get_string_id() ];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string wrap tag.
|
||||
*
|
||||
* @param stdClass $string WPML string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_wrap_tag( $string ) {
|
||||
return isset( $string->wrap_tag ) ? $string->wrap_tag : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate field slug.
|
||||
*
|
||||
* @param int $package_id Package id.
|
||||
* @param int $string_id String id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function generate_field_slug( $package_id, $string_id ) {
|
||||
return self::SLUG_BASE . $package_id . '-' . $string_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract string id.
|
||||
*
|
||||
* @param string $field_slug Field slug.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function extract_string_id( $field_slug ) {
|
||||
$result = false;
|
||||
|
||||
if ( is_string( $field_slug ) && preg_match( '#^' . self::SLUG_BASE . '#', $field_slug ) ) {
|
||||
$result = preg_replace( '#^' . self::SLUG_BASE . '([0-9]+)-([0-9]+)$#', '$2', $field_slug, 1 );
|
||||
}
|
||||
|
||||
return is_numeric( $result ) ? $result : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract string package id.
|
||||
*
|
||||
* @param string $field_slug Field slug.
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function extract_string_package_id( $field_slug ) {
|
||||
$result = false;
|
||||
|
||||
if ( is_string( $field_slug ) && preg_match( '#^' . self::SLUG_BASE . '#', $field_slug ) ) {
|
||||
$result = preg_replace( '#^' . self::SLUG_BASE . '([0-9]+)-([0-9]+)$#', '$1', $field_slug, 1 );
|
||||
}
|
||||
|
||||
return is_numeric( $result ) ? $result : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string title.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_string_title() {
|
||||
if ( null === $this->string_id ) {
|
||||
$this->string_id = $this->extract_string_id( $this->field_slug );
|
||||
}
|
||||
|
||||
return apply_filters( 'wpml_string_title_from_id', false, $this->string_id );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Page_Builders_Hooks {
|
||||
|
||||
/* @var WPML_TM_Page_Builders $worker */
|
||||
private $worker;
|
||||
|
||||
/** @var SitePress $sitepress */
|
||||
private $sitepress;
|
||||
|
||||
/**
|
||||
* WPML_TM_Page_Builders constructor.
|
||||
*
|
||||
* @param WPML_TM_Page_Builders $worker
|
||||
*/
|
||||
public function __construct( WPML_TM_Page_Builders $worker = null, SitePress $sitepress ) {
|
||||
$this->worker = $worker;
|
||||
$this->sitepress = $sitepress;
|
||||
}
|
||||
|
||||
public function init_hooks() {
|
||||
add_filter( 'wpml_tm_translation_job_data', array( $this, 'translation_job_data_filter' ), 10, 2 );
|
||||
add_action( 'wpml_pro_translation_completed', array( $this, 'pro_translation_completed_action' ), 10, 3 );
|
||||
add_filter( 'wpml_tm_adjust_translation_fields', array( $this, 'adjust_translation_fields_filter' ), 10, 2 );
|
||||
add_filter( 'wpml_tm_job_layout', array( $this, 'job_layout_filter' ) );
|
||||
add_filter( 'wpml_link_to_translation', array( $this, 'link_to_translation_filter' ), 20, 4 );
|
||||
add_filter( 'wpml_get_translatable_types', array( $this, 'remove_shortcode_strings_type_filter' ), 11);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $translation_package
|
||||
* @param mixed $post
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function translation_job_data_filter( array $translation_package, $post ) {
|
||||
$worker = $this->get_worker();
|
||||
return $worker->translation_job_data_filter( $translation_package, $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_post_id
|
||||
* @param array $fields
|
||||
* @param stdClass $job
|
||||
*/
|
||||
public function pro_translation_completed_action( $new_post_id, array $fields, stdClass $job ) {
|
||||
$worker = $this->get_worker();
|
||||
$worker->pro_translation_completed_action( $new_post_id, $fields, $job );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter translation fields.
|
||||
*
|
||||
* @param array $fields Translation fields.
|
||||
* @param stdClass $job Translation job.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function adjust_translation_fields_filter( array $fields, $job ) {
|
||||
$worker = $this->get_worker();
|
||||
$fields = $worker->adjust_translation_fields_filter( $fields, $job );
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $layout
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function job_layout_filter( array $layout ) {
|
||||
$worker = $this->get_worker();
|
||||
return $worker->job_layout_filter( $layout );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $link
|
||||
* @param int $post_id
|
||||
* @param string $lang
|
||||
* @param int $trid
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function link_to_translation_filter( $link, $post_id, $lang, $trid ) {
|
||||
$worker = $this->get_worker();
|
||||
return $worker->link_to_translation_filter( $link, $post_id, $lang, $trid );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove "Page Builder ShortCode Strings" from translation dashboard filters
|
||||
*
|
||||
* @param array $types
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function remove_shortcode_strings_type_filter( $types ) {
|
||||
|
||||
if ( array_key_exists( 'page-builder-shortcode-strings', $types ) ) {
|
||||
unset( $types['page-builder-shortcode-strings'] );
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WPML_TM_Page_Builders
|
||||
*/
|
||||
private function get_worker() {
|
||||
if ( ! $this->worker ) {
|
||||
$this->worker = new WPML_TM_Page_Builders( $this->sitepress );
|
||||
}
|
||||
|
||||
return $this->worker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
class WPML_TM_Page_Builders {
|
||||
const PACKAGE_TYPE_EXTERNAL = 'external';
|
||||
const TRANSLATION_COMPLETE = 10;
|
||||
|
||||
const FIELD_STYLE_AREA = 'AREA';
|
||||
const FIELD_STYLE_VISUAL = 'VISUAL';
|
||||
const FIELD_STYLE_LINK = 'LINK';
|
||||
|
||||
/** @var SitePress $sitepress */
|
||||
private $sitepress;
|
||||
|
||||
/** @var \WPML_PB_Integration|null */
|
||||
private $wpmlPbIntegration;
|
||||
|
||||
/**
|
||||
* @param SitePress $sitepress
|
||||
* @param WPML_PB_Integration|null $wpmlPbIntegration
|
||||
*/
|
||||
public function __construct( SitePress $sitepress, \WPML_PB_Integration $wpmlPbIntegration = null ) {
|
||||
$this->sitepress = $sitepress;
|
||||
$this->wpmlPbIntegration = $wpmlPbIntegration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter translation job data.
|
||||
*
|
||||
* @param array $translation_package Translation package.
|
||||
* @param mixed $post Post.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function translation_job_data_filter( array $translation_package, $post ) {
|
||||
if ( self::PACKAGE_TYPE_EXTERNAL !== $translation_package['type'] && isset( $post->ID ) ) {
|
||||
|
||||
$post_element = new WPML_Post_Element( $post->ID, $this->sitepress );
|
||||
$source_post_id = $post->ID;
|
||||
$job_lang_from = $post_element->get_language_code();
|
||||
|
||||
if ( ! $post_element->is_root_source() && WPML_PB_Last_Translation_Edit_Mode::is_native_editor( $post->ID ) ) {
|
||||
$this->getWpmlPbIntegration()->register_all_strings_for_translation( $post, true );
|
||||
$source_post_element = $post_element->get_translation( $job_lang_from );
|
||||
} else {
|
||||
$source_post_element = $post_element->get_source_element();
|
||||
}
|
||||
|
||||
if ( $source_post_element ) {
|
||||
$source_post_id = $source_post_element->get_id();
|
||||
}
|
||||
|
||||
$job_source_is_not_post_source = $post->ID !== $source_post_id;
|
||||
|
||||
$string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $source_post_id );
|
||||
|
||||
$translation_package['contents']['body']['translate'] = apply_filters( 'wpml_pb_should_body_be_translated', $translation_package['contents']['body']['translate'], $post );
|
||||
|
||||
if ( $string_packages ) {
|
||||
|
||||
foreach ( $string_packages as $package_id => $string_package ) {
|
||||
|
||||
/**
|
||||
* String package.
|
||||
*
|
||||
* @var WPML_Package $string_package
|
||||
*/
|
||||
$strings = $string_package->get_package_strings();
|
||||
$string_translations = array();
|
||||
|
||||
if ( $job_source_is_not_post_source ) {
|
||||
$string_translations = $string_package->get_translated_strings( array() );
|
||||
}
|
||||
|
||||
foreach ( $strings as $string ) {
|
||||
|
||||
if ( self::FIELD_STYLE_LINK !== $string->type ) {
|
||||
$string_value = $string->value;
|
||||
|
||||
if ( isset( $string_translations[ $string->name ][ $job_lang_from ]['value'] ) ) {
|
||||
$string_value = $string_translations[ $string->name ][ $job_lang_from ]['value'];
|
||||
}
|
||||
|
||||
$field_name = WPML_TM_Page_Builders_Field_Wrapper::generate_field_slug(
|
||||
$package_id,
|
||||
$string->id
|
||||
);
|
||||
|
||||
$translation_package['contents'][ $field_name ] = array(
|
||||
'translate' => 1,
|
||||
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
'data' => base64_encode( $string_value ),
|
||||
// phpcs:enable
|
||||
'wrap_tag' => WPML_TM_Page_Builders_Field_Wrapper::get_wrap_tag( $string ),
|
||||
'format' => 'base64',
|
||||
);
|
||||
}
|
||||
|
||||
$translation_package['contents']['body']['translate'] = 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $translation_package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $new_element_id
|
||||
* @param array $fields
|
||||
* @param stdClass $job
|
||||
*/
|
||||
public function pro_translation_completed_action( $new_element_id, array $fields, stdClass $job ) {
|
||||
if ( 'post' !== $job->element_type_prefix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $fields as $field_id => $field ) {
|
||||
$field_slug = isset( $field['field_type'] ) ? $field['field_type'] : $field_id;
|
||||
$wrapper = $this->create_field_wrapper( $field_slug );
|
||||
$string_id = $wrapper->get_string_id();
|
||||
|
||||
if ( $string_id ) {
|
||||
do_action(
|
||||
'wpml_add_string_translation',
|
||||
$string_id,
|
||||
$job->language_code,
|
||||
$field['data'],
|
||||
self::TRANSLATION_COMPLETE,
|
||||
$job->translator_id,
|
||||
$job->translation_service
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
do_action( 'wpml_pb_finished_adding_string_translations', $new_element_id, $job->original_doc_id, $fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust translation fields.
|
||||
*
|
||||
* @param array $fields Translation fields.
|
||||
* @param stdClass $job Translation job.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function adjust_translation_fields_filter( array $fields, $job ) {
|
||||
foreach ( $fields as &$field ) {
|
||||
$wrapper = $this->create_field_wrapper( $field['field_type'] );
|
||||
$type = $wrapper->get_string_type();
|
||||
$string_title = $wrapper->get_string_title();
|
||||
|
||||
if ( $string_title ) {
|
||||
$field['title'] = $string_title;
|
||||
}
|
||||
|
||||
if ( isset( $field['field_wrap_tag'] ) && $field['field_wrap_tag'] ) {
|
||||
$field['title'] = isset( $field['title'] ) ? $field['title'] : '';
|
||||
|
||||
$field['title'] .= ' (' . $field['field_wrap_tag'] . ')';
|
||||
}
|
||||
|
||||
if ( false !== $type ) {
|
||||
switch ( $type ) {
|
||||
case self::FIELD_STYLE_AREA:
|
||||
$field['field_style'] = '1';
|
||||
break;
|
||||
case self::FIELD_STYLE_VISUAL:
|
||||
$field['field_style'] = '2';
|
||||
break;
|
||||
default:
|
||||
$field['field_style'] = '0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $layout
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function job_layout_filter( array $layout ) {
|
||||
|
||||
$string_groups = array();
|
||||
|
||||
foreach ( $layout as $k => $field ) {
|
||||
$wrapper = $this->create_field_wrapper( $field );
|
||||
if ( $wrapper->is_valid() ) {
|
||||
$string_groups[ $wrapper->get_package_id() ][] = $field;
|
||||
unset( $layout[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $string_groups as $string_package_id => $fields ) {
|
||||
$string_package = apply_filters( 'wpml_st_get_string_package', false, $string_package_id );
|
||||
|
||||
$section = array(
|
||||
'field_type' => 'tm-section',
|
||||
'title' => isset( $string_package->title ) ? $string_package->title : '',
|
||||
'fields' => $fields,
|
||||
'empty' => false,
|
||||
'empty_message' => '',
|
||||
'sub_title' => '',
|
||||
);
|
||||
$layout[] = $section;
|
||||
}
|
||||
|
||||
return array_values( $layout );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $link
|
||||
* @param int $post_id
|
||||
* @param string $lang
|
||||
* @param int $trid
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function link_to_translation_filter( $link, $post_id, $lang, $trid ) {
|
||||
/* @var WPML_TM_Translation_Status $wpml_tm_translation_status */
|
||||
global $wpml_tm_translation_status;
|
||||
|
||||
$status = $wpml_tm_translation_status->filter_translation_status( null, $trid, $lang );
|
||||
|
||||
if ( $link && ICL_TM_NEEDS_UPDATE === $status ) {
|
||||
$args = array(
|
||||
'update_needed' => 1,
|
||||
'trid' => $trid,
|
||||
'language_code' => $lang,
|
||||
);
|
||||
|
||||
$link = add_query_arg( $args, $link );
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field_slug
|
||||
*
|
||||
* @return WPML_TM_Page_Builders_Field_Wrapper
|
||||
*/
|
||||
public function create_field_wrapper( $field_slug ) {
|
||||
return new WPML_TM_Page_Builders_Field_Wrapper( $field_slug );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \WPML_PB_Integration
|
||||
*
|
||||
*/
|
||||
private function getWpmlPbIntegration() {
|
||||
if ( null === $this->wpmlPbIntegration ) {
|
||||
$this->wpmlPbIntegration = \WPML\Container\make( \WPML_PB_Integration::class );
|
||||
}
|
||||
return $this->wpmlPbIntegration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
class WPML_Page_Builders_Page_Built {
|
||||
|
||||
private $config;
|
||||
|
||||
public function __construct( WPML_Config_Built_With_Page_Builders $config ) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WP_Post $post
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_page_builder_page( WP_Post $post ) {
|
||||
$result = false;
|
||||
$config_data = $this->config->get();
|
||||
|
||||
if ( is_array( $config_data ) ) {
|
||||
foreach ( $config_data as $pattern ) {
|
||||
$result = (bool) preg_match_all( $pattern, $post->post_content, $matches );
|
||||
|
||||
if ( $result ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'wpml_pb_is_page_builder_page', $result, $post );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Based on https://github.com/paulgb/simplediff/blob/master/php/simplediff.php
|
||||
*/
|
||||
|
||||
class WPML_ST_Diff {
|
||||
|
||||
/**
|
||||
* @param string[] $old_words
|
||||
* @param string[] $new_words
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function diff( $old_words, $new_words ) {
|
||||
$matrix = array();
|
||||
$max_length = 0;
|
||||
$old_max = null;
|
||||
$new_max = null;
|
||||
foreach ( $old_words as $old_index => $old_value ) {
|
||||
$new_keys = array_keys( $new_words, $old_value );
|
||||
foreach ( $new_keys as $new_index ) {
|
||||
$matrix[ $old_index ][ $new_index ] = isset( $matrix[ $old_index - 1 ][ $new_index - 1 ] ) ?
|
||||
$matrix[ $old_index - 1 ][ $new_index - 1 ] + 1 : 1;
|
||||
if ( $matrix[ $old_index ][ $new_index ] > $max_length ) {
|
||||
$max_length = $matrix[ $old_index ][ $new_index ];
|
||||
$old_max = $old_index + 1 - $max_length;
|
||||
$new_max = $new_index + 1 - $max_length;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( $max_length == 0 ) {
|
||||
return array( array( 'deleted' => $old_words, 'inserted' => $new_words ) );
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
self::diff( array_slice( $old_words, 0, $old_max ), array_slice( $new_words, 0, $new_max ) ),
|
||||
array_slice( $new_words, $new_max, $max_length ),
|
||||
self::diff( array_slice( $old_words, $old_max + $max_length ), array_slice( $new_words, $new_max + $max_length ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $old_text
|
||||
* @param string $new_text
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public static function get_sameness_percent( $old_text, $new_text ) {
|
||||
$old_text = $old_text ? strip_tags( $old_text ) : $old_text;
|
||||
if ( $old_text ) {
|
||||
$new_text = strip_tags( $new_text );
|
||||
|
||||
$diff = self::diff( preg_split( '/[\s]+/', $old_text ), preg_split( '/[\s]+/', $new_text ) );
|
||||
|
||||
$common_length = 0;
|
||||
foreach ( $diff as $diff_data ) {
|
||||
if ( ! is_array( $diff_data ) ) {
|
||||
$common_length += strlen( $diff_data );
|
||||
}
|
||||
}
|
||||
|
||||
return ( $common_length * 100 ) / strlen( $old_text );
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user