first commit

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

View File

@@ -0,0 +1,20 @@
<?php
namespace WPML\PB\BeaverBuilder\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'beaver-builder-widgets',
'defaultConditionKey' => 'type',
'pbKey' => 'beaver-builder',
'translatableWidgetsHook' => 'wpml_beaver_builder_modules_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace WPML\PB\BeaverBuilder\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Frontend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Obj::path( [ 'fl_builder_data', 'action' ], $_POST ) === 'save_layout'
&& (int) Obj::path( [ 'fl_builder_data', 'post_id' ], $_POST ) === $translatedPostId
);
} ) );
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace WPML\PB\BeaverBuilder\TranslationJob;
use WPML_Beaver_Builder_Data_Settings;
class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
/** @var WPML_Beaver_Builder_Data_Settings $dataSettings */
private $dataSettings;
public function __construct( WPML_Beaver_Builder_Data_Settings $dataSettings ) {
$this->dataSettings = $dataSettings;
}
public function add_hooks() {
add_filter( 'wpml_tm_translation_job_data', [ $this, 'filterFieldsByPageBuilderKind' ], PHP_INT_MAX, 2 );
}
/**
* @param array $translationPackage
* @param \stdClass|\WP_Post $post
*
* @return array
*/
public function filterFieldsByPageBuilderKind( array $translationPackage, $post ) {
if ( ! $this->isPostPackage( $translationPackage, $post ) ) {
return $translationPackage;
}
if ( $this->dataSettings->is_handling_post( $post->ID ) ) {
return $this->removeFieldsFromKind( $translationPackage, $post->ID, 'gutenberg' );
}
/**
*
*/
return $this->removeFieldsFromKind( $translationPackage, $post->ID, 'beaver-builder' );
}
/**
* @param array $translationPackage
* @param \stdClass|\WP_Post $post
*
* @return bool
*/
private function isPostPackage( array $translationPackage, $post ) {
return 'external' !== $translationPackage['type'] && isset( $post->ID );
}
/**
* @param array $translationPackage
* @param int $postId
* @param string $kindSlug
*
* @return array
*/
private function removeFieldsFromKind( array $translationPackage, $postId, $kindSlug ) {
$packageIdToRemove = wpml_collect( apply_filters( 'wpml_st_get_post_string_packages', [], $postId ) )
->pluck( 'ID', 'kind_slug' )
->get( $kindSlug );
if ( $packageIdToRemove ) {
$isFieldFromPackageToRemove = function( $value, $key ) use ( $packageIdToRemove ) {
return preg_match( '/^package-string-' . $packageIdToRemove . '-/', $key );
};
$translationPackage['contents'] = wpml_collect( $translationPackage['contents'] )
->reject( $isFieldFromPackageToRemove )
->toArray();
}
return $translationPackage;
}
}

View File

@@ -0,0 +1,11 @@
<?php
class WPML_Beaver_Builder_Data_Settings_For_Media extends WPML_Beaver_Builder_Data_Settings {
/**
* @return array
*/
public function get_fields_to_copy() {
return [];
}
}

View File

@@ -0,0 +1,95 @@
<?php
class WPML_Beaver_Builder_Data_Settings implements IWPML_Page_Builders_Data_Settings {
/**
* @return string
*/
public function get_meta_field() {
return '_fl_builder_data';
}
/**
* @return string
*/
public function get_node_id_field() {
return 'node';
}
/**
* @return array
*/
public function get_fields_to_copy() {
return array( '_fl_builder_draft_settings', '_fl_builder_data_settings', '_fl_builder_enabled' );
}
/**
* @param array $data
*
* @return array
*/
public function convert_data_to_array( $data ) {
return $data;
}
/**
* @param array $data
*
* @return array
*/
public function prepare_data_for_saving( array $data ) {
return $this->slash( $data );
}
/**
* @return string
*/
public function get_pb_name(){
return 'Beaver builder';
}
/**
* @return array
*/
public function get_fields_to_save() {
return array( '_fl_builder_data', '_fl_builder_draft' );
}
public function add_hooks(){}
/**
* Adds slashes to data going into the database as WordPress
* removes them when we save using update_metadata. This is done
* to ensure slashes in user input aren't removed.
*
* Inspired by `\FLBuilderModel::slash_settings`
*
* @param mixed $data The data to slash.
*
* @return mixed The slashed data.
*/
private function slash( $data ) {
if ( is_array( $data ) ) {
foreach ( $data as $key => $val ) {
$data[ $key ] = $this->slash( $val );
}
} elseif ( is_object( $data ) ) {
foreach ( $data as $key => $val ) {
$data->$key = $this->slash( $val );
}
} elseif ( is_string( $data ) ) {
$data = wp_slash( $data );
}
return $data;
}
/**
* @param int $postId
*
* @return bool
*/
public function is_handling_post( $postId ) {
return (bool) get_post_meta( $postId, '_fl_builder_enabled', true );
}
}

View File

@@ -0,0 +1,8 @@
<?php
class WPML_PB_Beaver_Builder_Handle_Custom_Fields_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_PB_Handle_Custom_Fields( new WPML_Beaver_Builder_Data_Settings() );
}
}

View File

@@ -0,0 +1,30 @@
<?php
class WPML_Beaver_Builder_Integration_Factory {
const SLUG = 'beaver-builder';
public function create() {
$action_filter_loader = new WPML_Action_Filter_Loader();
$action_filter_loader->load(
array(
'WPML_PB_Beaver_Builder_Handle_Custom_Fields_Factory',
'WPML_Beaver_Builder_Media_Hooks_Factory',
\WPML\PB\BeaverBuilder\TranslationJob\Hooks::class,
\WPML\PB\BeaverBuilder\Config\Factory::class,
\WPML\PB\BeaverBuilder\Hooks\Editor::class,
)
);
$nodes = new WPML_Beaver_Builder_Translatable_Nodes();
$data_settings = new WPML_Beaver_Builder_Data_Settings();
$string_registration_factory = new WPML_String_Registration_Factory( $data_settings->get_pb_name() );
$string_registration = $string_registration_factory->create();
$register_strings = new WPML_Beaver_Builder_Register_Strings( $nodes, $data_settings, $string_registration );
$update_translation = new WPML_Beaver_Builder_Update_Translation( $nodes, $data_settings );
return new WPML_Page_Builders_Integration( $register_strings, $update_translation, $data_settings );
}
}

View File

@@ -0,0 +1,100 @@
<?php
/**
* Class WPML_Beaver_Builder_Register_Strings
*/
class WPML_Beaver_Builder_Register_Strings extends WPML_Page_Builders_Register_Strings {
/**
* @param array $data_array
* @param array $package
*/
protected function register_strings_for_modules( array $data_array, array $package ) {
foreach ( $data_array as $data ) {
if ( is_array( $data ) ) {
$data = $this->sort_modules_before_string_registration( $data );
$this->register_strings_for_modules( $data, $package );
} elseif ( is_object( $data ) ) {
if ( isset( $data->type, $data->node, $data->settings ) && 'module' === $data->type && ! $this->is_embedded_global_module( $data ) ) {
$this->register_strings_for_node( $data->node, $data->settings, $package );
}
}
}
}
/**
* The modules are not in the order they appear on the page,
* so we need to sort it before to register the strings.
*
* @param array $modules
*
* @return array
*/
private function sort_modules_before_string_registration( array $modules ) {
if ( count( $modules ) > 1 ) {
uasort( $modules, array( $this, 'sort_modules_by_position_only' ) );
return $this->sort_modules_by_parent_and_child( $modules );
}
return $modules;
}
/**
* We receive all modules as a flat tree and we need to reorder from:
* - child A
* - child A
* - parent A
* - child B
* - parent B
* - child B
* - child C
*
* To:
* - parent A
* - child A
* - child B
* - parent B
* - child A
* - child B
* - child C
*
* The relative positions are already sorted by `sort_modules_by_position_only`
*
* @param array $all_modules
* @param string|null $parent_hash
* @param array $sorted_modules
*
* @return array
*/
private function sort_modules_by_parent_and_child( array $all_modules, $parent_hash = null, array $sorted_modules = array() ){
foreach ( $all_modules as $hash => $module ) {
if ( $module->parent === $parent_hash ) {
$sorted_modules[ $hash ] = $module;
unset( $all_modules[ $hash ] );
$sorted_modules = $this->sort_modules_by_parent_and_child( $all_modules, $module->node, $sorted_modules );
}
}
return $sorted_modules;
}
/**
* @param stdClass $a
* @param stdClass $b
*
* @return int
*/
private function sort_modules_by_position_only( stdClass $a, stdClass $b ) {
return ( (int) $a->position < (int) $b->position ) ? -1 : 1;
}
/**
* @param object $data
*
* @return bool
*/
private function is_embedded_global_module( $data ) {
return ! empty( $data->template_node_id ) && isset( $data->node ) && $data->template_node_id !== $data->node;
}
}

View File

