5909f7bb by Jeff Balicki

wpml

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 400cee25
Showing 1000 changed files with 4472 additions and 0 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

<?php
namespace WPML\TM\API;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Post;
use WPML_TM_ATE_API;
use WPML_TM_ATE_Jobs;
use function WPML\FP\pipe;
class ATE {
/** @var WPML_TM_ATE_API $ateApi */
private $ateApi;
/** @var WPML_TM_ATE_Jobs $ateJobs */
private $ateJobs;
public function __construct( WPML_TM_ATE_API $ateApi, WPML_TM_ATE_Jobs $ateJobs ) {
$this->ateApi = $ateApi;
$this->ateJobs = $ateJobs;
}
public function checkJobStatus( $wpmlJobId ) {
$ateJobId = $this->ateJobs->get_ate_job_id( $wpmlJobId );
$response = $this->ateApi->get_job_status_with_priority( $ateJobId );
if ( is_wp_error( $response ) ) {
return [];
}
$encoded = wp_json_encode( $response );
if ( ! $encoded ) {
return [];
}
return wpml_collect( json_decode( $encoded, true ) )->first(
pipe(
Obj::prop( 'ate_job_id' ),
Relation::equals( $ateJobId )
)
);
}
public function applyTranslation( $wpmlJobId, $postId, $xliffUrl ) {
$ateJobId = $this->ateJobs->get_ate_job_id( $wpmlJobId );
$xliffContent = $this->ateApi->get_remote_xliff_content( $xliffUrl, [ 'jobId' => $wpmlJobId, 'ateJobId' => $ateJobId ] );
if ( ! function_exists( 'wpml_tm_save_data' ) ) {
require_once WPML_TM_PATH . '/inc/wpml-private-actions.php';
}
$prevPostStatus = Post::getStatus( $postId );
if ( $this->ateJobs->apply( $xliffContent ) ) {
if ( Post::getStatus( $postId ) !== $prevPostStatus ) {
Post::setStatus( $postId, $prevPostStatus );
}
$response = $this->ateApi->confirm_received_job( $ateJobId );
return ! is_wp_error( $response );
}
return false;
}
}
<?php
namespace WPML\TM\API\ATE;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\LIB\WP\WordPress;
use WPML\WP\OptionManager;
use function WPML\Container\make;
class Account {
/**
* @return Either<array>
*/
public static function getCredits() {
return WordPress::handleError( make( \WPML_TM_AMS_API::class )->getCredits() )
->filter( Fns::identity() )
/** @phpstan-ignore-next-line */
->map( Fns::tap( OptionManager::update( 'TM', 'Account::credits' ) ) )
->bimap( Fns::always( [ 'error' => 'communication error' ] ), Fns::identity() );
}
/**
* @param array $creditInfo
*
* @return bool
*/
public static function hasActiveSubscription( array $creditInfo ) {
return (bool) Obj::propOr( false, 'active_subscription', $creditInfo );
}
/**
* @param array $creditInfo
*
* @return int
*/
public static function getAvailableBalance( array $creditInfo ) {
return (int) Obj::propOr( 0, 'available_balance', $creditInfo );
}
/**
* @return bool
*/
public static function isAbleToTranslateAutomatically() {
$creditInfo = OptionManager::getOr( [], 'TM', 'Account::credits' );
if ( ! array_key_exists( 'active_subscription', $creditInfo ) ) {
$creditInfo = self::getCredits()->getOrElse( [] );
}
return self::hasActiveSubscription( $creditInfo ) || self::getAvailableBalance( $creditInfo ) > 0;
}
}
<?php
namespace WPML\TM\API\ATE;
use WPML\TM\ATE\API\CachedATEAPI;
use WPML\TM\ATE\API\CacheStorage\Transient;
use function WPML\Container\make;
class CachedLanguageMappings extends LanguageMappings {
/**
* @return CachedATEAPI
*/
protected static function getATEAPI() {
return new CachedATEAPI( make( \WPML_TM_ATE_API::class ), new Transient() );
}
public static function clearCache() {
$transientStorage = new Transient();
$transientStorage->delete( CachedATEAPI::CACHE_OPTION );
}
}
<?php
namespace WPML\TM\API\ATE;
use WPML\Element\API\Languages;
use WPML\FP\Either;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Lst;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Wrapper;
use WPML\LIB\WP\Option;
use WPML\Element\API\Entity\LanguageMapping;
use WPML\TM\ATE\API\CacheStorage\StaticVariable;
use WPML\TM\ATE\API\CachedATEAPI;
use function WPML\Container\make;
use function WPML\FP\curryN;
use function WPML\FP\invoke;
use function WPML\FP\pipe;
class LanguageMappings {
const IGNORE_MAPPING_OPTION = 'wpml-languages-ignore-mapping';
const IGNORE_MAPPING_ID = - 1;
public static function withCanBeTranslatedAutomatically( $languages = null ) {
$fn = curryN( 1, function ( $languages ) {
if ( ! is_object( $languages ) && ! is_array( $languages ) ) {
return $languages;
}
$ateAPI = static::getATEAPI();
$targetCodes = Lst::pluck( 'code', Obj::values( $languages ) );
$supportedLanguages = $ateAPI->get_languages_supported_by_automatic_translations( $targetCodes )->getOrElse( [] );
$areThereAnySupportedLanguages = Lst::find( Logic::isNotNull(), $supportedLanguages );
$isSupportedCode = pipe( Obj::prop( Fns::__, $supportedLanguages ), Logic::isNotNull() );
$isNotMarkedAsDontMap = Logic::complement( Lst::includes( Fns::__, Option::getOr( self::IGNORE_MAPPING_OPTION, [] ) ) );
$isDefaultCode = Relation::equals( Languages::getDefaultCode() );
$isSupportedByAnyEngine = pipe(
pipe( [ $ateAPI, 'get_language_details' ], invoke( 'getOrElse' )->with( [] ) ),
Logic::anyPass( [ Obj::prop( 'ms_api_iso' ), Obj::prop( 'google_api_iso' ), Obj::prop( 'deepl_api_iso' ) ] )
);
$isDefaultLangSupported = Logic::anyPass( [ Fns::always( $areThereAnySupportedLanguages ), $isSupportedByAnyEngine ] );
$isSupported = pipe( Obj::prop( 'code' ), Logic::both(
$isNotMarkedAsDontMap,
Logic::ifElse( $isDefaultCode, $isDefaultLangSupported, $isSupportedCode )
) );
return Fns::map( Obj::addProp( 'can_be_translated_automatically', $isSupported ), $languages );
} );
return call_user_func_array( $fn, func_get_args() );
}
public static function isCodeEligibleForAutomaticTranslations( $languageCode = null ) {
$fn = Lst::includes( Fns::__, static::geCodesEligibleForAutomaticTranslations() );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @return LanguageMapping[] $mappings
*/
public static function get() {
$ignoredMappings = Fns::map( function ( $code ) {
return new LanguageMapping( $code, '', self::IGNORE_MAPPING_ID );
}, Option::getOr( self::IGNORE_MAPPING_OPTION, [] ) );
$mappingInATE = Fns::map( function ( $record ) {
return new LanguageMapping(
Obj::prop( 'source_code', $record ),
Obj::path( [ 'source_language', 'name' ], $record ),
Obj::path( [ 'target_language', 'id' ], $record ),
Obj::prop( 'target_code', $record )
);
}, static::getATEAPI()->get_language_mapping()->getOrElse( [] ) );
return Lst::concat( $ignoredMappings, $mappingInATE );
}
public static function withMapping( $languages = null ) {
$fn = curryN( 1, function ( $languages ) {
$mapping = self::get();
$findMappingByCode = function ( $language ) use ( $mapping ) {
return Lst::find( invoke( 'matches' )->with( Obj::prop( 'code', $language ) ), $mapping );
};
return Fns::map( Obj::addProp( 'mapping', $findMappingByCode ), $languages );
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @return array
*/
public static function getAvailable() {
$mapping = static::getATEAPI()->get_available_languages();
return Relation::sortWith( [ Fns::ascend( Obj::prop( 'name' ) ) ], $mapping );
}
/**
* @param LanguageMapping[] $mappings
*
* @return Either
*/
public static function saveMapping( array $mappings ) {
list( $ignoredMapping, $mappingSet ) = \wpml_collect( $mappings )->partition( Relation::propEq( 'targetId', self::IGNORE_MAPPING_ID ) );
$ignoredCodes = $ignoredMapping->pluck( 'sourceCode' )->toArray();
Option::update( self::IGNORE_MAPPING_OPTION, $ignoredCodes );
$ateAPI = static::getATEAPI();
if ( count( $ignoredCodes ) ) {
$ateAPI->get_language_mapping()
->map( Fns::filter( pipe( Obj::prop( 'source_code' ), Lst::includes( Fns::__, $ignoredCodes ) ) ) )
->map( Lst::pluck( 'id' ) )
->filter( Logic::complement( Logic::isEmpty() ) )
->map( [ $ateAPI, 'remove_language_mapping' ] );
}
return $ateAPI->create_language_mapping( $mappingSet->values()->toArray() );
}
/**
* @return array
*/
public static function getLanguagesEligibleForAutomaticTranslations() {
return Wrapper::of( Languages::getSecondaries() )
->map( static::withCanBeTranslatedAutomatically() )
->map( Fns::filter( Obj::prop( 'can_be_translated_automatically' ) ) )
->get();
}
/**
* @return string[]
*/
public static function geCodesEligibleForAutomaticTranslations() {
return Lst::pluck( 'code', static::getLanguagesEligibleForAutomaticTranslations() );
}
public static function hasTheSameMappingAsDefaultLang( $language = null ) {
$fn = curryN( 1, function ( $language ) {
$defaultLanguage = Lst::last( static::withMapping( [ Languages::getDefault() ] ) );
if ( ! is_object( $defaultLanguage ) && ! is_array( $defaultLanguage ) ) {
return false;
}
$defaultLanguageMappingTargetCode = Obj::pathOr( Obj::prop( 'code', $defaultLanguage ), [ 'mapping', 'targetCode' ], $defaultLanguage );
return Obj::pathOr( null, [ 'mapping', 'targetCode' ], $language ) === $defaultLanguageMappingTargetCode;
} );
return call_user_func_array( $fn, func_get_args() );
}
/**
* @return CachedATEAPI
*/
protected static function getATEAPI() {
return new CachedATEAPI( make( \WPML_TM_ATE_API::class ), StaticVariable::getInstance() );
}
}
<?php
namespace WPML\TM\API;
use WPML\Element\API\Languages;
use WPML\LIB\WP\User;
use WPML\Setup\Option;
use function WPML\Container\make;
class Basket {
/**
* @return bool
*/
public static function shouldUse( $currentLanguageCode = null ) {
$doesNotHaveUserForEachLanguage = function () use ( $currentLanguageCode ) {
global $sitepress;
$theCurrentUserId = User::getCurrentId();
$translator_records = make( \WPML_Translator_Records::class );
$current_language = $currentLanguageCode ?: Languages::getCurrentCode();
$active_languages = $sitepress->get_active_languages();
unset( $active_languages[ $current_language ] );
$active_languages = array_keys( $active_languages );
foreach ( $active_languages as $active_language ) {
$translators = $translator_records->get_users_with_languages( $current_language, [ $active_language ] );
$number_of_translators = count( $translators );
$hasOneTranslatorButHeIsNotACurrentUser = $number_of_translators === 1 && $translators[0]->ID !== $theCurrentUserId;
if ( $hasOneTranslatorButHeIsNotACurrentUser || $number_of_translators !== 1 ) {
return true;
}
}
return false;
};
/** @var TranslationServices $translationService */
$translationService = make(TranslationServices::class);
return $translationService->isAuthorized() || ! Option::shouldTranslateEverything() && $doesNotHaveUserForEachLanguage();
}
}
<?php
namespace WPML\TM\API;
use WPML\FP\Curryable;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\TM\Jobs\Dispatch\Messages;
use function WPML\Container\make;
/**
* Class Batch
* @package WPML\TM\API
*
* @method static callable|void rollback( ...$batchName ) - Curried :: string->void
*
* It rollbacks just sent batch.
*/
class Batch {
use Curryable;
public static function init() {
self::curryN( 'rollback', 1, function ( $basketName ) {
$batch = make( \WPML_Translation_Basket::class )->get_basket_batch( $basketName );
$batch->cancel_all_jobs();
$batch->clear_batch_data();
} );
}
public static function sendPosts( Messages $messages, $batch, $sendFrom = Jobs::SENT_VIA_BASKET ) {
$dispatchActions = function ( $batch ) use ( $sendFrom ) {
$allowedTypes = array_keys( \TranslationProxy_Basket::get_basket_items_types() );
foreach ( $allowedTypes as $type ) {
do_action( 'wpml_tm_send_' . $type . '_jobs', $batch, $type, $sendFrom );
}
};
self::send( $dispatchActions, [ $messages, 'showForPosts' ], $batch );
}
public static function sendStrings( Messages $messages, $batch ) {
$dispatchActions = function ( $batch ) {
do_action( 'wpml_tm_send_st-batch_jobs', $batch, 'st-batch' );
};
self::send( $dispatchActions, [ $messages, 'showForStrings' ], $batch );
}
private static function send( callable $dispatchAction, callable $displayErrors, $batch ) {
$dispatchAction( $batch );
$errors = wpml_load_core_tm()->messages_by_type( 'error' );
if ( $errors ) {
self::rollback( $batch->get_basket_name() );
$displayErrors( Fns::map( Obj::prop( 'text' ), $errors ), 'error' );
}
}
}
Batch::init();
<?php
namespace WPML\TM\API\Job;
use WPML\Collect\Support\Traits\Macroable;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
use function WPML\FP\curryN;
/**
* @method static callable|int fromJobId( ...$job_id )
* @method static callable|int|null fromRid( ...$rid )
*/
class Map {
use Macroable;
private static $rid_to_jobId = [];
public static function init() {
self::$rid_to_jobId = [];
self::macro( 'fromJobId', curryN( 1, Fns::memorize( function ( $jobId ) {
$rid = Obj::prop( $jobId, array_flip( array_filter( self::$rid_to_jobId ) ) );
if ( $rid ) {
return $rid;
}
$rid = self::ridFromDB( $jobId );
self::$rid_to_jobId[$rid] = $jobId;
return $rid;
})));
self::macro( 'fromRid', curryN( 1, function ( $rid ) {
$jobId = Obj::prop( $rid, self::$rid_to_jobId );
if ( $jobId ) {
return $jobId;
}
$jobId = self::jobIdFromDB( $rid );
self::$rid_to_jobId[ $rid ] = $jobId;
return $jobId;
} ) );
}
public static function jobIdFromDB( $rid ) {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT MAX(job_id) FROM {$wpdb->prefix}icl_translate_job WHERE rid=%d",
$rid
)
);
}
public static function ridFromDB( $jobId ) {
global $wpdb;
return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT rid FROM {$wpdb->prefix}icl_translate_job WHERE job_id=%d",
$jobId
)
);
}
}
Map::init();
<?php
namespace WPML\TM\API;
use WPML\FP\Either;
use WPML\TM\TranslationProxy\Services\AuthorizationFactory;
class TranslationServices {
/**
* @var AuthorizationFactory
*/
private $authorizationFactory;
/**
* @param AuthorizationFactory $authorizationFactory
*/
public function __construct( AuthorizationFactory $authorizationFactory ) {
$this->authorizationFactory = $authorizationFactory;
}
/**
* @param string $suid
*
* @return Either
*/
public function selectBySUID( $suid ) {
try {
$service = \TranslationProxy_Service::get_service_by_suid( $suid );
return $this->selectByServiceId( $service->id );
} catch ( \Exception $e ) {
return Either::left( sprintf( __( 'Service with SUID=%s cannot be found', ' sitepress-multilingual-cms' ), $suid ) );
}
}
/**
* @param int $serviceId
*
* @return Either
*/
public function selectByServiceId( $serviceId ) {
$result = \TranslationProxy::select_service( $serviceId );
return \is_wp_error( $result ) ? Either::left( $result->get_error_message() ) : Either::of( $serviceId );
}
public function deselect() {
if ( \TranslationProxy::get_current_service_id() ) {
\TranslationProxy::clear_preferred_translation_service();
\TranslationProxy::deselect_active_service();
}
}
public function authorize( $apiToken ) {
$authorization = $this->authorizationFactory->create();
try {
$authorization->authorize( (object) [ 'api_token' => $apiToken ] );
return Either::of( true );
} catch ( \Exception $e ) {
$authorization->deauthorize();
return Either::left( $e->getMessage() );
}
}
/**
* @return null|\TranslationProxy_Service
*/
public function getCurrentService() {
$service = \TranslationProxy::get_current_service();
return ! is_wp_error( $service ) ? $service : null;
}
public function isAnyActive() {
return $this->getCurrentService() !== null;
}
public function isAuthorized() {
return \TranslationProxy::is_current_service_active_and_authenticated();
}
}
<?php
namespace WPML\TM\API;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\LIB\WP\User;
class Translators {
/**
* @return \WPML_Translator
*/
public static function getCurrent() {
$translator = wpml_load_core_tm()->get_current_translator();
if ( ! $translator->ID ) {
return $translator;
}
if ( empty( $translator->language_pairs ) && User::canManageTranslations() ) {
return Obj::assoc( 'language_pairs', \WPML_All_Language_Pairs::get(), $translator );
}
return Obj::over(
Obj::lensProp( 'language_pairs' ),
Fns::map( Obj::keys() ),
$translator
);
}
}
<?php
if ( ! defined( 'WPML_PAGE_BUILDERS_LOADED' ) ) {
throw new Exception( 'This file should be called from the loader only.' );
}
require_once __DIR__ . '/classes/OldPlugin.php';
if ( WPML\PB\OldPlugin::handle() ) {
return;
}
define( 'WPML_PAGE_BUILDERS_VERSION', '2.1.2' );
define( 'WPML_PAGE_BUILDERS_PATH', __DIR__ );
if ( ! class_exists( 'WPML_Core_Version_Check' ) ) {
require_once WPML_PAGE_BUILDERS_PATH . '/vendor/wpml-shared/wpml-lib-dependencies/src/dependencies/class-wpml-core-version-check.php';
}
if ( ! WPML_Core_Version_Check::is_ok( WPML_PAGE_BUILDERS_PATH . '/wpml-dependencies.json' ) ) {
return;
}
require_once WPML_PAGE_BUILDERS_PATH . '/vendor/autoload.php';
\WPML\PB\App::run();
\ No newline at end of file
<?php
namespace WPML\PB;
class App {
public static function run() {
global $sitepress, $wpdb;
LegacyIntegration::load();
if (
$sitepress->is_setup_complete()
&& has_action( 'wpml_before_init', 'load_wpml_st_basics' )
) {
if ( self::shouldLoadTMHooks() ) {
$page_builder_hooks = new \WPML_TM_Page_Builders_Hooks(
new \WPML_TM_Page_Builders( $sitepress ),
$sitepress
);
$page_builder_hooks->init_hooks();
}
$app = new \WPML_Page_Builders_App( new \WPML_Page_Builders_Defined() );
$app->add_hooks();
new \WPML_PB_Loader( new \WPML_ST_Settings() );
}
}
/**
* @return bool
*/
private static function shouldLoadTMHooks() {
return defined( 'WPML_TM_VERSION' )
&& (
is_admin()
|| ( defined( 'XMLRPC_REQUEST' ) && constant( 'XMLRPC_REQUEST' ) )
|| wpml_is_rest_request()
);
}
}
<?php
namespace WPML\PB\BeaverBuilder\BeaverThemer;
class HooksFactory implements \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader {
public function create() {
if ( self::isActive() ) {
return [
new LocationHooks(),
];
}
return null;
}
/**
* @return bool
*/
public static function isActive() {
return defined( 'FL_THEME_BUILDER_VERSION' );
}
}
<?php
namespace WPML\PB\BeaverBuilder\BeaverThemer;
use WPML\Convert\Ids;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class LocationHooks implements \IWPML_Backend_Action {
const LAYOUT_CPT = 'fl-theme-layout';
const LOCATIONS_RULES_KEY = '_fl_theme_builder_locations';
const EXCLUSIONS_RULES_KEY = '_fl_theme_builder_exclusions';
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_copy_meta_field', 10, 4 )
->then( spreadArgs( [ $this, 'translateLocationRulesMeta' ] ) );
}
/**
* @param mixed $copiedValue
* @param int $translatedPostId
* @param int $originalPostId
* @param string $metaKey
*
* @return mixed
*/
public function translateLocationRulesMeta( $copiedValue, $translatedPostId, $originalPostId, $metaKey ) {
if ( in_array( $metaKey, [ self::LOCATIONS_RULES_KEY, self::EXCLUSIONS_RULES_KEY ], true ) ) {
$targetLang = self::getLayoutLanguage( $translatedPostId );
foreach ( $copiedValue as &$rule ) {
$rule = $this->translateRule( $rule, $targetLang );
}
}
return $copiedValue;
}
/**
* Translate IDs in locations rules.
*
* Location rules are an array of rules. Each rule is separated by (:).
* General rules can be like:
* 'general:site'
* 'general:archive'
* 'general:single'
* 'general:404'
* 'post:post'
* 'post:page'
*
* This translates the cases for posts and taxonomies. Their rules can be like:
* 'post:page:12'
* 'post:post:taxonomy:category:45'
*
* @param string $rule
* @param string $targetLangCode
*
* @return string
*/
private function translateRule( $rule, $targetLangCode ) {
$parts = explode( ':', $rule );
if ( 3 === count( $parts ) ) {
$rule = implode( ':', [ $parts[0], $parts[1], self::translateElement( $parts[2], $parts[1], $targetLangCode ) ] );
} elseif ( 5 === count( $parts ) ) {
$rule = implode( ':', [ $parts[0], $parts[1], $parts[2], $parts[3], self::translateElement( $parts[4], $parts[3], $targetLangCode ) ] );
}
return $rule;
}
/**
* @param int $translatedPostId
*
* @return string|null
*/
private static function getLayoutLanguage( $translatedPostId ) {
return apply_filters( 'wpml_element_language_code', null, [
'element_id' => $translatedPostId,
'element_type' => self::LAYOUT_CPT,
] );
}
/**
* @param string $elementId
* @param string $elementType
* @param string $targetLangCode
*
* @return string
*/
private static function translateElement( $elementId, $elementType, $targetLangCode ) {
return Ids::convert( $elementId, $elementType, true, $targetLangCode );
}
}
<?php
namespace WPML\PB\BeaverBuilder\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'beaver-builder-widgets',
'defaultConditionKey' => 'type',
'pbKey' => 'beaver-builder',
'translatableWidgetsHook' => 'wpml_beaver_builder_modules_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}
<?php
namespace WPML\PB\BeaverBuilder\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Frontend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Obj::path( [ 'fl_builder_data', 'action' ], $_POST ) === 'save_layout'
&& (int) Obj::path( [ 'fl_builder_data', 'post_id' ], $_POST ) === $translatedPostId
);
} ) );
}
}
<?php
namespace WPML\PB\BeaverBuilder\Hooks;
class Menu implements \IWPML_Frontend_Action {
const TERM_TAXONOMY = 'nav_menu';
public function add_hooks() {
add_filter( 'fl_builder_menu_module_core_menu', [ $this, 'adjustTranslatedMenu' ], 10, 2 );
}
/**
* @param string $menu
* @param object $settings module settings object.
*
* @return string
*/
public function adjustTranslatedMenu( $menu, $settings ) {
$targetMenuSlug = $settings->menu;
$targetMenu = get_term_by( 'slug', $targetMenuSlug, self::TERM_TAXONOMY );
if ( $targetMenu ) {
$menu = $targetMenu->slug;
}
return $menu;
}
}
<?php
namespace WPML\PB\BeaverBuilder\TranslationJob;
use WPML_Beaver_Builder_Data_Settings;
class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
/** @var WPML_Beaver_Builder_Data_Settings $dataSettings */
private $dataSettings;
public function __construct( WPML_Beaver_Builder_Data_Settings $dataSettings ) {
$this->dataSettings = $dataSettings;
}
public function add_hooks() {
add_filter( 'wpml_tm_translation_job_data', [ $this, 'filterFieldsByPageBuilderKind' ], PHP_INT_MAX, 2 );
}
/**
* @param array $translationPackage
* @param \stdClass|\WP_Post $post
*
* @return array
*/
public function filterFieldsByPageBuilderKind( array $translationPackage, $post ) {
if ( ! $this->isPostPackage( $translationPackage, $post ) ) {
return $translationPackage;
}
if ( $this->dataSettings->is_handling_post( $post->ID ) ) {
return $this->removeFieldsFromKind( $translationPackage, $post->ID, 'gutenberg' );
}
/**
*
*/
return $this->removeFieldsFromKind( $translationPackage, $post->ID, 'beaver-builder' );
}
/**
* @param array $translationPackage
* @param \stdClass|\WP_Post $post
*
* @return bool
*/
private function isPostPackage( array $translationPackage, $post ) {
return 'external' !== $translationPackage['type'] && isset( $post->ID );
}
/**
* @param array $translationPackage
* @param int $postId
* @param string $kindSlug
*
* @return array
*/
private function removeFieldsFromKind( array $translationPackage, $postId, $kindSlug ) {
$packageIdToRemove = wpml_collect( apply_filters( 'wpml_st_get_post_string_packages', [], $postId ) )
->pluck( 'ID', 'kind_slug' )
->get( $kindSlug );
if ( $packageIdToRemove ) {
$isFieldFromPackageToRemove = function( $value, $key ) use ( $packageIdToRemove ) {
return preg_match( '/^package-string-' . $packageIdToRemove . '-/', $key );
};
$translationPackage['contents'] = wpml_collect( $translationPackage['contents'] )
->reject( $isFieldFromPackageToRemove )
->toArray();
}
return $translationPackage;
}
}
<?php
class WPML_Beaver_Builder_Data_Settings_For_Media extends WPML_Beaver_Builder_Data_Settings {
/**
* @return array
*/
public function get_fields_to_copy() {
return [];
}
}
<?php
use WPML\PB\BeaverBuilder\BeaverThemer\HooksFactory as BeaverThemer;
class WPML_Beaver_Builder_Data_Settings implements IWPML_Page_Builders_Data_Settings {
/**
* @return string
*/
public function get_meta_field() {
return '_fl_builder_data';
}
/**
* @return string
*/
public function get_node_id_field() {
return 'node';
}
/**
* @return array
*/
public function get_fields_to_copy() {
$fields = [
'_fl_builder_draft_settings',
'_fl_builder_data_settings',
'_fl_builder_enabled',
];
if ( BeaverThemer::isActive() ) {
return array_merge(
$fields,
[
'_fl_theme_builder_locations',
'_fl_theme_builder_exclusions',
'_fl_theme_builder_edit_mode',
]
);
}
return $fields;
}
/**
* @param array $data
*
* @return array
*/
public function convert_data_to_array( $data ) {
return $data;
}
/**
* @param array $data
*
* @return array
*/
public function prepare_data_for_saving( array $data ) {
return $this->slash( $data );
}
/**
* @return string
*/
public function get_pb_name() {
return 'Beaver builder';
}
/**
* @return array
*/
public function get_fields_to_save() {
return array( '_fl_builder_data', '_fl_builder_draft' );
}
public function add_hooks(){}
/**
* Adds slashes to data going into the database as WordPress
* removes them when we save using update_metadata. This is done
* to ensure slashes in user input aren't removed.
*
* Inspired by `\FLBuilderModel::slash_settings`
*
* @param mixed $data The data to slash.
*
* @return mixed The slashed data.
*/
private function slash( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $key => $val ) {
$data[ $key ] = $this->slash( $val );
}
} elseif ( is_object( $data ) ) {
foreach ( $data as $key => $val ) {
$data->$key = $this->slash( $val );
}
} elseif ( is_string( $data ) ) {
$data = wp_slash( $data );
}
return $data;
}
/**
* @param int $postId
*
* @return bool
*/
public function is_handling_post( $postId ) {
return (bool) get_post_meta( $postId, '_fl_builder_enabled', true );
}
}
<?php
class WPML_PB_Beaver_Builder_Handle_Custom_Fields_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_PB_Handle_Custom_Fields( new WPML_Beaver_Builder_Data_Settings() );
}
}
\ No newline at end of file
<?php
class WPML_Beaver_Builder_Integration_Factory {
const SLUG = 'beaver-builder';
public function create() {
$action_filter_loader = new WPML_Action_Filter_Loader();
$action_filter_loader->load(
array(
'WPML_PB_Beaver_Builder_Handle_Custom_Fields_Factory',
'WPML_Beaver_Builder_Media_Hooks_Factory',
\WPML\PB\BeaverBuilder\TranslationJob\Hooks::class,
\WPML\PB\BeaverBuilder\Config\Factory::class,
\WPML\PB\BeaverBuilder\Hooks\Editor::class,
\WPML\PB\BeaverBuilder\Hooks\Menu::class,
// BeaverThemer.
\WPML\PB\BeaverBuilder\BeaverThemer\HooksFactory::class,
)
);
$nodes = new WPML_Beaver_Builder_Translatable_Nodes();
$data_settings = new WPML_Beaver_Builder_Data_Settings();
$string_registration_factory = new WPML_String_Registration_Factory( $data_settings->get_pb_name() );
$string_registration = $string_registration_factory->create();
$register_strings = new WPML_Beaver_Builder_Register_Strings( $nodes, $data_settings, $string_registration );
$update_translation = new WPML_Beaver_Builder_Update_Translation( $nodes, $data_settings );
return new WPML_Page_Builders_Integration( $register_strings, $update_translation, $data_settings );
}
}
\ No newline at end of file
<?php
/**
* Class WPML_Beaver_Builder_Register_Strings
*/
class WPML_Beaver_Builder_Register_Strings extends WPML_Page_Builders_Register_Strings {
/**
* @param array $data_array
* @param array $package
*/
protected function register_strings_for_modules( array $data_array, array $package ) {
foreach ( $data_array as $data ) {
if ( is_array( $data ) ) {
$data = $this->sort_modules_before_string_registration( $data );
$this->register_strings_for_modules( $data, $package );
} elseif ( is_object( $data ) ) {
if ( isset( $data->type, $data->node, $data->settings ) && 'module' === $data->type && ! $this->is_embedded_global_module( $data ) ) {
$this->register_strings_for_node( $data->node, $data->settings, $package );
}
}
}
}
/**
* The modules are not in the order they appear on the page,
* so we need to sort it before to register the strings.
*
* @param array $modules
*
* @return array
*/
private function sort_modules_before_string_registration( array $modules ) {
if ( count( $modules ) > 1 ) {
uasort( $modules, array( $this, 'sort_modules_by_position_only' ) );
return $this->sort_modules_by_parent_and_child( $modules );
}
return $modules;
}
/**
* We receive all modules as a flat tree and we need to reorder from:
* - child A
* - child A
* - parent A
* - child B
* - parent B
* - child B
* - child C
*
* To:
* - parent A
* - child A
* - child B
* - parent B
* - child A
* - child B
* - child C
*
* The relative positions are already sorted by `sort_modules_by_position_only`
*
* @param array $all_modules
* @param string|null $parent_hash
* @param array $sorted_modules
*
* @return array
*/
private function sort_modules_by_parent_and_child( array $all_modules, $parent_hash = null, array $sorted_modules = array() ){
foreach ( $all_modules as $hash => $module ) {
if ( $module->parent === $parent_hash ) {
$sorted_modules[ $hash ] = $module;
unset( $all_modules[ $hash ] );
$sorted_modules = $this->sort_modules_by_parent_and_child( $all_modules, $module->node, $sorted_modules );
}
}
return $sorted_modules;
}
/**
* @param stdClass $a
* @param stdClass $b
*
* @return int
*/
private function sort_modules_by_position_only( stdClass $a, stdClass $b ) {
return ( (int) $a->position < (int) $b->position ) ? -1 : 1;
}
/**
* @param object $data
*
* @return bool
*/
private function is_embedded_global_module( $data ) {
return ! empty( $data->template_node_id ) && isset( $data->node ) && $data->template_node_id !== $data->node;
}
}
<?php
/**
* Class WPML_Beaver_Builder_Update_Translation
*/
class WPML_Beaver_Builder_Update_Translation extends WPML_Page_Builders_Update_Translation {
/** @param array $data_array */
public function update_strings_in_modules( array &$data_array ) {
foreach ( $data_array as &$data ) {
if ( is_array( $data ) ) {
$this->update_strings_in_modules( $data );
} elseif ( is_object( $data ) ) {
if ( isset( $data->type, $data->node, $data->settings ) && 'module' === $data->type ) {
$data->settings = $this->update_strings_in_node( $data->node, $data->settings );
}
}
}
}
/**
* @param string $node_id
* @param array $settings
*
* @return mixed
*/
public function update_strings_in_node( $node_id, $settings ) {
$strings = $this->translatable_nodes->get( $node_id, $settings );
foreach ( $strings as $string ) {
$translation = $this->get_translation( $string );
$settings = $this->translatable_nodes->update( $node_id, $settings, $translation );
}
return $settings;
}
}
\ No newline at end of file
<?php
class WPML_Beaver_Builder_Media_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_Page_Builders_Media_Hooks(
new WPML_Beaver_Builder_Update_Media_Factory(),
WPML_Beaver_Builder_Integration_Factory::SLUG
);
}
}
<?php
class WPML_Beaver_Builder_Media_Node_Provider {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
private $media_translate;
/** @var WPML_Beaver_Builder_Media_Node[] */
private $nodes = array();
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param string $type
*
* @return WPML_Beaver_Builder_Media_Node|null
*/
public function get( $type ) {
if ( ! array_key_exists( $type, $this->nodes ) ) {
switch ( $type ) {
case 'photo':
$node = new WPML_Beaver_Builder_Media_Node_Photo( $this->media_translate );
break;
case 'gallery':
$node = new WPML_Beaver_Builder_Media_Node_Gallery( $this->media_translate );
break;
case 'content-slider':
$node = new WPML_Beaver_Builder_Media_Node_Content_Slider( $this->media_translate );
break;
case 'slideshow':
$node = new WPML_Beaver_Builder_Media_Node_Slideshow( $this->media_translate );
break;
default:
$node = null;
}
$this->nodes[ $type ] = $node;
}
return $this->nodes[ $type ];
}
}
<?php
class WPML_Beaver_Builder_Media_Nodes_Iterator implements IWPML_PB_Media_Nodes_Iterator {
/** @var WPML_Beaver_Builder_Media_Node_Provider $node_provider */
private $node_provider;
public function __construct( WPML_Beaver_Builder_Media_Node_Provider $node_provider ) {
$this->node_provider = $node_provider;
}
/**
* @param array $data_array
* @param string $lang
* @param string $source_lang
*
* @return array
*/
public function translate( $data_array, $lang, $source_lang ) {
foreach ( $data_array as &$data ) {
if ( is_array( $data ) ) {
$data = $this->translate( $data, $lang, $source_lang );
} elseif ( is_object( $data )
&& isset( $data->type ) && 'module' === $data->type
&& isset( $data->settings ) && isset( $data->settings->type )
) {
$data->settings = $this->translate_node( $data->settings, $lang, $source_lang );
}
}
return $data_array;
}
/**
* @param stdClass $settings
* @param string $lang
* @param string $source_lang
*
* @return stdClass
*/
private function translate_node( $settings, $lang, $source_lang ) {
$node = $this->node_provider->get( $settings->type );
if ( $node ) {
$settings = $node->translate( $settings, $lang, $source_lang );
}
return $settings;
}
}
<?php
class WPML_Beaver_Builder_Update_Media_Factory implements IWPML_PB_Media_Update_Factory {
public function create() {
global $sitepress;
$media_translate = new WPML_Page_Builders_Media_Translate(
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Media_Image_Translate( $sitepress, new WPML_Media_Attachment_By_URL_Factory(), new \WPML\Media\Factories\WPML_Media_Attachment_By_URL_Query_Factory() )
);
return new WPML_Page_Builders_Update_Media(
new WPML_Page_Builders_Update( new WPML_Beaver_Builder_Data_Settings_For_Media() ),
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Beaver_Builder_Media_Nodes_Iterator(
new WPML_Beaver_Builder_Media_Node_Provider( $media_translate )
),
new WPML_Page_Builders_Media_Usage( $media_translate, new WPML_Media_Usage_Factory() )
);
}
}
<?php
class WPML_Beaver_Builder_Media_Node_Content_Slider extends WPML_Beaver_Builder_Media_Node {
private $property_prefixes = array(
'bg_', // i.e. `bg_photo` for an ID or `bg_photo_src` for a URL
'fg_',
'r_',
);
public function translate( $node_data, $target_lang, $source_lang ) {
if ( ! isset( $node_data->slides ) || ! is_array( $node_data->slides ) ) {
return $node_data;
}
foreach ( $node_data->slides as &$slide ) {
foreach ( $this->property_prefixes as $prefix ) {
$id_prop = $prefix . 'photo';
$src_prop = $prefix . 'photo_src';
if ( isset( $slide->{$id_prop} ) && $slide->{$id_prop} ) {
$slide->{$id_prop} = $this->media_translate->translate_id( $slide->{$id_prop}, $target_lang );
}
if ( isset( $slide->{$src_prop} ) && $slide->{$src_prop} ) {
$slide->{$src_prop} = $this->media_translate->translate_image_url( $slide->{$src_prop}, $target_lang, $source_lang );
}
}
}
return $node_data;
}
}
<?php
class WPML_Beaver_Builder_Media_Node_Gallery extends WPML_Beaver_Builder_Media_Node {
public function translate( $node_data, $target_lang, $source_lang ) {
foreach ( $node_data->photos as &$photo ) {
$photo = $this->media_translate->translate_id( $photo, $target_lang );
}
foreach ( $node_data->photo_data as &$photo_data ) {
$translated_id = $this->media_translate->translate_id( $photo_data->id, $target_lang );
if ( $translated_id !== $photo_data->id ) {
$translation_data = wp_prepare_attachment_for_js( $translated_id );
$photo_data->id = $translated_id;
$photo_data->alt = $translation_data['alt'];
$photo_data->caption = $translation_data['caption'];
$photo_data->description = $translation_data['description'];
$photo_data->title = $translation_data['title'];
$photo_data->src = $translation_data['url'];
$photo_data->link = $translation_data['url'];
}
}
return $node_data;
}
}
<?php
class WPML_Beaver_Builder_Media_Node_Photo extends WPML_Beaver_Builder_Media_Node {
public function translate( $node_data, $target_lang, $source_lang ) {
$translated_id = $this->media_translate->translate_id( $node_data->photo, $target_lang );
if ( $translated_id !== $node_data->photo ) {
$node_data->photo = $translated_id;
$node_data->photo_src = $this->media_translate->translate_image_url( $node_data->photo_src, $target_lang, $source_lang );
$node_data->data = wp_prepare_attachment_for_js( $translated_id );
}
return $node_data;
}
}
<?php
/**
* @group media
*/
class WPML_Beaver_Builder_Media_Node_Slideshow extends WPML_Beaver_Builder_Media_Node {
private $url_properties = array(
'largeURL',
'x3largeURL',
'thumbURL',
);
public function translate( $node_data, $target_lang, $source_lang ) {
if ( ! isset( $node_data->photos, $node_data->photo_data ) || ! is_array( $node_data->photos ) ) {
return $node_data;
}
foreach ( $node_data->photos as &$photo ) {
$photo = $this->media_translate->translate_id( $photo, $target_lang );
}
foreach ( $node_data->photo_data as $photo_id => $photo_data ) {
$translated_id = $this->media_translate->translate_id( $photo_id, $target_lang );
if ( $translated_id !== $photo_id ) {
$translation_data = wp_prepare_attachment_for_js( $translated_id );
$photo_data->caption = $translation_data['caption'];
foreach ( $this->url_properties as $property ) {
if ( isset( $photo_data->{$property} ) && $photo_data->{$property} ) {
$photo_data->{$property} = $this->media_translate->translate_image_url( $photo_data->{$property}, $target_lang, $source_lang );
}
}
$node_data->photo_data[ $translated_id ] = $photo_data;
unset( $node_data->photo_data[ $photo_id ] );
}
}
return $node_data;
}
}
<?php
abstract class WPML_Beaver_Builder_Media_Node {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
protected $media_translate;
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
abstract function translate( $node_data, $target_lang, $source_lang );
}
\ No newline at end of file
<?php
namespace WPML\PB\BeaverBuilder\Modules;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
class ModuleWithItemsFromConfig extends \WPML_Beaver_Builder_Module_With_Items {
/** @var array $fieldDefinitions */
private $fieldDefinitions = [];
/** @var string $itemsField */
private $itemsField;
/**
* @param string $itemsField
* @param array $config
*/
public function __construct( $itemsField, array $config ) {
$this->itemsField = $itemsField;
$this->init( $config );
}
private function init( array $config ) {
$keyByField = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'field' ), Fns::identity() ] );
$this->fieldDefinitions = $keyByField( $config );
}
/**
* @inheritDoc
*/
public function get_title( $field ) {
return Obj::path( [ $field, 'type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_fields() {
return array_keys( $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_editor_type( $field ) {
return Obj::path( [ $field, 'editor_type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function &get_items( $settings ) {
return $settings->{$this->itemsField};
}
}
<?php
class WPML_Beaver_Builder_Accordion extends WPML_Beaver_Builder_Module_With_Items {
protected function get_title( $field ) {
switch( $field ) {
case 'label':
return esc_html__( 'Accordion Item Label', 'sitepress' );
case 'content':
return esc_html__( 'Accordion Item Content', 'sitepress' );
default:
return '';
}
}
}
<?php
class WPML_Beaver_Builder_Content_Slider extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->slides;
}
public function get_fields() {
return array( 'title', 'text', 'cta_text', 'link' );
}
protected function get_title( $field ) {
switch( $field ) {
case 'title':
return esc_html__( 'Content Slider: Slide heading', 'sitepress' );
case 'text':
return esc_html__( 'Content Slider: Slide content', 'sitepress' );
case 'cta_text':
return esc_html__( 'Content Slider: Slide call to action text', 'sitepress' );
case 'link':
return esc_html__( 'Content Slider: Slide call to action link', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch( $field ) {
case 'title':
case 'cta_text':
return 'LINE';
case 'link':
return 'LINK';
case 'text':
return 'VISUAL';
default:
return '';
}
}
}
<?php
class WPML_Beaver_Builder_Icon_Group extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->icons;
}
public function get_fields() {
return array( 'link' );
}
protected function get_title( $field ) {
switch ( $field ) {
case 'link':
return esc_html__( 'Icon link', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch ( $field ) {
case 'link':
return 'LINK';
default:
return '';
}
}
}
<?php
/**
* Class WPML_Beaver_Builder_Module_With_Items
*/
abstract class WPML_Beaver_Builder_Module_With_Items implements IWPML_Page_Builders_Module {
/**
* @param string $field
*
* @return string
*/
abstract protected function get_title( $field );
/** @return array */
protected function get_fields() {
return array( 'label', 'content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
switch( $field ) {
case 'label':
return 'LINE';
case 'content':
return 'VISUAL';
default:
return '';
}
}
/**
* @param object $settings
*
* @return array
*/
protected function &get_items( $settings ) {
return $settings->items;
}
/**
* @param string|int $node_id
* @param object $settings
* @param WPML_PB_String[] $strings
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings, $strings ) {
foreach ( $this->get_items( $settings ) as $item ) {
foreach( $this->get_fields() as $field ) {
if ( is_array( $item->$field ) ) {
foreach ( $item->$field as $key => $value ) {
$strings[] = new WPML_PB_String(
$value,
$this->get_string_name( $node_id, $value, $field, $key ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
} else {
$strings[] = new WPML_PB_String(
$item->$field,
$this->get_string_name( $node_id, $item->$field, $field ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
}
}
return $strings;
}
/**
* @param string|int $node_id
* @param object $settings
* @param WPML_PB_String $string
*
* @return null
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
foreach ( $this->get_items( $settings ) as &$item ) {
foreach( $this->get_fields() as $field ) {
if ( is_array( $item->$field ) ) {
foreach ( $item->$field as $key => &$value ) {
if ( $this->get_string_name( $node_id, $value, $field, $key ) == $string->get_name() ) {
$value = $string->get_value();
}
}
} else {
if ( $this->get_string_name( $node_id, $item->$field, $field ) == $string->get_name() ) {
$item->$field = $string->get_value();
}
}
}
}
return null;
}
private function get_string_name( $node_id, $value, $type, $key = '' ) {
return md5( $value ) . '-' . $type . $key . '-' . $node_id;
}
}
\ No newline at end of file
<?php
class WPML_Beaver_Builder_Pricing_Table extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->pricing_columns;
}
public function get_fields() {
return array( 'title', 'button_text', 'button_url', 'features', 'price', 'duration' );
}
protected function get_title( $field ) {
switch ( $field ) {
case 'title':
return esc_html__( 'Pricing table: Title', 'sitepress' );
case 'button_text':
return esc_html__( 'Pricing table: Button text', 'sitepress' );
case 'button_url':
return esc_html__( 'Pricing table: Button link', 'sitepress' );
case 'features':
return esc_html__( 'Pricing table: Feature', 'sitepress' );
case 'price':
return esc_html__( 'Pricing table: Price', 'sitepress' );
case 'duration':
return esc_html__( 'Pricing table: Duration', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch ( $field ) {
case 'title':
case 'button_text':
case 'price':
case 'duration':
return 'LINE';
case 'button_url':
return 'LINK';
case 'features':
return 'VISUAL';
default:
return '';
}
}
}
<?php
class WPML_Beaver_Builder_Tab extends WPML_Beaver_Builder_Module_With_Items {
protected function get_title( $field ) {
switch( $field ) {
case 'label':
return esc_html__( 'Tab Item Label', 'sitepress' );
case 'content':
return esc_html__( 'Tab Item Content', 'sitepress' );
default:
return '';
}
}
}
<?php
class WPML_Beaver_Builder_Testimonials extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->testimonials;
}
public function get_fields() {
return array( 'testimonial' );
}
protected function get_title( $field ) {
switch( $field ) {
case 'testimonial':
return esc_html__( 'Testimonial content', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch( $field ) {
case 'testimonial':
return 'VISUAL';
default:
return '';
}
}
}
<?php
namespace WPML\PB\Cornerstone\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'cornerstone-widgets',
'defaultConditionKey' => '_type',
'pbKey' => 'cornerstone',
'translatableWidgetsHook' => 'wpml_cornerstone_modules_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}
<?php
namespace WPML\PB\Cornerstone\Hooks;
use WPML\FP\Cast;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Str;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Frontend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Str::includes( 'themeco/data/save', Obj::prop( 'REQUEST_URI', $_SERVER ) )
&& self::getEditedId() === $translatedPostId
);
} ) );
}
/**
* @return int|null
*/
private static function getEditedId() {
/**
* @see \Cornerstone_Routing::process_params
* $decodeCornerstoneData :: string -> array
*/
$decodeCornerstoneData = function( $data ) {
$request = Obj::prop( 'request', $data );
if ( Obj::prop( 'gzip', $data ) ) {
return (array) json_decode( gzdecode( base64_decode( $request, true ) ), true );
}
return (array) $request;
};
$key = version_compare( constant( 'CS_VERSION' ), '7.1.3', '>=' ) ? 'document' : 'builder';
$getId = Obj::path( [ 'requests', $key, 'id' ] );
return Maybe::fromNullable( \WP_REST_Server::get_raw_data() )
->map( 'json_decode' )
->map( $decodeCornerstoneData )
->map( $getId )
->map( Cast::toInt() )
->getOrElse( null );
}
}
<?php
namespace WPML\PB\Cornerstone\Styles;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Maybe;
use WPML\LIB\WP\Post;
class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
const META_KEY_OLD = '_cs_generated_styles';
const META_KEY_V6 = '_cs_generated_tss';
/** @var callable $shouldInvalidateStyle */
private $shouldInvalidateStyles;
/**
* Hooks constructor.
*
* @param \WPML_PB_Last_Translation_Edit_Mode $lastEditMode
* @param \WPML_Cornerstone_Data_Settings $dataSettings
*/
public function __construct(
\WPML_PB_Last_Translation_Edit_Mode $lastEditMode,
\WPML_Cornerstone_Data_Settings $dataSettings
) {
$this->shouldInvalidateStyles = Logic::both( [ $dataSettings, 'is_handling_post' ], [ $lastEditMode, 'is_translation_editor' ] );
}
public function add_hooks() {
add_action( 'save_post', [ $this, 'invalidateStylesInTranslation' ] );
}
/**
* @param int $postId
*/
public function invalidateStylesInTranslation( $postId ) {
Maybe::of( $postId )
->filter( $this->shouldInvalidateStyles )
->map( Fns::tap( Post::deleteMeta( Fns::__, self::META_KEY_V6 ) ) )
->map( Fns::tap( Post::deleteMeta( Fns::__, self::META_KEY_OLD ) ) );
}
}
\ No newline at end of file
<?php
class WPML_Cornerstone_Data_Settings implements IWPML_Page_Builders_Data_Settings {
/**
* @return string
*/
public function get_meta_field() {
return '_cornerstone_data';
}
/**
* @return string
*/
public function get_node_id_field() {
return '_type';
}
/**
* @return array
*/
public function get_fields_to_copy() {
return array( '_cornerstone_settings', '_cornerstone_version', 'post_content' );
}
/**
* @param array $data
*
* @return array
*/
public function convert_data_to_array( $data ) {
$converted_data = $data;
if ( is_array( $data ) ) {
$converted_data = $data[0];
}
return json_decode( $converted_data, true );
}
/**
* @param array $data
*
* @return string
*/
public function prepare_data_for_saving( array $data ) {
return wp_slash( wp_json_encode( $data ) );
}
/**
* @return string
*/
public function get_pb_name() {
return 'Cornerstone';
}
/**
* @return array
*/
public function get_fields_to_save() {
return array( '_cornerstone_data' );
}
public function add_hooks() {
}
/**
* @param int $postId
*
* @return bool
*/
public function is_handling_post( $postId ) {
return get_post_meta( $postId, $this->get_meta_field(), true )
&& ! get_post_meta( $postId, '_cornerstone_override', true );
}
}
\ No newline at end of file
<?php
class WPML_PB_Cornerstone_Handle_Custom_Fields_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_PB_Handle_Custom_Fields( new WPML_Cornerstone_Data_Settings() );
}
}
\ No newline at end of file
<?php
use function WPML\Container\make;
class WPML_Cornerstone_Integration_Factory {
const SLUG = 'cornerstone';
public function create() {
$action_filter_loader = new WPML_Action_Filter_Loader();
$action_filter_loader->load(
[
'WPML_PB_Cornerstone_Handle_Custom_Fields_Factory',
'WPML_Cornerstone_Media_Hooks_Factory',
\WPML\PB\Cornerstone\Config\Factory::class,
\WPML\PB\Cornerstone\Styles\Hooks::class,
\WPML\PB\Cornerstone\Hooks\Editor::class,
]
);
$nodes = new WPML_Cornerstone_Translatable_Nodes();
$data_settings = new WPML_Cornerstone_Data_Settings();
$string_registration_factory = new WPML_String_Registration_Factory( $data_settings->get_pb_name() );
$string_registration = $string_registration_factory->create();
$factory = make( WPML_PB_Factory::class );
$strategy = make( WPML_PB_API_Hooks_Strategy::class, [ ':name' => $data_settings->get_pb_name() ] );
$strategy->set_factory( $factory );
$reuse_translation = make( WPML_PB_Reuse_Translations_By_Strategy::class, [ ':strategy' => $strategy ] );
$register_strings = new WPML_Cornerstone_Register_Strings( $nodes, $data_settings, $string_registration, $reuse_translation );
$update_translation = new WPML_Cornerstone_Update_Translation( $nodes, $data_settings );
return new WPML_Page_Builders_Integration( $register_strings, $update_translation, $data_settings );
}
}
<?php
use WPML\PB\Cornerstone\Utils;
class WPML_Cornerstone_Register_Strings extends WPML_Page_Builders_Register_Strings {
/**
* @param array $data_array
* @param array $package
*/
protected function register_strings_for_modules( array $data_array, array $package ) {
foreach ( $data_array as $data ) {
if ( isset( $data['_type'] ) && ! Utils::typeIsLayout( $data['_type'] ) ) {
$this->register_strings_for_node( Utils::getNodeId( $data ), $data, $package );
} elseif ( is_array( $data ) ) {
$this->register_strings_for_modules( $data, $package );
}
}
}
}
<?php
use WPML\PB\Cornerstone\Utils;
class WPML_Cornerstone_Update_Translation extends WPML_Page_Builders_Update_Translation {
/** @param array $data_array */
public function update_strings_in_modules( array &$data_array ) {
foreach ( $data_array as $key => &$data ) {
if ( isset( $data['_type'] ) && ! Utils::typeIsLayout( $data['_type'] ) ) {
$data = $this->update_strings_in_node( Utils::getNodeId( $data ), $data );
} elseif ( is_array( $data ) ) {
$this->update_strings_in_modules( $data );
}
}
}
/**
* @param string $node_id
* @param array $settings
*
* @return mixed
*/
protected function update_strings_in_node( $node_id, $settings ) {
$strings = $this->translatable_nodes->get( $node_id, $settings );
foreach ( $strings as $string ) {
$translation = $this->get_translation( $string );
$settings = $this->translatable_nodes->update( $node_id, $settings, $translation );
}
return $settings;
}
}
<?php
namespace WPML\PB\Cornerstone;
class Utils {
const MODULE_TYPE_PREFIX = 'classic:';
const LAYOUT_TYPES = [
'bar',
'container',
'section',
'row',
'column',
'layout-row',
'layout-column',
'layout-grid',
'layout-cell',
'layout-div',
];
/**
* @param array $data
* @return string
*/
public static function getNodeId( $data ) {
return md5( serialize( $data ) );
}
/**
* Check if the type is a layout type.
*
* @param string $type The type to check.
* @return bool
*/
public static function typeIsLayout( $type ) {
// Remove the classic prefix before checking.
$type = preg_replace( '/^' . self::MODULE_TYPE_PREFIX . '/', '', $type );
return in_array( $type, self::LAYOUT_TYPES, true );
}
}
<?php
class WPML_Cornerstone_Media_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_Page_Builders_Media_Hooks(
new WPML_Cornerstone_Update_Media_Factory(),
WPML_Cornerstone_Integration_Factory::SLUG
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Provider {
private $media_translate;
private $nodes = array();
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param string $type
*
* @return WPML_Cornerstone_Media_Node|null
*/
public function get( $type ) {
if ( ! array_key_exists( $type, $this->nodes ) ) {
$this->add( $type );
}
return $this->nodes[ $type ];
}
/**
* @param string $type
*/
private function add( $type ) {
switch ( $type ) {
case 'image':
$node = new WPML_Cornerstone_Media_Node_Image( $this->media_translate );
break;
case 'classic:creative-cta':
$node = new WPML_Cornerstone_Media_Node_Classic_Creative_CTA( $this->media_translate );
break;
case 'classic:feature-box':
$node = new WPML_Cornerstone_Media_Node_Classic_Feature_Box( $this->media_translate );
break;
case 'classic:card':
$node = new WPML_Cornerstone_Media_Node_Classic_Card( $this->media_translate );
break;
case 'classic:image':
$node = new WPML_Cornerstone_Media_Node_Classic_Image( $this->media_translate );
break;
case 'classic:promo':
$node = new WPML_Cornerstone_Media_Node_Classic_Promo( $this->media_translate );
break;
default:
$node = null;
}
$this->nodes[ $type ] = $node;
}
}
<?php
class WPML_Cornerstone_Media_Nodes_Iterator implements IWPML_PB_Media_Nodes_Iterator {
const ITEMS_FIELD = WPML_Cornerstone_Module_With_Items::ITEMS_FIELD;
/** @var WPML_Cornerstone_Media_Node_Provider $node_provider */
private $node_provider;
public function __construct( WPML_Cornerstone_Media_Node_Provider $node_provider ) {
$this->node_provider = $node_provider;
}
/**
* @param array $data_array
* @param string $lang
* @param string $source_lang
*
* @return array
*/
public function translate( $data_array, $lang, $source_lang ) {
foreach ( $data_array as $key => &$data ) {
if ( isset( $data[ self::ITEMS_FIELD ] ) && $data[ self::ITEMS_FIELD ] ) {
$data[ self::ITEMS_FIELD ] = $this->translate( $data[ self::ITEMS_FIELD ], $lang, $source_lang );
} elseif ( is_numeric( $key ) && isset( $data['_type'] ) ) {
$data = $this->translate_node( $data, $lang, $source_lang );
}
}
return $data_array;
}
/**
* @param stdClass $settings
* @param string $lang
* @param string $source_lang
*
* @return stdClass
*/
private function translate_node( $settings, $lang, $source_lang ) {
$node = $this->node_provider->get( $settings['_type'] );
if ( $node ) {
$settings = $node->translate( $settings, $lang, $source_lang );
}
return $settings;
}
}
<?php
class WPML_Cornerstone_Update_Media_Factory implements IWPML_PB_Media_Update_Factory {
/** @var WPML_Page_Builders_Media_Translate|null $media_translate */
private $media_translate;
public function create() {
global $sitepress;
return new WPML_Page_Builders_Update_Media(
new WPML_Page_Builders_Update( new WPML_Cornerstone_Data_Settings() ),
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Cornerstone_Media_Nodes_Iterator(
new WPML_Cornerstone_Media_Node_Provider( $this->get_media_translate() )
),
new WPML_Page_Builders_Media_Usage( $this->get_media_translate(), new WPML_Media_Usage_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(
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Media_Image_Translate( $sitepress, new WPML_Media_Attachment_By_URL_Factory(), new \WPML\Media\Factories\WPML_Media_Attachment_By_URL_Query_Factory() )
);
}
return $this->media_translate;
}
}
<?php
abstract class WPML_Cornerstone_Media_Node_With_URLs extends WPML_Cornerstone_Media_Node {
/** @return array */
abstract protected function get_keys();
/**
* @param array $node_data
* @param string $target_lang
* @param string $source_lang
*
* @return array
*/
public function translate( $node_data, $target_lang, $source_lang ) {
foreach ( $this->get_keys() as $key ) {
if ( ! empty( $node_data[ $key ] ) ) {
list( $attachment_id, $type ) = explode( ':', $node_data[ $key ], 2 );
if ( is_numeric( $attachment_id ) ) {
$attachment_id = apply_filters( 'wpml_object_id', $attachment_id, 'attachment', true, $target_lang );
$node_data[ $key ] = $attachment_id . ':' . $type;
} else {
$node_data[ $key ] = $this->media_translate->translate_image_url( $node_data[ $key ], $target_lang, $source_lang );
}
}
}
return $node_data;
}
}
<?php
abstract class WPML_Cornerstone_Media_Node {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
protected $media_translate;
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param array $node_data
* @param string $target_lang
* @param string $source_lang
*
* @return array
*/
abstract function translate( $node_data, $target_lang, $source_lang );
}
\ No newline at end of file
<?php
class WPML_Cornerstone_Media_Node_Classic_Card extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'front_image',
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Classic_Creative_CTA extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image',
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Classic_Feature_Box extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'graphic_image',
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Classic_Image extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'src',
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Classic_Promo extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image',
);
}
}
<?php
class WPML_Cornerstone_Media_Node_Image extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image_src',
);
}
}
<?php
namespace WPML\PB\Cornerstone\Modules;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
class ModuleWithItemsFromConfig extends \WPML_Cornerstone_Module_With_Items {
/** @var array $fieldDefinitions */
private $fieldDefinitions;
public function __construct( array $config ) {
$keyByField = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'field' ), Fns::identity() ] );
$this->fieldDefinitions = $keyByField( $config );
}
/**
* @inheritDoc
*/
public function get_title( $field ) {
return Obj::path( [ $field, 'type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_fields() {
return array_keys( $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_editor_type( $field ) {
return Obj::path( [ $field, 'editor_type' ], $this->fieldDefinitions );
}
}
<?php
class WPML_Cornerstone_Accordion extends WPML_Cornerstone_Module_With_Items {
/**
* @return array
*/
public function get_fields() {
return array( 'accordion_item_header_content', 'accordion_item_content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_title( $field ) {
if ( 'accordion_item_header_content' === $field ) {
return esc_html__( 'Accordion: header content', 'sitepress' );
}
if ( 'accordion_item_content' === $field ) {
return esc_html__( 'Accordion: content', 'sitepress' );
}
return '';
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
if ( 'accordion_item_header_content' === $field ) {
return 'LINE';
} else {
return 'VISUAL';
}
}
}
\ No newline at end of file
<?php
/**
* Class WPML_Cornerstone_Module_With_Items
*/
abstract class WPML_Cornerstone_Module_With_Items implements IWPML_Page_Builders_Module {
const ITEMS_FIELD = WPML_Cornerstone_Translatable_Nodes::SETTINGS_FIELD;
/**
* @param string $field
*
* @return string
*/
abstract protected function get_title( $field );
/** @return array */
abstract protected function get_fields();
/**
* @param string $field
*
* @return string
*/
abstract protected function get_editor_type( $field );
/**
* @param array $settings
*
* @return array
*/
protected function get_items( $settings ) {
return $settings[ self::ITEMS_FIELD ];
}
/**
* @param string|int $node_id
* @param array $settings
* @param WPML_PB_String[] $strings
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings, $strings ) {
foreach ( $this->get_items( $settings ) as $item ) {
foreach ( $this->get_fields() as $field ) {
if ( is_array( $item[ $field ] ) ) {
foreach ( $item[ $field ] as $key => $value ) {
$strings[] = new WPML_PB_String(
$value,
$this->get_string_name( $node_id, $value, $field, $key ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
} else {
$strings[] = new WPML_PB_String(
$item[ $field ],
$this->get_string_name( $node_id, $item[ $field ], $field ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
}
}
return $strings;
}
/**
* @param string|int $node_id
* @param array $settings
* @param WPML_PB_String $string
*
* @return array
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
foreach ( $this->get_items( $settings ) as $key => $item ) {
foreach ( $this->get_fields() as $field ) {
if ( $this->get_string_name( $node_id, $item[ $field ], $field ) === $string->get_name() ) {
$settings[ self::ITEMS_FIELD ][ $key ][ $field ] = $string->get_value();
}
}
}
return $settings;
}
private function get_string_name( $node_id, $value, $type, $key = '' ) {
return md5( $value ) . '-' . $type . $key . '-' . $node_id;
}
}
\ No newline at end of file
<?php
class WPML_Cornerstone_Tabs extends WPML_Cornerstone_Module_With_Items {
/**
* @return array
*/
public function get_fields() {
return array( 'tab_label_content', 'tab_content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_title( $field ) {
if ( 'tab_label_content' === $field ) {
return esc_html__( 'Tabs: label', 'sitepress' );
}
if ( 'tab_content' === $field ) {
return esc_html__( 'Tabs: content', 'sitepress' );
}
return '';
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
if ( 'tab_label_content' === $field ) {
return 'LINE';
} else {
return 'VISUAL';
}
}
}
\ No newline at end of file
<?php
namespace WPML\Compatibility\Divi;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\FP\Obj;
class DisplayConditions implements \IWPML_Frontend_Action {
const BASE64_EMPTY_ARRAY = 'W10=';
public function add_hooks() {
Hooks::onFilter( 'et_pb_module_shortcode_attributes' )
->then( spreadArgs( [ $this, 'translateAttributes' ] ) );
}
/**
* @param array $atts
* @return array
*/
public function translateAttributes( $atts ) {
$displayConditions = Obj::prop( 'display_conditions', $atts );
if ( $displayConditions && self::BASE64_EMPTY_ARRAY !== $displayConditions ) {
/* phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode */
$conditions = json_decode( base64_decode( $atts['display_conditions'] ), true );
foreach ( $conditions as &$condition ) {
if ( 'categoryPage' === $condition['condition'] ) {
foreach ( $condition['conditionSettings']['categories'] as &$category ) {
$category['value'] = (string) apply_filters( 'wpml_object_id', $category['value'], $category['groupSlug'] );
}
}
}
/* phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode */
$atts['display_conditions'] = base64_encode( wp_json_encode( $conditions ) );
}
return $atts;
}
}
<?php
namespace WPML\Compatibility\Divi;
/**
* Divi replaces double quotes with %22 when saving shortcode attributes.
* ATE needs valid HTML so we temporarily decode the double quotes.
* When we receive the translation we undo the change.
*
* @package WPML\Compatibility\Divi
*/
class DoubleQuotes implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
public function add_hooks() {
add_filter( 'wpml_pb_shortcode_decode', [ $this, 'decode' ], -PHP_INT_MAX, 2 );
add_filter( 'wpml_pb_shortcode_encode', [ $this, 'encode' ], PHP_INT_MAX, 2 );
}
/**
* @param string $string
* @param string $encoding
*
* @return string
*/
public function decode( $string, $encoding ) {
if ( self::canHaveDoubleQuotes( $string, $encoding ) ) {
$string = str_replace( '%22', '"', $string );
}
return $string;
}
/**
* @param string $string
* @param string $encoding
*
* @return string
*/
public function encode( $string, $encoding ) {
if ( self::canHaveDoubleQuotes( $string, $encoding ) ) {
$string = str_replace( '"', '%22', $string );
}
return $string;
}
/**
* @param string $string
* @param string $encoding
*
* @return bool
*/
private static function canHaveDoubleQuotes( $string, $encoding ) {
return is_string( $string ) && 'allow_html_tags' === $encoding;
}
}
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\PB\Helper\LanguageNegotiation;
class DomainsBackendEditor implements \IWPML_Backend_Action {
public function add_hooks() {
if ( LanguageNegotiation::isUsingDomains()
&& self::isPostEditor()
&& self::getDomainByCurrentPostLanguage() !== $_SERVER['HTTP_HOST']
) {
Hooks::onAction( 'admin_notices' )
->then( spreadArgs( [ $this, 'displayNotice' ] ) );
}
}
public function displayNotice() {
$url = ( is_ssl() ? 'https://' : 'http://' ) . self::getDomainByCurrentPostLanguage() . $_SERVER['REQUEST_URI'];
?>
<div class="notice notice-warning is-dismissible">
<p>
<?php
echo sprintf(
// translators: placeholders are opening and closing <a> tag.
esc_html__( "It is not possible to use Divi's backend builder to edit a post in a different language than your domain. Please use Divi's frontend builder to edit this post or %1\$s switch to the correct domain %2\$s to use the backend builder.", 'sitepress' ),
sprintf( '<a href="%s">', esc_url( $url ) ),
'</a>'
);
?>
</p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text">Dismiss this notice.</span>
</button>
</div>
<?php
}
/**
* @return bool
*/
private static function isPostEditor() {
global $pagenow;
return 'post.php' === $pagenow
&& self::getPostId();
}
/**
* @return int
*/
private static function getPostId() {
/* phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification */
return (int) Obj::prop( 'post', $_GET );
}
/**
* @return string|null
*/
private static function getDomainByCurrentPostLanguage() {
$postDetails = apply_filters( 'wpml_post_language_details', null, self::getPostId() );
$language = Obj::prop( 'language_code', $postDetails );
return LanguageNegotiation::getDomainByLanguage( $language );
}
}
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Relation::propEq( 'action', 'et_fb_ajax_save', $_POST )
&& (int) Obj::prop( 'post_id', $_POST ) === $translatedPostId
);
} ) );
}
}
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class GutenbergUpdate implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_post_built_with_shortcodes', 10, 2 )
->then( spreadArgs( [ $this, 'isPostBuiltWithShortcodes' ] ) );
}
/**
* @param string $builtWithShortcodes
* @param \WP_Post $post
*
* @return bool
*/
public static function isPostBuiltWithShortcodes( $builtWithShortcodes, $post ) {
return self::isDiviPost( $post->ID ) || $builtWithShortcodes;
}
/**
* @param int $postId
*
* @return bool
*/
private static function isDiviPost( $postId ) {
return 'on' === get_post_meta( $postId, '_et_pb_use_builder', true );
}
}
<?php
namespace WPML\Compatibility\Divi;
use WPML\FP\Obj;
class TinyMCE implements \IWPML_Backend_Action {
public function add_hooks() {
if ( defined( 'WPML_TM_FOLDER' ) ) {
add_filter( 'tiny_mce_before_init', [ $this, 'filterEditorAutoTags' ] );
}
}
/**
* @param array $config
*
* @return array
*/
public function filterEditorAutoTags( $config ) {
if ( did_action( 'admin_init' ) ) {
$screen = get_current_screen();
$cteUrl = 'wpml_page_' . constant( 'WPML_TM_FOLDER' ) . '/menu/translations-queue';
if ( Obj::prop( 'id', $screen ) === $cteUrl ) {
$config['wpautop'] = false;
$config['indent'] = true;
$config['tadv_noautop'] = true;
}
}
return $config;
}
}
<?php
namespace WPML\Compatibility\Divi;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\FP\Obj;
class WooShortcodes implements \IWPML_Frontend_Action {
const WOO_SHORTCODES = [
'et_pb_wc_description',
'et_pb_wc_title',
];
public function add_hooks() {
Hooks::onFilter( 'et_pb_module_shortcode_attributes', 10, 3 )
->then( spreadArgs( [ $this, 'translateAttributes' ] ) );
}
/**
* @param array $shortcodeAttrs
* @param array $attrs
* @param string $slug
*
* @return array
*/
public function translateAttributes( $shortcodeAttrs, $attrs, $slug ) {
if ( in_array( $slug, self::WOO_SHORTCODES, true ) && (int) Obj::prop( 'product', $shortcodeAttrs ) ) {
$shortcodeAttrs['product'] = apply_filters( 'wpml_object_id', $shortcodeAttrs['product'], 'product', true );
}
return $shortcodeAttrs;
}
}
<?php
namespace WPML\Compatibility\Divi;
class Builder implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_AJAX_Action {
public function add_hooks() {
add_filter( 'theme_locale', [ $this, 'switch_to_user_language' ] );
}
public function switch_to_user_language( $locale ) {
if ( isset( $_POST['action'] ) && ( 'et_fb_update_builder_assets' === $_POST['action'] ) ) { // phpcs:ignore WordPress.CSRF.NonceVerification
return get_user_locale();
}
return $locale;
}
}
<?php
class WPML_Compatibility_Divi implements \IWPML_DIC_Action, \IWPML_Backend_Action, \IWPML_Frontend_Action {
const REGEX_REMOVE_OPENING_PARAGRAPH = '/(<p>[\n\r]*)([\n\r]{1}\[\/et_)/m';
const REGEX_REMOVE_CLOSING_PARAGRAPH = '/(\[et_.*\][\n\r]{1})([\n\r]*<\/p>)/m';
/** @var SitePress */
private $sitepress;
/**
* @param SitePress $sitepress
*/
public function __construct( SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
public function add_hooks() {
if ( $this->sitepress->is_setup_complete() ) {
add_action( 'init', [ $this, 'load_resources_if_they_are_required' ], 10, 0 );
add_filter( 'et_builder_load_actions', [ $this, 'load_builder_for_ajax_actions' ] );
add_action( 'admin_init', [ $this, 'display_warning_notice' ], 10, 0 );
add_filter( 'wpml_pb_should_handle_content', [ $this, 'should_handle_shortcode_content' ], 10, 2 );
add_filter( 'wpml_pb_shortcode_content_for_translation', [ $this, 'cleanup_global_layout_content' ], 10, 2 );
add_filter( 'icl_job_elements', [ $this, 'remove_old_content_from_translation' ], 10, 2 );
add_filter( 'wpml_words_count_custom_fields_to_count', [ $this, 'remove_old_content_from_words_count' ], 10, 2 );
}
}
/**
* @return bool
*/
private function is_standard_editor_used() {
$tm_settings = $this->sitepress->get_setting( 'translation-management', [] );
return ! isset( $tm_settings['doc_translation_method'] ) ||
ICL_TM_TMETHOD_MANUAL === $tm_settings['doc_translation_method'];
}
public function display_warning_notice() {
$notices = wpml_get_admin_notices();
if ( $this->is_standard_editor_used() ) {
$notices->add_notice( new WPML_Compatibility_Divi_Notice() );
} elseif ( $notices->get_notice( WPML_Compatibility_Divi_Notice::ID, WPML_Compatibility_Divi_Notice::GROUP ) ) {
$notices->remove_notice( WPML_Compatibility_Divi_Notice::GROUP, WPML_Compatibility_Divi_Notice::ID );
}
}
/**
* These actions require the custom widget area to be initialized.
*
* @param array $actions
* @return array
*/
public function load_builder_for_ajax_actions( $actions ) {
$actions[] = 'save-widget';
$actions[] = 'widgets-order';
$actions[] = 'wpml-ls-save-settings';
return $actions;
}
public function load_resources_if_they_are_required() {
if ( ! isset( $_GET['page'] ) || ! is_admin() ) { /* phpcs:ignore */
return;
}
$pages = [ self::get_duplication_action_page() ];
if ( self::is_tm_active() ) {
$pages[] = self::get_translation_dashboard_page();
$pages[] = self::get_translation_editor_page();
}
if ( self::is_sl_active() ) {
$pages[] = self::get_sl_page();
}
if ( in_array( $_GET['page'], $pages, true ) ) { /* phpcs:ignore */
$this->register_layouts();
}
}
private static function get_translation_dashboard_page() {
return constant( 'WPML_TM_FOLDER' ) . '/menu/main.php';
}
private static function get_translation_editor_page() {
return constant( 'WPML_TM_FOLDER' ) . '/menu/translations-queue.php';
}
private static function get_duplication_action_page() {
return constant( 'WPML_PLUGIN_FOLDER' ) . '/menu/languages.php';
}
private static function get_sl_page() {
return 'wpml-sticky-links';
}
private static function is_tm_active() {
return defined( 'WPML_TM_FOLDER' );
}
private static function is_sl_active() {
return defined( 'WPML_STICKY_LINKS_VERSION' );
}
private function register_layouts() {
/**
* @phpstan-ignore-next-line
*/
if ( function_exists( 'et_builder_should_load_framework' ) && ! et_builder_should_load_framework() ) {
if ( function_exists( 'et_builder_register_layouts' ) ) {
/**
* @phpstan-ignore-next-line
*/
et_builder_register_layouts();
} else {
$lib_file = ET_BUILDER_DIR . 'feature/Library.php';
if ( ! class_exists( 'ET_Builder_Library' )
&& defined( 'ET_BUILDER_DIR' )
&& file_exists( $lib_file )
) {
require_once $lib_file;
}
if ( class_exists( 'ET_Builder_Library' ) ) {
ET_Builder_Library::instance();
}
}
}
}
/**
* The global layout is not properly extracted from the page
* because it adds <p> tags either not opened or not closed.
*
* See the global content below as an example:
*
* [et_pb_section prev_background_color="#000000" next_background_color="#000000"][et_pb_text]
*
* </p>
* <p>Global text 1 EN5</p>
* <p>
*
* [/et_pb_text][/et_pb_section]
*
* We also need to remove `prev_background` and `next_background` attributes which are added from the page.
*
* @param string $content
* @param int $post_id
*/
public function cleanup_global_layout_content( $content, $post_id ) {
if ( 'et_pb_layout' === get_post_type( $post_id ) ) {
$content = preg_replace( self::REGEX_REMOVE_OPENING_PARAGRAPH, '$2', $content );
$content = preg_replace( self::REGEX_REMOVE_CLOSING_PARAGRAPH, '$1', $content );
$content = preg_replace( '/( prev_background_color="#[0-9a-f]*")/', '', $content );
$content = preg_replace( '/( next_background_color="#[0-9a-f]*")/', '', $content );
}
return $content;
}
public function should_handle_shortcode_content( $handle_content, $shortcode ) {
if (
strpos( $shortcode['tag'], 'et_' ) === 0 &&
strpos( $shortcode['attributes'], 'global_module=' ) !== false
) {
// If a translatable attribute has been excluded from sync, we need to handle it.
$handle_content = $this->is_excluded_from_sync( $shortcode );
}
return $handle_content;
}
/**
* Check if a global module has excluded any translatable text that we need to handle
*
* @param array $shortcode
* {
* @type string $tag.
* @type string $content.
* @type string $attributes.
* }
* @return bool
*/
private function is_excluded_from_sync( $shortcode ) {
$handle_content = false;
preg_match( '/global_module="([0-9]+)"/', $shortcode['attributes'], $matches );
$excluded = json_decode( get_post_meta( $matches[1], '_et_pb_excluded_global_options', true ), true );
if ( is_array( $excluded ) && count( $excluded ) > 0 ) {
$attributes = $this->get_translatable_shortcode_attributes( $shortcode['tag'] );
foreach ( $excluded as $field ) {
if ( in_array( $field, $attributes, true ) ) {
$handle_content = true;
break;
}
}
}
return $handle_content;
}
/**
* Get a list of translatable attributes for a shortcode tag.
* This includes the inner content and any attributes found in XML configuration.
*
* @param string $tag The shortcode tag.
* @return array
*/
private function get_translatable_shortcode_attributes( $tag ) {
$attributes = [ 'et_pb_content_field' ];
$settings = get_option( 'icl_st_settings', [] );
if ( ! isset( $settings['pb_shortcode'] ) ) {
return $attributes;
}
foreach ( $settings['pb_shortcode'] as $setting ) {
if ( $tag === $setting['tag']['value'] ) {
foreach ( $setting['attributes'] as $attribute ) {
if ( empty( $attribute['type'] ) ) {
$attributes[] = $attribute['value'];
}
}
break;
}
}
return $attributes;
}
/**
* Remove the `_et_pb_old_content` meta field from translation jobs, except for products.
*
* @param array $fields Array of fields to translate.
* @param object $post_id The ID of the post being translated.
*
* @return array
*/
public function remove_old_content_from_translation( $fields, $post_id ) {
// Bail out early if its a product.
if ( 'product' === get_post_type( $post_id ) ) {
return $fields;
}
// Search for the _et_pb_old_content element and empty it.
$field_types = wp_list_pluck( $fields, 'field_type' );
$index = array_search( 'field-_et_pb_old_content-0', $field_types, true );
if ( false !== $index ) {
$fields[ $index ]->field_data = '';
$fields[ $index ]->field_data_translated = '';
}
return $fields;
}
/**
* Remove the `_et_pb_old_content` meta field from words count, except for products.
*
* @param array $fields_to_count Array of custom fields to count.
* @param object $post_id The ID of the post for which we are counting the words.
*
* @return array
*/
public function remove_old_content_from_words_count( $fields_to_count, $post_id ) {
if ( 'product' !== get_post_type( $post_id ) ) {
$index = array_search( '_et_pb_old_content', $fields_to_count, true );
if ( false !== $index ) {
unset( $fields_to_count[ $index ] );
}
}
return $fields_to_count;
}
}
<?php
class WPML_Compatibility_Divi_Notice extends WPML_Notice {
const ID = 'wpml-compatibility-divi-editor-warning';
const GROUP = 'wpml-compatibility-divi';
public function __construct() {
parent::__construct( self::ID, $this->get_message(), self::GROUP );
$this->set_dismissible( true );
$this->set_css_class_types( 'warning' );
}
/**
* @return string
*/
private function get_message() {
$msg = esc_html_x(
'You are using DIVI theme, and you have chosen to use the standard editor for translating content.',
'Use Translation Editor notice 1/3',
'sitepress'
);
$msg .= ' ' . esc_html_x(
'Some functionalities may not work properly. We encourage you to switch to use the Translation Editor.',
'Use Translation Editor notice 2/3',
'sitepress'
);
$msg .= ' ' . sprintf(
/* translators: %s will be replaced with a URL. */
esc_html_x(
'You can find more information here: %s',
'Use Translation Editor notice 2/3',
'sitepress'
),
'<a href="https://wpml.org/errata/some-internal-taxonomies-will-be-missing-when-you-translate-divi-layouts/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore">Some internal taxonomies will be missing when you translate Divi layouts</a>'
);
return $msg;
}
}
<?php
namespace WPML\Compatibility\Divi;
class DiviOptionsEncoding implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
const CHARS_ENCODED = [ '%22', '%91', '%93' ];
const CHARS_DECODED = [ '"', '[', ']' ];
const DELIMITER = '_';
const TRANSLATABLE_KEYS = [ 'value', 'link_url', 'link_text' ];
public function add_hooks() {
add_filter( 'wpml_pb_shortcode_decode', [ $this, 'decode_divi_options' ], 10, 2 );
add_filter( 'wpml_pb_shortcode_encode', [ $this, 'encode_divi_options' ], 10, 2 );
}
public function decode_divi_options( $string, $encoding ) {
if ( 'divi_options' === $encoding ) {
$options = str_replace( self::CHARS_ENCODED, self::CHARS_DECODED, $string );
$options = json_decode( $options, true );
$string = [];
foreach ( $options as $index => $option ) {
foreach ( $option as $key => $value ) {
$string[ $key . self::DELIMITER . $index ] = [
'value' => $value,
'translate' => in_array( $key, self::TRANSLATABLE_KEYS, true ),
];
}
}
}
return $string;
}
public function encode_divi_options( $string, $encoding ) {
if ( 'divi_options' === $encoding ) {
$output = [];
foreach ( $string as $combined_key => $value ) {
$parts = explode( self::DELIMITER, $combined_key );
$index = array_pop( $parts );
$key = implode( self::DELIMITER, $parts );
$output[ $index ][ $key ] = $value;
}
$output = wp_json_encode( $output, JSON_UNESCAPED_UNICODE );
$string = str_replace( self::CHARS_DECODED, self::CHARS_ENCODED, $output );
}
return $string;
}
}
<?php
namespace WPML\Compatibility\Divi;
use WPML\Compatibility\BaseDynamicContent;
class DynamicContent extends BaseDynamicContent {
const ENCODED_CONTENT_START = '@ET-DC@';
const ENCODED_CONTENT_END = '@';
/** @var array */
protected $positions = [ 'before', 'after' ];
/**
* Sets $positions dynamic content to be translatable.
*
* @param string $string The decoded string so far.
* @param string $encoding The encoding used.
*
* @return string|array
*/
public function decode_dynamic_content( $string, $encoding ) {
if ( $this->is_dynamic_content( $string ) ) {
$field = $this->decode_field( $string );
$decodedContent = [
'et-dynamic-content' => [
'value' => $string,
'translate' => false,
],
];
foreach ( $this->positions as $position ) {
if ( ! empty( $field['settings'][ $position ] ) ) {
$decodedContent[ $position ] = [
'value' => $field['settings'][ $position ],
'translate' => true,
];
}
}
return $decodedContent;
}
return $string;
}
/**
* Rebuilds dynamic content with translated strings.
*
* @param string|array $string The field array or string.
* @param string $encoding The encoding used.
*
* @return string
*/
public function encode_dynamic_content( $string, $encoding ) {
if ( is_array( $string ) && isset( $string['et-dynamic-content'] ) ) {
$field = $this->decode_field( $string['et-dynamic-content'] );
foreach ( $this->positions as $position ) {
if ( isset( $string[ $position ] ) ) {
$field['settings'][ $position ] = $string[ $position ];
}
}
return $this->encode_field( $field );
}
return $string;
}
/**
* Decode a dynamic-content field.
*
* @param string $string The string to decode.
*
* @return bool
*/
protected function is_dynamic_content( $string ) {
return substr( $string, 0, strlen( self::ENCODED_CONTENT_START ) ) === self::ENCODED_CONTENT_START;
}
/**
* Decode a dynamic-content field.
*
* @param string $string The string to decode.
*
* @return array
*/
protected function decode_field( $string ) {
$start = strlen( self::ENCODED_CONTENT_START );
$end = strlen( self::ENCODED_CONTENT_END );
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
return json_decode( base64_decode( substr( $string, $start, -$end ) ), true );
}
/**
* Encode a dynamic-content field.
*
* @param array $field The field to encode.
*
* @return string
*/
protected function encode_field( $field ) {
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return self::ENCODED_CONTENT_START
. base64_encode( wp_json_encode( $field ) )
. self::ENCODED_CONTENT_END;
}
}
<?php
namespace WPML\Compatibility\Divi;
class Search implements \IWPML_Frontend_Action {
public function add_hooks() {
add_action( 'et_search_form_fields', [ $this, 'add_language_form_field' ] );
}
public function add_language_form_field() {
do_action( 'wpml_add_language_form_field' );
}
}
<?php
namespace WPML\Compatibility\Divi;
class ThemeBuilderFactory implements \IWPML_Deferred_Action_Loader, \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader {
public function get_load_action() {
return 'init';
}
public function create() {
global $sitepress;
return new ThemeBuilder( $sitepress );
}
}
<?php
namespace WPML\Compatibility\Divi;
use SitePress;
class ThemeBuilder implements \IWPML_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 ( ! defined( 'ET_THEME_BUILDER_DIR' ) ) {
return;
}
if ( $this->sitepress->is_setup_complete() ) {
if ( is_admin() ) {
add_action( 'init', [ $this, 'make_layouts_editable' ], 1000 ); // Before WPML_Sticky_Links::init.
add_filter( 'wpml_document_view_item_link', [ $this, 'document_view_layout_link' ], 10, 5 );
} else {
add_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10, 4 );
}
}
}
/**
* Gets all post types that are layouts.
*/
private static function get_types() {
return [
ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
];
}
/**
* Access the global post types array to tweak the settings for layouts
*/
public function make_layouts_editable() {
global $wp_post_types;
foreach ( $this->get_types() as $type ) {
$wp_post_types[ $type ]->show_ui = true;
$wp_post_types[ $type ]->show_in_menu = false;
$wp_post_types[ $type ]->_edit_link = 'post.php?post=%d';
}
}
/**
* Translate theme builder layout ids in the frontend.
*
* @param string $value The layout id.
* @param int $post_id The post it belongs to.
* @param string $key The meta key we are handling.
* @param bool $single Fetch a single row or an array.
* @return string
*/
public function translate_layout_ids( $value, $post_id, $key, $single ) {
if ( in_array( $key, [ '_et_header_layout_id', '_et_body_layout_id', '_et_footer_layout_id' ], true ) ) {
/**
* The `get_post_metadata` filter provides `null` as the initial `$value`.
* When we return a different $value it is used directly, to avoid a second query.
* This means that we have to get the original value first, removing ourselves so
* we don't fall into an infinite loop.
*/
remove_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10 );
$original_id = get_post_meta( $post_id, $key, true );
add_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10, 4 );
$type = substr( $key, 1, -3 );
$value = $this->sitepress->get_object_id( $original_id, $type, true );
if ( ! $single ) {
$value = [ $value ];
}
}
return $value;
}
/**
* Remove the 'View' link because you can't view layouts alone.
*
* @param string $link The complete link.
* @param string $text The text to link.
* @param object $job The corresponding translation job.
* @param string $prefix The prefix of the element type.
* @param string $type The element type.
*
* @return string
*/
public function document_view_layout_link( $link, $text, $job, $prefix, $type ) {
if ( 'post' === $prefix && $this->is_theme_layout( $type ) ) {
$link = '';
}
return $link;
}
/**
* Check if a certain Type is a theme builder layout.
*
* @param string $type The type to check.
*
* @return bool
*/
private function is_theme_layout( $type ) {
return in_array( $type, $this->get_types(), true );
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements\EssentialAddons;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\compose;
/**
* @see https://essential-addons.com/elementor/docs/creative-elements/content-timeline/
*/
class ContentTimeline {
/**
* @return array
*/
public static function get() {
// $isEAContentTimeline :: array -> bool
$isEAContentTimeline = Relation::propEq( 'widgetType', 'eael-content-timeline' );
// $contentTimelineLinksLens :: callable -> callable -> mixed
$contentTimelineLinksLens = compose(
Obj::lensProp( 'settings' ),
Obj::lensMappedProp( 'eael_coustom_content_posts' ),
Obj::lensPath( [ '__dynamic__', 'eael_read_more_text_link' ] )
);
return [ $isEAContentTimeline, $contentTimelineLinksLens, 'popup', 'popup' ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\FP\Relation;
class FormPopup {
/**
* @return array
*/
public static function get() {
$popupIdPath = [ 'settings', 'popup_action_popup_id' ];
$isFormWithPopup = Logic::allPass( [
Relation::propEq( 'widgetType', 'form' ),
Obj::path( $popupIdPath ),
] );
$popupIdLens = Obj::lensPath( $popupIdPath );
return [ $isFormWithPopup, $popupIdLens ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\compose;
class Hotspot{
/**
* @return array
*/
public static function get() {
$isHotspot = Relation::propEq( 'widgetType', 'hotspot' );
// $hotspotLinksLens :: callable -> callable -> mixed
$hotspotLinksLens = compose(
Obj::lensProp( 'settings' ),
Obj::lensMappedProp( 'hotspot' ),
Obj::lensPath( [ '__dynamic__', 'hotspot_link' ] )
);
return [ $isHotspot, $hotspotLinksLens, 'popup', 'popup' ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\compose;
class IconList {
/**
* @return array
*/
public static function get() {
// $isIconList :: array -> bool
$isIconList = Relation::propEq( 'widgetType', 'icon-list' );
$iconListLinksLens = compose(
Obj::lensProp( 'settings' ),
Obj::lensMappedProp( 'icon_list' ),
Obj::lensPath( [ '__dynamic__', 'link' ] )
);
return [ $isIconList, $iconListLinksLens, 'popup', 'popup' ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\FP\Relation;
class LoopCarousel {
/**
* @return array
*/
public static function get() {
$loopCarouselIdPath = [ 'settings', 'template_id' ];
$hasLoopCarousel = Logic::allPass( [
Relation::propEq( 'widgetType', 'loop-carousel' ),
Obj::path( $loopCarouselIdPath ),
] );
$loopCarouselIdLens = Obj::lensPath( $loopCarouselIdPath );
return [ $hasLoopCarousel, $loopCarouselIdLens ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\FP\Relation;
class LoopGrid {
/**
* @return array
*/
public static function get() {
$loopIdPath = [ 'settings', 'template_id' ];
$hasLoop = Logic::allPass( [
Relation::propEq( 'widgetType', 'loop-grid' ),
Obj::path( $loopIdPath ),
] );
$loopIdLens = Obj::lensPath( $loopIdPath );
return [ $hasLoop, $loopIdLens ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Logic;
class Popup {
/**
* @return array
*/
public static function get() {
$popupPath = [ 'settings', '__dynamic__', 'link' ];
// $isDynamicLink :: array -> bool
$isDynamicLink = Logic::allPass( [
Relation::propEq( 'elType', 'widget' ),
Obj::path( $popupPath ),
] );
$lens = Obj::lensPath( $popupPath );
return [ $isDynamicLink, $lens, 'popup', 'popup' ];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
class Provider {
/**
* @return array
*/
public static function get() {
return [
EssentialAddons\ContentTimeline::get(),
LoopGrid::get(),
LoopCarousel::get(),
Hotspot::get(),
Popup::get(),
IconList::get(),
FormPopup::get(),
WooProduct::get( 'title' ),
WooProduct::get( 'short-description' ),
];
}
}
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Logic;
class WooProduct {
/**
* @param string $widgetName
*
* @return callable(string): string
*/
private static function getConfig( $widgetName ) {
$widgetConfig = wpml_collect( [
'title' => [
'dynamicKey' => 'title',
'widgetType' => 'heading',
'shortcodeName' => 'woocommerce-product-title-tag',
],
'short-description' => [
'dynamicKey' => 'editor',
'widgetType' => 'text-editor',
'shortcodeName' => 'woocommerce-product-short-description-tag',
],
] )->get( $widgetName );
return Obj::prop( Fns::__, $widgetConfig );
}
/**
* @param string $widget
*
* @return array
*/
public static function get( $widget ) {
$get = self::getConfig( $widget );
$widgetPath = [ 'settings', '__dynamic__', $get( 'dynamicKey' ) ];
// $isWooWidget :: array -> bool
$isWooWidget = Logic::allPass( [
Relation::propEq( 'widgetType', $get( 'widgetType' ) ),
Obj::path( $widgetPath ),
] );
// $widgetLens :: callable -> callable -> mixed
$widgetLens = Obj::lensPath( $widgetPath );
return [ $isWooWidget, $widgetLens, $get( 'shortcodeName' ), 'product_id' ];
}
}
<?php
namespace WPML\PB\Elementor\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'elementor-widgets',
'defaultConditionKey' => 'widgetType',
'pbKey' => 'elementor',
'translatableWidgetsHook' => 'wpml_elementor_widgets_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}
<?php
namespace WPML\PB\Elementor;
class DataConvert {
/**
* @param array $data
*
* @return string
*/
public static function serialize( array $data ) {
return wp_slash( wp_json_encode( $data ) );
}
/**
* @param array|string $data
*
* @return array
*/
public static function unserialize( $data ) {
if ( self::isElementorArray( $data ) ) {
return $data;
}
$value = is_array( $data ) ? $data[0] : $data;
if ( self::isElementorArray( $value ) ) {
return $value;
}
return self::unserializeString( $value );
}
/**
* @param string $string
*
* @return array
*/
private static function unserializeString( $string ) {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
return is_serialized( $string ) ? unserialize( $string ) : json_decode( $string, true );
}
/**
* @param mixed $data
*
* @return bool
*/
private static function isElementorArray( $data ) {
return is_array( $data ) && count( $data ) > 0 && isset( $data[0]['id'] );
}
}
<?php
namespace WPML\PB\Elementor\DynamicContent;
use WPML_PB_String;
class Field {
/**
* e.g. '[elementor-tag id="cc0b6c6" name="post-title" settings="ENCODED_STRING"]'
*
* @var string $tagValue
*/
public $tagValue;
/**
* e.g. 'title'
*
* @var string $tagKey
*/
public $tagKey;
/**
* The node ID.
*
* @var string $nodeId
*/
public $nodeId;
/**
* The item ID inside the node with items.
*
* @var string $itemId
*/
public $itemId;
/**
* @param string $tagValue
* @param string $tagKey
* @param string $nodeId
* @param string $itemId
*/
public function __construct( $tagValue, $tagKey, $nodeId, $itemId = '' ) {
$this->tagValue = $tagValue;
$this->tagKey = $tagKey;
$this->nodeId = $nodeId;
$this->itemId = $itemId;
}
/**
* @see \WPML_Elementor_Translatable_Nodes::get_string_name()
* @see \WPML_Elementor_Module_With_Items::get_string_name()
*
* @param WPML_PB_String $string
*
* @return bool
*/
public function isMatchingStaticString( WPML_PB_String $string ) {
$pattern = '/^' . $this->tagKey . '-.*-' . $this->nodeId . '$/';
if ( $this->itemId ) {
$pattern = '/^' . $this->tagKey . '-.*-' . $this->nodeId . '-' . $this->itemId . '$/';
}
return (bool) preg_match( $pattern, $string->get_name() );
}
}
\ No newline at end of file
<?php
namespace WPML\PB\Elementor\DynamicContent;
use WPML\Collect\Support\Collection;
use WPML_Elementor_Translatable_Nodes;
use WPML_PB_String;
class Strings {
const KEY_SETTINGS = WPML_Elementor_Translatable_Nodes::SETTINGS_FIELD;
const KEY_DYNAMIC = '__dynamic__';
const KEY_NODE_ID = 'id';
const KEY_ITEM_ID = '_id';
const SETTINGS_REGEX = '/settings="(.*?(?="]))/';
const NAME_PREFIX = 'dynamic';
const DELIMITER = '-';
const TRANSLATABLE_SETTINGS = [
'before',
'after',
'fallback',
'video_url',
];
/**
* Remove the strings overwritten with dynamic content
* and add the extra strings "before", "after" and "fallback".
*
* @param WPML_PB_String[] $strings
* @param string $nodeId
* @param array $element
*
* @return WPML_PB_String[]
*/
public static function filter( array $strings, $nodeId, array $element ) {
$dynamicFields = self::getDynamicFields( $element );
$updateFromDynamicFields = function( WPML_PB_String $string ) use ( &$dynamicFields ) {
$matchingField = $dynamicFields->first(
function( Field $field ) use ( $string ) {
return $field->isMatchingStaticString( $string );
}
);
if ( $matchingField ) {
return self::addBeforeAfterAndFallback( wpml_collect( [ $dynamicFields->pull( $dynamicFields->search( $matchingField ) ) ] ) );
}
return $string;
};
return wpml_collect( $strings )
->map( $updateFromDynamicFields )
->merge( self::addBeforeAfterAndFallback( $dynamicFields ) )
->flatten()
->toArray();
}
/**
* @param array $element
*
* @return Collection
*/
private static function getDynamicFields( array $element ) {
if ( self::isModuleWithItems( $element ) ) {
return self::getDynamicFieldsForModuleWithItems( $element );
} elseif ( isset( $element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ] ) ) {
return self::getFields(
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ],
$element[ self::KEY_NODE_ID ]
);
}
return wpml_collect();
}
/**
* @param array $element
*
* @return Collection
*/
private static function getDynamicFieldsForModuleWithItems( array $element ) {
$isDynamic = function( $item ) {
return isset( $item[ self::KEY_DYNAMIC ] );
};
$getFields = function( array $item ) use ( $element ) {
return self::getFields(
$item[ self::KEY_DYNAMIC ],
$element[ self::KEY_NODE_ID ],
$item[ self::KEY_ITEM_ID ]
);
};
return wpml_collect( reset( $element[ self::KEY_SETTINGS ] ) )
->filter( $isDynamic )
->map( $getFields )
->flatten();
}
/**
* @param array $data
* @param string $nodeId
* @param string $itemId
*
* @return Collection
*/
private static function getFields( array $data, $nodeId, $itemId = '' ) {
$buildField = function( $tagValue, $tagKey ) use ( $nodeId, $itemId ) {
return new Field( $tagValue, $tagKey, $nodeId, $itemId );
};
return wpml_collect( $data )->map( $buildField );
}
/**
* @param array $element
*
* @return bool
*/
private static function isModuleWithItems( array $element ) {
if ( isset( $element[ self::KEY_SETTINGS ] ) ) {
$firstSettingElement = reset( $element[ self::KEY_SETTINGS ] );
return is_array( $firstSettingElement ) && 0 === key( $firstSettingElement );
}
return false;
}
/**
* @param Collection $dynamicFields
*
* @return Collection
*/
private static function addBeforeAfterAndFallback( Collection $dynamicFields ) {
$dynamicFieldToSettingStrings = function( Field $field ) {
preg_match( self::SETTINGS_REGEX, $field->tagValue, $matches );
$isTranslatableSetting = function( $value, $settingField ) {
return $value && is_string( $value ) && in_array( $settingField, self::TRANSLATABLE_SETTINGS, true );
};
$buildStringFromSetting = function( $value, $settingField ) use ( $field ) {
return new WPML_PB_String(
$value,
self::getStringName( $field->nodeId, $field->itemId, $field->tagKey, $settingField ),
sprintf( __( 'Dynamic content string: %s', 'sitepress' ), $field->tagKey ),
'LINE'
);
};
return wpml_collect( isset( $matches[1] ) ? self::decodeSettings( $matches[1] ) : [] )
->filter( $isTranslatableSetting )
->map( $buildStringFromSetting );
};
return $dynamicFields->map( $dynamicFieldToSettingStrings );
}
/**
* @param array $element
* @param WPML_PB_String $string
*
* @return array
*/
public static function updateNode( array $element, WPML_PB_String $string ) {
$stringNameParts = explode( self::DELIMITER, $string->get_name() );
if ( count( $stringNameParts ) !== 5 || self::NAME_PREFIX !== $stringNameParts[0] ) {
return $element;
}
list( , , $itemId, $dynamicField, $settingField ) = $stringNameParts;
if ( $itemId && self::isModuleWithItems( $element ) ) {
$element = self::updateNodeWithItems( $element, $string, $stringNameParts );
} elseif ( isset( $element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ] ) ) {
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ] = self::replaceSettingString(
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ],
$string,
$settingField
);
}
return $element;
}
/**
* @param string $encodedSettings
* @param WPML_PB_String $string
* @param string $settingField
*
* @return string|null
*/
private static function replaceSettingString( $encodedSettings, WPML_PB_String $string, $settingField ) {
$replace = function( array $matches ) use ( $string, $settingField ) {
$settings = self::decodeSettings( $matches[1] );
$settings[ $settingField ] = $string->get_value();
$replace = urlencode( json_encode( $settings ) );
return str_replace( $matches[1], $replace, $matches[0] );
};
return preg_replace_callback( self::SETTINGS_REGEX, $replace, $encodedSettings );
}
/**
* @param array $element
* @param WPML_PB_String $string
* @param array $stringNameParts
*
* @return array
*/
private static function updateNodeWithItems( array $element, WPML_PB_String $string, array $stringNameParts ) {
list( , , $itemId, $dynamicField, $settingField ) = $stringNameParts;
$items = wpml_collect( reset( $element[ self::KEY_SETTINGS ] ) );
$mainKey = key( $element[ self::KEY_SETTINGS ] );
$replaceStringInItem = function( array $item ) use ( $itemId, $string, $dynamicField, $settingField ) {
if (
isset( $item[ self::KEY_DYNAMIC ][ $dynamicField ], $item[ self::KEY_ITEM_ID ] )
&& $item[ self::KEY_ITEM_ID ] === $itemId
) {
$item[ self::KEY_DYNAMIC ][ $dynamicField ] = self::replaceSettingString( $item[ self::KEY_DYNAMIC ][ $dynamicField ], $string, $settingField );
}
return $item;
};
$element[ self::KEY_SETTINGS ][ $mainKey ] = $items->map( $replaceStringInItem )->toArray();
return $element;
}
/**
* @param string $settingsString
*
* @return array
*/
private static function decodeSettings( $settingsString ) {
return json_decode( urldecode( $settingsString ), true );
}
/**
* @param string $nodeId
* @param string $itemId
* @param string $tagKey
* @param string $settingField
*
* @return string
*/
public static function getStringName( $nodeId, $itemId, $tagKey, $settingField ) {
return self::NAME_PREFIX . self::DELIMITER
. $nodeId . self::DELIMITER
. $itemId . self::DELIMITER
. $tagKey . self::DELIMITER
. $settingField;
}
}
<?php
namespace WPML\PB\Elementor\Helper;
class Node {
/**
* @param array $element
*
* @return bool
*/
public static function isTranslatable( $element ) {
return isset( $element['elType'] ) && in_array( $element['elType'], [ 'widget', 'container' ], true );
}
/**
* @param array $element
*
* @return bool
*/
public static function hasChildren( $element ) {
return isset( $element['elements'] ) && count( $element['elements'] );
}
}
<?php
namespace WPML\PB\Elementor\Helper;
use WPML\FP\Obj;
use WPML\FP\Str;
class StringFormat {
/**
* @param array $settings
* @param \WPML_PB_String $string
*
* @return bool
*/
public static function useWpAutoP( $settings, $string ) {
return 'VISUAL' === $string->get_editor_type() && self::isOneLine( self::getOriginalString( $settings, $string ) );
}
/**
* @param string $content
*
* @return bool
*/
private static function isOneLine( $content ) {
return ! Str::includes( PHP_EOL, $content );
}
/**
* @param array $settings
* @param \WPML_PB_String $string
*
* @return string
*/
private static function getOriginalString( $settings, $string ) {
if ( 'text-editor' === $settings['widgetType'] ) {
return Obj::path( [ 'settings', 'editor' ], $settings );
} elseif ( 'hotspot' === $settings['widgetType'] ) {
$items = Obj::path( [ 'settings', 'hotspot' ], $settings );
$found = wpml_collect( $items )->first( function( $item ) use ( $string ) {
return Str::endsWith( $item['_id'], $string->get_name() );
});
return Obj::prop( 'hotspot_tooltip_content', $found );
}
return '';
}
}
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class CssCache implements \IWPML_Frontend_Action, \IWPML_Backend_Action {
const TEMPLATES_POST_TYPE = 'elementor_library';
/**
* @return void
*/
public function add_hooks() {
Hooks::onAction( 'wpml_translation_job_saved' )
->then( spreadArgs( [ self::class, 'flushCache' ] ) );
}
/**
* @param int $postId
*
* @return void
*/
public static function flushCache( $postId ) {
$post = get_post( $postId );
if ( self::TEMPLATES_POST_TYPE === Obj::prop( 'post_type', $post ) ) {
try {
$fileManager = \WPML\Container\make( \Elementor\Core\Files\Manager::class );
if ( $fileManager && method_exists( $fileManager, 'clear_cache' ) ) {
$fileManager->clear_cache();
}
} catch ( \Exception $e ) {}
}
}
}
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\PB\Helper\LanguageNegotiation;
class DomainsWithMultisite implements \IWPML_Backend_Action {
public function add_hooks() {
if ( is_multisite() && LanguageNegotiation::isUsingDomains() ) {
Hooks::onAction( 'elementor/editor/init' )
->then( spreadArgs( [ $this, 'onElementorEditor' ] ) );
}
}
public function onElementorEditor() {
$isCurrentLangDifferentThanDefault = apply_filters( 'wpml_current_language', null ) !== apply_filters( 'wpml_default_language', null );
if ( $isCurrentLangDifferentThanDefault ) {
Hooks::onFilter( 'admin_url' )
->then( spreadArgs( [ $this, 'filterUrl' ] ) );
}
}
/**
* @param string $url The admin area URL.
*/
public function filterUrl( $url ) {
$parsedUrl = wpml_parse_url( $url );
if ( is_array( $parsedUrl ) && ! empty( $parsedUrl['host'] ) ) {
return http_build_url( Obj::assoc( 'host', $_SERVER['HTTP_HOST'], $parsedUrl ) );
}
return $url;
}
private 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' );
}
}