first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
} ) );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user