@@ -0,0 +1,516 @@
<?php
/**
* WPML_Beaver_Builder_Translatable_Nodes class file.
*
* @package wpml-page-builders-beaver-builder
*/
use WPML\PB\BeaverBuilder\Modules\ModuleWithItemsFromConfig;
/**
* Class WPML_Beaver_Builder_Translatable_Nodes
*/
class WPML_Beaver_Builder_Translatable_Nodes implements IWPML_Page_Builders_Translatable_Nodes {
/**
* Nodes to translate.
*
* @var array
*/
private $nodes_to_translate;
/**
* Get translatable node.
*
* @param string|int $node_id Node id.
* @param stdClass $settings Node settings.
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings ) {
if ( ! $this->nodes_to_translate ) {
$this->initialize_nodes_to_translate();
}
$strings = array();
foreach ( $this->nodes_to_translate as $node_type => $node_data ) {
if ( $this->conditions_ok( $node_data, $settings ) ) {
foreach ( $node_data['fields'] as $field ) {
$field_key = $field['field'];
if ( isset( $settings->$field_key ) && trim( $settings->$field_key ) ) {
$string = new WPML_PB_String(
$settings->$field_key,
$this->get_string_name( $node_id, $field, $settings ),
$field['type'],
$field['editor_type'],
$this->get_wrap_tag( $settings )
);
$strings[] = $string;
}
}
foreach ( $this->get_integration_instances( $node_data ) as $node ) {
try {
$strings = $node->get( $node_id, $settings, $strings );
// phpcs:disable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch ( Exception $e ) {}
// phpcs:enable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
}
}
}
return $strings;
}
/**
* Update translatable node.
*
* @param string $node_id Node id.
* @param stdClass $settings Node settings.
* @param WPML_PB_String $string String object.
*
* @return stdClass
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
if ( ! $this->nodes_to_translate ) {
$this->initialize_nodes_to_translate();
}
foreach ( $this->nodes_to_translate as $node_type => $node_data ) {
if ( $this->conditions_ok( $node_data, $settings ) ) {
foreach ( $node_data['fields'] as $field ) {
$field_key = $field['field'];
if ( $this->get_string_name( $node_id, $field, $settings ) === $string->get_name() ) {
$settings->$field_key = $string->get_value();
}
}
foreach ( $this->get_integration_instances( $node_data ) as $node ) {
try {
$node->update( $node_id, $settings, $string );
// phpcs:disable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch ( Exception $e ) {}
// phpcs:enable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
}
}
}
return $settings;
}
/**
* @param array $node_data
*
* @return WPML_Beaver_Builder_Module_With_Items[]
*/
private function get_integration_instances( array $node_data ) {
$instances = [];
if ( isset( $node_data['integration-class'] ) ) {
try {
$instances[] = new $node_data['integration-class']();
// phpcs:disable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch ( Exception $e ) {}
// phpcs:enable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
}
if ( isset( $node_data['fields_in_item'] ) ) {
foreach ( $node_data['fields_in_item'] as $item_of => $config ) {
$instances[] = new ModuleWithItemsFromConfig( $item_of, $config );
}
}
return array_filter( $instances );
}
/**
* Get string name.
*
* @param string $node_id Node id.
* @param array $field Page builder field.
* @param stdClass $settings Node settings.
*
* @return string
*/
public function get_string_name( $node_id, $field, $settings ) {
return $field['field'] . '-' . $settings->type . '-' . $node_id;
}
/**
* Get wrap tag for string.
* Used for SEO, can contain (h1...h6, etc.)
*
* @param stdClass $settings Field settings.
*
* @return string
*/
private function get_wrap_tag( $settings ) {
if ( isset( $settings->type ) && 'heading' === $settings->type && isset( $settings->tag ) ) {
return $settings->tag;
}
return '';
}
/**
* Check if node condition is ok.
*
* @param array $node_data Node data.
* @param stdClass $settings Node settings.
*
* @return bool
*/
private function conditions_ok( $node_data, $settings ) {
$conditions_meet = true;
foreach ( $node_data['conditions'] as $field_key => $field_value ) {
if ( ! isset( $settings->$field_key ) || $settings->$field_key !== $field_value ) {
$conditions_meet = false;
break;
}
}
return $conditions_meet;
}
/**
* @return array
*/
public static function get_nodes_to_translate() {
return array(
'button' => array(
'conditions' => array( 'type' => 'button' ),
'fields' => array(
array(
'field' => 'text',
'type' => __( 'Button: Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'link',
'type' => __( 'Button: Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'heading' => array(
'conditions' => array( 'type' => 'heading' ),
'fields' => array(
array(
'field' => 'heading',
'type' => __( 'Heading', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'link',
'type' => __( 'Heading: Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'html' => array(
'conditions' => array( 'type' => 'html' ),
'fields' => array(
array(
'field' => 'html',
'type' => __( 'HTML', 'sitepress' ),
'editor_type' => 'VISUAL',
),
),
),
'photo' => array(
'conditions' => array( 'type' => 'photo' ),
'fields' => array(
array(
'field' => 'link_url',
'type' => __( 'Photo: Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'rich-text' => array(
'conditions' => array( 'type' => 'rich-text' ),
'fields' => array(
array(
'field' => 'text',
'type' => __( 'Text Editor', 'sitepress' ),
'editor_type' => 'VISUAL',
),
),
),
'accordion' => array(
'conditions' => array( 'type' => 'accordion' ),
'fields' => array(),
'integration-class' => 'WPML_Beaver_Builder_Accordion',
),
'pricing-table' => array(
'conditions' => array( 'type' => 'pricing-table' ),
'fields' => array(),
'integration-class' => 'WPML_Beaver_Builder_Pricing_Table',
),
'tabs' => array(
'conditions' => array( 'type' => 'tabs' ),
'fields' => array(),
'integration-class' => 'WPML_Beaver_Builder_Tab',
),
'callout' => array(
'conditions' => array( 'type' => 'callout' ),
'fields' => array(
array(
'field' => 'title',
'type' => __( 'Callout: Heading', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'text',
'type' => __( 'Callout: Text', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'cta_text',
'type' => __( 'Callout: Call to action text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'link',
'type' => __( 'Callout: Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'contact-form' => array(
'conditions' => array( 'type' => 'contact-form' ),
'fields' => array(
array(
'field' => 'name_placeholder',
'type' => __( 'Contact Form: Name Field Placeholder', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'subject_placeholder',
'type' => __( 'Contact Form: Subject Field Placeholder', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'email_placeholder',
'type' => __( 'Contact Form: Email Field Placeholder', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'phone_placeholder',
'type' => __( 'Contact Form: Phone Field Placeholder', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'message_placeholder',
'type' => __( 'Contact Form: Your Message Placeholder', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'terms_checkbox_text',
'type' => __( 'Contact Form: Checkbox Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'terms_text',
'type' => __( 'Contact Form: Terms and Conditions', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'success_message',
'type' => __( 'Contact Form: Success Message', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'btn_text',
'type' => __( 'Contact Form: Button Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'success_url',
'type' => __( 'Contact Form: Redirect Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'call-to-action' => array(
'conditions' => array( 'type' => 'cta' ),
'fields' => array(
array(
'field' => 'title',
'type' => __( 'Call to Action: Heading', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'text',
'type' => __( 'Call to Action: Text', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'btn_text',
'type' => __( 'Call to Action: Button text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'btn_link',
'type' => __( 'Call to Action: Button link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'subscribe-form' => array(
'conditions' => array( 'type' => 'subscribe-form' ),
'fields' => array(
array(
'field' => 'terms_checkbox_text',
'type' => __( 'Subscribe form: Checkbox Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'terms_text',
'type' => __( 'Subscribe form: Terms and Conditions', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'custom_subject',
'type' => __( 'Subscribe form: Notification Subject', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'success_message',
'type' => __( 'Subscribe form: Success Message', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'btn_text',
'type' => __( 'Subscribe form: Button Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'success_url',
'type' => __( 'Subscribe form: Redirect Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'content-slider' => array(
'conditions' => array( 'type' => 'content-slider' ),
'fields' => array(),
'integration-class' => 'WPML_Beaver_Builder_Content_Slider',
),
'icon' => array(
'conditions' => array( 'type' => 'icon' ),
'fields' => array(
array(
'field' => 'text',
'type' => __( 'Icon: Text', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'link',
'type' => __( 'Icon: Link', 'sitepress' ),
'editor_type' => 'LINK',
),
),
),
'icon-group' => array(
'conditions' => array( 'type' => 'icon-group' ),
'fields' => array(),
'integration-class' => 'WPML_Beaver_Builder_Icon_Group',
),
'map' => array(
'conditions' => array( 'type' => 'map' ),
'fields' => array(
array(
'field' => 'address',
'type' => __( 'Map: Address', 'sitepress' ),
'editor_type' => 'LINE',
),
),
),
'testimonials' => array(
'conditions' => array( 'type' => 'testimonials' ),
'fields' => array(
array(
'field' => 'heading',
'type' => __( 'Testimonial: Heading', 'sitepress' ),
'editor_type' => 'LINE',
),
),
'integration-class' => 'WPML_Beaver_Builder_Testimonials',
),
'numbers' => array(
'conditions' => array( 'type' => 'numbers' ),
'fields' => array(
array(
'field' => 'before_number_text',
'type' => __( 'Number Counter: Text before number', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'after_number_text',
'type' => __( 'Number Counter: Text after number', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'number_prefix',
'type' => __( 'Number Counter: Number Prefix', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'number_suffix',
'type' => __( 'Number Counter: Number Suffix', 'sitepress' ),
'editor_type' => 'LINE',
),
),
),
'post-grid' => array(
'conditions' => array( 'type' => 'post-grid' ),
'fields' => array(
array(
'field' => 'no_results_message',
'type' => __( 'Posts: No Results Message', 'sitepress' ),
'editor_type' => 'VISUAL',
),
array(
'field' => 'more_btn_text',
'type' => __( 'Posts: Button Text', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'terms_list_label',
'type' => __( 'Posts: Terms Label', 'sitepress' ),
'editor_type' => 'LINE',
),
array(
'field' => 'more_link_text',
'type' => __( 'Posts: More Link Text', 'sitepress' ),
'editor_type' => 'LINE',
),
),
),
'post-slider' => array(
'conditions' => array( 'type' => 'post-slider' ),
'fields' => array(
array(
'field' => 'more_link_text',
'type' => __( 'Posts Slider: More Link Text', 'sitepress' ),
'editor_type' => 'LINE',
),
),
),
);
}
/**
* Initialize translatable nodes.
*/
public function initialize_nodes_to_translate() {
$this->nodes_to_translate = apply_filters( 'wpml_beaver_builder_modules_to_translate', self::get_nodes_to_translate() );
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Class WPML_Beaver_Builder_Update_Translation
*/
class WPML_Beaver_Builder_Update_Translation extends WPML_Page_Builders_Update_Translation {
/** @param array $data_array */
public function update_strings_in_modules( array &$data_array ) {
foreach ( $data_array as &$data ) {
if ( is_array( $data ) ) {
$this->update_strings_in_modules( $data );
} elseif ( is_object( $data ) ) {
if ( isset( $data->type, $data->node, $data->settings ) && 'module' === $data->type ) {
$data->settings = $this->update_strings_in_node( $data->node, $data->settings );
}
}
}
}
/**
* @param string $node_id
* @param array $settings
*
* @return mixed
*/
public function update_strings_in_node( $node_id, $settings ) {
$strings = $this->translatable_nodes->get( $node_id, $settings );
foreach ( $strings as $string ) {
$translation = $this->get_translation( $string );
$settings = $this->translatable_nodes->update( $node_id, $settings, $translation );
}
return $settings;
}
}

View File

@@ -0,0 +1,11 @@
<?php
class WPML_Beaver_Builder_Media_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_Page_Builders_Media_Hooks(
new WPML_Beaver_Builder_Update_Media_Factory(),
WPML_Beaver_Builder_Integration_Factory::SLUG
);
}
}

View File

@@ -0,0 +1,48 @@
<?php
class WPML_Beaver_Builder_Media_Node_Provider {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
private $media_translate;
/** @var WPML_Beaver_Builder_Media_Node[] */
private $nodes = array();
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param string $type
*
* @return WPML_Beaver_Builder_Media_Node|null
*/
public function get( $type ) {
if ( ! array_key_exists( $type, $this->nodes ) ) {
switch ( $type ) {
case 'photo':
$node = new WPML_Beaver_Builder_Media_Node_Photo( $this->media_translate );
break;
case 'gallery':
$node = new WPML_Beaver_Builder_Media_Node_Gallery( $this->media_translate );
break;
case 'content-slider':
$node = new WPML_Beaver_Builder_Media_Node_Content_Slider( $this->media_translate );
break;
case 'slideshow':
$node = new WPML_Beaver_Builder_Media_Node_Slideshow( $this->media_translate );
break;
default:
$node = null;
}
$this->nodes[ $type ] = $node;
}
return $this->nodes[ $type ];
}
}

View File

@@ -0,0 +1,50 @@
<?php
class WPML_Beaver_Builder_Media_Nodes_Iterator implements IWPML_PB_Media_Nodes_Iterator {
/** @var WPML_Beaver_Builder_Media_Node_Provider $node_provider */
private $node_provider;
public function __construct( WPML_Beaver_Builder_Media_Node_Provider $node_provider ) {
$this->node_provider = $node_provider;
}
/**
* @param array $data_array
* @param string $lang
* @param string $source_lang
*
* @return array
*/
public function translate( $data_array, $lang, $source_lang ) {
foreach ( $data_array as &$data ) {
if ( is_array( $data ) ) {
$data = $this->translate( $data, $lang, $source_lang );
} elseif ( is_object( $data )
&& isset( $data->type ) && 'module' === $data->type
&& isset( $data->settings ) && isset( $data->settings->type )
) {
$data->settings = $this->translate_node( $data->settings, $lang, $source_lang );
}
}
return $data_array;
}
/**
* @param stdClass $settings
* @param string $lang
* @param string $source_lang
*
* @return stdClass
*/
private function translate_node( $settings, $lang, $source_lang ) {
$node = $this->node_provider->get( $settings->type );
if ( $node ) {
$settings = $node->translate( $settings, $lang, $source_lang );
}
return $settings;
}
}

View File

@@ -0,0 +1,23 @@
<?php
class WPML_Beaver_Builder_Update_Media_Factory implements IWPML_PB_Media_Update_Factory {
public function create() {
global $sitepress;
$media_translate = new WPML_Page_Builders_Media_Translate(
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Media_Image_Translate( $sitepress, new WPML_Media_Attachment_By_URL_Factory() )
);
return new WPML_Page_Builders_Update_Media(
new WPML_Page_Builders_Update( new WPML_Beaver_Builder_Data_Settings_For_Media() ),
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Beaver_Builder_Media_Nodes_Iterator(
new WPML_Beaver_Builder_Media_Node_Provider( $media_translate )
),
new WPML_Page_Builders_Media_Usage( $media_translate, new WPML_Media_Usage_Factory() )
);
}
}

View File

@@ -0,0 +1,34 @@
<?php
class WPML_Beaver_Builder_Media_Node_Content_Slider extends WPML_Beaver_Builder_Media_Node {
private $property_prefixes = array(
'bg_', // i.e. `bg_photo` for an ID or `bg_photo_src` for a URL
'fg_',
'r_',
);
public function translate( $node_data, $target_lang, $source_lang ) {
if ( ! isset( $node_data->slides ) || ! is_array( $node_data->slides ) ) {
return $node_data;
}
foreach ( $node_data->slides as &$slide ) {
foreach ( $this->property_prefixes as $prefix ) {
$id_prop = $prefix . 'photo';
$src_prop = $prefix . 'photo_src';
if ( isset( $slide->{$id_prop} ) && $slide->{$id_prop} ) {
$slide->{$id_prop} = $this->media_translate->translate_id( $slide->{$id_prop}, $target_lang );
}
if ( isset( $slide->{$src_prop} ) && $slide->{$src_prop} ) {
$slide->{$src_prop} = $this->media_translate->translate_image_url( $slide->{$src_prop}, $target_lang, $source_lang );
}
}
}
return $node_data;
}
}

View File

@@ -0,0 +1,27 @@
<?php
class WPML_Beaver_Builder_Media_Node_Gallery extends WPML_Beaver_Builder_Media_Node {
public function translate( $node_data, $target_lang, $source_lang ) {
foreach ( $node_data->photos as &$photo ) {
$photo = $this->media_translate->translate_id( $photo, $target_lang );
}
foreach ( $node_data->photo_data as &$photo_data ) {
$translated_id = $this->media_translate->translate_id( $photo_data->id, $target_lang );
if ( $translated_id !== $photo_data->id ) {
$translation_data = wp_prepare_attachment_for_js( $translated_id );
$photo_data->id = $translated_id;
$photo_data->alt = $translation_data['alt'];
$photo_data->caption = $translation_data['caption'];
$photo_data->description = $translation_data['description'];
$photo_data->title = $translation_data['title'];
$photo_data->src = $translation_data['url'];
$photo_data->link = $translation_data['url'];
}
}
return $node_data;
}
}

View File

@@ -0,0 +1,16 @@
<?php
class WPML_Beaver_Builder_Media_Node_Photo extends WPML_Beaver_Builder_Media_Node {
public function translate( $node_data, $target_lang, $source_lang ) {
$translated_id = $this->media_translate->translate_id( $node_data->photo, $target_lang );
if ( $translated_id !== $node_data->photo ) {
$node_data->photo = $translated_id;
$node_data->photo_src = $this->media_translate->translate_image_url( $node_data->photo_src, $target_lang, $source_lang );
$node_data->data = wp_prepare_attachment_for_js( $translated_id );
}
return $node_data;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* @group media
*/
class WPML_Beaver_Builder_Media_Node_Slideshow extends WPML_Beaver_Builder_Media_Node {
private $url_properties = array(
'largeURL',
'x3largeURL',
'thumbURL',
);
public function translate( $node_data, $target_lang, $source_lang ) {
if ( ! isset( $node_data->photos, $node_data->photo_data ) || ! is_array( $node_data->photos ) ) {
return $node_data;
}
foreach ( $node_data->photos as &$photo ) {
$photo = $this->media_translate->translate_id( $photo, $target_lang );
}
foreach ( $node_data->photo_data as $photo_id => $photo_data ) {
$translated_id = $this->media_translate->translate_id( $photo_id, $target_lang );
if ( $translated_id !== $photo_id ) {
$translation_data = wp_prepare_attachment_for_js( $translated_id );
$photo_data->caption = $translation_data['caption'];
foreach ( $this->url_properties as $property ) {
if ( isset( $photo_data->{$property} ) && $photo_data->{$property} ) {
$photo_data->{$property} = $this->media_translate->translate_image_url( $photo_data->{$property}, $target_lang, $source_lang );
}
}
$node_data->photo_data[ $translated_id ] = $photo_data;
unset( $node_data->photo_data[ $photo_id ] );
}
}
return $node_data;
}
}

View File

@@ -0,0 +1,13 @@
<?php
abstract class WPML_Beaver_Builder_Media_Node {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
protected $media_translate;
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
abstract function translate( $node_data, $target_lang, $source_lang );
}

View File

@@ -0,0 +1,58 @@
<?php
namespace WPML\PB\BeaverBuilder\Modules;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
class ModuleWithItemsFromConfig extends \WPML_Beaver_Builder_Module_With_Items {
/** @var array $fieldDefinitions */
private $fieldDefinitions = [];
/** @var string $itemsField */
private $itemsField;
/**
* @param string $itemsField
* @param array $config
*/
public function __construct( $itemsField, array $config ) {
$this->itemsField = $itemsField;
$this->init( $config );
}
private function init( array $config ) {
$keyByField = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'field' ), Fns::identity() ] );
$this->fieldDefinitions = $keyByField( $config );
}
/**
* @inheritDoc
*/
public function get_title( $field ) {
return Obj::path( [ $field, 'type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_fields() {
return array_keys( $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_editor_type( $field ) {
return Obj::path( [ $field, 'editor_type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function &get_items( $settings ) {
return $settings->{$this->itemsField};
}
}

View File

@@ -0,0 +1,18 @@
<?php
class WPML_Beaver_Builder_Accordion extends WPML_Beaver_Builder_Module_With_Items {
protected function get_title( $field ) {
switch( $field ) {
case 'label':
return esc_html__( 'Accordion Item Label', 'sitepress' );
case 'content':
return esc_html__( 'Accordion Item Content', 'sitepress' );
default:
return '';
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
class WPML_Beaver_Builder_Content_Slider extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->slides;
}
public function get_fields() {
return array( 'title', 'text', 'cta_text', 'link' );
}
protected function get_title( $field ) {
switch( $field ) {
case 'title':
return esc_html__( 'Content Slider: Slide heading', 'sitepress' );
case 'text':
return esc_html__( 'Content Slider: Slide content', 'sitepress' );
case 'cta_text':
return esc_html__( 'Content Slider: Slide call to action text', 'sitepress' );
case 'link':
return esc_html__( 'Content Slider: Slide call to action link', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch( $field ) {
case 'title':
case 'cta_text':
return 'LINE';
case 'link':
return 'LINK';
case 'text':
return 'VISUAL';
default:
return '';
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
class WPML_Beaver_Builder_Icon_Group extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->icons;
}
public function get_fields() {
return array( 'link' );
}
protected function get_title( $field ) {
switch ( $field ) {
case 'link':
return esc_html__( 'Icon link', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch ( $field ) {
case 'link':
return 'LINK';
default:
return '';
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* Class WPML_Beaver_Builder_Module_With_Items
*/
abstract class WPML_Beaver_Builder_Module_With_Items implements IWPML_Page_Builders_Module {
/**
* @param string $field
*
* @return string
*/
abstract protected function get_title( $field );
/** @return array */
protected function get_fields() {
return array( 'label', 'content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
switch( $field ) {
case 'label':
return 'LINE';
case 'content':
return 'VISUAL';
default:
return '';
}
}
/**
* @param object $settings
*
* @return array
*/
protected function &get_items( $settings ) {
return $settings->items;
}
/**
* @param string|int $node_id
* @param object $settings
* @param WPML_PB_String[] $strings
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings, $strings ) {
foreach ( $this->get_items( $settings ) as $item ) {
foreach( $this->get_fields() as $field ) {
if ( is_array( $item->$field ) ) {
foreach ( $item->$field as $key => $value ) {
$strings[] = new WPML_PB_String(
$value,
$this->get_string_name( $node_id, $value, $field, $key ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
} else {
$strings[] = new WPML_PB_String(
$item->$field,
$this->get_string_name( $node_id, $item->$field, $field ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
}
}
return $strings;
}
/**
* @param string|int $node_id
* @param object $settings
* @param WPML_PB_String $string
*
* @return null
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
foreach ( $this->get_items( $settings ) as &$item ) {
foreach( $this->get_fields() as $field ) {
if ( is_array( $item->$field ) ) {
foreach ( $item->$field as $key => &$value ) {
if ( $this->get_string_name( $node_id, $value, $field, $key ) == $string->get_name() ) {
$value = $string->get_value();
}
}
} else {
if ( $this->get_string_name( $node_id, $item->$field, $field ) == $string->get_name() ) {
$item->$field = $string->get_value();
}
}
}
}
return null;
}
private function get_string_name( $node_id, $value, $type, $key = '' ) {
return md5( $value ) . '-' . $type . $key . '-' . $node_id;
}
}

View File

@@ -0,0 +1,58 @@
<?php
class WPML_Beaver_Builder_Pricing_Table extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->pricing_columns;
}
public function get_fields() {
return array( 'title', 'button_text', 'button_url', 'features', 'price', 'duration' );
}
protected function get_title( $field ) {
switch ( $field ) {
case 'title':
return esc_html__( 'Pricing table: Title', 'sitepress' );
case 'button_text':
return esc_html__( 'Pricing table: Button text', 'sitepress' );
case 'button_url':
return esc_html__( 'Pricing table: Button link', 'sitepress' );
case 'features':
return esc_html__( 'Pricing table: Feature', 'sitepress' );
case 'price':
return esc_html__( 'Pricing table: Price', 'sitepress' );
case 'duration':
return esc_html__( 'Pricing table: Duration', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch ( $field ) {
case 'title':
case 'button_text':
case 'price':
case 'duration':
return 'LINE';
case 'button_url':
return 'LINK';
case 'features':
return 'VISUAL';
default:
return '';
}
}
}

View File

@@ -0,0 +1,18 @@
<?php
class WPML_Beaver_Builder_Tab extends WPML_Beaver_Builder_Module_With_Items {
protected function get_title( $field ) {
switch( $field ) {
case 'label':
return esc_html__( 'Tab Item Label', 'sitepress' );
case 'content':
return esc_html__( 'Tab Item Content', 'sitepress' );
default:
return '';
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
class WPML_Beaver_Builder_Testimonials extends WPML_Beaver_Builder_Module_With_Items {
public function &get_items( $settings ) {
return $settings->testimonials;
}
public function get_fields() {
return array( 'testimonial' );
}
protected function get_title( $field ) {
switch( $field ) {
case 'testimonial':
return esc_html__( 'Testimonial content', 'sitepress' );
default:
return '';
}
}
protected function get_editor_type( $field ) {
switch( $field ) {
case 'testimonial':
return 'VISUAL';
default:
return '';
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace WPML\PB\Cornerstone\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'cornerstone-widgets',
'defaultConditionKey' => '_type',
'pbKey' => 'cornerstone',
'translatableWidgetsHook' => 'wpml_cornerstone_modules_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace WPML\PB\Cornerstone\Hooks;
use WPML\FP\Cast;
use WPML\FP\Maybe;
use WPML\FP\Obj;
use WPML\FP\Str;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Frontend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Str::includes( 'themeco/data/save', Obj::prop( 'REQUEST_URI', $_SERVER ) )
&& self::getEditedId() === $translatedPostId
);
} ) );
}
/**
* @return int|null
*/
private static function getEditedId() {
/**
* @see \Cornerstone_Routing::process_params
* $decodeCornerstoneData :: string -> array
*/
$decodeCornerstoneData = function( $data ) {
$request = Obj::prop( 'request', $data );
if ( Obj::prop( 'gzip', $data ) ) {
return (array) json_decode( gzdecode( base64_decode( $request, true ) ), true );
}
return (array) $request;
};
return Maybe::fromNullable( \WP_REST_Server::get_raw_data() )
->map( 'json_decode' )
->map( $decodeCornerstoneData )
->map( Obj::path( [ 'requests', 'builder', 'id' ] ) )
->map( Cast::toInt() )
->getOrElse( null );
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace WPML\PB\Cornerstone\Styles;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Maybe;
use WPML\LIB\WP\Post;
class Hooks implements \IWPML_Backend_Action, \IWPML_Frontend_Action, \IWPML_DIC_Action {
const META_KEY_OLD = '_cs_generated_styles';
const META_KEY_V6 = '_cs_generated_tss';
/** @var callable $shouldInvalidateStyle */
private $shouldInvalidateStyles;
/**
* Hooks constructor.
*
* @param \WPML_PB_Last_Translation_Edit_Mode $lastEditMode
* @param \WPML_Cornerstone_Data_Settings $dataSettings
*/
public function __construct(
\WPML_PB_Last_Translation_Edit_Mode $lastEditMode,
\WPML_Cornerstone_Data_Settings $dataSettings
) {
$this->shouldInvalidateStyles = Logic::both( [ $dataSettings, 'is_handling_post' ], [ $lastEditMode, 'is_translation_editor' ] );
}
public function add_hooks() {
add_action( 'save_post', [ $this, 'invalidateStylesInTranslation' ] );
}
/**
* @param int $postId
*/
public function invalidateStylesInTranslation( $postId ) {
Maybe::of( $postId )
->filter( $this->shouldInvalidateStyles )
->map( Fns::tap( Post::deleteMeta( Fns::__, self::META_KEY_V6 ) ) )
->map( Fns::tap( Post::deleteMeta( Fns::__, self::META_KEY_OLD ) ) );
}
}

View File

@@ -0,0 +1,75 @@
<?php
class WPML_Cornerstone_Data_Settings implements IWPML_Page_Builders_Data_Settings {
/**
* @return string
*/
public function get_meta_field() {
return '_cornerstone_data';
}
/**
* @return string
*/
public function get_node_id_field() {
return '_type';
}
/**
* @return array
*/
public function get_fields_to_copy() {
return array( '_cornerstone_settings', '_cornerstone_version', 'post_content' );
}
/**
* @param array $data
*
* @return array
*/
public function convert_data_to_array( $data ) {
$converted_data = $data;
if ( is_array( $data ) ) {
$converted_data = $data[0];
}
return json_decode( $converted_data, true );
}
/**
* @param array $data
*
* @return string
*/
public function prepare_data_for_saving( array $data ) {
return wp_slash( wp_json_encode( $data ) );
}
/**
* @return string
*/
public function get_pb_name() {
return 'Cornerstone';
}
/**
* @return array
*/
public function get_fields_to_save() {
return array( '_cornerstone_data' );
}
public function add_hooks() {
}
/**
* @param int $postId
*
* @return bool
*/
public function is_handling_post( $postId ) {
return get_post_meta( $postId, $this->get_meta_field(), true )
&& ! get_post_meta( $postId, '_cornerstone_override', true );
}
}

View File

@@ -0,0 +1,8 @@
<?php
class WPML_PB_Cornerstone_Handle_Custom_Fields_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_PB_Handle_Custom_Fields( new WPML_Cornerstone_Data_Settings() );
}
}

View File

@@ -0,0 +1,38 @@
<?php
use function WPML\Container\make;
class WPML_Cornerstone_Integration_Factory {
const SLUG = 'cornerstone';
public function create() {
$action_filter_loader = new WPML_Action_Filter_Loader();
$action_filter_loader->load(
[
'WPML_PB_Cornerstone_Handle_Custom_Fields_Factory',
'WPML_Cornerstone_Media_Hooks_Factory',
\WPML\PB\Cornerstone\Config\Factory::class,
\WPML\PB\Cornerstone\Styles\Hooks::class,
\WPML\PB\Cornerstone\Hooks\Editor::class,
]
);
$nodes = new WPML_Cornerstone_Translatable_Nodes();
$data_settings = new WPML_Cornerstone_Data_Settings();
$string_registration_factory = new WPML_String_Registration_Factory( $data_settings->get_pb_name() );
$string_registration = $string_registration_factory->create();
$factory = make( WPML_PB_Factory::class );
$strategy = make( WPML_PB_API_Hooks_Strategy::class, [ ':name' => $data_settings->get_pb_name() ] );
$strategy->set_factory( $factory );
$reuse_translation = make( WPML_PB_Reuse_Translations_By_Strategy::class, [ ':strategy' => $strategy ] );
$register_strings = new WPML_Cornerstone_Register_Strings( $nodes, $data_settings, $string_registration, $reuse_translation );
$update_translation = new WPML_Cornerstone_Update_Translation( $nodes, $data_settings );
return new WPML_Page_Builders_Integration( $register_strings, $update_translation, $data_settings );
}
}

View File

@@ -0,0 +1,21 @@
<?php
use WPML\PB\Cornerstone\Utils;
class WPML_Cornerstone_Register_Strings extends WPML_Page_Builders_Register_Strings {
/**
* @param array $data_array
* @param array $package
*/
protected function register_strings_for_modules( array $data_array, array $package ) {
foreach ( $data_array as $data ) {
if ( isset( $data['_type'] ) && ! Utils::typeIsLayout( $data['_type'] ) ) {
$this->register_strings_for_node( Utils::getNodeId( $data ), $data, $package );
} elseif ( is_array( $data ) ) {
$this->register_strings_for_modules( $data, $package );
}
}
}
}

View File

@@ -0,0 +1,404 @@
<?php
/**
* WPML_Cornerstone_Translatable_Nodes class file.
*
* @package wpml-page-builders-cornerstone
*/
use WPML\PB\Cornerstone\Modules\ModuleWithItemsFromConfig;
use WPML\FP\Obj;
/**
* Class WPML_Cornerstone_Translatable_Nodes
*/
class WPML_Cornerstone_Translatable_Nodes implements IWPML_Page_Builders_Translatable_Nodes {
const SETTINGS_FIELD = '_modules';
/**
* Nodes to translate.
*
* @var array
*/
protected $nodes_to_translate;
/**
* Get translatable node.
*
* @param string|int $node_id Node id.
* @param array $settings Node settings.
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings ) {
if ( ! $this->nodes_to_translate ) {
$this->initialize_nodes_to_translate();
}
$strings = array();
foreach ( $this->nodes_to_translate as $node_type => $node_data ) {
if ( $this->conditions_ok( $node_data, $settings ) ) {
foreach ( $node_data['fields'] as $field ) {
$field_key = $field['field'];
if ( isset( $settings[ $field_key ] ) && trim( $settings[ $field_key ] ) ) {
$string = new WPML_PB_String(
$settings[ $field_key ],
$this->get_string_name( $node_id, $field, $settings ),
$field['type'],
$field['editor_type'],
$this->get_wrap_tag( $settings )
);
$strings[] = $string;
}
}
foreach ( $this->get_integration_instances( $node_data ) as $node ) {
$strings = $node->get( $node_id, $settings, $strings );
}
}
}
return $strings;
}
/**
* Update translatable node.
*
* @param string $node_id Node id.
* @param array $settings Node settings.
* @param WPML_PB_String $string String object.
*
* @return array
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
if ( ! $this->nodes_to_translate ) {
$this->initialize_nodes_to_translate();
}
foreach ( $this->nodes_to_translate as $node_type => $node_data ) {
if ( $this->conditions_ok( $node_data, $settings ) ) {
foreach ( $node_data['fields'] as $field ) {
$field_key = $field['field'];
if ( $this->get_string_name( $node_id, $field, $settings ) === $string->get_name() ) {
$settings[ $field_key ] = $string->get_value();
}
}
foreach ( $this->get_integration_instances( $node_data ) as $node ) {
$settings = $node->update( $node_id, $settings, $string );
}
}
}
return $settings;
}
/**
* @param array $node_data
*
* @return WPML_Cornerstone_Module_With_Items[]
*/
private function get_integration_instances( $node_data ) {
$instances = [];
if ( isset( $node_data['integration-class'] ) ) {
try {
$instances[] = new $node_data['integration-class']();
// phpcs:disable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch ( Exception $e ) {}
// phpcs:enable Generic.CodeAnalysis.EmptyStatement.DetectedCatch
}
if ( isset( $node_data['fields_in_item'] ) ) {
foreach ( $node_data['fields_in_item'] as $config ) {
$instances[] = new ModuleWithItemsFromConfig( $config );
}
}
return $instances;
}
/**
* Get string name.
*
* @param string $node_id Node id.
* @param array $field Page builder field.
* @param array $settings Node settings.
*
* @return string
*/
public function get_string_name( $node_id, $field, $settings ) {
return $field['field'] . '-' . $settings['_type'] . '-' . $node_id;
}
/**
* Get wrap tag for string.
* Used for SEO, can contain (h1...h6, etc.)
*
* @param array $settings Field settings.
*
* @return string
*/
private function get_wrap_tag( $settings ) {
if ( isset( $settings['_type'] ) && 'headline' === $settings['_type'] ) {
return Obj::propOr( 'h1', 'text_tag', $settings );
}
return '';
}
/**
* Check if node condition is ok.
*
* @param array $node_data Node data.
* @param array $settings Node settings.
*
* @return bool
*/
private function conditions_ok( $node_data, $settings ) {
$conditions_meet = true;
foreach ( $node_data['conditions'] as $field_key => $field_value ) {
if ( ! isset( $settings[ $field_key ] ) || $settings[ $field_key ] !== $field_value ) {
$conditions_meet = false;
break;
}
}
return $conditions_meet;
}
/**
* @return array[]
*/
public static function get_nodes_to_translate() {
return [
'card' => [
'conditions' => [ '_type' => 'card' ],
'fields' => [
[
'field' => 'card_front_text_content',
'type' => __( 'Card: front text content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
[
'field' => 'card_back_text_content',
'type' => __( 'Card: back text content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
[
'field' => 'anchor_text_primary_content',
'type' => __( 'Card: anchor text primary content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
[
'field' => 'anchor_text_secondary_content',
'type' => __( 'Card: anchor text secondary content', 'sitepress' ),
'editor_type' => 'LINE',
],
[
'field' => 'anchor_href',
'type' => __( 'Card: anchor link', 'sitepress' ),
'editor_type' => 'LINK',
],
],
],
'alert' => [
'conditions' => [ '_type' => 'alert' ],
'fields' => [
[
'field' => 'alert_content',
'type' => __( 'Alert Content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'text' => [
'conditions' => [ '_type' => 'text' ],
'fields' => [
[
'field' => 'text_content',
'type' => __( 'Text content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'quote' => [
'conditions' => [ '_type' => 'quote' ],
'fields' => [
[
'field' => 'quote_content',
'type' => __( 'Quote content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
[
'field' => 'quote_cite_content',
'type' => __( 'Quote: cite content', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'counter' => [
'conditions' => [ '_type' => 'counter' ],
'fields' => [
[
'field' => 'counter_number_prefix_content',
'type' => __( 'Counter: number prefix', 'sitepress' ),
'editor_type' => 'LINE',
],
[
'field' => 'counter_number_suffix_content',
'type' => __( 'Counter: number suffix', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'content-area' => [
'conditions' => [ '_type' => 'content-area' ],
'fields' => [
[
'field' => 'content',
'type' => __( 'Content Area: content', 'sitepress' ),
'editor_type' => 'AREA',
],
],
],
'breadcrumbs' => [
'conditions' => [ '_type' => 'breadcrumbs' ],
'fields' => [
[
'field' => 'breadcrumbs_home_label_text',
'type' => __( 'Breadcrumbs: home label text', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'audio' => [
'conditions' => [ '_type' => 'audio' ],
'fields' => [
[
'field' => 'audio_embed_code',
'type' => __( 'Audio: embed code', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'headline' => [
'conditions' => [ '_type' => 'headline' ],
'fields' => [
[
'field' => 'text_content',
'type' => __( 'Headline text content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'content-area-off-canvas' => [
'conditions' => [ '_type' => 'content-area-off-canvas' ],
'fields' => [
[
'field' => 'off_canvas_content',
'type' => __( 'Canvas content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'content-area-modal' => [
'conditions' => [ '_type' => 'content-area-modal' ],
'fields' => [
[
'field' => 'modal_content',
'type' => __( 'Modal content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'content-area-dropdown' => [
'conditions' => [ '_type' => 'content-area-dropdown' ],
'fields' => [
[
'field' => 'dropdown_content',
'type' => __( 'Dropdown content', 'sitepress' ),
'editor_type' => 'VISUAL',
],
],
],
'button' => [
'conditions' => [ '_type' => 'button' ],
'fields' => [
[
'field' => 'anchor_text_primary_content',
'type' => __( 'Anchor text: primary content', 'sitepress' ),
'editor_type' => 'LINE',
],
[
'field' => 'anchor_text_secondary_content',
'type' => __( 'Anchor text: secondary content', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'video' => [
'conditions' => [ '_type' => 'video' ],
'fields' => [
[
'field' => 'video_embed_code',
'type' => __( 'Video: embed code', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'search-inline' => [
'conditions' => [ '_type' => 'search-inline' ],
'fields' => [
[
'field' => 'search_placeholder',
'type' => __( 'Search Inline: placeholder', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'search-modal' => [
'conditions' => [ '_type' => 'search-modal' ],
'fields' => [
[
'field' => 'search_placeholder',
'type' => __( 'Search Modal: placeholder', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'search-dropdown' => [
'conditions' => [ '_type' => 'search-dropdown' ],
'fields' => [
[
'field' => 'search_placeholder',
'type' => __( 'Search Dropdown: placeholder', 'sitepress' ),
'editor_type' => 'LINE',
],
],
],
'accordion' => [
'conditions' => [ '_type' => 'accordion' ],
'fields' => [],
'integration-class' => 'WPML_Cornerstone_Accordion',
],
'tabs' => [
'conditions' => [ '_type' => 'tabs' ],
'fields' => [],
'integration-class' => 'WPML_Cornerstone_Tabs',
],
];
}
/**
* Initialize translatable nodes.
*/
public function initialize_nodes_to_translate() {
$this->nodes_to_translate = apply_filters( 'wpml_cornerstone_modules_to_translate', self::get_nodes_to_translate() );
}
}

View File

@@ -0,0 +1,34 @@
<?php
use WPML\PB\Cornerstone\Utils;
class WPML_Cornerstone_Update_Translation extends WPML_Page_Builders_Update_Translation {
/** @param array $data_array */
public function update_strings_in_modules( array &$data_array ) {
foreach ( $data_array as $key => &$data ) {
if ( isset( $data['_type'] ) && ! Utils::typeIsLayout( $data['_type'] ) ) {
$data = $this->update_strings_in_node( Utils::getNodeId( $data ), $data );
} elseif ( is_array( $data ) ) {
$this->update_strings_in_modules( $data );
}
}
}
/**
* @param string $node_id
* @param array $settings
*
* @return mixed
*/
protected function update_strings_in_node( $node_id, $settings ) {
$strings = $this->translatable_nodes->get( $node_id, $settings );
foreach ( $strings as $string ) {
$translation = $this->get_translation( $string );
$settings = $this->translatable_nodes->update( $node_id, $settings, $translation );
}
return $settings;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\PB\Cornerstone;
class Utils {
const MODULE_TYPE_PREFIX = 'classic:';
/**
* @param array $data
* @return string
*/
public static function getNodeId( $data ) {
return md5( serialize( $data ) );
}
/**
* Check if the type is a layout type.
*
* @param string $type The type to check.
* @return bool
*/
public static function typeIsLayout( $type ) {
// Remove the classic prefix before checking.
$type = preg_replace( '/^' . self::MODULE_TYPE_PREFIX . '/', '', $type );
return in_array(
$type,
[ 'bar', 'container', 'section', 'row', 'column', 'layout-row', 'layout-column', 'layout-grid', 'layout-cell' ],
true
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Hooks_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
return new WPML_Page_Builders_Media_Hooks(
new WPML_Cornerstone_Update_Media_Factory(),
WPML_Cornerstone_Integration_Factory::SLUG
);
}
}

View File

@@ -0,0 +1,61 @@
<?php
class WPML_Cornerstone_Media_Node_Provider {
private $media_translate;
private $nodes = array();
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param string $type
*
* @return WPML_Cornerstone_Media_Node|null
*/
public function get( $type ) {
if ( ! array_key_exists( $type, $this->nodes ) ) {
$this->add( $type );
}
return $this->nodes[ $type ];
}
/**
* @param string $type
*/
private function add( $type ) {
switch ( $type ) {
case 'image':
$node = new WPML_Cornerstone_Media_Node_Image( $this->media_translate );
break;
case 'classic:creative-cta':
$node = new WPML_Cornerstone_Media_Node_Classic_Creative_CTA( $this->media_translate );
break;
case 'classic:feature-box':
$node = new WPML_Cornerstone_Media_Node_Classic_Feature_Box( $this->media_translate );
break;
case 'classic:card':
$node = new WPML_Cornerstone_Media_Node_Classic_Card( $this->media_translate );
break;
case 'classic:image':
$node = new WPML_Cornerstone_Media_Node_Classic_Image( $this->media_translate );
break;
case 'classic:promo':
$node = new WPML_Cornerstone_Media_Node_Classic_Promo( $this->media_translate );
break;
default:
$node = null;
}
$this->nodes[ $type ] = $node;
}
}

View File

@@ -0,0 +1,48 @@
<?php
class WPML_Cornerstone_Media_Nodes_Iterator implements IWPML_PB_Media_Nodes_Iterator {
const ITEMS_FIELD = WPML_Cornerstone_Module_With_Items::ITEMS_FIELD;
/** @var WPML_Cornerstone_Media_Node_Provider $node_provider */
private $node_provider;
public function __construct( WPML_Cornerstone_Media_Node_Provider $node_provider ) {
$this->node_provider = $node_provider;
}
/**
* @param array $data_array
* @param string $lang
* @param string $source_lang
*
* @return array
*/
public function translate( $data_array, $lang, $source_lang ) {
foreach ( $data_array as $key => &$data ) {
if ( isset( $data[ self::ITEMS_FIELD ] ) && $data[ self::ITEMS_FIELD ] ) {
$data[ self::ITEMS_FIELD ] = $this->translate( $data[ self::ITEMS_FIELD ], $lang, $source_lang );
} elseif ( is_numeric( $key ) && isset( $data['_type'] ) ) {
$data = $this->translate_node( $data, $lang, $source_lang );
}
}
return $data_array;
}
/**
* @param stdClass $settings
* @param string $lang
* @param string $source_lang
*
* @return stdClass
*/
private function translate_node( $settings, $lang, $source_lang ) {
$node = $this->node_provider->get( $settings['_type'] );
if ( $node ) {
$settings = $node->translate( $settings, $lang, $source_lang );
}
return $settings;
}
}

View File

@@ -0,0 +1,33 @@
<?php
class WPML_Cornerstone_Update_Media_Factory implements IWPML_PB_Media_Update_Factory {
/** @var WPML_Page_Builders_Media_Translate|null $media_translate */
private $media_translate;
public function create() {
global $sitepress;
return new WPML_Page_Builders_Update_Media(
new WPML_Page_Builders_Update( new WPML_Cornerstone_Data_Settings() ),
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Cornerstone_Media_Nodes_Iterator(
new WPML_Cornerstone_Media_Node_Provider( $this->get_media_translate() )
),
new WPML_Page_Builders_Media_Usage( $this->get_media_translate(), new WPML_Media_Usage_Factory() )
);
}
/** @return WPML_Page_Builders_Media_Translate */
private function get_media_translate() {
global $sitepress;
if ( ! $this->media_translate ) {
$this->media_translate = new WPML_Page_Builders_Media_Translate(
new WPML_Translation_Element_Factory( $sitepress ),
new WPML_Media_Image_Translate( $sitepress, new WPML_Media_Attachment_By_URL_Factory() )
);
}
return $this->media_translate;
}
}

View File

@@ -0,0 +1,30 @@
<?php
abstract class WPML_Cornerstone_Media_Node_With_URLs extends WPML_Cornerstone_Media_Node {
/** @return array */
abstract protected function get_keys();
/**
* @param array $node_data
* @param string $target_lang
* @param string $source_lang
*
* @return array
*/
public function translate( $node_data, $target_lang, $source_lang ) {
foreach ( $this->get_keys() as $key ) {
if ( ! empty( $node_data[ $key ] ) ) {
list( $attachment_id, $type ) = explode( ':', $node_data[ $key ], 2 );
if ( is_numeric( $attachment_id ) ) {
$attachment_id = apply_filters( 'wpml_object_id', $attachment_id, 'attachment', true, $target_lang );
$node_data[ $key ] = $attachment_id . ':' . $type;
} else {
$node_data[ $key ] = $this->media_translate->translate_image_url( $node_data[ $key ], $target_lang, $source_lang );
}
}
}
return $node_data;
}
}

View File

@@ -0,0 +1,20 @@
<?php
abstract class WPML_Cornerstone_Media_Node {
/** @var WPML_Page_Builders_Media_Translate $media_translate */
protected $media_translate;
public function __construct( WPML_Page_Builders_Media_Translate $media_translate ) {
$this->media_translate = $media_translate;
}
/**
* @param array $node_data
* @param string $target_lang
* @param string $source_lang
*
* @return array
*/
abstract function translate( $node_data, $target_lang, $source_lang );
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Classic_Card extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'front_image',
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Classic_Creative_CTA extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image',
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Classic_Feature_Box extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'graphic_image',
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Classic_Image extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'src',
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Classic_Promo extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image',
);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class WPML_Cornerstone_Media_Node_Image extends WPML_Cornerstone_Media_Node_With_URLs {
protected function get_keys() {
return array(
'image_src',
);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace WPML\PB\Cornerstone\Modules;
use WPML\FP\Fns;
use WPML\FP\Lst;
use WPML\FP\Obj;
class ModuleWithItemsFromConfig extends \WPML_Cornerstone_Module_With_Items {
/** @var array $fieldDefinitions */
private $fieldDefinitions;
public function __construct( array $config ) {
$keyByField = Fns::converge( Lst::zipObj(), [ Lst::pluck( 'field' ), Fns::identity() ] );
$this->fieldDefinitions = $keyByField( $config );
}
/**
* @inheritDoc
*/
public function get_title( $field ) {
return Obj::path( [ $field, 'type' ], $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_fields() {
return array_keys( $this->fieldDefinitions );
}
/**
* @inheritDoc
*/
public function get_editor_type( $field ) {
return Obj::path( [ $field, 'editor_type' ], $this->fieldDefinitions );
}
}

View File

@@ -0,0 +1,41 @@
<?php
class WPML_Cornerstone_Accordion extends WPML_Cornerstone_Module_With_Items {
/**
* @return array
*/
public function get_fields() {
return array( 'accordion_item_header_content', 'accordion_item_content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_title( $field ) {
if ( 'accordion_item_header_content' === $field ) {
return esc_html__( 'Accordion: header content', 'sitepress' );
}
if ( 'accordion_item_content' === $field ) {
return esc_html__( 'Accordion: content', 'sitepress' );
}
return '';
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
if ( 'accordion_item_header_content' === $field ) {
return 'LINE';
} else {
return 'VISUAL';
}
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Class WPML_Cornerstone_Module_With_Items
*/
abstract class WPML_Cornerstone_Module_With_Items implements IWPML_Page_Builders_Module {
const ITEMS_FIELD = WPML_Cornerstone_Translatable_Nodes::SETTINGS_FIELD;
/**
* @param string $field
*
* @return string
*/
abstract protected function get_title( $field );
/** @return array */
abstract protected function get_fields();
/**
* @param string $field
*
* @return string
*/
abstract protected function get_editor_type( $field );
/**
* @param array $settings
*
* @return array
*/
protected function get_items( $settings ) {
return $settings[ self::ITEMS_FIELD ];
}
/**
* @param string|int $node_id
* @param array $settings
* @param WPML_PB_String[] $strings
*
* @return WPML_PB_String[]
*/
public function get( $node_id, $settings, $strings ) {
foreach ( $this->get_items( $settings ) as $item ) {
foreach ( $this->get_fields() as $field ) {
if ( is_array( $item[ $field ] ) ) {
foreach ( $item[ $field ] as $key => $value ) {
$strings[] = new WPML_PB_String(
$value,
$this->get_string_name( $node_id, $value, $field, $key ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
} else {
$strings[] = new WPML_PB_String(
$item[ $field ],
$this->get_string_name( $node_id, $item[ $field ], $field ),
$this->get_title( $field ),
$this->get_editor_type( $field )
);
}
}
}
return $strings;
}
/**
* @param string|int $node_id
* @param array $settings
* @param WPML_PB_String $string
*
* @return array
*/
public function update( $node_id, $settings, WPML_PB_String $string ) {
foreach ( $this->get_items( $settings ) as $key => $item ) {
foreach ( $this->get_fields() as $field ) {
if ( $this->get_string_name( $node_id, $item[ $field ], $field ) === $string->get_name() ) {
$settings[ self::ITEMS_FIELD ][ $key ][ $field ] = $string->get_value();
}
}
}
return $settings;
}
private function get_string_name( $node_id, $value, $type, $key = '' ) {
return md5( $value ) . '-' . $type . $key . '-' . $node_id;
}
}

View File

@@ -0,0 +1,41 @@
<?php
class WPML_Cornerstone_Tabs extends WPML_Cornerstone_Module_With_Items {
/**
* @return array
*/
public function get_fields() {
return array( 'tab_label_content', 'tab_content' );
}
/**
* @param string $field
*
* @return string
*/
protected function get_title( $field ) {
if ( 'tab_label_content' === $field ) {
return esc_html__( 'Tabs: label', 'sitepress' );
}
if ( 'tab_content' === $field ) {
return esc_html__( 'Tabs: content', 'sitepress' );
}
return '';
}
/**
* @param string $field
*
* @return string
*/
protected function get_editor_type( $field ) {
if ( 'tab_label_content' === $field ) {
return 'LINE';
} else {
return 'VISUAL';
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace WPML\Compatibility\Divi;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\FP\Obj;
class DisplayConditions implements \IWPML_Frontend_Action {
const BASE64_EMPTY_ARRAY = 'W10=';
public function add_hooks() {
Hooks::onFilter( 'et_pb_module_shortcode_attributes' )
->then( spreadArgs( [ $this, 'translateAttributes' ] ) );
}
/**
* @param array $atts
* @return array
*/
public function translateAttributes( $atts ) {
$displayConditions = Obj::prop( 'display_conditions', $atts );
if ( $displayConditions && self::BASE64_EMPTY_ARRAY !== $displayConditions ) {
/* phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode */
$conditions = json_decode( base64_decode( $atts['display_conditions'] ), true );
foreach ( $conditions as &$condition ) {
if ( 'categoryPage' === $condition['condition'] ) {
foreach ( $condition['conditionSettings']['categories'] as &$category ) {
$category['value'] = (string) apply_filters( 'wpml_object_id', $category['value'], $category['groupSlug'] );
}
}
}
/* phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode */
$atts['display_conditions'] = base64_encode( wp_json_encode( $conditions ) );
}
return $atts;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace WPML\Compatibility\Divi;
/**
* Divi replaces double quotes with %22 when saving shortcode attributes.
* ATE needs valid HTML so we temporarily decode the double quotes.
* When we receive the translation we undo the change.
*
* @package WPML\Compatibility\Divi
*/
class DoubleQuotes implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
public function add_hooks() {
add_filter( 'wpml_pb_shortcode_decode', [ $this, 'decode' ], -PHP_INT_MAX, 2 );
add_filter( 'wpml_pb_shortcode_encode', [ $this, 'encode' ], PHP_INT_MAX, 2 );
}
/**
* @param string $string
* @param string $encoding
*
* @return string
*/
public function decode( $string, $encoding ) {
if ( self::canHaveDoubleQuotes( $string, $encoding ) ) {
$string = str_replace( '%22', '"', $string );
}
return $string;
}
/**
* @param string $string
* @param string $encoding
*
* @return string
*/
public function encode( $string, $encoding ) {
if ( self::canHaveDoubleQuotes( $string, $encoding ) ) {
$string = str_replace( '"', '%22', $string );
}
return $string;
}
/**
* @param string $string
* @param string $encoding
*
* @return bool
*/
private static function canHaveDoubleQuotes( $string, $encoding ) {
return is_string( $string ) && 'allow_html_tags' === $encoding;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\PB\Helper\LanguageNegotiation;
class DomainsBackendEditor implements \IWPML_Backend_Action {
public function add_hooks() {
if ( LanguageNegotiation::isUsingDomains()
&& self::isPostEditor()
&& self::getDomainByCurrentPostLanguage() !== $_SERVER['HTTP_HOST']
) {
Hooks::onAction( 'admin_notices' )
->then( spreadArgs( [ $this, 'displayNotice' ] ) );
}
}
public function displayNotice() {
$url = ( is_ssl() ? 'https://' : 'http://' ) . self::getDomainByCurrentPostLanguage() . $_SERVER['REQUEST_URI'];
?>
<div class="notice notice-warning is-dismissible">
<p>
<?php
echo sprintf(
// translators: placeholders are opening and closing <a> tag.
esc_html__( "It is not possible to use Divi's backend builder to edit a post in a different language than your domain. Please use Divi's frontend builder to edit this post or %1\$s switch to the correct domain %2\$s to use the backend builder.", 'sitepress' ),
sprintf( '<a href="%s">', esc_url( $url ) ),
'</a>'
);
?>
</p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text">Dismiss this notice.</span>
</button>
</div>
<?php
}
/**
* @return bool
*/
private static function isPostEditor() {
global $pagenow;
return 'post.php' === $pagenow
&& self::getPostId();
}
/**
* @return int
*/
private static function getPostId() {
/* phpcs:ignore WordPress.CSRF.NonceVerification.NoNonceVerification */
return (int) Obj::prop( 'post', $_GET );
}
/**
* @return string|null
*/
private static function getDomainByCurrentPostLanguage() {
$postDetails = apply_filters( 'wpml_post_language_details', null, self::getPostId() );
$language = Obj::prop( 'language_code', $postDetails );
return LanguageNegotiation::getDomainByLanguage( $language );
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
Relation::propEq( 'action', 'et_fb_ajax_save', $_POST )
&& (int) Obj::prop( 'post_id', $_POST ) === $translatedPostId
);
} ) );
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace WPML\Compatibility\Divi\Hooks;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class GutenbergUpdate implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_post_built_with_shortcodes', 10, 2 )
->then( spreadArgs( [ $this, 'isPostBuiltWithShortcodes' ] ) );
}
/**
* @param string $builtWithShortcodes
* @param \WP_Post $post
*
* @return bool
*/
public static function isPostBuiltWithShortcodes( $builtWithShortcodes, $post ) {
return self::isDiviPost( $post->ID ) || $builtWithShortcodes;
}
/**
* @param int $postId
*
* @return bool
*/
private static function isDiviPost( $postId ) {
return 'on' === get_post_meta( $postId, '_et_pb_use_builder', true );
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\Compatibility\Divi;
use WPML\FP\Obj;
class TinyMCE implements \IWPML_Backend_Action {
public function add_hooks() {
if ( defined( 'WPML_TM_FOLDER' ) ) {
add_filter( 'tiny_mce_before_init', [ $this, 'filterEditorAutoTags' ] );
}
}
/**
* @param array $config
*
* @return array
*/
public function filterEditorAutoTags( $config ) {
if ( did_action( 'admin_init' ) ) {
$screen = get_current_screen();
$cteUrl = 'wpml_page_' . constant( 'WPML_TM_FOLDER' ) . '/menu/translations-queue';
if ( Obj::prop( 'id', $screen ) === $cteUrl ) {
$config['wpautop'] = false;
$config['indent'] = true;
$config['tadv_noautop'] = true;
}
}
return $config;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace WPML\Compatibility\Divi;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\FP\Obj;
class WooShortcodes implements \IWPML_Frontend_Action {
const WOO_SHORTCODES = [
'et_pb_wc_description',
'et_pb_wc_title',
];
public function add_hooks() {
Hooks::onFilter( 'et_pb_module_shortcode_attributes', 10, 3 )
->then( spreadArgs( [ $this, 'translateAttributes' ] ) );
}
/**
* @param array $shortcodeAttrs
* @param array $attrs
* @param string $slug
*
* @return array
*/
public function translateAttributes( $shortcodeAttrs, $attrs, $slug ) {
if ( in_array( $slug, self::WOO_SHORTCODES, true ) && (int) Obj::prop( 'product', $shortcodeAttrs ) ) {
$shortcodeAttrs['product'] = apply_filters( 'wpml_object_id', $shortcodeAttrs['product'], 'product', true );
}
return $shortcodeAttrs;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace WPML\Compatibility\Divi;
class Builder implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_AJAX_Action {
public function add_hooks() {
add_filter( 'theme_locale', [ $this, 'switch_to_user_language' ] );
}
public function switch_to_user_language( $locale ) {
if ( isset( $_POST['action'] ) && ( 'et_fb_update_builder_assets' === $_POST['action'] ) ) { // phpcs:ignore WordPress.CSRF.NonceVerification
return get_user_locale();
}
return $locale;
}
}

View File

@@ -0,0 +1,281 @@
<?php
class WPML_Compatibility_Divi implements \IWPML_DIC_Action, \IWPML_Backend_Action, \IWPML_Frontend_Action {
const REGEX_REMOVE_OPENING_PARAGRAPH = '/(<p>[\n\r]*)([\n\r]{1}\[\/et_)/m';
const REGEX_REMOVE_CLOSING_PARAGRAPH = '/(\[et_.*\][\n\r]{1})([\n\r]*<\/p>)/m';
/** @var SitePress */
private $sitepress;
/**
* @param SitePress $sitepress
*/
public function __construct( SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
public function add_hooks() {
if ( $this->sitepress->is_setup_complete() ) {
add_action( 'init', [ $this, 'load_resources_if_they_are_required' ], 10, 0 );
add_filter( 'et_builder_load_actions', [ $this, 'load_builder_for_ajax_actions' ] );
add_action( 'admin_init', [ $this, 'display_warning_notice' ], 10, 0 );
add_filter( 'wpml_pb_should_handle_content', [ $this, 'should_handle_shortcode_content' ], 10, 2 );
add_filter( 'wpml_pb_shortcode_content_for_translation', [ $this, 'cleanup_global_layout_content' ], 10, 2 );
add_filter( 'icl_job_elements', [ $this, 'remove_old_content_from_translation' ], 10, 2 );
add_filter( 'wpml_words_count_custom_fields_to_count', [ $this, 'remove_old_content_from_words_count' ], 10, 2 );
}
}
/**
* @return bool
*/
private function is_standard_editor_used() {
$tm_settings = $this->sitepress->get_setting( 'translation-management', [] );
return ! isset( $tm_settings['doc_translation_method'] ) ||
ICL_TM_TMETHOD_MANUAL === $tm_settings['doc_translation_method'];
}
public function display_warning_notice() {
$notices = wpml_get_admin_notices();
if ( $this->is_standard_editor_used() ) {
$notices->add_notice( new WPML_Compatibility_Divi_Notice() );
} elseif ( $notices->get_notice( WPML_Compatibility_Divi_Notice::ID, WPML_Compatibility_Divi_Notice::GROUP ) ) {
$notices->remove_notice( WPML_Compatibility_Divi_Notice::GROUP, WPML_Compatibility_Divi_Notice::ID );
}
}
/**
* These actions require the custom widget area to be initialized.
*
* @param array $actions
* @return array
*/
public function load_builder_for_ajax_actions( $actions ) {
$actions[] = 'save-widget';
$actions[] = 'widgets-order';
$actions[] = 'wpml-ls-save-settings';
return $actions;
}
public function load_resources_if_they_are_required() {
if ( ! isset( $_GET['page'] ) || ! is_admin() ) { /* phpcs:ignore */
return;
}
$pages = [ self::get_duplication_action_page() ];
if ( self::is_tm_active() ) {
$pages[] = self::get_translation_dashboard_page();
$pages[] = self::get_translation_editor_page();
}
if ( self::is_sl_active() ) {
$pages[] = self::get_sl_page();
}
if ( in_array( $_GET['page'], $pages, true ) ) { /* phpcs:ignore */
$this->register_layouts();
}
}
private static function get_translation_dashboard_page() {
return constant( 'WPML_TM_FOLDER' ) . '/menu/main.php';
}
private static function get_translation_editor_page() {
return constant( 'WPML_TM_FOLDER' ) . '/menu/translations-queue.php';
}
private static function get_duplication_action_page() {
return constant( 'WPML_PLUGIN_FOLDER' ) . '/menu/languages.php';
}
private static function get_sl_page() {
return 'wpml-sticky-links';
}
private static function is_tm_active() {
return defined( 'WPML_TM_FOLDER' );
}
private static function is_sl_active() {
return defined( 'WPML_STICKY_LINKS_VERSION' );
}
private function register_layouts() {
/**
* @phpstan-ignore-next-line
*/
if ( function_exists( 'et_builder_should_load_framework' ) && ! et_builder_should_load_framework() ) {
if ( function_exists( 'et_builder_register_layouts' ) ) {
/**
* @phpstan-ignore-next-line
*/
et_builder_register_layouts();
} else {
$lib_file = ET_BUILDER_DIR . 'feature/Library.php';
if ( ! class_exists( 'ET_Builder_Library' )
&& defined( 'ET_BUILDER_DIR' )
&& file_exists( $lib_file )
) {
require_once $lib_file;
}
if ( class_exists( 'ET_Builder_Library' ) ) {
ET_Builder_Library::instance();
}
}
}
}
/**
* The global layout is not properly extracted from the page
* because it adds <p> tags either not opened or not closed.
*
* See the global content below as an example:
*
* [et_pb_section prev_background_color="#000000" next_background_color="#000000"][et_pb_text]
*
* </p>
* <p>Global text 1 EN5</p>
* <p>
*
* [/et_pb_text][/et_pb_section]
*
* We also need to remove `prev_background` and `next_background` attributes which are added from the page.
*
* @param string $content
* @param int $post_id
*/
public function cleanup_global_layout_content( $content, $post_id ) {
if ( 'et_pb_layout' === get_post_type( $post_id ) ) {
$content = preg_replace( self::REGEX_REMOVE_OPENING_PARAGRAPH, '$2', $content );
$content = preg_replace( self::REGEX_REMOVE_CLOSING_PARAGRAPH, '$1', $content );
$content = preg_replace( '/( prev_background_color="#[0-9a-f]*")/', '', $content );
$content = preg_replace( '/( next_background_color="#[0-9a-f]*")/', '', $content );
}
return $content;
}
public function should_handle_shortcode_content( $handle_content, $shortcode ) {
if (
strpos( $shortcode['tag'], 'et_' ) === 0 &&
strpos( $shortcode['attributes'], 'global_module=' ) !== false
) {
// If a translatable attribute has been excluded from sync, we need to handle it.
$handle_content = $this->is_excluded_from_sync( $shortcode );
}
return $handle_content;
}
/**
* Check if a global module has excluded any translatable text that we need to handle
*
* @param array $shortcode
* {
* @type string $tag.
* @type string $content.
* @type string $attributes.
* }
* @return bool
*/
private function is_excluded_from_sync( $shortcode ) {
$handle_content = false;
preg_match( '/global_module="([0-9]+)"/', $shortcode['attributes'], $matches );
$excluded = json_decode( get_post_meta( $matches[1], '_et_pb_excluded_global_options', true ), true );
if ( is_array( $excluded ) && count( $excluded ) > 0 ) {
$attributes = $this->get_translatable_shortcode_attributes( $shortcode['tag'] );
foreach ( $excluded as $field ) {
if ( in_array( $field, $attributes, true ) ) {
$handle_content = true;
break;
}
}
}
return $handle_content;
}
/**
* Get a list of translatable attributes for a shortcode tag.
* This includes the inner content and any attributes found in XML configuration.
*
* @param string $tag The shortcode tag.
* @return array
*/
private function get_translatable_shortcode_attributes( $tag ) {
$attributes = [ 'et_pb_content_field' ];
$settings = get_option( 'icl_st_settings', [] );
if ( ! isset( $settings['pb_shortcode'] ) ) {
return $attributes;
}
foreach ( $settings['pb_shortcode'] as $setting ) {
if ( $tag === $setting['tag']['value'] ) {
foreach ( $setting['attributes'] as $attribute ) {
if ( empty( $attribute['type'] ) ) {
$attributes[] = $attribute['value'];
}
}
break;
}
}
return $attributes;
}
/**
* Remove the `_et_pb_old_content` meta field from translation jobs, except for products.
*
* @param array $fields Array of fields to translate.
* @param object $post_id The ID of the post being translated.
*
* @return array
*/
public function remove_old_content_from_translation( $fields, $post_id ) {
// Bail out early if its a product.
if ( 'product' === get_post_type( $post_id ) ) {
return $fields;
}
// Search for the _et_pb_old_content element and empty it.
$field_types = wp_list_pluck( $fields, 'field_type' );
$index = array_search( 'field-_et_pb_old_content-0', $field_types, true );
if ( false !== $index ) {
$fields[ $index ]->field_data = '';
$fields[ $index ]->field_data_translated = '';
}
return $fields;
}
/**
* Remove the `_et_pb_old_content` meta field from words count, except for products.
*
* @param array $fields_to_count Array of custom fields to count.
* @param object $post_id The ID of the post for which we are counting the words.
*
* @return array
*/
public function remove_old_content_from_words_count( $fields_to_count, $post_id ) {
if ( 'product' !== get_post_type( $post_id ) ) {
$index = array_search( '_et_pb_old_content', $fields_to_count, true );
if ( false !== $index ) {
unset( $fields_to_count[ $index ] );
}
}
return $fields_to_count;
}
}

View File

@@ -0,0 +1,42 @@
<?php
class WPML_Compatibility_Divi_Notice extends WPML_Notice {
const ID = 'wpml-compatibility-divi-editor-warning';
const GROUP = 'wpml-compatibility-divi';
public function __construct() {
parent::__construct( self::ID, $this->get_message(), self::GROUP );
$this->set_dismissible( true );
$this->set_css_class_types( 'warning' );
}
/**
* @return string
*/
private function get_message() {
$msg = esc_html_x(
'You are using DIVI theme, and you have chosen to use the standard editor for translating content.',
'Use Translation Editor notice 1/3',
'sitepress'
);
$msg .= ' ' . esc_html_x(
'Some functionalities may not work properly. We encourage you to switch to use the Translation Editor.',
'Use Translation Editor notice 2/3',
'sitepress'
);
$msg .= ' ' . sprintf(
/* translators: %s will be replaced with a URL. */
esc_html_x(
'You can find more information here: %s',
'Use Translation Editor notice 2/3',
'sitepress'
),
'<a href="https://wpml.org/errata/some-internal-taxonomies-will-be-missing-when-you-translate-divi-layouts/?utm_source=plugin&utm_medium=gui&utm_campaign=wpmlcore">Some internal taxonomies will be missing when you translate Divi layouts</a>'
);
return $msg;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace WPML\Compatibility\Divi;
class DiviOptionsEncoding implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
const CHARS_ENCODED = [ '%22', '%91', '%93' ];
const CHARS_DECODED = [ '"', '[', ']' ];
const DELIMITER = '_';
const TRANSLATABLE_KEYS = [ 'value', 'link_url', 'link_text' ];
public function add_hooks() {
add_filter( 'wpml_pb_shortcode_decode', [ $this, 'decode_divi_options' ], 10, 2 );
add_filter( 'wpml_pb_shortcode_encode', [ $this, 'encode_divi_options' ], 10, 2 );
}
public function decode_divi_options( $string, $encoding ) {
if ( 'divi_options' === $encoding ) {
$options = str_replace( self::CHARS_ENCODED, self::CHARS_DECODED, $string );
$options = json_decode( $options, true );
$string = [];
foreach ( $options as $index => $option ) {
foreach ( $option as $key => $value ) {
$string[ $key . self::DELIMITER . $index ] = [
'value' => $value,
'translate' => in_array( $key, self::TRANSLATABLE_KEYS, true ),
];
}
}
}
return $string;
}
public function encode_divi_options( $string, $encoding ) {
if ( 'divi_options' === $encoding ) {
$output = [];
foreach ( $string as $combined_key => $value ) {
$parts = explode( self::DELIMITER, $combined_key );
$index = array_pop( $parts );
$key = implode( self::DELIMITER, $parts );
$output[ $index ][ $key ] = $value;
}
$output = wp_json_encode( $output, JSON_UNESCAPED_UNICODE );
$string = str_replace( self::CHARS_DECODED, self::CHARS_ENCODED, $output );
}
return $string;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace WPML\Compatibility\Divi;
use WPML\Compatibility\BaseDynamicContent;
class DynamicContent extends BaseDynamicContent {
const ENCODED_CONTENT_START = '@ET-DC@';
const ENCODED_CONTENT_END = '@';
/** @var array */
protected $positions = [ 'before', 'after' ];
/**
* Sets $positions dynamic content to be translatable.
*
* @param string $string The decoded string so far.
* @param string $encoding The encoding used.
*
* @return string|array
*/
public function decode_dynamic_content( $string, $encoding ) {
if ( $this->is_dynamic_content( $string ) ) {
$field = $this->decode_field( $string );
$decodedContent = [
'et-dynamic-content' => [
'value' => $string,
'translate' => false,
],
];
foreach ( $this->positions as $position ) {
if ( ! empty( $field['settings'][ $position ] ) ) {
$decodedContent[ $position ] = [
'value' => $field['settings'][ $position ],
'translate' => true,
];
}
}
return $decodedContent;
}
return $string;
}
/**
* Rebuilds dynamic content with translated strings.
*
* @param string|array $string The field array or string.
* @param string $encoding The encoding used.
*
* @return string
*/
public function encode_dynamic_content( $string, $encoding ) {
if ( is_array( $string ) && isset( $string['et-dynamic-content'] ) ) {
$field = $this->decode_field( $string['et-dynamic-content'] );
foreach ( $this->positions as $position ) {
if ( isset( $string[ $position ] ) ) {
$field['settings'][ $position ] = $string[ $position ];
}
}
return $this->encode_field( $field );
}
return $string;
}
/**
* Decode a dynamic-content field.
*
* @param string $string The string to decode.
*
* @return bool
*/
protected function is_dynamic_content( $string ) {
return substr( $string, 0, strlen( self::ENCODED_CONTENT_START ) ) === self::ENCODED_CONTENT_START;
}
/**
* Decode a dynamic-content field.
*
* @param string $string The string to decode.
*
* @return array
*/
protected function decode_field( $string ) {
$start = strlen( self::ENCODED_CONTENT_START );
$end = strlen( self::ENCODED_CONTENT_END );
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
return json_decode( base64_decode( substr( $string, $start, -$end ) ), true );
}
/**
* Encode a dynamic-content field.
*
* @param array $field The field to encode.
*
* @return string
*/
protected function encode_field( $field ) {
// phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return self::ENCODED_CONTENT_START
. base64_encode( wp_json_encode( $field ) )
. self::ENCODED_CONTENT_END;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace WPML\Compatibility\Divi;
class Search implements \IWPML_Frontend_Action {
public function add_hooks() {
add_action( 'et_search_form_fields', [ $this, 'add_language_form_field' ] );
}
public function add_language_form_field() {
do_action( 'wpml_add_language_form_field' );
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace WPML\Compatibility\Divi;
class ThemeBuilderFactory implements \IWPML_Deferred_Action_Loader, \IWPML_Backend_Action_Loader, \IWPML_Frontend_Action_Loader {
public function get_load_action() {
return 'init';
}
public function create() {
global $sitepress;
return new ThemeBuilder( $sitepress );
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace WPML\Compatibility\Divi;
use SitePress;
class ThemeBuilder implements \IWPML_Action {
/** @var SitePress */
private $sitepress;
/**
* @param SitePress $sitepress
*/
public function __construct( SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
/**
* Add filters and actions.
*/
public function add_hooks() {
if ( ! defined( 'ET_THEME_BUILDER_DIR' ) ) {
return;
}
if ( $this->sitepress->is_setup_complete() ) {
if ( is_admin() ) {
add_action( 'init', [ $this, 'make_layouts_editable' ], 1000 ); // Before WPML_Sticky_Links::init.
add_filter( 'wpml_document_view_item_link', [ $this, 'document_view_layout_link' ], 10, 5 );
} else {
add_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10, 4 );
}
}
}
/**
* Gets all post types that are layouts.
*/
private static function get_types() {
return [
ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
];
}
/**
* Access the global post types array to tweak the settings for layouts
*/
public function make_layouts_editable() {
global $wp_post_types;
foreach ( $this->get_types() as $type ) {
$wp_post_types[ $type ]->show_ui = true;
$wp_post_types[ $type ]->show_in_menu = false;
$wp_post_types[ $type ]->_edit_link = 'post.php?post=%d';
}
}
/**
* Translate theme builder layout ids in the frontend.
*
* @param string $value The layout id.
* @param int $post_id The post it belongs to.
* @param string $key The meta key we are handling.
* @param bool $single Fetch a single row or an array.
* @return string
*/
public function translate_layout_ids( $value, $post_id, $key, $single ) {
if ( in_array( $key, [ '_et_header_layout_id', '_et_body_layout_id', '_et_footer_layout_id' ], true ) ) {
/**
* The `get_post_metadata` filter provides `null` as the initial `$value`.
* When we return a different $value it is used directly, to avoid a second query.
* This means that we have to get the original value first, removing ourselves so
* we don't fall into an infinite loop.
*/
remove_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10 );
$original_id = get_post_meta( $post_id, $key, true );
add_filter( 'get_post_metadata', [ $this, 'translate_layout_ids' ], 10, 4 );
$type = substr( $key, 1, -3 );
$value = $this->sitepress->get_object_id( $original_id, $type, true );
if ( ! $single ) {
$value = [ $value ];
}
}
return $value;
}
/**
* Remove the 'View' link because you can't view layouts alone.
*
* @param string $link The complete link.
* @param string $text The text to link.
* @param object $job The corresponding translation job.
* @param string $prefix The prefix of the element type.
* @param string $type The element type.
*
* @return string
*/
public function document_view_layout_link( $link, $text, $job, $prefix, $type ) {
if ( 'post' === $prefix && $this->is_theme_layout( $type ) ) {
$link = '';
}
return $link;
}
/**
* Check if a certain Type is a theme builder layout.
*
* @param string $type The type to check.
*
* @return bool
*/
private function is_theme_layout( $type ) {
return in_array( $type, $this->get_types(), true );
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements\EssentialAddons;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\compose;
/**
* @see https://essential-addons.com/elementor/docs/creative-elements/content-timeline/
*/
class ContentTimeline {
/**
* @return array
*/
public static function get() {
// $isEAContentTimeline :: array -> bool
$isEAContentTimeline = Relation::propEq( 'widgetType', 'eael-content-timeline' );
// $contentTimelineLinksLens :: callable -> callable -> mixed
$contentTimelineLinksLens = compose(
Obj::lensProp( 'settings' ),
Obj::lensMappedProp( 'eael_coustom_content_posts' ),
Obj::lensPath( [ '__dynamic__', 'eael_read_more_text_link' ] )
);
return [ $isEAContentTimeline, $contentTimelineLinksLens, 'popup', 'popup' ];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Logic;
use WPML\FP\Obj;
use WPML\FP\Relation;
class FormPopup {
/**
* @return array
*/
public static function get() {
$popupIdPath = [ 'settings', 'popup_action_popup_id' ];
$isFormWithPopup = Logic::allPass( [
Relation::propEq( 'widgetType', 'form' ),
Obj::path( $popupIdPath ),
] );
$popupIdLens = Obj::lensPath( $popupIdPath );
return [ $isFormWithPopup, $popupIdLens ];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Obj;
use WPML\FP\Relation;
use function WPML\FP\compose;
class Hotspot{
/**
* @return array
*/
public static function get() {
$isHotspot = Relation::propEq( 'widgetType', 'hotspot' );
// $hotspotLinksLens :: callable -> callable -> mixed
$hotspotLinksLens = compose(
Obj::lensProp( 'settings' ),
Obj::lensMappedProp( 'hotspot' ),
Obj::lensPath( [ '__dynamic__', 'hotspot_link' ] )
);
return [ $isHotspot, $hotspotLinksLens, 'popup', 'popup' ];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Logic;
class Popup {
/**
* @return array
*/
public static function get() {
$popupPath = [ 'settings', '__dynamic__', 'link' ];
// $isDynamicLink :: array -> bool
$isDynamicLink = Logic::allPass( [
Relation::propEq( 'elType', 'widget' ),
Obj::path( $popupPath ),
] );
$lens = Obj::lensPath( $popupPath );
return [ $isDynamicLink, $lens, 'popup', 'popup' ];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
class Provider {
/**
* @return array
*/
public static function get() {
return [
EssentialAddons\ContentTimeline::get(),
Hotspot::get(),
Popup::get(),
FormPopup::get(),
WooProduct::get( 'title' ),
WooProduct::get( 'short-description' ),
];
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace WPML\PB\Elementor\Config\DynamicElements;
use WPML\FP\Fns;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\FP\Logic;
class WooProduct {
/**
* @param string $widgetName
*
* @return callable(string): string
*/
private static function getConfig( $widgetName ) {
$widgetConfig = wpml_collect( [
'title' => [
'dynamicKey' => 'title',
'widgetType' => 'heading',
'shortcodeName' => 'woocommerce-product-title-tag',
],
'short-description' => [
'dynamicKey' => 'editor',
'widgetType' => 'text-editor',
'shortcodeName' => 'woocommerce-product-short-description-tag',
],
] )->get( $widgetName );
return Obj::prop( Fns::__, $widgetConfig );
}
/**
* @param string $widget
*
* @return array
*/
public static function get( $widget ) {
$get = self::getConfig( $widget );
$widgetPath = [ 'settings', '__dynamic__', $get( 'dynamicKey' ) ];
// $isWooWidget :: array -> bool
$isWooWidget = Logic::allPass( [
Relation::propEq( 'widgetType', $get( 'widgetType' ) ),
Obj::path( $widgetPath ),
] );
// $widgetLens :: callable -> callable -> mixed
$widgetLens = Obj::lensPath( $widgetPath );
return [ $isWooWidget, $widgetLens, $get( 'shortcodeName' ), 'product_id' ];
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace WPML\PB\Elementor\Config;
class Factory extends \WPML\PB\Config\Factory {
const DATA = [
'configRoot' => 'elementor-widgets',
'defaultConditionKey' => 'widgetType',
'pbKey' => 'elementor',
'translatableWidgetsHook' => 'wpml_elementor_widgets_to_translate',
];
/**
* @inheritDoc
*/
protected function getPbData( $key ) {
return self::DATA[ $key ];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace WPML\PB\Elementor;
class DataConvert {
/**
* @param array $data
*
* @return string
*/
public static function serialize( array $data ) {
return wp_slash( wp_json_encode( $data ) );
}
/**
* @param array|string $data
*
* @return array
*/
public static function unserialize( $data ) {
return json_decode( is_array( $data ) ? $data[0] : $data, true );
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace WPML\PB\Elementor\DynamicContent;
use WPML_PB_String;
class Field {
/**
* e.g. '[elementor-tag id="cc0b6c6" name="post-title" settings="ENCODED_STRING"]'
*
* @var string $tagValue
*/
public $tagValue;
/**
* e.g. 'title'
*
* @var string $tagKey
*/
public $tagKey;
/**
* The node ID.
*
* @var string $nodeId
*/
public $nodeId;
/**
* The item ID inside the node with items.
*
* @var string $itemId
*/
public $itemId;
/**
* @param string $tagValue
* @param string $tagKey
* @param string $nodeId
* @param string $itemId
*/
public function __construct( $tagValue, $tagKey, $nodeId, $itemId = '' ) {
$this->tagValue = $tagValue;
$this->tagKey = $tagKey;
$this->nodeId = $nodeId;
$this->itemId = $itemId;
}
/**
* @see \WPML_Elementor_Translatable_Nodes::get_string_name()
* @see \WPML_Elementor_Module_With_Items::get_string_name()
*
* @param WPML_PB_String $string
*
* @return bool
*/
public function isMatchingStaticString( WPML_PB_String $string ) {
$pattern = '/^' . $this->tagKey . '-.*-' . $this->nodeId . '$/';
if ( $this->itemId ) {
$pattern = '/^' . $this->tagKey . '-.*-' . $this->nodeId . '-' . $this->itemId . '$/';
}
return (bool) preg_match( $pattern, $string->get_name() );
}
}

View File

@@ -0,0 +1,260 @@
<?php
namespace WPML\PB\Elementor\DynamicContent;
use WPML\Collect\Support\Collection;
use WPML_Elementor_Translatable_Nodes;
use WPML_PB_String;
class Strings {
const KEY_SETTINGS = WPML_Elementor_Translatable_Nodes::SETTINGS_FIELD;
const KEY_DYNAMIC = '__dynamic__';
const KEY_NODE_ID = 'id';
const KEY_ITEM_ID = '_id';
const SETTINGS_REGEX = '/settings="(.*?(?="]))/';
const NAME_PREFIX = 'dynamic';
const DELIMITER = '-';
const TRANSLATABLE_SETTINGS = [
'before',
'after',
'fallback',
];
/**
* Remove the strings overwritten with dynamic content
* and add the extra strings "before", "after" and "fallback".
*
* @param WPML_PB_String[] $strings
* @param string $nodeId
* @param array $element
*
* @return WPML_PB_String[]
*/
public static function filter( array $strings, $nodeId, array $element ) {
$dynamicFields = self::getDynamicFields( $element );
$updateFromDynamicFields = function( WPML_PB_String $string ) use ( &$dynamicFields ) {
$matchingField = $dynamicFields->first(
function( Field $field ) use ( $string ) {
return $field->isMatchingStaticString( $string );
}
);
if ( $matchingField ) {
return self::addBeforeAfterAndFallback( wpml_collect( [ $dynamicFields->pull( $dynamicFields->search( $matchingField ) ) ] ) );
}
return $string;
};
return wpml_collect( $strings )
->map( $updateFromDynamicFields )
->merge( self::addBeforeAfterAndFallback( $dynamicFields ) )
->flatten()
->toArray();
}
/**
* @param array $element
*
* @return Collection
*/
private static function getDynamicFields( array $element ) {
if ( self::isModuleWithItems( $element ) ) {
return self::getDynamicFieldsForModuleWithItems( $element );
} elseif ( isset( $element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ] ) ) {
return self::getFields(
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ],
$element[ self::KEY_NODE_ID ]
);
}
return wpml_collect();
}
/**
* @param array $element
*
* @return Collection
*/
private static function getDynamicFieldsForModuleWithItems( array $element ) {
$isDynamic = function( $item ) {
return isset( $item[ self::KEY_DYNAMIC ] );
};
$getFields = function( array $item ) use ( $element ) {
return self::getFields(
$item[ self::KEY_DYNAMIC ],
$element[ self::KEY_NODE_ID ],
$item[ self::KEY_ITEM_ID ]
);
};
return wpml_collect( reset( $element[ self::KEY_SETTINGS ] ) )
->filter( $isDynamic )
->map( $getFields )
->flatten();
}
/**
* @param array $data
* @param string $nodeId
* @param string $itemId
*
* @return Collection
*/
private static function getFields( array $data, $nodeId, $itemId = '' ) {
$buildField = function( $tagValue, $tagKey ) use ( $nodeId, $itemId ) {
return new Field( $tagValue, $tagKey, $nodeId, $itemId );
};
return wpml_collect( $data )->map( $buildField );
}
/**
* @param array $element
*
* @return bool
*/
private static function isModuleWithItems( array $element ) {
if ( isset( $element[ self::KEY_SETTINGS ] ) ) {
$firstSettingElement = reset( $element[ self::KEY_SETTINGS ] );
return is_array( $firstSettingElement ) && 0 === key( $firstSettingElement );
}
return false;
}
/**
* @param Collection $dynamicFields
*
* @return Collection
*/
private static function addBeforeAfterAndFallback( Collection $dynamicFields ) {
$dynamicFieldToSettingStrings = function( Field $field ) {
preg_match( self::SETTINGS_REGEX, $field->tagValue, $matches );
$isTranslatableSetting = function( $value, $settingField ) {
return $value && is_string( $value ) && in_array( $settingField, self::TRANSLATABLE_SETTINGS, true );
};
$buildStringFromSetting = function( $value, $settingField ) use ( $field ) {
return new WPML_PB_String(
$value,
self::getStringName( $field->nodeId, $field->itemId, $field->tagKey, $settingField ),
sprintf( __( 'Dynamic content string: %s', 'sitepress' ), $field->tagKey ),
'LINE'
);
};
return wpml_collect( isset( $matches[1] ) ? self::decodeSettings( $matches[1] ) : [] )
->filter( $isTranslatableSetting )
->map( $buildStringFromSetting );
};
return $dynamicFields->map( $dynamicFieldToSettingStrings );
}
/**
* @param array $element
* @param WPML_PB_String $string
*
* @return array
*/
public static function updateNode( array $element, WPML_PB_String $string ) {
$stringNameParts = explode( self::DELIMITER, $string->get_name() );
if ( count( $stringNameParts ) !== 5 || self::NAME_PREFIX !== $stringNameParts[0] ) {
return $element;
}
list( , , $itemId, $dynamicField, $settingField ) = $stringNameParts;
if ( $itemId && self::isModuleWithItems( $element ) ) {
$element = self::updateNodeWithItems( $element, $string, $stringNameParts );
} elseif ( isset( $element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ] ) ) {
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ] = self::replaceSettingString(
$element[ self::KEY_SETTINGS ][ self::KEY_DYNAMIC ][ $dynamicField ],
$string,
$settingField
);
}
return $element;
}
/**
* @param string $encodedSettings
* @param WPML_PB_String $string
* @param string $settingField
*
* @return string|null
*/
private static function replaceSettingString( $encodedSettings, WPML_PB_String $string, $settingField ) {
$replace = function( array $matches ) use ( $string, $settingField ) {
$settings = self::decodeSettings( $matches[1] );
$settings[ $settingField ] = $string->get_value();
$replace = urlencode( json_encode( $settings ) );
return str_replace( $matches[1], $replace, $matches[0] );
};
return preg_replace_callback( self::SETTINGS_REGEX, $replace, $encodedSettings );
}
/**
* @param array $element
* @param WPML_PB_String $string
* @param array $stringNameParts
*
* @return array
*/
private static function updateNodeWithItems( array $element, WPML_PB_String $string, array $stringNameParts ) {
list( , , $itemId, $dynamicField, $settingField ) = $stringNameParts;
$items = wpml_collect( reset( $element[ self::KEY_SETTINGS ] ) );
$mainKey = key( $element[ self::KEY_SETTINGS ] );
$replaceStringInItem = function( array $item ) use ( $itemId, $string, $dynamicField, $settingField ) {
if (
isset( $item[ self::KEY_DYNAMIC ][ $dynamicField ], $item[ self::KEY_ITEM_ID ] )
&& $item[ self::KEY_ITEM_ID ] === $itemId
) {
$item[ self::KEY_DYNAMIC ][ $dynamicField ] = self::replaceSettingString( $item[ self::KEY_DYNAMIC ][ $dynamicField ], $string, $settingField );
}
return $item;
};
$element[ self::KEY_SETTINGS ][ $mainKey ] = $items->map( $replaceStringInItem )->toArray();
return $element;
}
/**
* @param string $settingsString
*
* @return array
*/
private static function decodeSettings( $settingsString ) {
return json_decode( urldecode( $settingsString ), true );
}
/**
* @param string $nodeId
* @param string $itemId
* @param string $tagKey
* @param string $settingField
*
* @return string
*/
public static function getStringName( $nodeId, $itemId, $tagKey, $settingField ) {
return self::NAME_PREFIX . self::DELIMITER
. $nodeId . self::DELIMITER
. $itemId . self::DELIMITER
. $tagKey . self::DELIMITER
. $settingField;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace WPML\PB\Elementor\Helper;
class Node {
/**
* @param array $element
*
* @return bool
*/
public static function isTranslatable( $element ) {
return isset( $element['elType'] ) && in_array( $element['elType'], [ 'widget', 'container' ], true );
}
/**
* @param array $element
*
* @return bool
*/
public static function hasChildren( $element ) {
return isset( $element['elements'] ) && count( $element['elements'] );
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace WPML\PB\Elementor\Helper;
use WPML\FP\Obj;
use WPML\FP\Str;
class StringFormat {
/**
* @param array $settings
* @param \WPML_PB_String $string
*
* @return bool
*/
public static function useWpAutoP( $settings, $string ) {
return 'VISUAL' === $string->get_editor_type() && self::isOneLine( self::getOriginalString( $settings, $string ) );
}
/**
* @param string $content
*
* @return bool
*/
private static function isOneLine( $content ) {
return ! Str::includes( PHP_EOL, $content );
}
/**
* @param array $settings
* @param \WPML_PB_String $string
*
* @return string
*/
private static function getOriginalString( $settings, $string ) {
if ( 'text-editor' === $settings['widgetType'] ) {
return Obj::path( [ 'settings', 'editor' ], $settings );
} elseif ( 'hotspot' === $settings['widgetType'] ) {
$items = Obj::path( [ 'settings', 'hotspot' ], $settings );
$found = wpml_collect( $items )->first( function( $item ) use ( $string ) {
return Str::endsWith( $item['_id'], $string->get_name() );
});
return Obj::prop( 'hotspot_tooltip_content', $found );
}
return '';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use WPML\PB\Helper\LanguageNegotiation;
class DomainsWithMultisite implements \IWPML_Backend_Action {
public function add_hooks() {
if ( is_multisite() && LanguageNegotiation::isUsingDomains() ) {
Hooks::onAction( 'elementor/editor/init' )
->then( spreadArgs( [ $this, 'onElementorEditor' ] ) );
}
}
public function onElementorEditor() {
$isCurrentLangDifferentThanDefault = apply_filters( 'wpml_current_language', null ) !== apply_filters( 'wpml_default_language', null );
if ( $isCurrentLangDifferentThanDefault ) {
Hooks::onFilter( 'admin_url' )
->then( spreadArgs( [ $this, 'filterUrl' ] ) );
}
}
/**
* @param string $url The admin area URL.
*/
public function filterUrl( $url ) {
$parsedUrl = wpml_parse_url( $url );
if ( is_array( $parsedUrl ) && ! empty( $parsedUrl['host'] ) ) {
return http_build_url( Obj::assoc( 'host', $_SERVER['HTTP_HOST'], $parsedUrl ) );
}
return $url;
}
private static function isUsingDomains() {
return apply_filters( 'wpml_setting', [], 'language_domains' )
&& constant( 'WPML_LANGUAGE_NEGOTIATION_TYPE_DOMAIN' ) === (int) apply_filters( 'wpml_setting', 1, 'language_negotiation_type' );
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\PB\Elementor\Config\DynamicElements\Provider;
use function WPML\FP\curryN;
use function WPML\FP\spreadArgs;
class DynamicElements implements \IWPML_Frontend_Action, \IWPML_DIC_Action {
public function add_hooks() {
add_filter( 'elementor/frontend/builder_content_data', [ $this, 'convert' ] );
}
/**
* @param array $data
*
* @return array
*/
public function convert( array $data ) {
// $convertTag :: (curried) string -> string -> string -> string
$convertTag = curryN( 3, [ __CLASS__, 'convertTag' ] );
// $assignConvertCallable :: (callable, callable, string|null, string|null) -> array
$assignConvertCallable = function( $shouldConvert, $lens, $allowedTag = null, $idKey = null ) use ( $convertTag ) {
if ( $allowedTag && $idKey ) {
$convert = $convertTag( $allowedTag, $idKey );
} else {
$convert = [ __CLASS__, 'convertId' ];
}
return [ $shouldConvert, Obj::over( $lens, $convert ) ];
};
/**
* Filter widget dynamic id conversion configuration.
*
* Gather conversion configuration for Elementor widget dynamic ids.
* The id can be stored in a widget key, or in a shortcode string.
*
* @since 2.0.4
*
* @param array $args {
* @type array $configuration {
* Conversion configuration.
*
* @type callable $shouldConvert Check if the widget should be converted.
* @type callable $keyLens Lens to the key that holds the dynamic id.
* @type string $tagName Optional. Shortcode name attribute.
* @type string $idKey Optional. Id key in the shortcode's settings attribute.
* }
* }
*/
$converters = apply_filters( 'wpml_pb_elementor_widget_dynamic_id_converters', Provider::get() );
$converters = wpml_collect( $converters )
->map( spreadArgs( $assignConvertCallable ) )
->toArray();
return $this->applyConverters( $data, $converters );
}
/**
* @param array $data
* @param array $converters
*
* @return array
*/
private function applyConverters( $data, $converters ) {
foreach ( $data as &$item ) {
foreach ( $converters as $converter ) {
list( $shouldConvert, $convert ) = $converter;
if ( $shouldConvert( $item ) ) {
$item = $convert( $item );
}
}
$item['elements'] = $this->applyConverters( $item['elements'], $converters );
}
return $data;
}
/**
* @param string $allowedTag
* @param string $idKey
* @param string $tagString
*
* @return string
*/
public static function convertTag( $allowedTag, $idKey, $tagString ) {
preg_match( '/name="(.*?(?="))"/', $tagString, $tagNameMatch );
if ( ! $tagNameMatch || $tagNameMatch[1] !== $allowedTag ) {
return $tagString;
}
return preg_replace_callback( '/settings="(.*?(?="]))/', function( array $matches ) use ( $idKey ) {
$settings = json_decode( urldecode( $matches[1] ), true );
if ( ! isset( $settings[ $idKey ] ) ) {
return $matches[0];
}
$settings[ $idKey ] = self::convertId( $settings[ $idKey ] );
$replace = urlencode( json_encode( $settings ) );
return str_replace( $matches[1], $replace, $matches[0] );
}, $tagString );
}
/**
* @param int $elementId
*
* @return int
*/
public static function convertId( $elementId ) {
return apply_filters( 'wpml_object_id', $elementId, get_post_type( $elementId ), true );
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Editor implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'wpml_pb_is_editing_translation_with_native_editor', 10, 2 )
->then( spreadArgs( function( $isTranslationWithNativeEditor, $translatedPostId ) {
return $isTranslationWithNativeEditor
|| (
(int) Obj::prop( 'editor_post_id', $_POST ) === $translatedPostId
&& Relation::propEq( 'action', 'elementor_ajax', $_POST )
);
} ) );
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class FormPopup implements \IWPML_Frontend_Action, \IWPML_AJAX_Action {
public function add_hooks() {
$isElementorFormAjaxSubmit = Relation::propEq( 'action', 'elementor_pro_forms_send_form' );
if ( $isElementorFormAjaxSubmit( $_POST ) ) {
Hooks::onAction( 'elementor_pro/forms/new_record', 10, 2 )
->then( spreadArgs( [ $this, 'convertFormPopupIdAjax' ] ) );
}
}
/**
* @param \ElementorPro\Modules\Forms\Classes\Form_Record $record An instance of the form record.
* @param \ElementorPro\Modules\Forms\Classes\Ajax_Handler $ajaxHandler An instance of the ajax handler.
*/
public function convertFormPopupIdAjax ( $record, $ajaxHandler ) {
if ( Obj::path( [ 'data', 'popup', 'id' ], $ajaxHandler ) ) {
$popup = Obj::path( [ 'data', 'popup'], $ajaxHandler );
$getCurrentLanguageFromPost = function () {
return Obj::prop( 'language_code', apply_filters( 'wpml_post_language_details', null, sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) ) );
};
$popup['id'] = apply_filters( 'wpml_object_id', $popup['id'], get_post_type( $popup['id'] ), false, $getCurrentLanguageFromPost() );
$ajaxHandler->add_response_data( 'popup', $popup );
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace WPML\PB\Elementor\Hooks;
class Frontend implements \IWPML_Frontend_Action {
public function add_hooks() {
add_action( 'elementor_pro/search_form/after_input', [ $this, 'addLanguageFormField' ] );
}
public function addLanguageFormField() {
do_action( 'wpml_add_language_form_field' );
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Fns;
use WPML\FP\Logic;
use WPML\FP\Maybe;
use WPML\FP\Relation;
use WPML\PB\Elementor\DataConvert;
use WPML\PB\GutenbergCleanup\Package;
use WPML_Elementor_Data_Settings;
use function WPML\FP\curryN;
use function WPML\FP\pipe;
class GutenbergCleanup implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
public function add_hooks() {
add_filter(
'update_post_metadata',
Fns::withoutRecursion( Fns::identity(), [ $this, 'removeGutenbergFootprint' ] ),
10,
4
);
}
/**
* If we detect Gutenberg footprint in the Elementor data,
* we'll remove it and delete the Gutenberg string package.
*
* @param null|bool $check
* @param int $postId
* @param string $metaKey
* @param string $metaValue
*
* @return mixed
*/
public function removeGutenbergFootprint( $check, $postId, $metaKey, $metaValue ) {
if (
WPML_Elementor_Data_Settings::META_KEY_DATA === $metaKey
&& WPML_Elementor_Data_Settings::is_edited_with_elementor( $postId )
) {
// $ifValueHasChanged :: string -> bool
$ifValueHasChanged = pipe( Relation::equals( $metaValue ), Logic::not() );
// $update :: int -> string -> bool
$update = curryN( 2, function( $postId, $meta ) {
// Do not use update_post_meta, we need update meta for revisions too.
update_metadata( 'post', $postId, WPML_Elementor_Data_Settings::META_KEY_DATA, $meta );
Package::delete( Package::get( $postId ) );
return true;
} );
return Maybe::of( $metaValue )
->map( [ DataConvert::class, 'unserialize' ] )
->map( [ $this, 'removeBlockMetaInEditorWidget' ] )
->map( [ DataConvert::class, 'serialize' ] )
->filter( $ifValueHasChanged )
->map( $update( $postId ) )
->getOrElse( $check );
}
return $check;
}
/**
* @param array $data
*
* @return array
*/
public function removeBlockMetaInEditorWidget( array $data ) {
foreach ( $data as &$element ) {
if ( $element['elements'] ) {
$element['elements'] = $this->removeBlockMetaInEditorWidget( $element['elements'] );
} elseif ( 'widget' === $element['elType'] && isset( $element['settings']['editor'] ) ) {
$element['settings']['editor'] = preg_replace( '(<!--[^<]*-->)', '', $element['settings']['editor'] );
}
}
return $data;
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\FP\Relation;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
use function WPML\FP\pipe;
class LandingPages implements \IWPML_Frontend_Action, \IWPML_Backend_Action, \IWPML_DIC_Action {
const POST_TYPE = 'e-landing-page';
const BACKUP_HOOK_PRIORITY = 11;
/** @var \SitePress $sitepress */
private $sitepress;
public function __construct( \SitePress $sitepress ) {
$this->sitepress = $sitepress;
}
public function add_hooks() {
if ( get_option( 'permalink_structure' ) ) {
add_filter( 'post_type_link', [ $this, 'adjustLink' ], PHP_INT_MAX, 2 );
Hooks::onFilter( 'pre_handle_404', self::BACKUP_HOOK_PRIORITY, 2 )
->then( spreadArgs( [ $this, 'backupQuery' ] ) );
}
}
/**
* @see \Elementor\Modules\LandingPages\Module::remove_post_type_slug
*
* @param string $postUrl
* @param \WP_Post $post
*
* @return string
*/
public function adjustLink( $postUrl, $post ) {
if ( self::POST_TYPE !== $post->post_type || 'publish' !== $post->post_status ) {
return $postUrl;
}
$homeUrl = get_home_url();
$urlParts = wp_parse_url( $homeUrl );
$urlParts['path'] = trailingslashit( Obj::prop( 'path', $urlParts ) ) . $post->post_name . '/';
$newPostUrl = http_build_url( null, $urlParts );
$postLangCode = $this->sitepress->get_language_for_element( $post->ID, 'post_' . self::POST_TYPE );
return $this->sitepress->convert_url( $newPostUrl, $postLangCode );
}
/**
* Backup and restore the global $wp_query that was overwritten by Elementor.
*
* In order for Elementor to treat landing pages(post_type: e-landing-page) as pages, it overwrites the global
* $wp_query when using the /%category%/%postname%/ permalink structure.
* WPML then resets the global $wp_query in SitePress::get_ls_languages() and this causes 404 errors on landing pages
* and prevents editing them in the Elementor editor.
*
* In this case, we backup the global $wp_query when Elementor overwrites it on 'pre_handle_404' hook, and we restore
* it after WPML resets it on 'wp_head' hook.
*
* @see \WPML_SEO_HeadLangs::head_langs()
* @see \SitePress::get_ls_languages()
* @see \Elementor\Modules\LandingPages\Module::handle_404
*
* @param bool $value
* @param \WP_Query $query
*
* @return bool
*/
public function backupQuery( $value, $query ) {
global $wp_query;
if ( $value ) {
return $value;
}
// $wasModifiedByElementor :: \WP_Query -> bool
$wasModifiedByElementor = pipe(
Obj::prop( 'query' ),
Relation::propEq( 'post_type', self::POST_TYPE )
);
// $hasPosts :: \WP_Query -> array
$hasPosts = Obj::prop( 'posts' );
if (
$query->is_main_query()
&& empty( $query->posts )
&& ! empty( $query->query['category_name'] )
&& empty( $query->tax_query->table_aliases )
&& $wasModifiedByElementor( $wp_query )
&& $hasPosts( $wp_query )
) {
if ( apply_filters( 'wpml_sub_setting', 1, 'seo', 'head_langs' ) ) {
$wpQueryBackup = clone $wp_query;
$priority = (int) apply_filters( 'wpml_sub_setting', 1, 'seo', 'head_langs_priority' );
Hooks::onAction( 'wp_head', $priority + 1 )
->then( function() use ( $wpQueryBackup ) {
global $wp_query;
$wp_query = $wpQueryBackup; //phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited
} );
}
}
return false;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Obj;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class Templates implements \IWPML_Frontend_Action, \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onFilter( 'elementor/theme/conditions/cache/regenerate/query_args' )
->then( spreadArgs( Obj::assoc( 'suppress_filters', true ) ) );
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\FP\Fns;
use WPML\LIB\WP\Hooks;
use function WPML\FP\spreadArgs;
class WooCommerce implements \IWPML_Frontend_Action {
public function add_hooks() {
Hooks::onFilter( 'option_elementor_woocommerce_purchase_summary_page_id' )
->then( spreadArgs( Fns::memorize( function( $pageId ) {
return apply_filters( 'wpml_object_id', $pageId, 'page', true );
} ) ) );
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace WPML\PB\Elementor\Hooks;
use WPML\LIB\WP\Hooks;
class WordPressWidgets implements \IWPML_Backend_Action {
public function add_hooks() {
Hooks::onAction( 'wp_ajax_elementor_ajax' )
->then( function() {
add_filter( 'wpml_widget_language_selector_disable', '__return_true' );
} );
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace WPML\PB\Elementor\LanguageSwitcher;
use Elementor\Plugin;
class LanguageSwitcher implements \IWPML_Backend_Action, \IWPML_Frontend_Action {
public function add_hooks() {
add_action( 'elementor/widgets/widgets_registered', [ $this, 'registerWidgets' ] );
add_filter( 'wpml_custom_language_switcher_is_enabled', '__return_true' );
}
public function registerWidgets() {
// @phpstan-ignore-next-line
Plugin::instance()->widgets_manager->register_widget_type( new Widget() );
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace WPML\PB\Elementor\LanguageSwitcher;
use Elementor\Widget_Base;
class Widget extends Widget_Base {
/** @var WidgetAdaptor $adaptor */
private $adaptor;
public function __construct( $data = [], $args = null, WidgetAdaptor $adaptor = null ) {
$this->adaptor = $adaptor ?: new WidgetAdaptor();
$this->adaptor->setTarget( $this );
parent::__construct( $data, $args );
}
/** @return string */
public function get_name() {
return $this->adaptor->getName();
}
/** @return string */
public function get_title() {
return $this->adaptor->getTitle();
}
/** @return string */
public function get_icon() {
return $this->adaptor->getIcon();
}
/** @return array */
public function get_categories() {
return $this->adaptor->getCategories();
}
/**
* Register controls.
*
* Used to add new controls to any element type. For example, external
* developers use this method to register controls in a widget.
*
* Should be inherited and register new controls using `add_control()`,
* `add_responsive_control()` and `add_group_control()`, inside control
* wrappers like `start_controls_section()`, `start_controls_tabs()` and
* `start_controls_tab()`.
*/
protected function register_controls() {
$this->adaptor->registerControls();
}
/**
* Render element.
*
* Generates the final HTML on the frontend.
*/
protected function render() {
$this->adaptor->render();
}
}

View File

@@ -0,0 +1,355 @@
<?php
namespace WPML\PB\Elementor\LanguageSwitcher;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Typography;
use Elementor\Core\Schemes\Color as SchemeColor;
class WidgetAdaptor {
/** @var Widget $widget */
private $widget;
public function setTarget( Widget $widget ) {
$this->widget = $widget;
}
/** @return string */
public function getName() {
return 'wpml-language-switcher';
}
/** @return string */
public function getTitle() {
return __( 'WPML Language Switcher', 'sitepress' );
}
/** @return string */
public function getIcon() {
return 'fa fa-globe';
}
/** @return array */
public function getCategories() {
return [ 'general' ];
}
/**
* Register controls.
*
* Used to add new controls to any element type. For example, external
* developers use this method to register controls in a widget.
*
* Should be inherited and register new controls using `add_control()`,
* `add_responsive_control()` and `add_group_control()`, inside control
* wrappers like `start_controls_section()`, `start_controls_tabs()` and
* `start_controls_tab()`.
*/
public function registerControls() {
//Content Tab
$this->widget->start_controls_section(
'section_content',
[
'label' => __( 'Content', 'sitepress' ),
'type' => Controls_Manager::SECTION,
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$this->widget->add_control(
'style',
[
'label' => __('Language switcher type', 'sitepress'),
'type' => Controls_Manager::SELECT,
'default' => 'custom',
'options' => [
'custom' => __( 'Custom', 'sitepress' ),
'footer' => __( 'Footer', 'sitepress' ),
'post_translations' => __( 'Post Translations', 'sitepress' ),
],
]
);
$this->widget->add_control(
'display_flag',
[
'label' => __( 'Display Flag', 'sitepress' ),
'type' => Controls_Manager::SWITCHER,
'return_value' => 1,
'default' => 1,
]
);
$this->widget->add_control(
'link_current',
[
'label' => __( 'Show Active Language - has to be ON with Dropdown', 'sitepress' ),
'type' => Controls_Manager::SWITCHER,
'return_value' => 1,
'default' => 1,
'conditions' => [
'relation' => 'and',
'terms' => [
[
'name' => 'style',
'operator' => '!=',
'value' => 'custom',
]
]
],
]
);
$this->widget->add_control(
'native_language_name',
[
'label' => __( 'Native language name', 'sitepress' ),
'type' => Controls_Manager::SWITCHER,
'return_value' => 1,
'default' => 1,
]
);
$this->widget->add_control(
'language_name_current_language',
[
'label' => __( 'Language name in current language', 'sitepress' ),
'type' => Controls_Manager::SWITCHER,
'return_value' => 1,
'default' => 1,
]
);
$this->widget->end_controls_section();
$this->widget->start_controls_section(
'style_section',
[
'label' => __( 'Style', 'sitepress' ),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->widget->start_controls_tabs( 'style_tabs' );
$this->widget->start_controls_tab(
'style_normal_tab',
[
'label' => __( 'Normal', 'sitepress' ),
]
);
$this->widget->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'switcher_typography',
'selector' => '{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item',
]
);
$this->widget->add_control(
'switcher_text_color',
[
'label' => __( 'Text Color', 'sitepress' ),
'type' => Controls_Manager::COLOR,
'scheme' => [
'type' => SchemeColor::get_type(),
'value' => SchemeColor::COLOR_3,
],
'default' => '',
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-legacy-dropdown a' => 'color: {{VALUE}}',
],
]
);
$this->widget->add_control(
'switcher_bg_color',
[
'label' => __( 'Background Color', 'sitepress' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-legacy-dropdown a' => 'background-color: {{VALUE}}',
],
]
);
$this->widget->end_controls_tab();
$this->widget->start_controls_tab(
'style_hover_tab',
[
'label' => __( 'Hover', 'sitepress' ),
]
);
$this->widget->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'switcher_hover_typography',
'selector' => '{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item:hover,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item.wpml-ls-item__active,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item.highlighted,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item:focus',
]
);
$this->widget->add_control(
'switcher_hover_color',
[
'label' => __( 'Text Color', 'sitepress' ),
'type' => Controls_Manager::COLOR,
'scheme' => [
'type' => SchemeColor::get_type(),
'value' => SchemeColor::COLOR_4,
],
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-legacy-dropdown a:hover,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-legacy-dropdown a:focus,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-legacy-dropdown .wpml-ls-current-language:hover>a,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link:hover,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link.wpml-ls-link__active,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link.highlighted,
{{WRAPPER}} .wpml-elementor-ls .wpml-ls-item .wpml-ls-link:focus' => 'color: {{VALUE}}',
],
]
);
$this->widget->end_controls_tab();
$this->widget->end_controls_tabs();
$this->widget->end_controls_section();
$this->widget->start_controls_section(
'language_flag',
[
'label' => __( 'Language Flag', 'sitepress' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'display_flag' => [ 1 ],
],
]
);
$this->widget->add_control(
'flag_margin',
[
'label' => __( 'Margin', 'sitepress' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em' ],
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-flag' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->widget->end_controls_section();
$this->widget->start_controls_section(
'post_translation_text',
[
'label' => __( 'Post Translation Text', 'sitepress' ),
'tab' => Controls_Manager::TAB_STYLE,
'condition' => [
'style' => [ 'post_translations' ],
],
]
);
$this->widget->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => 'post_translation_typography',
'selector' => '{{WRAPPER}} .wpml-elementor-ls .wpml-ls-statics-post_translations',
]
);
$this->widget->add_control(
'post_translation_color',
[
'label' => __( 'Text Color', 'sitepress' ),
'type' => Controls_Manager::COLOR,
'scheme' => [
'type' => SchemeColor::get_type(),
'value' => SchemeColor::COLOR_3,
],
'default' => '',
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-statics-post_translations' => 'color: {{VALUE}}',
],
]
);
$this->widget->add_control(
'post_translation_bg_color',
[
'label' => __( 'Background Color', 'sitepress' ),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-statics-post_translations' => 'background-color: {{VALUE}}',
],
]
);
$this->widget->add_control(
'post_translation_padding',
[
'label' => __( 'Padding', 'sitepress' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em' ],
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-statics-post_translations' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->widget->add_control(
'post_translation_margin',
[
'label' => __( 'Margin', 'sitepress' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%', 'em' ],
'selectors' => [
'{{WRAPPER}} .wpml-elementor-ls .wpml-ls-statics-post_translations' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->widget->end_controls_section();
}
/**
* Render element.
*
* Generates the final HTML on the frontend.
*/
public function render() {
$settings = $this->widget->get_settings_for_display();
$this->widget->add_render_attribute('wpml-elementor-ls', 'class', [
'wpml-elementor-ls',
]);
$args = array(
'display_link_for_current_lang' => $settings['link_current'],
'flags' => $settings['display_flag'],
'native' => $settings['native_language_name'],
'translated' => $settings['language_name_current_language'],
'type' => $settings['style'],
);
if ( 'custom' === $settings['style'] ) {
//forcing in dropdown case
$args['display_link_for_current_lang'] = 1;
}
echo "<div " . $this->widget->get_render_attribute_string('wpml-elementor-ls') . ">";
do_action('wpml_language_switcher', $args);
echo "</div>";
}
}

View File

@@ -0,0 +1,17 @@
<?php
class WPML_Elementor_Adjust_Global_Widget_ID_Factory implements IWPML_Backend_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
global $sitepress;
$elementor_db_factory = new WPML_Elementor_DB_Factory();
$data_settings = new WPML_Elementor_Data_Settings( $elementor_db_factory->create() );
return new WPML_Elementor_Adjust_Global_Widget_ID(
$data_settings,
new WPML_Translation_Element_Factory( $sitepress ),
$sitepress
);
}
}

View File

@@ -0,0 +1,156 @@
<?php
class WPML_Elementor_Adjust_Global_Widget_ID {
/** @var IWPML_Page_Builders_Data_Settings */
private $elementor_settings;
/** @var WPML_Translation_Element_Factory */
private $translation_element_factory;
/** @var SitePress */
private $sitepress;
/** @var string */
private $current_language;
public function __construct(
IWPML_Page_Builders_Data_Settings $elementor_settings,
WPML_Translation_Element_Factory $translation_element_factory,
SitePress $sitepress
) {
$this->elementor_settings = $elementor_settings;
$this->translation_element_factory = $translation_element_factory;
$this->sitepress = $sitepress;
}
public function add_hooks() {
add_action( 'elementor/editor/before_enqueue_scripts', array( $this, 'adjust_ids' ) );
add_action( 'elementor/editor/after_enqueue_scripts', array( $this, 'restore_current_language' ) );
add_action( 'elementor/frontend/the_content', array( $this, 'duplicate_css_class_with_original_id' ) );
if ( is_admin() ) {
add_filter(
'wpml_should_use_display_as_translated_snippet',
array( $this, 'should_use_display_as_translated_snippet' ),
PHP_INT_MAX,
2
);
}
}
public function adjust_ids() {
$this->current_language = $this->sitepress->get_current_language();
$post_id = absint( $_REQUEST['post'] ); // WPCS: sanitization ok.
$post = $this->translation_element_factory->create_post( $post_id );
$language = $post->get_language_code();
$this->sitepress->switch_lang( $language );
$custom_field_data = get_post_meta(
$post_id,
$this->elementor_settings->get_meta_field()
);
if ( ! $custom_field_data ) {
return;
}
$custom_field_data = $this->elementor_settings->convert_data_to_array( $custom_field_data );
$custom_field_data_adjusted = $this->set_global_widget_id_for_language( $custom_field_data, $language );
if ( $custom_field_data_adjusted !== $custom_field_data ) {
update_post_meta(
$post_id,
$this->elementor_settings->get_meta_field(),
$this->elementor_settings->prepare_data_for_saving( $custom_field_data_adjusted )
);
// Update post date so Elementor doesn't use auto saved post
$post_data = get_post( $post_id, ARRAY_A );
$post_data['post_date'] = current_time( 'mysql' );
$post_data['post_date_gmt'] = '';
wp_update_post( $post_data );
}
}
private function set_global_widget_id_for_language( $data_array, $language ) {
foreach ( $data_array as &$data ) {
if ( isset( $data['elType'] ) && 'widget' === $data['elType'] && 'global' === $data['widgetType'] ) {
try {
$widget_post = $this->translation_element_factory->create_post( $data['templateID'] );
if ( $widget_post->get_language_code() !== $language ) {
$translation = $widget_post->get_translation( $language );
if ( $translation ) {
$data['templateID'] = $translation->get_element_id();
}
}
} catch ( Exception $e ) {
// Not much we can do if the elementor templateID is a non existing post
}
}
$data['elements'] = $this->set_global_widget_id_for_language( $data['elements'], $language );
}
return $data_array;
}
public function restore_current_language() {
$this->sitepress->switch_lang( $this->current_language );
}
/**
* The snippet is a WHERE condition which is added to a DB query.
* This will include the source element in the query results in case the element
* does not exist in the current language.
*
* @see WPML_Query_Filter::display_as_translated_snippet
*
* @param bool $display_as_translated
* @param array $post_types
*
* @return bool
*/
public function should_use_display_as_translated_snippet( $display_as_translated, $post_types ) {
if ( isset( $_GET['action'] ) && 'elementor' === $_GET['action']
&& in_array( 'elementor_library', array_keys( $post_types ), true ) ) {
return true;
}
return $display_as_translated;
}
/**
* @param string $content
*
* @return string
*/
public function duplicate_css_class_with_original_id( $content ) {
$classPrefixes = wpml_collect( [
'elementor-',
'elementor-global-',
] )->implode( '|' );
return preg_replace_callback( '/(class=".*(' . $classPrefixes . '))(\d+)/', array( $this, 'convert_id_to_original' ), $content );
}
private function convert_id_to_original( array $matches ) {
$class_prefix = $matches[2];
$id = (int)$matches[3];
$element = $this->translation_element_factory->create_post( $id );
$source = $element->get_source_element();
if ( $source ) {
$new_class = $class_prefix . $source->get_id();
$search = $matches[0];
$replace = $search . ' ' . $new_class;
$matches[0] = str_replace( $search, $replace, $matches[0] );
}
return $matches[0];
}
}

View File

@@ -0,0 +1,155 @@
<?php
use WPML\PB\AutoUpdate\Settings as AutoUpdateSettings;
use WPML\PB\Elementor\DataConvert;
class WPML_Elementor_Data_Settings implements IWPML_Page_Builders_Data_Settings {
const META_KEY_DATA = '_elementor_data';
const META_KEY_MODE = '_elementor_edit_mode';
/**
* @var WPML_Elementor_DB|null
*/
private $elementor_db;
public function __construct( WPML_Elementor_DB $elementor_db = null ) {
$this->elementor_db = $elementor_db;
}
public function add_hooks() {
add_filter( 'wpml_custom_field_values_for_post_signature', array( $this, 'add_data_custom_field_to_md5' ), 10, 2 );
add_filter( 'wpml_pb_copy_meta_field', array( $this, 'mark_css_field_as_empty' ), 10, 4 );
if ( $this->elementor_db ) {
add_action(
'wpml_page_builder_string_translated',
array( $this, 'save_post_body_as_plain_text' ),
11,
5
);
}
}
/**
* @param array $value
* @param int $translated_post_id
* @param int $original_post_id
* @param string $meta_key
*
* @return mixed
*/
public function mark_css_field_as_empty( $value, $translated_post_id, $original_post_id, $meta_key ) {
if ( '_elementor_css' === $meta_key && is_array( $value ) ) {
if ( ! isset( $value['status'] ) ) {
$value = current( $value );
$value['status'] = '';
$value = array( $value );
} else {
$value['status'] = '';
}
}
return $value;
}
public function save_post_body_as_plain_text( $type, $post_id, $original_post, $string_translations, $lang ) {
if ( $this->is_handling_post( $post_id ) ) {
$this->elementor_db->save_plain_text( $post_id );
}
}
/**
* @return string
*/
public function get_meta_field() {
return self::META_KEY_DATA;
}
/**
* @return string
*/
public function get_node_id_field() {
return 'id';
}
/**
* @return array
*/
public function get_fields_to_copy() {
return array(
'_elementor_version',
self::META_KEY_MODE,
'_elementor_css',
'_elementor_template_type',
'_elementor_template_widget_type',
);
}
/**
* @param array|string $data
*
* @return array
*/
public function convert_data_to_array( $data ) {
return DataConvert::unserialize( $data );
}
/**
* @param array $data
*
* @return string
*/
public function prepare_data_for_saving( array $data ) {
return DataConvert::serialize( $data );
}
/**
* @return string
*/
public function get_pb_name(){
return 'Elementor';
}
/**
* @return array
*/
public function get_fields_to_save() {
return array( '_elementor_data' );
}
/**
* @param array $custom_fields_values
* @param int $post_id
*
* @return array
*/
public function add_data_custom_field_to_md5( array $custom_fields_values, $post_id ) {
if ( AutoUpdateSettings::isEnabled() ) {
unset( $custom_fields_values[ $this->get_meta_field() ] );
} else {
$custom_fields_values[ $this->get_meta_field() ] = get_post_meta( $post_id, $this->get_meta_field(), true );
}
return $custom_fields_values;
}
/**
* @param int $postId
*
* @return bool
*/
public function is_handling_post( $postId ) {
return (bool) get_post_meta( $postId, $this->get_meta_field(), true )
&& self::is_edited_with_elementor( $postId );
}
/**
* @param int $postId
*
* @return bool
*/
public static function is_edited_with_elementor( $postId ) {
return 'builder' === get_post_meta( $postId, self::META_KEY_MODE, true );
}
}

View File

@@ -0,0 +1,22 @@
<?php
class WPML_Elementor_DB_Factory {
/**
* @return null|WPML_Elementor_DB
*/
public function create() {
$wpml_elementor_db = null;
if ( version_compare( phpversion(), '5.3.0', '>=' ) && class_exists( '\Elementor\DB' ) ) {
// @codingStandardsIgnoreLine
$elementor_db = new \Elementor\DB();
if ( method_exists( $elementor_db, 'save_plain_text' ) ) {
$wpml_elementor_db = new WPML_Elementor_DB( $elementor_db );
}
}
return $wpml_elementor_db;
}
}

View File

@@ -0,0 +1,21 @@
<?php
class WPML_Elementor_DB {
/**
* @var \Elementor\DB
*/
private $elementor_db;
// @codingStandardsIgnoreLine
public function __construct( \Elementor\DB $elementor_db ) {
$this->elementor_db = $elementor_db;
}
/**
* @param int $post_id
*/
public function save_plain_text( $post_id ) {
$this->elementor_db->save_plain_text( $post_id );
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* Class WPML_Elementor_Integration_Factory
*/
class WPML_Elementor_Integration_Factory {
const SLUG = 'elementor';
/**
* @return WPML_Page_Builders_Integration
*/
public function create() {
$action_filter_loader = new WPML_Action_Filter_Loader();
$action_filter_loader->load(
array(
'WPML_Elementor_Translate_IDs_Factory',
'WPML_Elementor_URLs_Factory',
'WPML_Elementor_Adjust_Global_Widget_ID_Factory',
'WPML_PB_Elementor_Handle_Custom_Fields_Factory',
'WPML_Elementor_Media_Hooks_Factory',
'WPML_Elementor_WooCommerce_Hooks_Factory',
\WPML\PB\Elementor\Hooks\WooCommerce::class,
\WPML\PB\Elementor\LanguageSwitcher\LanguageSwitcher::class,
\WPML\PB\Elementor\Hooks\DynamicElements::class,
\WPML\PB\Elementor\Hooks\FormPopup::class,
\WPML\PB\Elementor\Hooks\GutenbergCleanup::class,
\WPML\PB\Elementor\Hooks\Frontend::class,
\WPML\PB\Elementor\Hooks\DomainsWithMultisite::class,
\WPML\PB\Elementor\Config\Factory::class,
\WPML\PB\Elementor\Hooks\LandingPages::class,
\WPML\PB\Elementor\Hooks\Editor::class,
\WPML\PB\Elementor\Hooks\WordPressWidgets::class,
\WPML\PB\Elementor\Hooks\Templates::class,
\WPML_PB_Fix_Maintenance_Query::class,
)
);
$nodes = new WPML_Elementor_Translatable_Nodes();
$elementor_db_factory = new WPML_Elementor_DB_Factory();
$data_settings = new WPML_Elementor_Data_Settings( $elementor_db_factory->create() );
$string_registration_factory = new WPML_String_Registration_Factory( $data_settings->get_pb_name() );
$string_registration = $string_registration_factory->create();
$register_strings = new WPML_Elementor_Register_Strings( $nodes, $data_settings, $string_registration );
$update_translation = new WPML_Elementor_Update_Translation( $nodes, $data_settings );
return new WPML_Page_Builders_Integration( $register_strings, $update_translation, $data_settings );
}
}

View File

@@ -0,0 +1,11 @@
<?php
class WPML_PB_Elementor_Handle_Custom_Fields_Factory implements IWPML_Backend_Action_Loader, IWPML_AJAX_Action_Loader, IWPML_Frontend_Action_Loader {
public function create() {
$elementor_db_factory = new WPML_Elementor_DB_Factory();
$data_settings = new WPML_Elementor_Data_Settings( $elementor_db_factory->create() );
return new WPML_PB_Handle_Custom_Fields( $data_settings );
}
}

Some files were not shown because too many files have changed in this diff Show More