'_elementor_template_type', 'value' => 'widget', ); $is_elementor_conditions = isset( $query->query_vars['meta_key'] ) && '_elementor_conditions' === $query->query_vars['meta_key']; $is_global_widget = isset( $query->query_vars['post_type'], $query->query_vars['meta_query'] ) && 'elementor_library' === $query->query_vars['post_type'] && is_array( $query->query_vars['meta_query'] ) && in_array( $global_widget_meta_query, $query->query_vars['meta_query'], true ); if ( $is_elementor_conditions || $is_global_widget ) { $query->set( 'lang', '' ); } } /** * Return empty conditions on secondary translations * * @since 2.0.0 * * @param mixed $null null for bypass. * @param int $post_id current post ID. * @param string $meta_key name of meta key. * @return mixed null or empty array */ public function elementor_conditions_empty_on_translations( $null, $post_id, $meta_key ) { return '_elementor_conditions' === $meta_key && cpel_is_translation( $post_id ) ? array( array() ) : $null; } /** * Clear empty conditions before save 'elementor_pro_theme_builder_conditions' option * * @since 2.0.0 * * @param array $value array of theme builder conditions. * @return array filtered array */ public function theme_builder_conditions_remove_empty( $value ) { foreach ( $value as $location => $items ) { $value[ $location ] = array_filter( $items ); } return array_filter( $value ); } /** * Bypass Elementor template shortcode with their translation for the current language (if exists). * * @since 2.2.0 * * @uses pll_get_post() * * @param mixed $false false or string with bypass output. * @param string $tag shortcode tag. * @param array $attr shortcode attributes. * @return false|string false or string with bypass output */ public function shortcode_template_translate( $false, $tag, $attr ) { if ( 'elementor-template' !== $tag ) { return $false; } if ( isset( $attr['skip'] ) ) { return $false; } // Translate post_id safely. if ( isset( $attr['id'] ) && '' !== $attr['id'] ) { $id = absint( $attr['id'] ); if ( $id ) { $attr['id'] = pll_get_post( $id ) ?: $id; //phpcs:ignore WordPress.PHP.DisallowShortTernary } } // Skip next call. $attr['skip'] = 1; $output = ''; foreach ( $attr as $key => $val ) { $output .= ' ' . esc_attr( sanitize_key( $key ) ) . '="' . esc_attr( $val ) . '"'; } return do_shortcode( '[elementor-template' . $output . ']' ); } /** * Widget Template translate template_id * * @since 2.3.5 * * @uses pll_get_post() * * @param \Elementor\Element_Base $element * @return void */ public function widget_template_translate( $element ) { if ( 'template' !== $element->get_name() ) { return; } $template_id = pll_get_post( $element->get_settings( 'template_id' ) ) ?: $element->get_settings( 'template_id' ); //phpcs:ignore WordPress.PHP.DisallowShortTernary $element->set_settings( 'template_id', $template_id ); } /** * WordPress Widget "Elementor Library" translate 'template_id' * * @since 2.4.4 * * @param array $instance * @param WP_Widget $widget * @return array */ public function wp_widget_template_translate( $instance, $widget ) { if ( is_a( $widget, 'ElementorPro\Modules\Library\WP_Widgets\Elementor_Library' ) ) { $instance['template_id'] = pll_get_post( absint( $instance['template_id'] ) ) ?: $instance['template_id']; //phpcs:ignore WordPress.PHP.DisallowShortTernary } return $instance; } /** * Change Elementor Kit template with their translation for the current language (if exists). * * @since 2.3.0 * * @uses pll_get_post() * @uses pll_get_post_language() * * @param mixed $value Value of 'elementor_active_kit' option, the ID of current Elementor Kit. * @return int The translation ID, or the original Elementor Kit ID */ public function elementor_kit_translation( $value ) { $translation = null; // Is API REST '/wp-json/elementor/v1/globals'. if ( defined( 'REST_REQUEST' ) && REST_REQUEST && isset( $_SERVER['HTTP_REFERER'] ) ) { // Referrer is Elementor Editor? $referer = esc_url_raw( $_SERVER['HTTP_REFERER'] ); wp_parse_str( wp_parse_url( $referer, PHP_URL_QUERY ), $query ); if ( isset( $query['action'], $query['post'] ) && 'elementor' === $query['action'] ) { $translation = pll_get_post( $value, pll_get_post_language( absint( $query['post'] ) ) ); } } elseif ( cpel_is_elementor_editor() ) { $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; $translation = $post_id ? pll_get_post( $value, pll_get_post_language( $post_id ) ) : null; } elseif ( ! is_admin() ) { $translation = pll_get_post( $value ); } return $translation ? $translation : $value; } /** * Change Elementor template with their translation for the current language (if exists). * * @link https://github.com/pojome/elementor/issues/4839 * * @since 2.0.0 * * @uses pll_get_post() * * @param int $post_id ID of the current post. * @return string Based translation, the translation ID, or the original Post ID */ public function template_id_translation( $post_id ) { $post_id = pll_get_post( $post_id ) ?: $post_id; //phpcs:ignore WordPress.PHP.DisallowShortTernary $this->template_id = $post_id; // Save for check sub_id. return $post_id; } /** * Filter Elementor sub_conditions system * * If is translated template that is based on term or post * return the translation ID of term or post. * * @since 2.0.0 * * @uses pll_get_post() * @uses pll_get_term() * * @param int $sub_id ID of the object in subcondition. * @param array $parsed_condition condition parts. * @return int original sub ID or translated ID */ public function condition_sub_id_translation( $sub_id, $parsed_condition ) { if ( $sub_id && cpel_is_translation( $this->template_id ) ) { if ( in_array( $parsed_condition['sub_name'], get_post_types(), true ) ) { $sub_id = pll_get_post( $sub_id ) ?: $sub_id; //phpcs:ignore WordPress.PHP.DisallowShortTernary } else { $sub_id = pll_get_term( $sub_id ) ?: $sub_id; //phpcs:ignore WordPress.PHP.DisallowShortTernary } } return $sub_id; } /** * Update Elementor conditions * * On change post_translations terms on Elementor Library trigger conditions regenerate. * * @since 2.0.0 * * @param mixed $post_id * @param mixed $terms * @param mixed $tt_ids * @param mixed $taxonomy * @return void */ public function update_conditions_on_term_change( $post_id, $terms, $tt_ids, $taxonomy ) { if ( 'post_translations' === $taxonomy && 'elementor_library' === get_post_type( $post_id ) ) { $theme_builder = \ElementorPro\Plugin::instance()->modules_manager->get_modules( 'theme-builder' ); $theme_builder->get_conditions_manager()->get_cache()->regenerate(); } } /** * Hide language column info pre * * Wrap language info for Global Widgets with a hidden div (open) * * @since 2.0.0 * * @param string $column * @param int $post_id * @return void */ public function hide_language_column_pre( $column, $post_id ) { if ( false !== strpos( $column, 'language_' ) && 'widget' === get_post_meta( $post_id, '_elementor_template_type', true ) ) { echo '
'; } } /** * Show default language instances in translations * * (Also wrap "None" with a hidden div) * * @since 2.0.4 * * @param string $column * @param int $post_id * @return void */ public function instances_column_pre( $column, $post_id ) { if ( 'instances' === $column && 'widget' !== get_post_meta( $post_id, '_elementor_template_type', true ) && cpel_is_translation( $post_id ) ) { $default_post = pll_get_post( $post_id, pll_default_language() ); $theme_builder = \ElementorPro\Plugin::instance()->modules_manager->get_modules( 'theme-builder' ); $instances = $theme_builder->get_conditions_manager()->get_document_instances( $default_post ); if ( empty( $instances ) ) { $instances = array( 'none' => esc_html__( 'None', 'elementor-pro' ) ); // phpcs:ignore WordPress.WP.I18n } echo '' . esc_html( implode( ', ', $instances ) ) . ''; } } /** * Don't copy '_elementor_css' meta on Polylang add new translation * * Without this meta Elementor generates the css for the new post. * * @since 2.0.0 * * @param mixed $null * @param int $post_id * @param string $meta_key * @return mixed null or false */ public function prevent_elementor_css_meta( $null, $post_id, $meta_key ) { global $pagenow; return '_elementor_css' === $meta_key && 'post-new.php' === $pagenow && isset( $_GET['from_post'], $_GET['new_lang'] ) && ! empty( absint( wp_unslash( $_GET['from_post'] ) ) ) && ! empty( sanitize_key( wp_unslash( $_GET['new_lang'] ) ) ) ? false : $null; } /** * Delete '_elementor_css' meta on Polylang bulk translation * * Without this meta Elementor generates the css for the new post. * * @since 2.4.3 * * @param int $post_id * @param int $tr_id * @param string $lang * @return void */ public function bulk_delete_elementor_css_meta( $post_id, $tr_id, $lang ) { delete_post_meta( $tr_id, '_elementor_css' ); } /** * Whitelist Elementor Pro home_url() * * Polylang add home_url() to whitelist for Elementor Pro * "Search Form" widget and "Site Url" dynamic tag. * * @since 2.0.0 * * @param array $white_list * @return array */ public function elementor_home_url_white_list( $white_list ) { $white_list[] = array( 'file' => 'site-url.php' ); return $white_list; } /** * Language subdir add trailing slash * * @since 2.0.0 * * @param string $url * @param string $path * @return string */ public function home_url_language_dir_slash( $url, $path ) { return empty( $path ) && ! is_admin() && get_option( 'home' ) !== $url && function_exists( 'PLL' ) && 1 === PLL()->options['force_lang'] ? trailingslashit( $url ) : $url; } /** * Replace home_url with correct language search url * * Only for Elementor Search Form that uses home_url() in form action. * * @since 2.0.6 * * @param string $url * @param string $path * @return string */ public function search_form_home_url_filter( $url, $path ) { if ( ! function_exists( 'PLL' ) || ! is_a( PLL()->curlang, 'PLL_Language', true ) ) { return $url; } return method_exists( PLL()->curlang, 'get_search_url' ) ? PLL()->curlang->get_search_url() : PLL()->curlang->search_url; } /** * Add home_url() filter before render Search Form * * @since 2.0.6 * * @param Element_Base $element * @return void */ public function add_search_form_home_url_filter( $element ) { if ( 'search' === $element->get_name() || 'search-form' === $element->get_name() ) { add_filter( 'home_url', array( $this, 'search_form_home_url_filter' ), 10, 2 ); } } /** * Remove home_url() filter after render Search Form * * @since 2.0.6 * * @param Element_Base $element * @return void */ public function remove_search_form_home_url_filter( $element ) { if ( 'search' === $element->get_name() || 'search-form' === $element->get_name() ) { remove_filter( 'home_url', array( $this, 'search_form_home_url_filter' ) ); } } /** * Elementor editor script * * Add script with links to translations on Elementor editor panel. * * @since 2.0.0 * * @return void */ public function elementor_editor_script() { global $typenow, $post; // If is post type translatable. if ( pll_is_translated_post_type( $typenow ) ) { $languages = pll_languages_list( array( 'fields' => '' ) ); $translations = pll_get_post_translations( $post->ID ); $current = pll_get_post_language( $post->ID, 'name' ); $use_emojis = apply_filters( 'cpel/filter/use_emojis', true ); $items = array(); foreach ( $languages as $language ) { if ( $language->name === $current ) { $translation_id = $translations[ $language->slug ]; $items[] = array( 'name' => 'cpel-current', 'icon' => 'eicon-document-file', 'title' => sprintf( '%s — %s', get_the_title( $translation_id ), $use_emojis ? cpel_flag_emoji( $language->flag_code ) : $language->name ), 'callback' => 'function(){}', ); } elseif ( isset( $translations[ $language->slug ] ) ) { $translation_id = $translations[ $language->slug ]; $link = $this->fix_url_domain( get_edit_post_link( $translation_id, 'edit' ), $translation_id ); if ( get_post_meta( $translation_id, '_elementor_edit_mode', true ) ) { $link = add_query_arg( 'action', 'elementor', $link ); } $items[] = array( 'name' => "cpel-{$language->slug}", 'icon' => 'eicon-document-file', 'title' => sprintf( '%s — %s', get_the_title( $translation_id ), $use_emojis ? cpel_flag_emoji( $language->flag_code ) : $language->name ), 'type' => 'link', 'link' => $link, ); } else { $args = array( 'post_type' => $typenow, 'from_post' => $post->ID, 'new_lang' => $language->slug, '_wpnonce' => wp_create_nonce( 'new-post-translation' ), ); $link = add_query_arg( $args, admin_url( 'post-new.php' ) ); $items[] = array( 'name' => "cpel-{$language->slug}", 'icon' => 'eicon-plus', 'title' => $use_emojis ? sprintf( __( 'Add a translation — %s', 'connect-polylang-elementor' ), cpel_flag_emoji( $language->flag_code ) ) // phpcs:ignore WordPress.WP.I18n : sprintf( __( 'Add a translation in %s', 'polylang' ), $language->name ), // phpcs:ignore WordPress.WP.I18n 'type' => 'link', 'link' => $link, ); } } $group = array( 'name' => 'cpel', 'title' => __( 'Languages', 'polylang' ), // phpcs:ignore WordPress.WP.I18n 'items' => $items, ); $script = 'jQuery(window).on("elementor:init", () => { window.elementor.on("panel:init", () => { setTimeout(() => { window.elementor.modules.layouts.panel.pages.menu.Menu.groups.add(' . wp_json_encode( $group ) . '); }); }); });'; // Add after Elementor editor script. wp_add_inline_script( 'elementor-editor', $script ); } } /** * Elementor editor script * * Add script with links to translations on Elementor editor panel. * * @since 2.3.5 * * @return void */ public function elementor_editor_style() { global $typenow; // If is post type translatable. if ( ! pll_is_translated_post_type( $typenow ) ) { return; } // New language switcher styles for Elementor 3.25.0+. if ( cpel_elementor_min_version( '3.25.0' ) ) { $style = ' .elementor-control-cpel-languages a { display: flex; gap: 8px; align-items: center; padding: 8px 5px; border: none; color: inherit; line-height: 1.25em; font-weight: 400; text-decoration: none; transition: none; } .elementor-control-cpel-languages a.current, .elementor-control-cpel-languages a:hover { background-color: var(--e-a-bg-hover); color: inherit; } .elementor-control-cpel-languages i { font-size: 16px; } .elementor-control-cpel-languages .flag { margin-left: auto; font-size: 16px; }'; wp_add_inline_style( 'elementor-editor', $style ); return; } // Old language switcher styles for Elementor < 3.25.0 $style = '' . ".elementor-panel .elementor-panel-menu-item.elementor-panel-menu-item-cpel-current {\n" . " background: #eceeef;\n" . " cursor: default;\n" . '}'; wp_add_inline_style( 'elementor-editor', $style ); $ui_theme = SettingsManager::get_settings_managers( 'editorPreferences' )->get_model()->get_settings( 'ui_theme' ); if ( 'light' !== $ui_theme ) { $ui_theme_media_queries = 'auto' === $ui_theme ? '(prefers-color-scheme: dark)' : 'all'; $dark = '' . "@media $ui_theme_media_queries {\n" . " .elementor-panel .elementor-panel-menu-item.elementor-panel-menu-item-cpel-current {\n" . " background: #7d7e82;\n" . " }\n" . '}'; wp_add_inline_style( 'elementor-editor-dark-mode', $dark ); } } /** * Elementor Site Editor template changes * * At 2.0.0 named "elementor_theme_editor_title" * * @since 2.0.4 * * @param array $data * @return array */ public function elementor_site_editor_template( $data ) { $post_id = $data['id']; // Add lang info to title. if ( apply_filters( 'cpel/filter/use_emojis', true ) ) { $data['title'] = sprintf( '%s — %s', $data['title'], cpel_flag_emoji( pll_get_post_language( $post_id, 'flag_code' ) ) ); } else { $data['title'] = sprintf( '%s — %s', $data['title'], pll_get_post_language( $post_id, 'name' ) ); } // Show default language instances in translations (and recalc isActive). if ( cpel_is_translation( $post_id ) ) { $language = pll_default_language(); $default_post = pll_get_post( $post_id, $language ); $theme_builder = \ElementorPro\Plugin::instance()->modules_manager->get_modules( 'theme-builder' ); $instances = $theme_builder->get_conditions_manager()->get_document_instances( $default_post ); if ( empty( $instances ) ) { $instances = array( 'no_instances' => esc_html__( 'No instances', 'elementor-pro' ) ); // phpcs:ignore WordPress.WP.I18n $is_active = false; } else { $is_active = 'publish' === $data['status']; } $data['instances'] = array( 'cpel' => sprintf( esc_html__( '(from %s)', 'connect-polylang-elementor' ), strtoupper( $language ) ) ) + $instances; // phpcs:ignore WordPress.WP.I18n $data['isActive'] = $is_active; } return $data; } /** * Fix url domain * * @param mixed $url current url. * @param mixed $post_id current post ID. * @return string fixed domain url */ private function fix_url_domain( $url, $post_id ) { $current_host = wp_parse_url( pll_current_language( 'home_url' ) ?: trailingslashit( '//' . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ), PHP_URL_HOST ); //phpcs:ignore WordPress.PHP.DisallowShortTernary $post_host = wp_parse_url( pll_get_post_language( $post_id, 'home_url' ), PHP_URL_HOST ); if ( $current_host !== $post_host ) { $url = str_replace( $current_host, $post_host, $url ); } return $url; } /** * Fix domain for Elementor edit links in posts table * * @param array $actions * @param WP_Post $post * @return array */ public function fix_edit_link( $actions, $post ) { if ( ! empty( $actions['edit_with_elementor'] ) ) { // $actions['edit'] = $this->fix_url_domain( $actions['edit'], $post->ID ); $actions['edit_with_elementor'] = $this->fix_url_domain( $actions['edit_with_elementor'], $post->ID ); } return $actions; } /** * Fix domain for Elementor edit links in Theme Builder * * @param string $url * @param Elementor\Core\Base\Document $document * @return string */ public function fix_elementor_edit_link( $url, $document ) { return $this->fix_url_domain( $url, $document->get_main_id() ); } /** * Register language switcher controls in Elementor's document settings panel. * This function adds a "Languages" section where users can manage translations for the current post. * * @param \Elementor\Base\Document $document The Elementor document object. * @since 2.5.0 */ public function register_language_switcher_controls( $document ) { global $typenow, $post; // Exit if is not translatable. if ( ! pll_is_translated_post_type( $typenow ) ) { return; } // Get the current post ID being edited in Elementor. $post_id = $post->ID; // Retrieve available languages from Polylang. $languages = pll_languages_list( array( 'fields' => '' ) ); $translations = pll_get_post_translations( $post_id ); $raw_html = ''; // Start adding a new section in Elementor settings panel. $document->start_controls_section( 'cpel_language_section', array( 'label' => esc_html__( 'Languages', 'polylang' ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch 'tab' => \Elementor\Controls_Manager::TAB_SETTINGS, ) ); // Loop through each available language. foreach ( $languages as $language ) { $lang_parts = array( 'class' => 'cpel-language', 'href' => '', 'icon' => '', 'text' => '', 'flag' => str_replace( '