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.

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