diff --git a/wp-content/plugins/polylang/admin/admin-base.php b/wp-content/plugins/polylang/admin/admin-base.php new file mode 100644 index 0000000000..1553c3dd0b --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-base.php @@ -0,0 +1,575 @@ +static_pages = new PLL_Admin_Static_Pages( $this ); + $this->model->set_languages_ready(); + } + + /** + * Setups filters and action needed on all admin pages and on plugins page + * Loads the settings pages or the filters base on the request + * + * @since 1.2 + */ + public function init() { + parent::init(); + + $this->notices = new PLL_Admin_Notices( $this ); + + $this->default_term = new PLL_Admin_Default_Term( $this ); + $this->default_term->add_hooks(); + + if ( ! $this->model->has_languages() ) { + return; + } + + $this->links = new PLL_Admin_Links( $this ); // FIXME needed here ? + $this->filters_links = new PLL_Filters_Links( $this ); // FIXME needed here ? + + // Filter admin language for users + // We must not call user info before WordPress defines user roles in wp-settings.php + add_action( 'setup_theme', array( $this, 'init_user' ) ); + add_filter( 'request', array( $this, 'request' ) ); + + // Adds the languages in admin bar + add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ), 100 ); // 100 determines the position + } + + /** + * Adds the link to the languages panel in the WordPress admin menu + * + * @since 0.1 + * + * @return void + */ + public function add_menus() { + global $admin_page_hooks; + + // Prepare the list of tabs + $tabs = array( 'lang' => __( 'Languages', 'polylang' ) ); + + // Only if at least one language has been created + if ( $this->model->has_languages() ) { + $tabs['strings'] = __( 'Translations', 'polylang' ); + } + + $tabs['settings'] = __( 'Settings', 'polylang' ); + + /** + * Filter the list of tabs in Polylang settings + * + * @since 1.5.1 + * + * @param array $tabs list of tab names + */ + $tabs = apply_filters( 'pll_settings_tabs', $tabs ); + + $parent = ''; + + foreach ( $tabs as $tab => $title ) { + $page = 'lang' === $tab ? 'mlang' : "mlang_$tab"; + if ( empty( $parent ) ) { + $parent = $page; + add_menu_page( $title, __( 'Languages', 'polylang' ), 'manage_options', $page, '__return_null', 'dashicons-translation' ); + $admin_page_hooks[ $page ] = 'languages'; // Hack to avoid the localization of the hook name. See: https://core.trac.wordpress.org/ticket/18857 + } + + add_submenu_page( $parent, $title, $title, 'manage_options', $page, array( $this, 'languages_page' ) ); + } + } + + /** + * Setup js scripts & css styles ( only on the relevant pages ) + * + * @since 0.6 + * + * @return void + */ + public function admin_enqueue_scripts() { + $screen = get_current_screen(); + if ( empty( $screen ) ) { + return; + } + + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + /* + * For each script: + * 0 => the pages on which to load the script + * 1 => the scripts it needs to work + * 2 => true if loaded even if languages have not been defined yet, false otherwise + * 3 => true if loaded in footer + */ + $scripts = array( + 'user' => array( array( 'profile', 'user-edit' ), array( 'jquery' ), false, false ), + 'widgets' => array( array( 'widgets' ), array( 'jquery' ), false, false ), + ); + + $block_screens = array( 'widgets', 'site-editor' ); + + if ( ! empty( $screen->post_type ) && $this->model->is_translated_post_type( $screen->post_type ) ) { + $scripts['post'] = array( array( 'edit', 'upload' ), array( 'jquery', 'wp-ajax-response' ), false, true ); + + // Classic editor. + if ( ! method_exists( $screen, 'is_block_editor' ) || ! $screen->is_block_editor() ) { + $scripts['classic-editor'] = array( array( 'post', 'media', 'async-upload' ), array( 'jquery', 'wp-ajax-response', 'post', 'jquery-ui-dialog', 'wp-i18n' ), false, true ); + } + + // Block editor with legacy metabox in WP 5.0+. + $block_screens[] = 'post'; + } + + if ( $this->is_block_editor( $screen ) ) { + $scripts['block-editor'] = array( $block_screens, array( 'jquery', 'wp-ajax-response', 'wp-api-fetch', 'jquery-ui-dialog', 'wp-i18n' ), false, true ); + } + + if ( ! empty( $screen->taxonomy ) && $this->model->is_translated_taxonomy( $screen->taxonomy ) ) { + $scripts['term'] = array( array( 'edit-tags', 'term' ), array( 'jquery', 'wp-ajax-response', 'jquery-ui-autocomplete' ), false, true ); + } + + foreach ( $scripts as $script => $v ) { + if ( in_array( $screen->base, $v[0] ) && ( $v[2] || $this->model->has_languages() ) ) { + wp_enqueue_script( 'pll_' . $script, plugins_url( '/js/build/' . $script . $suffix . '.js', POLYLANG_ROOT_FILE ), $v[1], POLYLANG_VERSION, $v[3] ); + if ( 'classic-editor' === $script || 'block-editor' === $script ) { + wp_set_script_translations( 'pll_' . $script, 'polylang' ); + } + } + } + + wp_register_style( 'polylang_admin', plugins_url( '/css/build/admin' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'wp-jquery-ui-dialog' ), POLYLANG_VERSION ); + wp_enqueue_style( 'polylang_dialog', plugins_url( '/css/build/dialog' . $suffix . '.css', POLYLANG_ROOT_FILE ), array( 'polylang_admin' ), POLYLANG_VERSION ); + + $this->add_inline_scripts(); + } + + /** + * Tells whether or not the given screen is block editor kind. + * e.g. widget, site or post editor. + * + * @since 3.3 + * + * @param WP_Screen $screen Screen object. + * @return bool True if the screen is a block editor, false otherwise. + */ + protected function is_block_editor( $screen ) { + return method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() && ! pll_use_block_editor_plugin(); + } + + /** + * Enqueue scripts to the WP Customizer. + * + * @since 2.4.0 + * + * @return void + */ + public function customize_controls_enqueue_scripts() { + if ( $this->model->has_languages() ) { + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + wp_enqueue_script( 'pll_widgets', plugins_url( '/js/build/widgets' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true ); + $this->add_inline_scripts(); + } + } + + /** + * Adds inline scripts to set the default language in JS + * and localizes scripts. + * + * @since 3.3 + * + * @return void + */ + private function add_inline_scripts() { + if ( wp_script_is( 'pll_block-editor', 'enqueued' ) ) { + $default_lang_script = 'const pllDefaultLanguage = "' . $this->options['default_lang'] . '";'; + wp_add_inline_script( + 'pll_block-editor', + $default_lang_script, + 'before' + ); + } + if ( wp_script_is( 'pll_widgets', 'enqueued' ) ) { + wp_localize_script( + 'pll_widgets', + 'pll_widgets', + array( + 'flags' => wp_list_pluck( $this->model->get_languages_list(), 'flag', 'slug' ), + ) + ); + } + } + + /** + * Sets pll_ajax_backend on all backend ajax request + * The final goal is to detect if an ajax request is made on admin or frontend + * + * Takes care to various situations: + * when the ajax request has no options.data thanks to ScreenfeedFr + * see: https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected + * when options.data is a json string + * see: https://wordpress.org/support/topic/polylang-breaking-third-party-ajax-requests-on-admin-panels + * when options.data is an empty string (GET request with the method 'load') + * see: https://wordpress.org/support/topic/invalid-url-during-wordpress-new-dashboard-widget-operation + * + * @since 1.4 + * + * @return void + */ + public function admin_print_footer_scripts() { + global $post_ID, $tag_ID; + + $params = array( 'pll_ajax_backend' => 1 ); + if ( ! empty( $post_ID ) ) { + $params = array_merge( $params, array( 'pll_post_id' => (int) $post_ID ) ); + } + + if ( ! empty( $tag_ID ) ) { + $params = array_merge( $params, array( 'pll_term_id' => (int) $tag_ID ) ); + } + + /** + * Filters the list of parameters to add to the admin ajax request. + * + * @since 3.4.5 + * + * @param array $params List of parameters to add to the admin ajax request. + */ + $params = apply_filters( 'pll_admin_ajax_params', $params ); + + $str = http_build_query( $params ); + $arr = wp_json_encode( $params ); + ?> + + curlang = $this->filter_lang; + + // Edit Post + if ( isset( $_REQUEST['pll_post_id'] ) && $lang = $this->model->post->get_language( (int) $_REQUEST['pll_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $lang; + } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['post'] ) && $this->model->is_translated_post_type( get_post_type( (int) $_GET['post'] ) ) && $lang = $this->model->post->get_language( (int) $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $lang; + } elseif ( 'post-new.php' === $GLOBALS['pagenow'] && ( empty( $_GET['post_type'] ) || $this->model->is_translated_post_type( sanitize_key( $_GET['post_type'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = empty( $_GET['new_lang'] ) ? $this->pref_lang : $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + } + + // Edit Term + elseif ( isset( $_REQUEST['pll_term_id'] ) && $lang = $this->model->term->get_language( (int) $_REQUEST['pll_term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $lang; + } elseif ( in_array( $GLOBALS['pagenow'], array( 'edit-tags.php', 'term.php' ) ) && isset( $_GET['taxonomy'] ) && $this->model->is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + if ( isset( $_GET['tag_ID'] ) && $lang = $this->model->term->get_language( (int) $_GET['tag_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $lang; + } elseif ( ! empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + } elseif ( empty( $this->curlang ) ) { + $this->curlang = $this->pref_lang; + } + } + + // Ajax + if ( wp_doing_ajax() && ! empty( $_REQUEST['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->curlang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Filters the current language used by Polylang in the admin context. + * + * @since 3.2 + * + * @param PLL_Language|false|null $curlang Instance of the current language. + * @param PLL_Admin_Base $polylang Instance of the main Polylang's object. + */ + $this->curlang = apply_filters( 'pll_admin_current_language', $this->curlang, $this ); + + // Inform that the admin language has been set. + if ( $this->curlang instanceof PLL_Language ) { + /** This action is documented in frontend/choose-lang.php */ + do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang ); + } else { + /** This action is documented in include/class-polylang.php */ + do_action( 'pll_no_language_defined' ); // To load overridden textdomains. + } + } + + /** + * Defines the backend language and the admin language filter based on user preferences + * + * @since 1.2.3 + * + * @return void + */ + public function init_user() { + // Language for admin language filter: may be empty + // $_GET['lang'] is numeric when editing a language, not when selecting a new language in the filter + // We intentionally don't use a nonce to update the language filter + if ( ! wp_doing_ajax() && ! empty( $_GET['lang'] ) && ! is_numeric( sanitize_key( $_GET['lang'] ) ) && current_user_can( 'edit_user', $user_id = get_current_user_id() ) ) { // phpcs:ignore WordPress.Security.NonceVerification + update_user_meta( $user_id, 'pll_filter_content', ( $lang = $this->model->get_language( sanitize_key( $_GET['lang'] ) ) ) ? $lang->slug : '' ); // phpcs:ignore WordPress.Security.NonceVerification + } + + $this->filter_lang = $this->model->get_language( get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ); + + // Set preferred language for use when saving posts and terms: must not be empty + $this->pref_lang = empty( $this->filter_lang ) ? $this->model->get_default_language() : $this->filter_lang; + + /** + * Filters the preferred language on admin side. + * The preferred language is used for example to determine the language of a new post. + * + * @since 1.2.3 + * + * @param PLL_Language $pref_lang Preferred language. + */ + $this->pref_lang = apply_filters( 'pll_admin_preferred_language', $this->pref_lang ); + + $this->set_current_language(); + } + + /** + * Avoids parsing a tax query when all languages are requested + * Fixes https://wordpress.org/support/topic/notice-undefined-offset-0-in-wp-includesqueryphp-on-line-3877 introduced in WP 4.1 + * + * @see https://core.trac.wordpress.org/ticket/31246 the suggestion of @boonebgorges. + * + * @since 1.6.5 + * + * @param array $qvars The array of requested query variables. + * @return array + */ + public function request( $qvars ) { + if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) { + unset( $qvars['lang'] ); + } + + return $qvars; + } + + /** + * Adds the languages list in admin bar for the admin languages filter. + * + * @since 0.9 + * + * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar global object. + * @return void + */ + public function admin_bar_menu( $wp_admin_bar ) { + $all_item = (object) array( + 'slug' => 'all', + 'name' => __( 'Show all languages', 'polylang' ), + 'flag' => '', + ); + + $selected = empty( $this->filter_lang ) ? $all_item : $this->filter_lang; + + $title = sprintf( + '%2$s%3$s', + $selected instanceof PLL_Language ? sprintf( ' lang="%s"', esc_attr( $selected->get_locale( 'display' ) ) ) : '', + __( 'Filters content by language', 'polylang' ), + esc_html( $selected->name ) + ); + + /** + * Filters the admin languages filter submenu items + * + * @since 2.6 + * + * @param array $items The admin languages filter submenu items. + */ + $items = apply_filters( 'pll_admin_languages_filter', array_merge( array( $all_item ), $this->model->get_languages_list() ) ); + + $menu = array( + 'id' => 'languages', + 'title' => $selected->flag . $title, + 'href' => esc_url( add_query_arg( 'lang', $selected->slug, remove_query_arg( 'paged' ) ) ), + 'meta' => array( + 'title' => __( 'Filters content by language', 'polylang' ), + ), + ); + + if ( 'all' !== $selected->slug ) { + $menu['meta']['class'] = 'pll-filtered-languages'; + } + + if ( ! empty( $items ) ) { + $wp_admin_bar->add_menu( $menu ); + } + + foreach ( $items as $lang ) { + if ( $selected->slug === $lang->slug ) { + continue; + } + + $wp_admin_bar->add_menu( + array( + 'parent' => 'languages', + 'id' => $lang->slug, + 'title' => $lang->flag . esc_html( $lang->name ), + 'href' => esc_url( add_query_arg( 'lang', $lang->slug, remove_query_arg( 'paged' ) ) ), + 'meta' => 'all' === $lang->slug ? array() : array( 'lang' => esc_attr( $lang->get_locale( 'display' ) ) ), + ) + ); + } + } + + /** + * Remove the customize submenu when using a block theme. + * + * WordPress removes the Customizer menu if a block theme is activated and no other plugins interact with it. + * As Polylang interacts with the Customizer, we have to delete this menu ourselves in the case of a block theme, + * unless another plugin than Polylang interacts with the Customizer. + * + * @since 3.2 + * + * @return void + */ + public function remove_customize_submenu() { + if ( ! $this->should_customize_menu_be_removed() ) { + return; + } + + global $submenu; + + if ( ! empty( $submenu['themes.php'] ) ) { + foreach ( $submenu['themes.php'] as $submenu_item ) { + if ( 'customize' === $submenu_item[1] ) { + remove_submenu_page( 'themes.php', $submenu_item[2] ); + } + } + } + } +} diff --git a/wp-content/plugins/polylang/admin/admin-block-editor.php b/wp-content/plugins/polylang/admin/admin-block-editor.php new file mode 100644 index 0000000000..6174c73264 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-block-editor.php @@ -0,0 +1,92 @@ +model = &$polylang->model; + $this->filter_rest_routes = new PLL_Filter_REST_Routes( $polylang->model ); + + add_filter( 'block_editor_rest_api_preload_paths', array( $this, 'filter_preload_paths' ), 50, 2 ); + add_action( 'admin_enqueue_scripts', array( $this, 'add_block_editor_inline_script' ), 15 ); // After `PLL_Admin_Base::admin_enqueue_scripts()` to ensure `pll_block-editor`script is enqueued. + } + + /** + * Filters preload paths based on the context (block editor for posts, site editor or widget editor for instance). + * + * @since 3.5 + * + * @param array $preload_paths Preload paths. + * @param WP_Block_Editor_Context $context Editor context. + * @return array Filtered preload paths. + */ + public function filter_preload_paths( $preload_paths, $context ) { + if ( ! $context instanceof WP_Block_Editor_Context ) { + return $preload_paths; + } + + // Backward compatibility with WP < 6.0 where `WP_Block_Editor_Context::$name` doesn't exist yet. + if ( + ( property_exists( $context, 'name' ) && 'core/edit-post' !== $context->name ) + || ! $context->post instanceof WP_Post + ) { + // Do nothing if not post editor. + return $preload_paths; + } + + if ( ! $this->model->is_translated_post_type( $context->post->post_type ) ) { + return $preload_paths; + } + + $language = $this->model->post->get_language( $context->post->ID ); + + if ( empty( $language ) ) { + return $preload_paths; + } + + return $this->filter_rest_routes->add_query_parameters( + $preload_paths, + array( + 'lang' => $language->slug, + ) + ); + } + + /** + * Adds inline block editor script for filterable REST routes. + * + * @since 3.5 + * + * @return void + */ + public function add_block_editor_inline_script() { + $handle = 'pll_block-editor'; + + if ( wp_script_is( $handle, 'enqueued' ) ) { + $this->filter_rest_routes->add_inline_script( $handle ); + } + } +} diff --git a/wp-content/plugins/polylang/admin/admin-classic-editor.php b/wp-content/plugins/polylang/admin/admin-classic-editor.php new file mode 100644 index 0000000000..5f6f1d0c70 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-classic-editor.php @@ -0,0 +1,371 @@ +model = &$polylang->model; + $this->links = &$polylang->links; + $this->curlang = &$polylang->curlang; + $this->pref_lang = &$polylang->pref_lang; + + // Adds the Languages box in the 'Edit Post' and 'Edit Page' panels + add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) ); + + // Ajax response for changing the language in the post metabox + add_action( 'wp_ajax_post_lang_choice', array( $this, 'post_lang_choice' ) ); + add_action( 'wp_ajax_pll_posts_not_translated', array( $this, 'ajax_posts_not_translated' ) ); + + // Filters the pages by language in the parent dropdown list in the page attributes metabox + add_filter( 'page_attributes_dropdown_pages_args', array( $this, 'page_attributes_dropdown_pages_args' ), 10, 2 ); + + // Notice + add_action( 'edit_form_top', array( $this, 'edit_form_top' ) ); + } + + /** + * Adds the Language box in the 'Edit Post' and 'Edit Page' panels ( as well as in custom post types panels ) + * + * @since 0.1 + * + * @param string $post_type Current post type + * @return void + */ + public function add_meta_boxes( $post_type ) { + if ( $this->model->is_translated_post_type( $post_type ) ) { + add_meta_box( + 'ml_box', + __( 'Languages', 'polylang' ), + array( $this, 'post_language' ), + $post_type, + 'side', + 'high', + array( + '__back_compat_meta_box' => pll_use_block_editor_plugin(), + ) + ); + } + } + + /** + * Displays the Languages metabox in the 'Edit Post' and 'Edit Page' panels + * + * @since 0.1 + * + * @return void + */ + public function post_language() { + global $post_ID; + $post_type = get_post_type( $post_ID ); + + // phpcs:ignore WordPress.Security.NonceVerification, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $from_post_id = isset( $_GET['from_post'] ) ? (int) $_GET['from_post'] : 0; + + $lang = ( $lg = $this->model->post->get_language( $post_ID ) ) ? $lg : + ( isset( $_GET['new_lang'] ) ? $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification + $this->pref_lang ); + + $dropdown = new PLL_Walker_Dropdown(); + + $id = ( 'attachment' === $post_type ) ? sprintf( 'attachments[%d][language]', (int) $post_ID ) : 'post_lang_choice'; + + $dropdown_html = $dropdown->walk( + $this->model->get_languages_list(), + -1, + array( + 'name' => $id, + 'class' => 'post_lang_choice tags-input', + 'selected' => $lang ? $lang->slug : '', + 'flag' => true, + ) + ); + + wp_nonce_field( 'pll_language', '_pll_nonce' ); + + // NOTE: the class "tags-input" allows to include the field in the autosave $_POST ( see autosave.js ) + printf( + '

%1$s

+ +
%4$s
', + esc_html__( 'Language', 'polylang' ), + esc_attr( $id ), + ( 'attachment' === $post_type ? 'media' : 'post' ), + $dropdown_html // phpcs:ignore WordPress.Security.EscapeOutput + ); + + /** + * Fires before displaying the list of translations in the Languages metabox for posts + * + * @since 1.8 + */ + do_action( 'pll_before_post_translations', $post_type ); + + echo '
'; + if ( $lang ) { + if ( 'attachment' === $post_type ) { + include __DIR__ . '/view-translations-media.php'; + } else { + include __DIR__ . '/view-translations-post.php'; + } + } + echo '
' . "\n"; + } + + /** + * Ajax response for changing the language in the post metabox + * + * @since 0.2 + * + * @return void + */ + public function post_lang_choice() { + check_ajax_referer( 'pll_language', '_pll_nonce' ); + + if ( ! isset( $_POST['post_id'], $_POST['lang'], $_POST['post_type'] ) ) { + wp_die( 'The request is missing the parameter "post_type", "lang" and/or "post_id".' ); + } + + global $post_ID; // Obliged to use the global variable for wp_popular_terms_checklist + $post_ID = (int) $_POST['post_id']; + $lang_slug = sanitize_key( $_POST['lang'] ); + $lang = $this->model->get_language( $lang_slug ); + $post_type = sanitize_key( $_POST['post_type'] ); + + if ( empty( $lang ) ) { + wp_die( esc_html( "{$lang_slug} is not a valid language code." ) ); + } + + $post_type_object = get_post_type_object( $post_type ); + + if ( empty( $post_type_object ) ) { + wp_die( esc_html( "{$post_type} is not a valid post type." ) ); + } + + if ( ! current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) { + wp_die( 'You are not allowed to edit this post.' ); + } + + $this->model->post->set_language( $post_ID, $lang ); + + ob_start(); + if ( 'attachment' === $post_type ) { + include __DIR__ . '/view-translations-media.php'; + } else { + include __DIR__ . '/view-translations-post.php'; + } + $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) ); + ob_end_clean(); + + // Categories + if ( isset( $_POST['taxonomies'] ) ) { // Not set for pages + $supplemental = array(); + + foreach ( array_map( 'sanitize_key', $_POST['taxonomies'] ) as $taxname ) { + $taxonomy = get_taxonomy( $taxname ); + + if ( ! empty( $taxonomy ) ) { + ob_start(); + $popular_ids = wp_popular_terms_checklist( $taxonomy->name ); + $supplemental['populars'] = ob_get_contents(); + ob_end_clean(); + + ob_start(); + // Use $post_ID to remember checked terms in case we come back to the original language + wp_terms_checklist( $post_ID, array( 'taxonomy' => $taxonomy->name, 'popular_cats' => $popular_ids ) ); + $supplemental['all'] = ob_get_contents(); + ob_end_clean(); + + $supplemental['dropdown'] = wp_dropdown_categories( + array( + 'taxonomy' => $taxonomy->name, + 'hide_empty' => 0, + 'name' => 'new' . $taxonomy->name . '_parent', + 'orderby' => 'name', + 'hierarchical' => 1, + 'show_option_none' => '— ' . $taxonomy->labels->parent_item . ' —', + 'echo' => 0, + ) + ); + + $x->Add( array( 'what' => 'taxonomy', 'data' => $taxonomy->name, 'supplemental' => $supplemental ) ); + } + } + } + + // Parent dropdown list ( only for hierarchical post types ) + if ( in_array( $post_type, get_post_types( array( 'hierarchical' => true ) ) ) ) { + $post = get_post( $post_ID ); + + if ( ! empty( $post ) ) { + // Args and filter from 'page_attributes_meta_box' in wp-admin/includes/meta-boxes.php of WP 4.2.1 + $dropdown_args = array( + 'post_type' => $post->post_type, + 'exclude_tree' => $post->ID, + 'selected' => $post->post_parent, + 'name' => 'parent_id', + 'show_option_none' => __( '(no parent)', 'polylang' ), + 'sort_column' => 'menu_order, post_title', + 'echo' => 0, + ); + + /** This filter is documented in wp-admin/includes/meta-boxes.php */ + $dropdown_args = (array) apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post ); // Since WP 3.3. + $dropdown_args['echo'] = 0; // Make sure to not print it. + + /** @var string $data */ + $data = wp_dropdown_pages( $dropdown_args ); // phpcs:ignore WordPress.Security.EscapeOutput + + $x->Add( array( 'what' => 'pages', 'data' => $data ) ); + } + } + + // Flag + $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) ); + + // Sample permalink + $x->Add( array( 'what' => 'permalink', 'data' => get_sample_permalink_html( $post_ID ) ) ); + + $x->send(); + } + + /** + * Ajax response for input in translation autocomplete input box + * + * @since 1.5 + * + * @return void + */ + public function ajax_posts_not_translated() { + check_ajax_referer( 'pll_language', '_pll_nonce' ); + + if ( ! isset( $_GET['post_type'], $_GET['post_language'], $_GET['translation_language'], $_GET['term'], $_GET['pll_post_id'] ) ) { + wp_die( 0 ); + } + + $post_type = sanitize_key( $_GET['post_type'] ); + + if ( ! post_type_exists( $post_type ) ) { + wp_die( 0 ); + } + + $term = wp_unslash( $_GET['term'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + + $post_language = $this->model->get_language( sanitize_key( $_GET['post_language'] ) ); + $translation_language = $this->model->get_language( sanitize_key( $_GET['translation_language'] ) ); + + $return = array(); + + $untranslated_posts = $this->model->post->get_untranslated( $post_type, $post_language, $translation_language, $term ); + + // format output + foreach ( $untranslated_posts as $post ) { + $return[] = array( + 'id' => $post->ID, + 'value' => $post->post_title, + 'link' => $this->links->edit_post_translation_link( $post->ID ), + ); + } + + // Add current translation in list + if ( $post_id = $this->model->post->get_translation( (int) $_GET['pll_post_id'], $translation_language ) ) { + $post = get_post( $post_id ); + + if ( ! empty( $post ) ) { + array_unshift( + $return, + array( + 'id' => $post_id, + 'value' => $post->post_title, + 'link' => $this->links->edit_post_translation_link( $post_id ), + ) + ); + } + } + + wp_die( wp_json_encode( $return ) ); + } + + /** + * Filters the pages by language in the parent dropdown list in the page attributes metabox. + * + * @since 0.6 + * + * @param array $dropdown_args Arguments passed to wp_dropdown_pages(). + * @param WP_Post $post The page being edited. + * @return array Modified arguments. + */ + public function page_attributes_dropdown_pages_args( $dropdown_args, $post ) { + $language = isset( $_POST['lang'] ) ? $this->model->get_language( sanitize_key( $_POST['lang'] ) ) : $this->model->post->get_language( $post->ID ); // phpcs:ignore WordPress.Security.NonceVerification + + if ( empty( $language ) ) { + $language = $this->pref_lang; + } + + if ( ! empty( $language ) ) { + $dropdown_args['lang'] = $language->slug; + } + + return $dropdown_args; + } + + /** + * Displays a notice if the user has not sufficient rights to overwrite synchronized taxonomies and metas. + * + * @since 2.6 + * + * @param WP_Post $post the post currently being edited. + * @return void + */ + public function edit_form_top( $post ) { + if ( ! $this->model->post->current_user_can_synchronize( $post->ID ) ) { + ?> +
+

+ +

+
+ model = &$polylang->model; + $this->pref_lang = &$polylang->pref_lang; + $this->taxonomies = $this->model->get_translated_taxonomies(); + } + + /** + * Setups filters and actions needed. + * + * @since 3.1 + * + * @return void + */ + public function add_hooks() { + foreach ( $this->taxonomies as $taxonomy ) { + if ( 'category' === $taxonomy ) { + // Allows to get the default terms in all languages + add_filter( 'option_default_' . $taxonomy, array( $this, 'option_default_term' ) ); + add_action( 'update_option_default_' . $taxonomy, array( $this, 'update_option_default_term' ), 10, 2 ); + + // Adds the language column in the 'Terms' table. + add_filter( 'manage_' . $taxonomy . '_custom_column', array( $this, 'term_column' ), 10, 3 ); + } + } + add_action( 'pll_add_language', array( $this, 'handle_default_term_on_create_language' ) ); + + // The default term should be in the default language + add_action( 'pll_update_default_lang', array( $this, 'update_default_term_language' ) ); + + // Prevents deleting all the translations of the default term + add_filter( 'map_meta_cap', array( $this, 'fix_delete_default_term' ), 10, 4 ); + } + + /** + * Filters the default term in note below the term list table and in settings->writing dropdown + * + * @since 1.2 + * + * @param int $taxonomy_term_id The taxonomy term id. + * @return int A taxonomy term id. + */ + public function option_default_term( $taxonomy_term_id ) { + if ( isset( $this->pref_lang ) && $tr = $this->model->term->get( $taxonomy_term_id, $this->pref_lang ) ) { + $taxonomy_term_id = $tr; + } + return $taxonomy_term_id; + } + + /** + * Checks if the new default term is translated in all languages + * If not, create the translations + * + * @since 1.7 + * + * @param int $old_value The old option value. + * @param int $value The new option value. + * @return void + */ + public function update_option_default_term( $old_value, $value ) { + $default_cat_lang = $this->model->term->get_language( $value ); + + // Assign a default language to default term + if ( ! $default_cat_lang ) { + $default_cat_lang = $this->model->get_default_language(); + $this->model->term->set_language( (int) $value, $default_cat_lang ); + } + + if ( empty( $default_cat_lang ) ) { + return; + } + + $taxonomy = substr( current_filter(), 22 ); + + foreach ( $this->model->get_languages_list() as $language ) { + if ( $language->slug != $default_cat_lang->slug && ! $this->model->term->get_translation( $value, $language ) ) { + $this->create_default_term( $language, $taxonomy ); + } + } + } + + /** + * Create a default term for a language + * + * @since 1.2 + * + * @param object|string|int $lang language + * @param string $taxonomy The current taxonomy + * @return void + */ + public function create_default_term( $lang, $taxonomy ) { + $lang = $this->model->get_language( $lang ); + + // create a new term + // FIXME this is translated in admin language when we would like it in $lang + $cat_name = __( 'Uncategorized', 'polylang' ); + $cat_slug = sanitize_title( $cat_name . '-' . $lang->slug ); + $cat = wp_insert_term( $cat_name, $taxonomy, array( 'slug' => $cat_slug ) ); + + // check that the term was not previously created ( in case the language was deleted and recreated ) + $cat = isset( $cat->error_data['term_exists'] ) ? $cat->error_data['term_exists'] : $cat['term_id']; + + // set language + $this->model->term->set_language( (int) $cat, $lang ); + + // this is a translation of the default term + $default = (int) get_option( 'default_' . $taxonomy ); + $translations = $this->model->term->get_translations( $default ); + + $this->model->term->save_translations( (int) $cat, $translations ); + } + + /** + * Manages the default term when new languages are created. + * + * @since 3.1 + * + * @param array $args Argument used to create the language. @see PLL_Admin_Model::add_language(). + * @return void + */ + public function handle_default_term_on_create_language( $args ) { + foreach ( $this->taxonomies as $taxonomy ) { + if ( 'category' === $taxonomy ) { + $default = (int) get_option( 'default_' . $taxonomy ); + + // Assign default language to default term + if ( ! $this->model->term->get_language( $default ) ) { + $this->model->term->set_language( $default, $args['slug'] ); + } elseif ( empty( $args['no_default_cat'] ) && ! $this->model->term->get( $default, $args['slug'] ) ) { + $this->create_default_term( $args['slug'], $taxonomy ); + } + } + } + } + + /** + * Identify the default term in the terms list table to disable the language dropdown in js. + * + * @since 3.1 + * + * @param string $out The output. + * @param string $column The custom column's name. + * @param int $term_id The term id. + * @return string The HTML string. + */ + public function term_column( $out, $column, $term_id ) { + if ( $column === $this->get_first_language_column() && $this->is_default_term( $term_id ) ) { + $out .= sprintf( '', intval( $term_id ) ); + } + + return $out; + } + + /** + * Returns the first language column in the posts, pages and media library tables + * + * @since 0.9 + * + * @return string first language column name + */ + protected function get_first_language_column() { + $columns = array(); + + foreach ( $this->model->get_languages_list() as $language ) { + $columns[] = 'language_' . $language->slug; + } + + return empty( $columns ) ? '' : reset( $columns ); + } + + /** + * Prevents deleting all the translations of the default term + * + * @since 2.1 + * + * @param array $caps The user's actual capabilities. + * @param string $cap Capability name. + * @param int $user_id The user ID. + * @param array $args Adds the context to the cap. The term id. + * @return array + */ + public function fix_delete_default_term( $caps, $cap, $user_id, $args ) { + if ( 'delete_term' === $cap && $this->is_default_term( reset( $args ) ) ) { + $caps[] = 'do_not_allow'; + } + + return $caps; + } + + /** + * Check if the term is the default term. + * + * @since 3.1 + * + * @param int $term_id The term id. + * @return bool True if the term is the default term, false otherwise. + */ + public function is_default_term( $term_id ) { + $term = get_term( $term_id ); + if ( $term instanceof WP_Term ) { + $default_term_id = get_option( 'default_' . $term->taxonomy ); + return $default_term_id && in_array( $default_term_id, $this->model->term->get_translations( $term_id ) ); + } + return false; + } + + /** + * Updates the default term language. + * + * @since 3.1 + * + * @param string $slug Language slug. + * @return void + */ + public function update_default_term_language( $slug ) { + foreach ( $this->taxonomies as $taxonomy ) { + if ( 'category' === $taxonomy ) { + $default_cats = $this->model->term->get_translations( get_option( 'default_' . $taxonomy ) ); + if ( isset( $default_cats[ $slug ] ) ) { + update_option( 'default_' . $taxonomy, $default_cats[ $slug ] ); + } + } + } + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-columns.php b/wp-content/plugins/polylang/admin/admin-filters-columns.php new file mode 100644 index 0000000000..135b7556f6 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-columns.php @@ -0,0 +1,451 @@ +links = &$polylang->links; + $this->model = &$polylang->model; + $this->filter_lang = &$polylang->filter_lang; + + // Hide the column of the filtered language. + add_filter( 'hidden_columns', array( $this, 'hidden_columns' ) ); // Since WP 4.4. + + // Add the language and translations columns in 'All Posts', 'All Pages' and 'Media library' panels. + foreach ( $this->model->get_translated_post_types() as $type ) { + // Use the latest filter late as some plugins purely overwrite what's done by others :( + // Specific case for media. + add_filter( 'manage_' . ( 'attachment' == $type ? 'upload' : 'edit-' . $type ) . '_columns', array( $this, 'add_post_column' ), 100 ); + add_action( 'manage_' . ( 'attachment' == $type ? 'media' : $type . '_posts' ) . '_custom_column', array( $this, 'post_column' ), 10, 2 ); + } + + // Quick edit and bulk edit. + add_filter( 'quick_edit_custom_box', array( $this, 'quick_edit_custom_box' ) ); + add_filter( 'bulk_edit_custom_box', array( $this, 'quick_edit_custom_box' ) ); + + // Adds the language column in the 'Categories' and 'Post Tags' tables. + foreach ( $this->model->get_translated_taxonomies() as $tax ) { + add_filter( 'manage_edit-' . $tax . '_columns', array( $this, 'add_term_column' ) ); + add_filter( 'manage_' . $tax . '_custom_column', array( $this, 'term_column' ), 10, 3 ); + } + + // Ajax responses to update list table rows. + add_action( 'wp_ajax_pll_update_post_rows', array( $this, 'ajax_update_post_rows' ) ); + add_action( 'wp_ajax_pll_update_term_rows', array( $this, 'ajax_update_term_rows' ) ); + } + + /** + * Adds languages and translations columns in posts, pages, media, categories and tags tables. + * + * @since 0.8.2 + * + * @param string[] $columns List of table columns. + * @param string $before The column before which we want to add our languages. + * @return string[] Modified list of columns. + */ + protected function add_column( $columns, $before ) { + if ( $n = array_search( $before, array_keys( $columns ) ) ) { + $end = array_slice( $columns, $n ); + $columns = array_slice( $columns, 0, $n ); + } + + foreach ( $this->model->get_languages_list() as $language ) { + $columns[ 'language_' . $language->slug ] = $this->get_flag_html( $language ) . '' . esc_html( $language->name ) . ''; + } + + return isset( $end ) ? array_merge( $columns, $end ) : $columns; + } + + /** + * Returns the first language column in the posts, pages and media library tables + * + * @since 0.9 + * + * @return string first language column name + */ + protected function get_first_language_column() { + $columns = array(); + + foreach ( $this->model->get_languages_list() as $language ) { + $columns[] = 'language_' . $language->slug; + } + + return empty( $columns ) ? '' : reset( $columns ); + } + + /** + * Hides the column for the filtered language. + * + * @since 2.7 + * + * @param string[] $hidden Array of hidden columns. + * @return string[] + */ + public function hidden_columns( $hidden ) { + if ( ! empty( $this->filter_lang ) ) { + $hidden[] = 'language_' . $this->filter_lang->slug; + } + return $hidden; + } + + /** + * Adds the language and translations columns ( before the comments column ) in the posts, pages and media library tables. + * + * @since 0.1 + * + * @param string[] $columns List of posts table columns. + * @return string[] Modified list of columns. + */ + public function add_post_column( $columns ) { + return $this->add_column( $columns, 'comments' ); + } + + /** + * Fills the language and translations columns in the posts, pages and media library tables + * take care that when doing ajax inline edit, the post may not be updated in database yet + * + * @since 0.1 + * + * @param string $column Column name. + * @param int $post_id Post ID. + * @return void + */ + public function post_column( $column, $post_id ) { + $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification + $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->post->get_language( $post_id ); // phpcs:ignore WordPress.Security.NonceVerification + + if ( false === strpos( $column, 'language_' ) || ! $lang ) { + return; + } + + $language = $this->model->get_language( substr( $column, 9 ) ); + + if ( empty( $language ) ) { + return; + } + + // Hidden field containing the post language for quick edit + if ( $column == $this->get_first_language_column() ) { + printf( '', intval( $post_id ), esc_html( $lang->slug ) ); + } + + // Link to edit post ( or a translation ) + if ( $id = $this->model->post->get( $post_id, $language ) ) { + // get_edit_post_link returns nothing if the user cannot edit the post + // Thanks to Solinx. See http://wordpress.org/support/topic/feature-request-incl-code-check-for-capabilities-in-admin-screens + if ( $link = get_edit_post_link( $id ) ) { + $flag = ''; + if ( $id === $post_id ) { + $flag = $this->get_flag_html( $language ); + $class = 'pll_column_flag'; + /* translators: accessibility text, %s is a native language name */ + $s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name ); + } else { + $class = esc_attr( 'pll_icon_edit translation_' . $id ); + /* translators: accessibility text, %s is a native language name */ + $s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ); + } + + $post = get_post( $id ); + + if ( ! empty( $post ) ) { + printf( + '%4$s%5$s', + esc_attr( $class ), + esc_attr( $post->post_title ), + esc_url( $link ), + esc_html( $s ), + $flag // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + } elseif ( $id === $post_id ) { + printf( + '%1$s%2$s', + /* translators: accessibility text, %s is a native language name */ + esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) ), + $this->get_flag_html( $language ) // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + } + // Link to add a new translation + else { + echo $this->links->new_post_translation_link( $post_id, $language ); // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Quick edit & bulk edit + * + * @since 0.9 + * + * @param string $column column name + * @return string unmodified $column + */ + public function quick_edit_custom_box( $column ) { + if ( $column == $this->get_first_language_column() ) { + + $elements = $this->model->get_languages_list(); + if ( current_filter() == 'bulk_edit_custom_box' ) { + array_unshift( $elements, (object) array( 'slug' => -1, 'name' => __( '— No Change —', 'polylang' ) ) ); + } + + $dropdown = new PLL_Walker_Dropdown(); + // The hidden field 'old_lang' allows to pass the old language to ajax request + printf( + '
+
+ +
+
', + esc_html__( 'Language', 'polylang' ), + $dropdown->walk( $elements, -1, array( 'name' => 'inline_lang_choice', 'id' => '' ) ) // phpcs:ignore WordPress.Security.EscapeOutput + ); + } + return $column; + } + + /** + * Adds the language column ( before the posts column ) in the 'Categories' or 'Post Tags' table. + * + * @since 0.1 + * + * @param string[] $columns List of terms table columns. + * @return string[] modified List of columns. + */ + public function add_term_column( $columns ) { + $screen = get_current_screen(); + + // Avoid displaying languages in screen options when editing a term. + if ( $screen instanceof WP_Screen && 'term' === $screen->base ) { + return $columns; + } + + return $this->add_column( $columns, 'posts' ); + } + + /** + * Fills the language column in the taxonomy terms list table. + * + * @since 0.1 + * + * @param string $out Column output. + * @param string $column Column name. + * @param int $term_id Term ID. + * @return string + */ + public function term_column( $out, $column, $term_id ) { + $inline = wp_doing_ajax() && isset( $_REQUEST['action'], $_POST['inline_lang_choice'] ) && 'inline-save-tax' === $_REQUEST['action']; // phpcs:ignore WordPress.Security.NonceVerification + if ( false === strpos( $column, 'language_' ) || ! ( $lang = $inline ? $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) : $this->model->term->get_language( $term_id ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + return $out; + } + + if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $GLOBALS['post_type'] ) ) { + $post_type = $GLOBALS['post_type']; + } + + if ( isset( $_REQUEST['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $taxonomy = sanitize_key( $_REQUEST['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $GLOBALS['taxonomy'] ) ) { + $taxonomy = $GLOBALS['taxonomy']; + } + + if ( ! isset( $taxonomy, $post_type ) || ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) { + return $out; + } + + $term_id = (int) $term_id; + $language = $this->model->get_language( substr( $column, 9 ) ); + + if ( empty( $language ) ) { + return $out; + } + + if ( $column == $this->get_first_language_column() ) { + $out .= sprintf( '', intval( $term_id ), esc_html( $lang->slug ) ); + } + + // Link to edit term ( or a translation ) + if ( ( $id = $this->model->term->get( $term_id, $language ) ) && $term = get_term( $id, $taxonomy ) ) { + if ( $term instanceof WP_Term && $link = get_edit_term_link( $id, $taxonomy, $post_type ) ) { + $flag = ''; + if ( $id === $term_id ) { + $flag = $this->get_flag_html( $language ); + $class = 'pll_column_flag'; + /* translators: accessibility text, %s is a native language name */ + $s = sprintf( __( 'Edit this item in %s', 'polylang' ), $language->name ); + } else { + $class = esc_attr( 'pll_icon_edit translation_' . $id ); + /* translators: accessibility text, %s is a native language name */ + $s = sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ); + } + $out .= sprintf( + '%4$s%5$s', + $class, + esc_attr( $term->name ), + esc_url( $link ), + esc_html( $s ), + $flag // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } elseif ( $id === $term_id ) { + $out .= sprintf( + '%1$s%2$s', + /* translators: accessibility text, %s is a native language name */ + esc_html( sprintf( __( 'This item is in %s', 'polylang' ), $language->name ) ), + $this->get_flag_html( $language ) // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + } + + // Link to add a new translation + else { + $out .= $this->links->new_term_translation_link( $term_id, $taxonomy, $post_type, $language ); + } + + return $out; + } + + /** + * Update rows of translated posts when the language is modified in quick edit + * + * @since 1.7 + * + * @return void + */ + public function ajax_update_post_rows() { + check_ajax_referer( 'inlineeditnonce', '_pll_nonce' ); + + if ( ! isset( $_POST['post_type'], $_POST['post_id'], $_POST['screen'] ) ) { + wp_die( 0 ); + } + + $post_type = sanitize_key( $_POST['post_type'] ); + + if ( ! post_type_exists( $post_type ) || ! $this->model->is_translated_post_type( $post_type ) ) { + wp_die( 0 ); + } + + /** @var WP_Posts_List_Table $wp_list_table */ + $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) ); + + $x = new WP_Ajax_Response(); + + // Collect old translations + $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + $translations = array_map( 'intval', $translations ); + + $translations = array_merge( $translations, array( (int) $_POST['post_id'] ) ); // Add current post + + foreach ( $translations as $post_id ) { + $level = is_post_type_hierarchical( $post_type ) ? count( get_ancestors( $post_id, $post_type ) ) : 0; + if ( $post = get_post( $post_id ) ) { + ob_start(); + $wp_list_table->single_row( $post, $level ); + $data = ob_get_clean(); + $x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'post_id' => $post_id ) ) ); + } + } + + $x->send(); + } + + /** + * Update rows of translated terms when adding / deleting a translation or when the language is modified in quick edit + * + * @since 1.7 + * + * @return void + */ + public function ajax_update_term_rows() { + check_ajax_referer( 'pll_language', '_pll_nonce' ); + + if ( ! isset( $_POST['taxonomy'], $_POST['term_id'], $_POST['screen'] ) ) { + wp_die( 0 ); + } + + $taxonomy = sanitize_key( $_POST['taxonomy'] ); + + if ( ! taxonomy_exists( $taxonomy ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + wp_die( 0 ); + } + + /** @var WP_Terms_List_Table $wp_list_table */ + $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => sanitize_key( $_POST['screen'] ) ) ); + + $x = new WP_Ajax_Response(); + + // Collect old translations + $translations = empty( $_POST['translations'] ) ? array() : explode( ',', $_POST['translations'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + $translations = array_map( 'intval', $translations ); + + $translations = array_merge( $translations, $this->model->term->get_translations( (int) $_POST['term_id'] ) ); // Add current translations + $translations = array_unique( $translations ); // Remove duplicates + + foreach ( $translations as $term_id ) { + $level = is_taxonomy_hierarchical( $taxonomy ) ? count( get_ancestors( $term_id, $taxonomy ) ) : 0; + $tag = get_term( $term_id, $taxonomy ); + + if ( ! $tag instanceof WP_Term ) { + continue; + } + + ob_start(); + $wp_list_table->single_row( $tag, $level ); + $data = ob_get_clean(); + $x->add( array( 'what' => 'row', 'data' => $data, 'supplemental' => array( 'term_id' => $term_id ) ) ); + } + + $x->send(); + } + + /** + * Returns the language flag or the language slug if there is no flag. + * + * @since 2.8 + * + * @param PLL_Language $language PLL_Language object. + * @return string + */ + protected function get_flag_html( $language ) { + return $language->flag ? $language->flag : sprintf( '%s', esc_html( $language->slug ) ); + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-media.php b/wp-content/plugins/polylang/admin/admin-filters-media.php new file mode 100644 index 0000000000..477f005808 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-media.php @@ -0,0 +1,132 @@ +posts = &$polylang->posts; + + // Adds the language field and translations tables in the 'Edit Media' panel + add_filter( 'attachment_fields_to_edit', array( $this, 'attachment_fields_to_edit' ), 10, 2 ); + + // Adds actions related to languages when creating, saving or deleting media + add_filter( 'attachment_fields_to_save', array( $this, 'save_media' ), 10, 2 ); + + // Creates a media translation + if ( isset( $_GET['action'], $_GET['new_lang'], $_GET['from_media'] ) && 'translate_media' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification + add_action( 'admin_init', array( $this, 'translate_media' ) ); + } + } + + /** + * Adds the language field and translations tables in the 'Edit Media' panel. + * Needs WP 3.5+ + * + * @since 0.9 + * + * @param array $fields List of form fields. + * @param WP_Post $post The attachment being edited. + * @return array Modified list of form fields. + */ + public function attachment_fields_to_edit( $fields, $post ) { + if ( 'post.php' == $GLOBALS['pagenow'] ) { + return $fields; // Don't add anything on edit media panel for WP 3.5+ since we have the metabox + } + + $post_id = $post->ID; + $lang = $this->model->post->get_language( $post_id ); + + $dropdown = new PLL_Walker_Dropdown(); + $fields['language'] = array( + 'label' => __( 'Language', 'polylang' ), + 'input' => 'html', + 'html' => $dropdown->walk( + $this->model->get_languages_list(), + -1, + array( + 'name' => sprintf( 'attachments[%d][language]', $post_id ), + 'class' => 'media_lang_choice', + 'selected' => $lang ? $lang->slug : '', + ) + ), + ); + + return $fields; + } + + /** + * Creates a media translation + * + * @since 0.9 + * + * @return void + */ + public function translate_media() { + if ( isset( $_GET['from_media'], $_GET['new_lang'] ) ) { + // Security check + check_admin_referer( 'translate_media' ); + $post_id = (int) $_GET['from_media']; + + // Bails if the translations already exists + // See https://wordpress.org/support/topic/edit-translation-in-media-attachments?#post-7322303 + // Or if the source media does not exist + if ( $this->model->post->get_translation( $post_id, sanitize_key( $_GET['new_lang'] ) ) || ! get_post( $post_id ) ) { + wp_safe_redirect( wp_get_referer() ); + exit; + } + + $tr_id = $this->posts->create_media_translation( $post_id, sanitize_key( $_GET['new_lang'] ) ); + wp_safe_redirect( admin_url( sprintf( 'post.php?post=%d&action=edit', $tr_id ) ) ); // WP 3.5+ + exit; + } + } + + /** + * Called when a media is saved + * Saves language and translations + * + * @since 0.9 + * + * @param array $post An array of post data. + * @param array $attachment An array of attachment metadata. + * @return array Unmodified $post + */ + public function save_media( $post, $attachment ) { + // Language is filled in attachment by the function applying the filter 'attachment_fields_to_save' + // All security checks have been done by functions applying this filter + if ( empty( $attachment['language'] ) || ! current_user_can( 'edit_post', $post['ID'] ) ) { + return $post; + } + + $language = $this->model->get_language( $attachment['language'] ); + + if ( empty( $language ) ) { + return $post; + } + + $this->model->post->set_language( $post['ID'], $language ); + + return $post; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-post-base.php b/wp-content/plugins/polylang/admin/admin-filters-post-base.php new file mode 100644 index 0000000000..ae29435253 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-post-base.php @@ -0,0 +1,65 @@ +links = &$polylang->links; + $this->model = &$polylang->model; + $this->pref_lang = &$polylang->pref_lang; + } + + /** + * Save translations from the languages metabox. + * + * @since 1.5 + * + * @param int $post_id Post id of the post being saved. + * @param int[] $arr An array with language codes as key and post id as value. + * @return int[] The array of translated post ids. + */ + protected function save_translations( $post_id, $arr ) { + // Security check as 'wp_insert_post' can be called from outside WP admin. + check_admin_referer( 'pll_language', '_pll_nonce' ); + + $translations = $this->model->post->save_translations( $post_id, $arr ); + return $translations; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-post.php b/wp-content/plugins/polylang/admin/admin-filters-post.php new file mode 100644 index 0000000000..ed7e57c116 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-post.php @@ -0,0 +1,223 @@ +curlang = &$polylang->curlang; + + add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); + + // Filters posts, pages and media by language + add_action( 'parse_query', array( $this, 'parse_query' ) ); + + // Adds actions and filters related to languages when creating, saving or deleting posts and pages + add_action( 'load-post.php', array( $this, 'edit_post' ) ); + add_action( 'load-edit.php', array( $this, 'bulk_edit_posts' ) ); + add_action( 'wp_ajax_inline-save', array( $this, 'inline_edit_post' ), 0 ); // Before WordPress + + // Sets the language in Tiny MCE + add_filter( 'tiny_mce_before_init', array( $this, 'tiny_mce_before_init' ) ); + } + + /** + * Outputs a javascript list of terms ordered by language and hierarchical taxonomies + * to filter the category checklist per post language in quick edit + * Outputs a javascript list of pages ordered by language + * to filter the parent dropdown per post language in quick edit + * + * @since 1.7 + * + * @return void + */ + public function admin_enqueue_scripts() { + $screen = get_current_screen(); + + if ( empty( $screen ) ) { + return; + } + + // Hierarchical taxonomies + if ( 'edit' == $screen->base && $taxonomies = get_object_taxonomies( $screen->post_type, 'objects' ) ) { + // Get translated hierarchical taxonomies + $hierarchical_taxonomies = array(); + foreach ( $taxonomies as $taxonomy ) { + if ( $taxonomy->hierarchical && $taxonomy->show_in_quick_edit && $this->model->is_translated_taxonomy( $taxonomy->name ) ) { + $hierarchical_taxonomies[] = $taxonomy->name; + } + } + + if ( ! empty( $hierarchical_taxonomies ) ) { + $terms = get_terms( array( 'taxonomy' => $hierarchical_taxonomies, 'get' => 'all' ) ); + $term_languages = array(); + + if ( is_array( $terms ) ) { + foreach ( $terms as $term ) { + if ( $lang = $this->model->term->get_language( $term->term_id ) ) { + $term_languages[ $lang->slug ][ $term->taxonomy ][] = $term->term_id; + } + } + } + + // Send all these data to javascript + if ( ! empty( $term_languages ) ) { + wp_localize_script( 'pll_post', 'pll_term_languages', $term_languages ); + } + } + } + + // Hierarchical post types + if ( 'edit' == $screen->base && is_post_type_hierarchical( $screen->post_type ) ) { + $pages = get_pages( array( 'sort_column' => 'menu_order, post_title' ) ); // Same arguments as the parent pages dropdown to avoid an extra query. + + update_post_caches( $pages, $screen->post_type, true, false ); + + $page_languages = array(); + + foreach ( $pages as $page ) { + if ( $lang = $this->model->post->get_language( $page->ID ) ) { + $page_languages[ $lang->slug ][] = $page->ID; + } + } + + // Send all these data to javascript + if ( ! empty( $page_languages ) ) { + wp_localize_script( 'pll_post', 'pll_page_languages', $page_languages ); + } + } + } + + /** + * Filters posts, pages and media by language. + * + * @since 0.1 + * + * @param WP_Query $query WP_Query object. + * @return void + */ + public function parse_query( $query ) { + $pll_query = new PLL_Query( $query, $this->model ); + $pll_query->filter_query( $this->curlang ); + } + + /** + * Save language and translation when editing a post (post.php) + * + * @since 2.3 + * + * @return void + */ + public function edit_post() { + if ( isset( $_POST['post_lang_choice'], $_POST['post_ID'] ) && $post_id = (int) $_POST['post_ID'] ) { // phpcs:ignore WordPress.Security.NonceVerification + check_admin_referer( 'pll_language', '_pll_nonce' ); + + $post = get_post( $post_id ); + + if ( empty( $post ) ) { + return; + } + + $post_type_object = get_post_type_object( $post->post_type ); + + if ( empty( $post_type_object ) ) { + return; + } + + if ( ! current_user_can( $post_type_object->cap->edit_post, $post_id ) ) { + return; + } + + $language = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ); + + if ( empty( $language ) ) { + return; + } + + $this->model->post->set_language( $post_id, $language ); + + if ( ! isset( $_POST['post_tr_lang'] ) ) { + return; + } + + $this->save_translations( $post_id, array_map( 'absint', $_POST['post_tr_lang'] ) ); + } + } + + /** + * Save language when bulk editing a post + * + * @since 2.3 + * + * @return void + */ + public function bulk_edit_posts() { + if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'], $_REQUEST['post'] ) && -1 !== $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification + check_admin_referer( 'bulk-posts' ); + + if ( $lang = $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) ) { + $post_ids = array_map( 'intval', (array) $_REQUEST['post'] ); + foreach ( $post_ids as $post_id ) { + if ( current_user_can( 'edit_post', $post_id ) ) { + $this->model->post->set_language( $post_id, $lang ); + } + } + } + } + } + + /** + * Save language when inline editing a post + * + * @since 2.3 + * + * @return void + */ + public function inline_edit_post() { + check_admin_referer( 'inlineeditnonce', '_inline_edit' ); + + if ( isset( $_POST['post_ID'], $_POST['inline_lang_choice'] ) ) { + $post_id = (int) $_POST['post_ID']; + $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ); + if ( $post_id && $lang && current_user_can( 'edit_post', $post_id ) ) { + $this->model->post->set_language( $post_id, $lang ); + } + } + } + + /** + * Sets the language attribute and text direction for Tiny MCE + * + * @since 2.2 + * + * @param array $mce_init TinyMCE config + * @return array + */ + public function tiny_mce_before_init( $mce_init ) { + if ( ! empty( $this->curlang ) ) { + $mce_init['wp_lang_attr'] = $this->curlang->get_locale( 'display' ); + $mce_init['directionality'] = $this->curlang->is_rtl ? 'rtl' : 'ltr'; + } + return $mce_init; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-term.php b/wp-content/plugins/polylang/admin/admin-filters-term.php new file mode 100644 index 0000000000..e958c6a8b6 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-term.php @@ -0,0 +1,716 @@ +links = &$polylang->links; + $this->model = &$polylang->model; + $this->pref_lang = &$polylang->pref_lang; + $this->default_term = &$polylang->default_term; + + foreach ( $this->model->get_translated_taxonomies() as $tax ) { + // Adds the language field in the 'Categories' and 'Post Tags' panels + add_action( $tax . '_add_form_fields', array( $this, 'add_term_form' ) ); + + // Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels + add_action( $tax . '_edit_form_fields', array( $this, 'edit_term_form' ) ); + } + + // Adds actions related to languages when creating or saving categories and post tags + add_filter( 'wp_dropdown_cats', array( $this, 'wp_dropdown_cats' ) ); + add_action( 'create_term', array( $this, 'save_term' ), 900, 3 ); + add_action( 'edit_term', array( $this, 'save_term' ), 900, 3 ); // Late as it may conflict with other plugins, see http://wordpress.org/support/topic/polylang-and-wordpress-seo-by-yoast + add_action( 'pre_post_update', array( $this, 'pre_post_update' ) ); + add_filter( 'pll_inserted_term_language', array( $this, 'get_inserted_term_language' ) ); + add_filter( 'pll_inserted_term_parent', array( $this, 'get_inserted_term_parent' ), 10, 2 ); + + // Ajax response for edit term form + add_action( 'wp_ajax_term_lang_choice', array( $this, 'term_lang_choice' ) ); + add_action( 'wp_ajax_pll_terms_not_translated', array( $this, 'ajax_terms_not_translated' ) ); + + // Updates the translations term ids when splitting a shared term + add_action( 'split_shared_term', array( $this, 'split_shared_term' ), 10, 4 ); // WP 4.2 + } + + /** + * Adds the language field in the 'Categories' and 'Post Tags' panels + * + * @since 0.1 + * + * @return void + */ + public function add_term_form() { + if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $GLOBALS['post_type'] ) ) { + $post_type = $GLOBALS['post_type']; + } + + if ( ! isset( $taxonomy, $post_type ) || ! taxonomy_exists( $taxonomy ) || ! post_type_exists( $post_type ) ) { + return; + } + + $from_term_id = isset( $_GET['from_tag'] ) ? (int) $_GET['from_tag'] : 0; // phpcs:ignore WordPress.Security.NonceVerification + + $lang = isset( $_GET['new_lang'] ) ? $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) : $this->pref_lang; // phpcs:ignore WordPress.Security.NonceVerification + + $dropdown = new PLL_Walker_Dropdown(); + + $dropdown_html = $dropdown->walk( + $this->model->get_languages_list(), + -1, + array( + 'name' => 'term_lang_choice', + 'value' => 'term_id', + 'selected' => $lang ? $lang->term_id : '', + 'flag' => true, + ) + ); + + wp_nonce_field( 'pll_language', '_pll_nonce' ); + + printf( + '
+ +
%s
+

%s

+
', + esc_html__( 'Language', 'polylang' ), + $dropdown_html, // phpcs:ignore + esc_html__( 'Sets the language', 'polylang' ) + ); + + if ( ! empty( $from_term_id ) ) { + printf( '', (int) $from_term_id ); + } + + // Adds translation fields + echo '
'; + if ( $lang ) { + include __DIR__ . '/view-translations-term.php'; + } + echo '
' . "\n"; + } + + /** + * Adds the language field and translations tables in the 'Edit Category' and 'Edit Tag' panels. + * + * @since 0.1 + * + * @param WP_Term $tag The term being edited. + * @return void + */ + public function edit_term_form( $tag ) { + if ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $post_type = sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $GLOBALS['post_type'] ) ) { + $post_type = $GLOBALS['post_type']; + } + + if ( ! isset( $post_type ) || ! post_type_exists( $post_type ) ) { + return; + } + + $term_id = $tag->term_id; + $taxonomy = $tag->taxonomy; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + + $lang = $this->model->term->get_language( $term_id ); + $lang = empty( $lang ) ? $this->pref_lang : $lang; + + // Disable the language dropdown and the translations input fields for default terms to prevent removal + $disabled = $this->default_term->is_default_term( $term_id ); + + $dropdown = new PLL_Walker_Dropdown(); + + $dropdown_html = $dropdown->walk( + $this->model->get_languages_list(), + -1, + array( + 'name' => 'term_lang_choice', + 'value' => 'term_id', + 'selected' => $lang->term_id, + 'disabled' => $disabled, + 'flag' => true, + ) + ); + + wp_nonce_field( 'pll_language', '_pll_nonce' ); + + printf( + ' + + + + + %s
+

%s

+ + ', + esc_html__( 'Language', 'polylang' ), + $dropdown_html, // phpcs:ignore + esc_html__( 'Sets the language', 'polylang' ) + ); + + echo ''; + include __DIR__ . '/view-translations-term.php'; + echo '' . "\n"; + } + + /** + * Translates term parent if exists when using "Add new" ( translation ) + * + * @since 0.7 + * + * @param string $output html markup for dropdown list of categories + * @return string modified html + */ + public function wp_dropdown_cats( $output ) { + if ( isset( $_GET['taxonomy'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $taxonomy = sanitize_key( $_GET['taxonomy'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + if ( isset( $taxonomy, $_GET['from_tag'], $_GET['new_lang'] ) && taxonomy_exists( $taxonomy ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $term = get_term( (int) $_GET['from_tag'], $taxonomy ); // phpcs:ignore WordPress.Security.NonceVerification + + if ( $term instanceof WP_Term && $id = $term->parent ) { + $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + if ( $parent = $this->model->term->get_translation( $id, $lang ) ) { + return str_replace( '"' . $parent . '"', '"' . $parent . '" selected="selected"', $output ); + } + } + } + return $output; + } + + /** + * Stores the current post_id when bulk editing posts for use in save_language and get_inserted_term_language. + * + * @since 1.7 + * + * @param int $post_id Post ID. + * @return void + */ + public function pre_post_update( $post_id ) { + if ( isset( $_GET['bulk_edit'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->post_id = $post_id; + } + } + + /** + * Saves the language of a term. + * + * @since 1.5 + * + * @param int $term_id Term ID. + * @param string $taxonomy Taxonomy name. + * @return void + */ + protected function save_language( $term_id, $taxonomy ) { + global $wpdb; + // Security checks are necessary to accept language modifications + // as 'wp_update_term' can be called from outside WP admin + + // Edit tags + if ( isset( $_POST['term_lang_choice'] ) ) { + if ( isset( $_POST['action'] ) && sanitize_key( $_POST['action'] ) === 'add-' . $taxonomy ) { // phpcs:ignore WordPress.Security.NonceVerification + check_ajax_referer( 'add-' . $taxonomy, '_ajax_nonce-add-' . $taxonomy ); // Category metabox + } else { + check_admin_referer( 'pll_language', '_pll_nonce' ); // Edit tags or tags metabox + } + + $language = $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ); + + if ( ! empty( $language ) ) { + $this->model->term->set_language( $term_id, $language ); + } + } + + // *Post* bulk edit, in case a new term is created + elseif ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { + check_admin_referer( 'bulk-posts' ); + + // Bulk edit does not modify the language + // So we possibly create a tag in several languages + if ( -1 === (int) $_GET['inline_lang_choice'] ) { + // The language of the current term is set a according to the language of the current post. + $language = $this->model->post->get_language( $this->post_id ); + + if ( empty( $language ) ) { + return; + } + + $this->model->term->set_language( $term_id, $language ); + $term = get_term( $term_id, $taxonomy ); + $terms = array(); + + // Get all terms with the same name + // FIXME backward compatibility WP < 4.2 + // No WP function to get all terms with the exact same name so let's use a custom query + // $terms = get_terms( $taxonomy, array( 'name' => $term->name, 'hide_empty' => false, 'fields' => 'ids' ) ); should be OK in 4.2 + // I may need to rework the loop below + if ( $term instanceof WP_Term ) { + $terms = $wpdb->get_results( + $wpdb->prepare( + "SELECT t.term_id FROM $wpdb->terms AS t + INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id + WHERE tt.taxonomy = %s AND t.name = %s", + $taxonomy, + $term->name + ) + ); + } + + // If we have several terms with the same name, they are translations of each other + if ( count( $terms ) > 1 ) { + $translations = array(); + + foreach ( $terms as $term ) { + $translations[ $this->model->term->get_language( $term->term_id )->slug ] = $term->term_id; + } + + $this->model->term->save_translations( $term_id, $translations ); + } + } + + elseif ( current_user_can( 'edit_term', $term_id ) ) { + $this->model->term->set_language( $term_id, $this->model->get_language( sanitize_key( $_GET['inline_lang_choice'] ) ) ); + } + } + + // Quick edit + elseif ( isset( $_POST['inline_lang_choice'] ) ) { + check_ajax_referer( + isset( $_POST['action'] ) && 'inline-save' == $_POST['action'] ? 'inlineeditnonce' : 'taxinlineeditnonce', // Post quick edit or tag quick edit ? + '_inline_edit' + ); + + $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ); + $this->model->term->set_language( $term_id, $lang ); + } + + // Edit post + elseif ( isset( $_POST['post_lang_choice'] ) ) { // FIXME should be useless now + check_admin_referer( 'pll_language', '_pll_nonce' ); + + $language = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ); + + if ( ! empty( $language ) ) { + $this->model->term->set_language( $term_id, $language ); + } + } + } + + /** + * Save translations from our form. + * + * @since 1.5 + * + * @param int $term_id The term id of the term being saved. + * @return int[] The array of translated term ids. + */ + protected function save_translations( $term_id ) { + // Security check as 'wp_update_term' can be called from outside WP admin. + check_admin_referer( 'pll_language', '_pll_nonce' ); + + $translations = array(); + + // Save translations after checking the translated term is in the right language ( as well as cast id to int ). + if ( isset( $_POST['term_tr_lang'] ) ) { + foreach ( array_map( 'absint', $_POST['term_tr_lang'] ) as $lang => $tr_id ) { + $tr_lang = $this->model->term->get_language( $tr_id ); + $translations[ $lang ] = $tr_lang && $tr_lang->slug == $lang ? $tr_id : 0; + } + } + + $this->model->term->save_translations( $term_id, $translations ); + + return $translations; + } + + /** + * Called when a category or post tag is created or edited + * Saves language and translations + * + * @since 0.1 + * + * @param int $term_id Term ID. + * @param int $tt_id Term taxonomy ID. + * @param string $taxonomy Taxonomy name. + * @return void + */ + public function save_term( $term_id, $tt_id, $taxonomy ) { + // Does nothing except on taxonomies which are filterable + if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return; + } + + $tax = get_taxonomy( $taxonomy ); + + if ( empty( $tax ) ) { + return; + } + + // Capability check + // As 'wp_update_term' can be called from outside WP admin + // 2nd test for creating tags when creating / editing a post + if ( current_user_can( $tax->cap->edit_terms ) || ( isset( $_POST['tax_input'][ $taxonomy ] ) && current_user_can( $tax->cap->assign_terms ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->save_language( $term_id, $taxonomy ); + + if ( isset( $_POST['term_tr_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $this->save_translations( $term_id ); + } + } + } + + /** + * Ajax response for edit term form + * + * @since 0.2 + * + * @return void + */ + public function term_lang_choice() { + check_ajax_referer( 'pll_language', '_pll_nonce' ); + + if ( ! isset( $_POST['taxonomy'], $_POST['post_type'], $_POST['lang'] ) ) { + wp_die( 0 ); + } + + $lang = $this->model->get_language( sanitize_key( $_POST['lang'] ) ); + $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : null; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $taxonomy = sanitize_key( $_POST['taxonomy'] ); + $post_type = sanitize_key( $_POST['post_type'] ); + + if ( empty( $lang ) || ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) { + wp_die( 0 ); + } + + ob_start(); + include __DIR__ . '/view-translations-term.php'; + $x = new WP_Ajax_Response( array( 'what' => 'translations', 'data' => ob_get_contents() ) ); + ob_end_clean(); + + // Parent dropdown list ( only for hierarchical taxonomies ) + // $args copied from edit_tags.php except echo + if ( is_taxonomy_hierarchical( $taxonomy ) ) { + $args = array( + 'hide_empty' => 0, + 'hide_if_empty' => false, + 'taxonomy' => $taxonomy, + 'name' => 'parent', + 'orderby' => 'name', + 'hierarchical' => true, + 'show_option_none' => __( 'None', 'polylang' ), + 'echo' => 0, + ); + $x->Add( array( 'what' => 'parent', 'data' => wp_dropdown_categories( $args ) ) ); + } + + // Tag cloud + // Tests copied from edit_tags.php + else { + $tax = get_taxonomy( $taxonomy ); + if ( ! empty( $tax ) && ! is_null( $tax->labels->popular_items ) ) { + $args = array( 'taxonomy' => $taxonomy, 'echo' => false ); + if ( current_user_can( $tax->cap->edit_terms ) ) { + $args = array_merge( $args, array( 'link' => 'edit' ) ); + } + + $tag_cloud = wp_tag_cloud( $args ); + + if ( ! empty( $tag_cloud ) ) { + /** @phpstan-var non-falsy-string $tag_cloud */ + $html = sprintf( '

%1$s

%2$s
', esc_html( $tax->labels->popular_items ), $tag_cloud ); + $x->Add( array( 'what' => 'tag_cloud', 'data' => $html ) ); + } + } + } + + // Flag + $x->Add( array( 'what' => 'flag', 'data' => empty( $lang->flag ) ? esc_html( $lang->slug ) : $lang->flag ) ); + + $x->send(); + } + + /** + * Ajax response for input in translation autocomplete input box. + * + * @since 1.5 + * + * @return void + */ + public function ajax_terms_not_translated() { + check_ajax_referer( 'pll_language', '_pll_nonce' ); + + if ( ! isset( $_GET['term'], $_GET['post_type'], $_GET['taxonomy'], $_GET['term_language'], $_GET['translation_language'] ) ) { + wp_die( 0 ); + } + + /** @var string */ + $s = wp_unslash( $_GET['term'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + $post_type = sanitize_key( $_GET['post_type'] ); + $taxonomy = sanitize_key( $_GET['taxonomy'] ); + + if ( ! post_type_exists( $post_type ) || ! taxonomy_exists( $taxonomy ) ) { + wp_die( 0 ); + } + + $term_language = $this->model->get_language( sanitize_key( $_GET['term_language'] ) ); + $translation_language = $this->model->get_language( sanitize_key( $_GET['translation_language'] ) ); + + $terms = array(); + $return = array(); + + // Add current translation in list. + // Not in add term as term_id is not set. + if ( isset( $_GET['term_id'] ) && 'undefined' !== $_GET['term_id'] && $term_id = $this->model->term->get_translation( (int) $_GET['term_id'], $translation_language ) ) { + $terms = array( get_term( $term_id, $taxonomy ) ); + } + + // It is more efficient to use one common query for all languages as soon as there are more than 2. + $all_terms = get_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => false, 'lang' => '', 'name__like' => $s ) ); + if ( is_array( $all_terms ) ) { + foreach ( $all_terms as $term ) { + $lang = $this->model->term->get_language( $term->term_id ); + + if ( $lang && $lang->slug == $translation_language->slug && ! $this->model->term->get_translation( $term->term_id, $term_language ) ) { + $terms[] = $term; + } + } + } + + // Format the ajax response. + foreach ( $terms as $term ) { + if ( ! $term instanceof WP_Term ) { + continue; + } + + $parents_list = get_term_parents_list( + $term->term_id, + $term->taxonomy, + array( + 'separator' => ' > ', + 'link' => false, + ) + ); + + if ( ! is_string( $parents_list ) ) { + continue; + } + + $return[] = array( + 'id' => $term->term_id, + 'value' => rtrim( $parents_list, ' >' ), // Trim the separator added at the end by WP. + 'link' => $this->links->edit_term_translation_link( $term->term_id, $term->taxonomy, $post_type ), + ); + } + + wp_die( wp_json_encode( $return ) ); + } + + /** + * Updates the translations term ids when splitting a shared term + * Splits translations if these are shared terms too + * + * @since 1.7 + * + * @param int $term_id ID of the formerly shared term. + * @param int $new_term_id ID of the new term created for the $term_taxonomy_id. + * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split. + * @param string $taxonomy Taxonomy name. + * @return void + */ + public function split_shared_term( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) { + if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return; + } + + // Avoid recursion + static $avoid_recursion = false; + if ( $avoid_recursion ) { + return; + } + + $lang = $this->model->term->get_language( $term_id ); + if ( empty( $lang ) ) { + return; + } + + $avoid_recursion = true; + $translations = array(); + + foreach ( $this->model->term->get_translations( $term_id ) as $key => $tr_id ) { + if ( $lang->slug == $key ) { + $translations[ $key ] = $new_term_id; + } + else { + $tr_term = get_term( $tr_id, $taxonomy ); + + if ( ! $tr_term instanceof WP_Term ) { + continue; + } + + $split_term_id = _split_shared_term( $tr_id, $tr_term->term_taxonomy_id ); + + if ( is_int( $split_term_id ) ) { + $translations[ $key ] = $split_term_id; + } else { + $translations[ $key ] = $tr_id; + } + + // Hack translation ids sent by the form to avoid overwrite in PLL_Admin_Filters_Term::save_translations + if ( isset( $_POST['term_tr_lang'][ $key ] ) && $_POST['term_tr_lang'][ $key ] == $tr_id ) { // phpcs:ignore WordPress.Security.NonceVerification + $_POST['term_tr_lang'][ $key ] = $translations[ $key ]; + } + } + + $this->model->term->set_language( $translations[ $key ], $key ); + } + + $this->model->term->save_translations( $new_term_id, $translations ); + $avoid_recursion = false; + } + + /** + * Returns the language for subsequently inserted term in admin. + * + * @since 3.3 + * + * @param PLL_Language|null $lang Term language object if found, null otherwise. + * @return PLL_Language|null Language object, null if none found. + */ + public function get_inserted_term_language( $lang ) { + if ( $lang instanceof PLL_Language ) { + return $lang; + } + + if ( ! empty( $_POST['term_lang_choice'] ) && is_string( $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang_slug = sanitize_key( $_POST['term_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->get_language( $lang_slug ); + return $lang instanceof PLL_Language ? $lang : null; + } + + if ( ! empty( $_POST['inline_lang_choice'] ) && is_string( $_POST['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang_slug = sanitize_key( $_POST['inline_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->get_language( $lang_slug ); + return $lang instanceof PLL_Language ? $lang : null; + } + + // *Post* bulk edit, in case a new term is created + if ( isset( $_GET['bulk_edit'], $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // Bulk edit does not modify the language + if ( -1 === (int) $_GET['inline_lang_choice'] ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->post->get_language( $this->post_id ); + return $lang instanceof PLL_Language ? $lang : null; + } elseif ( is_string( $_GET['inline_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang_slug = sanitize_key( $_GET['inline_lang_choice'] ); // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->get_language( $lang_slug ); + return $lang instanceof PLL_Language ? $lang : null; + } + } + + // Special cases for default categories as the select is disabled. + $default_term = get_option( 'default_category' ); + + if ( ! is_numeric( $default_term ) ) { + return null; + } + + if ( ! empty( $_POST['tag_ID'] ) && in_array( (int) $default_term, $this->model->term->get_translations( (int) $_POST['tag_ID'] ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->term->get_language( (int) $_POST['tag_ID'] ); // phpcs:ignore WordPress.Security.NonceVerification + return $lang instanceof PLL_Language ? $lang : null; + } + + if ( ! empty( $_POST['tax_ID'] ) && in_array( (int) $default_term, $this->model->term->get_translations( (int) $_POST['tax_ID'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $lang = $this->model->term->get_language( (int) $_POST['tax_ID'] ); // phpcs:ignore WordPress.Security.NonceVerification + return $lang instanceof PLL_Language ? $lang : null; + } + + return null; + } + + /** + * Filters the subsequently inserted term parent in admin. + * + * @since 3.3 + * + * @param int $parent Parent term ID, 0 if none found. + * @param string $taxonomy Term taxonomy. + * @return int Parent term ID if found, 0 otherwise. + */ + public function get_inserted_term_parent( $parent, $taxonomy ) { + if ( $parent ) { + return $parent; + } + + if ( isset( $_POST['parent'], $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $parent = intval( $_POST['parent'] ); // phpcs:ignore WordPress.Security.NonceVerification + } elseif ( isset( $_POST[ "new{$taxonomy}_parent" ], $_POST['term_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $parent = intval( $_POST[ "new{$taxonomy}_parent" ] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + return $parent; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters-widgets-options.php b/wp-content/plugins/polylang/admin/admin-filters-widgets-options.php new file mode 100644 index 0000000000..e62251ddc6 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters-widgets-options.php @@ -0,0 +1,34 @@ +base ) || ( isset( $_REQUEST['action'] ) && 'save-widget' === $_REQUEST['action'] ) || isset( $GLOBALS['wp_customize'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + parent::in_widget_form( $widget, $return, $instance ); + } + } +} diff --git a/wp-content/plugins/polylang/admin/admin-filters.php b/wp-content/plugins/polylang/admin/admin-filters.php new file mode 100644 index 0000000000..eb79643714 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-filters.php @@ -0,0 +1,127 @@ +model->get_languages_list() as $lang ) { + $meta = $lang->is_default ? 'description' : 'description_' . $lang->slug; + $description = empty( $_POST[ 'description_' . $lang->slug ] ) ? '' : trim( $_POST[ 'description_' . $lang->slug ] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput + + /** This filter is documented in wp-includes/user.php */ + $description = apply_filters( 'pre_user_description', $description ); // Applies WP default filter wp_filter_kses + update_user_meta( $user_id, $meta, $description ); + } + } + + /** + * Outputs hidden information to modify the biography form with js. + * + * @since 0.4 + * + * @param WP_User $profileuser The current WP_User object. + * @return void + */ + public function personal_options( $profileuser ) { + foreach ( $this->model->get_languages_list() as $lang ) { + $meta = $lang->is_default ? 'description' : 'description_' . $lang->slug; + $description = get_user_meta( $profileuser->ID, $meta, true ); + + printf( + '', + esc_attr( $lang->slug ), + esc_attr( $lang->name ), + sanitize_user_field( 'description', $description, $profileuser->ID, 'edit' ) + ); + } + } + + /** + * Allows to update translations files for plugins and themes. + * + * @since 1.6 + * + * @param string[] $locales List of locales to update for plugins and themes. + * @return string[] + */ + public function update_check_locales( $locales ) { + return array_merge( $locales, $this->model->get_languages_list( array( 'fields' => 'locale' ) ) ); + } + + /** + * Adds custom classes to the body + * + * @since 2.2 Adds a text direction dependent class to the body. + * @since 3.4 Adds a language dependent class to the body. + * + * @param string $classes Space-separated list of CSS classes. + * @return string + */ + public function admin_body_class( $classes ) { + if ( ! empty( $this->curlang ) ) { + $classes .= ' pll-dir-' . ( $this->curlang->is_rtl ? 'rtl' : 'ltr' ); + $classes .= ' pll-lang-' . $this->curlang->slug; + } + return $classes; + } + + /** + * Adds post state for translations of the privacy policy page. + * + * @since 2.7 + * + * @param string[] $post_states An array of post display states. + * @param WP_Post $post The current post object. + * @return string[] + */ + public function display_post_states( $post_states, $post ) { + $page_for_privacy_policy = get_option( 'wp_page_for_privacy_policy' ); + + if ( $page_for_privacy_policy && in_array( $post->ID, $this->model->post->get_translations( $page_for_privacy_policy ) ) ) { + $post_states['page_for_privacy_policy'] = __( 'Privacy Policy Page', 'polylang' ); + } + + return $post_states; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-links.php b/wp-content/plugins/polylang/admin/admin-links.php new file mode 100644 index 0000000000..84c60ebe0f --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-links.php @@ -0,0 +1,226 @@ +name ); + + $str = sprintf( + '%3$s', + esc_url( $link ), + esc_attr( $hint ), + esc_html( $hint ) + ); + } + + return $str; + } + + /** + * Returns the html markup for a translation link. + * + * @since 2.6 + * + * @param string $link The translation link. + * @param PLL_Language $language The language of the translation. + * @return string + */ + public function edit_translation_link( $link, $language ) { + return $link ? sprintf( + '%2$s', + esc_url( $link ), + /* translators: accessibility text, %s is a native language name */ + esc_html( sprintf( __( 'Edit the translation in %s', 'polylang' ), $language->name ) ) + ) : ''; + } + + /** + * Get the link to create a new post translation. + * + * @since 1.5 + * + * @param int $post_id The source post id. + * @param PLL_Language $language The language of the new translation. + * @param string $context Optional. Defaults to 'display' which encodes '&' to '&'. + * Otherwise, preserves '&'. + * @return string + */ + public function get_new_post_translation_link( $post_id, $language, $context = 'display' ) { + $post_type = get_post_type( $post_id ); + $post_type_object = get_post_type_object( get_post_type( $post_id ) ); + if ( empty( $post_type_object ) || ! current_user_can( $post_type_object->cap->create_posts ) ) { + return ''; + } + + // Special case for the privacy policy page which is associated to a specific capability + if ( 'page' === $post_type_object->name && ! current_user_can( 'manage_privacy_options' ) ) { + $privacy_page = get_option( 'wp_page_for_privacy_policy' ); + if ( $privacy_page && in_array( $post_id, $this->model->post->get_translations( $privacy_page ) ) ) { + return ''; + } + } + + if ( 'attachment' === $post_type ) { + $args = array( + 'action' => 'translate_media', + 'from_media' => $post_id, + 'new_lang' => $language->slug, + ); + + $link = add_query_arg( $args, admin_url( 'admin.php' ) ); + + // Add nonce for media as we will directly publish a new attachment from a click on this link + if ( 'display' === $context ) { + $link = wp_nonce_url( $link, 'translate_media' ); + } else { + $link = add_query_arg( '_wpnonce', wp_create_nonce( 'translate_media' ), $link ); + } + } else { + $args = array( + 'post_type' => $post_type, + 'from_post' => $post_id, + 'new_lang' => $language->slug, + ); + + $link = add_query_arg( $args, admin_url( 'post-new.php' ) ); + + if ( 'display' === $context ) { + $link = wp_nonce_url( $link, 'new-post-translation' ); + } else { + $link = add_query_arg( '_wpnonce', wp_create_nonce( 'new-post-translation' ), $link ); + } + } + + /** + * Filters the new post translation link. + * + * @since 1.8 + * + * @param string $link The new post translation link. + * @param PLL_Language $language The language of the new translation. + * @param int $post_id The source post id. + */ + return apply_filters( 'pll_get_new_post_translation_link', $link, $language, $post_id ); + } + + /** + * Returns the html markup for a new post translation link. + * + * @since 1.8 + * + * @param int $post_id The source post id. + * @param PLL_Language $language The language of the new translation. + * @return string + */ + public function new_post_translation_link( $post_id, $language ) { + $link = $this->get_new_post_translation_link( $post_id, $language ); + return $this->new_translation_link( $link, $language ); + } + + /** + * Returns the html markup for a post translation link. + * + * @since 1.4 + * + * @param int $post_id The translation post id. + * @return string + */ + public function edit_post_translation_link( $post_id ) { + $link = get_edit_post_link( $post_id ); + $language = $this->model->post->get_language( $post_id ); + return $this->edit_translation_link( $link, $language ); + } + + /** + * Get the link to create a new term translation. + * + * @since 1.5 + * + * @param int $term_id Source term id. + * @param string $taxonomy Taxonomy name. + * @param string $post_type Post type name. + * @param PLL_Language $language The language of the new translation. + * @return string + */ + public function get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) { + $tax = get_taxonomy( $taxonomy ); + if ( ! $tax || ! current_user_can( $tax->cap->edit_terms ) ) { + return ''; + } + + $args = array( + 'taxonomy' => $taxonomy, + 'post_type' => $post_type, + 'from_tag' => $term_id, + 'new_lang' => $language->slug, + ); + + $link = add_query_arg( $args, admin_url( 'edit-tags.php' ) ); + + /** + * Filters the new term translation link. + * + * @since 1.8 + * + * @param string $link The new term translation link. + * @param PLL_Language $language The language of the new translation. + * @param int $term_id The source term id. + * @param string $taxonomy Taxonomy name. + * @param string $post_type Post type name. + */ + return apply_filters( 'pll_get_new_term_translation_link', $link, $language, $term_id, $taxonomy, $post_type ); + } + + /** + * Returns the html markup for a new term translation. + * + * @since 1.8 + * + * @param int $term_id Source term id. + * @param string $taxonomy Taxonomy name. + * @param string $post_type Post type name. + * @param PLL_Language $language The language of the new translation. + * @return string + */ + public function new_term_translation_link( $term_id, $taxonomy, $post_type, $language ) { + $link = $this->get_new_term_translation_link( $term_id, $taxonomy, $post_type, $language ); + return $this->new_translation_link( $link, $language ); + } + + /** + * Returns the html markup for a term translation link. + * + * @since 1.4 + * + * @param int $term_id Translation term id. + * @param string $taxonomy Taxonomy name. + * @param string $post_type Post type name. + * @return string + */ + public function edit_term_translation_link( $term_id, $taxonomy, $post_type ) { + $link = get_edit_term_link( $term_id, $taxonomy, $post_type ); + $language = $this->model->term->get_language( $term_id ); + return $this->edit_translation_link( $link, $language ); + } +} diff --git a/wp-content/plugins/polylang/admin/admin-model.php b/wp-content/plugins/polylang/admin/admin-model.php new file mode 100644 index 0000000000..37db9bfc34 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-model.php @@ -0,0 +1,540 @@ +validate_lang( $args ); + if ( $errors->has_errors() ) { + return $errors; + } + + // First the language taxonomy + $r = wp_insert_term( + $args['name'], + 'language', + array( + 'slug' => $args['slug'], + 'description' => $this->build_language_metas( $args ), + ) + ); + if ( is_wp_error( $r ) ) { + // Avoid an ugly fatal error if something went wrong ( reported once in the forum ) + return new WP_Error( 'pll_add_language', __( 'Impossible to add the language.', 'polylang' ) ); + } + wp_update_term( (int) $r['term_id'], 'language', array( 'term_group' => (int) $args['term_group'] ) ); // can't set the term group directly in wp_insert_term + + // The other language taxonomies. + $this->update_secondary_language_terms( $args['slug'], $args['name'] ); + + if ( ! isset( $this->options['default_lang'] ) ) { + // If this is the first language created, set it as default language + $this->options['default_lang'] = $args['slug']; + update_option( 'polylang', $this->options ); + } + + // Refresh languages. + $this->clean_languages_cache(); + $this->get_languages_list(); + + flush_rewrite_rules(); // Refresh rewrite rules. + + /** + * Fires when a language is added. + * + * @since 1.9 + * + * @param array $args Arguments used to create the language. @see PLL_Admin_Model::add_language(). + */ + do_action( 'pll_add_language', $args ); + + return true; + } + + /** + * Delete a language. + * + * @since 1.2 + * + * @param int $lang_id Language term_id. + * @return bool + */ + public function delete_language( $lang_id ) { + $lang = $this->get_language( (int) $lang_id ); + + if ( empty( $lang ) ) { + return false; + } + + // Oops ! we are deleting the default language... + // Need to do this before loosing the information for default category translations + if ( $lang->is_default ) { + $slugs = $this->get_languages_list( array( 'fields' => 'slug' ) ); + $slugs = array_diff( $slugs, array( $lang->slug ) ); + + if ( ! empty( $slugs ) ) { + $this->update_default_lang( reset( $slugs ) ); // Arbitrary choice... + } else { + unset( $this->options['default_lang'] ); + } + } + + // Delete the translations + $this->update_translations( $lang->slug ); + + // Delete language option in widgets + foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) { + if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) { + $obj = $widget['callback'][0]; + $number = $widget['params'][0]['number']; + if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) { + $settings = $obj->get_settings(); + if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $lang->slug ) { + unset( $settings[ $number ]['pll_lang'] ); + $obj->save_settings( $settings ); + } + } + } + } + + // Delete menus locations + if ( ! empty( $this->options['nav_menus'] ) ) { + foreach ( $this->options['nav_menus'] as $theme => $locations ) { + foreach ( array_keys( $locations ) as $location ) { + unset( $this->options['nav_menus'][ $theme ][ $location ][ $lang->slug ] ); + } + } + } + + // Delete users options + delete_metadata( 'user', 0, 'pll_filter_content', '', true ); + delete_metadata( 'user', 0, "description_{$lang->slug}", '', true ); + + // Delete domain + unset( $this->options['domains'][ $lang->slug ] ); + + /* + * Delete the language itself. + * + * Reverses the language taxonomies order is required to make sure 'language' is deleted in last. + * + * The initial order with the 'language' taxonomy at the beginning of 'PLL_Language::term_props' property + * is done by {@see PLL_Model::filter_language_terms_orderby()} + */ + foreach ( array_reverse( $lang->get_tax_props( 'term_id' ) ) as $taxonomy_name => $term_id ) { + wp_delete_term( $term_id, $taxonomy_name ); + } + + // Refresh languages. + $this->clean_languages_cache(); + $this->get_languages_list(); + + update_option( 'polylang', $this->options ); + flush_rewrite_rules(); // refresh rewrite rules + return true; + } + + /** + * Updates language properties. + * + * @since 1.2 + * + * @param array $args { + * @type int $lang_id Id of the language to modify. + * @type string $name Language name ( used only for display ). + * @type string $slug Language code ( ideally 2-letters ISO 639-1 language code ). + * @type string $locale WordPress locale. If something wrong is used for the locale, the .mo files will not be loaded... + * @type int $rtl 1 if rtl language, 0 otherwise. + * @type int $term_group Language order when displayed. + * @type string $flag Optional, country code, @see flags.php. + * } + * @return WP_Error|true true if success / WP_Error if failed. + */ + public function update_language( $args ) { + $lang = $this->get_language( (int) $args['lang_id'] ); + + if ( empty( $lang ) ) { + return new WP_Error( 'pll_invalid_language_id', __( 'The language does not seem to exist.', 'polylang' ) ); + } + + $errors = $this->validate_lang( $args, $lang ); + if ( $errors->get_error_code() ) { // Using has_errors() would be more meaningful but is available only since WP 5.0 + return $errors; + } + + // Update links to this language in posts and terms in case the slug has been modified + $slug = $args['slug']; + $old_slug = $lang->slug; + + if ( $old_slug != $slug ) { + // Update the language slug in translations + $this->update_translations( $old_slug, $slug ); + + // Update language option in widgets + foreach ( $GLOBALS['wp_registered_widgets'] as $widget ) { + if ( ! empty( $widget['callback'][0] ) && ! empty( $widget['params'][0]['number'] ) ) { + $obj = $widget['callback'][0]; + $number = $widget['params'][0]['number']; + if ( is_object( $obj ) && method_exists( $obj, 'get_settings' ) && method_exists( $obj, 'save_settings' ) ) { + $settings = $obj->get_settings(); + if ( isset( $settings[ $number ]['pll_lang'] ) && $settings[ $number ]['pll_lang'] == $old_slug ) { + $settings[ $number ]['pll_lang'] = $slug; + $obj->save_settings( $settings ); + } + } + } + } + + // Update menus locations + if ( ! empty( $this->options['nav_menus'] ) ) { + foreach ( $this->options['nav_menus'] as $theme => $locations ) { + foreach ( array_keys( $locations ) as $location ) { + if ( ! empty( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] ) ) { + $this->options['nav_menus'][ $theme ][ $location ][ $slug ] = $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ]; + unset( $this->options['nav_menus'][ $theme ][ $location ][ $old_slug ] ); + } + } + } + } + + // Update domains + if ( ! empty( $this->options['domains'][ $old_slug ] ) ) { + $this->options['domains'][ $slug ] = $this->options['domains'][ $old_slug ]; + unset( $this->options['domains'][ $old_slug ] ); + } + + // Update the default language option if necessary + if ( $lang->is_default ) { + $this->options['default_lang'] = $slug; + } + } + + update_option( 'polylang', $this->options ); + + // And finally update the language itself. + $this->update_secondary_language_terms( $args['slug'], $args['name'], $lang ); + + $description = $this->build_language_metas( $args ); + wp_update_term( $lang->get_tax_prop( 'language', 'term_id' ), 'language', array( 'slug' => $slug, 'name' => $args['name'], 'description' => $description, 'term_group' => (int) $args['term_group'] ) ); + + // Refresh languages. + $this->clean_languages_cache(); + $this->get_languages_list(); + + // Refresh rewrite rules. + flush_rewrite_rules(); + + /** + * Fires after a language is updated. + * + * @since 1.9 + * @since 3.2 Added $lang parameter. + * + * @param array $args { + * Arguments used to modify the language. @see PLL_Admin_Model::update_language(). + * + * @type string $name Language name (used only for display). + * @type string $slug Language code (ideally 2-letters ISO 639-1 language code). + * @type string $locale WordPress locale. + * @type int $rtl 1 if rtl language, 0 otherwise. + * @type int $term_group Language order when displayed. + * @type string $no_default_cat Optional, if set, no default category has been created for this language. + * @type string $flag Optional, country code, @see flags.php. + * } + * @param PLL_Language $lang Previous value of the language being edited. + */ + do_action( 'pll_update_language', $args, $lang ); + + return true; + } + + /** + * Builds the language metas into an array and serializes it, to be stored in the term description. + * + * @since 3.4 + * + * @param array $args { + * @type string $name Language name (used only for display). + * @type string $slug Language code (ideally 2-letters ISO 639-1 language code). + * @type string $locale WordPress locale. If something wrong is used for the locale, the .mo files will not be + * loaded... + * @type int $rtl 1 if rtl language, 0 otherwise. + * @type int $term_group Language order when displayed. + * @type int $lang_id Optional, ID of the language to modify. An empty value means the language is being + * created. + * @type string $flag Optional, country code, {@see settings/flags.php}. + * } + * @return string The serialized description array updated. + */ + protected function build_language_metas( array $args ) { + if ( ! empty( $args['lang_id'] ) ) { + $language_term = get_term( (int) $args['lang_id'] ); + + if ( $language_term instanceof WP_Term ) { + $old_data = maybe_unserialize( $language_term->description ); + } + } + + if ( empty( $old_data ) || ! is_array( $old_data ) ) { + $old_data = array(); + } + + $new_data = array( + 'locale' => $args['locale'], + 'rtl' => ! empty( $args['rtl'] ) ? 1 : 0, + 'flag_code' => empty( $args['flag'] ) ? '' : $args['flag'], + ); + + /** + * Allow to add data to store for a language. + * `$locale`, `$rtl`, and `$flag_code` cannot be overwritten. + * + * @since 3.4 + * + * @param mixed[] $add_data Data to add. + * @param mixed[] $args { + * Arguments used to create the language. + * + * @type string $name Language name (used only for display). + * @type string $slug Language code (ideally 2-letters ISO 639-1 language code). + * @type string $locale WordPress locale. If something wrong is used for the locale, the .mo files will + * not be loaded... + * @type int $rtl 1 if rtl language, 0 otherwise. + * @type int $term_group Language order when displayed. + * @type int $lang_id Optional, ID of the language to modify. An empty value means the language is + * being created. + * @type string $flag Optional, country code, {@see settings/flags.php}. + * } + * @param mixed[] $new_data New data. + * @param mixed[] $old_data { + * Original data. Contains at least the following: + * + * @type string $locale WordPress locale. + * @type int $rtl 1 if rtl language, 0 otherwise. + * @type string $flag_code Country code. + * } + */ + $add_data = apply_filters( 'pll_language_metas', array(), $args, $new_data, $old_data ); + // Don't allow to overwrite `$locale`, `$rtl`, and `$flag_code`. + $new_data = array_merge( $old_data, $add_data, $new_data ); + + /** @var non-empty-string $serialized maybe_serialize() cannot return anything else than a string when fed by an array. */ + $serialized = maybe_serialize( $new_data ); + return $serialized; + } + + /** + * Validates data entered when creating or updating a language. + * + * @see PLL_Admin_Model::add_language(). + * + * @since 0.4 + * + * @param array $args Parameters of {@see PLL_Admin_Model::add_language() or @see PLL_Admin_Model::update_language()}. + * @param PLL_Language|null $lang Optional the language currently updated, the language is created if not set. + * @return WP_Error + */ + protected function validate_lang( $args, $lang = null ) { + $errors = new WP_Error(); + + // Validate locale with the same pattern as WP 4.3. See #28303 + if ( ! preg_match( '#^[a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?$#', $args['locale'], $matches ) ) { + $errors->add( 'pll_invalid_locale', __( 'Enter a valid WordPress locale', 'polylang' ) ); + } + + // Validate slug characters + if ( ! preg_match( '#^[a-z_-]+$#', $args['slug'] ) ) { + $errors->add( 'pll_invalid_slug', __( 'The language code contains invalid characters', 'polylang' ) ); + } + + // Validate slug is unique + foreach ( $this->get_languages_list() as $language ) { + if ( $language->slug === $args['slug'] && ( null === $lang || $lang->term_id !== $language->term_id ) ) { + $errors->add( 'pll_non_unique_slug', __( 'The language code must be unique', 'polylang' ) ); + } + } + + // Validate name + // No need to sanitize it as wp_insert_term will do it for us + if ( empty( $args['name'] ) ) { + $errors->add( 'pll_invalid_name', __( 'The language must have a name', 'polylang' ) ); + } + + // Validate flag + if ( ! empty( $args['flag'] ) && ! is_readable( POLYLANG_DIR . '/flags/' . $args['flag'] . '.png' ) ) { + $flag = PLL_Language::get_flag_informations( $args['flag'] ); + + if ( ! empty( $flag['url'] ) ) { + $response = function_exists( 'vip_safe_wp_remote_get' ) ? vip_safe_wp_remote_get( esc_url_raw( $flag['url'] ) ) : wp_remote_get( esc_url_raw( $flag['url'] ) ); + } + + if ( empty( $response ) || is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + $errors->add( 'pll_invalid_flag', __( 'The flag does not exist', 'polylang' ) ); + } + } + + return $errors; + } + + /** + * Updates the translations when a language slug has been modified in settings + * or deletes them when a language is removed. + * + * @since 0.5 + * + * @param string $old_slug The old language slug. + * @param string $new_slug Optional, the new language slug, if not set it means that the language has been deleted. + * @return void + */ + public function update_translations( $old_slug, $new_slug = '' ) { + global $wpdb; + + $term_ids = array(); + $dr = array(); + $dt = array(); + $ut = array(); + + $taxonomies = $this->translatable_objects->get_taxonomy_names( array( 'translations' ) ); + $terms = get_terms( array( 'taxonomy' => $taxonomies ) ); + + if ( is_array( $terms ) ) { + foreach ( $terms as $term ) { + $term_ids[ $term->taxonomy ][] = $term->term_id; + $tr = maybe_unserialize( $term->description ); + + /** + * Filters the unserialized translation group description before it is + * updated when a language is deleted or a language slug is changed. + * + * @since 3.2 + * + * @param (int|string[])[] $tr { + * List of translations with lang codes as array keys and IDs as array values. + * Also in this array: + * + * @type string[] $sync List of synchronized translations with lang codes as array keys and array values. + * } + * @param string $old_slug The old language slug. + * @param string $new_slug The new language slug. + * @param WP_Term $term The term containing the post or term translation group. + */ + $tr = apply_filters( 'update_translation_group', $tr, $old_slug, $new_slug, $term ); + + if ( ! empty( $tr[ $old_slug ] ) ) { + if ( $new_slug ) { + $tr[ $new_slug ] = $tr[ $old_slug ]; // Suppress this for delete + } else { + $dr['id'][] = (int) $tr[ $old_slug ]; + $dr['tt'][] = (int) $term->term_taxonomy_id; + } + unset( $tr[ $old_slug ] ); + + if ( empty( $tr ) || 1 == count( $tr ) ) { + $dt['t'][] = (int) $term->term_id; + $dt['tt'][] = (int) $term->term_taxonomy_id; + } else { + $ut['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $term->term_id, maybe_serialize( $tr ) ); + $ut['in'][] = (int) $term->term_id; + } + } + } + } + + // Delete relationships + if ( ! empty( $dr ) ) { + // PHPCS:disable WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( + "DELETE FROM $wpdb->term_relationships + WHERE object_id IN ( " . implode( ',', $dr['id'] ) . ' ) + AND term_taxonomy_id IN ( ' . implode( ',', $dr['tt'] ) . ' )' + ); + // PHPCS:enable + } + + // Delete terms + if ( ! empty( $dt ) ) { + $wpdb->query( "DELETE FROM $wpdb->terms WHERE term_id IN ( " . implode( ',', $dt['t'] ) . ' )' ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( "DELETE FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ( " . implode( ',', $dt['tt'] ) . ' )' ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + // Update terms + if ( ! empty( $ut ) ) { + // PHPCS:disable WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( + "UPDATE $wpdb->term_taxonomy + SET description = ( CASE term_id " . implode( ' ', $ut['case'] ) . ' END ) + WHERE term_id IN ( ' . implode( ',', $ut['in'] ) . ' )' + ); + // PHPCS:enable + } + + if ( ! empty( $term_ids ) ) { + foreach ( $term_ids as $taxonomy => $ids ) { + clean_term_cache( $ids, $taxonomy ); + } + } + } + + /** + * Updates the default language + * taking care to update the default category & the nav menu locations. + * + * @since 1.8 + * + * @param string $slug New language slug. + * @return void + */ + public function update_default_lang( $slug ) { + // The nav menus stored in theme locations should be in the default language + $theme = get_stylesheet(); + if ( ! empty( $this->options['nav_menus'][ $theme ] ) ) { + $menus = array(); + + foreach ( $this->options['nav_menus'][ $theme ] as $key => $loc ) { + $menus[ $key ] = empty( $loc[ $slug ] ) ? 0 : $loc[ $slug ]; + } + set_theme_mod( 'nav_menu_locations', $menus ); + } + + /** + * Fires when a default language is updated. + * + * @since 3.1 + * + * @param string $slug Slug. + */ + do_action( 'pll_update_default_lang', $slug ); + + // Update options + $this->options['default_lang'] = $slug; + update_option( 'polylang', $this->options ); + + $this->clean_languages_cache(); + flush_rewrite_rules(); + } +} diff --git a/wp-content/plugins/polylang/admin/admin-nav-menu.php b/wp-content/plugins/polylang/admin/admin-nav-menu.php new file mode 100644 index 0000000000..8317fa4692 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-nav-menu.php @@ -0,0 +1,280 @@ +theme, array( $this, 'pre_update_option_theme_mods' ) ); + add_action( 'delete_nav_menu', array( $this, 'delete_nav_menu' ) ); + + // FIXME is it possible to choose the order ( after theme locations in WP3.5 and older ) ? + // FIXME not displayed if Polylang is activated before the first time the user goes to nav menus http://core.trac.wordpress.org/ticket/16828 + add_meta_box( 'pll_lang_switch_box', __( 'Language switcher', 'polylang' ), array( $this, 'lang_switch' ), 'nav-menus', 'side', 'high' ); + + $this->create_nav_menu_locations(); + } + + /** + * Language switcher metabox + * The checkbox and all hidden fields are important + * Thanks to John Morris for his very interesting post http://www.johnmorrisonline.com/how-to-add-a-fully-functional-custom-meta-box-to-wordpress-navigation-menus/ + * + * @since 1.1 + * + * @return void + */ + public function lang_switch() { + global $_nav_menu_placeholder, $nav_menu_selected_id; + $_nav_menu_placeholder = 0 > $_nav_menu_placeholder ? $_nav_menu_placeholder - 1 : -1; + ?> +
+
+ +
+

+ + class="button-secondary submit-add-to-menu right" value="" name="add-post-type-menu-item" id="submit-posttype-lang-switch"> + + +

+
+ base ) { + return; + } + + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + wp_enqueue_script( 'pll_nav_menu', plugins_url( '/js/build/nav-menu' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION ); + + $data = array( + 'strings' => PLL_Switcher::get_switcher_options( 'menu', 'string' ), // The strings for the options + 'title' => __( 'Languages', 'polylang' ), // The title + 'val' => array(), + ); + + // Get all language switcher menu items + $items = get_posts( + array( + 'numberposts' => -1, + 'nopaging' => true, + 'post_type' => 'nav_menu_item', + 'fields' => 'ids', + 'meta_key' => '_pll_menu_item', + ) + ); + + // The options values for the language switcher + foreach ( $items as $item ) { + $data['val'][ $item ] = get_post_meta( $item, '_pll_menu_item', true ); + } + + // Send all these data to javascript + wp_localize_script( 'pll_nav_menu', 'pll_data', $data ); + } + + /** + * Save our menu item options. + * + * @since 1.1 + * + * @param int $menu_id ID of the updated menu. + * @param int $menu_item_db_id ID of the updated menu item. + * @return void + */ + public function wp_update_nav_menu_item( $menu_id = 0, $menu_item_db_id = 0 ) { + if ( empty( $_POST['menu-item-url'][ $menu_item_db_id ] ) || '#pll_switcher' !== $_POST['menu-item-url'][ $menu_item_db_id ] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + // Security check as 'wp_update_nav_menu_item' can be called from outside WP admin + if ( current_user_can( 'edit_theme_options' ) ) { + check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' ); + + $options = array( 'hide_if_no_translation' => 0, 'hide_current' => 0, 'force_home' => 0, 'show_flags' => 0, 'show_names' => 1, 'dropdown' => 0 ); // Default values + // Our jQuery form has not been displayed + if ( empty( $_POST['menu-item-pll-detect'][ $menu_item_db_id ] ) ) { + if ( ! get_post_meta( $menu_item_db_id, '_pll_menu_item', true ) ) { // Our options were never saved + update_post_meta( $menu_item_db_id, '_pll_menu_item', $options ); + } + } + else { + foreach ( array_keys( $options ) as $opt ) { + $options[ $opt ] = empty( $_POST[ 'menu-item-' . $opt ][ $menu_item_db_id ] ) ? 0 : 1; + } + update_post_meta( $menu_item_db_id, '_pll_menu_item', $options ); // Allow us to easily identify our nav menu item + } + } + } + + /** + * Assign menu languages and translations based on ( temporary ) locations + * + * @since 1.8 + * + * @param array $locations nav menu locations + * @return array + */ + public function update_nav_menu_locations( $locations ) { + // Extract language and menu from locations + foreach ( $locations as $loc => $menu ) { + $infos = $this->explode_location( $loc ); + $this->options['nav_menus'][ $this->theme ][ $infos['location'] ][ $infos['lang'] ] = $menu; + if ( $this->options['default_lang'] != $infos['lang'] ) { + unset( $locations[ $loc ] ); // Remove temporary locations before database update + } + } + + update_option( 'polylang', $this->options ); + return $locations; + } + + /** + * Assign menu languages and translations based on ( temporary ) locations. + * + * @since 1.1 + * + * @param mixed $mods Theme mods. + * @return mixed + */ + public function pre_update_option_theme_mods( $mods ) { + if ( current_user_can( 'edit_theme_options' ) && is_array( $mods ) && isset( $mods['nav_menu_locations'] ) ) { + + // Manage Locations tab in Appearance -> Menus + if ( isset( $_GET['action'] ) && 'locations' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification + check_admin_referer( 'save-menu-locations' ); + $this->options['nav_menus'][ $this->theme ] = array(); + } + + // Edit Menus tab in Appearance -> Menus + // Add the test of $_POST['update-nav-menu-nonce'] to avoid conflict with Vantage theme + elseif ( isset( $_POST['action'], $_POST['update-nav-menu-nonce'] ) && 'update' === $_POST['action'] ) { + check_admin_referer( 'update-nav_menu', 'update-nav-menu-nonce' ); + $this->options['nav_menus'][ $this->theme ] = array(); + } + + // Customizer + // Don't reset locations in this case. + // see http://wordpress.org/support/topic/menus-doesnt-show-and-not-saved-in-theme-settings-multilingual-site + elseif ( isset( $_POST['action'] ) && 'customize_save' == $_POST['action'] ) { + check_ajax_referer( 'save-customize_' . $GLOBALS['wp_customize']->get_stylesheet(), 'nonce' ); + } + + else { + return $mods; // No modification for nav menu locations + } + + $mods['nav_menu_locations'] = $this->update_nav_menu_locations( $mods['nav_menu_locations'] ); + } + return $mods; + } + + /** + * Fills temporary menu locations based on menus translations + * + * @since 1.2 + * + * @param bool|array $menus Associative array of registered navigation menu IDs keyed by their location name. + * @return bool|array + */ + public function theme_mod_nav_menu_locations( $menus ) { + // Prefill locations with 0 value in case a location does not exist in $menus + $locations = get_registered_nav_menus(); + if ( is_array( $locations ) ) { + $locations = array_fill_keys( array_keys( $locations ), 0 ); + $menus = is_array( $menus ) ? array_merge( $locations, $menus ) : $locations; + } + + if ( is_array( $menus ) ) { + foreach ( array_keys( $menus ) as $loc ) { + foreach ( $this->model->get_languages_list() as $lang ) { + if ( ! empty( $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ] ) && term_exists( $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ], 'nav_menu' ) ) { + $menus[ $this->combine_location( $loc, $lang ) ] = $this->options['nav_menus'][ $this->theme ][ $loc ][ $lang->slug ]; + } + } + } + } + + return $menus; + } + + /** + * Removes the nav menu term_id from the locations stored in Polylang options when a nav menu is deleted + * + * @since 1.7.3 + * + * @param int $term_id nav menu id + * @return void + */ + public function delete_nav_menu( $term_id ) { + if ( isset( $this->options['nav_menus'] ) ) { + foreach ( $this->options['nav_menus'] as $theme => $locations ) { + foreach ( $locations as $loc => $languages ) { + foreach ( $languages as $lang => $menu_id ) { + if ( $menu_id === $term_id ) { + unset( $this->options['nav_menus'][ $theme ][ $loc ][ $lang ] ); + } + } + } + } + + update_option( 'polylang', $this->options ); + } + } +} diff --git a/wp-content/plugins/polylang/admin/admin-notices.php b/wp-content/plugins/polylang/admin/admin-notices.php new file mode 100644 index 0000000000..99cfed7f68 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-notices.php @@ -0,0 +1,270 @@ +options = &$polylang->options; + + add_action( 'admin_init', array( $this, 'hide_notice' ) ); + add_action( 'admin_notices', array( $this, 'display_notices' ) ); + } + + /** + * Add a custom notice + * + * @since 2.3.9 + * + * @param string $name Notice name + * @param string $html Content of the notice + * @return void + */ + public static function add_notice( $name, $html ) { + self::$notices[ $name ] = $html; + } + + /** + * Get custom notices. + * + * @since 2.3.9 + * + * @return string[] + */ + public static function get_notices() { + return self::$notices; + } + + /** + * Has a notice been dismissed? + * + * @since 2.3.9 + * + * @param string $notice Notice name + * @return bool + */ + public static function is_dismissed( $notice ) { + $dismissed = get_option( 'pll_dismissed_notices', array() ); + + // Handle legacy user meta + $dismissed_meta = get_user_meta( get_current_user_id(), 'pll_dismissed_notices', true ); + if ( is_array( $dismissed_meta ) ) { + if ( array_diff( $dismissed_meta, $dismissed ) ) { + $dismissed = array_merge( $dismissed, $dismissed_meta ); + update_option( 'pll_dismissed_notices', $dismissed ); + } + if ( ! is_multisite() ) { + // Don't delete on multisite to avoid the notices to appear in other sites. + delete_user_meta( get_current_user_id(), 'pll_dismissed_notices' ); + } + } + + return in_array( $notice, $dismissed ); + } + + /** + * Should we display notices on this screen? + * + * @since 2.3.9 + * + * @param string $notice The notice name. + * @return bool + */ + protected function can_display_notice( $notice ) { + $screen = get_current_screen(); + + if ( empty( $screen ) ) { + return false; + } + + $screen_id = sanitize_title( __( 'Languages', 'polylang' ) ); + + /** + * Filter admin notices which can be displayed + * + * @since 2.7.0 + * + * @param bool $display Whether the notice should be displayed or not. + * @param string $notice The notice name. + */ + return apply_filters( + 'pll_can_display_notice', + in_array( + $screen->id, + array( + 'dashboard', + 'plugins', + 'toplevel_page_mlang', + $screen_id . '_page_mlang_strings', + $screen_id . '_page_mlang_settings', + ) + ), + $notice + ); + } + + /** + * Stores a dismissed notice in the database. + * + * @since 2.3.9 + * + * @param string $notice Notice name. + * @return void + */ + public static function dismiss( $notice ) { + $dismissed = get_option( 'pll_dismissed_notices', array() ); + + if ( ! in_array( $notice, $dismissed ) ) { + $dismissed[] = $notice; + update_option( 'pll_dismissed_notices', array_unique( $dismissed ) ); + } + } + + /** + * Handle a click on the dismiss button + * + * @since 2.3.9 + * + * @return void + */ + public function hide_notice() { + if ( isset( $_GET['pll-hide-notice'], $_GET['_pll_notice_nonce'] ) ) { + $notice = sanitize_key( $_GET['pll-hide-notice'] ); + check_admin_referer( $notice, '_pll_notice_nonce' ); + self::dismiss( $notice ); + wp_safe_redirect( remove_query_arg( array( 'pll-hide-notice', '_pll_notice_nonce' ), wp_get_referer() ) ); + exit; + } + } + + /** + * Displays notices + * + * @since 2.3.9 + * + * @return void + */ + public function display_notices() { + if ( current_user_can( 'manage_options' ) ) { + // Core notices + if ( defined( 'WOOCOMMERCE_VERSION' ) && ! defined( 'PLLWC_VERSION' ) && $this->can_display_notice( 'pllwc' ) && ! $this->is_dismissed( 'pllwc' ) ) { + $this->pllwc_notice(); + } + + if ( ! defined( 'POLYLANG_PRO' ) && $this->can_display_notice( 'review' ) && ! $this->is_dismissed( 'review' ) && ! empty( $this->options['first_activation'] ) && time() > $this->options['first_activation'] + 15 * DAY_IN_SECONDS ) { + $this->review_notice(); + } + + // Custom notices + foreach ( $this->get_notices() as $notice => $html ) { + if ( $this->can_display_notice( $notice ) && ! $this->is_dismissed( $notice ) ) { + ?> +
+ dismiss_button( $notice ); + echo wp_kses_post( $html ); + ?> +
+ %s', + esc_url( wp_nonce_url( add_query_arg( 'pll-hide-notice', $name ), $name, '_pll_notice_nonce' ) ), + /* translators: accessibility text */ + esc_html__( 'Dismiss this notice.', 'polylang' ) + ); + } + + /** + * Displays a notice if WooCommerce is activated without Polylang for WooCommerce + * + * @since 2.3.9 + * + * @return void + */ + private function pllwc_notice() { + ?> +
+ dismiss_button( 'pllwc' ); ?> +

+ ', + '' + ); + ?> +

+
+ +
+ dismiss_button( 'review' ); ?> +

+ ', + '' + ); + ?> +

+
+ links = &$polylang->links; + + // Add post state for translations of the front page and posts page + add_filter( 'display_post_states', array( $this, 'display_post_states' ), 10, 2 ); + + // Refreshes the language cache when a static front page or page for for posts has been translated. + add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 ); + + // Prevents WP resetting the option + add_filter( 'pre_update_option_show_on_front', array( $this, 'update_show_on_front' ), 10, 2 ); + + add_action( 'admin_notices', array( $this, 'notice_must_translate' ) ); + } + + /** + * Adds post state for translations of the front page and posts page. + * + * @since 1.8 + * + * @param string[] $post_states An array of post display states. + * @param WP_Post $post The current post object. + * @return string[] + */ + public function display_post_states( $post_states, $post ) { + if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_on_front' ) ) ) ) { + $post_states['page_on_front'] = __( 'Front Page', 'polylang' ); + } + + if ( in_array( $post->ID, $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ) ) ) { + $post_states['page_for_posts'] = __( 'Posts Page', 'polylang' ); + } + + return $post_states; + } + + /** + * Refreshes the language cache when a static front page or page for for posts has been translated. + * + * @since 1.8 + * + * @param int $post_id Not used. + * @param WP_Post $post Not used. + * @param int[] $translations Translations of the post being saved. + * @return void + */ + public function pll_save_post( $post_id, $post, $translations ) { + if ( in_array( $this->page_on_front, $translations ) || in_array( $this->page_for_posts, $translations ) ) { + $this->model->clean_languages_cache(); + } + } + + /** + * Prevents WP resetting the option if the admin language filter is active for a language with no pages. + * + * @since 1.9.3 + * + * @param string $value The new, unserialized option value. + * @param string $old_value The old option value. + * @return string + */ + public function update_show_on_front( $value, $old_value ) { + if ( ! empty( $GLOBALS['pagenow'] ) && 'options-reading.php' === $GLOBALS['pagenow'] && 'posts' === $value && ! get_pages() && get_pages( array( 'lang' => '' ) ) ) { + $value = $old_value; + } + return $value; + } + + /** + * Add a notice to translate the static front page if it is not translated in all languages + * This is especially useful after a new language is created. + * The notice is not dismissible and displayed on the Languages pages and the list of pages. + * + * @since 2.6 + * + * @return void + */ + public function notice_must_translate() { + $screen = get_current_screen(); + + if ( ! empty( $screen ) && ( 'toplevel_page_mlang' === $screen->id || 'edit-page' === $screen->id ) ) { + $message = $this->get_must_translate_message(); + + if ( ! empty( $message ) ) { + printf( + '

%s

', + $message // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + } + } + + /** + * Returns the message asking to translate the static front page in all languages. + * + * @since 2.8 + * + * @return string + */ + public function get_must_translate_message() { + $message = ''; + + if ( $this->page_on_front ) { + $untranslated = array(); + + foreach ( $this->model->get_languages_list() as $language ) { + if ( ! $this->model->post->get( $this->page_on_front, $language ) ) { + $untranslated[] = sprintf( + '%s', + esc_url( $this->links->get_new_post_translation_link( $this->page_on_front, $language ) ), + esc_html( $language->name ) + ); + } + } + + if ( ! empty( $untranslated ) ) { + $message = sprintf( + /* translators: %s is a comma separated list of native language names */ + esc_html__( 'You must translate your static front page in %s.', 'polylang' ), + implode( ', ', $untranslated ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + } + + return $message; + } +} diff --git a/wp-content/plugins/polylang/admin/admin-strings.php b/wp-content/plugins/polylang/admin/admin-strings.php new file mode 100644 index 0000000000..bd0b014589 --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin-strings.php @@ -0,0 +1,136 @@ + __( 'Widget title', 'polylang' ), + 'widget_text' => __( 'Widget text', 'polylang' ), + ); + + // Widgets titles + global $wp_registered_widgets; + $sidebars = wp_get_sidebars_widgets(); + foreach ( $sidebars as $sidebar => $widgets ) { + if ( 'wp_inactive_widgets' == $sidebar || empty( $widgets ) ) { + continue; + } + + foreach ( $widgets as $widget ) { + // Nothing can be done if the widget is created using pre WP2.8 API :( + // There is no object, so we can't access it to get the widget options + if ( ! isset( $wp_registered_widgets[ $widget ]['callback'][0] ) || ! is_object( $wp_registered_widgets[ $widget ]['callback'][0] ) || ! method_exists( $wp_registered_widgets[ $widget ]['callback'][0], 'get_settings' ) ) { + continue; + } + + $widget_settings = $wp_registered_widgets[ $widget ]['callback'][0]->get_settings(); + $number = $wp_registered_widgets[ $widget ]['params'][0]['number']; + + // Don't enable widget translation if the widget is visible in only one language or if there is no title + if ( empty( $widget_settings[ $number ]['pll_lang'] ) ) { + if ( isset( $widget_settings[ $number ]['title'] ) && $title = $widget_settings[ $number ]['title'] ) { + self::register_string( self::$default_strings['widget_title'], $title, 'Widget' ); + } + + if ( isset( $widget_settings[ $number ]['text'] ) && $text = $widget_settings[ $number ]['text'] ) { + self::register_string( self::$default_strings['widget_text'], $text, 'Widget', true ); + } + } + } + } + + /** + * Filter the list of strings registered for translation + * Mainly for use by our PLL_WPML_Compat class + * + * @since 1.0.2 + * + * @param array $strings list of strings + */ + self::$strings = apply_filters( 'pll_get_strings', self::$strings ); + return self::$strings; + } + + /** + * Performs the sanitization ( before saving in DB ) of default strings translations + * + * @since 1.6 + * + * @param string $translation translation to sanitize + * @param string $name unique name for the string + * @return string + */ + public static function sanitize_string_translation( $translation, $name ) { + if ( $name == self::$default_strings['widget_title'] ) { + $translation = sanitize_text_field( $translation ); + } + + if ( $name == self::$default_strings['widget_text'] && ! current_user_can( 'unfiltered_html' ) ) { + $translation = wp_kses_post( $translation ); + } + + return $translation; + } +} diff --git a/wp-content/plugins/polylang/admin/admin.php b/wp-content/plugins/polylang/admin/admin.php new file mode 100644 index 0000000000..4b95c18d2a --- /dev/null +++ b/wp-content/plugins/polylang/admin/admin.php @@ -0,0 +1,180 @@ +model->has_languages() ) { + add_action( 'wp_loaded', array( $this, 'add_filters' ), 5 ); + } + } + + /** + * Adds a 'settings' link for our plugin in the plugins list table. + * + * @since 0.1 + * + * @param string[] $links List of links associated to the plugin. + * @return string[] Modified list of links. + */ + public function plugin_action_links( $links ) { + array_unshift( $links, '' . __( 'Settings', 'polylang' ) . '' ); + return $links; + } + + /** + * Adds the upgrade notice in plugins table + * + * @since 1.1.6 + * + * @param array $plugin_data Not used + * @param object $r Plugin update data + * @return void + */ + public function plugin_update_message( $plugin_data, $r ) { + if ( ! empty( $r->upgrade_notice ) ) { + printf( '

%s

', esc_html( $r->upgrade_notice ) ); + } + } + + /** + * Setup filters for admin pages + * + * @since 1.2 + * @since 2.7 instantiate a PLL_Bulk_Translate instance. + * @return void + */ + public function add_filters() { + $this->filters_sanitization = new PLL_Filters_Sanitization( $this->get_locale_for_sanitization() ); + $this->filters_widgets_options = new PLL_Admin_Filters_Widgets_Options( $this ); + + // All these are separated just for convenience and maintainability + $classes = array( 'Filters', 'Filters_Columns', 'Filters_Post', 'Filters_Term', 'Nav_Menu', 'Classic_Editor', 'Block_Editor' ); + + // Don't load media filters if option is disabled or if user has no right + if ( $this->options['media_support'] && ( $obj = get_post_type_object( 'attachment' ) ) && ( current_user_can( $obj->cap->edit_posts ) || current_user_can( $obj->cap->create_posts ) ) ) { + $classes[] = 'Filters_Media'; + } + + foreach ( $classes as $class ) { + $obj = strtolower( $class ); + + /** + * Filter the class to instantiate when loading admin filters + * + * @since 1.5 + * + * @param string $class class name + */ + $class = apply_filters( 'pll_' . $obj, 'PLL_Admin_' . $class ); + $this->$obj = new $class( $this ); + } + } + + /** + * Retrieve the locale according to the current language instead of the language + * of the admin interface. + * + * @since 2.0 + * + * @return string + */ + public function get_locale_for_sanitization() { + $locale = get_locale(); + + if ( isset( $_POST['post_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $locale = $lang->locale; + } elseif ( isset( $_POST['term_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['term_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $locale = $lang->locale; + } elseif ( isset( $_POST['inline_lang_choice'] ) && $lang = $this->model->get_language( sanitize_key( $_POST['inline_lang_choice'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $locale = $lang->locale; + } elseif ( ! empty( $this->curlang ) ) { + $locale = $this->curlang->locale; + } + + return $locale; + } +} diff --git a/wp-content/plugins/polylang/admin/view-translations-media.php b/wp-content/plugins/polylang/admin/view-translations-media.php new file mode 100644 index 0000000000..d0a14ba996 --- /dev/null +++ b/wp-content/plugins/polylang/admin/view-translations-media.php @@ -0,0 +1,43 @@ + +

+ + model->get_languages_list() as $language ) { + if ( $language->term_id == $lang->term_id ) { + continue; + } + ?> + + + + + +
flag; // phpcs:ignore WordPress.Security.EscapeOutput ?>name ); ?> + model->post->get_translation( $post_ID, $language ) ) && $translation_id !== $post_ID ) { + // The translation exists + printf( + '', + esc_attr( $language->slug ), + esc_attr( $translation_id ) + ); + echo $this->links->edit_post_translation_link( $translation_id ); // phpcs:ignore WordPress.Security.EscapeOutput + } else { + // No translation + echo $this->links->new_post_translation_link( $post_ID, $language ); // phpcs:ignore WordPress.Security.EscapeOutput + } + ?> +
diff --git a/wp-content/plugins/polylang/admin/view-translations-post.php b/wp-content/plugins/polylang/admin/view-translations-post.php new file mode 100644 index 0000000000..2a9356bfd4 --- /dev/null +++ b/wp-content/plugins/polylang/admin/view-translations-post.php @@ -0,0 +1,69 @@ + +

+ + model->get_languages_list() as $language ) { + if ( $language->term_id === $lang->term_id ) { + continue; + } + + $translation_id = $this->model->post->get_translation( $post_ID, $language ); + if ( ! $translation_id || $translation_id === $post_ID ) { // $translation_id == $post_ID happens if the post has been (auto)saved before changing the language. + $translation_id = 0; + } + + if ( ! empty( $from_post_id ) ) { + $translation_id = $this->model->post->get( $from_post_id, $language ); + } + + $add_link = $this->links->new_post_translation_link( $post_ID, $language ); + $link = $add_link; + $translation = null; + if ( $translation_id ) { + $translation = get_post( $translation_id ); + $link = $this->links->edit_post_translation_link( $translation_id ); + } + ?> + + + + + slug ); + ?> + + + +
flag ? $language->flag : esc_html( $language->slug ); // phpcs:ignore WordPress.Security.EscapeOutput ?> + %2$s + + ', + esc_attr( $language->slug ), + /* translators: accessibility text */ + esc_html__( 'Translation', 'polylang' ), + ( empty( $translation ) ? 0 : esc_attr( $translation->ID ) ), + ( empty( $translation ) ? '' : esc_attr( $translation->post_title ) ), + esc_attr( $language->get_locale( 'display' ) ), + ( $language->is_rtl ? 'rtl' : 'ltr' ) + ); + ?> +
diff --git a/wp-content/plugins/polylang/admin/view-translations-term.php b/wp-content/plugins/polylang/admin/view-translations-term.php new file mode 100644 index 0000000000..ac565a197e --- /dev/null +++ b/wp-content/plugins/polylang/admin/view-translations-term.php @@ -0,0 +1,98 @@ + + + + +

+ + + model->get_languages_list() as $language ) { + if ( $language->term_id == $lang->term_id ) { + continue; + } + + // Look for any existing translation in this language + // Take care not to propose a self link + $translation = 0; + if ( isset( $term_id ) && ( $translation_id = $this->model->term->get_translation( $term_id, $language ) ) && $translation_id != $term_id ) { + $translation = get_term( $translation_id, $taxonomy ); + } + if ( ! empty( $from_term_id ) && ( $translation_id = $this->model->term->get( $from_term_id, $language ) ) && ! $this->model->term->get_translation( $translation_id, $lang ) ) { + $translation = get_term( $translation_id, $taxonomy ); + } + + if ( isset( $term_id ) ) { // Do not display the add new link in add term form ( $term_id not set !!! ) + $link = $add_link = $this->links->new_term_translation_link( $term_id, $taxonomy, $post_type, $language ); + } + + if ( $translation ) { + $link = $this->links->edit_term_translation_link( $translation->term_id, $taxonomy, $post_type ); + } + ?> + + + + + + + + + +
+ flag ? $language->flag : esc_html( $language->slug ); // phpcs:ignore WordPress.Security.EscapeOutput ?> + %2$s', + isset( $term_id ) ? '' : ' screen-reader-text', + esc_html( $language->name ) + ); + ?> + + %2$s + + ', + esc_attr( $language->slug ), + /* translators: accessibility text */ + esc_html__( 'Translation', 'polylang' ), + ( empty( $translation ) ? 0 : esc_attr( $translation->term_id ) ), + ( empty( $translation ) ? '' : esc_attr( $translation->name ) ), + disabled( empty( $disabled ), false, false ), + esc_attr( $language->get_locale( 'display' ) ), + ( $language->is_rtl ? 'rtl' : 'ltr' ) + ); + ?> +
+ + + 14.0 + += 2.9 (2020-12-07) = + +* Add compatibility with WordPress 5.6 +* Pro: Add locale fallback used when the theme or plugins translations are not available +* Pro: Fix SSO and browser preferred language redirect when using multiple domains +* Pro: Fix post slugs for German and Danish in the REST API +* Pro: Fix a fatal error in ACF integration when saving url modifications with multiple domains +* Pro: Fix a deprecated notice fired by ACF since the version 5.9.2 +* Pro: Fix ACF relationship fields not reloaded when changing the language in the classic editor +* Update plugin updater to version 1.8 +* Add Lower Sorbian to the list of predefined language +* Options are now translated on backend when using the admin language filter +* Keep previous translations when modifying an option value +* Add navigation markup to the language switcher widget +* Fix canonical redirect for taxonomy terms +* Fix a fatal error when deleting a post with a translation group corrupted in the database +* Fix a fatal error when switching to plain permalinks and using multiple domains +* Fix a conflict with WP Sweep which could corrupt languages +* Fix title displayed instead of meta description with Yoast SEO > 14.0 +* Fix PHP Notice: Undefined index: wp_the_query in /frontend/choose-lang-content.php on line 92 + += 2.8.4 (2020-11-03) = + +* Pro: Remove useless bulk translate action for ACF fields groups +* Pro: Fix the translation of the CPTUI labels when the language is set from the content +* Fix sitemaps redirected to the default language since WP 5.5.1 +* Fix object cache not flushed for sticky posts #601 +* Fix blog page broken when trashing a page and the blog page is not translated in all languages +* Fix custom flags ignored in WPML compatibility mode +* Fix breadcrumb for untranslated post types in Yoast SEO + += 2.8.3 (2020-10-13) = + +* Honor install_languages capability to download language packs +* Pro: Fix integrations not loaded (with The Events Calendar, CPTUI, Content blocks) +* Pro: Fix fatal error with ACF if a flexible content includes a repeater and a relationship +* Pro: Fix terms sharing their slug impossible to update without changing the slug +* When available, use wpcom_vip_get_page_by_path() instead of get_page_by_path() +* Fix queries filtered when editing a post that was declared untranslatable after it got a language +* Fix issues with Yoast SEO 14.0+ (breadcrumbs, canonical, title and description) + += 2.8.2 (2020-09-08) = + +* Pro: Fix posts sharing the same slug displayed on the same page +* Fix: Don't use a javascript localized string removed in WP 5.5 #568 +* Fix fatal error in site health when no language is defined #563 +* Fix various issues with Yoast SEO 14.x #65, #503, #505 +* Fix fatal error with MU Domain Mapping when saving domains in Polylang settings #569 + += 2.8.1 (2020-08-25) = + +* Pro: Fix fatal error with WP 4.9 +* Fix pll_the_languages() with 'raw' option returning html flag instead of flag url #558 +* Fix compatibility with Duplicate Posts not correctly loaded #557 +* Fix custom flag size in admin bar language switcher #559 +* Fix tag clouds mixed in the classic editor #561 + += 2.8 (2020-08-17) = + +* Pro: Add a language switcher block +* Pro: Add compatibility with block image edition introduced in WP 5.5 +* Pro: Fix our private taxonomies being displayed in the ACF field group rules. +* Pro: Fix incorrect flags loaded from the block editor +* Pro: Fix SSO causing a wrong redirect when using subdomains (introduced in 2.7.4) +* Pro: Fix a performance issue on the plugins list +* Pro: Fix option to automatically duplicate media in all languages when uploading a new file not honored in block image +* Use composer for autoload and Polylang Pro dependency on Polylang +* Display a flag for each post in the posts list tables (same for terms). #515 +* Add test for the homepage translations to Site Health +* Add debug information to Site Health +* Add compatibility with the sitemaps introduced in WP 5.5 #451 +* Always filter WP_Query by the current language +* Support wildcards in "admin-texts" parent keys in wpml-config.xml +* Fix sticky posts showed for all languages when the admin language filter is active #469 +* Fix a performance issue on the pages list +* Fix dependency to jQuery Migrate removed from WP 5.5 #539 +* Fix: output secure cookie when using a cache plugin and ssl #542 +* Fix the possibility to create 2 terms with the same name in the same language, without specifying the second slug. +* Fix sticky posts appearing 2 times in WP 5.5 + += 2.7.4 (2020-06-29) = + +* Pro: Allow using our /untranslated-posts REST endpoint for non-public post types +* Pro: Fix broken display in the block editor sidebar when a language has no flag +* Pro: Fix SSO breaking the preview on secondary domains +* Pro: Fix ACF translation option not working for term custom fields +* Pro: Fix a styling issue in the fields group list table in ACF 5.9 +* Add Spanish from Puerto Rico to the predefined list of languages + += 2.7.3 (2020-05-26) = + +* Security: Slash metas +* Pro: Fix categories not savedafter the language has been switched in the block editor +* Pro: Fix ACF fields stored as integers instead of strings +* Pro: Fix ACF untranslated posts or terms being copied when creating a new translation +* Pro: Fix PHP notice with ACF when a repeater or group is included in a flexible content +* Pro: Fix "DevTools failed to load SourceMap" warning in browser console +* Update plugin updater to 1.7.1 +* Honor the filter "pll_the_language_link" when the language switcher displays a dropdown #506 +* Fix "Something went wrong" message when quick editing untranslated post types #508 +* Fix wpseo_opengraph deprecated warning #509 + += 2.7.2 (2020-04-27) = + +* Pro: Re-allow to modify the capability for strings translations +* Pro: Fix redirect for posts having the same slug as a media +* Pro: Fix PHP notice with ACF flexible content +* Pro: Fix a fatal error with InfiniteWP +* Update plugin updater to 1.7 +* Fix font in setup wizard + += 2.7.1 (2020-04-09) = + +* Pro: Fix untranslated post types filtered by the parameter in the REST API #493 +* Fix fatal error when the function idn_to_ascii is not available +* Fix PHP warning warning when a 3rd party plugin declares options not stored in DB in wpml-config.xml #492 +* Fix fatal error when a 3rd party plugin declares options stored as objects in wpml-config.xml #494 + += 2.7 (2020-04-06) = + +* Minimum WordPress version is now 4.9 +* Pro: Strings translations can now be exported and imported (in PO format) +* Pro: Allow to decide individually which ACF fields to copy or synchronize +* Pro: Add action pll_inactive_language_requested +* Pro; Fix fatal error in The Events Calendar compatibility when no language is defined yet +* Pro: Fix bulk translate when a post has no language +* Pro: Fix reusable block saved without language +* Pro: Fix post requested by slug not filtered in REST API, when the slug is shared +* Add a setup wizard +* Add Swahili, Upper Sorbian, Sindhi and Spanish from Uruguay to the list of predefined languages +* Add flags in the predefined list of languages +* Allow to hide the metaboxes from the screen options +* The deletion of the plugin's data at uninstall is now controlled by a PHP constant instead of an option #456 +* Add parent in ajax response when selecting a term in autocomplete field #328 +* Add Vary: Accept-Language http header in home page redirect. Props @chesio #452 +* Improve performance to register/unregister WPML strings +* Add support for the action wpml_switch_language +* Add post_status to the list of accepted args of pll_count_posts() +* Apply the filter pll_preferred_language in wp-login.php +* Use filtered wrappers to create meta when creating media translations #231 +* Allow to translate the Twenty Seventeen header video Youtube url #460 +* Notices are now dismissed per site instead of per user #478 +* Fix terms not visible in the quick edit when only one language is defined and the admin language filter is active +* Fix post state not displayed for translations of the privacy policy page #395 +* Fix wildcards not correctly interpreted in wpml-config.xml +* Fix product categories with special characters duplicated when importing WooCommerce products #474 + += 2.6.10 (2020-02-19) = + +* Pro: Fix sticky posts not filtered in REST API (introduced in 2.6.9) +* Fix wrong language detected if a child page uses the slug of another language +* Fix a PHP notice with PHP 7.4. #438 +* Fix lang-item-first class in language switcher when the current language is hidden. #445 +* Fix partially a conflict with Fusion Builder (the other part of the conflict being in Fusion Builder). + += 2.6.9 (2020-01-15) = + +* Pro: Use 'parse_query' rather than 'rest_{$type}_query' to filter REST requests. +* Pro: Filter the comments REST endpoint. +* Pro: Fix duplication of terms without language. +* Pro: Fix fatal error when Admin Columns is activated and no language is defined yet. +* Fix shortlink when using one subdomain or domain per language + += 2.6.8 (2019-12-11) = + +* Pro: Fix conflict with JetThemesCore from Crocoblock +* Fix: better detection of REST requests when using plain permalinks +* Fix usage of deprecated action wpmu_new_blog in WP 5.1+ +* Fix PHP notices with PHP 7.4 + += 2.6.7 (2019-11-14) = + +* Require PHP 5.6 +* Fix PHP warning in WP 5.3 + += 2.6.6 (2019-11-12) = + +* Pro: Fix wrong ajax url when using one domain per language +* Pro: Fix conflict with user switching plugin when using multiple domains +* Pro: Fix latest posts block in WP 5.3 +* Fix database error when attempting to sync an untranslated page parent +* Fix a conflict with the theme Neptune by Osetin + += 2.6.5 (2019-10-09) = + +* Pro: Require ACF 5.7.11+ to activate the compatibility to avoid fatal errors with older versions +* Pro: Avoid translating empty front slug (could cause a wrong redirect to /wp-admin) +* Pro: Fix filter wp_unique_term_slug not always correctly applied. +* Pro: Fix a conflict with Divi causing post synchronization buttons to be displayed multiple times +* Avoid notice in WP CLI context + += 2.6.4 (2019-08-27) = + +* Pro: Fix a conflict preventing meta synchronization when ACF is active +* Pro: Fix post metas not correctly copied when translating a Beaver Builder page +* Pro: Fix a fatal error when posts made with Elementor are synchronized +* Pro: Fix Prewiew button not working correctly when using one domain per language +* Pro: Fix post synchronization not available for WP CRON and WP CLI +* Fix future posts not available in the autocomplete input field of the languages metabox +* Fix translations files not loaded on REST requests +* Fix deleted term parent not synchronized + += 2.6.3 (2019-08-06) = + +* Pro: Fix fatal error when updating an ACF field from frontend +* Pro: Add action 'pll_post_synchronized' +* Allow to get the current or default language object using the API. Props Jory Hogeveen. #359 +* Fix empty span in languages switcher widget when showing only flags +* Fix wpml_register_single_string when updating the original string + += 2.6.2 (2019-07-16) = + +* Pro: Fix slow admin in case the translations update server can't be reached +* Pro: Fix value not correctly translated for ACF clone fields in repeater +* Fix strings translations mixed when registered via the WPML compatibility. #381 + += 2.6.1 (2019-07-03) = + +* Pro: Fix Yoast SEO sitemap for inactive languages when using subdomains or multiple domains +* Fix fatal error in combination with Yoast SEO and Social Warfare +* Fix post type archive url in Yoast SEO sitemap + += 2.6 (2019-06-26) = + +* Pro: Remove all languages files. All translations are now maintained on TranslationsPress +* Pro: Move the languages metabox to a block editor plugin +* Pro: Better management of user capabilities when synchronizing posts +* Pro: Separate REST requests from the frontend +* Pro: Copy the post slug when duplicating a post +* Pro: Duplicate ACF term metas when terms are automatically duplicated when creating a new post translation +* Pro: Fix hierarchy lost when duplicating terms +* Pro: Fix page shared slugs with special characters +* Pro: Fix synchronized posts sharing their slug when the language is set from the content +* Pro: Fix PHP warning with ACF Pro 5.8.1 +* Pro: Fix ACF clone fields not translated in repeaters +* Better management of user capabilities when synchronizing taxonomies terms and custom fields +* Extend string translations search to translated strings #207 +* Update plugin updater to 1.6.18 +* Honor the filter `pll_flag` when performing the flag validation when creating a new language +* Modify the title and the label for the language switcher menu items #307 +* Add support for international domain names +* Add a title to the link icon used to add a translation #325 +* Add a notice when a static front page is not translated in a language +* Add support for custom term fields in wpml-config.xml +* Add filter `pll_admin_languages_filter` for the list of items the admin bar language filter +* Add compatibility with WP Offload Media Lite. Props Daniel Berkman +* Yoast SEO: Add post type archive url in all languages to the sitemap +* Fix www. not redirected to not www. for the home page in multiple domains #311 +* Fix cropped images not being synchronized +* Fix auto added page to menus when the page is created with the block editor +* Fix embed of translated static front page #318 +* Fix a possible infinite redirect if the static front page is not translated +* Fix incorrect behavior of action 'wpml_register_single_string' when updating the string source +* Fix fatal error with Jetpack when no languages has been defined yet #330 +* Fix a conflict with Laravel Valet. Props @chesio. #250 +* Fix a conflict with Thesis. +* Fix a conflict with Pods in the block editor. Props Jory Hogeveen. #369 +* Fix fatal error with Twenty Fourteen introduced in version 2.5.4. #374 + += 2.5.4 (2019-05-28) = + +* Add Kannada to the predefined languages list +* Yoast SEO: Fix primary product cat not copied or synchronized +* WPMU Domain Mapping: Fix incorrect domain used for the theme +* Fix style-rtl.css not loaded when the language is set from the content #356 +* Fix Jetpack featured pages not working. Props Anis Ladram. #357 +* Fix Call to undefined function wp_generate_attachment_metadata() + += 2.5.3 (2019-04-16) = + +* Add de_AT and pt_AO to the predefined languages list +* Pro: Add filter pll_translate_blocks +* Pro: fix PHP notice when the queried post type has been modified to an array +* Pro: fix PHP warning when combined with The Event Calendar and Page builder by SiteOrigin + += 2.5.2 (2019-02-12) = + +* Pro: Fix translated slugs not accepting forward slashes +* Pro: Fix fatal error with ACF Pro 5.7.11 +* Fix parent categories incorrectly synchronized #327 + += 2.5.1 (2019-01-16) = + +* Security: Fix categories and media duplication not protected from CSRF +* Pro: Allow to update the plugin with WP CLI +* Pro: Fix search in the button block not filtered in the correct language (needs WP 5.1) +* Add Saraiki to the predefined languages list +* Fix a conflict causing a blank page with Divi + += 2.5 (2018-12-06) = + +* Add compatibility with WP 5.0 +* Fix custom flags when the WP content folder is not in the WP install folder +* Fix PHP notice if a language has no flag + += 2.4.1 (2018-11-27) = + +* Pro: Add compatibility with REST API changes made in WP 5.0 +* Pro: Fix sticky posts in the REST API +* Pro: Fix overwritten custom post slug when the post is updated with the REST API +* Pro: Fix bulk translate for media +* Fix a conflict with Custom sidebars and Content aware sidebars +* Fix a conflict with the theme Pokemania +* Fix PHP notices when using the function 'icl_link_to_element' for terms +* Fix title slugs for posts written in German + += 2.4 (2018-11-12) = + +* Minimum WordPress version is now 4.7 +* Pro: Add the possibility to bulk duplicate or bulk synchronize posts. +* Pro: Add compatibility with Admin Columns +* Pro: Add synchronized posts to the REST API +* Pro: Fix variations messed when changing WooCommerce attributes slugs +* Pro: Fix incorrect language for ajax requests made on front by The Events Calendar +* Pro: Fix term not duplicated correctly when the language is set from the content +* Refactor the core to activate on front and for the REST api actions that were previously available only in the backend (language checks, synchronizations...). +* Add flags to widgets displayed in only one language (Props Jory Hogeveen) #257 +* Honor the filter 'pll_the_language_args' for all options in menus #237 +* Add better filters for default flags and custom flags +* Custom flags can now be stored in the polylang directory in the theme +* Custom flags can now use SVG +* Add compatibility with Jetpack featured content module +* Fix Twenty Fourteen featured posts possibly not filtered per language +* Fix home url not working with WordPress MU Domain mapping +* Fix Assigning a parent category breaking the hierarchy of translated category +* Fix: Accept 0,1 and 1.0 as q factors in browser preferred language detection (Props Dominic Rubas) +* Fix performance issue when using hundreds of widgets +* Fix translations possibly wrong if the post language is changed without saving the post after + += 2.3.11 (2018-10-03) = + +* Pro: Add action 'pll_created_sync_post' +* Pro: Fix language and translations not included for tags in the REST API +* Fix Assigning a parent category breaking the hierarchy of translated category + += 2.3.10 (2018-08-16) = + +* Fix Lingotek notice not dismissable +* Fix fatal error with the widget calendar + += 2.3.9 (2018-08-14) = + +* Add a notice to inform about Polylang for WooCommerce +* Deprecate PLL_Pointer +* Fix bulk editing pages with no language breaking hierarchy #281 +* Fix an edge case where rewrite rules could be messed on a multisite +* MU Domain Mapping: fix secondary domain redirected to primary domain + += 2.3.8 (2018-07-16) = + +* Pro: Duplicate term meta when duplicating a post creates new terms +* Pro: Add compatibility with ACF Pro when it's bundled with the theme +* Pro: Fix a fatal error when duplicating posts +* Set cookie during the home redirect +* Accept a port in the url to detect the site home +* Add filter 'pll_is_cache_active' to allow to load the cache compatibility #270 #274 +* Fix potential fatal error when a 3rd party misuses the 'wpml_active_languages' filter #268 +* Fix Uncaught TypeError: s.split is not a function. Props Wouter Van Vliet #262 +* Fix text alignment for RTL scripts in Lingotek panel #247 +* Fix html language attribute filter on admin +* Fix cookie expiration time when set in js. Props Jens Nachtigall #271 +* Fix fatal error when a 3rd party misuses the WP_Query tax_query param. Props JanneAalto #252 +* Fix an edge case which could mess home pages on a multisite + + += 2.3.7 (2018-06-07) = + +* Pro: The Events Calendar: Fix untranslated events shown in all languages +* Avoid displaying edit links of translations of the privacy policy page to non-admin +* Fix draft created when creating a new page on multisite +* Do not prevent using the cache for home when using WP Rocket 3.0.5 or later #236 +* Fix language filter applied to wrong queries on admin side + += 2.3.6 (2018-05-17) = + +* Pro: Fix post type archive slug not translated in ACF page link fields +* WP 4.9.6: Translate the privacy policy page +* WP 4.9.6: Add the translated user descriptions to exported personal data +* Update Plugin updater to version 1.6.16 +* Fix conflict with the plugin View Admin As. Props Jory Hogeveen. #253 + += 2.3.5 (2018-05-08) = + +* Pro: Fix translated CPT slugs when one CPT name is a substring of another one. Props Steve Reimer. +* Pro: Fix canonical redirection for post types archives when the CPT slug is translated +* Pro: Fix ACF private key uselessly synchronized when the public custom field is not synchronized +* Add filter 'pll_filter_query_excluded_query_vars' +* Redirect www. to non www. when using multiple domains +* Fix Yoast SEO category sitemap not filtered by language when using multiple domains +* Fix PLL_COOKIE === false not honored when using a cache plugin. #248 +* Fix empty predefined languages list + += 2.3.4 (2018-03-27) = + +* Pro: Fix conflict with Pods related to translated slugs for custom post types +* Add Friulian to the predefined languages list +* Fix conflict (javascript error) with Gütenberg #225 +* Fix conflict on ajax requests introduced by WooCoommerce 3.3.4 +* Fix queries by 'category_name' not auto translated #238 + += 2.3.3 (2018-03-15) = + +* Pro: Fix tax query using a term sharing slugs (fix a conflict with Fusion Builder) +* Restore Polylang (free) on REST requests, while disabling the language filter as in v2.3 +* Rework auto translated query with taxonomy in different language #223 +* Synchronize Yoast SEO primary category (needs Yoast SEO 7.0+) +* Fix PHP warning introduced by Yoast SEO 7.0 #229 +* Fix tax query when using the relation 'OR' +* Fix a conflict with the combination of Barrel + WP Bakery Page Builder +* Fix broken redirect with MU domain mapping #226 +* Fix site title not translated in password change email + += 2.3.2 (2018-03-05) = + +* Pro: Fix REST requests not filtered by the requested language (introduced in 2.3). +* Pro: Fix error 404 on single posts if posts are untranslatable +* Deactivate Polylang (free) on REST requests by default. +* Fix translated terms unassigned from posts when deleting a term +* Fix auto translated query with taxonomy in different language returning empty results since WP 4.9 #223 +* Fix conflict with a homepage option of the theme Extra +* Fix warning when filtering get_pages() + += 2.3.1 (2018-02-15) = + +* Pro: Fix GET REST request with slug parameter deleting the post slug +* Fix http request with a custom query var being redirected to the home page #216 + += 2.3 (2018-01-30) = + +* Pro: Duplicating a post now duplicates untranslated terms and the featured image (if media are translatable) +* Pro: Add filter 'pll_sync_post_fields' +* Pro: Translate ACF Pro clone fields when creating a new field group translation +* Pro: Allow to share slugs when creating a post or term with the REST API +* Pro: Load asynchronously the script added on front for multiple domains and subdomains +* Pro: Fix 'lang' parameter not interpreted when the query includes 'name' +* Refactor the synchronization of metas for better synchronization and performance improvement +* Refactor the synchronization of taxonomy terms for performance improvement +* Refactor language and translations saving for performance improvement +* Refactor the synchronization of sticky posts +* Remove all languages files. All translations are now maintained on https://translate.wordpress.org/projects/wp-plugins/polylang #199 +* Refactor the list of languages to merge predefined languages, Facebook locales and fixes for W3C locales +* Automatically deactivate Polylang when activating Polylang Pro +* Disable programmatically translated post types and taxonomies in settings. Props Ulrich Pogson. #180 +* Set the cookie language in Javascript when a cache plugin is active +* Automatically remove the home page from cache when requesting the detection of the browser preferred language +* Use relative urls for the admin language filter in admin bar. #209 +* Disable auto translation of WP_Term_Query if it has a 'lang' parameter +* Don't filter REST requests by default. #211 +* Fix Yoast SEO statistics in dashboard showing only the default language. #211 +* Fix WP Rocket clearing the cache of the wrong adjacent post +* Fix random header image +* Fix home page not correctly loaded when adding a query var +* Fix: Impossible to change the language code when the language code is also a WordPress locale. + += 2.2.8 (2018-01-09) = + +* Pro: Fix: Impossible to link past events by translation in The Events Calendar +* Disallow to delete translations of the default term for all taxonomies +* Fix: Auto add pages adds WooCommerce pages in default language to menus in all languages +* Fix most used tag cloud in Tags metabox in WP4.9+. Props Pär Thernström. #208 + += 2.2.7 (2017-11-30) = + +* Fix queries by taxonomy broken since WP 4.9 +* Fix PHP notice in icl_object_id() + += 2.2.6 (2017-11-22) = + +* Pro: Fix query by post name and alternative language always returning the post in current language (when sharing slugs) +* Pro: Fix query by taxonomy and alternative language returning empty results +* Rework how translation files are loaded in ajax on front when the user is logged (in WP 4.7+) +* Add filter 'get_objects_with_no_lang_limit' +* Force loading the admin side when using WP CLI (Props chrisschrijver) +* Fix check for terms with no language not scaling +* Fix pll_count_posts not working with multiple post types +* Fix inconsistent spacing between flag and language name in language switcher parent menu item (Props Amit Tal) +* Fix spacing between flag and language name when displaying an RTL language +* Fix get_terms not accepting comma separated values for 'lang' parameter (Props Pavlo Zhukov) +* Fix possible wrong language detected in url when using subdomains (Props Pavlo Zhukov) +* Fix double escaped query + += 2.2.5 (2017-11-09) = + +* Update plugin updater class to 1.6.15 +* Add $link in cache key of links filters +* Add support for 'nav_menu' post type in wpml_object_id +* Fix conflict with Timber (introduced in 2.2.4) + += 2.2.4 (2017-10-26) = + +* Pro: Fix unknown language not redirected to default when using multiple domains +* Pro: Fix empty 'lang' query var not deactivating the language query filter +* Pro: Fix conflict with The Events Calendar and Visual Composer when used together +* Add new filter `pll_hide_archive_translation_url` #174 +* Add support for undocumented and deprecated WPML functions `wpml_object_id_filter` and `icl_get_current_language` +* Fix 'orderby' and 'order' in `wpml_active_languages`. Needs WP 4.7+ +* Fix `icl_get_languages` not returning all languages when skip_missing = 0. Props Loïc Blascos +* Fix `pll_translate_string` not working on admin #178 +* Fix PHP Warning in widget video in WP 4.9 +* Fix query using 'any' post type not filtered per language (introduced in 2.2) +* Fix untranslatable string in About metabox. Props Farhad Sakhaei +* Fix error with PHP 7.1 and Duplicate Post. Props Enea Scerba +* Fix query auto translation not active in ajax requests on frontend +* Fix query auto translation for 'postname' and 'pagename' +* Fix terms query auto translation not working for 'include' when no taxonomy is provided (WP 4.5+) + += 2.2.3 (2017-09-24) = + +* Fix editor removed on pages (introduced in 2.2.2) + += 2.2.2 (2017-09-22) = + +* Pro: Fix Duplicate post button not working when the user meta has been corrupted +* Fix PHP notice with the plugin Members #175 +* Fix page template select displayed when editing a translated page for posts +* Fix incompatibility with WP 4.8.2 (placeholder %1$s in prepare) + += 2.2.1 (2017-08-30) = + +* Pro: partially refactor REST API classes +* Pro: Fix duplicate content user meta not removed from DB when uninstalling the plugin +* Fix strings translations not removed from DB when uninstalling the plugin +* Fix incorrect translation files loaded in ajax on front when the user is logged in (WP 4.7+) +* Fix widget language dropdown removed when saving a widget (introduced in 2.2) +* Fix queries with negative values for the 'cat' parameter (introduced in 2.2 for queries made on frontend) +* Fix performance issue in combination with some plugins when the language is set from the content (introduced in 2.2) + += 2.2 (2017-08-16) = + +* Pro: Add support for the REST API +* Pro: Add integration with The Events Calendar +* Pro: Refactor ACF Pro integration for post metas and integrate term metas +* Pro: Ask confirmation if synchronizing a post overwrites an existing translation +* Pro: Separate sync post logic from interface +* Pro: Fix 'Detect browser language' option automatically deactivated +* Pro: Fix redirect to 404 when the 'page' slug translation includes non alphanumeric characters. +* Pro: Fix untranslated post type archive slug +* Pro: Fix ACF taxonomy fields not copied when the taxonomy is not translated #156 +* Pro: Fix fatal error with ACF4 +* Support a different content text direction in admin #45 +* Add support for wildcards and 'copy-once' attribute in wpml-config.xml +* Add minimal support for the filters 'wpml_display_language_names' and 'icl_ls_languages' +* Improve compatibility with the plugin WordPress MU Domain Mapping #116 +* Improve speed of the sticky posts filter #41 +* Remove redirect_lang option for multiple domains and subdomains +* Use secure cookie when using SSL +* Allow to copy/sync term metas with the filter 'pll_copy_term_metas' +* Filter ajax requests in term.php according to the term language +* Add error message in customizer when setting an untranslated static front page #47 +* Load static page class only if we are using a static front page +* Refactor parse_query filters to use the same code on frontend and admin +* Don't use add_language_to_link in filters +* Move ajaxPrefilter footer script on top +* Use wp_doing_ajax() instead of DOING_AJAX constant +* Fix queries custom tax not excluded from language filter on admin +* Fix WP translation not loaded when the language is set from the content on multisite. +* Fix the list of core post types in PLL_OLT_Manager for WP 4.7+ +* Fix post name and tag slug incorrectly sanitized for German and Danish +* Fix lang attribute in dropdowns +* Fix wpml_permalink filter #139 +* Fix WPML constants undefined on backend #151 +* Fix a conflict with the plugin Custom Permalinks #143 +* Fix menu location unexpectedly unset + += 2.1.6 (2017-07-17) = + +* Pro: fix duplicate post button not working in PHP 7.1 +* Pro: fix CPTUI untranslated labels on admin +* Adapt related posts filter to use slug instead of name to follow changes made on Jetpack server ( Props Steve Kaeser ) +* Fix PHP notices when translating CPT and custom tax titles in Yoast SEO +* Fix PHP warning when all plugins are networked activated + += 2.1.5 (2017-05-31) = + +* Add compatibility with new media widgets introduced in WP 4.8 +* Removing the language information in URL for the default language is now default +* Update plugin updater class to 1.6.12 +* Pro: fix PHP notices when duplicating the content +* Fix: test existence of `twentyseventeen_panel_count` instead of relying only on the active template +* Fix: set current property to false when removing the current-menu-item class #134 props @mowar +* Fix PHP notice when editing a term without language +* Fix possible PHP notice when deleting a category +* Fix fatal error with Gantry 5 + += 2.1.4 (2017-05-16) = + +* Pro: fix user not logged in on secondary domain when previewing changes +* Pro: fix archive links without language code in ACF link field (ACF 5.4.0+) +* Fix redirection from www subdomain to wrong language domain. +* Fix: selecting "Front page displays latest posts" in the customizer not cleaning the languages cache +* Fix accessibility of the admin language switcher + += 2.1.3 (2017-04-11) = + +* Pro: Fix translated slug of 'page' if it is translated to an empty string +* Update plugin updater class to 1.6.11 +* Strings registered with a wpml-config.xml file or WPML functions are now multiline by default +* Translate the site title in emails sent to the user +* Fix sanitize_user for specific locales +* Fix deprecation notice in Yoast SEO integration +* Fix: Clean term cache after the language has been set in mass #119 + += 2.1.2 (2017-03-09) = + +* Pro: Add filter 'pll_xdata_nonce_life' +* Pro: Fix translation of WooCommerce product attribute slug +* Pro: Fix product synchronization in WooCommerce 2.7 +* Pro: Fix error message when bulk trashing synchronized posts +* Add option to discard item spacing in the output of pll_the_languages() ( Props Ceslav Przywara ) #93 #95 +* Add as, dzo, kab, km, ml_IN, nl_BE, pa_IN, rhg, sah, ta_IN, tah, te, tt_RU to the predefined list of languages +* Update plugin updater class to 1.6.10 +* Fix: Remove the dependency to is_ssl() to detect the language in the url ( language set from the directory name ) +* Fix issue with secondary level domains +* Fix strings not translated in emails +* Fix incorrect usage of add_action() ( Props Peter J. Herrel ) #103 +* Fix wrong redirect in customizer in WP 4.7 + += 2.1.1 (2017-02-15) = + +* Pro: Add filter 'pll_enable_duplicate_media' for a fine control of automatic media duplication +* Add filter 'pll_links_model' for the links model class name +* Trim any starting ^ from modified rewrite rules +* Pro: Fix wrong count of plugins to update +* Fix slashed strings translations not saved #94 + += 2.1 (2017-01-25) = + +* Minimum WordPress version is now 4.4 +* Pro: Add support for synchronized posts (same post in multiple languages) +* Pro: Add support for custom post type UI and the Divi Builder +* Improve support of Yoast SEO (no category base and post type archive breadcrumb title) +* Move Languages menu at top level instead of submenu of the WordPress settings +* Copy the original post date when creating a translation and when the date is synchronized (Props Jory Hogeveen) #32 +* Remove hreflang attributes on paged pages and paged posts +* Add label to widget language dropdown for better accessibility (Props Lawrence Francell) #53 #56 +* Remove constants POLYLANG_URL and PLL_LOCAL_URL +* wp_get_sidebars_widgets() and is_active_sidebar() are now filtered according to widgets languages #54 +* Add functions pll_esc_html__(), pll_esc_html_e(), pll_esc_attr__() and pll_esc_attr_e() to the API (Props jegbagus) #83 +* Pro: Fix conflict between WooCommerce shop on front and translated shop base slug +* Pro: Fix $wp_rewrite search base and author_base not translated #68 +* Pro: Fix page preview does not log in the user when using subdomains +* Fix: avoid setting the language cookie on 404 pages +* Fix: rewrite rules order modified for custom post types archives +* Fix: conflict with WP All Import causing our filters to fail in "Add Media" modal when editing a post +* Fix: auto add pages not working for nav menus assigned to several locations +* Fix: Jetpack infinite scroll for multiple domains #58 #74 +* Fix: serialize error in Strings translations when balanceTags option is active #63 +* Fix: static front page preview when redirected from the languages page #49 +* Fix: Auto add pages not working for nav menus assigned to several locations +* Fix: Conflict with Woocommerce Show Single Variation +* Fix: Parent page not synchronized in Quick edit (introduced in 2.0.8) +* Fix: WPML API wpml_element_has_translations and wpml_post_language_details +* Fix: unattached media translations not in language switcher +* Fix: Conflict with WP Residence advanced search + += 2.0.12 (2016-12-19) = + +* Fix plugin not loaded first (introduced in 2.0.11) +* Fix wrong translations files loaded when the language is set from the content in WP 4.7 #76 +* Fix notice when a tax query has no terms (using EXISTS or NOT EXISTS) + += 2.0.11 (2016-12-12) = + +* Pro: Fix shared term slugs broken by a late change in WP 4.7 #73 +* Pro: Fix media taxonomies lost when creating a media translation when taxonomies sync is activated #72 +* Fix fatal error in customizer when Twenty Seventeen is activated and another theme is previewed #71 +* Fix wrong plugin language on admin if user locale is different from site locale in WP 4.7 + += 2.0.10 (2016-12-05) = + +* Add support for front page panels of Twenty Seventeen +* Remove draft posts from the language switcher even when the user is logged in +* Fix: Make argument 2 of icl_object_id optional +* Fix a conflict with the Divi theme (#67) + += 2.0.9 (2016-11-15) = + +* Fix javascript error in some ajax requests + += 2.0.8 (2016-11-14) = + +* Disable admin language feature in WP 4.7+ +* Pro: fix case where a media could lose its parent post when translated on the fly by the content duplication +* Pro: fix on the fly media created at content duplication attached to parent page instead of child page +* Fix translations input fields not populated in languages metabox when creating a new translation in WP 4.7 +* Fix possibility to delete the translations of the default category in WP 4.7 +* Fix tag search not filtered per language in Quick edit in WP 4.7 +* Fix dropdown language switcher not working for untranslated pages + += 2.0.7 (2016-10-18) = + +* Fix issues with static front pages introduced in version 2.0.6 + += 2.0.6 (2016-10-17) = + +* Pro: Fix translated paged slug not working on paged static front page +* Add support for WPML filter 'wpml_language_form_input_field' +* Fix PHP notice when using the WPML filter 'wpml_current_language' +* Fix cases where the admin language filter is not correctly taken into account +* Fix paged static front pages in plain permalinks +* Fix paged static front pages for multiple domains (#43) +* Fix warning occurring when a 3rd party plugin attempts to register anything but a string in the strings translations panel +* Fix cross domain http request for media when using multiple domains or subdomains +* Fix error 404 on pages when no language has been created yet + += 2.0.5 (2016-09-22) Five years after! = + +* Pro: Fix conflict with WPBakery Visual Composer +* Pro: Fix conflict between multiple domains SSO and FORCE_SSL_ADMIN +* Pro: Fix duplicated fields not displayed in new translation in ACF Pro 5.4+ +* Add Tibetan and Silesian to the predefined languages list +* Remove duplicated strings from the strings translations (even when they have a different name or group) +* The languages and translations of custom post types and taxonomies are no more activated by default at activation +* Allow to deactivate auto translation in secondary by setting 'lang' to an empty value +* Fix: invalidate the cache of PLL_MO ids when adding a new language +* Fix: don't filter secondary queries when editing a post in an untranslated post type + += 2.0.4 (2016-09-06) = + +* Add Gujarati to the predefined languages list +* Fix conflict with Page Builder. Other parts of the conflict are fixed in Page Builder 2.4.14 +* Fix plugins translations incorrectly loaded in WP 4.6 +* Fix error 404 on paged urls when using a non standard port + += 2.0.3 (2016-08-16) = + +* Pro: Fix PHP notice when hiding the language code in url and the language is set from subdomains +* Pro: Fix one more media being created when the duplicate media in all languages is activated (introduced in 2.0) +* Pro: Fix shared term slugs not working on PHP 7 +* Pro: Fix Polylang storing integers in some ACF Pro fields where ACF Pro stores strings +* Pro: Fix ACF Pro custom fields synchronized even when the custom fields synchronization option is deactivated (#40) +* Fix PHP notice: Undefined variable: original_value in /modules/wpml/wpml-api.php on line 168 +* Fix translations loaded too soon by plugins not correctly reloaded since WP 4.6 (#39) +* Fix: Remove the delete link for translations of the default category on PHP 7 +* Fix unescaped i18n strings in Lingotek presentation + += 2.0.2 (2016-08-03) = + +* Avoid fatal error when a 3rd party theme or plugin has a malformed wpml-config.xml file: the malformed wpml-config.xml file is simply ignored + += 2.0.1 (2016-08-02) = + +* Fix fatal error on PHP < 5.4 (introduced in 2.0) +* Fix custom flags not being loaded (introduced in 2.0) + += 2.0 (2016-08-02) = + +* Pro: Improve integration with ACF Pro +* Pro: Add support for single sign on across multiple domains or subdomains +* Pro: Add support for browser language detection when using multiple domains +* Pro: Add support for translation of the static portion of the post permalink structure +* Pro: Fix deactivated languages appearing in Yoast SEO sitemaps +* Pro: Fix impossibility to visit a deactivated language when using subdomains or multiple domains (#10) +* Pro: Fix when sharing slug on the page for posts, only one of them is accessible (#33) +* Add the possibility to use the language switcher as dropdown in menu +* Add support for custom logo introduced in WP 4.5 (#6) +* The backend current language ( PLL()->curlang ) is now equal to the language of current post or term being edited (#19) +* The sample permalink is now updated when changing the language in the Languages metabox +* Revamp the wpml-config.xml reader to use simplexml instead of our custom xml parser +* Improve support for the WPML API (including Hook API introduced in WPML 3.2) +* Add support for translation of meta titles and descriptions of custom post types and custom taxonomies in Yoast SEO +* Replace uncached functions by WPCOM VIP functions when available +* Improve compatibility with WP 4.6 +* Fix parent category wrongly assigned to post when synchronizing children categories (#21) +* Fix custom fonts not loaded when using multiple domains or subdomains +* Fix remove_accents() not working for German and Danish (#24) +* Fix incorrect static front pages urls on backend +* Fix impossible to directly enter the page number in strings translation table (introduced in 1.9.3) +* Fix conflict with WP Sweep (needs WP Sweep 1.0.8+) +* Fix potential performance issue by querying only taxonomies to show in quick edit to filter the category checklist +* Fix conflict (database error) with ReOrder-posts-within-categories plugin +* Fix languages per page option not saved + += 1.9.3 (2016-06-28) = + +* Pro: Allow to add slashes in url slugs translations +* Pro: Fix archive links not using translated slugs +* Pro: Fix visitor being redirected to 404 if his browser preference is set to an inactive language +* Fix strings translations table always back to page 1 when submitting the form (#14) +* Fix get_pages( array( 'lang' => '' ) ) not querying all the languages +* Fix switching the admin language filter can override the static front page settings (#16) + += 1.9.2 (2016-06-06) = + +* Pro: fix unreachable hierarchical custom post type posts when they are sharing slugs across languages +* Fix missing argument 3 in icl_t +* Fix conflict with WooCommerce product variations + += 1.9.1 (2016-05-23) = + +* Pro: add compatibility with Beaver Builder +* Pro: fix media wrongly created when adding a new media translation +* Add azb, ceb, de_CH_informal, es_GT, mr, nl_NL_formal to the predefined list of languages +* Fix the language switcher not linking to media translations for anonymous visitors + += 1.9 (2016-04-27) = + +* Pro: add the possibility to translate custom post types slugs, taxonomies slugs and more +* Pro: add the possibility to share the same post or term slug across languages +* Pro: add the possibility to duplicate the content when creating a new translation +* Pro: add the possibility to create all translations at once when uploading a media +* Pro: add the possibility to disable a language +* Add license and update management +* Add inline docs for all filters and actions +* When possible, the rel alternate hreflang now display only the language code (without the country code) +* When combined with flags in the language switcher, wrap the language name inside tags +* Add customizer selective refresh support for the language switcher widget ( needs WP 4.5+ ) +* Fix dynamic options of the language switcher widget not working in the customizer +* Fix possible error 404 on page shortlink when using subdomains or multiple domains +* Fix get_adjacent_post() and wp_get_archives() for untranslated post types ( needs WP 4.4+ ) +* Fix language homepage urls not present in Yoast SEO sitemap (when the homepages display posts) + += 1.8.5 (2016-04-03) = + +* Revert from $_SERVER['PHP_SELF'] to $_SERVER['SCRIPT_FILENAME'] to detect if the user is on login/register/signup page +* Fix incompatibility introduced by WP 4.5 in Edit single taxonomy term screen +* Fix existing post overridden when creating a language and a conflicting plugin sets the global $post on languages pages + += 1.8.4 (2016-03-06) = + +* Revert canonical redirection of static front page when combining plain permalinks + default language hidden in url (introduced in 1.8.2) + += 1.8.3 (2016-03-04) = + +* fix: All pages are redirected to the home page on some installations (introduced in 1.8.2) + += 1.8.2 (2016-03-02) = + +* Add support for the 'wpml_get_default_language()' function from the WPML API +* Stop blocking saving settings when errors are detected (invalid domains) +* Use publicly_queryable => true instead of public => true for the language taxonomy (WP 4.5+) +* fix: PHP notice when pll_default_language() is called before a language is created +* fix: PHP notice undefined property PLL_Language::$page_on_front +* fix: canonical redirection of static front page when combining plain permalinks + default language hidden in url +* fix: YARPP compatibility broken in v1.8 +* fix: Remove the delete link for translations of the default category (introduced back by WP 4.3) +* fix: settings not displayed with WP 4.1 or older + += 1.8.1 (2016-01-31) = + +* Update the list of Facebook locales used for Opengraph support with Yoast SEO and Jetpack +* fix: secondary query with translated post type and untranslated taxonomy mixes languages (introduced in 1.8) +* fix: issue with paged static front page when hiding the default language in url +* fix: potential issue with cache after synchronizations +* fix: trailing slash added to canonical home url outputted by Yoast SEO when using default permalinks + += 1.8 (2016-01-19) = + +* Minimum WordPress version is now 4.0 +* Add ary, bn_BD, en_ZA, es_AR, fr_CA and fr_BE to the predefined languages list +* Adopt WordPress coding standards +* New structure for translated posts and terms (=> several methods of PLL_Model are deprecated). +* Revamp the management of the static front page and page for posts +* Improve performance for navigation menus with a lot of pages +* The Polylang and WPML API are now loaded when 'plugins_loaded' is fired (on frontend only if at least one language has been defined) +* Add 'pll_get_post_translations()' and 'pll_get_term_translations()' to the API +* Add filter 'pll_cookie_expiration' to change the cookie expiration time +* Add support for 'wpml_get_language_information()' function from the WPML API +* The default language is now managed directly from the languages list table +* Various accessibility improvements +* It is now possible to choose the languages flags from the available list (custom flags on frontend still work as previously) +* Revamp the settings page (now a list table with inline configuration) +* Add an option to remove all data when uninstalling the plugin +* Add test of subdomains and domains accessibility +* Add post state for translations of the front page and posts page +* Add better support of the customizer menus introduced in WP 4.3 +* Media taxonomies (created by 3rd party plugins) are now filtered by language when editing a media +* Synchronization of taxonomies (created by 3rd party plugins) and meta are now enabled for media +* The 'hreflang' tag now refers to the locale instead of the 2-letters language code +* Workaround for WordPress locales not being W3C valid (see #33511) +* Workaround a bug in Nextgen Gallery causing redirect on album +* Add compatibility with Duplicate Post plugin to avoid duplicated post keeping the link to translations +* Add compatibility with Jetpack Related Posts +* fix: incorrect rewrite rules after changing how the language is set (need to flush rewrite rules after this) +* fix: password protected pages don't work on multiple domains +* fix: ensure that the page parent is in the correct language when using bulk edit +* fix: is_tax set on category and post tags archives when it should not +* fix: automatically added new top-level pages to menus are not filtered by language +* fix: nav menus locations are messed when changing the default language +* fix: error 404 for untranslated taxonomies pages +* fix: single posts and pages links do not include the language code when using the default permalinks and forcing the language code in url +* fix: missing trailing slash on home url when using default permalinks or a static front page +* fix: sticky visibility is copied to new translation only if the synchronization is activated +* fix: remove "» Languages » [language name]" from the feed title +* fix: spaces are not honored when searching strings translations +* fix: default language not set and terms translations not correctly imported when using WordPress Importer +* fix: the browser language detection does not differentiate 'en_US' and 'en_GB' +* fix: non alphanumeric characters query vars values lead to an infinite redirection loop on static front pages +* fix: user profile not saved for a language when the language code contains a "-" +* fix: non translated posts page always link to the static front page even when they should not +* fix: remove hreflang="x-default" when using one domain per language +* fix: deprecated function notice in WP 4.5 alpha +* fix: wrong url for attachments when media are translated and using subdomains +* fix: wrong url for unattached attachments when using subdirectories (since WP 4.4) +* fix: wrong url scheme for custom flags + += 1.7.12 (2015-11-13) = + +* The language taxonomy is now public for compatibility with WP 4.4 +* fix: nav menus locations are not correctly populated in customizer in WP 4.4 +* fix: the termmeta table was still deleted at upgrade +* fix: fatal error when using the argument 'post_id' in 'pll_the_languages()' (introduced in 1.7.11) [props EKesty](https://wordpress.org/support/topic/bug-on) +* fix: potential notice in 'pll_the_languages()' [props mattkeys](https://wordpress.org/support/topic/bug-on) + += 1.7.11 (2015-10-15) = + +* fix: conflict with GET ajax requests sent by the jquery method load +* fix: notice in frontend-nav-menu.php at line 211 (introduced in 1.7.10) [props Jesse Graupmann](https://wordpress.org/support/topic/warning-and-notice-on-upgrade) +* fix: the parent list in page attributes metabox is not in the correct language (introduced in 1.7.10) +* fix: error 404 for attachments +* fix: the language switcher is not displayed when combining "Forces link to front page" and "Hides languages with no translation" + += 1.7.10 (2015-09-28) = + +* Add Occitan translation contributed by [Cédric Valmary](http://www.totenoc.eu/) +* Add de_DE_formal, en_NZ, es_CO, hy, oci, ps and tl to the predefined languages list +* Add the filter 'pll_predefined_languages' and the actions 'pll_language_edit_form_fields' and 'pll_language_add_form_fields' +* the termmeta table (used in Polylang < 1.2) is no more deleted when uninstalling the plugin (as it will soon be included in WP) +* fix: prevent creating a media translation if one already exists +* fix: Attempt to translate the nav menus for themes registering a theme location but not using it in wp_nav_menu() +* fix: Jetpack infinite scroll +* fix: issue with terms languages when two languages have the same name +* fix: notices when deleting a tag and Lingotek is active +* fix: the languages cache is not cleaned when updating the site home url +* fix: conflict with the theme Ambition +* fix: front page canonical url displayed by Yoast SEO +* fix: typo in options definition at install [props null.bit](https://wordpress.org/support/topic/suggestions-for-two-new-filters?replies=5#post-7466159) +* fix: error when adding a term in a non-translated taxonomy + += 1.7.9 (2015-08-17) = + +* Minimum WordPress version is now v3.9 +* Add: hreflang="x-default" on front page when the default language code is not hidden in urls +* fix: remove hreflang links in html head section of paged archives to please Google +* fix: conflict with WPSEO sitemap caching when using multiple domains. [props Junaid Bhura](https://wordpress.org/support/topic/wp-seo-sitemap-and-translation-subdomain-issue?replies=8#post-7113817) +* fix: change the order of strings translations columns for better display on mobile devices in WP 4.3 +* fix: various issues with nav menus and customizer in WP 4.3 +* fix: correctly disallow unchecking both show names and show flags in the language switcher form + += 1.7.8 (2015-07-21) = + +* fix: conflict with PHP < 5.4 introduced in 1.7.7 + += 1.7.7 (2015-07-20) = + +* Add Romanian translation contributed by uskro +* Add Japanese translation contributed by [Eiko Toda](http://www.eikotoda.com) +* Update French translation contributed by [fxbenard](http://fxbenard.com/) +* The language locale is now validated with the same pattern as in WP 4.3. See #28303 +* fix: make sure that the language switcher never finds translations for untranslated post types (could occur when the post type was previously translated) +* fix: display the default category according to the admin language filter in settings->writing +* fix: flushing rewrite rules at network activation and de-activation is back. [props RavanH](https://polylang.wordpress.com/2015/06/10/polylang-1-7-6-and-multisite/comment-page-1/#comment-1138) +* fix: avoid a conflict with WP Super Cache preloading (loading 'polylang_mo' posts which are 404). [props ecdltf](https://wordpress.org/support/topic/polylang_mo-and-404s-take-2) +* fix: customizer menus issues introduced by changes in WP 4.1 +* fix: strings translations are not saved when pressing enter +* fix: it is not possible to de-activate the translation for custom post types and taxonomies from wpml-config.xml +* fix: conflict with plugins using stringified json in ajax requests + += 1.7.6 (2015-06-10) = + +* Add Galician translation contributed by [Toño Calo](http://fedellar.wordpress.com/) +* fix: incorrect post type archive link for untranslated post types +* fix: notices in wp-import.php +* fix: avoid flushing rewrite rules at network activation and de-activation +* fix: the note below the category list table displays the default category according to the admin language filter +* fix: wrong future posts permalinks +* fix: deleting a media translation deletes the file too +* fix: when using persistent object cache, get_terms is not always filtered by the correct language on admin side +* fix: it is possible to create two categories having the same translation +* fix: fatal error when using the dropdown language switcher in WP < 4.1 + += 1.7.5 (2015-05-11) = + +* Add 'pll_languages_list' filter +* fix: warning when a plugin calls 'icl_object_id' with an untranslated post type (seen in ACF 4.4.1) +* fix: the language is not correctly set from the url when using PATHINFO permalinks (introduced in 1.6!) +* fix: notice when a search is filtered by a taxonomy term in a different language + += 1.7.4 (2015-05-03) = + +* fix: translated taxonomies and post types from wpml-config.xml are not filtered on frontend (introduced in 1.7.2) +* fix: WPML strings translations not always loaded (introduced in 1.7) +* fix: $.ajaxPrefilter() may not work as expected [props ScreenfeedFr](https://wordpress.org/support/topic/ajaxprefilter-may-not-work-as-expected) +* fix: can't hide the language code for the default language when using subdomains +* fix: incorrect static front page url when hiding the default language information +* fix: an untranslated posts page may display posts in all languages +* fix: javascript error when changing the language of a hierarchical post type from the languages metabox in WP 4.2 +* fix: subdomains urls are malformed when the main site uses www. +* fix: suggest tags are not filtered in quick edit +* fix: parent page dropdown list not filtered in quick edit + += 1.7.3 (2015-04-11) = + +* the transient 'pll_languages_list' now stores an array of arrays instead of an array of PLL_Language objects +* fix: fatal error for users hosted at GoDaddy (due to PLL_Language objects stored in a transient) +* fix: additional query vars are removed from home page +* fix: categories are not filtered by the admin language switcher in posts list table (introduced in 1.7) +* fix: when using multiple domains, the domain url is lost when modifying the language slug +* fix: the queried object is incorrectly set for author archives (introduced in 1.6.5) +* fix: notice when a nav menu assigned to a translated nav menu location has been deleted +* fix: no canonical redirection when using pretty permalinks and querying default permalinks + += 1.7.2 (2015-03-23) = + +* fix: comments are filtered for posts in a post type not managed by Polylang +* fix: translated static front page don't work when setting PLL_CACHE_HOME_URL to false (introduced in 1.7) +* fix: the query for taxonomies on custom post types is broken (when adding the language code to the url) + += 1.7.1 (2015-03-20) = + +* fix: wrong redirection when using a static front page and replacing the page name by the language code (introduced in 1.7) + += 1.7 (2015-03-19) = + +* Minimum WordPress version is now v3.8 +* Add new languages to the predefined languages list: Swiss German, Hazaragi +* Add compatibility with nested tax queries introduced in WP 4.1 +* Add compatibility with splitting shared terms to be introduced in WP 4.2 +* Add the possibility to change the domain in the default language when using multiple domains (avoids a conflict with the domain mapping plugin) +* Add the possibility to set the language from the code in url when using default permalinks +* Adding the language code in url is now default at first activation (should improve the out of the box compatibility with other plugins and themes) +* Add new language switcher option to hide a language with no translation +* pll_the_languages() now outputs the js code to handle language change in dropdown list (as done by the widget) +* Improve performance by using base64 encoded flags + various slight optimizations +* Improve protection against chained redirects +* The find posts list is now filtered per media language when clicking on attach link in Media library +* Copy alternative text when creating a media translation +* The category checklist in quick edit is now filtered per post language instead of admin language filter +* Quick and bulk language edit don't break translations anymore if the new language is free +* Make it impossible to change the language of the default categories +* Make sure that a default category defined in settings->writing is translated in all languages +* Tweak css for mobiles in add and edit term form +* Tweak the query getting the list of available posts in the autocomplete input field in the post languages metabox +* fix: after adding a term translation, need to refresh the page before adding a new term +* fix: term translations rows are not modified in list table when a term is added / deleted or inline edited +* fix: post translations rows are not modified in list table when a post is inline edited +* fix: using brackets in language name breaks strings translations +* fix: quick edit may conflict with other plugins +* fix: impossible to use several dropdown languages widgets +* fix: pll_the_languages() may display a dropdown with empty options +* fix: the categories widget does not work correctly with dropdown +* fix: autosave post always created after manual save +* fix: tax query not filtered by language when using 'NOT IN' operator on a translated taxonomy +* fix: incorrect translation url for searches filtered by taxonomy +* fix: backward incompatibility for edited_term_taxonomy action introduced in WP 4.2 +* fix: the home link may be incorrect on MS Windows +* fix: tags in wrong language may be assigned when bulk editing posts in several languages +* fix: tags created when bulk editing posts are not assigned any language +* fix: Illegal string offset 'taxonomy' introduced in v1.6.5 +* fix: Undefined property: WP_Query::$queried_object_id when calling pll_the_languages(array('raw' => 1)) in a function hooked to 'wp'. props [KLicheR](https://wordpress.org/support/profile/klicher) +* fix: Notice in admin.php when used with MailPoet plugin + += 1.6.5 (2015-02-18) = + +* Add new correspondences between WordPress locales and Facebook locales (for WPSEO and Jetpack users) +* fix: quick draft posts are always assigned the default category in the default language +* fix: Notice: Undefined offset: 0 in wp-includes/query.php introduced in WP 4.1 +* fix: is_tax and is_archive are not correctly set when a custom taxonomy term is queried +* fix: conflict introduced by WPSEO 1.7.2+ + += 1.6.4 (2015-02-01) = + +* Add es_MX to predefined languages list +* Add compatibility with WordPress SEO sitemaps for multiple domains and subdomains +* fix: a new post is assigned the wrong (untranslated) default category if no category is assigned by the user +* fix: the home links now have the right scheme even if PLL_CACHE_HOME_URL is not set to false +* fix: fatal error when using old versions of WPSEO (I should do what I tell other to do!) +* fix: strings translations are not switched when using switch_to_blog + += 1.6.3 (2015-01-09) = + +* Add Georgian translation contributed by [Tours in Georgia](http://www.georgia-tours.eu/) +* fix: WXR export does not include the language of untranslated terms (will now work only for newly saved terms) +* fix: better cleaning of DB when translated objects are deleted +* fix: incorrect (ajax) translations links when modifying a term language +* fix: warning: Illegal string offset 'taxonomy' introduced by the combination of WP 4.1 and some plugins. + += 1.6.2 (2014-12-14) = + +* fix: bugs and inconsistencies compared to WPML in 'icl_get_languages' (should fix a conflict with Avada) +* fix: https issue +* fix: stop displaying an error when adding en_US as new language (translation not downloaded) +* fix: infinite redirect loop on (unattached) attachment links +* fix: impossible to add tags in post quick edit (introduced in 1.5) +* fix: the customizer does not land to the right page when cumulating: static front page + page name in url + default language code not hidden +* fix: read parent theme wpml-config.xml before child theme +* fix: add protection to avoid empty language +* fix: page preview link again + += 1.6.1 (2014-11-19) = + +* Add Brazilian Portuguese translation contributed by [Henrique Vianna](http://henriquevianna.com/) +* Improve compatibility with Types: allow custom fields to be populated when creating a new translation +* Make it impossible to remove the translations of the default category +* Fix: possibility to add a path when using multiple domains (same path for all languages) broken since v1.5.6 +* Fix: preview link for non default language when using multiple domains +* Fix: error displayed when setting the static front page and only one language has been defined +* Fix: revert changes on rewrite rules with front introduced in 1.6 +* Fix: conflict with WordPress SEO when no language has been created + += 1.6 (2014-10-27) = + +* Add Croatian translation contributed by Bajro +* Add new languages to predefined languages list: Azerbaijani, English (Australia), English (UK), Basque +* Add flag in front of the language select dropdown for posts and terms +* Add widget text translation +* Add opengraph support for locale and translations when WordPress SEO or Jetpack are activated +* Add error message if attempting to assign an untranslated page as static front page +* Add 'pll_sanitize_string_translation' filter to sanitize registered strings translations when saved +* Fix: change the en_US flag to US flag. The UK flag is now associated to en_GB +* Fix: change Belarusian locale from be_BY to bel to in agreement with translate.wordpress.org +* Fix home pages duplicate urls when using domains or subdomains +* Fix rewrite rules with front +* Fix: terms are always in default language when created from post bulk edit + += 1.5.6 (2014-10-11) = + +* Fix: the admin language filter is not active for paginated taxonomy in nav menu admin panel +* Fix: wrong redirection if a domain is a substring of another domain (ex: mysite.com and mysite.co) +* Fix: impossible to translate numeric values in options defined in wpml-config.xml +* Fix: call to undefined method PLL_Links::get_translation_url() with Avada theme +* Fix: manage_{$this->screen->taxonomy}_custom_icolumn is a filter and not an action + += 1.5.5 (2014-09-10) = + +* Fix: missing argument 4 in icl_translate +* Fix: conflict with Vantage theme +* Fix: possible issue with cookie domain on 'localhost' +* Fix: filtering string translations does not work when the group name contains a space +* Fix: Possible 404 error for attachments +* Fix: PHP notice when a shared term is not translated in all taxonomies + += 1.5.4 (2014-08-13) = + +* Add new API functions: pll_get_post_language, pll_get_term_language, pll_translate_string +* Add better compatibility with Jetpack 3 +* Fix: attachments don't get any language when uploaded from frontend +* Fix: authors cannot create tags +* Fix: too restrictive capability checks for some edge cases +* Fix: conflict with WPSEO: taxonomy metas cannot be saved + += 1.5.3 (2014-07-12) = + +* Add: Capability check before creating links in post list table +* Add: Possibility not to cache languages objects with option PLL_CACHE_LANGUAGES (for GoDaddy users) +* Fix: Saving a header or a background in menu Appearance resets nav menus locations (introduced in 1.5) +* Fix: sub-sub-options and deeper levels defined in wpml-config.xml are not translated +* Fix: Fatal error when creating a new site when Polylang is network activated (introduced in v1.5.1) +* Fix: Admin language forced to English when activating Polylang (before creating any new language) +* Fix: 'pll_count_posts' second parameter not taken into account +* Fix: 'edit-post' and 'create-posts' capabilities are not differentiated when saving a post + += 1.5.2 (2014-06-24) = + +* Fix: Revert post translations terms cleaning introduced in 1.5 as it seems to cause problems +* Fix: Impossible to delete a biographical info (introduced in 1.5) +* Fix: Security issue reported by [Gregory Viguier](http://www.screenfeed.fr/) + += 1.5.1 (2014-06-19) = + +* Add: filter 'pll_settings_tabs' and action 'pll_settings_active_tab_{$tab}' +* Add: possibility to add a path when using multiple domains (same path for all languages) +* Fix: Bad redirection if /language/ is added to urls (introduced in 1.5) +* Fix: Nav menu locations are not saved in customizer (introduced in 1.4) +* Fix: Unable to unset nav menu locations +* Fix: Incorrect link for date archives in language switcher (introduced in 1.5) +* Fix: Fatal error when using featured content in Twenty Fourteen +* Fix: Posts bulk edit broken (introduced in 1.5) +* Fix: Polylang does not play nice with switch_to_blog +* Fix: Warning: reset() expects parameter 1 to be array, null given in admin-filters-columns.php on line 81 + += 1.5 (2014-05-29) = + +* Add Ukrainian translation contributed by [http://getvoip.com/](http://getvoip.com/) +* Refresh translation metaboxes (again): now translated posts are chosen from an autocomplete input field +* Categories and post tags translations are also chosen in an autocomplete input field +* Better error management on languages pages +* Use Dashicons instead of Icomoon icons for WP 3.8+ +* Check if translated post is readable by the current user before displaying the language switcher +* Minimum Twenty Fourteen version is now 1.1 +* Code cleaning +* Add support for Quick draft introduced in WP 3.8 +* Add support for object cache plugins for recent posts and recent comments widgets +* Add support for pages with modified query in the language switcher (ex: when multiple post types queried on the same page) +* Add new API functions: pll_languages_list, pll_set_post_language, pll_set_term_language, pll_save_post_translations, pll_save_term_translations, pll_count_posts +* Add new filter pll_the_languages_args +* Add support for ICL_LANGUAGE_CODE == 'all' on admin side +* Fix: Galician flag +* Fix: static page on front pagination is broken +* Fix: search url may be broken +* Fix: PHP notice in icl_get_languages +* Fix: more robust way of detecting language in url when using directory +* Fix: delete translations terms orphans in database +* Fix: inconsistent behavior when setting page on front from customizer +* Fix: deleting a category assigns posts to wrong default category +* Fix: quick edit breaks synchronization +* Fix: some security issues + += 1.4.5 (2014-04-19) = + +* Fix: Notice when combined with WPSEO 1.5+ +* Fix: Impossible to disable a widget language filter once set (introduced in 1.4.4) +* Fix: Unexpected redirection of the homepage with language code when permalink structure has no trailing slash (introduced in 1.4.4) +* Fix: Some installs lead to wrong redirection when using domains (introduced in 1.4.4) +* Fix: Possible infinite redirection while previewing posts (introduced in 1.4.4) +* Fix: Uploaded medias don't get a language since WP 3.9 +* Fix: Compatibility with Twenty Fourteen Ephemera widget in the version shipped with WP 3.9 + += 1.4.4 (2014-04-09) = + +* Add: Compatibility with widgets customizer introduced in WP 3.9 +* Fix: No post in translation dropdown after switching the language in edit post (introduced in 1.4.3) +* Fix: No canonical redirection when there is no language code in url and the language code is not hidden for the default language +* Fix: Suppress language cookie when using multiple domains + += 1.4.3 (2014-03-22) = + +* Add: Serbian translation contributed by Sinisa +* Add: Myanmar translation contributed by Sithu Thwin +* Fix: comment form redirects to wp-admin when using multiple domains or subdomains. +* Fix: fatal error with old versions of PHP (tested on PHP 5.2.4) +* Fix: Bad gateway experienced by users hosted by wpengine.com +* Fix: links got from tiny MCE link button are filtered with admin language filter instead of current post language +* Fix: possibly wrong redirection in check_language_code_in_url when using multiple domains or subdomains + += 1.4.2 (2014-02-24) = + +* Add: check multiple post types in PLL_Model::count_posts +* Fix: error 404 on category links when setting the language by content (introduced in 1.4.1) +* Fix: PHP notices in frontend-nav-menu.php with Artisteer themes +* Fix: decrease the memory usage of untranslated posts list +* Fix: home page not correctly redirected to canonical when using page on front and page name is kept in url + += 1.4.1 (2014-02-16) = + +* Add: Czech translation contributed by [Přemysl Karbula](http://www.premyslkarbula.cz) +* Fix: the displayed language is not correct in quick edit for categories and post tags +* Fix: the language switcher does not display the correct link for translated parent categories if only children have posts +* Fix: 3rd parameter of icl_object_id is not optional +* Fix: issue when combining multiple domains and browser detection -> the combination is now forbidden +* Fix: conflict Shiba Media Library: link between media translations is lost when using media quick edit +* Fix: notice when using taxonomies in wpml-config.xml +* Fix: incorrect post format link +* Fix: Twenty Fourteen Ephemera widget strings are not translated + += 1.4 (2014-01-22) = + +* Add Traditional Chinese translation contributed by [香腸](http://sofree.cc/) +* Minimum WordPress version is now v3.5 +* Refresh translations metaboxes: now translated posts are chosen in a dropdown list +* Check if translated archives for category, tag and post format are empty before displaying the language switcher +* Add specific management of translated featured tag in Twenty Fourteen +* Add the possibility not to cache homepage urls with option PLL_CACHE_HOME_URL (for users having several domains). +* The function get_pages is now filtered by language +* Ajax requests on frontend are now automatically detected. It is no more necessary to set 'pll_load_front' :) +* Various performance improvements +* 'pll_get_post_types' and 'pll_get_taxonomies' filters must be added *before* 'after_setup_theme' is fired +* Pre 1.2 data will be removed from DB at first upgrade at least 60 days after upgrade to 1.4 +* Removed some duplicate code between admin and frontend +* Bug correction: incorrect pagination when using domains or subdomains +* Bug correction: post format link not translated +* Bug correction: impossible to use child terms with same name in hierarchical taxonomies +* Bug correction: the terms list table is filtered according to new translation language instead of admin language filter + += 1.3.1 (2013-12-13) = + +* Bug correction: fatal error on settings page if a static front page without language is set +* Bug correction: wrong home url when using different domains per language + += 1.3 (2013-12-11) = + +* Refresh admin UI for better look in WP 3.8 and more dynamic comportment +* The "Detect browser language" option does now also controls returning visits (based on cookie). +* Improved performance by optimizing some queries for WP 3.5+ +* The user biography in default language is now stored in default WordPress usermeta +* Add language parameter in API function pll_home_url and allow to call it on admin side +* Calling 'get_terms' with the 'lang' parameter now uses a cache object per language +* Bug correction: conflict with unstranslated taxonomies +* Bug correction: possible malformed translation archive url in language switcher +* Bug correction: a wrong language may be displayed in quick edit dropdown +* Bug correction: it is possible to add multiple translations (in the same language) for a single taxonomy term +* Bug correction: non public post types and taxonomies are visible in Polylang settings +* Bug correction: the language is always chosen from cookie (or browser preferences) in some installations +* Bug correction: Firefox language preference is not recognized when comparison is made on locale (instead of ISO 639-1 language code) +* Bug correction: incorrect tax_query in PLL_Auto_Translate + += 1.2.4 (2013-11-28) = + +* Better support for theme customizer +* Bug correction: admin bar search does not filter by language +* Bug correction: possible conflict on secondary query when querying taxonomies or single page +* Bug correction: post type is not included in url when editing or adding a term translation +* Bug correction: various warnings and PHP notices + += 1.2.3 (2013-11-17) = + +* Avoid fatal error when upgrading with Nextgen Gallery active +* Bug correction: menus locations of non default language are lost at theme deactivation +* Bug correction: impossible to set menus locations of non default language in some specific cases +* Bug correction: bbpress admin is broken + += 1.2.2 (2013-11-14) = + +* Updated Polish translation thanks to [Bartosz](http://www.dfactory.eu/) +* Delay strings translations upgrade from 'wp_loaded' to 'admin_init' to avoid fatal error when wp-ecommerce is active +* Remove Jetpack infinite scroll compatibility code as it seems useless with new Polylang 1.2 code structure +* Bug correction: fatal error when doing ajax on frontend +* Bug correction: ICL_LANGUAGE_CODE incorrectly defined when doing ajax on frontend +* Bug correction: ['current_lang'] and ['no-translation'] indexes disappeared from pll_the_languages raw output +* Bug correction: invalid argument supplied for foreach() in /polylang/include/mo.php on line 57 +* Bug correction: cookie may not be correctly set +* Bug correction: languages columns may not be displayed in custom post types and custom taxonomies tables + += 1.2.1 (2013-11-11) = + +* Update badly encoded Latvian translation +* Suppress one query in PLL_WPML_Config when not in multisite +* Bug correction: strings translations are not correctly upgraded +* Bug correction: nav menus locations are not correctly upgraded for non default language + += 1.2 (2013-11-10) = + +This version does include important changes in database. More than ever, make a database backup before upgrading + +* Add Arabic translation contributed by [Anas Sulaiman](http://ahs.pw/) +* Major rewrite with new structure +* Change the language and translations model from meta to taxonomy (no extra termmeta table created anymore) +* Move the strings translations from option to a custom post type +* Add support for language code in subdomain and for one different domain per language (experimental) +* Add support of WordPress Importer plugin. Export must have been done with Polylang 1.2+ (experimental) +* Add support for theme navigation customizer (was de-activated by Polylang since WP 3.4) +* Request confirmation for deleting a language +* Better management of default category for each language +* Now check if date and post type archives are translated before displaying the language switcher +* Update management of the 'copy' action of the custom fields section in wpml-config.xml +* Add support for ICL_LANGUAGE_CODE and ICL_LANGUAGE_NAME of the WPML API on admin side +* Add support of WPSEO custom strings translations when the language is set from content +* Modify admin language filter for valid html and better visibility +* Synchronization is now disabled by default (due to too much conflicts / questions on the forum) +* Include rel="alternate" hreflang="x" selflink per google recommendation +* Improve inline documentation +* Bug correction: wrong datatype for second argument in polylang/include/auto-translate.php (introduced in 1.1.6) +* Bug correction: same id is used for all language items in menu +* Bug correction: wpml-config.xml file not loaded for sitewide active plugins on network installations +* Bug correction: page parent dropdown list (in page attributes metabox) not correctly displayed when switching from a language with empty list + += 1.1.6 (2013-10-13) = + +* Add the possibility to display the upgrade notice on plugins page +* Bug correction: Illegal string offset 'taxonomy' in polylang/include/auto-translate.php +* Bug correction: user defined strings translations are not loaded on admin side +* Bug correction: untranslated post types are auto translated +* Bug correction: tags are not added to post when the name exists in several languages and they are not translations of each other + += 1.1.5 (2013-09-15) = + +* Add compatibility with Aqua Resizer (often used in porfolio themes) +* Add support of 'icl_get_default_language' function from the WPML API +* Remove the 3 characters limitation for the language code +* Change default names for zh_CN, zh_HK, zh_TW +* Bug correction: urls are modified in search forms + += 1.1.4 (2013-08-16) = + +* Add simplified Chinese language contributed by [Changmeng Hu](http://www.wpdaxue.com) +* Add Indonesian language contributed by [ajoull](http://www.ajoull.com/) +* Bug correction: nav menu locations are lost when using the admin language filter +* Bug correction: the cookie is not set when adding the language code to all urls (introduced in 1.1.3) + += 1.1.3 (2013-07-21) = + +* Add Venetian language contributed by Michele Brunelli +* Bug correction: wrong rewrite rules for non translated custom post type archives +* Bug correction: 'post_id' parameter of pll_the_languages does not work +* Bug correction: warning in wp_nav_menu_objects with Artisteer generated themes +* Bug correction: warning when used together with theme my login plugin +* Bug correction: language slug is modified and translations are lost when creating a nav menu with the same name as a language + += 1.1.2 (2013-06-18) = + +* Posts and terms now inherit parent's language if created outside the standard WordPress ui +* Improve the compatibility with the plugins Types and The Events Calendar, and again with WordPress SEO +* Improve performance +* Improve html validation +* Add 'raw' argument to 'pll_the_languages' +* Add the filter 'pll_translation_url' +* Bug correction: no language is set for a (translated custom taxonomy) term when added from a (non translated) custom post type edit page +* Bug correction: warning if 'get_terms' is called with a non-array 'include' argument (introduced in 1.1.1) +* Bug correction: warning if the menu language switcher has nothing to display + += 1.1.1 (2013-05-20) = + +* Move nav menu language switcher split from 'wp_nav_menu_objects' to 'wp_get_nav_menu_items' filter +* Add the filter 'pll_redirect_home' +* Automatically translate ids in 'include' argument of 'get_terms' (useful for the menus in the Suffusion theme) +* Add compatibility with Jetpack infinite scroll +* Bug correction: rtl text direction not set when adding the language code to all urls (introduced in 1.1) +* Bug correction: hide again navigation panel in theme customizer as it still doesn't work +* Bug correction: is_home not set on translated page when searching an empty string +* Bug correction: fatal error when creating a post or term from frontend (introduced in 1.1) +* Bug correction: attachments may load a wrong language when media translation was enabled then disabled +* Bug correction: warning when querying posts before the action 'wp_loaded' has been fired (in auto-translate.php) +* Bug correction: potential issue if other plugins use the filter 'get_nav_menu' +* Bug correction: interference between language inline edit and search in admin list tables +* Bug correction: auto-translate breaks queries tax_query when the 'field' is set to 'id' +* Bug correction: search is not filtered by language for default permalinks (introduced in 1.1) +* Tests done with WP 3.6 beta 3 and Twenty thirteen + += 1.1 (2013-05-10) = + +* When adding the language to all urls, the language is now defined in (plugins_loaded, 1) for better compatibility with some plugins (WordPress SEO) +* When querying posts and terms, ids are now automatically translated +* Add the possibility to group string translations +* Add the possibility to delete strings registered with 'icl_register_string' +* Move the option 'polylang_widgets' in general polylang options +* Better integration of the multilingual nav menus (everything is now integrated in the menus page of WordPress +* The language switcher is now a menu item which can be placed everywhere in a nav menu +* Posts or terms created from frontend are now assigned the current language (or another one if specified in the variable 'lang') +* Bug correction: continents-cities-xx_XX.mo not downloaded +* Bug correction: a gzipped 404 page is downloaded when a mo file does not exist on WordPress languages files repository +* Bug correction: post_date_gmt not synchronized together with post_date +* Tests done with WP 3.6 beta 2 and Twenty thirteen + += 1.0.4 (2013-04-08) = + +* Add Estonian translation contributed by [Ahto Naris](http://profiles.wordpress.org/ahtonaris/) +* Now compatible with languages files stored in wp-content/languages/themes +* Bug correction: page preview does not work when adding the language code to all urls +* Bug correction: error when a post type or taxonomy label is not a string +* Bug correction: admin text section of wpml-config.xml (introduced in 1.0.3) +* Bug correction: infinite redirect loop when querying an unattached media and the language code is added to all urls +* Bug correction: the text direction is not set from Polylang options when the language code is added to all urls +* Bug correction: get_adjacent_post is filtered by language even for post types without language +* Bug correction: the home url is not not in the correct language in wp-login.php +* Bug correction: the language is not correctly set when using date and name permalinks (introduced in 1.0.3) + += 1.0.3 (2013-03-17) = + +* Add Catalan translation contributed by [Núria Martínez Berenguer](http://nuriamb.capa.webfactional.com) +* Add Ukrainian translation contributed by [cmd soft](http://www.cmd-soft.com/) +* Improve compatibility with WordPress SEO (sitemap for categories and tags) +* A query is no more filtered by language when setting the parameter 'lang' to an empty value +* Add the possibility to create a custom wpml-config.xml file in wp-content/polylang/ +* Bug correction: custom menus are not displayed on search page (introduced in 1.0.2) +* Bug correction: sql error when filtering terms by language (introduced in 1.0.2) +* Bug correction: SSL doesn't work properly +* Bug correction: php notice on IIS servers +* Bug correction: clicking on the radio buttons in the admin language switcher does not work in Chrome +* Bug correction: on multisite, the signup page is redirected to the home page +* Bug correction: date archives are not correctly filtered for the default language when hiding the language code and using date and name permalinks +* Bug correction: only one wpml-config.xml file is parsed + += 1.0.2 (2013-02-26) = + +* Add the possibility to query comments by language +* Add the possibility not to set a cookie by defining PLL_COOKIE to false (Polylang may not work as expected on some pages) +* Now a returning visitor is redirected to its preferred language when visiting the front page in the default language +* Add compatibility with the plugin Custom field template (copy and synchronize custom fields) +* Improve compatibility with plugins or themes which overwrite columns in posts list table +* Add the filter 'pll_get_flag' +* Add support of 'icl_unregister_string' function from the WPML API +* Bug correction: synchronizing custom fields breaks the plugin Advanced Custom Fields +* Bug correction: 'pll_default_language' broken +* Bug correction: rewrite rules are not flushed when re-activating the plugin +* Bug correction: feed urls are not correctly escaped when using default permalinks +* Bug correction: notice Undefined index: media_support +* Bug correction: custom post types and taxonomies set in wpml-config.xml are not hidden +* Bug correction: get_terms cannot query multiple languages +* Bug correction: 'icl_register_string' is now persistent as in WPML (fixes Nextgen gallery translations which were not working) + += 1.0.1 (2013-01-28) = + +* Add Swedish translation contributed by [matsii](http://wordpress.org/support/profile/matsii) +* Add 2 new API functions : 'pll_is_translated_post_type' and 'pll_is_translated_taxonomy' +* Bug correction: when using a static front page, the posts page is not filtered by language (introduced in 1.0) +* Bug correction: disable translation for hard coded menu as it creates more problems than it solves (introduced in 1.0) + += 1.0 (2013-01-24) = + +* Add Hungarian translation contributed by Csaba Erdei +* Add Norwegian translation contributed by [Tom Boersma](http://www.oransje.com/) +* Add Slovak translation contributed by [Branco (WebHostingGeeks.com)](http://webhostinggeeks.com/user-reviews/) +* Code cleaning -> remove compatibility with versions older than 0.8 +* Add search in the string translations list table +* Add options to better control the synchronization of various metas for posts +* It is now possible to synchronize sticky posts and publication dates +* Add option to disable the multilingual support of media +* Add options to better control the multilingual capability of custom post types and taxonomies +* Better integration with new media management in WP 3.5 +* Improve menu translation for themes which register a theme location but don't use it in wp_nav_menu (hard coded menu) +* Add the pll_preferred_language filter allowing plugins to modify the language set by browser preferences detection +* Add support of the WPML config file +* Add support of 'icl_get_languages' and 'icl_link_to_element' functions from the WPML API +* Add compatibility with YARPP and improve compatibility with WordPress SEO +* Change cookie name which conflicts with Quick cache and allow users to overwrite it by defining the constant PLL_COOKIE +* Bug correction: again the canonical redirection +* Bug correction: the languages are not correctly displayed after they have been modified using quick edit +* Bug correction: undefined index notice when saving strings translation when the admin language filter is active +* Bug correction: rewrite rules are not correctly flushed when adding / deleting a language (introduced in 0.9.2) +* Bug correction: the list of pages is displayed when a static font page translation is not translated (now replaced by the list of posts) +* Bug correction: permalinks are not modified when doing cron and the language code is added to all urls +* Bug correction: creating a new term with the same name as a language may modify the language code (slug) + += 0.9.8 (2012-12-05) = + +* Bug correction: ajax on frontend does not work when adding the language code to all urls +* Bug correction: search forms using the get_search_form filter do not work + += 0.9.7 (2012-12-04) = + +* Bug correction: the admin language filter does filter non translatable post types +* Bug correction: again the canonical redirection +* Bug correction: fatal error when Polylang is used together with 'Author Avatars List' +* Bug correction: widget titles uselessly appear in the strings translations table when the widget is set for only one language +* Tests done with WordPress 3.5 RC3 and Twenty Twelve + += 0.9.6 (2012-11-26) = + +* It is now possible to query the terms by language using the WordPress function 'get_terms' +* Bug correction: search for empty string in default language displays posts in all languages when hiding the URL language information for default language +* Bug correction: completely reworked the canonical redirection introduced in 0.9.5 which created more problems than it solved +* Bug correction: ajax for media translations does not work +* Started tests with WordPress 3.5 RC1 and Twenty Twelve + += 0.9.5 (2012-11-13) = + +* The user can now choose the number of languages and strings translations to display +* Add compatibility with the 'icl_object_id' function and ICL_LANGUAGE_CODE and ICL_LANGUAGE_NAME constants from the WPML API +* Add 17 languages to the predefined list (automatic download and update of language files won't work) +* Bug correction: post preview does not work when adding the language code to all urls +* Bug correction: redirect to front page in default language when posting a comment on static front page +* Bug correction: impossible to create terms with the same name in different languages +* Bug correction: query string added by other plugins is erased when adding the language code to all urls +* Bug correction: redirect erase 'POST' variables on homepage when adding the language code to all urls +* Bug correction: English (en_US) loads rtl style when using a localized WordPress package with an rtl language +* Bug correction: on some installation strings translations do not work with some special characters +* Bug correction: incoming links are not redirected to canonical url when adding the language code to all urls and hiding the code for the default language +* Bug correction: search form does not work in non default language when using permalinks without trailing slash + += 0.9.4 (2012-10-23) = + +* Add Afrikaans translation contributed by [Kobus Joubert](http://translate3d.com/) +* Add Belarusian translation contributed by [Alexander Markevitch](http://fourfeathers.by/) +* Add Afrikaans (af) and Belarusian (be_BY) to predefined languages list (automatic download and update of language files won't work) +* Add the possibility to translate the date format and time format +* Add compatibility with the 'icl_get_home_url' function from the WPML API +* Bug correction: still some issues with string translations +* Bug correction: search is not filtered by the (default) language when the language is set by content and the language code is hidden for the default language +* Bug correction: posts & pages preview urls are broken when adding the language code to all urls +* Bug correction: automatically added new top-level pages to menus are not filtered by language +* Bug correction: the admin language filter messes the categories languages when editing a post and the parent dropdown list when editing a category +* Bug correction: search form does not work when using a static front page (introduced in 0.9.2) +* Bug correction: can't set languages for categories and post tags on blogs created after polylang has been activated at network level +* Bug correction: menus don't work with catch box theme ('has_nav_menu' not correctly filtered) + += 0.9.3 (2012-10-08) = + +* Add Bulgarian translation contributed by [pavelsof](http://wordpress.org/support/profile/pavelsof) +* Add compatibility with WPML API for strings translations +* Bug correction: dates are not translated (introduced in 0.9.2) +* Bug correction: the language is lost when keeping - No change - for language in bulk edit +* Bug correction: categories and tags are duplicate (when default language is set automatically to existing content and categories and tags share the same name) + += 0.9.2 (2012-09-30) = + +* Support new WordPress (WP 3.5+) convention for js and css files naming +* Improve performance, mainly on frontend +* Bug correction: the category language is not set when creating it in the post editor (introduced in 0.9) +* Bug correction: unable to add a query string when using a static front page +* Bug correction: ajax tag suggestion in "edit post" conflicts with the admin content language filter +* Bug correction: ugly notices when trying to access a static front page which has not been translated +* Bug correction: the language code is added to custom post types and taxonomies permalinks even if they are not translatable +* Bug correction: some arrays in wp_locale mix English and other language +* Bug correction: the media language is not correctly set when uploading from post if the post has not been saved after choosing the language + += 0.9.1 (2012-09-20) = + +* Add Finnish translation contributed by [Jani Alha](http://www.wysiwyg.fi) +* Bug correction: improve the robustness of the admin content language filter +* Bug correction: the language switcher displays languages which have no posts or pages (introduced in 0.9) +* Bug correction: wrong default language when adding a new media +* Bug correction: the dropdown language switcher does not switch language when there is no post translation +* Bug correction: issue with translations when using category quick edit +* Bug correction: home redirects to 404 when combining static front page + force_lang = 1 + hide_default = 0 + += 0.9 (2012-09-12) = + +* Add Turkish translation contributed by [darchws](http://darch.ws/) +* Add media translation support +* Add a persistent content language filter on admin side (WP 3.2+ required) +* Add biographical info translation +* Add multiline support for string translations +* Add the possibility to clean the strings translation database +* Add quick edit and bulk edit support for posts and pages +* Add quick edit support for categories and tags +* The language is now loaded with 'setup_theme' action instead of 'wp' action when always adding language information url +* Search form now does use javascript only for searchform.php when pretty permalinks are not used +* Add the option PLL_SEARCH_FORM_JS to disable the js code used to modify the search form +* Suppress the option PLL_SYNC, replaced by an option in the language settings ui +* Suppress the PLL_DISPLAY_ALL option +* Suppress the template tag 'the_languages' (replaced by 'pll_the_languages' since v0.5) +* Suppress the function 'pll_is_front_page' (useless since 0.8.2) +* Bug correction: the browser language is sometimes not correctly detected by Android +* Bug correction: the rtl text direction is not correct when editing an existing language +* Bug correction: rss feed does not work if translated site title or tagline contains special characters +* Bug correction: post types and taxonomies labels are not translated on frontend +* Bug correction: the filter 'pll_copy_post_metas' does not work for metas with multiple values +* Bug correction: translations table for post and terms are uselessly serialized two times +* Bug correction: attempt to suppress conflict with themes which hardcode the name of nav menus (but do define a theme location) +* Bug correction: homepage displays all posts when the front page displays a static page and no page is selected for front page (but one is selected for posts page) +* Bug correction: widgets disappear when Polylang is enabled + += 0.8.10 (2012-08-06) = + +* Add Lithuanian (lt_LT) to predefined languages list (automatic download and update of language files won't work) +* Add Lithuanian translation contributed by [Naglis Jonaitis](http://najo.lt/) +* Bug correction: empty string translation issue +* Bug correction: 'wp_list_pages' does not filter custom post types +* Bug correction: warning if posts are queried before the action 'wp_loaded' has been fired +* Bug correction: notice in twentyten when requesting a date archive with no posts + += 0.8.9 (2012-07-20) = + +* Add Portuguese translation contributed by [Vitor Carvalho](http://vcarvalho.com/) + += 0.8.8 (2012-07-18) = + +* Validation improvement thanks to kg69design +* Bug correction: custom post types rewrite rules are broken when registered with query_var=>false +* Bug correction: user admin language not deleted when uninstalling the plugin +* Bug correction: pll_current_language('name') returns locale instead of language name +* Bug correction: ajax on frontend does not work +* Bug correction: homepage pagination broken when redirecting the language page to a static front page +* Bug correction: taxonomies conflicts on custom post types +* Bug correction: the admin language is not updated when edited by other users + += 0.8.7 (2012-06-10) = + +* Add the possibility to load Polylang API for ajax requests on frontend +* Bug correction: search form is broken when using a static front page +* Bug correction: admin bar search does not work +* Tests done with WordPress 3.4 RC2 + += 0.8.6 (2012-05-23) = + +* Add the possibility to use a local config file to set options +* Improve robustness (less PHP notices) +* Bug correction: Menus not showing in preview mode +* Bug correction: fatal error when customizing a theme in WP 3.4 beta 4 +* Bug correction: second page of search results returns 404 when using pretty permalinks + += 0.8.5 (2012-05-14) = + +* Bug correction : sites using static front page are messed in v0.8.4 + += 0.8.4 (2012-05-13) = + +* Add a new argument 'post_id' to the function pll_the_languages to display posts translations within the loop +* Bug correction: every posts in every languages are shown on the homepage when requesting the wrong one with or without 'www.' +* Bug correction: every posts in every languages are shown when requesting /?p=string +* Bug correction: the language is not correctly set for wp-signup.php and wp-activate.php +* Bug correction: wrong home links when using permalinks with front with WP 3.3 and older +* Bug correction: wrong redirection after posting a comment when adding the language information to all urls +* Bug correction: term language may be lost in some situations +* Bug correction: post language is set to default if updated outside the edit post page +* Bug correction: javascript error in WP 3.1 +* Bug correction: can't toggle visibility of tags metabox in edit post panel +* Tests done with WordPress 3.4 beta 4 + += 0.8.3 (2012-04-10) = + +* Add Danish translation contributed by [Compute](http://wordpress.org/support/profile/compute) +* Add Spanish translation contributed by Curro +* Add the possibility to add a content in a different language than the current one by setting explicitly the lang parameter in the secondary query +* Add support of PATHINFO permalinks +* Bug correction: secondary queries not correctly filtered by language +* Bug correction: wrong archives links when using permalinks with front +* Bug correction: wrong homepage link when keeping 'language' in permalinks with front +* Bug correction: flush_rewrite_rules notice when setting up a static front page (introduced in 0.8.2) +* Bug correction: every posts in every languages are shown when hitting the homepage with a query string unknown to WP (thanks to Gonçalo Peres) +* Bug correction: every posts in every languages are shown on the homepage when PHP adds index.php to the url +* Tests done with WordPress 3.4 beta 1 + + += 0.8.2 (2012-03-20) = + +* Add Italian translation contributed by [Luca Barbetti](http://wordpress.org/support/profile/lucabarbetti) +* Improve performance on admin side +* Comment status and ping status are now copied when adding a new translation +* Deprecated API function 'pll_is_front_page' as it is now useless +* Bug correction: Wrong translation url for taxonomies when adding the language information to all urls +* Bug correction: "translation" of search page does not work if the site is only made of pages +* Bug correction: wrong language permalink structure introduced in 0.8.1 +* Bug correction: wrong language set when clicking on "add new" translation in edit category and edit tags panels +* Bug correction: site does not display if no languages are set +* Bug correction: get_author_posts_url is 404 +* Bug correction: homepage is 404 when using a static front page and adding the language information to all urls + += 0.8.1 (2012-03-11) = + +* Add Latvian translation contributed by [@AndyDeGroo](http://twitter.com/AndyDeGroo) +* It is now possible to synchronize multiple values for custom fields +* Add new API function pll_current_language +* Add the pll_rewrite_rules filter allowing plugins to filter rewrite rules by language +* WP 3.4 preparation: disable the menu section in the customize theme admin panel (unusable with Polylang) +* Bug correction: removing 'language' in permalinks does not work in WP 3.4 alpha +* Bug correction: problems with custom post type archives when 'has_archive' is set (thanks to AndyDeGroo) +* Bug correction: 404 error when combining %postname% permastructure with "Add language information to all URL" option +* Bug correction: translated custom strings are duplicated if registered several times +* Bug correction: queries with an array of post types are not correctly filtered +* Bug correction: wp-login.php always in English + += 0.8 (2012-02-29) = + +* Sticky posts are now filtered by language +* It is now possible to use the language page as home page +* Add an "About Polylang" metabox on the languages admin page +* Add the pll_the_languages filter allowing to filter the whole output of the language switcher +* Add a new argument 'display_names_as' to the function pll_the_languages +* Add pll_get_post_types & pll_get_taxonomies filters allowing to enable / disable the language filter for post types & taxonomies +* Add ckb to predefined languages list +* Completely reworked the string translation storage in the database +* Some performance improvements on admin side +* Improve compatibility with other plugins broken by the home url filter +* Add an option to disable the home url filter +* Add an option to disable synchronization of metas between translations +* Bug correction: body class 'home' is not set on translated homepage +* Bug correction: robots.txt is broken when adding the language code to all urls (including default language) +* Bug correction: bad name for the Czech flag +* Bug correction: bad language information in rss feed for WP < 3.4 +* Bug correction: signup broken on multisite +* Bug correction: the translation url is set to self when using a static front page and no page for posts and there is no translation +* Bug correction: problems with custom post type archive titles +* Bug correction: problems with custom post type if rewrite slug is different from post_type (thanks to AndyDeGroo) +* Bug correction: quick edit still breaks translation linking of pages (thanks to AndyDeGroo) +* Bug correction: bad rewrite rules for feeds (introduced in 0.7.2) +* Bug correction: the order is not saved when creating a language +* Bug correction: the categories list is not updated when adding a new category (ajax broken) + += 0.7.2 (2012-02-15) = + +* Add Polish translation contributed by [Peter Paciorkiewicz](http://www.paciorkiewicz.pl) +* Add 5 new languages to predefined list +* completely reworked rewrite rules +* WP 3.4 preparation: add new WordPress languages files to download when creating a new language +* Bug correction: custom nav menus do not work in Artisteer generated themes +* Bug correction: having a single language causes multiple warnings while saving post/page. +* Bug correction: custom nav menu broken on archives pages +* Bug correction: the language switcher does not link to translated post type archive when using pretty permalinks +* Bug correction: the tags are not saved in the right language when translated tags have the same name +* Bug correction: bad link in post preview when adding language code to all urls +* Bug correction: feed not filtered by language when adding language code to all urls +* Bug correction: duplicate canonical link when used together with WordPress SEO by Yoast +* Bug correction: the all posts admin page is messed if another plugin adds a column +* Bug correction: 404 error on static front page when adding language code to all urls (including default language) + += 0.7.1 (2012-02-06) = + +* Allow using 3 characters languages codes (ISO 639-2 or 639-3) +* The predefined languages dropdown list now displays the locale to help differentiate some languages +* Add 5 new languages to predefined list +* Bug correction: the filter 'pll_copy_post_metas' does not work +* Bug correction: impossible to add a tag in the edit post panel +* Bug correction: rewrite rules not correct +* Bug correction: cache issue with css and js files + += 0.7 (2012-01-30) = + +* Add Hebrew translation contributed by [ArielK](http://www.arielk.net) +* Add support for RTL languages for both frontend and admin +* Twenty Ten and Twenty Eleven languages files are now automatically downloaded when creating a new language +* Improve filtering tags by language in the edit post panel +* Category parent dropdown list is now filtered by language +* Category parents are now synchronized between translations +* Add the possibility to have the language information in all URL +* Add support for post formats +* Add option allowing not to show the current language in the language switcher (for both menu and widget) +* Add a title attribute (and the possibility to personalize it with a filter) to flags +* pll_get_post and pll_get_term second parameter is now optional and defaults to current language +* Add pll_the_language_link filter allowing to filter translation links outputted by the language switcher +* The option PLL_DISPLAY_ALL is no longer supported +* Bug correction: Autosave reset to default language +* Bug correction: blog info not translated in feeds +* Bug correction: post comments feed always in default language +* Bug correction: undefined index notice when setting up a custom menu widget +* Bug correction: rewrite rules are not correctly reset when deactivating the plugin +* Bug correction: is_home not correctly set on pages 2, 3... +* Bug correction: avoid naming conflicts (in sql queries) with other themes / plugins +* Bug correction: bad language detection and url rewriting of custom post types archives + += 0.6.1 (2012-01-12) = + +* Add Dutch translation contributed by [AlbertGn](http://wordpress.org/support/profile/albertgn) +* Disable everything except the languages management panel while no language has been created +* Bug correction: can't have the same featured image in translated posts +* Bug correction: parent page dropdown does appear only after the page has been saved +* Bug correction: archives widget not working anymore +* Bug correction: string translations does not work for WP < 3.3 +* Bug correction: fix fatal error in string translations caused by widgets using the old API +* Bug correction: the strings translation panel is unable to translate strings with special characters +* Bug correction: Polylang "is_front_page" returns true on archives pages + += 0.6 (2012-01-07) = + +* Add Greek translation contributed by [theodotos](http://www.ubuntucy.org) +* WordPress languages files are now automatically downloaded when creating a new language (and updated when updating WordPress) +* Add the possibility to change the order of the languages in the language switcher +* Add the possibility to translate the site title, tagline and widgets titles +* Categories, post tags, featured image, page parent, page template and menu order are now copied when adding a new translation +* Translations are now accessibles in the "Posts", "Pages", "Categories" and "Post tags" admin panels +* Improve the dropdown language switcher widget (sends now to translated page or home page based on options) +* Move custom flags from polylang/local_flags to wp_content/polylang +* Add two options to "pll_the_languages" ('hide_if_no_translation' and 'hide_current'). *The function does not output ul tag anymore* +* Improve API +* Bug correction: Twenty eleven custom Header problem with v0.5.1 +* Bug correction: front-page.php not loaded for translated front page + += 0.5.1 (2011-12-18) = + +* Improved German translation contributed by [Christian Ries](http://www.singbyfoot.lu) +* Bug correction: translated homepage not recognized as home page when it displays posts +* Bug correction: predefined language list does not work on IE8 +* Bug correction: on some installations, "Add New" post doesn't keep intended language +* Bug correction: fatal error when Polylang is used together with the plugin Tabbed Widgets +* Bug correction: language Switcher points sometimes to wrong places + += 0.5 (2011-12-07) = + +* Add multisite support +* Rework the Polylang admin panel. There is now a set of predefined languages +* Improve categories and tags language filter in the edit post panel +* Categories and tags created in the edit post panel are now created with the same language as the post +* The language switcher can now force the link to the front page instead of the translated page +* The nav menus can now display a language switcher +* Improved performance +* Optimized the calendar widget (less code and sql queries executed) +* Added the possibility to display posts and terms with no language set (see the documentation to know how to enable this functionality) +* Started the creation of a small API for theme and plugin programmers +* Bug correction: when using a static front page, the page for posts does not work when using the default permalink settings +* Bug correction: the search form does not work if a static front page is used +* Bug correction: quick edit breaks translations +* Bug correction: categories and post tags translations don't work for more than 2 languages +* Bug correction: the output of wp_page_menu is not correct for non default languages + += 0.4.4 (2011-11-28) = + +* Bug correction: When using a static front page, the translated home page displays posts instead of the translated page +* Bug correction: Automatic language setting of existing categories and post tags does not work correctly + += 0.4.3 (2011-11-19) = + +* Add Russian translation contributed by [yoyurec](http://yoyurec.in.ua) +* Bug correction: impossible to suppress the language name in the language switcher widget settings +* Bug correction: post's page does not work when using a static front page +* Bug correction: flags in local_flags directory are removed after an automatic upgrade (now works for an upgrade from 0.4.3+ to a higher version) +* Bug correction: switching to default language displays a 404 Error when hiding the default language in url and displaying the language switcher as dropdown +* Other minor bug corrections +* Tests done with WordPress 3.3 beta 3 + += 0.4.2 (2011-11-16) = + +* Bug correction: language settings page is broken in v0.4.1 + += 0.4.1 (2011-11-16) = + +* Bug correction: flags shows even when you set doesn't to show +* Bug correction: custom taxonomies do not work +* Bug correction: some users get the fatal error: call to undefined function wp_get_current_user() in /wp-includes/user.php on line 227 + += 0.4 (2011-11-10) = + +* Add a documentation (in English only) +* Add the possibility to hide the url language information for the default language +* Add the possibility to set the admin language in the user profile +* Add the possibility to fill existing posts, pages, categories & tags with the default language +* Add support for custom post types and custom taxonomies +* Add the possibility to display flags in the language switcher +* Add CSS classes to customize rendering of the language switcher +* Add the possibility to display the language switcher as a dropdown list +* Add support for calendar widget +* Improve performance: less sql queries +* Improve data validation when creating or updating languages +* Bug correction: 'wp_list_pages' page order is ignored when the plugin is enabled +* Bug correction: when using 'edit' or 'add new' (translation) for posts, the categories appear in the wrong language +* Bug correction: pages are not included in language post count +* Bug correction: the language switcher does not display languages if there are only pages +* Bug correction: the widget filter does not allow to come back to 'all languages' once a language has been set +* Other minor bug corrections + += 0.3.2 (2011-10-20) = + +* Bug correction: authors pages are not filtered by language +* Bug correction: language pages use the archive template +* Bug correction: database error for comments on posts and pages +* Bug correction: "Add new" translation for pages creates a post instead of a page +* Bug correction: the search query does not look into pages + += 0.3.1 (2011-10-16) = + +* Bug correction: the widget settings cannot be saved when activating Polylang +* Bug correction: the archives widget does not display any links +* Bug correction: ajax form for translations not working in the 'Categories' and 'Post tags' admin panels + += 0.3 (2011-10-07) = + +* Add language filter for widgets +* Improved performance for filtering pages by language +* Improved security +* Minor bug correction with versions management + += 0.2 (2011-10-05) = + +* Add language filter for nav menus +* Add German translation +* Add language filter for recent comments +* Add ajax to term edit form +* Add ajax to post metabox +* Improved performance for filtering terms by language +* Bugs correction + += 0.1 (2011-09-22) = +* Initial release diff --git a/wp-content/plugins/polylang/css/build/admin.css b/wp-content/plugins/polylang/css/build/admin.css new file mode 100644 index 0000000000..d71aad602d --- /dev/null +++ b/wp-content/plugins/polylang/css/build/admin.css @@ -0,0 +1,459 @@ +/* languages admin panel */ +#add-lang select { + width: 95%; +} + +#add-lang label { + margin: 0.35em 0 0.5em; +} + +.pll-legend { + display: block; + padding: 2px 0; + color: #1d2327; + font-weight: 400; + text-shadow: none; + margin: 0.35em 0 0.5em; +} + +.column-locale, +.languages .column-slug { + width : 15% +} + +.column-default_lang { + width : 5%; +} + +.column-term_group, +.column-flag, .column-count { + width : 10%; +} + +td.column-default_lang .icon-default-lang:before, +.pll-wizard-content .icon-default-lang:before { + font-family: 'dashicons'; + content: "\f155"; +} + +.pll-icon:before{ + display: inline-block; + text-align: left; + width: 15px; +} +.pll-circle:before{ + content: "\25cf"; +} + +/* about Polylang metabox */ +#pll-about-box p, +#pll-recommended p { + text-align: justify; +} + +#pll-about-box input { + margin: 0; + padding: 0; + float: right; +} + +/* strings translation table */ +.stringstranslations .column-name, +.stringstranslations .column-context { + width: 10%; +} + +.stringstranslations .column-string { + width: 33%; +} + +.translation label { + display: inline-block; + width: 23%; + vertical-align: top; +} + +.translation { + display: flex; /* fix #691 to remove default margin bottom */ +} +@media screen and (max-width: 782px) { /* reset default display property for small device */ + .translation{ + display: block; + } +} +.translation textarea{ + display: block; /* fix #691 to remove default margin bottom */ +} +.translation input, +.translation textarea { + width: 72%; + box-sizing: border-box; /* to be sure field don't overrun outside their wrapper */ + margin-bottom: 4px; /* fix #691 set the same margin bottom for both textarea and input tags */ +} + +/* settings */ +.pll-settings { + margin-top: 20px; +} + +.pll-settings .plugin-title { + width: 25%; +} + +#wpbody-content .pll-settings .pll-configure tr { + display: table-row; +} + +#wpbody-content .pll-settings .pll-configure td { + display: table-cell; +} + +#wpbody-content .pll-settings .pll-configure > td { + padding: 20px 20px 20px 40px; +} + +.pll-configure legend { + font-size: 14px; + font-weight: 600; + margin-bottom: 0.5em; +} + +.pll-configure td .description { + margin-top: 2px; + margin-bottom: 0.5em; +} + +.pll-configure p.submit { + margin-top: 20px; +} + +.pll-configure .button { + margin-right: 20px; +} + +.pll-configure fieldset { + margin-bottom: 1.5em; +} + +.pll-inline-block-list { + margin: 0; +} + +.pll-inline-block-list li { + display: inline-block; + margin: 0; + width: 250px; +} + +/* settings URL modifications */ +#pll-domains-table td { + padding: 2px 2px 2px 1.5em; + -webkit-box-shadow: none; + box-shadow: none; + border: none; +} + +.pll-settings-url-col { + display: inline-block; + width: 49%; + vertical-align: top; +} + +/* settings Activation keys */ +.pll-table-top td { + vertical-align: top; +} + +#pll-licenses-table label { + font-size: 1em; + font-weight: 600; +} + +.pll-configure .pll-deactivate-license { + margin: 0 0 0 20px; +} + +/* language columns in edit.php and edit-tags.php */ +.wp-list-table th[class*='column-language_'], +.wp-list-table td[class*='column-language_'] { + width: 1.5em; + box-sizing: content-box; /* Override ACF 5.9.0 styles */ +} + +/* Text direction in post.php and edit-tags.php */ +.pll-dir-rtl textarea, +.pll-dir-rtl input[type="text"] { + direction: rtl; +} + +.pll-dir-ltr textarea, +.pll-dir-ltr input[type="text"] { + direction: ltr; +} + +.pll-dir-ltr .tr_lang, +.pll-dir-rtl .tr_lang { + direction: inherit; +} + +/* languages metabox in post.php */ +#ml_box p { + margin-top: 1em; +} + +#post-translations p { + float: left; + margin-top: 1em; +} + +.rtl #post-translations p { + float: right; +} + +#post-translations table { + table-layout: fixed; + width: 100%; + clear: both; +} + +#post-translations a { + text-decoration: none; +} + +#post-translations .pll-language-column, +#post-translations .pll-column-icon { + width: 20px; +} + +#post-translations .tr_lang { + width: 100%; +} + +#post-translations td { + padding: 2px; +} + +#post-translations .spinner, +#term-translations .spinner { + float: none; + margin: 0; + background-position: center; + width: auto; +} + +#select-post-language .pll-select-flag { + padding: 4px; + margin-right: 10px; +} + +.rtl #select-post-language .pll-select-flag { + padding: 4px; + margin-right: 0px; + margin-left: 10px; +} + +/* specific cases for media */ +#select-media-language .pll-select-flag { + padding: 4px; + margin-right: 10px; +} + +.pll-media-edit-column { + float: right; +} + +/* language and translations in edit-tags.php */ +.pll-translation-flag { /* also for media */ + margin-right: 14px; +} + +#select-add-term-language .pll-select-flag { + padding: 11px; + margin-right: 13px; +} + +#select-edit-term-language .pll-select-flag { + padding: 11px; + margin-right: 4px; +} + +#term-translations p { + /* same style as label */ + font-weight: 400; + font-style: normal; + padding: 2px; + color: #23282d; +} + +#add-term-translations, +#edit-term-translations { + width: 95%; +} + +#term-translations .pll-language-column { + line-height: 28px; + width: 20%; +} + +#term-translations .pll-edit-column, +#add-term-translations .pll-language-column { + width: 20px; +} + +#edit-term-translations .pll-language-column { + padding: 15px 10px; + font-weight: normal; +} + +/* icon fonts */ +.pll_icon_add:before { + content: "\f132"; +} + +.pll_icon_edit:before { + content: "\f464"; +} + +[class^="pll_icon_"] { + font: 20px/1 'dashicons'; + vertical-align: middle; +} + +/* admin bar */ +#wpadminbar #wp-admin-bar-languages .ab-item img { + margin: 0 8px 0 2px; +} + +#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon { + float: none; + top: 4px; +} + +#wpadminbar #wp-admin-bar-languages .ab-icon:before { + content: "\f326"; + top: 1px; +} + +#wp-admin-bar-languages.pll-filtered-languages { + background: #a03f3f; +} + +#wpadminbar #wp-admin-bar-languages.pll-filtered-languages span.ab-label{ /* Enforce white color for WordPress admin light theme. */ + color: #fff; +} + +/* Notices */ +.pll-notice.notice { + padding-right: 38px; + position: relative; +} + +.pll-notice a.notice-dismiss { + text-decoration: none; +} + +.pll-notice .button { + margin-right: 10px; +} + +@media screen and ( max-width: 782px ) { + /* settings */ + #wpbody-content .pll-settings .pll-configure > td { + padding: 20px; + } + + #wpbody-content .pll-settings #cb { + padding: 20px 9px; + } + + /* settings URL modifications */ + .pll-inline-block { + width: auto; + } + + .pll-settings-url-col { + display: block; + width: 100%; + } + + /* settings licenses */ + #wpbody-content .pll-settings #pll-licenses-table td { + display: block; + } + + .pll-configure .pll-deactivate-license { + margin: 10px 0 5px; + } + + /* strings translations table */ + .translation label { + display: block; + width: 95%; + padding-left: 0; + } + + .translation input, + .translation textarea { + width: 95%; + } + + /* hide selected language flag and translations language name */ + #select-add-term-language .pll-select-flag, + #select-edit-term-language .pll-select-flag, + #edit-term-translations .pll-language-name { + display: none; + } + + #edit-term-translations { + width: 100%; + } + + #add-term-translations .pll-language-column { + line-height: 38px; + } + + #edit-term-translations td { + padding: 8px 10px; + } + + #edit-term-translations .pll-language-column, + #edit-term-translations .pll-edit-column { + width: 20px; + } + + /* translations tables should be kept as table */ + .term-translations .pll-language-column, + .term-translations .pll-edit-column, + .term-translations .pll-translation-column { + display: table-cell; + } + + .term-translations .hidden { + display: none; + } + + /* admin bar */ + #wpadminbar #wp-admin-bar-languages { + display: block; /*shows our menu on mobile devices */ + } + + #wpadminbar #wp-admin-bar-languages > .ab-item { + width: 50px; + text-align: center; + } + + #wpadminbar #wp-admin-bar-languages > .ab-item .ab-icon:before { + font: 32px/1 'dashicons'; + top: -1px; + } + + #wpadminbar #wp-admin-bar-languages > .ab-item img { + margin: 19px 0; + } + + #wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon { + margin-right: 6px; + font-size: 20px !important; + line-height: 20px !important; + } +} diff --git a/wp-content/plugins/polylang/css/build/admin.min.css b/wp-content/plugins/polylang/css/build/admin.min.css new file mode 100644 index 0000000000..81a59eb505 --- /dev/null +++ b/wp-content/plugins/polylang/css/build/admin.min.css @@ -0,0 +1 @@ +#add-lang select{width:95%}#add-lang label,.pll-legend{margin:.35em 0 .5em}.pll-legend{color:#1d2327;display:block;font-weight:400;padding:2px 0;text-shadow:none}.column-locale,.languages .column-slug{width:15%}.column-default_lang{width:5%}.column-count,.column-flag,.column-term_group{width:10%}.pll-wizard-content .icon-default-lang:before,td.column-default_lang .icon-default-lang:before{content:"\f155";font-family:dashicons}.pll-icon:before{display:inline-block;text-align:left;width:15px}.pll-circle:before{content:"\25cf"}#pll-about-box p,#pll-recommended p{text-align:justify}#pll-about-box input{float:right;margin:0;padding:0}.stringstranslations .column-context,.stringstranslations .column-name{width:10%}.stringstranslations .column-string{width:33%}.translation label{display:inline-block;vertical-align:top;width:23%}.translation{display:flex}@media screen and (max-width:782px){.translation{display:block}}.translation textarea{display:block}.translation input,.translation textarea{box-sizing:border-box;margin-bottom:4px;width:72%}.pll-settings{margin-top:20px}.pll-settings .plugin-title{width:25%}#wpbody-content .pll-settings .pll-configure tr{display:table-row}#wpbody-content .pll-settings .pll-configure td{display:table-cell}#wpbody-content .pll-settings .pll-configure>td{padding:20px 20px 20px 40px}.pll-configure legend{font-size:14px;font-weight:600;margin-bottom:.5em}.pll-configure td .description{margin-bottom:.5em;margin-top:2px}.pll-configure p.submit{margin-top:20px}.pll-configure .button{margin-right:20px}.pll-configure fieldset{margin-bottom:1.5em}.pll-inline-block-list{margin:0}.pll-inline-block-list li{display:inline-block;margin:0;width:250px}#pll-domains-table td{border:none;-webkit-box-shadow:none;box-shadow:none;padding:2px 2px 2px 1.5em}.pll-settings-url-col{display:inline-block;vertical-align:top;width:49%}.pll-table-top td{vertical-align:top}#pll-licenses-table label{font-size:1em;font-weight:600}.pll-configure .pll-deactivate-license{margin:0 0 0 20px}.wp-list-table td[class*=column-language_],.wp-list-table th[class*=column-language_]{box-sizing:content-box;width:1.5em}.pll-dir-rtl input[type=text],.pll-dir-rtl textarea{direction:rtl}.pll-dir-ltr input[type=text],.pll-dir-ltr textarea{direction:ltr}.pll-dir-ltr .tr_lang,.pll-dir-rtl .tr_lang{direction:inherit}#ml_box p{margin-top:1em}#post-translations p{float:left;margin-top:1em}.rtl #post-translations p{float:right}#post-translations table{clear:both;table-layout:fixed;width:100%}#post-translations a{text-decoration:none}#post-translations .pll-column-icon,#post-translations .pll-language-column{width:20px}#post-translations .tr_lang{width:100%}#post-translations td{padding:2px}#post-translations .spinner,#term-translations .spinner{background-position:50%;float:none;margin:0;width:auto}#select-post-language .pll-select-flag{margin-right:10px;padding:4px}.rtl #select-post-language .pll-select-flag{margin-left:10px;margin-right:0;padding:4px}#select-media-language .pll-select-flag{margin-right:10px;padding:4px}.pll-media-edit-column{float:right}.pll-translation-flag{margin-right:14px}#select-add-term-language .pll-select-flag{margin-right:13px;padding:11px}#select-edit-term-language .pll-select-flag{margin-right:4px;padding:11px}#term-translations p{color:#23282d;font-style:normal;font-weight:400;padding:2px}#add-term-translations,#edit-term-translations{width:95%}#term-translations .pll-language-column{line-height:28px;width:20%}#add-term-translations .pll-language-column,#term-translations .pll-edit-column{width:20px}#edit-term-translations .pll-language-column{font-weight:400;padding:15px 10px}.pll_icon_add:before{content:"\f132"}.pll_icon_edit:before{content:"\f464"}[class^=pll_icon_]{font:20px/1 dashicons;vertical-align:middle}#wpadminbar #wp-admin-bar-languages .ab-item img{margin:0 8px 0 2px}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{float:none;top:4px}#wpadminbar #wp-admin-bar-languages .ab-icon:before{content:"\f326";top:1px}#wp-admin-bar-languages.pll-filtered-languages{background:#a03f3f}#wpadminbar #wp-admin-bar-languages.pll-filtered-languages span.ab-label{color:#fff}.pll-notice.notice{padding-right:38px;position:relative}.pll-notice a.notice-dismiss{text-decoration:none}.pll-notice .button{margin-right:10px}@media screen and (max-width:782px){#wpbody-content .pll-settings .pll-configure>td{padding:20px}#wpbody-content .pll-settings #cb{padding:20px 9px}.pll-inline-block{width:auto}.pll-settings-url-col{display:block;width:100%}#wpbody-content .pll-settings #pll-licenses-table td{display:block}.pll-configure .pll-deactivate-license{margin:10px 0 5px}.translation label{display:block;padding-left:0;width:95%}.translation input,.translation textarea{width:95%}#edit-term-translations .pll-language-name,#select-add-term-language .pll-select-flag,#select-edit-term-language .pll-select-flag{display:none}#edit-term-translations{width:100%}#add-term-translations .pll-language-column{line-height:38px}#edit-term-translations td{padding:8px 10px}#edit-term-translations .pll-edit-column,#edit-term-translations .pll-language-column{width:20px}.term-translations .pll-edit-column,.term-translations .pll-language-column,.term-translations .pll-translation-column{display:table-cell}.term-translations .hidden{display:none}#wpadminbar #wp-admin-bar-languages{display:block}#wpadminbar #wp-admin-bar-languages>.ab-item{text-align:center;width:50px}#wpadminbar #wp-admin-bar-languages>.ab-item .ab-icon:before{font:32px/1 dashicons;top:-1px}#wpadminbar #wp-admin-bar-languages>.ab-item img{margin:19px 0}#wpadminbar #wp-admin-bar-languages #wp-admin-bar-all .ab-item .ab-icon{font-size:20px!important;line-height:20px!important;margin-right:6px}} \ No newline at end of file diff --git a/wp-content/plugins/polylang/css/build/dialog.css b/wp-content/plugins/polylang/css/build/dialog.css new file mode 100644 index 0000000000..103d15af14 --- /dev/null +++ b/wp-content/plugins/polylang/css/build/dialog.css @@ -0,0 +1,85 @@ +/* By default Polylang dialog box use WordPress jQuery UI dialog styles. + However WooCommerce loads its own jQuery UI dialog styles and we need to override them by ours + to revert to the default WordPress ones. +*/ +.pll-confirmation-modal.ui-widget, +.pll-confirmation-modal.ui-widget .ui-widget, +.pll-confirmation-modal .ui-widget { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 13px; +} +.pll-confirmation-modal.ui-dialog { + padding: 0; + z-index: 100102; + background: #fff; + border: 0; + color: #444; + border-radius: 0; /* Override WooCommerce dialog styles - jQuery UI 1.11.4 - WP < 5.6 */ +} +.ui-dialog.pll-confirmation-modal .ui-dialog-titlebar { + background: #fcfcfc; + border-radius: 0; + border: 0; + border-bottom: 1px solid #dfdfdf; + height: 36px; + font-size: 18px; + font-weight: 600; + line-height: 2; + padding: 0 36px 0 16px; + color: #444; + position: static; +} +.ui-dialog.pll-confirmation-modal .ui-dialog-title { + float: none; + width: auto; + margin: 0; +} +.pll-confirmation-modal .ui-widget-header .ui-icon { + background: none; + position: static; +} +.pll-confirmation-modal .ui-button.ui-dialog-titlebar-close { + padding: 0; + margin: 0; + top: 0; + right: 0; + width: 36px; + height: 36px; + border: 0; /* Override WooCommerce dialog styles - jQuery UI 1.11.4 - WP < 5.6 */ + background: none; /* Override WooCommerce dialog styles - jQuery UI 1.11.4 - WP < 5.6 */ +} +.ui-dialog.pll-confirmation-modal .ui-dialog-content { + border: 0; + padding: 16px; + color: #444; + position: static; + box-sizing: border-box; +} +.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane{ + margin: 0; + padding: 16px; + border: 0; + background: #fcfcfc; + border-top: 1px solid #dfdfdf; +} +.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane .ui-button{ + margin: 0 0 0 16px; + padding: 0 10px 1px; + background: #f7f7f7; + border: 1px solid #cccccc; + border-radius: 3px; + position: static; + line-height: 2; + vertical-align: top; +} +.ui-dialog.pll-confirmation-modal .ui-button:hover, +.ui-dialog.pll-confirmation-modal .ui-button:focus { + background: #fafafa; + border-color: #999; + color: #23282d; +} +.pll-confirmation-modal + .ui-widget-overlay { + background: #000; + opacity: 0.7; + z-index: 100101; +} diff --git a/wp-content/plugins/polylang/css/build/dialog.min.css b/wp-content/plugins/polylang/css/build/dialog.min.css new file mode 100644 index 0000000000..b5bd718578 --- /dev/null +++ b/wp-content/plugins/polylang/css/build/dialog.min.css @@ -0,0 +1 @@ +.pll-confirmation-modal .ui-widget,.pll-confirmation-modal.ui-widget,.pll-confirmation-modal.ui-widget .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.pll-confirmation-modal.ui-dialog{background:#fff;border:0;border-radius:0;color:#444;padding:0;z-index:100102}.ui-dialog.pll-confirmation-modal .ui-dialog-titlebar{background:#fcfcfc;border:0;border-bottom:1px solid #dfdfdf;border-radius:0;color:#444;font-size:18px;font-weight:600;height:36px;line-height:2;padding:0 36px 0 16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-title{float:none;margin:0;width:auto}.pll-confirmation-modal .ui-widget-header .ui-icon{background:none;position:static}.pll-confirmation-modal .ui-button.ui-dialog-titlebar-close{background:none;border:0;height:36px;margin:0;padding:0;right:0;top:0;width:36px}.ui-dialog.pll-confirmation-modal .ui-dialog-content{border:0;box-sizing:border-box;color:#444;padding:16px;position:static}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane{background:#fcfcfc;border:0;border-top:1px solid #dfdfdf;margin:0;padding:16px}.ui-dialog.pll-confirmation-modal .ui-dialog-buttonpane .ui-button{background:#f7f7f7;border:1px solid #ccc;border-radius:3px;line-height:2;margin:0 0 0 16px;padding:0 10px 1px;position:static;vertical-align:top}.ui-dialog.pll-confirmation-modal .ui-button:focus,.ui-dialog.pll-confirmation-modal .ui-button:hover{background:#fafafa;border-color:#999;color:#23282d}.pll-confirmation-modal+.ui-widget-overlay{background:#000;opacity:.7;z-index:100101} \ No newline at end of file diff --git a/wp-content/plugins/polylang/css/build/selectmenu.css b/wp-content/plugins/polylang/css/build/selectmenu.css new file mode 100644 index 0000000000..6040faa269 --- /dev/null +++ b/wp-content/plugins/polylang/css/build/selectmenu.css @@ -0,0 +1,239 @@ +/* Greatly modified version of the jquery-ui.css */ + +.ui-widget-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.ui-menu { + list-style: none; + padding: 0; + margin: 0; + display: block; + outline: none; +} + +.ui-menu .ui-menu { + position: absolute; +} + +.ui-menu .ui-menu-item { + position: relative; + margin: 0; + padding: 3px 1em 3px .4em; + cursor: pointer; + min-height: 0; /* support: IE7 */ + /* support: IE10, see #8844 */ + list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); +} + +/* for jQuery UI 1.12 which introduces a wrapper */ +.ui-menu .ui-menu-item:not([role]) { + padding: 0; +} + +.ui-menu-item-wrapper { + padding: 3px 1em 3px 2em; +} +.rtl .ui-menu .ui-menu-item { + text-align: right; +} + +/* icon support */ +.ui-menu-icons { + position: relative; +} + +.ui-menu-icons .ui-menu-item[role] { + padding-left: 2em; +} + +.rtl .ui-menu-item-wrapper, /* for jQuery UI 1.12 which introduces a wrapper */ +.rtl .ui-menu-icons .ui-menu-item[role] { + padding-left: 1em; + padding-right: 2em; +} + +/* left-aligned */ +.ui-selectmenu-text .ui-icon, +.ui-menu .ui-icon { + position: absolute; + top: 0; + bottom: 0; + left: .3em; + margin: auto 0; +} + +.rtl .ui-selectmenu-text .ui-icon, +.rtl .ui-menu .ui-icon { + right: .3em; + left: auto; +} + +/* right-aligned */ +.ui-menu .ui-menu-icon { + left: auto; + right: 0; +} + +.ui-selectmenu-menu { + padding: 0; + margin: 0; + position: absolute; + top: 0; + left: 0; + display: none; +} + +.ui-selectmenu-menu .ui-menu { + overflow: auto; + /* Support: IE7 */ + overflow-x: hidden; + padding-bottom: 1px; +} + +.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { + font-size: 1em; + font-weight: bold; + line-height: 23px; + padding: 2px 0.4em; + margin: 0.5em 0 0 0; + height: auto; + border: 0; +} + +.ui-selectmenu-open { + display: block; +} + +.ui-selectmenu-button, /* jQuery UI 1.11.4 - WP < 5.6 */ +.ui-selectmenu-button.ui-button { + display: inline-block; + overflow: hidden; + position: relative; + text-decoration: none; + box-sizing: border-box; /* To keep width calculation in percent since WP 5.6 */ + text-align: left; + white-space: nowrap; + vertical-align: top; + padding: 0; + line-height: normal; /* Override WC Bookings styles with WP < 5.6 */ + height: 28px; /* Override WC Bookings styles with WP < 5.6 */ +} + +.ui-selectmenu-button span.ui-icon { + right: 0.5em; + left: auto; + position: absolute; + top: 26%; + width: 16px; + height: 16px; + text-indent: 0; /* due to text-indent for jquery ui-dialog in wizard */ + background: none; +} + +.rtl .ui-selectmenu-button span.ui-icon { + left: 0.5em; + right: auto; +} + + +.ui-selectmenu-button.ui-widget span.ui-selectmenu-text, /* Override WC Bookings styles with WP < 5.6 */ +.ui-selectmenu-button span.ui-selectmenu-text { + text-align: left; + padding: 0.1em 2.1em 0.2em 2em; + display: block; + line-height: 23px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin: 0; +} + +.rtl .ui-selectmenu-button span.ui-selectmenu-text { + text-align: right; + padding: 0.2em 2em 0.2em 2.1em; +} + +.ui-widget-content, +.ui-state-default, +.ui-selectmenu-button.ui-state-default, /* Override WC Bookings styles with WP < 5.6 */ +.ui-button.ui-selectmenu-button-closed, /* To be compatible jQuery UI 1.12.1 since WordPress 5.6 */ +.ui-button.ui-selectmenu-button-open, /* To be compatible jQuery UI 1.12.1 since WordPress 5.6 */ +.ui-widget-content .ui-state-default, +.ui-widget-header .ui-state-default { + background: #fff; + border: 1px solid #ddd; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.07) inset; + color: #32373c; +} +/* Override to have same styles as WP form styles since WordPress 5.4 */ +.toplevel_page_mlang .ui-selectmenu-button.ui-state-default, +.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-closed, /* To be compatible jQuery UI 1.12.1 since WordPress 5.6 */ +.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-open{ /* To be compatible jQuery UI 1.12.1 since WordPress 5.6 */ + box-shadow: 0 0 0 transparent; + border-radius: 4px; + border: 1px solid #7e8993; +} + +/* From this line and below: override WooCommerce bookings plugin styles which overrides default WordPress styles */ +.pll-selectmenu-menu .ui-widget, +.pll-selectmenu-button.ui-widget { + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; +} + +.toplevel_page_mlang .ui-button.ui-selectmenu-button:focus{ + color: #016087; /* Same color as WordPress focused select HTML tag */ + border-color: #007cba; + box-shadow: 0 0 0 1px #007cba; + outline: 2px solid transparent; + background: #fff; /* Override bookings plugin styles which overrides default WordPress styles */ +} + +.toplevel_page_mlang .ui-menu-item, +.toplevel_page_mlang .ui-widget-content .ui-state-hover, +.toplevel_page_mlang .ui-widget-content .ui-state-focus, +.toplevel_page_mlang .ui-widget-content .ui-state-active { + color: #016087; /* Same color as option in a WordPress focused select HTML tag */ + margin: 0; +} + +.ui-selectmenu-open .ui-widget-content .ui-state-hover, /* Override WC Bookings styles with WP < 5.6 */ +.ui-selectmenu-open .ui-widget-content .ui-state-focus, /* Override WC Bookings styles with WP < 5.6 */ +.ui-selectmenu-open .ui-widget-content .ui-state-active, /* Override WC Bookings styles with WP < 5.6 */ +.pll-selectmenu-menu .ui-widget-content .ui-state-hover, +.pll-selectmenu-menu .ui-widget-content .ui-state-focus, +.pll-selectmenu-menu .ui-widget-content .ui-state-active { /* To be compatible jQuery UI 1.12.1 since WordPress 5.6 */ + background: #d5d5d5; + border: 0; +} + +.ui-selectmenu-button.ui-state-focus { + border: 1px solid #5b9dd9; + box-shadow: 0 0 2px rgba(30, 140, 190, 0.8); +} + +.ui-icon-triangle-1-s:before { + content: ""; + background: #fff url(data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E) no-repeat right 0px top 55%; + background-size: 16px 16px; + box-sizing: border-box; + position: absolute; + width: 16px; + height: 16px; +} + +.pll-selectmenu-button.ui-button:hover, +.pll-wizard .ui-button:hover, +.pll-wizard .ui-button:focus { + background: #fff; /* To override jQuery ui-dialog styles provided by WordPress */ +} + +.ui-widget-content { + max-height: 231px; + box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3); +} diff --git a/wp-content/plugins/polylang/css/build/selectmenu.min.css b/wp-content/plugins/polylang/css/build/selectmenu.min.css new file mode 100644 index 0000000000..92e0fbd3cf --- /dev/null +++ b/wp-content/plugins/polylang/css/build/selectmenu.min.css @@ -0,0 +1 @@ +.ui-widget-overlay{height:100%;left:0;position:fixed;top:0;width:100%}.ui-menu{display:block;list-style:none;margin:0;outline:none;padding:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{cursor:pointer;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);margin:0;min-height:0;padding:3px 1em 3px .4em;position:relative}.ui-menu .ui-menu-item:not([role]){padding:0}.ui-menu-item-wrapper{padding:3px 1em 3px 2em}.rtl .ui-menu .ui-menu-item{text-align:right}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item[role]{padding-left:2em}.rtl .ui-menu-icons .ui-menu-item[role],.rtl .ui-menu-item-wrapper{padding-left:1em;padding-right:2em}.ui-menu .ui-icon,.ui-selectmenu-text .ui-icon{bottom:0;left:.3em;margin:auto 0;position:absolute;top:0}.rtl .ui-menu .ui-icon,.rtl .ui-selectmenu-text .ui-icon{left:auto;right:.3em}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-selectmenu-menu{display:none;left:0;margin:0;padding:0;position:absolute;top:0}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{border:0;font-size:1em;font-weight:700;height:auto;line-height:23px;margin:.5em 0 0;padding:2px .4em}.ui-selectmenu-open{display:block}.ui-selectmenu-button,.ui-selectmenu-button.ui-button{box-sizing:border-box;display:inline-block;height:28px;line-height:normal;overflow:hidden;padding:0;position:relative;text-align:left;text-decoration:none;vertical-align:top;white-space:nowrap}.ui-selectmenu-button span.ui-icon{background:none;height:16px;left:auto;position:absolute;right:.5em;text-indent:0;top:26%;width:16px}.rtl .ui-selectmenu-button span.ui-icon{left:.5em;right:auto}.ui-selectmenu-button span.ui-selectmenu-text,.ui-selectmenu-button.ui-widget span.ui-selectmenu-text{display:block;line-height:23px;margin:0;overflow:hidden;padding:.1em 2.1em .2em 2em;text-align:left;text-overflow:ellipsis;white-space:nowrap}.rtl .ui-selectmenu-button span.ui-selectmenu-text{padding:.2em 2em .2em 2.1em;text-align:right}.ui-button.ui-selectmenu-button-closed,.ui-button.ui-selectmenu-button-open,.ui-selectmenu-button.ui-state-default,.ui-state-default,.ui-widget-content,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{background:#fff;border:1px solid #ddd;box-shadow:inset 0 1px 2px rgba(0,0,0,.07);color:#32373c}.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-closed,.toplevel_page_mlang .ui-selectmenu-button.ui-selectmenu-button-open,.toplevel_page_mlang .ui-selectmenu-button.ui-state-default{border:1px solid #7e8993;border-radius:4px;box-shadow:0 0 0 transparent}.pll-selectmenu-button.ui-widget,.pll-selectmenu-menu .ui-widget{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px}.toplevel_page_mlang .ui-button.ui-selectmenu-button:focus{background:#fff;border-color:#007cba;box-shadow:0 0 0 1px #007cba;color:#016087;outline:2px solid transparent}.toplevel_page_mlang .ui-menu-item,.toplevel_page_mlang .ui-widget-content .ui-state-active,.toplevel_page_mlang .ui-widget-content .ui-state-focus,.toplevel_page_mlang .ui-widget-content .ui-state-hover{color:#016087;margin:0}.pll-selectmenu-menu .ui-widget-content .ui-state-active,.pll-selectmenu-menu .ui-widget-content .ui-state-focus,.pll-selectmenu-menu .ui-widget-content .ui-state-hover,.ui-selectmenu-open .ui-widget-content .ui-state-active,.ui-selectmenu-open .ui-widget-content .ui-state-focus,.ui-selectmenu-open .ui-widget-content .ui-state-hover{background:#d5d5d5;border:0}.ui-selectmenu-button.ui-state-focus{border:1px solid #5b9dd9;box-shadow:0 0 2px rgba(30,140,190,.8)}.ui-icon-triangle-1-s:before{background:#fff url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E") no-repeat right 0 top 55%;background-size:16px 16px;box-sizing:border-box;content:"";height:16px;position:absolute;width:16px}.pll-selectmenu-button.ui-button:hover,.pll-wizard .ui-button:focus,.pll-wizard .ui-button:hover{background:#fff}.ui-widget-content{box-shadow:0 2px 6px hsla(0,0%,39%,.3);max-height:231px} \ No newline at end of file diff --git a/wp-content/plugins/polylang/css/build/wizard.css b/wp-content/plugins/polylang/css/build/wizard.css new file mode 100644 index 0000000000..b6c6a62a9c --- /dev/null +++ b/wp-content/plugins/polylang/css/build/wizard.css @@ -0,0 +1,951 @@ +@charset "UTF-8"; +body { + margin: 65px auto 24px; + box-shadow: none; + background: #f1f1f1; + padding: 0; + border: 0; /* fix-pro #856 override WP install.css */ +} + +#pll-logo { + border: 0; + margin: 0 0 24px; + padding: 0; + text-align: center; + font-family: sans-serif; + font-size: 64px; + text-transform: uppercase; + color: #000; + line-height: normal; +} +#pll-logo a { + display: flex; + justify-content: center; + color: #000; + text-decoration: none; +} + +#pll-logo img { + max-width: 100%; + margin-right: 16px; +} +.rtl #pll-logo img { + margin-right: 0; + margin-left: 16px; +} + +.pll-wizard-footer { + text-align: center +} + +.pll-wizard .select2-container { + text-align: left; + width: auto +} + +.pll-wizard .hidden { + display: none +} + +.pll-wizard-content { + box-shadow: 0 1px 3px rgba(0, 0, 0, .13); + padding: 2em; + margin: 0 0 20px; + background: #fff; + overflow: hidden; + zoom: 1; + text-align: left; +} +.rtl .pll-wizard-content{ + text-align: right; +} + +.pll-wizard-content h1, +.pll-wizard-content h2, +.pll-wizard-content h3, +.pll-wizard-content table { + margin: 0 0 20px; + border: 0; + padding: 0; + color: #666; + clear: none; + font-weight: 500 +} + +.pll-wizard-content p { + margin: 20px 0; + font-size: 1em; + line-height: 1.75em; + color: #666 +} + +.pll-wizard-content table { + font-size: 1em; + line-height: 1.75em; + color: #666; + width: 100%; + margin-top: 20px; +} +.pll-wizard-content table td span{ + display: inline-block; +} + +.pll-wizard-content table caption { + caption-side: bottom; + font-style: italic; + text-align: right; +} +.rtl .pll-wizard-content table caption { + text-align: left; +} + +.pll-wizard-content table caption .icon-default-lang{ + font-style: normal; +} + +.pll-wizard-content a { + color: #a03f3f; +} + +.pll-wizard-content a:focus, +.pll-wizard-content a:hover, +.pll-wizard-footer-links:hover { + color: #dd5454 +} + +.pll-wizard-content .pll-wizard-next-steps { + overflow: hidden; + margin: 0 0 24px; + padding-bottom: 2px +} + +.pll-wizard-content .pll-wizard-next-steps h2 { + margin-bottom: 12px +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-first { + float: left; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-last { + float: right; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps ul { + padding: 0 2em 0 0; + list-style: none outside; + margin: 0 +} + +.pll-wizard-content .pll-wizard-next-steps ul li a { + display: block; + padding: 0 0 .75em +} + +.pll-wizard-content .pll-wizard-next-steps ul li a::before { + color: #82878c; + font: normal 20px/1 dashicons; + speak: none; + display: inline-block; + padding: 0 10px 0 0; + top: 1px; + position: relative; + text-decoration: none!important; + vertical-align: top +} + +.pll-wizard-steps { + padding: 0 0 24px; + margin: 0; + list-style: none outside; + overflow: hidden; + color: #ccc; + width: 100%; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: inline-flex +} + +.pll-wizard-steps li { + width: 100%; + float: left; + padding: 0 0 .8em; + margin: 0; + text-align: center; + position: relative; + border-bottom: 4px solid #ccc; + line-height: 1.4em +} + +.pll-wizard-steps li a { + color: #a03f3f; + text-decoration: none; + padding: 1.5em; + margin: -1.5em; + position: relative; + z-index: 1 +} + +.pll-wizard-steps li a:focus, +.pll-wizard-steps li a:hover { + color: #dd5454; + text-decoration: underline +} + +.pll-wizard-steps li::before { + content: ""; + border: 4px solid #ccc; + border-radius: 100%; + width: 4px; + height: 4px; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -6px; + margin-bottom: -8px; + background: #fff +} + +.pll-wizard-steps li.active { + border-color: #a03f3f; + color: #a03f3f; + font-weight: 700 +} + +.pll-wizard-steps li.active::before { + border-color: #a03f3f +} + +.pll-wizard-steps li.done { + border-color: #a03f3f; + color: #a03f3f +} + +.pll-wizard-steps li.done::before { + border-color: #a03f3f; + background: #a03f3f +} + +.pll-wizard .pll-wizard-actions { + overflow: hidden; + margin: 20px 0 0; + position: relative +} + +.pll-wizard .pll-wizard-actions .button { + font-size: 16px; + font-weight: 300; + padding: 1em 2em; + line-height: 1em; + margin-right: .5em; + margin-bottom: 2px; + margin-top: 10px; + height: auto; + border-radius: 4px; + box-shadow: none; + min-width: auto; + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button { + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button-primary, +.pll-wizard .pll-wizard-actions .button-primary { + background-color: #a03f3f; + border-color: #a03f3f; + color: #fff; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #a03f3f; + text-shadow: 0 -1px 1px #a03f3f, 1px 0 1px #a03f3f, 0 1px 1px #a03f3f, -1px 0 1px #a03f3f; + margin: 0; + opacity: 1 +} + +.pll-wizard .pll-wizard-content .button-small .dashicons { + font-size: 15px; + height: auto; + vertical-align: middle; +} + +.pll-wizard .button-primary:active, +.pll-wizard .button-primary:focus, +.pll-wizard input[type="checkbox"]:focus + label.button-primary, +.pll-wizard .button-primary:hover { + background: #dd5454; + border-color: #dd5454; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #dd5454 +} +.pll-wizard .pll-wizard-actions .button-primary[disabled], +.pll-wizard .pll-wizard-actions .button-primary:disabled, +.pll-wizard .pll-wizard-actions .button-primary.disabled { + cursor: wait; + background-color: #bb5454 !important; + border-color: #bb5454 !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #bb5454 !important; + text-shadow: 0 -1px 1px #bb5454, 1px 0 1px #bb5454, 0 1px 1px #bb5454, -1px 0 1px #bb5454 !important; + color: #ffa3a3 !important; +} +.pll-wizard-content p:last-child { + margin-bottom: 0 +} + +.pll-wizard-footer-links { + font-size: .85em; + color: #7b7b7b; + margin: 1.18em auto; + display: inline-block; + text-align: center +} + +.pll-wizard-services { + border: 1px solid #eee; + padding: 0; + margin: 0 0 1em; + list-style: none outside; + border-radius: 4px; + overflow: hidden +} + +.pll-wizard-services p { + margin: 0 0 1em 0; + padding: 0; + font-size: 1em; + line-height: 1.5em +} + +.pll-wizard-service-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + padding: 0; + border-bottom: 1px solid #eee; + color: #666; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.media-step .pll-wizard-service-item{ + border: 0; +} + +.media-step .pll-wizard-service-item:last-child{ + display: block; +} + +.media-step .pll-wizard-service-item .pll-wizard-service-enable{ + padding-bottom: 0; +} + +.pll-wizard-service-item:last-child { + border-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-name { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 160px; + text-align: center; + font-weight: 700; + padding: 2em 0; + -webkit-align-self: stretch; + align-self: stretch; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: baseline; + -webkit-align-items: baseline; + align-items: baseline +} + +.pll-wizard-service-item .pll-wizard-service-name img { + max-width: 75px +} + +.pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + padding: 20px +} + +.pll-wizard-service-item .pll-wizard-service-example { + padding: 0 20px 20px +} + +.pll-wizard-service-item .pll-wizard-service-example p{ + text-align: right; +} +.rtl .pll-wizard-service-item .pll-wizard-service-example p{ + text-align: left; +} + +.pll-wizard-service-item .pll-wizard-service-description p { + margin-bottom: 1em +} + +.pll-wizard-service-item .pll-wizard-service-description p:last-child { + margin-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-description .pll-wizard-service-settings-description { + display: block; + font-style: italic; + color: #999 +} + +.pll-wizard-service-item .pll-wizard-service-enable { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 75px; + text-align: center; + cursor: pointer; + padding: 2em 0; + position: relative; + max-height: 1.5em; + -webkit-align-self: flex-start; + align-self: flex-start; + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 +} + +.pll-wizard-service-item .pll-wizard-service-toggle { + position: relative +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] { + position:absolute; + opacity: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label { + position: relative; + display: inline-block; + width: 44px; + height: 20px; + border-radius: 10em; + cursor: pointer; + text-indent: -9999px; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:focus + label { + border:1px dashed #777; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before, +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + content: ''; + position: absolute; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before { + left: 0; + top: 0; + width: 44px; + height: 20px; + background: #ddd; + border-radius: 10em; + transition: background-color .2s; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + width: 16px; + height: 16px; + transition: all .2s; + border-radius: 50%; + background: #fff; + margin: 2px; + top: 0; + left: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::before { + background:#a03f3f; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::after { + right: 0; + left:auto; +} + +.pll-wizard-service-item .pll-wizard-service-settings { + display: none; + margin-top: .75em; + margin-bottom: 0; + cursor: default +} + +.pll-wizard-service-item .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings { + display: inline-block +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.closed { + border-bottom: 0 +} + +.step { + text-align: center +} + +.pll-wizard .button .dashicons{ + vertical-align: middle; +} +.rtl .dashicons-arrow-right-alt2:before { + content: "\f341"; +} +.pll-wizard .pll-wizard-actions .button:active, +.pll-wizard .pll-wizard-actions .button:focus, +.pll-wizard .pll-wizard-actions .button:hover { + box-shadow: none +} + +.pll-wizard-next-steps { + border: 1px solid #eee; + border-radius: 4px; + list-style: none; + padding: 0 +} + +.pll-wizard-next-steps li { + padding: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + border-top: 1px solid #eee +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border, +.pll-wizard-next-steps .pll-wizard-next-step-item:first-child { + border-top: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + margin: 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-action { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.pll-wizard-next-steps .pll-wizard-next-step-action .button { + margin: 1em 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-description, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-actions, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-action .button{ + margin-top: 0; +} + + +.pll-wizard-next-steps p.next-step-heading { + margin: 0; + font-size: .95em; + font-weight: 400; + font-variant: all-petite-caps +} + +.pll-wizard-next-steps p.next-step-extra-info { + margin: 0 +} + +.pll-wizard-next-steps h3.next-step-description { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps { + border-top: 1px solid #eee; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-next-step-description { + margin-bottom: 0 +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions { + margin: 0 0 1.5em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + font-size: 15px; + margin: 1em 0 1em 1.5em; +} +.rtl .pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + margin: 1em 1.5em 1em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button::last-child { + margin-right: 1.5em; +} + +.pll-wizard-content img{ + max-width: 100%; + margin-right: 0.5em; +} +.rtl .pll-wizard-content img{ + margin-left: 0.5em; +} + +.pll-wizard-content .form-field label{ + margin-bottom: 5px; + display: block; +} + +.pll-wizard-content .form-field select{ + padding: 3px; +} + +.pll-wizard-content .languages-step select, +.pll-wizard-content .untranslated-contents-step select{ + width: 100%; +} + +.languages-step .form-field .button{ + margin-left: 15px; +} +.languages-step .form-field .button > span{ + margin-right: 0.3em; +} +.rtl .languages-step .form-field .button{ + margin-left: 0; + margin-right: 15px; +} +.rtl .languages-step .form-field .button > span{ + margin-left: 0.3em; + margin-right: 0; +} + +.pll-wizard-content .languages-step .select-language-field{ + display: flex; +} + +.pll-wizard-content #languages{ + display: none; +} +.pll-wizard-content #languages tr th:first-child{ + width: 80%; +} +.pll-wizard-content #languages .dashicons{ + color: #a03f3f; +} +.pll-wizard-content #languages img{ + margin-right: 5px; +} +.pll-wizard-content .error{ + color: #a03f3f; + font-weight: bold; +} +.pll-wizard-content #messages .error{ + background: #fccfcf; + padding: 0.5rem; + border: 1px solid #a03f3f; + margin-bottom: 0.5rem; +} + +.pll-wizard-content #slide-toggle{ + position:absolute; + opacity: 0; +} + +.pll-wizard-content #slide-toggle + label{ + position:relative; +} +.pll-wizard-content #slide-toggle + label + span{ + display: block; +} + +.pll-wizard-content #slide-toggle + label .dashicons{ + margin-right: 0.3em; +} +.rtl .pll-wizard-content #slide-toggle + label .dashicons{ + margin-left: 0.3em; + margin-right: 0; +} +.pll-wizard-content #slide-toggle ~ #screenshot > img { + max-height: 500px; + margin-top: 10px; + -webkit-transition: all .5s cubic-bezier(0, 1, 0.5, 1); + transition: all .5s cubic-bezier(0, 1, 0.5, 1); +} +.pll-wizard-content #slide-toggle:checked ~ #screenshot > img { + max-height: 0; +} +.hide { + display: none; +} + +input[type="text"].field-in-error, +input[type="password"].field-in-error, +input[type="checkbox"].field-in-error, +input[type="color"].field-in-error, +input[type="date"].field-in-error, +input[type="datetime"].field-in-error, +input[type="datetime-local"].field-in-error, +input[type="email"].field-in-error, +input[type="month"].field-in-error, +input[type="number"].field-in-error, +input[type="search"].field-in-error, +input[type="radio"].field-in-error, +input[type="tel"].field-in-error, +input[type="text"].field-in-error, +input[type="time"].field-in-error, +input[type="url"].field-in-error, +input[type="week"].field-in-error, +select.field-in-error, +textarea.field-in-error, +span.field-in-error, +.field-in-error{ + border-color: #a03f3f; +} + +input[type="text"].field-in-error:focus, +input[type="password"].field-in-error:focus, +input[type="checkbox"].field-in-error:focus, +input[type="color"].field-in-error:focus, +input[type="date"].field-in-error:focus, +input[type="datetime"].field-in-error:focus, +input[type="datetime-local"].field-in-error:focus, +input[type="email"].field-in-error:focus, +input[type="month"].field-in-error:focus, +input[type="number"].field-in-error:focus, +input[type="search"].field-in-error:focus, +input[type="radio"].field-in-error:focus, +input[type="tel"].field-in-error:focus, +input[type="text"].field-in-error:focus, +input[type="time"].field-in-error:focus, +input[type="url"].field-in-error:focus, +input[type="week"].field-in-error:focus, +select.field-in-error:focus, +textarea.field-in-error:focus, +span.field-in-error:focus, +.field-in-error:focus{ + border: 1px solid #a03f3f; + box-shadow: 0 0 2px rgba(160, 63, 63, 0.8); + outline-color: #a03f3f; + outline-style: auto; + outline-width: thin; +} + +/* override install styles by returning back to forms styles */ +.form-table input.regular-text{ + width: 25em; +} +.form-table input.field-in-error{ + border-color: #a03f3f; +} +#pll-licenses-table td{ + padding: 10px 9px; +} +#pll-licenses-table .license-valid td p{ + min-width: 35em; +} +#pll-licenses-table .pll-deactivate-license{ + margin: 0 0 0 20px; +} +.rtl #pll-licenses-table .pll-deactivate-license{ + margin: 0 10px 0 0; +} +.pll-wizard-content .documentation { + padding: 24px 24px 0; + margin: 0 0 24px; + overflow: hidden; + background: #f5f5f5 +} + +.pll-wizard-content .documentation p { + padding: 0; + margin: 0 0 12px; +} +.documentation-container { + display: -webkit-box; + display: -webkit-flex; + display: flex; + justify-content: flex-end; +} + +.documentation-container .documentation-button-container { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; +} + +.wc-setup .wc-setup-actions .button.documentation-button { + height: 42px; + padding: 0 1em; + margin: 0; +} +#dialog{ + display: none; +} +.pll-wizard .ui-dialog.ui-widget-content{ + max-height: none; +} +.pll-wizard .ui-dialog-title::before{ + content: "\f534"; + font-family: dashicons; + display: inline-block; + line-height: 1; + font-weight: 400; + font-style: normal; + speak: none; + text-decoration: inherit; + text-transform: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 20px; + height: 20px; + font-size: 20px; + vertical-align: middle; + text-align: center; + margin: 0 5px 5px 0; + transition: color 0.1s ease-in; +} +.rtl.pll-wizard .ui-dialog-title::before{ + margin-right: 0; + margin-left: 5px; +} +.pll-wizard .ui-dialog ul{ + list-style: disc; + padding-left: 20px; +} +.rtl.pll-wizard .ui-dialog ul{ + padding-left: 0; + padding-right: 20px; +} +.pll-wizard li{ + margin-bottom: 0; +} +#translations{ + border-collapse: collapse; +} +#translations tbody:nth-child(odd){ + background-color: #f9f9f9; +} +#translations.striped > tbody > :nth-child(odd) { + background-color: transparent; /* Override common WordPress style */ +} +.pll-wizard-content mark{ + background: transparent none; +} +.pll-wizard-content mark{ + color: #7ad03a; +} +@media screen and (max-width: 782px) { + /* Override WordPress button css rules */ + .languages-step .form-field .button{ + font-size: 13px; + line-height: 26px; + height: 28px; + padding: 0 10px 1px; + vertical-align: top; + } + + #pll-licenses-table .pll-deactivate-license{ + margin: 10px 0 5px; + } +} +@media only screen and (max-width:620px) { + /* Override dialog width rule */ + .ui-dialog{ + width: 100% !important; + } + +} +@media only screen and (max-width:500px) { + #pll-logo a, + .select-language-field{ + flex-direction: column; + } + .select-language-field .action-buttons{ + display: flex; + justify-content: flex-end; + } + .languages-step .form-field .button{ + margin: 5px 0 0; + } +} +@media only screen and (max-width:400px) { + #pll-logo { + font-size: 56px; + } + .pll-wizard-steps { + display: none + } + .pll-wizard-service-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-service-item .pll-wizard-service-enable { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; + padding: 20px 0 0 + } + .pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 + } + .pll-wizard-service-item .pll-wizard-service-name { + padding: 20px 20px 0; + text-align: left; + -webkit-box-pack: justify!important; + -webkit-justify-content: space-between!important; + justify-content: space-between!important + } + .pll-wizard-service-item .pll-wizard-service-name img { + margin: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-description { + margin-bottom: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-action p { + margin: 0 + } +} +@media only screen and (max-width:360px) { + #pll-logo { + font-size: 48px; + } +} diff --git a/wp-content/plugins/polylang/css/build/wizard.min.css b/wp-content/plugins/polylang/css/build/wizard.min.css new file mode 100644 index 0000000000..d5d2623754 --- /dev/null +++ b/wp-content/plugins/polylang/css/build/wizard.min.css @@ -0,0 +1 @@ +body{background:#f1f1f1;box-shadow:none;margin:65px auto 24px}#pll-logo,body{border:0;padding:0}#pll-logo{color:#000;font-family:sans-serif;font-size:64px;line-height:normal;margin:0 0 24px;text-align:center;text-transform:uppercase}#pll-logo a{color:#000;display:flex;justify-content:center;text-decoration:none}#pll-logo img{margin-right:16px;max-width:100%}.rtl #pll-logo img{margin-left:16px;margin-right:0}.pll-wizard-footer{text-align:center}.pll-wizard .select2-container{text-align:left;width:auto}.pll-wizard .hidden{display:none}.pll-wizard-content{zoom:1;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.13);margin:0 0 20px;overflow:hidden;padding:2em;text-align:left}.rtl .pll-wizard-content{text-align:right}.pll-wizard-content h1,.pll-wizard-content h2,.pll-wizard-content h3,.pll-wizard-content table{border:0;clear:none;color:#666;font-weight:500;margin:0 0 20px;padding:0}.pll-wizard-content p{color:#666;font-size:1em;line-height:1.75em;margin:20px 0}.pll-wizard-content table{color:#666;font-size:1em;line-height:1.75em;margin-top:20px;width:100%}.pll-wizard-content table td span{display:inline-block}.pll-wizard-content table caption{caption-side:bottom;font-style:italic;text-align:right}.rtl .pll-wizard-content table caption{text-align:left}.pll-wizard-content table caption .icon-default-lang{font-style:normal}.pll-wizard-content a{color:#a03f3f}.pll-wizard-content a:focus,.pll-wizard-content a:hover,.pll-wizard-footer-links:hover{color:#dd5454}.pll-wizard-content .pll-wizard-next-steps{margin:0 0 24px;overflow:hidden;padding-bottom:2px}.pll-wizard-content .pll-wizard-next-steps h2{margin-bottom:12px}.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-first{box-sizing:border-box;float:left;width:50%}.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-last{box-sizing:border-box;float:right;width:50%}.pll-wizard-content .pll-wizard-next-steps ul{list-style:none outside;margin:0;padding:0 2em 0 0}.pll-wizard-content .pll-wizard-next-steps ul li a{display:block;padding:0 0 .75em}.pll-wizard-content .pll-wizard-next-steps ul li a:before{speak:none;color:#82878c;display:inline-block;font:normal 20px/1 dashicons;padding:0 10px 0 0;position:relative;text-decoration:none!important;top:1px;vertical-align:top}.pll-wizard-steps{color:#ccc;display:-webkit-inline-box;display:-webkit-inline-flex;display:inline-flex;list-style:none outside;margin:0;overflow:hidden;padding:0 0 24px;width:100%}.pll-wizard-steps li{border-bottom:4px solid #ccc;float:left;line-height:1.4em;margin:0;padding:0 0 .8em;position:relative;text-align:center;width:100%}.pll-wizard-steps li a{color:#a03f3f;margin:-1.5em;padding:1.5em;position:relative;text-decoration:none;z-index:1}.pll-wizard-steps li a:focus,.pll-wizard-steps li a:hover{color:#dd5454;text-decoration:underline}.pll-wizard-steps li:before{background:#fff;border:4px solid #ccc;border-radius:100%;bottom:0;content:"";height:4px;left:50%;margin-bottom:-8px;margin-left:-6px;position:absolute;width:4px}.pll-wizard-steps li.active{border-color:#a03f3f;color:#a03f3f;font-weight:700}.pll-wizard-steps li.active:before{border-color:#a03f3f}.pll-wizard-steps li.done{border-color:#a03f3f;color:#a03f3f}.pll-wizard-steps li.done:before{background:#a03f3f;border-color:#a03f3f}.pll-wizard .pll-wizard-actions{margin:20px 0 0;overflow:hidden;position:relative}.pll-wizard .pll-wizard-actions .button{border-color:#a03f3f;border-radius:4px;box-shadow:none;color:#a03f3f;font-size:16px;font-weight:300;height:auto;line-height:1em;margin-bottom:2px;margin-right:.5em;margin-top:10px;min-width:auto;padding:1em 2em}.pll-wizard .pll-wizard-content .button{border-color:#a03f3f;color:#a03f3f}.pll-wizard .pll-wizard-actions .button-primary,.pll-wizard .pll-wizard-content .button-primary{background-color:#a03f3f;border-color:#a03f3f;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.25),0 1px 0 #a03f3f;color:#fff;margin:0;opacity:1;text-shadow:0 -1px 1px #a03f3f,1px 0 1px #a03f3f,0 1px 1px #a03f3f,-1px 0 1px #a03f3f}.pll-wizard .pll-wizard-content .button-small .dashicons{font-size:15px;height:auto;vertical-align:middle}.pll-wizard .button-primary:active,.pll-wizard .button-primary:focus,.pll-wizard .button-primary:hover,.pll-wizard input[type=checkbox]:focus+label.button-primary{background:#dd5454;border-color:#dd5454;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.25),0 1px 0 #dd5454}.pll-wizard .pll-wizard-actions .button-primary.disabled,.pll-wizard .pll-wizard-actions .button-primary:disabled,.pll-wizard .pll-wizard-actions .button-primary[disabled]{background-color:#bb5454!important;border-color:#bb5454!important;box-shadow:inset 0 1px 0 hsla(0,0%,100%,.25),0 1px 0 #bb5454!important;color:#ffa3a3!important;cursor:wait;text-shadow:0 -1px 1px #bb5454,1px 0 1px #bb5454,0 1px 1px #bb5454,-1px 0 1px #bb5454!important}.pll-wizard-content p:last-child{margin-bottom:0}.pll-wizard-footer-links{color:#7b7b7b;display:inline-block;font-size:.85em;margin:1.18em auto;text-align:center}.pll-wizard-services{border:1px solid #eee;border-radius:4px;list-style:none outside;margin:0 0 1em;overflow:hidden;padding:0}.pll-wizard-services p{font-size:1em;line-height:1.5em;margin:0 0 1em;padding:0}.pll-wizard-service-item{-webkit-box-pack:justify;-webkit-box-align:center;-webkit-align-items:center;align-items:center;border-bottom:1px solid #eee;color:#666;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-justify-content:space-between;justify-content:space-between;padding:0}.media-step .pll-wizard-service-item{border:0}.media-step .pll-wizard-service-item:last-child{display:block}.media-step .pll-wizard-service-item .pll-wizard-service-enable{padding-bottom:0}.pll-wizard-service-item:last-child{border-bottom:0}.pll-wizard-service-item .pll-wizard-service-name{-webkit-box-align:baseline;-webkit-align-items:baseline;align-items:baseline;-webkit-align-self:stretch;align-self:stretch;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-basis:0;flex-basis:0;font-weight:700;min-width:160px;padding:2em 0;text-align:center}.pll-wizard-service-item .pll-wizard-service-name img{max-width:75px}.pll-wizard-service-item .pll-wizard-service-description{-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1;padding:20px}.pll-wizard-service-item .pll-wizard-service-example{padding:0 20px 20px}.pll-wizard-service-item .pll-wizard-service-example p{text-align:right}.rtl .pll-wizard-service-item .pll-wizard-service-example p{text-align:left}.pll-wizard-service-item .pll-wizard-service-description p{margin-bottom:1em}.pll-wizard-service-item .pll-wizard-service-description p:last-child{margin-bottom:0}.pll-wizard-service-item .pll-wizard-service-description .pll-wizard-service-settings-description{color:#999;display:block;font-style:italic}.pll-wizard-service-item .pll-wizard-service-enable{-webkit-box-ordinal-group:4;-webkit-align-self:flex-start;align-self:flex-start;cursor:pointer;-webkit-flex-basis:0;flex-basis:0;max-height:1.5em;min-width:75px;-webkit-order:3;order:3;padding:2em 0;position:relative;text-align:center}.pll-wizard-service-item .pll-wizard-service-toggle{position:relative}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]{opacity:0;position:absolute}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]+label{border-radius:10em;cursor:pointer;display:inline-block;height:20px;position:relative;text-indent:-9999px;width:44px}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:focus+label{border:1px dashed #777}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]+label:after,.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]+label:before{content:"";position:absolute}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]+label:before{background:#ddd;border-radius:10em;height:20px;left:0;top:0;transition:background-color .2s;width:44px}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]+label:after{background:#fff;border-radius:50%;height:16px;left:0;margin:2px;top:0;transition:all .2s;width:16px}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked+label:before{background:#a03f3f}.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked+label:after{left:auto;right:0}.pll-wizard-service-item .pll-wizard-service-settings{cursor:default;display:none;margin-bottom:0;margin-top:.75em}.pll-wizard-service-item .pll-wizard-service-settings.hide{display:none}.pll-wizard-service-item.checked .pll-wizard-service-settings{display:inline-block}.pll-wizard-service-item.checked .pll-wizard-service-settings.hide{display:none}.pll-wizard-service-item.closed{border-bottom:0}.step{text-align:center}.pll-wizard .button .dashicons{vertical-align:middle}.rtl .dashicons-arrow-right-alt2:before{content:"\f341"}.pll-wizard .pll-wizard-actions .button:active,.pll-wizard .pll-wizard-actions .button:focus,.pll-wizard .pll-wizard-actions .button:hover{box-shadow:none}.pll-wizard-next-steps{border:1px solid #eee;border-radius:4px;list-style:none;padding:0}.pll-wizard-next-steps li{padding:0}.pll-wizard-next-steps .pll-wizard-next-step-item{border-top:1px solid #eee;display:-webkit-box;display:-webkit-flex;display:flex}.pll-wizard-next-steps .pll-wizard-next-step-item.no-border,.pll-wizard-next-steps .pll-wizard-next-step-item:first-child{border-top:0}.pll-wizard-next-steps .pll-wizard-next-step-description{-webkit-box-flex:1;-webkit-flex-grow:1;flex-grow:1;margin:1.5em}.pll-wizard-next-steps .pll-wizard-next-step-action{-webkit-box-flex:0;-webkit-box-align:center;-webkit-align-items:center;align-items:center;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-grow:0;flex-grow:0}.pll-wizard-next-steps .pll-wizard-next-step-action .button{margin:1em 1.5em}.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-actions,.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-action .button,.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-description{margin-top:0}.pll-wizard-next-steps p.next-step-heading{font-size:.95em;font-variant:all-petite-caps;font-weight:400;margin:0}.pll-wizard-next-steps p.next-step-extra-info{margin:0}.pll-wizard-next-steps h3.next-step-description{font-size:16px;font-weight:600;margin:0}.pll-wizard-next-steps .pll-wizard-additional-steps{border-top:1px solid #eee}.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-next-step-description{margin-bottom:0}.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions{margin:0 0 1.5em}.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button{font-size:15px;margin:1em 0 1em 1.5em}.rtl .pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button{margin:1em 1.5em 1em 0}.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button::last-child{margin-right:1.5em}.pll-wizard-content img{margin-right:.5em;max-width:100%}.rtl .pll-wizard-content img{margin-left:.5em}.pll-wizard-content .form-field label{display:block;margin-bottom:5px}.pll-wizard-content .form-field select{padding:3px}.pll-wizard-content .languages-step select,.pll-wizard-content .untranslated-contents-step select{width:100%}.languages-step .form-field .button{margin-left:15px}.languages-step .form-field .button>span{margin-right:.3em}.rtl .languages-step .form-field .button{margin-left:0;margin-right:15px}.rtl .languages-step .form-field .button>span{margin-left:.3em;margin-right:0}.pll-wizard-content .languages-step .select-language-field{display:flex}.pll-wizard-content #languages{display:none}.pll-wizard-content #languages tr th:first-child{width:80%}.pll-wizard-content #languages .dashicons{color:#a03f3f}.pll-wizard-content #languages img{margin-right:5px}.pll-wizard-content .error{color:#a03f3f;font-weight:700}.pll-wizard-content #messages .error{background:#fccfcf;border:1px solid #a03f3f;margin-bottom:.5rem;padding:.5rem}.pll-wizard-content #slide-toggle{opacity:0;position:absolute}.pll-wizard-content #slide-toggle+label{position:relative}.pll-wizard-content #slide-toggle+label+span{display:block}.pll-wizard-content #slide-toggle+label .dashicons{margin-right:.3em}.rtl .pll-wizard-content #slide-toggle+label .dashicons{margin-left:.3em;margin-right:0}.pll-wizard-content #slide-toggle~#screenshot>img{margin-top:10px;max-height:500px;-webkit-transition:all .5s cubic-bezier(0,1,.5,1);transition:all .5s cubic-bezier(0,1,.5,1)}.pll-wizard-content #slide-toggle:checked~#screenshot>img{max-height:0}.hide{display:none}.field-in-error,input[type=checkbox].field-in-error,input[type=color].field-in-error,input[type=date].field-in-error,input[type=datetime-local].field-in-error,input[type=datetime].field-in-error,input[type=email].field-in-error,input[type=month].field-in-error,input[type=number].field-in-error,input[type=password].field-in-error,input[type=radio].field-in-error,input[type=search].field-in-error,input[type=tel].field-in-error,input[type=text].field-in-error,input[type=time].field-in-error,input[type=url].field-in-error,input[type=week].field-in-error,select.field-in-error,span.field-in-error,textarea.field-in-error{border-color:#a03f3f}.field-in-error:focus,input[type=checkbox].field-in-error:focus,input[type=color].field-in-error:focus,input[type=date].field-in-error:focus,input[type=datetime-local].field-in-error:focus,input[type=datetime].field-in-error:focus,input[type=email].field-in-error:focus,input[type=month].field-in-error:focus,input[type=number].field-in-error:focus,input[type=password].field-in-error:focus,input[type=radio].field-in-error:focus,input[type=search].field-in-error:focus,input[type=tel].field-in-error:focus,input[type=text].field-in-error:focus,input[type=time].field-in-error:focus,input[type=url].field-in-error:focus,input[type=week].field-in-error:focus,select.field-in-error:focus,span.field-in-error:focus,textarea.field-in-error:focus{border:1px solid #a03f3f;box-shadow:0 0 2px rgba(160,63,63,.8);outline-color:#a03f3f;outline-style:auto;outline-width:thin}.form-table input.regular-text{width:25em}.form-table input.field-in-error{border-color:#a03f3f}#pll-licenses-table td{padding:10px 9px}#pll-licenses-table .license-valid td p{min-width:35em}#pll-licenses-table .pll-deactivate-license{margin:0 0 0 20px}.rtl #pll-licenses-table .pll-deactivate-license{margin:0 10px 0 0}.pll-wizard-content .documentation{background:#f5f5f5;margin:0 0 24px;overflow:hidden;padding:24px 24px 0}.pll-wizard-content .documentation p{margin:0 0 12px;padding:0}.documentation-container{display:-webkit-box;display:-webkit-flex;display:flex;justify-content:flex-end}.documentation-container .documentation-button-container{-webkit-box-flex:0;-webkit-flex-grow:0;flex-grow:0}.wc-setup .wc-setup-actions .button.documentation-button{height:42px;margin:0;padding:0 1em}#dialog{display:none}.pll-wizard .ui-dialog.ui-widget-content{max-height:none}.pll-wizard .ui-dialog-title:before{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;content:"\f534";display:inline-block;font-family:dashicons;font-size:20px;font-style:normal;font-weight:400;height:20px;line-height:1;margin:0 5px 5px 0;text-align:center;text-decoration:inherit;text-rendering:auto;text-transform:none;transition:color .1s ease-in;vertical-align:middle;width:20px}.rtl.pll-wizard .ui-dialog-title:before{margin-left:5px;margin-right:0}.pll-wizard .ui-dialog ul{list-style:disc;padding-left:20px}.rtl.pll-wizard .ui-dialog ul{padding-left:0;padding-right:20px}.pll-wizard li{margin-bottom:0}#translations{border-collapse:collapse}#translations tbody:nth-child(odd){background-color:#f9f9f9}#translations.striped>tbody>:nth-child(odd){background-color:transparent}.pll-wizard-content mark{background:transparent none;color:#7ad03a}@media screen and (max-width:782px){.languages-step .form-field .button{font-size:13px;height:28px;line-height:26px;padding:0 10px 1px;vertical-align:top}#pll-licenses-table .pll-deactivate-license{margin:10px 0 5px}}@media only screen and (max-width:620px){.ui-dialog{width:100%!important}}@media only screen and (max-width:500px){#pll-logo a,.select-language-field{flex-direction:column}.select-language-field .action-buttons{display:flex;justify-content:flex-end}.languages-step .form-field .button{margin:5px 0 0}}@media only screen and (max-width:400px){#pll-logo{font-size:56px}.pll-wizard-steps{display:none}.pll-wizard-service-item{-webkit-flex-wrap:wrap;flex-wrap:wrap}.pll-wizard-service-item .pll-wizard-service-enable{-webkit-box-ordinal-group:3;-webkit-order:2;order:2;padding:20px 0 0}.pll-wizard-service-item .pll-wizard-service-description{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}.pll-wizard-service-item .pll-wizard-service-name{-webkit-box-pack:justify!important;-webkit-justify-content:space-between!important;justify-content:space-between!important;padding:20px 20px 0;text-align:left}.pll-wizard-service-item .pll-wizard-service-name img{margin:0}.pll-wizard-next-steps .pll-wizard-next-step-item{-webkit-flex-wrap:wrap;flex-wrap:wrap}.pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-description{margin-bottom:0}.pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-action p{margin:0}}@media only screen and (max-width:360px){#pll-logo{font-size:48px}} \ No newline at end of file diff --git a/wp-content/plugins/polylang/flags/ad.png b/wp-content/plugins/polylang/flags/ad.png new file mode 100644 index 0000000000..0523f1b2c9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ad.png differ diff --git a/wp-content/plugins/polylang/flags/ae.png b/wp-content/plugins/polylang/flags/ae.png new file mode 100644 index 0000000000..c47bfeb3ff Binary files /dev/null and b/wp-content/plugins/polylang/flags/ae.png differ diff --git a/wp-content/plugins/polylang/flags/af.png b/wp-content/plugins/polylang/flags/af.png new file mode 100644 index 0000000000..b1a1a3b6dd Binary files /dev/null and b/wp-content/plugins/polylang/flags/af.png differ diff --git a/wp-content/plugins/polylang/flags/ag.png b/wp-content/plugins/polylang/flags/ag.png new file mode 100644 index 0000000000..f607adc749 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ag.png differ diff --git a/wp-content/plugins/polylang/flags/ai.png b/wp-content/plugins/polylang/flags/ai.png new file mode 100644 index 0000000000..4aa642544b Binary files /dev/null and b/wp-content/plugins/polylang/flags/ai.png differ diff --git a/wp-content/plugins/polylang/flags/al.png b/wp-content/plugins/polylang/flags/al.png new file mode 100644 index 0000000000..6ffe0ac051 Binary files /dev/null and b/wp-content/plugins/polylang/flags/al.png differ diff --git a/wp-content/plugins/polylang/flags/am.png b/wp-content/plugins/polylang/flags/am.png new file mode 100644 index 0000000000..e7520d7af0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/am.png differ diff --git a/wp-content/plugins/polylang/flags/an.png b/wp-content/plugins/polylang/flags/an.png new file mode 100644 index 0000000000..2b61643ece Binary files /dev/null and b/wp-content/plugins/polylang/flags/an.png differ diff --git a/wp-content/plugins/polylang/flags/ao.png b/wp-content/plugins/polylang/flags/ao.png new file mode 100644 index 0000000000..742882fe1b Binary files /dev/null and b/wp-content/plugins/polylang/flags/ao.png differ diff --git a/wp-content/plugins/polylang/flags/ar.png b/wp-content/plugins/polylang/flags/ar.png new file mode 100644 index 0000000000..46d43f450e Binary files /dev/null and b/wp-content/plugins/polylang/flags/ar.png differ diff --git a/wp-content/plugins/polylang/flags/arab.png b/wp-content/plugins/polylang/flags/arab.png new file mode 100644 index 0000000000..fd70e4a225 Binary files /dev/null and b/wp-content/plugins/polylang/flags/arab.png differ diff --git a/wp-content/plugins/polylang/flags/as.png b/wp-content/plugins/polylang/flags/as.png new file mode 100644 index 0000000000..e66a3ae208 Binary files /dev/null and b/wp-content/plugins/polylang/flags/as.png differ diff --git a/wp-content/plugins/polylang/flags/at.png b/wp-content/plugins/polylang/flags/at.png new file mode 100644 index 0000000000..bed916d97e Binary files /dev/null and b/wp-content/plugins/polylang/flags/at.png differ diff --git a/wp-content/plugins/polylang/flags/au.png b/wp-content/plugins/polylang/flags/au.png new file mode 100644 index 0000000000..8b0d24453e Binary files /dev/null and b/wp-content/plugins/polylang/flags/au.png differ diff --git a/wp-content/plugins/polylang/flags/aw.png b/wp-content/plugins/polylang/flags/aw.png new file mode 100644 index 0000000000..b272f781e4 Binary files /dev/null and b/wp-content/plugins/polylang/flags/aw.png differ diff --git a/wp-content/plugins/polylang/flags/ax.png b/wp-content/plugins/polylang/flags/ax.png new file mode 100644 index 0000000000..256d61e611 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ax.png differ diff --git a/wp-content/plugins/polylang/flags/az.png b/wp-content/plugins/polylang/flags/az.png new file mode 100644 index 0000000000..d9a32de7bc Binary files /dev/null and b/wp-content/plugins/polylang/flags/az.png differ diff --git a/wp-content/plugins/polylang/flags/ba.png b/wp-content/plugins/polylang/flags/ba.png new file mode 100644 index 0000000000..d10cd5cf43 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ba.png differ diff --git a/wp-content/plugins/polylang/flags/basque.png b/wp-content/plugins/polylang/flags/basque.png new file mode 100644 index 0000000000..3b5936951c Binary files /dev/null and b/wp-content/plugins/polylang/flags/basque.png differ diff --git a/wp-content/plugins/polylang/flags/bb.png b/wp-content/plugins/polylang/flags/bb.png new file mode 100644 index 0000000000..e786361eb6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bb.png differ diff --git a/wp-content/plugins/polylang/flags/bd.png b/wp-content/plugins/polylang/flags/bd.png new file mode 100644 index 0000000000..de039dd85f Binary files /dev/null and b/wp-content/plugins/polylang/flags/bd.png differ diff --git a/wp-content/plugins/polylang/flags/be.png b/wp-content/plugins/polylang/flags/be.png new file mode 100644 index 0000000000..94470bc856 Binary files /dev/null and b/wp-content/plugins/polylang/flags/be.png differ diff --git a/wp-content/plugins/polylang/flags/bf.png b/wp-content/plugins/polylang/flags/bf.png new file mode 100644 index 0000000000..167207f3e8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bf.png differ diff --git a/wp-content/plugins/polylang/flags/bg.png b/wp-content/plugins/polylang/flags/bg.png new file mode 100644 index 0000000000..fd9b976673 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bg.png differ diff --git a/wp-content/plugins/polylang/flags/bh.png b/wp-content/plugins/polylang/flags/bh.png new file mode 100644 index 0000000000..1d2ff78595 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bh.png differ diff --git a/wp-content/plugins/polylang/flags/bi.png b/wp-content/plugins/polylang/flags/bi.png new file mode 100644 index 0000000000..2331b802c1 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bi.png differ diff --git a/wp-content/plugins/polylang/flags/bj.png b/wp-content/plugins/polylang/flags/bj.png new file mode 100644 index 0000000000..5405502f4e Binary files /dev/null and b/wp-content/plugins/polylang/flags/bj.png differ diff --git a/wp-content/plugins/polylang/flags/bm.png b/wp-content/plugins/polylang/flags/bm.png new file mode 100644 index 0000000000..7c8046c730 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bm.png differ diff --git a/wp-content/plugins/polylang/flags/bn.png b/wp-content/plugins/polylang/flags/bn.png new file mode 100644 index 0000000000..59c2eb4534 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bn.png differ diff --git a/wp-content/plugins/polylang/flags/bo.png b/wp-content/plugins/polylang/flags/bo.png new file mode 100644 index 0000000000..a7bdabc7bf Binary files /dev/null and b/wp-content/plugins/polylang/flags/bo.png differ diff --git a/wp-content/plugins/polylang/flags/br.png b/wp-content/plugins/polylang/flags/br.png new file mode 100644 index 0000000000..eabb7ba388 Binary files /dev/null and b/wp-content/plugins/polylang/flags/br.png differ diff --git a/wp-content/plugins/polylang/flags/bs.png b/wp-content/plugins/polylang/flags/bs.png new file mode 100644 index 0000000000..741a25ca79 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bs.png differ diff --git a/wp-content/plugins/polylang/flags/bt.png b/wp-content/plugins/polylang/flags/bt.png new file mode 100644 index 0000000000..65571d1b6e Binary files /dev/null and b/wp-content/plugins/polylang/flags/bt.png differ diff --git a/wp-content/plugins/polylang/flags/bw.png b/wp-content/plugins/polylang/flags/bw.png new file mode 100644 index 0000000000..f00a4166e0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bw.png differ diff --git a/wp-content/plugins/polylang/flags/by.png b/wp-content/plugins/polylang/flags/by.png new file mode 100644 index 0000000000..864c604263 Binary files /dev/null and b/wp-content/plugins/polylang/flags/by.png differ diff --git a/wp-content/plugins/polylang/flags/bz.png b/wp-content/plugins/polylang/flags/bz.png new file mode 100644 index 0000000000..b36bc0ad77 Binary files /dev/null and b/wp-content/plugins/polylang/flags/bz.png differ diff --git a/wp-content/plugins/polylang/flags/ca.png b/wp-content/plugins/polylang/flags/ca.png new file mode 100644 index 0000000000..5cbb1260be Binary files /dev/null and b/wp-content/plugins/polylang/flags/ca.png differ diff --git a/wp-content/plugins/polylang/flags/catalonia.png b/wp-content/plugins/polylang/flags/catalonia.png new file mode 100644 index 0000000000..616e1cd14a Binary files /dev/null and b/wp-content/plugins/polylang/flags/catalonia.png differ diff --git a/wp-content/plugins/polylang/flags/cc.png b/wp-content/plugins/polylang/flags/cc.png new file mode 100644 index 0000000000..6c50dbe8a4 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cc.png differ diff --git a/wp-content/plugins/polylang/flags/cd.png b/wp-content/plugins/polylang/flags/cd.png new file mode 100644 index 0000000000..c282ffe63b Binary files /dev/null and b/wp-content/plugins/polylang/flags/cd.png differ diff --git a/wp-content/plugins/polylang/flags/cf.png b/wp-content/plugins/polylang/flags/cf.png new file mode 100644 index 0000000000..b67aa2d09f Binary files /dev/null and b/wp-content/plugins/polylang/flags/cf.png differ diff --git a/wp-content/plugins/polylang/flags/cg.png b/wp-content/plugins/polylang/flags/cg.png new file mode 100644 index 0000000000..ff34693197 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cg.png differ diff --git a/wp-content/plugins/polylang/flags/ch.png b/wp-content/plugins/polylang/flags/ch.png new file mode 100644 index 0000000000..29f6c55bff Binary files /dev/null and b/wp-content/plugins/polylang/flags/ch.png differ diff --git a/wp-content/plugins/polylang/flags/ci.png b/wp-content/plugins/polylang/flags/ci.png new file mode 100644 index 0000000000..ae6c89b703 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ci.png differ diff --git a/wp-content/plugins/polylang/flags/ck.png b/wp-content/plugins/polylang/flags/ck.png new file mode 100644 index 0000000000..dfb2e78a2f Binary files /dev/null and b/wp-content/plugins/polylang/flags/ck.png differ diff --git a/wp-content/plugins/polylang/flags/cl.png b/wp-content/plugins/polylang/flags/cl.png new file mode 100644 index 0000000000..6d3f3b69d6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cl.png differ diff --git a/wp-content/plugins/polylang/flags/cm.png b/wp-content/plugins/polylang/flags/cm.png new file mode 100644 index 0000000000..a655979f4d Binary files /dev/null and b/wp-content/plugins/polylang/flags/cm.png differ diff --git a/wp-content/plugins/polylang/flags/cn.png b/wp-content/plugins/polylang/flags/cn.png new file mode 100644 index 0000000000..f64afc15d7 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cn.png differ diff --git a/wp-content/plugins/polylang/flags/co.png b/wp-content/plugins/polylang/flags/co.png new file mode 100644 index 0000000000..b62e131545 Binary files /dev/null and b/wp-content/plugins/polylang/flags/co.png differ diff --git a/wp-content/plugins/polylang/flags/cr.png b/wp-content/plugins/polylang/flags/cr.png new file mode 100644 index 0000000000..55139ecf2c Binary files /dev/null and b/wp-content/plugins/polylang/flags/cr.png differ diff --git a/wp-content/plugins/polylang/flags/cu.png b/wp-content/plugins/polylang/flags/cu.png new file mode 100644 index 0000000000..859e59823d Binary files /dev/null and b/wp-content/plugins/polylang/flags/cu.png differ diff --git a/wp-content/plugins/polylang/flags/cv.png b/wp-content/plugins/polylang/flags/cv.png new file mode 100644 index 0000000000..cef21f92fa Binary files /dev/null and b/wp-content/plugins/polylang/flags/cv.png differ diff --git a/wp-content/plugins/polylang/flags/cx.png b/wp-content/plugins/polylang/flags/cx.png new file mode 100644 index 0000000000..a2d748e48c Binary files /dev/null and b/wp-content/plugins/polylang/flags/cx.png differ diff --git a/wp-content/plugins/polylang/flags/cy.png b/wp-content/plugins/polylang/flags/cy.png new file mode 100644 index 0000000000..7f9ed40d38 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cy.png differ diff --git a/wp-content/plugins/polylang/flags/cz.png b/wp-content/plugins/polylang/flags/cz.png new file mode 100644 index 0000000000..6c2c5656e9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/cz.png differ diff --git a/wp-content/plugins/polylang/flags/de.png b/wp-content/plugins/polylang/flags/de.png new file mode 100644 index 0000000000..41bc81b64c Binary files /dev/null and b/wp-content/plugins/polylang/flags/de.png differ diff --git a/wp-content/plugins/polylang/flags/dj.png b/wp-content/plugins/polylang/flags/dj.png new file mode 100644 index 0000000000..fb82dcc92d Binary files /dev/null and b/wp-content/plugins/polylang/flags/dj.png differ diff --git a/wp-content/plugins/polylang/flags/dk.png b/wp-content/plugins/polylang/flags/dk.png new file mode 100644 index 0000000000..3b9282c232 Binary files /dev/null and b/wp-content/plugins/polylang/flags/dk.png differ diff --git a/wp-content/plugins/polylang/flags/dm.png b/wp-content/plugins/polylang/flags/dm.png new file mode 100644 index 0000000000..5488cb52e0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/dm.png differ diff --git a/wp-content/plugins/polylang/flags/do.png b/wp-content/plugins/polylang/flags/do.png new file mode 100644 index 0000000000..d27ef3d79c Binary files /dev/null and b/wp-content/plugins/polylang/flags/do.png differ diff --git a/wp-content/plugins/polylang/flags/dz.png b/wp-content/plugins/polylang/flags/dz.png new file mode 100644 index 0000000000..78a7be65bc Binary files /dev/null and b/wp-content/plugins/polylang/flags/dz.png differ diff --git a/wp-content/plugins/polylang/flags/ec.png b/wp-content/plugins/polylang/flags/ec.png new file mode 100644 index 0000000000..fef746c2d7 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ec.png differ diff --git a/wp-content/plugins/polylang/flags/ee.png b/wp-content/plugins/polylang/flags/ee.png new file mode 100644 index 0000000000..e49b1b47fb Binary files /dev/null and b/wp-content/plugins/polylang/flags/ee.png differ diff --git a/wp-content/plugins/polylang/flags/eg.png b/wp-content/plugins/polylang/flags/eg.png new file mode 100644 index 0000000000..f37f60a6d9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/eg.png differ diff --git a/wp-content/plugins/polylang/flags/eh.png b/wp-content/plugins/polylang/flags/eh.png new file mode 100644 index 0000000000..49bff039a0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/eh.png differ diff --git a/wp-content/plugins/polylang/flags/england.png b/wp-content/plugins/polylang/flags/england.png new file mode 100644 index 0000000000..e0984c3008 Binary files /dev/null and b/wp-content/plugins/polylang/flags/england.png differ diff --git a/wp-content/plugins/polylang/flags/er.png b/wp-content/plugins/polylang/flags/er.png new file mode 100644 index 0000000000..5d6d16cb7f Binary files /dev/null and b/wp-content/plugins/polylang/flags/er.png differ diff --git a/wp-content/plugins/polylang/flags/es.png b/wp-content/plugins/polylang/flags/es.png new file mode 100644 index 0000000000..04c65aa9a7 Binary files /dev/null and b/wp-content/plugins/polylang/flags/es.png differ diff --git a/wp-content/plugins/polylang/flags/esperanto.png b/wp-content/plugins/polylang/flags/esperanto.png new file mode 100644 index 0000000000..00306b663f Binary files /dev/null and b/wp-content/plugins/polylang/flags/esperanto.png differ diff --git a/wp-content/plugins/polylang/flags/et.png b/wp-content/plugins/polylang/flags/et.png new file mode 100644 index 0000000000..45a622fe20 Binary files /dev/null and b/wp-content/plugins/polylang/flags/et.png differ diff --git a/wp-content/plugins/polylang/flags/fi.png b/wp-content/plugins/polylang/flags/fi.png new file mode 100644 index 0000000000..a1a843f4a5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/fi.png differ diff --git a/wp-content/plugins/polylang/flags/fj.png b/wp-content/plugins/polylang/flags/fj.png new file mode 100644 index 0000000000..308d1ecd19 Binary files /dev/null and b/wp-content/plugins/polylang/flags/fj.png differ diff --git a/wp-content/plugins/polylang/flags/fk.png b/wp-content/plugins/polylang/flags/fk.png new file mode 100644 index 0000000000..9a1c740e3f Binary files /dev/null and b/wp-content/plugins/polylang/flags/fk.png differ diff --git a/wp-content/plugins/polylang/flags/fm.png b/wp-content/plugins/polylang/flags/fm.png new file mode 100644 index 0000000000..b97a1248ca Binary files /dev/null and b/wp-content/plugins/polylang/flags/fm.png differ diff --git a/wp-content/plugins/polylang/flags/fo.png b/wp-content/plugins/polylang/flags/fo.png new file mode 100644 index 0000000000..4cad1fc6d5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/fo.png differ diff --git a/wp-content/plugins/polylang/flags/fr.png b/wp-content/plugins/polylang/flags/fr.png new file mode 100644 index 0000000000..9e443e685c Binary files /dev/null and b/wp-content/plugins/polylang/flags/fr.png differ diff --git a/wp-content/plugins/polylang/flags/ga.png b/wp-content/plugins/polylang/flags/ga.png new file mode 100644 index 0000000000..8c902acaa5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ga.png differ diff --git a/wp-content/plugins/polylang/flags/galicia.png b/wp-content/plugins/polylang/flags/galicia.png new file mode 100644 index 0000000000..ed6568761b Binary files /dev/null and b/wp-content/plugins/polylang/flags/galicia.png differ diff --git a/wp-content/plugins/polylang/flags/gb.png b/wp-content/plugins/polylang/flags/gb.png new file mode 100644 index 0000000000..f8c4cbdbdf Binary files /dev/null and b/wp-content/plugins/polylang/flags/gb.png differ diff --git a/wp-content/plugins/polylang/flags/gd.png b/wp-content/plugins/polylang/flags/gd.png new file mode 100644 index 0000000000..060fe2b967 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gd.png differ diff --git a/wp-content/plugins/polylang/flags/ge.png b/wp-content/plugins/polylang/flags/ge.png new file mode 100644 index 0000000000..26cbbc9dc9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ge.png differ diff --git a/wp-content/plugins/polylang/flags/gh.png b/wp-content/plugins/polylang/flags/gh.png new file mode 100644 index 0000000000..573e1b1e6e Binary files /dev/null and b/wp-content/plugins/polylang/flags/gh.png differ diff --git a/wp-content/plugins/polylang/flags/gi.png b/wp-content/plugins/polylang/flags/gi.png new file mode 100644 index 0000000000..412a6428b9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gi.png differ diff --git a/wp-content/plugins/polylang/flags/gl.png b/wp-content/plugins/polylang/flags/gl.png new file mode 100644 index 0000000000..9a376c8eef Binary files /dev/null and b/wp-content/plugins/polylang/flags/gl.png differ diff --git a/wp-content/plugins/polylang/flags/gm.png b/wp-content/plugins/polylang/flags/gm.png new file mode 100644 index 0000000000..d39aa5b6f7 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gm.png differ diff --git a/wp-content/plugins/polylang/flags/gn.png b/wp-content/plugins/polylang/flags/gn.png new file mode 100644 index 0000000000..e6e8ec88d5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gn.png differ diff --git a/wp-content/plugins/polylang/flags/gp.png b/wp-content/plugins/polylang/flags/gp.png new file mode 100644 index 0000000000..aceeea9251 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gp.png differ diff --git a/wp-content/plugins/polylang/flags/gq.png b/wp-content/plugins/polylang/flags/gq.png new file mode 100644 index 0000000000..5450d0b99a Binary files /dev/null and b/wp-content/plugins/polylang/flags/gq.png differ diff --git a/wp-content/plugins/polylang/flags/gr.png b/wp-content/plugins/polylang/flags/gr.png new file mode 100644 index 0000000000..57afba596d Binary files /dev/null and b/wp-content/plugins/polylang/flags/gr.png differ diff --git a/wp-content/plugins/polylang/flags/gs.png b/wp-content/plugins/polylang/flags/gs.png new file mode 100644 index 0000000000..2efa092bd6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gs.png differ diff --git a/wp-content/plugins/polylang/flags/gt.png b/wp-content/plugins/polylang/flags/gt.png new file mode 100644 index 0000000000..bcd0e148c5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gt.png differ diff --git a/wp-content/plugins/polylang/flags/gu.png b/wp-content/plugins/polylang/flags/gu.png new file mode 100644 index 0000000000..1e7644e099 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gu.png differ diff --git a/wp-content/plugins/polylang/flags/gw.png b/wp-content/plugins/polylang/flags/gw.png new file mode 100644 index 0000000000..762cdee121 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gw.png differ diff --git a/wp-content/plugins/polylang/flags/gy.png b/wp-content/plugins/polylang/flags/gy.png new file mode 100644 index 0000000000..e90b8f7899 Binary files /dev/null and b/wp-content/plugins/polylang/flags/gy.png differ diff --git a/wp-content/plugins/polylang/flags/hk.png b/wp-content/plugins/polylang/flags/hk.png new file mode 100644 index 0000000000..d285f57c9a Binary files /dev/null and b/wp-content/plugins/polylang/flags/hk.png differ diff --git a/wp-content/plugins/polylang/flags/hm.png b/wp-content/plugins/polylang/flags/hm.png new file mode 100644 index 0000000000..8b0d24453e Binary files /dev/null and b/wp-content/plugins/polylang/flags/hm.png differ diff --git a/wp-content/plugins/polylang/flags/hn.png b/wp-content/plugins/polylang/flags/hn.png new file mode 100644 index 0000000000..d108cd019e Binary files /dev/null and b/wp-content/plugins/polylang/flags/hn.png differ diff --git a/wp-content/plugins/polylang/flags/hr.png b/wp-content/plugins/polylang/flags/hr.png new file mode 100644 index 0000000000..ced0780997 Binary files /dev/null and b/wp-content/plugins/polylang/flags/hr.png differ diff --git a/wp-content/plugins/polylang/flags/ht.png b/wp-content/plugins/polylang/flags/ht.png new file mode 100644 index 0000000000..bc5a385739 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ht.png differ diff --git a/wp-content/plugins/polylang/flags/hu.png b/wp-content/plugins/polylang/flags/hu.png new file mode 100644 index 0000000000..3ce9369535 Binary files /dev/null and b/wp-content/plugins/polylang/flags/hu.png differ diff --git a/wp-content/plugins/polylang/flags/id.png b/wp-content/plugins/polylang/flags/id.png new file mode 100644 index 0000000000..fc6b7b990a Binary files /dev/null and b/wp-content/plugins/polylang/flags/id.png differ diff --git a/wp-content/plugins/polylang/flags/ie.png b/wp-content/plugins/polylang/flags/ie.png new file mode 100644 index 0000000000..0432bc7795 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ie.png differ diff --git a/wp-content/plugins/polylang/flags/il.png b/wp-content/plugins/polylang/flags/il.png new file mode 100644 index 0000000000..315ac318a5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/il.png differ diff --git a/wp-content/plugins/polylang/flags/in.png b/wp-content/plugins/polylang/flags/in.png new file mode 100644 index 0000000000..14f49a5932 Binary files /dev/null and b/wp-content/plugins/polylang/flags/in.png differ diff --git a/wp-content/plugins/polylang/flags/io.png b/wp-content/plugins/polylang/flags/io.png new file mode 100644 index 0000000000..e928207376 Binary files /dev/null and b/wp-content/plugins/polylang/flags/io.png differ diff --git a/wp-content/plugins/polylang/flags/iq.png b/wp-content/plugins/polylang/flags/iq.png new file mode 100644 index 0000000000..989cdc82d8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/iq.png differ diff --git a/wp-content/plugins/polylang/flags/ir.png b/wp-content/plugins/polylang/flags/ir.png new file mode 100644 index 0000000000..bdbf70da31 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ir.png differ diff --git a/wp-content/plugins/polylang/flags/is.png b/wp-content/plugins/polylang/flags/is.png new file mode 100644 index 0000000000..3dd06dba8e Binary files /dev/null and b/wp-content/plugins/polylang/flags/is.png differ diff --git a/wp-content/plugins/polylang/flags/it.png b/wp-content/plugins/polylang/flags/it.png new file mode 100644 index 0000000000..9dc1a86bc5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/it.png differ diff --git a/wp-content/plugins/polylang/flags/jm.png b/wp-content/plugins/polylang/flags/jm.png new file mode 100644 index 0000000000..83fcc75601 Binary files /dev/null and b/wp-content/plugins/polylang/flags/jm.png differ diff --git a/wp-content/plugins/polylang/flags/jo.png b/wp-content/plugins/polylang/flags/jo.png new file mode 100644 index 0000000000..c6c11adab0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/jo.png differ diff --git a/wp-content/plugins/polylang/flags/jp.png b/wp-content/plugins/polylang/flags/jp.png new file mode 100644 index 0000000000..82168d961d Binary files /dev/null and b/wp-content/plugins/polylang/flags/jp.png differ diff --git a/wp-content/plugins/polylang/flags/ke.png b/wp-content/plugins/polylang/flags/ke.png new file mode 100644 index 0000000000..33306eea18 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ke.png differ diff --git a/wp-content/plugins/polylang/flags/kg.png b/wp-content/plugins/polylang/flags/kg.png new file mode 100644 index 0000000000..73f997f22c Binary files /dev/null and b/wp-content/plugins/polylang/flags/kg.png differ diff --git a/wp-content/plugins/polylang/flags/kh.png b/wp-content/plugins/polylang/flags/kh.png new file mode 100644 index 0000000000..ee718d9dcd Binary files /dev/null and b/wp-content/plugins/polylang/flags/kh.png differ diff --git a/wp-content/plugins/polylang/flags/ki.png b/wp-content/plugins/polylang/flags/ki.png new file mode 100644 index 0000000000..2835c4605f Binary files /dev/null and b/wp-content/plugins/polylang/flags/ki.png differ diff --git a/wp-content/plugins/polylang/flags/km.png b/wp-content/plugins/polylang/flags/km.png new file mode 100644 index 0000000000..2ee55b1caa Binary files /dev/null and b/wp-content/plugins/polylang/flags/km.png differ diff --git a/wp-content/plugins/polylang/flags/kn.png b/wp-content/plugins/polylang/flags/kn.png new file mode 100644 index 0000000000..f3f43999ad Binary files /dev/null and b/wp-content/plugins/polylang/flags/kn.png differ diff --git a/wp-content/plugins/polylang/flags/kp.png b/wp-content/plugins/polylang/flags/kp.png new file mode 100644 index 0000000000..c55ebef1a2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/kp.png differ diff --git a/wp-content/plugins/polylang/flags/kr.png b/wp-content/plugins/polylang/flags/kr.png new file mode 100644 index 0000000000..ea24bd06a6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/kr.png differ diff --git a/wp-content/plugins/polylang/flags/kurdistan.png b/wp-content/plugins/polylang/flags/kurdistan.png new file mode 100644 index 0000000000..fded8c5dbf Binary files /dev/null and b/wp-content/plugins/polylang/flags/kurdistan.png differ diff --git a/wp-content/plugins/polylang/flags/kw.png b/wp-content/plugins/polylang/flags/kw.png new file mode 100644 index 0000000000..9e852ebb59 Binary files /dev/null and b/wp-content/plugins/polylang/flags/kw.png differ diff --git a/wp-content/plugins/polylang/flags/ky.png b/wp-content/plugins/polylang/flags/ky.png new file mode 100644 index 0000000000..c2cd820172 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ky.png differ diff --git a/wp-content/plugins/polylang/flags/kz.png b/wp-content/plugins/polylang/flags/kz.png new file mode 100644 index 0000000000..52f6cfec2e Binary files /dev/null and b/wp-content/plugins/polylang/flags/kz.png differ diff --git a/wp-content/plugins/polylang/flags/la.png b/wp-content/plugins/polylang/flags/la.png new file mode 100644 index 0000000000..a93fb5da79 Binary files /dev/null and b/wp-content/plugins/polylang/flags/la.png differ diff --git a/wp-content/plugins/polylang/flags/lb.png b/wp-content/plugins/polylang/flags/lb.png new file mode 100644 index 0000000000..ba5df97e44 Binary files /dev/null and b/wp-content/plugins/polylang/flags/lb.png differ diff --git a/wp-content/plugins/polylang/flags/lc.png b/wp-content/plugins/polylang/flags/lc.png new file mode 100644 index 0000000000..72526dbd80 Binary files /dev/null and b/wp-content/plugins/polylang/flags/lc.png differ diff --git a/wp-content/plugins/polylang/flags/li.png b/wp-content/plugins/polylang/flags/li.png new file mode 100644 index 0000000000..cac346ba8a Binary files /dev/null and b/wp-content/plugins/polylang/flags/li.png differ diff --git a/wp-content/plugins/polylang/flags/lk.png b/wp-content/plugins/polylang/flags/lk.png new file mode 100644 index 0000000000..9942f39e66 Binary files /dev/null and b/wp-content/plugins/polylang/flags/lk.png differ diff --git a/wp-content/plugins/polylang/flags/lr.png b/wp-content/plugins/polylang/flags/lr.png new file mode 100644 index 0000000000..175bcd8e4d Binary files /dev/null and b/wp-content/plugins/polylang/flags/lr.png differ diff --git a/wp-content/plugins/polylang/flags/ls.png b/wp-content/plugins/polylang/flags/ls.png new file mode 100644 index 0000000000..3c0f37fd61 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ls.png differ diff --git a/wp-content/plugins/polylang/flags/lt.png b/wp-content/plugins/polylang/flags/lt.png new file mode 100644 index 0000000000..9860367ef2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/lt.png differ diff --git a/wp-content/plugins/polylang/flags/lu.png b/wp-content/plugins/polylang/flags/lu.png new file mode 100644 index 0000000000..cf14435c7f Binary files /dev/null and b/wp-content/plugins/polylang/flags/lu.png differ diff --git a/wp-content/plugins/polylang/flags/lv.png b/wp-content/plugins/polylang/flags/lv.png new file mode 100644 index 0000000000..ec616e835b Binary files /dev/null and b/wp-content/plugins/polylang/flags/lv.png differ diff --git a/wp-content/plugins/polylang/flags/ly.png b/wp-content/plugins/polylang/flags/ly.png new file mode 100644 index 0000000000..e04ecb376e Binary files /dev/null and b/wp-content/plugins/polylang/flags/ly.png differ diff --git a/wp-content/plugins/polylang/flags/ma.png b/wp-content/plugins/polylang/flags/ma.png new file mode 100644 index 0000000000..a96ae45788 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ma.png differ diff --git a/wp-content/plugins/polylang/flags/mc.png b/wp-content/plugins/polylang/flags/mc.png new file mode 100644 index 0000000000..a4a03893e8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/mc.png differ diff --git a/wp-content/plugins/polylang/flags/md.png b/wp-content/plugins/polylang/flags/md.png new file mode 100644 index 0000000000..4faa1d1b32 Binary files /dev/null and b/wp-content/plugins/polylang/flags/md.png differ diff --git a/wp-content/plugins/polylang/flags/me.png b/wp-content/plugins/polylang/flags/me.png new file mode 100644 index 0000000000..70d7b4f577 Binary files /dev/null and b/wp-content/plugins/polylang/flags/me.png differ diff --git a/wp-content/plugins/polylang/flags/mg.png b/wp-content/plugins/polylang/flags/mg.png new file mode 100644 index 0000000000..fd71126afd Binary files /dev/null and b/wp-content/plugins/polylang/flags/mg.png differ diff --git a/wp-content/plugins/polylang/flags/mh.png b/wp-content/plugins/polylang/flags/mh.png new file mode 100644 index 0000000000..572b2cc0eb Binary files /dev/null and b/wp-content/plugins/polylang/flags/mh.png differ diff --git a/wp-content/plugins/polylang/flags/mk.png b/wp-content/plugins/polylang/flags/mk.png new file mode 100644 index 0000000000..5b495c7810 Binary files /dev/null and b/wp-content/plugins/polylang/flags/mk.png differ diff --git a/wp-content/plugins/polylang/flags/ml.png b/wp-content/plugins/polylang/flags/ml.png new file mode 100644 index 0000000000..83d2d3ceee Binary files /dev/null and b/wp-content/plugins/polylang/flags/ml.png differ diff --git a/wp-content/plugins/polylang/flags/mm.png b/wp-content/plugins/polylang/flags/mm.png new file mode 100644 index 0000000000..c820369e7e Binary files /dev/null and b/wp-content/plugins/polylang/flags/mm.png differ diff --git a/wp-content/plugins/polylang/flags/mn.png b/wp-content/plugins/polylang/flags/mn.png new file mode 100644 index 0000000000..99af9598aa Binary files /dev/null and b/wp-content/plugins/polylang/flags/mn.png differ diff --git a/wp-content/plugins/polylang/flags/mo.png b/wp-content/plugins/polylang/flags/mo.png new file mode 100644 index 0000000000..179edf3f5d Binary files /dev/null and b/wp-content/plugins/polylang/flags/mo.png differ diff --git a/wp-content/plugins/polylang/flags/mp.png b/wp-content/plugins/polylang/flags/mp.png new file mode 100644 index 0000000000..5e51851f2d Binary files /dev/null and b/wp-content/plugins/polylang/flags/mp.png differ diff --git a/wp-content/plugins/polylang/flags/mq.png b/wp-content/plugins/polylang/flags/mq.png new file mode 100644 index 0000000000..16a24fce6b Binary files /dev/null and b/wp-content/plugins/polylang/flags/mq.png differ diff --git a/wp-content/plugins/polylang/flags/mr.png b/wp-content/plugins/polylang/flags/mr.png new file mode 100644 index 0000000000..60bc15f884 Binary files /dev/null and b/wp-content/plugins/polylang/flags/mr.png differ diff --git a/wp-content/plugins/polylang/flags/ms.png b/wp-content/plugins/polylang/flags/ms.png new file mode 100644 index 0000000000..19cb7b631a Binary files /dev/null and b/wp-content/plugins/polylang/flags/ms.png differ diff --git a/wp-content/plugins/polylang/flags/mt.png b/wp-content/plugins/polylang/flags/mt.png new file mode 100644 index 0000000000..32e460ce9f Binary files /dev/null and b/wp-content/plugins/polylang/flags/mt.png differ diff --git a/wp-content/plugins/polylang/flags/mu.png b/wp-content/plugins/polylang/flags/mu.png new file mode 100644 index 0000000000..6219cb677a Binary files /dev/null and b/wp-content/plugins/polylang/flags/mu.png differ diff --git a/wp-content/plugins/polylang/flags/mv.png b/wp-content/plugins/polylang/flags/mv.png new file mode 100644 index 0000000000..22a5982d7b Binary files /dev/null and b/wp-content/plugins/polylang/flags/mv.png differ diff --git a/wp-content/plugins/polylang/flags/mw.png b/wp-content/plugins/polylang/flags/mw.png new file mode 100644 index 0000000000..7b72870d2e Binary files /dev/null and b/wp-content/plugins/polylang/flags/mw.png differ diff --git a/wp-content/plugins/polylang/flags/mx.png b/wp-content/plugins/polylang/flags/mx.png new file mode 100644 index 0000000000..233d6c5257 Binary files /dev/null and b/wp-content/plugins/polylang/flags/mx.png differ diff --git a/wp-content/plugins/polylang/flags/my.png b/wp-content/plugins/polylang/flags/my.png new file mode 100644 index 0000000000..ef4fc0700c Binary files /dev/null and b/wp-content/plugins/polylang/flags/my.png differ diff --git a/wp-content/plugins/polylang/flags/mz.png b/wp-content/plugins/polylang/flags/mz.png new file mode 100644 index 0000000000..c120273959 Binary files /dev/null and b/wp-content/plugins/polylang/flags/mz.png differ diff --git a/wp-content/plugins/polylang/flags/na.png b/wp-content/plugins/polylang/flags/na.png new file mode 100644 index 0000000000..cb1488aa90 Binary files /dev/null and b/wp-content/plugins/polylang/flags/na.png differ diff --git a/wp-content/plugins/polylang/flags/nc.png b/wp-content/plugins/polylang/flags/nc.png new file mode 100644 index 0000000000..bf83cce3d4 Binary files /dev/null and b/wp-content/plugins/polylang/flags/nc.png differ diff --git a/wp-content/plugins/polylang/flags/ne.png b/wp-content/plugins/polylang/flags/ne.png new file mode 100644 index 0000000000..1db0c05b68 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ne.png differ diff --git a/wp-content/plugins/polylang/flags/nf.png b/wp-content/plugins/polylang/flags/nf.png new file mode 100644 index 0000000000..225e608c33 Binary files /dev/null and b/wp-content/plugins/polylang/flags/nf.png differ diff --git a/wp-content/plugins/polylang/flags/ng.png b/wp-content/plugins/polylang/flags/ng.png new file mode 100644 index 0000000000..71db60520e Binary files /dev/null and b/wp-content/plugins/polylang/flags/ng.png differ diff --git a/wp-content/plugins/polylang/flags/ni.png b/wp-content/plugins/polylang/flags/ni.png new file mode 100644 index 0000000000..8a3df9f4c8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ni.png differ diff --git a/wp-content/plugins/polylang/flags/nl.png b/wp-content/plugins/polylang/flags/nl.png new file mode 100644 index 0000000000..34b52a24af Binary files /dev/null and b/wp-content/plugins/polylang/flags/nl.png differ diff --git a/wp-content/plugins/polylang/flags/no.png b/wp-content/plugins/polylang/flags/no.png new file mode 100644 index 0000000000..0f98f5e2aa Binary files /dev/null and b/wp-content/plugins/polylang/flags/no.png differ diff --git a/wp-content/plugins/polylang/flags/np.png b/wp-content/plugins/polylang/flags/np.png new file mode 100644 index 0000000000..4fda5d66c8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/np.png differ diff --git a/wp-content/plugins/polylang/flags/nr.png b/wp-content/plugins/polylang/flags/nr.png new file mode 100644 index 0000000000..e4d797778f Binary files /dev/null and b/wp-content/plugins/polylang/flags/nr.png differ diff --git a/wp-content/plugins/polylang/flags/nu.png b/wp-content/plugins/polylang/flags/nu.png new file mode 100644 index 0000000000..350a9ddd89 Binary files /dev/null and b/wp-content/plugins/polylang/flags/nu.png differ diff --git a/wp-content/plugins/polylang/flags/nz.png b/wp-content/plugins/polylang/flags/nz.png new file mode 100644 index 0000000000..c83bd1ab20 Binary files /dev/null and b/wp-content/plugins/polylang/flags/nz.png differ diff --git a/wp-content/plugins/polylang/flags/occitania.png b/wp-content/plugins/polylang/flags/occitania.png new file mode 100644 index 0000000000..08688df9b0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/occitania.png differ diff --git a/wp-content/plugins/polylang/flags/om.png b/wp-content/plugins/polylang/flags/om.png new file mode 100644 index 0000000000..2eb3a1553f Binary files /dev/null and b/wp-content/plugins/polylang/flags/om.png differ diff --git a/wp-content/plugins/polylang/flags/pa.png b/wp-content/plugins/polylang/flags/pa.png new file mode 100644 index 0000000000..1f84ae5a99 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pa.png differ diff --git a/wp-content/plugins/polylang/flags/pe.png b/wp-content/plugins/polylang/flags/pe.png new file mode 100644 index 0000000000..342d003b49 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pe.png differ diff --git a/wp-content/plugins/polylang/flags/pf.png b/wp-content/plugins/polylang/flags/pf.png new file mode 100644 index 0000000000..4663091b2f Binary files /dev/null and b/wp-content/plugins/polylang/flags/pf.png differ diff --git a/wp-content/plugins/polylang/flags/pg.png b/wp-content/plugins/polylang/flags/pg.png new file mode 100644 index 0000000000..46d821896b Binary files /dev/null and b/wp-content/plugins/polylang/flags/pg.png differ diff --git a/wp-content/plugins/polylang/flags/ph.png b/wp-content/plugins/polylang/flags/ph.png new file mode 100644 index 0000000000..7aeee8c581 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ph.png differ diff --git a/wp-content/plugins/polylang/flags/pk.png b/wp-content/plugins/polylang/flags/pk.png new file mode 100644 index 0000000000..e898448cfb Binary files /dev/null and b/wp-content/plugins/polylang/flags/pk.png differ diff --git a/wp-content/plugins/polylang/flags/pl.png b/wp-content/plugins/polylang/flags/pl.png new file mode 100644 index 0000000000..10da179abb Binary files /dev/null and b/wp-content/plugins/polylang/flags/pl.png differ diff --git a/wp-content/plugins/polylang/flags/pm.png b/wp-content/plugins/polylang/flags/pm.png new file mode 100644 index 0000000000..b6cd50f243 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pm.png differ diff --git a/wp-content/plugins/polylang/flags/pn.png b/wp-content/plugins/polylang/flags/pn.png new file mode 100644 index 0000000000..600994df54 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pn.png differ diff --git a/wp-content/plugins/polylang/flags/pr.png b/wp-content/plugins/polylang/flags/pr.png new file mode 100644 index 0000000000..cf104c42c9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pr.png differ diff --git a/wp-content/plugins/polylang/flags/ps.png b/wp-content/plugins/polylang/flags/ps.png new file mode 100644 index 0000000000..e313f31837 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ps.png differ diff --git a/wp-content/plugins/polylang/flags/pt.png b/wp-content/plugins/polylang/flags/pt.png new file mode 100644 index 0000000000..c90c4456d1 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pt.png differ diff --git a/wp-content/plugins/polylang/flags/pw.png b/wp-content/plugins/polylang/flags/pw.png new file mode 100644 index 0000000000..b1a4f4fca2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/pw.png differ diff --git a/wp-content/plugins/polylang/flags/py.png b/wp-content/plugins/polylang/flags/py.png new file mode 100644 index 0000000000..0f143ffe6f Binary files /dev/null and b/wp-content/plugins/polylang/flags/py.png differ diff --git a/wp-content/plugins/polylang/flags/qa.png b/wp-content/plugins/polylang/flags/qa.png new file mode 100644 index 0000000000..9945c68540 Binary files /dev/null and b/wp-content/plugins/polylang/flags/qa.png differ diff --git a/wp-content/plugins/polylang/flags/quebec.png b/wp-content/plugins/polylang/flags/quebec.png new file mode 100644 index 0000000000..8c34644ad3 Binary files /dev/null and b/wp-content/plugins/polylang/flags/quebec.png differ diff --git a/wp-content/plugins/polylang/flags/ro.png b/wp-content/plugins/polylang/flags/ro.png new file mode 100644 index 0000000000..24e989a3a0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ro.png differ diff --git a/wp-content/plugins/polylang/flags/rs.png b/wp-content/plugins/polylang/flags/rs.png new file mode 100644 index 0000000000..8fb41a5d09 Binary files /dev/null and b/wp-content/plugins/polylang/flags/rs.png differ diff --git a/wp-content/plugins/polylang/flags/ru.png b/wp-content/plugins/polylang/flags/ru.png new file mode 100644 index 0000000000..2c5f1337a2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ru.png differ diff --git a/wp-content/plugins/polylang/flags/rw.png b/wp-content/plugins/polylang/flags/rw.png new file mode 100644 index 0000000000..2ce1645669 Binary files /dev/null and b/wp-content/plugins/polylang/flags/rw.png differ diff --git a/wp-content/plugins/polylang/flags/sa.png b/wp-content/plugins/polylang/flags/sa.png new file mode 100644 index 0000000000..0246b1a6ac Binary files /dev/null and b/wp-content/plugins/polylang/flags/sa.png differ diff --git a/wp-content/plugins/polylang/flags/sb.png b/wp-content/plugins/polylang/flags/sb.png new file mode 100644 index 0000000000..da51301e33 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sb.png differ diff --git a/wp-content/plugins/polylang/flags/sc.png b/wp-content/plugins/polylang/flags/sc.png new file mode 100644 index 0000000000..52408113d5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sc.png differ diff --git a/wp-content/plugins/polylang/flags/scotland.png b/wp-content/plugins/polylang/flags/scotland.png new file mode 100644 index 0000000000..eda0d49132 Binary files /dev/null and b/wp-content/plugins/polylang/flags/scotland.png differ diff --git a/wp-content/plugins/polylang/flags/sd.png b/wp-content/plugins/polylang/flags/sd.png new file mode 100644 index 0000000000..20fb230579 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sd.png differ diff --git a/wp-content/plugins/polylang/flags/se.png b/wp-content/plugins/polylang/flags/se.png new file mode 100644 index 0000000000..4f66b47220 Binary files /dev/null and b/wp-content/plugins/polylang/flags/se.png differ diff --git a/wp-content/plugins/polylang/flags/sg.png b/wp-content/plugins/polylang/flags/sg.png new file mode 100644 index 0000000000..a4e86518f2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sg.png differ diff --git a/wp-content/plugins/polylang/flags/sh.png b/wp-content/plugins/polylang/flags/sh.png new file mode 100644 index 0000000000..6ac0d80520 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sh.png differ diff --git a/wp-content/plugins/polylang/flags/si.png b/wp-content/plugins/polylang/flags/si.png new file mode 100644 index 0000000000..36f0a9599f Binary files /dev/null and b/wp-content/plugins/polylang/flags/si.png differ diff --git a/wp-content/plugins/polylang/flags/sk.png b/wp-content/plugins/polylang/flags/sk.png new file mode 100644 index 0000000000..5b613d99ea Binary files /dev/null and b/wp-content/plugins/polylang/flags/sk.png differ diff --git a/wp-content/plugins/polylang/flags/sl.png b/wp-content/plugins/polylang/flags/sl.png new file mode 100644 index 0000000000..72c6af4273 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sl.png differ diff --git a/wp-content/plugins/polylang/flags/sm.png b/wp-content/plugins/polylang/flags/sm.png new file mode 100644 index 0000000000..fe9a6bb9ed Binary files /dev/null and b/wp-content/plugins/polylang/flags/sm.png differ diff --git a/wp-content/plugins/polylang/flags/sn.png b/wp-content/plugins/polylang/flags/sn.png new file mode 100644 index 0000000000..889c002a2a Binary files /dev/null and b/wp-content/plugins/polylang/flags/sn.png differ diff --git a/wp-content/plugins/polylang/flags/so.png b/wp-content/plugins/polylang/flags/so.png new file mode 100644 index 0000000000..843606fd9d Binary files /dev/null and b/wp-content/plugins/polylang/flags/so.png differ diff --git a/wp-content/plugins/polylang/flags/sr.png b/wp-content/plugins/polylang/flags/sr.png new file mode 100644 index 0000000000..b1684cb1f5 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sr.png differ diff --git a/wp-content/plugins/polylang/flags/ss.png b/wp-content/plugins/polylang/flags/ss.png new file mode 100644 index 0000000000..833eef2409 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ss.png differ diff --git a/wp-content/plugins/polylang/flags/st.png b/wp-content/plugins/polylang/flags/st.png new file mode 100644 index 0000000000..f1b0b531c2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/st.png differ diff --git a/wp-content/plugins/polylang/flags/sv.png b/wp-content/plugins/polylang/flags/sv.png new file mode 100644 index 0000000000..70ac44f23c Binary files /dev/null and b/wp-content/plugins/polylang/flags/sv.png differ diff --git a/wp-content/plugins/polylang/flags/sy.png b/wp-content/plugins/polylang/flags/sy.png new file mode 100644 index 0000000000..a39ebefb0d Binary files /dev/null and b/wp-content/plugins/polylang/flags/sy.png differ diff --git a/wp-content/plugins/polylang/flags/sz.png b/wp-content/plugins/polylang/flags/sz.png new file mode 100644 index 0000000000..9ae4851238 Binary files /dev/null and b/wp-content/plugins/polylang/flags/sz.png differ diff --git a/wp-content/plugins/polylang/flags/tc.png b/wp-content/plugins/polylang/flags/tc.png new file mode 100644 index 0000000000..e6972adfb8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tc.png differ diff --git a/wp-content/plugins/polylang/flags/td.png b/wp-content/plugins/polylang/flags/td.png new file mode 100644 index 0000000000..acf1da6f54 Binary files /dev/null and b/wp-content/plugins/polylang/flags/td.png differ diff --git a/wp-content/plugins/polylang/flags/tf.png b/wp-content/plugins/polylang/flags/tf.png new file mode 100644 index 0000000000..fe4ade762b Binary files /dev/null and b/wp-content/plugins/polylang/flags/tf.png differ diff --git a/wp-content/plugins/polylang/flags/tg.png b/wp-content/plugins/polylang/flags/tg.png new file mode 100644 index 0000000000..b9a04838e8 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tg.png differ diff --git a/wp-content/plugins/polylang/flags/th.png b/wp-content/plugins/polylang/flags/th.png new file mode 100644 index 0000000000..ad9462a685 Binary files /dev/null and b/wp-content/plugins/polylang/flags/th.png differ diff --git a/wp-content/plugins/polylang/flags/tibet.png b/wp-content/plugins/polylang/flags/tibet.png new file mode 100644 index 0000000000..b2f743bdc4 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tibet.png differ diff --git a/wp-content/plugins/polylang/flags/tj.png b/wp-content/plugins/polylang/flags/tj.png new file mode 100644 index 0000000000..192bd87e0d Binary files /dev/null and b/wp-content/plugins/polylang/flags/tj.png differ diff --git a/wp-content/plugins/polylang/flags/tk.png b/wp-content/plugins/polylang/flags/tk.png new file mode 100644 index 0000000000..59a93bd78a Binary files /dev/null and b/wp-content/plugins/polylang/flags/tk.png differ diff --git a/wp-content/plugins/polylang/flags/tl.png b/wp-content/plugins/polylang/flags/tl.png new file mode 100644 index 0000000000..e65b54a777 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tl.png differ diff --git a/wp-content/plugins/polylang/flags/tm.png b/wp-content/plugins/polylang/flags/tm.png new file mode 100644 index 0000000000..a5a00005f0 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tm.png differ diff --git a/wp-content/plugins/polylang/flags/tn.png b/wp-content/plugins/polylang/flags/tn.png new file mode 100644 index 0000000000..57e21bdda6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tn.png differ diff --git a/wp-content/plugins/polylang/flags/to.png b/wp-content/plugins/polylang/flags/to.png new file mode 100644 index 0000000000..e320c68ffc Binary files /dev/null and b/wp-content/plugins/polylang/flags/to.png differ diff --git a/wp-content/plugins/polylang/flags/tr.png b/wp-content/plugins/polylang/flags/tr.png new file mode 100644 index 0000000000..4b472dd6d6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tr.png differ diff --git a/wp-content/plugins/polylang/flags/tt.png b/wp-content/plugins/polylang/flags/tt.png new file mode 100644 index 0000000000..fec31bc1e2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tt.png differ diff --git a/wp-content/plugins/polylang/flags/tv.png b/wp-content/plugins/polylang/flags/tv.png new file mode 100644 index 0000000000..359b8be846 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tv.png differ diff --git a/wp-content/plugins/polylang/flags/tw.png b/wp-content/plugins/polylang/flags/tw.png new file mode 100644 index 0000000000..e026cb7211 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tw.png differ diff --git a/wp-content/plugins/polylang/flags/tz.png b/wp-content/plugins/polylang/flags/tz.png new file mode 100644 index 0000000000..3b1090ac08 Binary files /dev/null and b/wp-content/plugins/polylang/flags/tz.png differ diff --git a/wp-content/plugins/polylang/flags/ua.png b/wp-content/plugins/polylang/flags/ua.png new file mode 100644 index 0000000000..24cd433c8d Binary files /dev/null and b/wp-content/plugins/polylang/flags/ua.png differ diff --git a/wp-content/plugins/polylang/flags/ug.png b/wp-content/plugins/polylang/flags/ug.png new file mode 100644 index 0000000000..1cac8d7c5b Binary files /dev/null and b/wp-content/plugins/polylang/flags/ug.png differ diff --git a/wp-content/plugins/polylang/flags/us.png b/wp-content/plugins/polylang/flags/us.png new file mode 100644 index 0000000000..81899ff3ae Binary files /dev/null and b/wp-content/plugins/polylang/flags/us.png differ diff --git a/wp-content/plugins/polylang/flags/uy.png b/wp-content/plugins/polylang/flags/uy.png new file mode 100644 index 0000000000..6d3f7e2460 Binary files /dev/null and b/wp-content/plugins/polylang/flags/uy.png differ diff --git a/wp-content/plugins/polylang/flags/uz.png b/wp-content/plugins/polylang/flags/uz.png new file mode 100644 index 0000000000..f9d4707ed9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/uz.png differ diff --git a/wp-content/plugins/polylang/flags/va.png b/wp-content/plugins/polylang/flags/va.png new file mode 100644 index 0000000000..05f59d182c Binary files /dev/null and b/wp-content/plugins/polylang/flags/va.png differ diff --git a/wp-content/plugins/polylang/flags/vc.png b/wp-content/plugins/polylang/flags/vc.png new file mode 100644 index 0000000000..0e0626b98d Binary files /dev/null and b/wp-content/plugins/polylang/flags/vc.png differ diff --git a/wp-content/plugins/polylang/flags/ve.png b/wp-content/plugins/polylang/flags/ve.png new file mode 100644 index 0000000000..a4ac3357cb Binary files /dev/null and b/wp-content/plugins/polylang/flags/ve.png differ diff --git a/wp-content/plugins/polylang/flags/veneto.png b/wp-content/plugins/polylang/flags/veneto.png new file mode 100644 index 0000000000..45c5a1cf4d Binary files /dev/null and b/wp-content/plugins/polylang/flags/veneto.png differ diff --git a/wp-content/plugins/polylang/flags/vg.png b/wp-content/plugins/polylang/flags/vg.png new file mode 100644 index 0000000000..47df07b9c6 Binary files /dev/null and b/wp-content/plugins/polylang/flags/vg.png differ diff --git a/wp-content/plugins/polylang/flags/vi.png b/wp-content/plugins/polylang/flags/vi.png new file mode 100644 index 0000000000..12e7fb367d Binary files /dev/null and b/wp-content/plugins/polylang/flags/vi.png differ diff --git a/wp-content/plugins/polylang/flags/vn.png b/wp-content/plugins/polylang/flags/vn.png new file mode 100644 index 0000000000..911897f73a Binary files /dev/null and b/wp-content/plugins/polylang/flags/vn.png differ diff --git a/wp-content/plugins/polylang/flags/vu.png b/wp-content/plugins/polylang/flags/vu.png new file mode 100644 index 0000000000..508fc0aea2 Binary files /dev/null and b/wp-content/plugins/polylang/flags/vu.png differ diff --git a/wp-content/plugins/polylang/flags/wales.png b/wp-content/plugins/polylang/flags/wales.png new file mode 100644 index 0000000000..d43bb1ac3c Binary files /dev/null and b/wp-content/plugins/polylang/flags/wales.png differ diff --git a/wp-content/plugins/polylang/flags/wf.png b/wp-content/plugins/polylang/flags/wf.png new file mode 100644 index 0000000000..45bfce65fc Binary files /dev/null and b/wp-content/plugins/polylang/flags/wf.png differ diff --git a/wp-content/plugins/polylang/flags/ws.png b/wp-content/plugins/polylang/flags/ws.png new file mode 100644 index 0000000000..cdda4f23c9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ws.png differ diff --git a/wp-content/plugins/polylang/flags/ye.png b/wp-content/plugins/polylang/flags/ye.png new file mode 100644 index 0000000000..0908b9b8c9 Binary files /dev/null and b/wp-content/plugins/polylang/flags/ye.png differ diff --git a/wp-content/plugins/polylang/flags/yt.png b/wp-content/plugins/polylang/flags/yt.png new file mode 100644 index 0000000000..4e43a1ed39 Binary files /dev/null and b/wp-content/plugins/polylang/flags/yt.png differ diff --git a/wp-content/plugins/polylang/flags/za.png b/wp-content/plugins/polylang/flags/za.png new file mode 100644 index 0000000000..c7fc63e01b Binary files /dev/null and b/wp-content/plugins/polylang/flags/za.png differ diff --git a/wp-content/plugins/polylang/flags/zm.png b/wp-content/plugins/polylang/flags/zm.png new file mode 100644 index 0000000000..0b4bbfaee7 Binary files /dev/null and b/wp-content/plugins/polylang/flags/zm.png differ diff --git a/wp-content/plugins/polylang/flags/zw.png b/wp-content/plugins/polylang/flags/zw.png new file mode 100644 index 0000000000..a808419eda Binary files /dev/null and b/wp-content/plugins/polylang/flags/zw.png differ diff --git a/wp-content/plugins/polylang/frontend/accept-language.php b/wp-content/plugins/polylang/frontend/accept-language.php new file mode 100644 index 0000000000..10b01ec590 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/accept-language.php @@ -0,0 +1,112 @@ + '(\b[a-z]{2,3}|[a-z]{4}|[a-z]{5-8}\b)', + 'language-extension' => '(?:-(\b[a-z]{3}){1,3}\b)?', + 'script' => '(?:-(\b[a-z]{4})\b)?', + 'region' => '(?:-(\b[a-z]{2}|[0-9]{3})\b)?', + 'variant' => '(?:-(\b[0-9][a-z]{1,3}|[a-z][a-z0-9]{4,7})\b)?', + 'extension' => '(?:-(\b[a-wy-z]-[a-z0-9]{2,8})\b)?', + 'private-use' => '(?:-(\bx-[a-z0-9]{1,8})\b)?', + ); + + /** + * @var string[] { + * @type string $language Usually 2 or three letters (ISO 639). + * @type string $language-extension Up to three groups of 3 letters. + * @type string $script Four letters. + * @type string $region Either two letters of three digits. + * @type string $variant Either one digit followed by 1 to 3 letters, or a letter followed by 2 to 7 alphanumerical characters. + * @type string $extension One letter that cannot be an 'x', followed by 2 to 8 alphanumerical characters. + * @type string $private-use Starts by 'x-', followed by 1 to 8 alphanumerical characters. + * } + */ + protected $subtags; + + /** + * @var float + */ + protected $quality; + + /** + * PLL_Accept_Language constructor. + * + * @since 3.0 + * + * @param string[] $subtags With subtag name as keys and subtag values as names. + * @param mixed $quality Floating point value from 0.0 to 1.0. Higher values indicates a user's preference. + */ + public function __construct( $subtags, $quality = 1.0 ) { + $this->subtags = $subtags; + $this->quality = is_numeric( $quality ) ? floatval( $quality ) : 1.0; + } + + /** + * Creates a new instance from an array resulting of a PHP {@see preg_match()} or {@see preg_match_all()} call. + * + * @since 3.0 + * + * @param string[] $matches Expects first entry to be full match, following entries to be subtags and last entry to be quality factor. + * @return PLL_Accept_Language + */ + public static function from_array( $matches ) { + $subtags = array_combine( + array_keys( array_slice( self::SUBTAG_PATTERNS, 0, count( $matches ) - 1 ) ), + array_slice( $matches, 1, count( self::SUBTAG_PATTERNS ) ) + ); + $quality = count( $matches ) === 9 ? $matches[8] : 1.0; + + return new PLL_Accept_Language( $subtags, $quality ); + } + + /** + * Returns the full language tag. + * + * @since 3.0 + * + * @return string + */ + public function __toString() { + $subtags = array_filter( + $this->subtags, + function ( $subtag ) { + return ! empty( trim( $subtag ) ); + } + ); + return implode( '-', $subtags ); + } + + /** + * Returns the quality factor as negotiated by the browser agent. + * + * @since 3.0 + * + * @return float + */ + public function get_quality() { + return $this->quality; + } + + /** + * Returns a subtag from the language tag. + * + * @since 3.0 + * + * @param string $name A valid subtag name, {@see PLL_Accept_Language::SUBTAG_PATTERNS} for available subtag names. + * @return string + */ + public function get_subtag( $name ) { + return isset( $this->subtags[ $name ] ) ? $this->subtags[ $name ] : ''; + } +} diff --git a/wp-content/plugins/polylang/frontend/accept-languages-collection.php b/wp-content/plugins/polylang/frontend/accept-languages-collection.php new file mode 100644 index 0000000000..1e512c18df --- /dev/null +++ b/wp-content/plugins/polylang/frontend/accept-languages-collection.php @@ -0,0 +1,143 @@ +1|0)(?>\.[0-9]+)?)'; + $full_pattern = "/{$language_pattern}(?:{$quality_pattern})?/i"; + + preg_match_all( + $full_pattern, + $http_header, + $lang_parse, + PREG_SET_ORDER + ); + + return new PLL_Accept_Languages_Collection( + array_map( + array( PLL_Accept_Language::class, 'from_array' ), + $lang_parse + ) + ); + } + + /** + * PLL_Accept_Languages_Collection constructor. + * + * @since 3.0 + * + * @param PLL_Accept_Language[] $accept_languages Objects representing Accept-Language HTTP headers. + */ + public function __construct( $accept_languages = array() ) { + $this->accept_languages = $accept_languages; + } + + /** + * Bubble sort (need a stable sort for Android, so can't use a PHP sort function). + * + * @since 3.0 + * + * @return void + */ + public function bubble_sort() { + $k = $this->accept_languages; + $v = array_map( + function ( $accept_lang ) { + return $accept_lang->get_quality(); + }, + $this->accept_languages + ); + + if ( $n = count( $k ) ) { + + if ( $n > 1 ) { + for ( $i = 2; $i <= $n; $i++ ) { + for ( $j = 0; $j <= $n - 2; $j++ ) { + if ( $v[ $j ] < $v[ $j + 1 ] ) { + // Swap values. + $temp = $v[ $j ]; + $v[ $j ] = $v[ $j + 1 ]; + $v[ $j + 1 ] = $temp; + // Swap keys. + $temp = $k[ $j ]; + $k[ $j ] = $k[ $j + 1 ]; + $k[ $j + 1 ] = $temp; + } + } + } + } + $this->accept_languages = array_filter( + $k, + function ( $accept_lang ) { + return $accept_lang->get_quality() > 0; + } + ); + } + } + + /** + * Looks through sorted list and use first one that matches our language list. + * + * @since 3.0 + * + * @param PLL_Language[] $languages The language list. + * @return string|false A language slug if there's a match, false otherwise. + */ + public function find_best_match( $languages = array() ) { + foreach ( $this->accept_languages as $accept_lang ) { + // First loop to match the exact locale. + foreach ( $languages as $language ) { + if ( 0 === strcasecmp( $accept_lang, $language->get_locale( 'display' ) ) ) { + return $language->slug; + } + } + + // In order of priority. + $subsets = array(); + if ( ! empty( $accept_lang->get_subtag( 'region' ) ) ) { + $subsets[] = $accept_lang->get_subtag( 'language' ) . '-' . $accept_lang->get_subtag( 'region' ); + $subsets[] = $accept_lang->get_subtag( 'region' ); + } + if ( ! empty( $accept_lang->get_subtag( 'variant' ) ) ) { + $subsets[] = $accept_lang->get_subtag( 'language' ) . '-' . $accept_lang->get_subtag( 'variant' ); + } + $subsets[] = $accept_lang->get_subtag( 'language' ); + + // More loops to match the subsets. + foreach ( $languages as $language ) { + foreach ( $subsets as $subset ) { + + if ( 0 === stripos( $subset, $language->slug ) || 0 === stripos( $language->get_locale( 'display' ), $subset ) ) { + return $language->slug; + } + } + } + } + return false; + } +} diff --git a/wp-content/plugins/polylang/frontend/canonical.php b/wp-content/plugins/polylang/frontend/canonical.php new file mode 100644 index 0000000000..b6f2a734f1 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/canonical.php @@ -0,0 +1,269 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + $this->curlang = &$polylang->curlang; + } + + /** + * If the language code is not in agreement with the language of the content, + * redirects incoming links to the proper URL to avoid duplicate content. + * + * @since 0.9.6 + * + * @global WP_Query $wp_query WordPress Query object. + * @global bool $is_IIS + * + * @param string $requested_url Optional, defaults to requested url. + * @param bool $do_redirect Optional, whether to perform the redirect or not. + * @return string|void Returns if redirect is not performed. + */ + public function check_canonical_url( $requested_url = '', $do_redirect = true ) { + global $wp_query; + + // Don't redirect in same cases as WP. + if ( is_trackback() || is_search() || is_admin() || is_preview() || is_robots() || ( $GLOBALS['is_IIS'] && ! iis7_supports_permalinks() ) ) { + return; + } + + // Don't redirect mysite.com/?attachment_id= to mysite.com/en/?attachment_id=. + if ( 1 == $this->options['force_lang'] && is_attachment() && isset( $_GET['attachment_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + /* + * If the default language code is not hidden and the static front page url contains the page name, + * the customizer lands here and the code below would redirect to the list of posts. + */ + if ( is_customize_preview() ) { + return; + } + + if ( empty( $requested_url ) ) { + $requested_url = pll_get_requested_url(); + } + + if ( ( is_single() || is_page() ) && ! is_front_page() ) { + $post = get_post(); + if ( $post instanceof WP_Post && $this->model->is_translated_post_type( $post->post_type ) ) { + $language = $this->model->post->get_language( (int) $post->ID ); + } + } + + if ( ! empty( $wp_query->tax_query ) ) { + if ( $this->model->is_translated_taxonomy( $this->get_queried_taxonomy( $wp_query->tax_query ) ) ) { + $term_id = $this->get_queried_term_id( $wp_query->tax_query ); + if ( $term_id ) { + $language = $this->model->term->get_language( $term_id ); + } + } + } + + if ( $wp_query->is_posts_page ) { + $page_id = get_query_var( 'page_id' ); + if ( ! $page_id ) { + $page_id = get_queried_object_id(); + } + if ( $page_id && is_numeric( $page_id ) ) { + $language = $this->model->post->get_language( (int) $page_id ); + } + } + + if ( 3 === $this->options['force_lang'] ) { + $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST ); + foreach ( $this->options['domains'] as $lang => $domain ) { + $host = wp_parse_url( $domain, PHP_URL_HOST ); + if ( $requested_host && $host && ltrim( $requested_host, 'w.' ) === ltrim( $host, 'w.' ) ) { + $language = $this->model->get_language( $lang ); + } + } + } + + if ( empty( $language ) ) { + $language = $this->curlang; + $redirect_url = $requested_url; + } else { + $redirect_url = $this->redirect_canonical( $requested_url, $language ); + $redirect_url = $this->options['force_lang'] ? + $this->links_model->switch_language_in_link( $redirect_url, $language ) : + $this->links_model->remove_language_from_link( $redirect_url ); // Works only for default permalinks. + } + + + /** + * Filters the canonical url detected by Polylang. + * + * @since 1.6 + * + * @param string|false $redirect_url False or the url to redirect to. + * @param PLL_Language $language The language detected. + */ + $redirect_url = apply_filters( 'pll_check_canonical_url', $redirect_url, $language ); + + if ( ! $redirect_url || $requested_url === $redirect_url ) { + return $requested_url; + } + + if ( ! $do_redirect ) { + return $redirect_url; + } + + // Protect against chained redirects. + if ( $redirect_url === $this->check_canonical_url( $redirect_url, false ) && wp_validate_redirect( $redirect_url ) ) { + wp_safe_redirect( $redirect_url, 301, POLYLANG ); + exit; + } + } + + /** + * Returns the term_id of the requested term. + * + * @since 2.9 + * + * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query. + * @return int + */ + protected function get_queried_term_id( $tax_query ) { + $queried_terms = $tax_query->queried_terms; + $taxonomy = $this->get_queried_taxonomy( $tax_query ); + + if ( ! is_array( $queried_terms[ $taxonomy ]['terms'] ) ) { + return 0; + } + $field = $queried_terms[ $taxonomy ]['field']; + $term = reset( $queried_terms[ $taxonomy ]['terms'] ); + $lang = isset( $queried_terms['language']['terms'] ) ? reset( $queried_terms['language']['terms'] ) : ''; + + // We can get a term_id when requesting a plain permalink, eg /?cat=1. + if ( 'term_id' === $field ) { + return $term; + } + + // We get a slug when requesting a pretty permalink. Let's query all corresponding terms. + $args = array( + 'lang' => '', + 'taxonomy' => $taxonomy, + $field => $term, + 'hide_empty' => false, + 'fields' => 'ids', + ); + $term_ids = get_terms( $args ); + + if ( ! is_array( $term_ids ) || empty( $term_ids ) ) { + return 0; + } + + $term_ids = array_filter( $term_ids, 'is_numeric' ); + + $filtered_terms_by_lang = array_filter( + $term_ids, + function ( $term_id ) use ( $lang ) { + $term_lang = $this->model->term->get_language( (int) $term_id ); + + return ! empty( $term_lang ) && $term_lang->slug === $lang; + } + ); + + $tr_term = (int) reset( $filtered_terms_by_lang ); + + if ( ! empty( $tr_term ) ) { + // The queried term exists in the desired language. + return $tr_term; + } + + // The queried term doesn't exist in the desired language, let's return the first one retrieved. + return (int) reset( $term_ids ); + } + + /** + * Find the taxonomy being queried. + * + * @since 2.9 + * + * @param WP_Tax_Query $tax_query An instance of WP_Tax_Query. + * @return string A taxonomy slug + */ + protected function get_queried_taxonomy( $tax_query ) { + $queried_terms = $tax_query->queried_terms; + unset( $queried_terms['language'] ); + + return (string) key( $queried_terms ); + } + + /** + * Evaluates the canonical redirect url through the deidcated WP function. + * + * @since 3.3 + * + * @global WP_Query $wp_query WordPress Query object. + * + * @param string $url Requested url. + * @param PLL_Language $language Language of the queried object. + * @return string + */ + protected function redirect_canonical( $url, $language ) { + /** + * @var WP_Query + */ + global $wp_query; + + $this->curlang = $language; // Hack to filter the `page_for_posts` option in the correct language. + + $backup_wp_query = $wp_query; + + if ( isset( $wp_query->tax_query ) ) { + unset( $wp_query->tax_query->queried_terms['language'] ); + unset( $wp_query->query['lang'] ); + } + + $redirect_url = redirect_canonical( $url, false ); + + $wp_query = $backup_wp_query; + + return $redirect_url ? $redirect_url : $url; + } +} diff --git a/wp-content/plugins/polylang/frontend/choose-lang-content.php b/wp-content/plugins/polylang/frontend/choose-lang-content.php new file mode 100644 index 0000000000..9222f1815f --- /dev/null +++ b/wp-content/plugins/polylang/frontend/choose-lang-content.php @@ -0,0 +1,166 @@ +options['media_support'] ) ) { + return $this->get_preferred_language(); + } + + if ( $var = get_query_var( 'lang' ) ) { + $lang = explode( ',', $var ); + $lang = $this->model->get_language( reset( $lang ) ); // Choose the first queried language + } + + elseif ( ( is_single() || is_page() || ( is_attachment() && $this->options['media_support'] ) ) && ( ( $var = get_queried_object_id() ) || ( $var = get_query_var( 'p' ) ) || ( $var = get_query_var( 'page_id' ) ) || ( $var = get_query_var( 'attachment_id' ) ) ) && is_numeric( $var ) ) { + $lang = $this->model->post->get_language( (int) $var ); + } + + else { + foreach ( $this->model->get_translated_taxonomies() as $taxonomy ) { + $tax_object = get_taxonomy( $taxonomy ); + + if ( empty( $tax_object ) ) { + continue; + } + + $var = get_query_var( $tax_object->query_var ); + + if ( ! is_string( $var ) || empty( $var ) ) { + continue; + } + + $term = get_term_by( 'slug', $var, $taxonomy ); + + if ( ! $term instanceof WP_Term ) { + continue; + } + + $lang = $this->model->term->get_language( $term->term_id ); + } + } + + /** + * Filters the language before it is set from the content. + * + * @since 0.9 + * + * @param PLL_Language|false $lang Language object or false if none was found. + */ + return apply_filters( 'pll_get_current_language', isset( $lang ) ? $lang : false ); + } + + /** + * Sets the language for the home page. + * Adds the lang query var when querying archives with no language code. + * + * @since 1.2 + * + * @param WP_Query $query Instance of WP_Query. + * @return void + */ + public function parse_main_query( $query ) { + if ( empty( $GLOBALS['wp_the_query'] ) || $query !== $GLOBALS['wp_the_query'] ) { + return; + } + + $qv = $query->query_vars; + + // Homepage is requested, let's set the language + // Take care to avoid posts page for which is_home = 1 + if ( empty( $query->query ) && ( is_home() || is_page() ) ) { + $this->home_language(); + $this->home_requested(); + } + + parent::parse_main_query( $query ); + + $is_archive = ( count( $query->query ) == 1 && ! empty( $qv['paged'] ) ) || + $query->is_date || + $query->is_author || + ( ! empty( $qv['post_type'] ) && $query->is_post_type_archive && $this->model->is_translated_post_type( $qv['post_type'] ) ); + + // Sets the language in case we hide the default language + // Use $query->query['s'] as is_search is not set when search is empty + // http://wordpress.org/support/topic/search-for-empty-string-in-default-language + if ( $this->options['hide_default'] && ! isset( $qv['lang'] ) && ( $is_archive || isset( $query->query['s'] ) || ( count( $query->query ) == 1 && ! empty( $qv['feed'] ) ) ) ) { + $this->set_language( $this->model->get_default_language() ); + $this->set_curlang_in_query( $query ); + } + } + + /** + * Sets the language from content + * + * @since 1.2 + * + * @return void + */ + public function wp() { + // Nothing to do if the language has already been set ( although normally the filter has been removed ) + if ( empty( $this->curlang ) && $curlang = $this->get_language_from_content() ) { + parent::set_language( $curlang ); + } + } + + /** + * If no language is found by {@see PLL_Choose_Lang_Content::get_language_from_content()}, returns the preferred one. + * + * @since 0.9 + * + * @param PLL_Language|false $lang Language found by {@see PLL_Choose_Lang_Content::get_language_from_content()}. + * @return PLL_Language|false + */ + public function pll_get_current_language( $lang ) { + return ! $lang ? $this->get_preferred_language() : $lang; + } +} diff --git a/wp-content/plugins/polylang/frontend/choose-lang-domain.php b/wp-content/plugins/polylang/frontend/choose-lang-domain.php new file mode 100644 index 0000000000..dd11f19f9b --- /dev/null +++ b/wp-content/plugins/polylang/frontend/choose-lang-domain.php @@ -0,0 +1,45 @@ +model->get_language( $this->links_model->get_language_from_url() ); + } + + /** + * Adds query vars to query for home pages in all languages + * + * @since 1.5 + * + * @return void + */ + public function home_requested() { + $this->set_curlang_in_query( $GLOBALS['wp_query'] ); + /** This action is documented in include/choose-lang.php */ + do_action( 'pll_home_requested' ); + } +} diff --git a/wp-content/plugins/polylang/frontend/choose-lang-url.php b/wp-content/plugins/polylang/frontend/choose-lang-url.php new file mode 100644 index 0000000000..cde95fd70e --- /dev/null +++ b/wp-content/plugins/polylang/frontend/choose-lang-url.php @@ -0,0 +1,118 @@ +set_language_from_url(); + } + + add_filter( 'request', array( $this, 'request' ) ); + } + + /** + * Finds the language according to information found in the url + * + * @since 1.2 + * + * @return void + */ + public function set_language_from_url() { + $host = str_replace( 'www.', '', (string) wp_parse_url( $this->links_model->home, PHP_URL_HOST ) ); // Remove www. for the comparison + $home_path = (string) wp_parse_url( $this->links_model->home, PHP_URL_PATH ); + + $requested_url = pll_get_requested_url(); + $requested_host = str_replace( 'www.', '', (string) wp_parse_url( $requested_url, PHP_URL_HOST ) ); // Remove www. for the comparison + $requested_path = rtrim( str_replace( $this->index, '', (string) wp_parse_url( $requested_url, PHP_URL_PATH ) ), '/' ); // Some PHP setups turn requests for / into /index.php in REQUEST_URI + $requested_query = wp_parse_url( $requested_url, PHP_URL_QUERY ); + + // Home is requested + if ( $requested_host === $host && $requested_path === $home_path && empty( $requested_query ) ) { + $this->home_language(); + add_action( 'setup_theme', array( $this, 'home_requested' ) ); + } + + // Take care to post & page preview http://wordpress.org/support/topic/static-frontpage-url-parameter-url-language-information + elseif ( isset( $_GET['preview'] ) && ( ( isset( $_GET['p'] ) && $id = (int) $_GET['p'] ) || ( isset( $_GET['page_id'] ) && $id = (int) $_GET['page_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->model->get_default_language(); + } + + // Take care to ( unattached ) attachments + elseif ( isset( $_GET['attachment_id'] ) && $id = (int) $_GET['attachment_id'] ) { // phpcs:ignore WordPress.Security.NonceVerification + $curlang = ( $lg = $this->model->post->get_language( $id ) ) ? $lg : $this->get_preferred_language(); + } + + elseif ( $slug = $this->links_model->get_language_from_url() ) { + $curlang = $this->model->get_language( $slug ); + } + + elseif ( $this->options['hide_default'] ) { + $curlang = $this->model->get_default_language(); + } + + // If no language found, check_language_code_in_url() will attempt to find one and redirect to the correct url + // Otherwise a 404 will be fired in the preferred language + $this->set_language( empty( $curlang ) ? $this->get_preferred_language() : $curlang ); + } + + + /** + * Adds the current language in query vars + * useful for subdomains and multiple domains + * + * @since 1.8 + * + * @param array $qv main request query vars + * @return array modified query vars + */ + public function request( $qv ) { + // FIXME take care not to break untranslated content + // FIXME media ? + + // Untranslated post types + if ( isset( $qv['post_type'] ) && ! $this->model->is_translated_post_type( $qv['post_type'] ) ) { + return $qv; + } + + // Untranslated taxonomies + $tax_qv = array_filter( wp_list_pluck( get_taxonomies( array(), 'objects' ), 'query_var' ) ); // Get all taxonomies query vars + $tax_qv = array_intersect( $tax_qv, array_keys( $qv ) ); // Get all queried taxonomies query vars + + if ( ! $this->model->is_translated_taxonomy( array_keys( $tax_qv ) ) ) { + return $qv; + } + + if ( isset( $this->curlang ) && empty( $qv['lang'] ) ) { + $qv['lang'] = $this->curlang->slug; + } + + return $qv; + } +} diff --git a/wp-content/plugins/polylang/frontend/choose-lang.php b/wp-content/plugins/polylang/frontend/choose-lang.php new file mode 100644 index 0000000000..433c31f384 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/choose-lang.php @@ -0,0 +1,351 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + + $this->curlang = &$polylang->curlang; + } + + /** + * Sets the language for ajax requests + * and setup actions + * Any child class must call this method if it overrides it + * + * @since 1.8 + * + * @return void + */ + public function init() { + if ( Polylang::is_ajax_on_front() || ! wp_using_themes() ) { + $this->set_language( empty( $_REQUEST['lang'] ) ? $this->get_preferred_language() : $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification + } + + add_action( 'pre_comment_on_post', array( $this, 'pre_comment_on_post' ) ); // sets the language of comment + add_action( 'parse_query', array( $this, 'parse_main_query' ), 2 ); // sets the language in special cases + add_action( 'wp', array( $this, 'maybe_setcookie' ), 7 ); + } + + /** + * Sets the current language + * and fires the action 'pll_language_defined'. + * + * @since 1.2 + * + * @param PLL_Language|false $curlang Current language. + * @return void + */ + protected function set_language( $curlang ) { + // Don't set the language a second time + if ( isset( $this->curlang ) ) { + return; + } + + // Final check in case $curlang has an unexpected value + // See https://wordpress.org/support/topic/detect-browser-language-sometimes-setting-null-language + if ( ! $curlang instanceof PLL_Language ) { + $curlang = $this->model->get_default_language(); + + if ( ! $curlang instanceof PLL_Language ) { + return; + } + } + + $this->curlang = $curlang; + + $GLOBALS['text_direction'] = $this->curlang->is_rtl ? 'rtl' : 'ltr'; + if ( did_action( 'wp_default_styles' ) ) { + wp_styles()->text_direction = $GLOBALS['text_direction']; + } + + /** + * Fires when the current language is defined. + * + * @since 0.9.5 + * + * @param string $slug Current language code. + * @param PLL_Language $curlang Current language object. + */ + do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang ); + } + + /** + * Set a cookie to remember the language. + * Setting PLL_COOKIE to false will disable cookie although it will break some functionalities + * + * @since 1.5 + * + * @return void + */ + public function maybe_setcookie() { + // Don't set cookie in javascript when a cache plugin is active. + if ( ! pll_is_cache_active() && ! empty( $this->curlang ) && ! is_404() ) { + $args = array( + 'domain' => 2 === $this->options['force_lang'] ? wp_parse_url( $this->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN, + 'samesite' => 3 === $this->options['force_lang'] ? 'None' : 'Lax', + ); + PLL_Cookie::set( $this->curlang->slug, $args ); + } + } + + /** + * Get the preferred language according to the browser preferences. + * + * @since 1.8 + * + * @return string|bool The preferred language slug or false. + */ + public function get_preferred_browser_language() { + if ( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) { + $accept_langs = PLL_Accept_Languages_Collection::from_accept_language_header( sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) ); + + $accept_langs->bubble_sort(); + + $languages = $this->model->get_languages_list( array( 'hide_empty' => true ) ); // Hides languages with no post. + + /** + * Filters the list of languages to use to match the browser preferences. + * + * @since 1.9.3 + * + * @param array $languages Array of PLL_Language objects. + */ + $languages = apply_filters( 'pll_languages_for_browser_preferences', $languages ); + + return $accept_langs->find_best_match( $languages ); + } + + return false; + } + + /** + * Returns the preferred language + * either from the cookie if it's a returning visit + * or according to browser preference + * or the default language + * + * @since 0.1 + * + * @return PLL_Language|false browser preferred language or default language + */ + public function get_preferred_language() { + $language = false; + $cookie = false; + + if ( isset( $_COOKIE[ PLL_COOKIE ] ) ) { + // Check first if the user was already browsing this site. + $language = sanitize_key( $_COOKIE[ PLL_COOKIE ] ); + $cookie = true; + } elseif ( $this->options['browser'] ) { + $language = $this->get_preferred_browser_language(); + } + + /** + * Filter the visitor's preferred language (normally set first by cookie + * if this is not the first visit, then by the browser preferences). + * If no preferred language has been found or set by this filter, + * Polylang fallbacks to the default language + * + * @since 1.0 + * @since 2.7 Added $cookie parameter. + * + * @param string|bool $language Preferred language code, false if none has been found. + * @param bool $cookie Whether the preferred language has been defined by the cookie. + */ + $slug = apply_filters( 'pll_preferred_language', $language, $cookie ); + + // Return default if there is no preferences in the browser or preferences does not match our languages or it is requested not to use the browser preference + return ( $lang = $this->model->get_language( $slug ) ) ? $lang : $this->model->get_default_language(); + } + + /** + * Sets the language when home page is requested + * + * @since 1.2 + * + * @return void + */ + protected function home_language() { + // Test referer in case PLL_COOKIE is set to false. Since WP 3.6.1, wp_get_referer() validates the host which is exactly what we want + // Thanks to Ov3rfly http://wordpress.org/support/topic/enhance-feature-when-front-page-is-visited-set-language-according-to-browser + $language = $this->options['hide_default'] && ( wp_get_referer() || ! $this->options['browser'] ) ? + $this->model->get_default_language() : + $this->get_preferred_language(); // Sets the language according to browser preference or default language + $this->set_language( $language ); + } + + /** + * To call when the home page has been requested + * Make sure to call this after 'setup_theme' has been fired as we need $wp_query + * Performs a redirection to the home page in the current language if needed + * + * @since 0.9 + * + * @return void + */ + public function home_requested() { + if ( empty( $this->curlang ) ) { + return; + } + + // We are already on the right page + if ( $this->curlang->is_default && $this->options['hide_default'] ) { + $this->set_curlang_in_query( $GLOBALS['wp_query'] ); + + /** + * Fires when the site root page is requested + * + * @since 1.8 + */ + do_action( 'pll_home_requested' ); + } + // Redirect to the home page in the right language + // Test to avoid crash if get_home_url returns something wrong + // FIXME why this happens? http://wordpress.org/support/topic/polylang-crashes-1 + // Don't redirect if $_POST is not empty as it could break other plugins + elseif ( is_string( $redirect = $this->curlang->get_home_url() ) && empty( $_POST ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // Don't forget the query string which may be added by plugins + $query_string = wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY ); + if ( ! empty( $query_string ) ) { + $redirect .= ( $this->links_model->using_permalinks ? '?' : '&' ) . $query_string; + } + + /** + * When a visitor reaches the site home, Polylang redirects to the home page in the correct language. + * This filter allows plugins to modify the redirected url or prevent this redirection + * /!\ this filter may be fired *before* the theme is loaded + * + * @since 1.1.1 + * + * @param string $redirect the url the visitor will be redirected to + */ + $redirect = apply_filters( 'pll_redirect_home', $redirect ); + if ( $redirect && wp_validate_redirect( $redirect ) ) { + $this->maybe_setcookie(); + header( 'Vary: Accept-Language' ); + wp_safe_redirect( $redirect, 302, POLYLANG ); + exit; + } + } + } + + /** + * Set the language when posting a comment + * + * @since 0.8.4 + * + * @param int $post_id the post being commented + * @return void + */ + public function pre_comment_on_post( $post_id ) { + $this->set_language( $this->model->post->get_language( $post_id ) ); + } + + /** + * Modifies some main query vars for the home page and the page for posts + * to enable one home page (and one page for posts) per language. + * + * @since 1.2 + * + * @param WP_Query $query Instance of WP_Query. + * @return void + */ + public function parse_main_query( $query ) { + if ( ! $query->is_main_query() ) { + return; + } + + /** + * This filter allows to set the language based on information contained in the main query + * + * @since 1.8 + * + * @param PLL_Language|false $lang Language object or false. + * @param WP_Query $query WP_Query object. + */ + if ( $lang = apply_filters( 'pll_set_language_from_query', false, $query ) ) { + $this->set_language( $lang ); + $this->set_curlang_in_query( $query ); + } elseif ( ( count( $query->query ) == 1 || ( is_paged() && count( $query->query ) == 2 ) ) && $lang = get_query_var( 'lang' ) ) { + $lang = $this->model->get_language( $lang ); + $this->set_language( $lang ); // Set the language now otherwise it will be too late to filter sticky posts! + + // Set is_home on translated home page when it displays posts. It must be true on page 2, 3... too. + $query->is_home = true; + $query->is_tax = false; + $query->is_archive = false; + + // Filters is_front_page() in case a static front page is not translated in this language. + add_filter( 'option_show_on_front', array( $this, 'filter_option_show_on_front' ) ); + } + } + + /** + * Filters the option show_on_front when the current front page displays posts. + * + * This is useful when a static front page is not translated in all languages. + * + * @return string + */ + public function filter_option_show_on_front() { + return 'posts'; + } + + /** + * Sets the current language in the query. + * + * @since 2.2 + * + * @param WP_Query $query Instance of WP_Query. + * @return void + */ + protected function set_curlang_in_query( &$query ) { + if ( ! empty( $this->curlang ) ) { + $pll_query = new PLL_Query( $query, $this->model ); + $pll_query->set_language( $this->curlang ); + } + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-auto-translate.php b/wp-content/plugins/polylang/frontend/frontend-auto-translate.php new file mode 100644 index 0000000000..8c1d4e86f9 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-auto-translate.php @@ -0,0 +1,331 @@ +model = &$polylang->model; + $this->curlang = &$polylang->curlang; + + add_action( 'parse_query', array( $this, 'translate_included_ids_in_query' ), 100 ); // After all Polylang filters. + add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 20, 2 ); + } + + /** + * Helper function to get the translated post in the current language. + * + * @since 1.8 + * + * @param int $post_id The ID of the post to translate. + * @return int + * + * @phpstan-return int<0, max> + */ + protected function get_post( $post_id ) { + return $this->model->post->get( $post_id, $this->curlang ); + } + + /** + * Helper function to get the translated term in the current language. + * + * @since 1.8 + * + * @param int $term_id The ID of the term to translate. + * @return int + * + * @phpstan-return int<0, max> + */ + protected function get_term( $term_id ) { + return $this->model->term->get( $term_id, $this->curlang ); + } + + /** + * Filters posts query to automatically translate included ids + * + * @since 1.1 + * + * @param WP_Query $query WP_Query object + * @return void + */ + public function translate_included_ids_in_query( $query ) { + global $wpdb; + $qv = &$query->query_vars; + + if ( $query->is_main_query() || isset( $qv['lang'] ) || ( ! empty( $qv['post_type'] ) && ! $this->model->is_translated_post_type( $qv['post_type'] ) ) ) { + return; + } + + // /!\ always keep untranslated as is + + // Term ids separated by a comma + $arr = array(); + if ( ! empty( $qv['cat'] ) ) { + foreach ( explode( ',', $qv['cat'] ) as $cat ) { + $tr = $this->get_term( abs( $cat ) ); + $arr[] = $cat < 0 ? -$tr : $tr; + } + + $qv['cat'] = implode( ',', $arr ); + } + + // Category_name + $arr = array(); + if ( ! empty( $qv['category_name'] ) ) { + foreach ( explode( ',', $qv['category_name'] ) as $slug ) { + $arr[] = $this->get_translated_term_by( 'slug', $slug, 'category' ); + } + + $qv['category_name'] = implode( ',', $arr ); + } + + // Array of term ids + foreach ( array( 'category__and', 'category__in', 'category__not_in', 'tag__and', 'tag__in', 'tag__not_in' ) as $key ) { + $arr = array(); + if ( ! empty( $qv[ $key ] ) ) { + foreach ( $qv[ $key ] as $cat ) { + $arr[] = ( $tr = $this->get_term( $cat ) ) ? $tr : $cat; + } + $qv[ $key ] = $arr; + } + } + + // Tag + if ( ! empty( $qv['tag'] ) ) { + $qv['tag'] = $this->translate_terms_list( $qv['tag'], 'post_tag' ); + } + + // tag_id can only take one id + if ( ! empty( $qv['tag_id'] ) && $tr_id = $this->get_term( $qv['tag_id'] ) ) { + $qv['tag_id'] = $tr_id; + } + + // Array of tag slugs + foreach ( array( 'tag_slug__and', 'tag_slug__in' ) as $key ) { + $arr = array(); + if ( ! empty( $qv[ $key ] ) ) { + foreach ( $qv[ $key ] as $slug ) { + $arr[] = $this->get_translated_term_by( 'slug', $slug, 'post_tag' ); + } + + $qv[ $key ] = $arr; + } + } + + // Custom taxonomies + // According to the codex, this type of query is deprecated as of WP 3.1 but it does not appear in WP 3.5 source code + foreach ( array_intersect( $this->model->get_translated_taxonomies(), get_taxonomies( array( '_builtin' => false ) ) ) as $taxonomy ) { + $tax = get_taxonomy( $taxonomy ); + if ( ! empty( $tax ) && ! empty( $qv[ $tax->query_var ] ) ) { + $qv[ $tax->query_var ] = $this->translate_terms_list( $qv[ $tax->query_var ], $taxonomy ); + } + } + + // Tax_query since WP 3.1 + if ( ! empty( $qv['tax_query'] ) && is_array( $qv['tax_query'] ) ) { + $qv['tax_query'] = $this->translate_tax_query_recursive( $qv['tax_query'] ); + } + + // p, page_id, post_parent can only take one id + foreach ( array( 'p', 'page_id', 'post_parent' ) as $key ) { + if ( ! empty( $qv[ $key ] ) && $tr_id = $this->get_post( $qv[ $key ] ) ) { + $qv[ $key ] = $tr_id; + } + } + + // name, can only take one slug + if ( ! empty( $qv['name'] ) && is_string( $qv['name'] ) ) { + if ( empty( $qv['post_type'] ) ) { + $post_types = array( 'post' ); + } elseif ( 'any' === $qv['post_type'] ) { + $post_types = get_post_types( array( 'exclude_from_search' => false ) ); // May return a empty array + } else { + $post_types = (array) $qv['post_type']; + } + + if ( ! empty( $post_types ) ) { + // No function to get post by name except get_posts itself + $id = $wpdb->get_var( + sprintf( + "SELECT ID from {$wpdb->posts} + WHERE {$wpdb->posts}.post_type IN ( '%s' ) + AND post_name='%s'", + implode( "', '", esc_sql( $post_types ) ), + esc_sql( $qv['name'] ) + ) + ); + $qv['name'] = ( $id && ( $tr_id = $this->get_post( $id ) ) && $tr = get_post( $tr_id ) ) ? $tr->post_name : $qv['name']; + } + } + + // pagename, the page id is already available in queried_object_id + if ( ! empty( $qv['pagename'] ) && ! empty( $query->queried_object_id ) && $tr_id = $this->get_post( $query->queried_object_id ) ) { + $query->queried_object_id = $tr_id; + $qv['pagename'] = get_page_uri( $tr_id ); + } + + // Array of post ids + // post_parent__in & post_parent__not_in since WP 3.6 + foreach ( array( 'post__in', 'post__not_in', 'post_parent__in', 'post_parent__not_in' ) as $key ) { // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn + $arr = array(); + if ( ! empty( $qv[ $key ] ) ) { + // post__in used by the 2 functions below + // Useless to filter them as output is already in the right language and would result in performance loss + foreach ( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ) as $trace ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions + if ( in_array( $trace['function'], array( 'wp_nav_menu', 'gallery_shortcode' ) ) ) { + return; + } + } + + foreach ( $qv[ $key ] as $p ) { + $arr[] = ( $tr = $this->get_post( $p ) ) ? $tr : $p; + } + + $qv[ $key ] = $arr; + } + } + } + + /** + * Filters the terms query to automatically translate included ids. + * + * @since 1.1.1 + * + * @param array $args An array of get_terms() arguments. + * @param array $taxonomies An array of taxonomy names. + * @return array + */ + public function get_terms_args( $args, $taxonomies ) { + if ( ! isset( $args['lang'] ) && ! empty( $args['include'] ) && ( empty( $taxonomies ) || $this->model->is_translated_taxonomy( $taxonomies ) ) ) { + $arr = array(); + + foreach ( wp_parse_id_list( $args['include'] ) as $id ) { + $arr[] = ( $tr = $this->get_term( $id ) ) ? $tr : $id; + } + + $args['include'] = $arr; + } + return $args; + } + + /** + * Translates tax queries + * Compatible with nested tax queries introduced in WP 4.1 + * + * @since 1.7 + * + * @param array $tax_queries An array of tax queries. + * @return array Translated tax queries. + */ + protected function translate_tax_query_recursive( $tax_queries ) { + foreach ( $tax_queries as $key => $q ) { + if ( ! is_array( $q ) ) { + continue; + } + + if ( isset( $q['taxonomy'], $q['terms'] ) && $this->model->is_translated_taxonomy( $q['taxonomy'] ) ) { + $arr = array(); + $field = isset( $q['field'] ) && in_array( $q['field'], array( 'slug', 'name' ) ) ? $q['field'] : 'term_id'; + foreach ( (array) $q['terms'] as $t ) { + $arr[] = $this->get_translated_term_by( $field, $t, $q['taxonomy'] ); + } + + $tax_queries[ $key ]['terms'] = $arr; + } else { + // Nested queries. + $tax_queries[ $key ] = $this->translate_tax_query_recursive( $q ); + } + } + + return $tax_queries; + } + + /** + * Translates a term given one field. + * + * @since 2.3.3 + * + * @param string $field Either 'slug', 'name', 'term_id', or 'term_taxonomy_id' + * @param string|int $term Search for this term value + * @param string $taxonomy Taxonomy name. + * @return string|int Translated term slug, name, term_id or term_taxonomy_id + */ + protected function get_translated_term_by( $field, $term, $taxonomy ) { + if ( 'term_id' === $field ) { + if ( $tr_id = $this->get_term( $term ) ) { + return $tr_id; + } + } else { + $terms = get_terms( array( 'taxonomy' => $taxonomy, $field => $term, 'lang' => '' ) ); + + if ( ! empty( $terms ) && is_array( $terms ) ) { + $t = reset( $terms ); + if ( ! $t instanceof WP_Term ) { + return $term; + } + $tr_id = $this->get_term( $t->term_id ); + + if ( ! is_wp_error( $tr = get_term( $tr_id, $taxonomy ) ) ) { + return $tr->$field; + } + } + } + return $term; + } + + /** + * Translates a list of term slugs provided either as an array or a string + * with slugs separated by a comma or a '+'. + * + * @since 3.2.8 + * + * @param string|string[] $query_var The list of term slugs. + * @param string $taxonomy The taxonomy for terms. + * @return string|string[] The translated list. + */ + protected function translate_terms_list( $query_var, $taxonomy ) { + $slugs = array(); + + if ( is_array( $query_var ) ) { + $slugs = &$query_var; + } elseif ( is_string( $query_var ) ) { + $sep = strpos( $query_var, ',' ) !== false ? ',' : '+'; // Two possible separators. + $slugs = explode( $sep, $query_var ); + } + + foreach ( $slugs as &$slug ) { + $slug = $this->get_translated_term_by( 'slug', $slug, $taxonomy ); + } + + if ( ! empty( $sep ) ) { + $query_var = implode( $sep, $slugs ); + } + + return $query_var; + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-filters-links.php b/wp-content/plugins/polylang/frontend/frontend-filters-links.php new file mode 100644 index 0000000000..f63bff1f69 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-filters-links.php @@ -0,0 +1,350 @@ + + */ + public $cache; + + /** + * Stores a list of files and functions that home_url() must not filter. + * + * @var array + */ + private $black_list = array(); + + /** + * Stores a list of files and functions that home_url() must filter. + * + * @var array + */ + private $white_list = array(); + + /** + * Constructor + * Adds filters once the language is defined + * Low priority on links filters to come after any other modification + * + * @since 1.8 + * + * @param object $polylang The Polylang object. + */ + public function __construct( &$polylang ) { + parent::__construct( $polylang ); + + $this->curlang = &$polylang->curlang; + $this->cache = new PLL_Cache(); + + // Rewrites author and date links to filter them by language + foreach ( array( 'feed_link', 'author_link', 'search_link', 'year_link', 'month_link', 'day_link' ) as $filter ) { + add_filter( $filter, array( $this, 'archive_link' ), 20 ); + } + + // Meta in the html head section + add_action( 'wp_head', array( $this, 'wp_head' ), 1 ); + + // Modifies the home url + if ( pll_get_constant( 'PLL_FILTER_HOME_URL', true ) ) { + add_filter( 'home_url', array( $this, 'home_url' ), 10, 2 ); + } + + if ( $this->options['force_lang'] > 1 ) { + // Rewrites next and previous post links when not automatically done by WordPress + add_filter( 'get_pagenum_link', array( $this, 'archive_link' ), 20 ); + + add_filter( 'get_shortlink', array( $this, 'shortlink' ), 20, 2 ); + + // Rewrites ajax url + add_filter( 'admin_url', array( $this, 'admin_url' ), 10, 2 ); + } + } + + /** + * Modifies the author and date links to add the language parameter (as well as feed link). + * + * @since 0.4 + * + * @param string $link The permalink to the archive. + * @return string The modified link. + */ + public function archive_link( $link ) { + return $this->links_model->switch_language_in_link( $link, $this->curlang ); + } + + /** + * Modifies page links + * and caches the result + * + * @since 1.7 + * + * @param string $link The page link. + * @param int $post_id The post ID. + * @return string The modified page link. + */ + public function _get_page_link( $link, $post_id ) { + $cache_key = "post:{$post_id}:{$link}"; + if ( false === $_link = $this->cache->get( $cache_key ) ) { + $_link = parent::_get_page_link( $link, $post_id ); + $this->cache->set( $cache_key, $_link ); + } + return $_link; + } + + /** + * Modifies attachment links + * and caches the result + * + * @since 1.6.2 + * + * @param string $link The attachment link. + * @param int $post_id The attachment post ID. + * @return string The modified attachment link. + */ + public function attachment_link( $link, $post_id ) { + $cache_key = "post:{$post_id}:{$link}"; + if ( false === $_link = $this->cache->get( $cache_key ) ) { + $_link = parent::attachment_link( $link, $post_id ); + $this->cache->set( $cache_key, $_link ); + } + return $_link; + } + + /** + * Modifies custom posts links + * and caches the result. + * + * @since 1.6 + * + * @param string $link The post link. + * @param WP_Post $post The post object. + * @return string The modified post link. + */ + public function post_type_link( $link, $post ) { + $cache_key = "post:{$post->ID}:{$link}"; + if ( false === $_link = $this->cache->get( $cache_key ) ) { + $_link = parent::post_type_link( $link, $post ); + $this->cache->set( $cache_key, $_link ); + } + return $_link; + } + + /** + * Modifies filtered taxonomies ( post format like ) and translated taxonomies links + * and caches the result. + * + * @since 0.7 + * + * @param string $link The term link. + * @param WP_Term $term The term object. + * @param string $tax The taxonomy name. + * @return string The modified link. + */ + public function term_link( $link, $term, $tax ) { + $cache_key = "term:{$term->term_id}:{$link}"; + if ( false === $_link = $this->cache->get( $cache_key ) ) { + if ( in_array( $tax, $this->model->get_filtered_taxonomies() ) ) { + $_link = $this->links_model->switch_language_in_link( $link, $this->curlang ); + + /** This filter is documented in include/filters-links.php */ + $_link = apply_filters( 'pll_term_link', $_link, $this->curlang, $term ); + } + + else { + $_link = parent::term_link( $link, $term, $tax ); + } + $this->cache->set( $cache_key, $_link ); + } + return $_link; + } + + /** + * Modifies the post short link when using one domain or subdomain per language. + * + * @since 2.6.9 + * + * @param string $link Post permalink. + * @param int $post_id Post id. + * @return string Post permalink with the correct domain. + */ + public function shortlink( $link, $post_id ) { + $post_type = get_post_type( $post_id ); + return $this->model->is_translated_post_type( $post_type ) ? $this->links_model->switch_language_in_link( $link, $this->model->post->get_language( $post_id ) ) : $link; + } + + /** + * Outputs references to translated pages ( if exists ) in the html head section + * + * @since 0.1 + * + * @return void + */ + public function wp_head() { + // Don't output anything on paged archives: see https://wordpress.org/support/topic/hreflang-on-page2 + // Don't output anything on paged pages and paged posts + if ( is_paged() || ( is_singular() && ( $page = get_query_var( 'page' ) ) && $page > 1 ) ) { + return; + } + + $urls = array(); + + // Google recommends to include self link https://support.google.com/webmasters/answer/189077?hl=en + foreach ( $this->model->get_languages_list() as $language ) { + if ( $url = $this->links->get_translation_url( $language ) ) { + $urls[ $language->get_locale( 'display' ) ] = $url; + } + } + + // Outputs the section only if there are translations ( $urls always contains self link ) + if ( ! empty( $urls ) && count( $urls ) > 1 ) { + $languages = array(); + $hreflangs = array(); + + // Prepare the list of languages to remove the country code + foreach ( array_keys( $urls ) as $locale ) { + $split = explode( '-', $locale ); + $languages[ $locale ] = reset( $split ); + } + + $count = array_count_values( $languages ); + + foreach ( $urls as $locale => $url ) { + $lang = $count[ $languages[ $locale ] ] > 1 ? $locale : $languages[ $locale ]; // Output the country code only when necessary + $hreflangs[ $lang ] = $url; + } + + // Adds the site root url when the default language code is not hidden + // See https://wordpress.org/support/topic/implementation-of-hreflangx-default + if ( is_front_page() && ! $this->options['hide_default'] && $this->options['force_lang'] < 3 ) { + $hreflangs['x-default'] = home_url( '/' ); + } + + /** + * Filters the list of rel hreflang attributes + * + * @since 2.1 + * + * @param array $hreflangs Array of urls with language codes as keys + */ + $hreflangs = apply_filters( 'pll_rel_hreflang_attributes', $hreflangs ); + + foreach ( $hreflangs as $lang => $url ) { + printf( '' . "\n", esc_url( $url ), esc_attr( $lang ) ); + } + } + } + + /** + * Filters the home url to get the right language. + * + * @since 0.4 + * + * @param string $url The home URL including scheme and path. + * @param string $path Path relative to the home URL. + * @return string + */ + public function home_url( $url, $path ) { + if ( ! ( did_action( 'template_redirect' ) || did_action( 'login_init' ) ) || rtrim( $url, '/' ) != $this->links_model->home ) { + return $url; + } + + // We *want* to filter the home url in these cases + if ( empty( $this->white_list ) ) { + // On Windows get_theme_root() mixes / and \ + // We want only \ for the comparison with debug_backtrace + $theme_root = get_theme_root(); + $theme_root = ( false === strpos( $theme_root, '\\' ) ) ? $theme_root : str_replace( '/', '\\', $theme_root ); + + $white_list = array( + array( 'file' => $theme_root ), + array( 'function' => 'wp_nav_menu' ), + array( 'function' => 'login_footer' ), + array( 'function' => 'get_custom_logo' ), + array( 'function' => 'render_block_core_site_title' ), + ); + + if ( 3 === $this->options['force_lang'] ) { + $white_list[] = array( 'function' => 'redirect_canonical' ); + } + + /** + * Filters the white list of the Polylang 'home_url' filter. + * + * @since 1.1.2 + * + * @param string[][] $white_list An array of arrays each of them having a 'file' key + * and/or a 'function' key to decide which functions in + * which files using home_url() calls must be filtered. + */ + $this->white_list = apply_filters( 'pll_home_url_white_list', $white_list ); + } + + // We don't want to filter the home url in these cases. + if ( empty( $this->black_list ) ) { + $black_list = array( + array( 'file' => 'searchform.php' ), // Since WP 3.6 searchform.php is passed through get_search_form. + array( 'function' => 'get_search_form' ), + ); + + /** + * Filters the black list of the Polylang 'home_url' filter. + * + * @since 1.1.2 + * + * @param string[][] $black_list An array of arrays each of them having a 'file' key + * and/or a 'function' key to decide which functions in + * which files using home_url() calls must be filtered. + */ + $this->black_list = apply_filters( 'pll_home_url_black_list', $black_list ); + } + + $traces = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions + unset( $traces[0], $traces[1] ); // We don't need the last 2 calls: this function + call_user_func_array (or apply_filters on PHP7+) + + foreach ( $traces as $trace ) { + // Black list first + foreach ( $this->black_list as $v ) { + if ( ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) ) || ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) ) { + return $url; + } + } + + foreach ( $this->white_list as $v ) { + if ( ( ! empty( $v['function'] ) && $trace['function'] === $v['function'] ) || + ( isset( $trace['file'], $v['file'] ) && false !== strpos( $trace['file'], $v['file'] ) && in_array( $trace['function'], array( 'home_url', 'get_home_url', 'bloginfo', 'get_bloginfo' ) ) ) ) { + $ok = true; + } + } + } + + return empty( $ok ) ? $url : ( empty( $path ) ? rtrim( $this->links->get_home_url( $this->curlang ), '/' ) : $this->links->get_home_url( $this->curlang ) ); + } + + /** + * Rewrites the ajax url when using domains or subdomains. + * + * @since 1.5 + * + * @param string $url The admin url with path evaluated by WordPress. + * @param string $path Path relative to the admin URL. + * @return string + */ + public function admin_url( $url, $path ) { + return 'admin-ajax.php' === $path ? $this->links_model->switch_language_in_link( $url, $this->curlang ) : $url; + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-filters-search.php b/wp-content/plugins/polylang/frontend/frontend-filters-search.php new file mode 100644 index 0000000000..cad7e22278 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-filters-search.php @@ -0,0 +1,183 @@ +links_model = &$polylang->links_model; + $this->curlang = &$polylang->curlang; + + // Adds the language information in the search form + // Low priority in case the search form is created using the same filter as described in http://codex.wordpress.org/Function_Reference/get_search_form + add_filter( 'get_search_form', array( $this, 'get_search_form' ), 99 ); + + // Adds the language information in the search block. + add_filter( 'render_block_core/search', array( $this, 'get_search_form' ) ); + + // Adds the language information in admin bar search form + add_action( 'add_admin_bar_menus', array( $this, 'add_admin_bar_menus' ) ); + + + // Adds javascript at the end of the document + // Was used for WP < 3.6. kept just in case + if ( defined( 'PLL_SEARCH_FORM_JS' ) && PLL_SEARCH_FORM_JS ) { + add_action( 'wp_footer', array( $this, 'wp_print_footer_scripts' ) ); + } + } + + /** + * Adds the language information in the search form. + * + * Does not work if searchform.php ( prior to WP 3.6 ) is used or if the search form is hardcoded in another template file + * + * @since 0.1 + * + * @param string $form The search form HTML. + * @return string Modified search form. + */ + public function get_search_form( $form ) { + if ( empty( $form ) || empty( $this->curlang ) ) { + return $form; + } + + if ( $this->links_model->using_permalinks ) { + // Take care to modify only the url in the
tag. + preg_match( '##s', $form, $matches ); + $old = reset( $matches ); + if ( empty( $old ) ) { + return $form; + } + // Replace action attribute (a text with no space and no closing tag within double quotes or simple quotes or without quotes). + $new = preg_replace( '#\saction=("[^"\r\n]+"|\'[^\'\r\n]+\'|[^\'"][^>\s]+)#', ' action="' . esc_url( $this->curlang->get_search_url() ) . '"', $old ); + if ( empty( $new ) ) { + return $form; + } + $form = str_replace( $old, $new, $form ); + } else { + $form = str_replace( '', '', $form ); + } + + return $form; + } + + /** + * Adds the language information in the admin bar search form. + * + * @since 1.2 + * + * @return void + */ + public function add_admin_bar_menus() { + // Backward compatibility with WP < 6.6. The priority was 4 before this version, 9999 since then. + $priority = has_action( 'admin_bar_menu', 'wp_admin_bar_search_menu' ); + if ( ! is_int( $priority ) ) { + return; + } + + remove_action( 'admin_bar_menu', 'wp_admin_bar_search_menu', $priority ); + add_action( 'admin_bar_menu', array( $this, 'admin_bar_search_menu' ), $priority ); + } + + /** + * Rewrites the admin bar search form to pass our get_search_form filter. See #21342. + * Code last checked: WP 5.4.1. + * + * @since 0.9 + * + * @param WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance, passed by reference. + * @return void + */ + public function admin_bar_search_menu( $wp_admin_bar ) { + $form = '
'; + $form .= ''; + $form .= ''; + $form .= ''; + $form .= '
'; + + $wp_admin_bar->add_node( + array( + 'parent' => 'top-secondary', + 'id' => 'search', + 'title' => $this->get_search_form( $form ), // Pass the get_search_form filter. + 'meta' => array( + 'class' => 'admin-bar-search', + 'tabindex' => -1, + ), + ) + ); + } + + /** + * Allows modifying the search form if it does not pass get_search_form. + * + * @since 0.1 + * + * @return void + */ + public function wp_print_footer_scripts() { + /* + * Don't use directly e[0] just in case there is somewhere else an element named 's' + * Check before if the hidden input has not already been introduced by get_search_form ( FIXME: is there a way to improve this ) ? + * Thanks to AndyDeGroo for improving the code for compatibility with old browsers + * http://wordpress.org/support/topic/development-of-polylang-version-08?replies=6#post-2645559 + */ + $lang = esc_js( $this->curlang->slug ); + $js = "e = document.getElementsByName( 's' ); + for ( i = 0; i < e.length; i++ ) { + if ( e[i].tagName.toUpperCase() == 'INPUT' ) { + s = e[i].parentNode.parentNode.children; + l = 0; + for ( j = 0; j < s.length; j++ ) { + if ( s[j].name == 'lang' ) { + l = 1; + } + } + if ( l == 0 ) { + var ih = document.createElement( 'input' ); + ih.type = 'hidden'; + ih.name = 'lang'; + ih.value = '{$lang}'; + e[i].parentNode.appendChild( ih ); + } + } + }"; + + $type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"'; + + if ( $type_attr ) { + $js = "/* */"; + } + + echo "\n{$js}\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-filters-widgets.php b/wp-content/plugins/polylang/frontend/frontend-filters-widgets.php new file mode 100644 index 0000000000..bcdf907c09 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-filters-widgets.php @@ -0,0 +1,153 @@ + + */ + public $cache; + + /** + * Current language. + * + * @var PLL_Language|null + */ + public $curlang; + + /** + * Constructor: setups filters and actions. + * + * @since 1.2 + * + * @param object $polylang The Polylang object. + */ + public function __construct( &$polylang ) { + $this->curlang = &$polylang->curlang; + $this->cache = new PLL_Cache(); + + add_filter( 'sidebars_widgets', array( $this, 'sidebars_widgets' ) ); + } + + /** + * Remove widgets from sidebars if they are not visible in the current language + * Needed to allow is_active_sidebar() to return false if all widgets are not for the current language. See #54 + * + * @since 2.1 + * @since 2.4 The result is cached as the function can be very expensive in case there are a lot of widgets + * + * @param array $sidebars_widgets An associative array of sidebars and their widgets + * @return array + */ + public function sidebars_widgets( $sidebars_widgets ) { + global $wp_registered_widgets; + + if ( empty( $wp_registered_widgets ) ) { + return $sidebars_widgets; + } + + $cache_key = $this->cache->get_unique_key( 'sidebars_widgets_', $sidebars_widgets ); + $_sidebars_widgets = $this->cache->get( $cache_key ); + + if ( false !== $_sidebars_widgets ) { + return $_sidebars_widgets; + } + + $sidebars_widgets = $this->filter_widgets_sidebars( $sidebars_widgets, $wp_registered_widgets ); + + return $this->cache->set( $cache_key, $sidebars_widgets ); + } + + /** + * Method that handles the removal of widgets in the sidebars depending on their display language. + * + * @since 3.1 + * + * @param array $widget_data An array containing the widget data + * @param array $sidebars_widgets An associative array of sidebars and their widgets + * @param string $sidebar Sidebar name + * @param int $key Widget number + * @return array An associative array of sidebars and their widgets + */ + public function handle_widget_in_sidebar_callback( $widget_data, $sidebars_widgets, $sidebar, $key ) { + // Remove the widget if not visible in the current language + if ( ! empty( $widget_data['settings'][ $widget_data['number'] ]['pll_lang'] ) && $widget_data['settings'][ $widget_data['number'] ]['pll_lang'] !== $this->curlang->slug ) { + unset( $sidebars_widgets[ $sidebar ][ $key ] ); + } + return $sidebars_widgets; + } + + /** + * Browse the widgets sidebars and sort the ones that should be displayed or not. + * + * @since 3.1 + * + * @param array $sidebars_widgets An associative array of sidebars and their widgets + * @param array $wp_registered_widgets Array of all registered widgets. + * @return array An associative array of sidebars and their widgets + */ + public function filter_widgets_sidebars( $sidebars_widgets, $wp_registered_widgets ) { + foreach ( $sidebars_widgets as $sidebar => $widgets ) { + if ( 'wp_inactive_widgets' === $sidebar || empty( $widgets ) ) { + continue; + } + + foreach ( $widgets as $key => $widget ) { + if ( ! $this->is_widget_object( $wp_registered_widgets, $widget ) ) { + continue; + } + + $widget_data = $this->get_widget_data( $wp_registered_widgets, $widget ); + + $sidebars_widgets = $this->handle_widget_in_sidebar_callback( $widget_data, $sidebars_widgets, $sidebar, $key ); + } + } + return $sidebars_widgets; + } + + /** + * Test if the widget is an object. + * + * @since 3.1 + * + * @param array $wp_registered_widgets Array of all registered widgets. + * @param string $widget String that identifies the widget. + * @return bool True if object, false otherwise. + */ + protected function is_widget_object( $wp_registered_widgets, $widget ) { + // Nothing can be done if the widget is created using pre WP2.8 API :( + // There is no object, so we can't access it to get the widget options + return isset( $wp_registered_widgets[ $widget ]['callback'] ) && + is_array( $wp_registered_widgets[ $widget ]['callback'] ) && + isset( $wp_registered_widgets[ $widget ]['callback'][0] ) && + is_object( $wp_registered_widgets[ $widget ]['callback'][0] ) && + method_exists( $wp_registered_widgets[ $widget ]['callback'][0], 'get_settings' ); + } + + /** + * Get widgets settings and number. + * + * @since 3.1 + * + * @param array $wp_registered_widgets Array of all registered widgets. + * @param string $widget String that identifies the widget. + * @return array An array containing the widget settings and number. + */ + protected function get_widget_data( $wp_registered_widgets, $widget ) { + $widget_settings = $wp_registered_widgets[ $widget ]['callback'][0]->get_settings(); + $number = $wp_registered_widgets[ $widget ]['params'][0]['number']; + + return array( + 'settings' => $widget_settings, + 'number' => $number, + ); + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-filters.php b/wp-content/plugins/polylang/frontend/frontend-filters.php new file mode 100644 index 0000000000..83558128e8 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-filters.php @@ -0,0 +1,210 @@ +options['media_support'] ) { + add_filter( 'widget_media_image_instance', array( $this, 'widget_media_instance' ), 1 ); // Since WP 4.8 + } + + // Strings translation ( must be applied before WordPress applies its default formatting filters ) + foreach ( array( 'widget_text', 'widget_title' ) as $filter ) { + add_filter( $filter, 'pll__', 1 ); + } + + // Translates biography + add_filter( 'get_user_metadata', array( $this, 'get_user_metadata' ), 10, 4 ); + + if ( Polylang::is_ajax_on_front() ) { + add_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ) ); + } + } + + /** + * Returns the locale based on current language + * + * @since 0.1 + * + * @return string + */ + public function get_locale() { + return $this->curlang->locale; + } + + /** + * Filters sticky posts by current language. + * + * @since 0.8 + * + * @param int[] $posts List of sticky posts ids. + * @return int[] Modified list of sticky posts ids + */ + public function option_sticky_posts( $posts ) { + global $wpdb; + + // Do not filter sticky posts on REST requests as $this->curlang is *not* the 'lang' parameter set in the request + if ( ! defined( 'REST_REQUEST' ) && ! empty( $this->curlang ) && ! empty( $posts ) ) { + $_posts = wp_cache_get( 'sticky_posts', 'options' ); // This option is usually cached in 'all_options' by WP. + $tt_id = $this->curlang->get_tax_prop( 'language', 'term_taxonomy_id' ); + + if ( empty( $_posts ) || ! is_array( $_posts ) || empty( $_posts[ $tt_id ] ) || ! is_array( $_posts[ $tt_id ] ) ) { + $posts = array_map( 'intval', $posts ); + $posts = implode( ',', $posts ); + + $languages = array(); + foreach ( $this->model->get_languages_list() as $language ) { + $languages[] = $language->get_tax_prop( 'language', 'term_taxonomy_id' ); + } + + $_posts = array_fill_keys( $languages, array() ); // Init with empty arrays + $languages = implode( ',', $languages ); + + // PHPCS:ignore WordPress.DB.PreparedSQL + $relations = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM {$wpdb->term_relationships} WHERE object_id IN ({$posts}) AND term_taxonomy_id IN ({$languages})" ); + + foreach ( $relations as $relation ) { + $_posts[ $relation->term_taxonomy_id ][] = (int) $relation->object_id; + } + wp_cache_add( 'sticky_posts', $_posts, 'options' ); + } + + $posts = $_posts[ $tt_id ]; + } + + return $posts; + } + + /** + * Modifies the sql request for wp_get_archives to filter by the current language + * + * @since 1.9 + * + * @param string $sql JOIN clause + * @param array $r wp_get_archives arguments + * @return string modified JOIN clause + */ + public function getarchives_join( $sql, $r ) { + return ! empty( $r['post_type'] ) && $this->model->is_translated_post_type( $r['post_type'] ) ? $sql . $this->model->post->join_clause() : $sql; + } + + /** + * Modifies the sql request for wp_get_archives to filter by the current language + * + * @since 1.9 + * + * @param string $sql WHERE clause + * @param array $r wp_get_archives arguments + * @return string modified WHERE clause + */ + public function getarchives_where( $sql, $r ) { + if ( ! $this->curlang instanceof PLL_Language ) { + return $sql; + } + + if ( empty( $r['post_type'] ) || ! $this->model->is_translated_post_type( $r['post_type'] ) ) { + return $sql; + } + + return $sql . $this->model->post->where_clause( $this->curlang ); + } + + /** + * Filters the widgets according to the current language + * Don't display if a language filter is set and this is not the current one + * Needed for {@see https://developer.wordpress.org/reference/functions/the_widget/ the_widget()}. + * + * @since 0.3 + * + * @param array $instance Widget settings + * @return bool|array false if we hide the widget, unmodified $instance otherwise + */ + public function widget_display_callback( $instance ) { + return ! empty( $instance['pll_lang'] ) && $instance['pll_lang'] != $this->curlang->slug ? false : $instance; + } + + /** + * Translates media in media widgets + * + * @since 2.1.5 + * + * @param array $instance Widget instance data + * @return array + */ + public function widget_media_instance( $instance ) { + if ( empty( $instance['pll_lang'] ) && $instance['attachment_id'] && $tr_id = pll_get_post( $instance['attachment_id'] ) ) { + $instance['attachment_id'] = $tr_id; + $attachment = get_post( $tr_id ); + + if ( $instance['caption'] && ! empty( $attachment->post_excerpt ) ) { + $instance['caption'] = $attachment->post_excerpt; + } + + if ( $instance['alt'] && $alt_text = get_post_meta( $tr_id, '_wp_attachment_image_alt', true ) ) { + $instance['alt'] = $alt_text; + } + + if ( $instance['image_title'] && ! empty( $attachment->post_title ) ) { + $instance['image_title'] = $attachment->post_title; + } + } + return $instance; + } + + /** + * Translates the biography. + * + * @since 0.9 + * + * @param null $null Expecting the default null value. + * @param int $id The user id. + * @param string $meta_key The metadata key. + * @param bool $single Whether to return only the first value of the specified $meta_key. + * @return string|null + */ + public function get_user_metadata( $null, $id, $meta_key, $single ) { + return 'description' === $meta_key && ! empty( $this->curlang ) && ! $this->curlang->is_default ? get_user_meta( $id, 'description_' . $this->curlang->slug, $single ) : $null; + } + + /** + * Filters the translation files to load when doing ajax on front + * This is needed because WP the language files associated to the user locale when a user is logged in + * + * @since 2.2.6 + * + * @param string $mofile Translation file name + * @return string + */ + public function load_textdomain_mofile( $mofile ) { + $user_locale = get_user_locale(); + return str_replace( "{$user_locale}.mo", "{$this->curlang->locale}.mo", $mofile ); + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-links.php b/wp-content/plugins/polylang/frontend/frontend-links.php new file mode 100644 index 0000000000..364f9d7165 --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-links.php @@ -0,0 +1,228 @@ + + */ + public $cache; + + /** + * Constructor + * + * @since 1.2 + * + * @param object $polylang The Polylang object. + */ + public function __construct( &$polylang ) { + parent::__construct( $polylang ); + + $this->curlang = &$polylang->curlang; + $this->cache = new PLL_Cache(); + } + + /** + * Returns the url of the translation (if it exists) of the current page. + * + * @since 0.1 + * + * @param PLL_Language $language Language object. + * @return string + */ + public function get_translation_url( $language ) { + global $wp_query; + + if ( false !== $translation_url = $this->cache->get( 'translation_url:' . $language->slug ) ) { + return $translation_url; + } + + // Make sure that we have the queried object + // See https://wordpress.org/support/topic/patch-for-fixing-a-notice + $queried_object_id = $wp_query->get_queried_object_id(); + + /** + * Filters the translation url before Polylang attempts to find one. + * Internally used by Polylang for the static front page and posts page. + * + * @since 1.8 + * + * @param string $url Empty string or the url of the translation of the current page. + * @param PLL_Language $language Language of the translation. + * @param int $queried_object_id Queried object ID. + */ + if ( ! $url = apply_filters( 'pll_pre_translation_url', '', $language, $queried_object_id ) ) { + $qv = $wp_query->query_vars; + + // Post and attachment + if ( is_single() && ( $this->options['media_support'] || ! is_attachment() ) && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->model->post->current_user_can_read( $id ) ) { + $url = get_permalink( $id ); + } + + // Page + elseif ( is_page() && ( $id = $this->model->post->get( $queried_object_id, $language ) ) && $this->model->post->current_user_can_read( $id ) ) { + $url = get_page_link( $id ); + } + + elseif ( is_search() ) { + $url = $this->get_archive_url( $language ); + + // Special case for search filtered by translated taxonomies: taxonomy terms are translated in the translation url + if ( ! empty( $wp_query->tax_query->queries ) ) { + foreach ( $wp_query->tax_query->queries as $tax_query ) { + if ( ! empty( $tax_query['taxonomy'] ) && $this->model->is_translated_taxonomy( $tax_query['taxonomy'] ) ) { + + $tax = get_taxonomy( $tax_query['taxonomy'] ); + $terms = get_terms( array( 'taxonomy' => $tax->name, 'fields' => 'id=>slug' ) ); // Filtered by current language + + foreach ( $tax_query['terms'] as $slug ) { + $term_id = array_search( $slug, $terms ); // What is the term_id corresponding to taxonomy term? + if ( $term_id && $term_id = $this->model->term->get_translation( $term_id, $language ) ) { // Get the translated term_id + $term = get_term( $term_id, $tax->name ); + + if ( ! $term instanceof WP_Term ) { + continue; + } + + $url = str_replace( $slug, $term->slug, $url ); + } + } + } + } + } + } + + // Translated taxonomy + // Take care that is_tax() is false for categories and tags + elseif ( ( is_category() || is_tag() || is_tax() ) && ( $term = get_queried_object() ) && $this->model->is_translated_taxonomy( $term->taxonomy ) ) { + $lang = $this->model->term->get_language( $term->term_id ); + + if ( ! $lang || $language->slug == $lang->slug ) { + $url = get_term_link( $term, $term->taxonomy ); // Self link + } + + elseif ( $tr_id = $this->model->term->get_translation( $term->term_id, $language ) ) { + $tr_term = get_term( $tr_id, $term->taxonomy ); + if ( $tr_term instanceof WP_Term ) { + // Check if translated term ( or children ) have posts + $count = $tr_term->count || ( is_taxonomy_hierarchical( $term->taxonomy ) && array_sum( wp_list_pluck( get_terms( array( 'taxonomy' => $term->taxonomy, 'child_of' => $tr_term->term_id, 'lang' => $language->slug ) ), 'count' ) ) ); + + /** + * Filter whether to hide an archive translation url + * + * @since 2.2.4 + * + * @param bool $hide True to hide the translation url. + * defaults to true when the translated archive is empty, false otherwise. + * @param string $lang The language code of the translation + * @param array $args Arguments used to evaluated the number of posts in the archive + */ + if ( ! apply_filters( 'pll_hide_archive_translation_url', ! $count, $language->slug, array( 'taxonomy' => $term->taxonomy ) ) ) { + $url = get_term_link( $tr_term, $term->taxonomy ); + } + } + } + } + + // Post type archive + elseif ( is_post_type_archive() ) { + if ( $this->model->is_translated_post_type( $qv['post_type'] ) ) { + $args = array( 'post_type' => $qv['post_type'] ); + $count = $this->model->count_posts( $language, $args ); + + /** This filter is documented in frontend/frontend-links.php */ + if ( ! apply_filters( 'pll_hide_archive_translation_url', ! $count, $language->slug, $args ) ) { + $url = $this->get_archive_url( $language ); + } + } + } + + // Other archives + elseif ( is_archive() ) { + $keys = array( 'post_type', 'm', 'year', 'monthnum', 'day', 'author', 'author_name' ); + $keys = array_merge( $keys, $this->model->get_filtered_taxonomies_query_vars() ); + $args = array_intersect_key( $qv, array_flip( $keys ) ); + $count = $this->model->count_posts( $language, $args ); + + /** This filter is documented in frontend/frontend-links.php */ + if ( ! apply_filters( 'pll_hide_archive_translation_url', ! $count, $language->slug, $args ) ) { + $url = $this->get_archive_url( $language ); + } + } + + // Front page when it is the list of posts + elseif ( is_front_page() ) { + $url = $this->get_home_url( $language ); + } + } + + $url = ! empty( $url ) && ! is_wp_error( $url ) ? $url : null; + + /** + * Filter the translation url of the current page before Polylang caches it + * + * @since 1.1.2 + * + * @param null|string $url The translation url, null if none was found + * @param string $language The language code of the translation + */ + $translation_url = (string) apply_filters( 'pll_translation_url', $url, $language->slug ); + + // Don't cache before template_redirect to avoid a conflict with Barrel + WP Bakery Page Builder + if ( did_action( 'template_redirect' ) ) { + $this->cache->set( 'translation_url:' . $language->slug, $translation_url ); + } + + return $translation_url; + } + + /** + * Get the translation of the current archive url + * used also for search + * + * @since 1.2 + * + * @param PLL_Language $language An object representing a language. + * @return string + */ + public function get_archive_url( $language ) { + $url = pll_get_requested_url(); + $url = $this->links_model->switch_language_in_link( $url, $language ); + $url = $this->links_model->remove_paged_from_link( $url ); + + /** + * Filter the archive url + * + * @since 1.6 + * + * @param string $url Url of the archive + * @param object $language Language of the archive + */ + return apply_filters( 'pll_get_archive_url', $url, $language ); + } + + /** + * Returns the home url in the right language. + * + * @since 0.1 + * + * @param PLL_Language|string $language Optional, defaults to current language. + * @param bool $is_search Optional, whether we need the home url for a search form, defaults to false. + */ + public function get_home_url( $language = '', $is_search = false ) { + if ( empty( $language ) ) { + $language = $this->curlang; + } + + return parent::get_home_url( $language, $is_search ); + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-nav-menu.php b/wp-content/plugins/polylang/frontend/frontend-nav-menu.php new file mode 100644 index 0000000000..c4ac02a60e --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-nav-menu.php @@ -0,0 +1,339 @@ +curlang = &$polylang->curlang; + + // Split the language switcher menu item in several language menu items + add_filter( 'wp_get_nav_menu_items', array( $this, 'wp_get_nav_menu_items' ), 20 ); // after the customizer menus + add_filter( 'wp_nav_menu_objects', array( $this, 'wp_nav_menu_objects' ) ); + add_filter( 'nav_menu_link_attributes', array( $this, 'nav_menu_link_attributes' ), 10, 2 ); + + // Filters menus by language + add_filter( 'theme_mod_nav_menu_locations', array( $this, 'nav_menu_locations' ), 20 ); + add_filter( 'wp_nav_menu_args', array( $this, 'wp_nav_menu_args' ) ); + + // The customizer + if ( isset( $_POST['wp_customize'], $_POST['customized'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_before_customizer' ) ); + add_filter( 'wp_nav_menu_args', array( $this, 'filter_args_after_customizer' ), 2000 ); + } + } + + /** + * Sorts menu items by menu order. + * + * @since 1.7.9 + * + * @param stdClass $a The first object to compare. + * @param stdClass $b The second object to compare. + * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b. + */ + protected function usort_menu_items( $a, $b ) { + return ( $a->menu_order < $b->menu_order ) ? -1 : 1; + } + + /** + * Format a language switcher menu item title based on options + * + * @since 2.2.6 + * + * @param string $flag Formatted flag + * @param string $name Language name + * @param array $options Language switcher options + * @return string Formatted menu item title + */ + protected function get_item_title( $flag, $name, $options ) { + if ( $options['show_flags'] ) { + if ( $options['show_names'] ) { + $title = sprintf( '%1$s%3$s', $flag, is_rtl() ? 'right' : 'left', esc_html( $name ) ); + } else { + $title = $flag; + } + } else { + $title = esc_html( $name ); + } + return $title; + } + + /** + * Splits the one language switcher menu item of backend in several menu items on frontend. + * Takes care to menu_order as it is used later in wp_nav_menu(). + * + * @since 1.1.1 + * + * @param stdClass[] $items Menu items. + * @return stdClass[] Modified menu items. + */ + public function wp_get_nav_menu_items( $items ) { + if ( empty( $this->curlang ) ) { + return $items; + } + + if ( doing_action( 'customize_register' ) ) { // needed since WP 4.3, doing_action available since WP 3.9 + return $items; + } + + // The customizer menus does not sort the items and we need them to be sorted before splitting the language switcher + usort( $items, array( $this, 'usort_menu_items' ) ); + + $new_items = array(); + + $offset = 0; + + foreach ( $items as $item ) { + if ( $options = get_post_meta( $item->ID, '_pll_menu_item', true ) ) { + /** This filter is documented in include/switcher.php */ + $options = apply_filters( 'pll_the_languages_args', $options ); // Honor the filter here for 'show_flags', 'show_names' and 'dropdown'. + + $switcher = new PLL_Switcher(); + $args = array_merge( array( 'raw' => 1 ), $options ); + + /** @var array */ + $the_languages = $switcher->the_languages( PLL()->links, $args ); + + // parent item for dropdown + if ( ! empty( $options['dropdown'] ) ) { + $name = isset( $options['display_names_as'] ) && 'slug' === $options['display_names_as'] ? $this->curlang->slug : $this->curlang->name; + $item->title = $this->get_item_title( $this->curlang->get_display_flag( empty( $options['show_names'] ) ? 'alt' : 'no-alt' ), $name, $options ); + $item->attr_title = ''; + $item->classes = array( 'pll-parent-menu-item' ); + $item->menu_order += $offset; + $new_items[] = $item; + ++$offset; + } + + $i = 0; // for incrementation of menu order only in case of dropdown + foreach ( $the_languages as $lang ) { + ++$i; + $lang_item = clone $item; + $lang_item->ID = $lang_item->ID . '-' . $lang['slug']; // A unique ID + $lang_item->title = $this->get_item_title( $lang['flag'], $lang['name'], $options ); + $lang_item->attr_title = ''; + $lang_item->url = $lang['url']; + $lang_item->lang = $lang['locale']; // Save this for use in nav_menu_link_attributes + $lang_item->classes = $lang['classes']; + if ( ! empty( $options['dropdown'] ) ) { + $lang_item->menu_order = $item->menu_order + $i; + $lang_item->menu_item_parent = $item->db_id; + $lang_item->db_id = 0; // to avoid recursion + } else { + $lang_item->menu_order += $offset; + } + $new_items[] = $lang_item; + ++$offset; + } + --$offset; + } else { + $item->menu_order += $offset; + $new_items[] = $item; + } + } + return $new_items; + } + + /** + * Returns the ancestors of a menu item. + * + * @since 1.1.1 + * + * @param stdClass $item Menu item. + * @return int[] Ancestors ids. + */ + public function get_ancestors( $item ) { + $ids = array(); + $_anc_id = (int) $item->db_id; + while ( ( $_anc_id = get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) ) && ! in_array( $_anc_id, $ids ) ) { + $ids[] = $_anc_id; + } + return $ids; + } + + /** + * Removes current-menu and current-menu-ancestor classes to lang switcher when not on the home page. + * + * @since 1.1.1 + * + * @param stdClass[] $items An array of menu items. + * @return stdClass[] + */ + public function wp_nav_menu_objects( $items ) { + $r_ids = $k_ids = array(); + + foreach ( $items as $item ) { + if ( ! empty( $item->classes ) && is_array( $item->classes ) ) { + if ( in_array( 'current-lang', $item->classes ) ) { + $item->current = false; + $item->classes = array_diff( $item->classes, array( 'current-menu-item' ) ); + $r_ids = array_merge( $r_ids, $this->get_ancestors( $item ) ); // Remove the classes for these ancestors + } elseif ( in_array( 'current-menu-item', $item->classes ) ) { + $k_ids = array_merge( $k_ids, $this->get_ancestors( $item ) ); // Keep the classes for these ancestors + } + } + } + + $r_ids = array_diff( $r_ids, $k_ids ); + + foreach ( $items as $item ) { + if ( ! empty( $item->db_id ) && in_array( $item->db_id, $r_ids ) ) { + $item->classes = array_diff( $item->classes, array( 'current-menu-ancestor', 'current-menu-parent', 'current_page_parent', 'current_page_ancestor' ) ); + } + } + + return $items; + } + + /** + * Adds hreflang attribute for the language switcher menu items. + * available since WP 3.6. + * + * @since 1.1 + * + * @param string[] $atts HTML attributes applied to the menu item's `` element. + * @param stdClass $item Menu item. + * @return string[] Modified attributes. + */ + public function nav_menu_link_attributes( $atts, $item ) { + if ( isset( $item->lang ) ) { + $atts['lang'] = $atts['hreflang'] = esc_attr( $item->lang ); + } + return $atts; + } + + /** + * Fills the theme nav menus locations with the right menu in the right language + * Needs to wait for the language to be defined + * + * @since 1.2 + * + * @param array|bool $menus list of nav menus locations, false if menu locations have not been filled yet + * @return array|bool modified list of nav menus locations + */ + public function nav_menu_locations( $menus ) { + if ( is_array( $menus ) && ! empty( $this->curlang ) ) { + // First get multilingual menu locations from DB + $theme = get_option( 'stylesheet' ); + + foreach ( array_keys( $menus ) as $loc ) { + $menus[ $loc ] = empty( $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ] ) ? 0 : $this->options['nav_menus'][ $theme ][ $loc ][ $this->curlang->slug ]; + } + + // Support for theme customizer + if ( is_customize_preview() ) { + global $wp_customize; + foreach ( $wp_customize->unsanitized_post_values() as $key => $value ) { + if ( false !== strpos( $key, 'nav_menu_locations[' ) ) { + $loc = substr( trim( $key, ']' ), 19 ); + $infos = $this->explode_location( $loc ); + if ( $infos['lang'] === $this->curlang->slug ) { + $menus[ $infos['location'] ] = (int) $value; + } elseif ( $this->curlang->is_default ) { + $menus[ $loc ] = (int) $value; + } + } + } + } + } + return $menus; + } + + /** + * Attempts to translate the nav menu when it is hardcoded or when no location is defined in wp_nav_menu(). + * + * @since 1.7.10 + * + * @param array $args Array of `wp_nav_menu()` arguments. + * @return array + */ + public function wp_nav_menu_args( $args ) { + $theme = get_option( 'stylesheet' ); + + if ( empty( $this->curlang ) || empty( $this->options['nav_menus'][ $theme ] ) ) { + return $args; + } + + // Get the nav menu based on the requested menu + $menu = wp_get_nav_menu_object( $args['menu'] ); + + // Attempt to find a translation of this menu + // This obviously does not work if the nav menu has no associated theme location + if ( $menu ) { + foreach ( $this->options['nav_menus'][ $theme ] as $menus ) { + if ( in_array( $menu->term_id, $menus ) && ! empty( $menus[ $this->curlang->slug ] ) ) { + $args['menu'] = $menus[ $this->curlang->slug ]; + return $args; + } + } + } + + // Get the first menu that has items and and is in the current language if we still can't find a menu + if ( ! $menu && ! $args['theme_location'] ) { + $menus = wp_get_nav_menus(); + foreach ( $menus as $menu_maybe ) { + if ( wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) ) ) { + foreach ( $this->options['nav_menus'][ $theme ] as $menus ) { + if ( in_array( $menu_maybe->term_id, $menus ) && ! empty( $menus[ $this->curlang->slug ] ) ) { + $args['menu'] = $menus[ $this->curlang->slug ]; + return $args; + } + } + } + } + } + + return $args; + } + + /** + * Filters the nav menu location before the customizer so that it matches the temporary location in the customizer + * + * @since 1.8 + * + * @param array $args wp_nav_menu $args + * @return array modified $args + */ + public function filter_args_before_customizer( $args ) { + if ( ! empty( $this->curlang ) ) { + $args['theme_location'] = $this->combine_location( $args['theme_location'], $this->curlang ); + } + return $args; + } + + /** + * Filters the nav menu location after the customizer to get back the true nav menu location for the theme + * + * @since 1.8 + * + * @param array $args wp_nav_menu $args + * @return array modified $args + */ + public function filter_args_after_customizer( $args ) { + $infos = $this->explode_location( $args['theme_location'] ); + $args['theme_location'] = $infos['location']; + return $args; + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend-static-pages.php b/wp-content/plugins/polylang/frontend/frontend-static-pages.php new file mode 100644 index 0000000000..bb076230ad --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend-static-pages.php @@ -0,0 +1,318 @@ +links_model = &$polylang->links_model; + $this->links = &$polylang->links; + $this->options = &$polylang->options; + + add_action( 'pll_home_requested', array( $this, 'pll_home_requested' ) ); + + // Manages the redirection of the homepage. + add_filter( 'redirect_canonical', array( $this, 'redirect_canonical' ) ); + + add_filter( 'pll_pre_translation_url', array( $this, 'pll_pre_translation_url' ), 10, 3 ); + add_filter( 'pll_check_canonical_url', array( $this, 'pll_check_canonical_url' ) ); + + add_filter( 'pll_set_language_from_query', array( $this, 'page_on_front_query' ), 10, 2 ); + add_filter( 'pll_set_language_from_query', array( $this, 'page_for_posts_query' ), 10, 2 ); + + // Specific cases for the customizer. + add_action( 'customize_register', array( $this, 'filter_customizer' ) ); + } + + /** + * Translates the page_id query var when the site root page is requested + * + * @since 1.8 + * + * @return void + */ + public function pll_home_requested() { + set_query_var( 'page_id', $this->curlang->page_on_front ); + } + + /** + * Manages the canonical redirect of the homepage when using a page on front. + * + * @since 0.1 + * + * @param string $redirect_url The redirect url. + * @return string|false The modified url, false if the redirect is canceled. + */ + public function redirect_canonical( $redirect_url ) { + if ( is_page() && ! is_feed() && get_queried_object_id() == $this->curlang->page_on_front ) { + $url = is_paged() ? $this->links_model->add_paged_to_link( $this->links->get_home_url(), get_query_var( 'page' ) ) : $this->links->get_home_url(); + + // Don't forget additional query vars + $query = wp_parse_url( $redirect_url, PHP_URL_QUERY ); + if ( ! empty( $query ) ) { + parse_str( $query, $query_vars ); + $query_vars = rawurlencode_deep( $query_vars ); // WP encodes query vars values + $url = add_query_arg( $query_vars, $url ); + } + + return $url; + } + + return $redirect_url; + } + + /** + * Translates the url of the page on front and page for posts. + * + * @since 1.8 + * + * @param string $url Empty string or the url of the translation of the current page. + * @param PLL_Language $language Language of the translation. + * @param int $queried_object_id Queried object ID. + * @return string The translation url. + */ + public function pll_pre_translation_url( $url, $language, $queried_object_id ) { + if ( empty( $queried_object_id ) ) { + return $url; + } + + // Page for posts. + if ( $GLOBALS['wp_query']->is_posts_page ) { + $id = $this->model->post->get( $queried_object_id, $language ); + + if ( ! empty( $id ) ) { + return (string) get_permalink( $id ); + } + } + + // Page on front. + if ( is_front_page() && ! empty( $language->page_on_front ) ) { + $id = $this->model->post->get( $queried_object_id, $language ); + + if ( $language->page_on_front === $id ) { + return $language->get_home_url(); + } + } + + return $url; + } + + /** + * Prevents the canonical redirect if we are on a static front page. + * + * @since 1.8 + * + * @param string $redirect_url The redirect url. + * @return string|false + */ + public function pll_check_canonical_url( $redirect_url ) { + return $this->options['redirect_lang'] && ! $this->options['force_lang'] && ! empty( $this->curlang->page_on_front ) && is_page( $this->curlang->page_on_front ) ? false : $redirect_url; + } + + /** + * Is the query for a the static front page (redirected from the language page)? + * + * @since 2.3 + * + * @param WP_Query $query The WP_Query object. + * @return bool + */ + protected function is_front_page( $query ) { + $query = array_diff( array_keys( $query->query ), array( 'preview', 'page', 'paged', 'cpage', 'orderby' ) ); + return 1 === count( $query ) && in_array( 'lang', $query ); + } + + /** + * Setups query vars when requesting a static front page + * + * @since 1.8 + * + * @param PLL_Language|false $lang The current language, false if it is not set yet. + * @param WP_Query $query The main WP query. + * @return PLL_Language|false + */ + public function page_on_front_query( $lang, $query ) { + if ( ! empty( $lang ) || ! $this->page_on_front ) { + return $lang; + } + + // Redirect the language page to the homepage when using a static front page + if ( ( $this->options['redirect_lang'] || $this->options['hide_default'] ) && $this->is_front_page( $query ) && $lang = $this->model->get_language( get_query_var( 'lang' ) ) ) { + $query->is_archive = $query->is_tax = false; + if ( 'page' === get_option( 'show_on_front' ) && ! empty( $lang->page_on_front ) ) { + $query->set( 'page_id', $lang->page_on_front ); + $query->is_singular = $query->is_page = true; + unset( $query->query_vars['lang'], $query->queried_object ); // Reset queried object + } else { + // Handle case where the static front page hasn't be translated to avoid a possible infinite redirect loop. + $query->is_home = true; + } + } + + // Fix paged static front page in plain permalinks when Settings > Reading doesn't match the default language + elseif ( ! $this->links_model->using_permalinks && count( $query->query ) === 1 && ! empty( $query->query['page'] ) ) { + $lang = $this->model->get_default_language(); + if ( empty( $lang ) ) { + return $lang; + } + $query->set( 'page_id', $lang->page_on_front ); + $query->is_singular = $query->is_page = true; + $query->is_archive = $query->is_tax = false; + unset( $query->query_vars['lang'], $query->queried_object ); // Reset queried object + } + + // Set the language when requesting a static front page + else { + $page_id = $this->get_page_id( $query ); + $languages = $this->model->get_languages_list(); + $pages = wp_list_pluck( $languages, 'page_on_front' ); + + if ( ! empty( $page_id ) && false !== $n = array_search( $page_id, $pages ) ) { + $lang = $languages[ $n ]; + } + } + + // Fix for page_on_front + if ( ( $this->options['force_lang'] < 2 || ! $this->options['redirect_lang'] ) && $this->links_model->using_permalinks && ! empty( $lang ) && isset( $query->query['paged'] ) ) { + $query->set( 'page', $query->query['paged'] ); + unset( $query->query['paged'] ); + } elseif ( ! $this->links_model->using_permalinks && ! empty( $query->query['page'] ) ) { + $query->is_paged = true; + } + + return $lang; + } + + /** + * Setups query vars when requesting a posts page + * + * @since 1.8 + * + * @param PLL_Language|false $lang The current language, false if it is not set yet. + * @param WP_Query $query The main WP query. + * @return PLL_Language|false + */ + public function page_for_posts_query( $lang, $query ) { + if ( ! empty( $lang ) || ! $this->page_for_posts ) { + return $lang; + } + + $page_id = $this->get_page_id( $query ); + + if ( empty( $page_id ) ) { + return $lang; + } + + $pages = $this->model->get_languages_list( array( 'fields' => 'page_for_posts' ) ); + $pages = array_filter( $pages ); + + if ( in_array( $page_id, $pages ) ) { + _prime_post_caches( $pages ); // Fill the cache with all pages for posts to avoid one query per page later. + + $lang = $this->model->post->get_language( $page_id ); + $query->is_singular = $query->is_page = false; + $query->is_home = $query->is_posts_page = true; + } + + return $lang; + } + + /** + * Get the queried page_id (if it exists ). + * + * If permalinks are used, WordPress does set and use `$query->queried_object_id` and sets `$query->query_vars['page_id']` to 0, + * and does set and use `$query->query_vars['page_id']` if permalinks are not used :(. + * + * @since 1.5 + * + * @param WP_Query $query Instance of WP_Query. + * @return int The page_id. + */ + protected function get_page_id( $query ) { + if ( ! empty( $query->query_vars['pagename'] ) && isset( $query->queried_object_id ) ) { + return $query->queried_object_id; + } + + if ( isset( $query->query_vars['page_id'] ) ) { + return $query->query_vars['page_id']; + } + + return 0; // No page queried. + } + + /** + * Adds support for the theme customizer. + * + * @since 3.4.2 + * + * @return void + */ + public function filter_customizer() { + add_filter( 'pre_option_page_on_front', array( $this, 'customize_page' ), 20 ); // After the customizer. + add_filter( 'pre_option_page_for_post', array( $this, 'customize_page' ), 20 ); + + add_filter( 'pll_pre_translation_url', array( $this, 'customize_translation_url' ), 20, 2 ); // After the generic hook in this class. + } + + /** + * Translates the page ID when customized. + * + * @since 3.4.2 + * + * @param int|false $pre A page ID if the setting is customized, false otherwise. + * @return int|false + */ + public function customize_page( $pre ) { + return is_numeric( $pre ) ? pll_get_post( (int) $pre ) : $pre; + } + + /** + * Fixes the translation URL if the option 'show_on_front' is customized. + * + * @since 3.4.2 + * + * @param string $url An empty string or the URL of the translation of the current page. + * @param PLL_Language $language The language of the translation. + * @return string + */ + public function customize_translation_url( $url, $language ) { + if ( 'posts' === get_option( 'show_on_front' ) && is_front_page() ) { + // When the page on front displays posts, the home URL is the same as the search URL. + return $language->get_search_url(); + } + return $url; + } +} diff --git a/wp-content/plugins/polylang/frontend/frontend.php b/wp-content/plugins/polylang/frontend/frontend.php new file mode 100644 index 0000000000..1997803b6a --- /dev/null +++ b/wp-content/plugins/polylang/frontend/frontend.php @@ -0,0 +1,287 @@ +static_pages = new PLL_Frontend_Static_Pages( $this ); + } + + $this->model->set_languages_ready(); + } + + /** + * Setups the language chooser based on options + * + * @since 1.2 + */ + public function init() { + parent::init(); + + $this->links = new PLL_Frontend_Links( $this ); + + // Setup the language chooser + $c = array( 'Content', 'Url', 'Url', 'Domain' ); + $class = 'PLL_Choose_Lang_' . $c[ $this->options['force_lang'] ]; + $this->choose_lang = new $class( $this ); + $this->choose_lang->init(); + + // Need to load nav menu class early to correctly define the locations in the customizer when the language is set from the content + $this->nav_menu = new PLL_Frontend_Nav_Menu( $this ); + } + + /** + * Setups filters and nav menus once the language has been defined + * + * @since 1.2 + * + * @return void + */ + public function pll_language_defined() { + // Filters + $this->filters_links = new PLL_Frontend_Filters_Links( $this ); + $this->filters = new PLL_Frontend_Filters( $this ); + $this->filters_search = new PLL_Frontend_Filters_Search( $this ); + $this->filters_widgets = new PLL_Frontend_Filters_Widgets( $this ); + + /* + * Redirects to canonical url before WordPress redirect_canonical + * but after Nextgen Gallery which hacks $_SERVER['REQUEST_URI'] !!! + * and restores it in 'template_redirect' with priority 1. + */ + $this->canonical = new PLL_Canonical( $this ); + add_action( 'template_redirect', array( $this->canonical, 'check_canonical_url' ), 4 ); + + // Auto translate for Ajax + if ( ( ! defined( 'PLL_AUTO_TRANSLATE' ) || PLL_AUTO_TRANSLATE ) && wp_doing_ajax() ) { + $this->auto_translate(); + } + } + + /** + * When querying multiple taxonomies, makes sure that the language is not the queried object. + * + * @since 1.8 + * + * @param WP_Query $query WP_Query object. + * @return void + */ + public function parse_tax_query( $query ) { + $pll_query = new PLL_Query( $query, $this->model ); + $queried_taxonomies = $pll_query->get_queried_taxonomies(); + + if ( ! empty( $queried_taxonomies ) && 'language' == reset( $queried_taxonomies ) ) { + $query->tax_query->queried_terms['language'] = array_shift( $query->tax_query->queried_terms ); + } + } + + /** + * Modifies some query vars to "hide" that the language is a taxonomy and avoid conflicts. + * + * @since 1.2 + * + * @param WP_Query $query WP_Query object. + * @return void + */ + public function parse_query( $query ) { + $qv = $query->query_vars; + $pll_query = new PLL_Query( $query, $this->model ); + $taxonomies = $pll_query->get_queried_taxonomies(); + + // Allow filtering recent posts and secondary queries by the current language + if ( ! empty( $this->curlang ) ) { + $pll_query->filter_query( $this->curlang ); + } + + // Modifies query vars when the language is queried + if ( ! empty( $qv['lang'] ) || ( ! empty( $taxonomies ) && array( 'language' ) == array_values( $taxonomies ) ) ) { + // Do we query a custom taxonomy? + $taxonomies = array_diff( $taxonomies, array( 'language', 'category', 'post_tag' ) ); + + // Remove pages query when the language is set unless we do a search + // Take care not to break the single page, attachment and taxonomies queries! + if ( empty( $qv['post_type'] ) && ! $query->is_search && ! $query->is_singular && empty( $taxonomies ) && ! $query->is_category && ! $query->is_tag ) { + $query->set( 'post_type', 'post' ); + } + + // Unset the is_archive flag for language pages to prevent loading the archive template + // Keep archive flag for comment feed otherwise the language filter does not work + if ( empty( $taxonomies ) && ! $query->is_comment_feed && ! $query->is_post_type_archive && ! $query->is_date && ! $query->is_author && ! $query->is_category && ! $query->is_tag ) { + $query->is_archive = false; + } + + // Unset the is_tax flag except if another custom tax is queried + if ( empty( $taxonomies ) && ( $query->is_category || $query->is_tag || $query->is_author || $query->is_post_type_archive || $query->is_date || $query->is_search || $query->is_feed ) ) { + $query->is_tax = false; + unset( $query->queried_object ); // FIXME useless? + } + } + } + + /** + * Auto translate posts and terms ids + * + * @since 1.2 + * + * @return void + */ + public function auto_translate() { + $this->auto_translate = new PLL_Frontend_Auto_Translate( $this ); + } + + /** + * Resets some variables when the blog is switched. + * Overrides the parent method. + * + * @since 1.5.1 + * + * @param int $new_blog_id New blog ID. + * @param int $prev_blog_id Previous blog ID. + * @return void + */ + public function switch_blog( $new_blog_id, $prev_blog_id ) { + if ( (int) $new_blog_id === (int) $prev_blog_id ) { + // Do nothing if same blog. + return; + } + + parent::switch_blog( $new_blog_id, $prev_blog_id ); + + // Need to check that some languages are defined when user is logged in, has several blogs, some without any languages. + if ( ! $this->is_active_on_current_site() || ! $this->model->has_languages() || ! did_action( 'pll_language_defined' ) ) { + return; + } + + static $restore_curlang; + + if ( empty( $restore_curlang ) ) { + $restore_curlang = $this->curlang->slug; // To always remember the current language through blogs. + } + + $lang = $this->model->get_language( $restore_curlang ); + $this->curlang = $lang ? $lang : $this->model->get_default_language(); + if ( empty( $this->curlang ) ) { + return; + } + + if ( isset( $this->static_pages ) ) { + $this->static_pages->init(); + } + + // Send the slug instead of the locale here to avoid conflicts with same locales. + $this->load_strings_translations( $this->curlang->slug ); + } + + /** + * Remove the customize admin bar on front-end when using a block theme. + * + * WordPress removes the Customizer menu if a block theme is activated and no other plugins interact with it. + * As Polylang interacts with the Customizer, we have to delete this menu ourselves in the case of a block theme, + * unless another plugin than Polylang interacts with the Customizer. + * + * @since 3.2 + * + * @return void + */ + public function remove_customize_admin_bar() { + if ( ! $this->should_customize_menu_be_removed() ) { + return; + } + + global $wp_admin_bar; + + remove_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' ); // To avoid the script launch. + $wp_admin_bar->remove_menu( 'customize' ); + } +} diff --git a/wp-content/plugins/polylang/include/api.php b/wp-content/plugins/polylang/include/api.php new file mode 100644 index 0000000000..927cd48d0e --- /dev/null +++ b/wp-content/plugins/polylang/include/api.php @@ -0,0 +1,574 @@ +links ) ) { + return empty( $args['raw'] ) ? '' : array(); + } + + $switcher = new PLL_Switcher(); + return $switcher->the_languages( PLL()->links, $args ); +} + +/** + * Returns the current language on frontend. + * Returns the language set in admin language filter on backend (false if set to all languages). + * + * @api + * @since 0.8.1 + * @since 3.4 Accepts composite values. + * + * @param string $field Optional, the language field to return (@see PLL_Language), defaults to `'slug'`. + * Pass `\OBJECT` constant to get the language object. A composite value can be used for language + * term property values, in the form of `{language_taxonomy_name}:{property_name}` (see + * {@see PLL_Language::get_tax_prop()} for the possible values). Ex: `term_language:term_taxonomy_id`. + * @return string|int|bool|string[]|PLL_Language The requested field or object for the current language, `false` if the field isn't set or if current language doesn't exist yet. + * + * @phpstan-return ( + * $field is \OBJECT ? PLL_Language : ( + * $field is 'slug' ? non-empty-string : string|int|bool|list + * ) + * )|false + */ +function pll_current_language( $field = 'slug' ) { + if ( empty( PLL()->curlang ) ) { + return false; + } + + if ( \OBJECT === $field ) { + return PLL()->curlang; + } + + return PLL()->curlang->get_prop( $field ); +} + +/** + * Returns the default language. + * + * @api + * @since 1.0 + * @since 3.4 Accepts composite values. + * + * @param string $field Optional, the language field to return (@see PLL_Language), defaults to `'slug'`. + * Pass `\OBJECT` constant to get the language object. A composite value can be used for language + * term property values, in the form of `{language_taxonomy_name}:{property_name}` (see + * {@see PLL_Language::get_tax_prop()} for the possible values). Ex: `term_language:term_taxonomy_id`. + * @return string|int|bool|string[]|PLL_Language The requested field or object for the default language, `false` if the field isn't set or if default language doesn't exist yet. + * + * @phpstan-return ( + * $field is \OBJECT ? PLL_Language : ( + * $field is 'slug' ? non-empty-string : string|int|bool|list + * ) + * )|false + */ +function pll_default_language( $field = 'slug' ) { + $lang = PLL()->model->get_default_language(); + + if ( empty( $lang ) ) { + return false; + } + + if ( \OBJECT === $field ) { + return $lang; + } + + return $lang->get_prop( $field ); +} + +/** + * Among the post and its translations, returns the ID of the post which is in the language represented by $lang. + * + * @api + * @since 0.5 + * @since 3.4 Returns 0 instead of false. + * @since 3.4 $lang accepts PLL_Language or string. + * + * @param int $post_id Post ID. + * @param PLL_Language|string $lang Optional language (object or slug), defaults to the current language. + * @return int|false The translation post ID if exists, otherwise the passed ID. False if the passed object has no language or if the language doesn't exist. + * + * @phpstan-return int<0, max>|false + */ +function pll_get_post( $post_id, $lang = '' ) { + $lang = $lang ? $lang : pll_current_language(); + + if ( empty( $lang ) ) { + return false; + } + + return PLL()->model->post->get( $post_id, $lang ); +} + +/** + * Among the term and its translations, returns the ID of the term which is in the language represented by $lang. + * + * @api + * @since 0.5 + * @since 3.4 Returns 0 instead of false. + * @since 3.4 $lang accepts PLL_Language or string. + * + * @param int $term_id Term ID. + * @param PLL_Language|string $lang Optional language (object or slug), defaults to the current language. + * @return int|false The translation term ID if exists, otherwise the passed ID. False if the passed object has no language or if the language doesn't exist. + * + * @phpstan-return int<0, max>|false + */ +function pll_get_term( $term_id, $lang = null ) { + $lang = $lang ? $lang : pll_current_language(); + + if ( empty( $lang ) ) { + return false; + } + + return PLL()->model->term->get( $term_id, $lang ); +} + +/** + * Returns the home url in a language. + * + * @api + * @since 0.8 + * + * @param string $lang Optional language code, defaults to the current language. + * @return string + */ +function pll_home_url( $lang = '' ) { + if ( empty( $lang ) ) { + $lang = pll_current_language(); + } + + if ( empty( $lang ) || empty( PLL()->links ) ) { + return home_url( '/' ); + } + + return PLL()->links->get_home_url( $lang ); +} + +/** + * Registers a string for translation in the "strings translation" panel. + * + * @api + * @since 0.6 + * + * @param string $name A unique name for the string. + * @param string $string The string to register. + * @param string $context Optional, the group in which the string is registered, defaults to 'polylang'. + * @param bool $multiline Optional, true if the string table should display a multiline textarea, + * false if should display a single line input, defaults to false. + * @return void + */ +function pll_register_string( $name, $string, $context = 'Polylang', $multiline = false ) { + if ( PLL() instanceof PLL_Admin_Base ) { + PLL_Admin_Strings::register_string( $name, $string, $context, $multiline ); + } +} + +/** + * Translates a string ( previously registered with pll_register_string ). + * + * @api + * @since 0.6 + * + * @param string $string The string to translate. + * @return string The string translated in the current language. + */ +function pll__( $string ) { + if ( ! is_scalar( $string ) || '' === $string ) { + return $string; + } + + return __( $string, 'pll_string' ); // PHPCS:ignore WordPress.WP.I18n +} + +/** + * Translates a string ( previously registered with pll_register_string ) and escapes it for safe use in HTML output. + * + * @api + * @since 2.1 + * + * @param string $string The string to translate. + * @return string The string translated in the current language. + */ +function pll_esc_html__( $string ) { + return esc_html( pll__( $string ) ); +} + +/** + * Translates a string ( previously registered with pll_register_string ) and escapes it for safe use in HTML attributes. + * + * @api + * @since 2.1 + * + * @param string $string The string to translate. + * @return string The string translated in the current language. + */ +function pll_esc_attr__( $string ) { + return esc_attr( pll__( $string ) ); +} + +/** + * Echoes a translated string ( previously registered with pll_register_string ) + * It is an equivalent of _e() and is not escaped. + * + * @api + * @since 0.6 + * + * @param string $string The string to translate. + * @return void + */ +function pll_e( $string ) { + echo pll__( $string ); // phpcs:ignore +} + +/** + * Echoes a translated string ( previously registered with pll_register_string ) and escapes it for safe use in HTML output. + * + * @api + * @since 2.1 + * + * @param string $string The string to translate. + * @return void + */ +function pll_esc_html_e( $string ) { + echo pll_esc_html__( $string ); // phpcs:ignore WordPress.Security.EscapeOutput +} + +/** + * Echoes a translated a string ( previously registered with pll_register_string ) and escapes it for safe use in HTML attributes. + * + * @api + * @since 2.1 + * + * @param string $string The string to translate. + * @return void + */ +function pll_esc_attr_e( $string ) { + echo pll_esc_attr__( $string ); // phpcs:ignore WordPress.Security.EscapeOutput +} + +/** + * Translates a string ( previously registered with pll_register_string ). + * + * @api + * @since 1.5.4 + * + * @param string $string The string to translate. + * @param string $lang Language code. + * @return string The string translated in the requested language. + */ +function pll_translate_string( $string, $lang ) { + if ( PLL() instanceof PLL_Frontend && pll_current_language() === $lang ) { + return pll__( $string ); + } + + if ( ! is_scalar( $string ) || '' === $string ) { + return $string; + } + + $lang = PLL()->model->get_language( $lang ); + + if ( empty( $lang ) ) { + return $string; + } + + static $cache; // Cache object to avoid loading the same translations object several times. + + if ( empty( $cache ) ) { + $cache = new PLL_Cache(); + } + + $mo = $cache->get( $lang->slug ); + + if ( ! $mo instanceof PLL_MO ) { + $mo = new PLL_MO(); + $mo->import_from_db( $lang ); + $cache->set( $lang->slug, $mo ); + } + + return $mo->translate( $string ); +} + +/** + * Returns true if Polylang manages languages and translations for this post type. + * + * @api + * @since 1.0.1 + * + * @param string $post_type Post type name. + * @return bool + */ +function pll_is_translated_post_type( $post_type ) { + return PLL()->model->is_translated_post_type( $post_type ); +} + +/** + * Returns true if Polylang manages languages and translations for this taxonomy. + * + * @api + * @since 1.0.1 + * + * @param string $tax Taxonomy name. + * @return bool + */ +function pll_is_translated_taxonomy( $tax ) { + return PLL()->model->is_translated_taxonomy( $tax ); +} + +/** + * Returns the list of available languages. + * + * @api + * @since 1.5 + * + * @param array $args { + * Optional array of arguments. + * + * @type bool $hide_empty Hides languages with no posts if set to true ( defaults to false ). + * @type string $fields Return only that field if set ( @see PLL_Language for a list of fields ), defaults to 'slug'. + * } + * @return string[] + */ +function pll_languages_list( $args = array() ) { + $args = wp_parse_args( $args, array( 'fields' => 'slug' ) ); + return PLL()->model->get_languages_list( $args ); +} + +/** + * Sets the post language. + * + * @api + * @since 1.5 + * @since 3.4 $lang accepts PLL_Language or string. + * @since 3.4 Returns a boolean. + * + * @param int $id Post ID. + * @param PLL_Language|string $lang Language (object or slug). + * @return bool True when successfully assigned. False otherwise (or if the given language is already assigned to + * the post). + */ +function pll_set_post_language( $id, $lang ) { + return PLL()->model->post->set_language( $id, $lang ); +} + +/** + * Sets the term language. + * + * @api + * @since 1.5 + * @since 3.4 $lang accepts PLL_Language or string. + * @since 3.4 Returns a boolean. + * + * @param int $id Term ID. + * @param PLL_Language|string $lang Language (object or slug). + * @return bool True when successfully assigned. False otherwise (or if the given language is already assigned to + * the term). + */ +function pll_set_term_language( $id, $lang ) { + return PLL()->model->term->set_language( $id, $lang ); +} + +/** + * Save posts translations. + * + * @api + * @since 1.5 + * @since 3.4 Returns an associative array of translations. + * + * @param int[] $arr An associative array of translations with language code as key and post ID as value. + * @return int[] An associative array with language codes as key and post IDs as values. + * + * @phpstan-return array + */ +function pll_save_post_translations( $arr ) { + $id = reset( $arr ); + if ( $id ) { + return PLL()->model->post->save_translations( $id, $arr ); + } + + return array(); +} + +/** + * Save terms translations + * + * @api + * @since 1.5 + * @since 3.4 Returns an associative array of translations. + * + * @param int[] $arr An associative array of translations with language code as key and term ID as value. + * @return int[] An associative array with language codes as key and term IDs as values. + * + * @phpstan-return array + */ +function pll_save_term_translations( $arr ) { + $id = reset( $arr ); + if ( $id ) { + return PLL()->model->term->save_translations( $id, $arr ); + } + + return array(); +} + +/** + * Returns the post language. + * + * @api + * @since 1.5.4 + * @since 3.4 Accepts composite values for `$field`. + * + * @param int $post_id Post ID. + * @param string $field Optional, the language field to return (@see PLL_Language), defaults to `'slug'`. + * Pass `\OBJECT` constant to get the language object. A composite value can be used for language + * term property values, in the form of `{language_taxonomy_name}:{property_name}` (see + * {@see PLL_Language::get_tax_prop()} for the possible values). Ex: `term_language:term_taxonomy_id`. + * @return string|int|bool|string[]|PLL_Language The requested field or object for the post language, `false` if no language is associated to that post. + * + * @phpstan-return ( + * $field is \OBJECT ? PLL_Language : ( + * $field is 'slug' ? non-empty-string : string|int|bool|list + * ) + * )|false + */ +function pll_get_post_language( $post_id, $field = 'slug' ) { + $lang = PLL()->model->post->get_language( $post_id ); + + if ( empty( $lang ) || \OBJECT === $field ) { + return $lang; + } + + return $lang->get_prop( $field ); +} + +/** + * Returns the term language. + * + * @api + * @since 1.5.4 + * @since 3.4 Accepts composite values for `$field`. + * + * @param int $term_id Term ID. + * @param string $field Optional, the language field to return (@see PLL_Language), defaults to `'slug'`. + * Pass `\OBJECT` constant to get the language object. A composite value can be used for language + * term property values, in the form of `{language_taxonomy_name}:{property_name}` (see + * {@see PLL_Language::get_tax_prop()} for the possible values). Ex: `term_language:term_taxonomy_id`. + * @return string|int|bool|string[]|PLL_Language The requested field or object for the post language, `false` if no language is associated to that term. + * + * @phpstan-return ( + * $field is \OBJECT ? PLL_Language : ( + * $field is 'slug' ? non-empty-string : string|int|bool|list + * ) + * )|false + */ +function pll_get_term_language( $term_id, $field = 'slug' ) { + $lang = PLL()->model->term->get_language( $term_id ); + + if ( empty( $lang ) || \OBJECT === $field ) { + return $lang; + } + + return $lang->get_prop( $field ); +} + +/** + * Returns an array of translations of a post. + * + * @api + * @since 1.8 + * + * @param int $post_id Post ID. + * @return int[] An associative array of translations with language code as key and translation post ID as value. + * + * @phpstan-return array + */ +function pll_get_post_translations( $post_id ) { + return PLL()->model->post->get_translations( $post_id ); +} + +/** + * Returns an array of translations of a term. + * + * @api + * @since 1.8 + * + * @param int $term_id Term ID. + * @return int[] An associative array of translations with language code as key and translation term ID as value. + * + * @phpstan-return array + */ +function pll_get_term_translations( $term_id ) { + return PLL()->model->term->get_translations( $term_id ); +} + +/** + * Counts posts in a language. + * + * @api + * @since 1.5 + * + * @param string $lang Language code. + * @param array $args { + * Optional array of arguments. + * + * @type string $post_type Post type. + * @type int $m YearMonth ( ex: 201307 ). + * @type int $year 4 digit year. + * @type int $monthnum Month number (from 1 to 12). + * @type int $day Day of the month (from 1 to 31). + * @type int $author Author id. + * @type string $author_name Author nicename. + * @type string $post_format Post format. + * @type string $post_status Post status. + * } + * @return int Posts count. + */ +function pll_count_posts( $lang, $args = array() ) { + $lang = PLL()->model->get_language( $lang ); + + if ( empty( $lang ) ) { + return 0; + } + + return PLL()->model->count_posts( $lang, $args ); +} + +/** + * Allows to access the Polylang instance. + * However, it is always preferable to use API functions + * as internal methods may be changed without prior notice. + * + * @since 1.8 + * + * @return PLL_Frontend|PLL_Admin|PLL_Settings|PLL_REST_Request + */ +function PLL() { // PHPCS:ignore WordPress.NamingConventions.ValidFunctionName + return $GLOBALS['polylang']; +} diff --git a/wp-content/plugins/polylang/include/base.php b/wp-content/plugins/polylang/include/base.php new file mode 100644 index 0000000000..305672dceb --- /dev/null +++ b/wp-content/plugins/polylang/include/base.php @@ -0,0 +1,219 @@ +links_model = &$links_model; + $this->model = &$links_model->model; + $this->options = &$this->model->options; + + $GLOBALS['l10n_unloaded']['pll_string'] = true; // Short-circuit _load_textdomain_just_in_time() for 'pll_string' domain in WP 4.6+ + + add_action( 'widgets_init', array( $this, 'widgets_init' ) ); + + // User defined strings translations + add_action( 'pll_language_defined', array( $this, 'load_strings_translations' ), 5 ); + add_action( 'change_locale', array( $this, 'load_strings_translations' ) ); // Since WP 4.7 + add_action( 'personal_options_update', array( $this, 'load_strings_translations' ), 1, 0 ); // Before WP, for confirmation request when changing the user email. + add_action( 'lostpassword_post', array( $this, 'load_strings_translations' ), 10, 0 ); // Password reset email. + // Switch_to_blog + add_action( 'switch_blog', array( $this, 'switch_blog' ), 10, 2 ); + } + + /** + * Instantiates classes reacting to CRUD operations on posts and terms, + * only when at least one language is defined. + * + * @since 2.6 + * + * @return void + */ + public function init() { + if ( $this->model->has_languages() ) { + $this->posts = new PLL_CRUD_Posts( $this ); + $this->terms = new PLL_CRUD_Terms( $this ); + + // WordPress options. + new PLL_Translate_Option( 'blogname', array(), array( 'context' => 'WordPress' ) ); + new PLL_Translate_Option( 'blogdescription', array(), array( 'context' => 'WordPress' ) ); + new PLL_Translate_Option( 'date_format', array(), array( 'context' => 'WordPress' ) ); + new PLL_Translate_Option( 'time_format', array(), array( 'context' => 'WordPress' ) ); + } + } + + /** + * Registers our widgets + * + * @since 0.1 + * + * @return void + */ + public function widgets_init() { + register_widget( 'PLL_Widget_Languages' ); + + // Overwrites the calendar widget to filter posts by language + if ( ! defined( 'PLL_WIDGET_CALENDAR' ) || PLL_WIDGET_CALENDAR ) { + unregister_widget( 'WP_Widget_Calendar' ); + register_widget( 'PLL_Widget_Calendar' ); + } + } + + /** + * Loads user defined strings translations + * + * @since 1.2 + * @since 2.1.3 $locale parameter added. + * + * @param string $locale Language locale or slug. Defaults to current locale. + * @return void + */ + public function load_strings_translations( $locale = '' ) { + if ( empty( $locale ) ) { + $locale = ( is_admin() && ! Polylang::is_ajax_on_front() ) ? get_user_locale() : get_locale(); + } + + $language = $this->model->get_language( $locale ); + + if ( ! empty( $language ) ) { + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + $GLOBALS['l10n']['pll_string'] = &$mo; + } else { + unset( $GLOBALS['l10n']['pll_string'] ); + } + } + + /** + * Resets some variables when the blog is switched. + * Applied only if Polylang is active on the new blog. + * + * @since 1.5.1 + * + * @param int $new_blog_id New blog ID. + * @param int $prev_blog_id Previous blog ID. + * @return void + */ + public function switch_blog( $new_blog_id, $prev_blog_id ) { + if ( (int) $new_blog_id === (int) $prev_blog_id ) { + // Do nothing if same blog. + return; + } + + $this->links_model->remove_filters(); + + if ( $this->is_active_on_current_site() ) { + $this->options = get_option( 'polylang' ); // Needed for menus. + $this->links_model = $this->model->get_links_model(); + } + } + + /** + * Checks if Polylang is active on the current blog (useful when the blog is switched). + * + * @since 3.5.2 + * + * @return bool + */ + protected function is_active_on_current_site(): bool { + return pll_is_plugin_active( POLYLANG_BASENAME ) && get_option( 'polylang' ); + } + + /** + * Check if the customize menu should be removed or not. + * + * @since 3.2 + * + * @return bool True if it should be removed, false otherwise. + */ + public function should_customize_menu_be_removed() { + // Exit if a block theme isn't activated. + if ( ! function_exists( 'wp_is_block_theme' ) || ! wp_is_block_theme() ) { + return false; + } + + return ! $this->is_customize_register_hooked(); + } + + /** + * Tells whether or not Polylang or third party callbacks are hooked to `customize_register`. + * + * @since 3.4.3 + * + * @global $wp_filter + * + * @return bool True if Polylang's callbacks are hooked, false otherwise. + */ + protected function is_customize_register_hooked() { + global $wp_filter; + + if ( empty( $wp_filter['customize_register'] ) || ! $wp_filter['customize_register'] instanceof WP_Hook ) { + return false; + } + + /* + * 'customize_register' is hooked by: + * @see PLL_Nav_Menu::create_nav_menu_locations() + * @see PLL_Frontend_Static_Pages::filter_customizer() + */ + $floor = 0; + if ( ! empty( $this->nav_menu ) && (bool) $wp_filter['customize_register']->has_filter( 'customize_register', array( $this->nav_menu, 'create_nav_menu_locations' ) ) ) { + ++$floor; + } + + if ( ! empty( $this->static_pages ) && (bool) $wp_filter['customize_register']->has_filter( 'customize_register', array( $this->static_pages, 'filter_customizer' ) ) ) { + ++$floor; + } + + $count = array_sum( array_map( 'count', $wp_filter['customize_register']->callbacks ) ); + + return $count > $floor; + } +} diff --git a/wp-content/plugins/polylang/include/cache.php b/wp-content/plugins/polylang/include/cache.php new file mode 100644 index 0000000000..69368f9211 --- /dev/null +++ b/wp-content/plugins/polylang/include/cache.php @@ -0,0 +1,121 @@ +> + */ + protected $cache = array(); + + /** + * Constructor. + * + * @since 1.7 + */ + public function __construct() { + $this->blog_id = get_current_blog_id(); + add_action( 'switch_blog', array( $this, 'switch_blog' ) ); + } + + /** + * Called when switching blog. + * + * @since 1.7 + * + * @param int $new_blog_id New blog ID. + * @return void + */ + public function switch_blog( $new_blog_id ) { + $this->blog_id = $new_blog_id; + } + + /** + * Adds a value in cache. + * + * @since 1.7 + * @since 3.6 Returns the cached value. + * + * @param string $key Cache key. + * @param mixed $data The value to add to the cache. + * @return mixed + * + * @phpstan-param non-empty-string $key + * @phpstan-param TCacheData $data + * @phpstan-return TCacheData + */ + public function set( $key, $data ) { + $this->cache[ $this->blog_id ][ $key ] = $data; + + return $data; + } + + /** + * Returns value from cache. + * + * @since 1.7 + * + * @param string $key Cache key. + * @return mixed + * + * @phpstan-param non-empty-string $key + * @phpstan-return TCacheData|false + */ + public function get( $key ) { + return isset( $this->cache[ $this->blog_id ][ $key ] ) ? $this->cache[ $this->blog_id ][ $key ] : false; + } + + /** + * Cleans the cache (for this blog only). + * + * @since 1.7 + * + * @param string $key Optional. Cache key. An empty string to clean the whole cache for the current blog. + * Default is an empty string. + * @return void + */ + public function clean( $key = '' ) { + if ( '' === $key ) { + unset( $this->cache[ $this->blog_id ] ); + } else { + unset( $this->cache[ $this->blog_id ][ $key ] ); + } + } + + /** + * Generates and returns a "unique" cache key, depending on `$data` and prefixed by `$prefix`. + * + * @since 3.6 + * + * @param string $prefix String to prefix the cache key. + * @param string|array|object $data Data. + * @return string + * + * @phpstan-param non-empty-string $prefix + * @phpstan-return non-empty-string + */ + public function get_unique_key( string $prefix, $data ): string { + /** @var scalar */ + $serialized = maybe_serialize( $data ); + return $prefix . md5( (string) $serialized ); + } +} diff --git a/wp-content/plugins/polylang/include/class-polylang.php b/wp-content/plugins/polylang/include/class-polylang.php new file mode 100644 index 0000000000..c1e7eb2bbb --- /dev/null +++ b/wp-content/plugins/polylang/include/class-polylang.php @@ -0,0 +1,298 @@ +is_deactivation() || ! $install->can_activate() ) { + return; + } + + // Plugin initialization + // Take no action before all plugins are loaded + add_action( 'plugins_loaded', array( $this, 'init' ), 1 ); + + // Override load text domain waiting for the language to be defined + // Here for plugins which load text domain as soon as loaded :( + if ( ! defined( 'PLL_OLT' ) || PLL_OLT ) { + PLL_OLT_Manager::instance(); + } + + /* + * Loads the compatibility with some plugins and themes. + * Loaded as soon as possible as we may need to act before other plugins are loaded. + */ + if ( ! defined( 'PLL_PLUGINS_COMPAT' ) || PLL_PLUGINS_COMPAT ) { + PLL_Integrations::instance(); + } + } + + /** + * Tells whether the current request is an ajax request on frontend or not + * + * @since 2.2 + * + * @return bool + */ + public static function is_ajax_on_front() { + // Special test for plupload which does not use jquery ajax and thus does not pass our ajax prefilter + // Special test for customize_save done in frontend but for which we want to load the admin + $in = isset( $_REQUEST['action'] ) && in_array( sanitize_key( $_REQUEST['action'] ), array( 'upload-attachment', 'customize_save' ) ); // phpcs:ignore WordPress.Security.NonceVerification + $is_ajax_on_front = wp_doing_ajax() && empty( $_REQUEST['pll_ajax_backend'] ) && ! $in; // phpcs:ignore WordPress.Security.NonceVerification + + /** + * Filters whether the current request is an ajax request on front. + * + * @since 2.3 + * + * @param bool $is_ajax_on_front Whether the current request is an ajax request on front. + */ + return apply_filters( 'pll_is_ajax_on_front', $is_ajax_on_front ); + } + + /** + * Is the current request a REST API request? + * Inspired by WP::parse_request() + * Needed because at this point, the constant REST_REQUEST is not defined yet + * + * @since 2.4.1 + * + * @return bool + */ + public static function is_rest_request() { + // Handle pretty permalinks. + $home_path = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); + $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) ); + + $req_uri = trim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' ); + $req_uri = (string) preg_replace( $home_path_regex, '', $req_uri ); + $req_uri = trim( $req_uri, '/' ); + $req_uri = str_replace( 'index.php', '', $req_uri ); + $req_uri = trim( $req_uri, '/' ); + + // And also test rest_route query string parameter is not empty for plain permalinks. + $query_string = array(); + wp_parse_str( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_QUERY ), $query_string ); + $rest_route = isset( $query_string['rest_route'] ) ? trim( $query_string['rest_route'], '/' ) : false; + + return 0 === strpos( $req_uri, rest_get_url_prefix() . '/' ) || ! empty( $rest_route ); + } + + /** + * Tells if we are in the wizard process. + * + * @since 2.7 + * + * @return bool + */ + public static function is_wizard() { + return isset( $_GET['page'] ) && ! empty( $_GET['page'] ) && 'mlang_wizard' === sanitize_key( $_GET['page'] ); // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Defines constants + * May be overridden by a plugin if set before plugins_loaded, 1 + * + * @since 1.6 + * + * @return void + */ + public static function define_constants() { + // Cookie name. no cookie will be used if set to false + if ( ! defined( 'PLL_COOKIE' ) ) { + define( 'PLL_COOKIE', 'pll_language' ); + } + + // Backward compatibility with Polylang < 2.3 + if ( ! defined( 'PLL_AJAX_ON_FRONT' ) ) { + define( 'PLL_AJAX_ON_FRONT', self::is_ajax_on_front() ); + } + + // Admin + if ( ! defined( 'PLL_ADMIN' ) ) { + define( 'PLL_ADMIN', wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) || ( is_admin() && ! PLL_AJAX_ON_FRONT ) ); + } + + // Settings page whatever the tab except for the wizard which needs to be an admin process. + if ( ! defined( 'PLL_SETTINGS' ) ) { + define( 'PLL_SETTINGS', is_admin() && ( ( isset( $_GET['page'] ) && 0 === strpos( sanitize_key( $_GET['page'] ), 'mlang' ) && ! self::is_wizard() ) || ! empty( $_REQUEST['pll_ajax_settings'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification + } + } + + /** + * Polylang initialization + * setups models and separate admin and frontend + * + * @since 1.2 + * + * @return void + */ + public function init() { + self::define_constants(); + $options = get_option( 'polylang' ); + + // Plugin upgrade + if ( $options && version_compare( $options['version'], POLYLANG_VERSION, '<' ) ) { + $upgrade = new PLL_Upgrade( $options ); + if ( ! $upgrade->upgrade() ) { // If the version is too old + return; + } + } + + // In some edge cases, it's possible that no options were found in the database. Load default options as we need some. + if ( ! $options ) { + $options = PLL_Install::get_default_options(); + } + + /** + * Filter the model class to use + * /!\ this filter is fired *before* the $polylang object is available + * + * @since 1.5 + * + * @param string $class either PLL_Model or PLL_Admin_Model + */ + $class = apply_filters( 'pll_model', PLL_SETTINGS || self::is_wizard() ? 'PLL_Admin_Model' : 'PLL_Model' ); + /** @var PLL_Model $model */ + $model = new $class( $options ); + + if ( ! $model->has_languages() ) { + /** + * Fires when no language has been defined yet + * Used to load overridden textdomains + * + * @since 1.2 + */ + do_action( 'pll_no_language_defined' ); + } + + $class = ''; + + if ( PLL_SETTINGS ) { + $class = 'PLL_Settings'; + } elseif ( PLL_ADMIN ) { + $class = 'PLL_Admin'; + } elseif ( self::is_rest_request() ) { + $class = 'PLL_REST_Request'; + } elseif ( $model->has_languages() ) { + $class = 'PLL_Frontend'; + } + + /** + * Filters the class to use to instantiate the $polylang object + * + * @since 2.6 + * + * @param string $class A class name. + */ + $class = apply_filters( 'pll_context', $class ); + + if ( ! empty( $class ) ) { + /** @phpstan-var class-string $class */ + $this->init_context( $class, $model ); + } + } + + /** + * Polylang initialization. + * Setups the Polylang Context, loads the modules and init Polylang. + * + * @since 3.6 + * + * @param string $class The class name. + * @param PLL_Model $model Instance of PLL_Model. + * @return PLL_Base + * + * @phpstan-param class-string $class + * @phpstan-return TPLLClass + */ + public function init_context( string $class, PLL_Model $model ): PLL_Base { + global $polylang; + + $links_model = $model->get_links_model(); + $polylang = new $class( $links_model ); + + /** + * Fires after Polylang's model init. + * This is the best place to register a custom table (see `PLL_Model`'s constructor). + * /!\ This hook is fired *before* the $polylang object is available. + * /!\ The languages are also not available yet. + * + * @since 3.4 + * + * @param PLL_Model $model Polylang model. + */ + do_action( 'pll_model_init', $model ); + + $model->maybe_create_language_terms(); + + /** + * Fires after the $polylang object is created and before the API is loaded + * + * @since 2.0 + * + * @param object $polylang + */ + do_action_ref_array( 'pll_pre_init', array( &$polylang ) ); + + // Loads the API + require_once POLYLANG_DIR . '/include/api.php'; + + // Loads the modules. + $load_scripts = glob( POLYLANG_DIR . '/modules/*/load.php', GLOB_NOSORT ); + if ( is_array( $load_scripts ) ) { + foreach ( $load_scripts as $load_script ) { + require_once $load_script; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable + } + } + + $polylang->init(); + + /** + * Fires after the $polylang object and the API is loaded + * + * @since 1.7 + * + * @param object $polylang + */ + do_action_ref_array( 'pll_init', array( &$polylang ) ); + + return $polylang; + } +} diff --git a/wp-content/plugins/polylang/include/cookie.php b/wp-content/plugins/polylang/include/cookie.php new file mode 100644 index 0000000000..0daac78866 --- /dev/null +++ b/wp-content/plugins/polylang/include/cookie.php @@ -0,0 +1,107 @@ + 0 !== $expiration ? time() + $expiration : 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, // Cookie domain must be set to false for localhost (default value for `COOKIE_DOMAIN`) thanks to Stephen Harris. + 'secure' => is_ssl(), + 'httponly' => false, + 'samesite' => 'Lax', + ); + + $args = wp_parse_args( $args, $defaults ); + + /** + * Filters the Polylang cookie arguments. + * /!\ This filter may be fired *before* the theme is loaded. + * + * @since 3.6 + * + * @param array $args { + * Optional. Array of arguments for setting the cookie. + * + * @type int $expires Cookie duration. + * If a cookie duration of 0 is specified, a session cookie will be set. + * If a negative cookie duration is specified, the cookie is removed. + * @type string $path Cookie path. + * @type string $domain Cookie domain. Must be set to false for localhost (default value for `COOKIE_DOMAIN`). + * @type bool $secure Should the cookie be sent only over https? + * @type bool $httponly Should the cookie be accessed only over http protocol?. + * @type string $samesite Either 'Strict', 'Lax' or 'None'. + * } + */ + return (array) apply_filters( 'pll_cookie_args', $args ); + } + + /** + * Sets the cookie. + * + * @since 2.9 + * + * @param string $lang Language cookie value. + * @param array $args { + * Optional. Array of arguments for setting the cookie. + * + * @type string $path Cookie path, defaults to COOKIEPATH. + * @type string $domain Cookie domain, defaults to COOKIE_DOMAIN + * @type bool $secure Should the cookie be sent only over https? + * @type bool $httponly Should the cookie accessed only over http protocol? Defaults to false. + * @type string $samesite Either 'Strict', 'Lax' or 'None', defaults to 'Lax'. + * } + * @return void + */ + public static function set( $lang, $args = array() ) { + $args = self::parse_args( $args ); + + if ( ! headers_sent() && PLL_COOKIE !== false && self::get() !== $lang ) { + if ( version_compare( PHP_VERSION, '7.3', '<' ) ) { + $args['path'] .= '; SameSite=' . $args['samesite']; // Hack to set SameSite value in PHP < 7.3. Doesn't work with newer versions. + setcookie( PLL_COOKIE, $lang, $args['expires'], $args['path'], $args['domain'], $args['secure'], $args['httponly'] ); + } else { + setcookie( PLL_COOKIE, $lang, $args ); + } + } + } + + /** + * Returns the language cookie value. + * + * @since 2.9 + * + * @return string + */ + public static function get() { + return isset( $_COOKIE[ PLL_COOKIE ] ) ? sanitize_key( $_COOKIE[ PLL_COOKIE ] ) : ''; + } +} diff --git a/wp-content/plugins/polylang/include/crud-posts.php b/wp-content/plugins/polylang/include/crud-posts.php new file mode 100644 index 0000000000..8ddc7577a1 --- /dev/null +++ b/wp-content/plugins/polylang/include/crud-posts.php @@ -0,0 +1,484 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + $this->pref_lang = &$polylang->pref_lang; + $this->curlang = &$polylang->curlang; + + add_action( 'save_post', array( $this, 'save_post' ), 10, 2 ); + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 4 ); + add_filter( 'wp_insert_post_parent', array( $this, 'wp_insert_post_parent' ), 10, 2 ); + add_action( 'before_delete_post', array( $this, 'delete_post' ) ); + add_action( 'post_updated', array( $this, 'force_tags_translation' ), 10, 3 ); + + // Specific for media + if ( $polylang->options['media_support'] ) { + add_action( 'add_attachment', array( $this, 'set_default_language' ) ); + add_action( 'delete_attachment', array( $this, 'delete_post' ) ); + add_filter( 'wp_delete_file', array( $this, 'wp_delete_file' ) ); + } + } + + /** + * Allows to set a language by default for posts if it has no language yet. + * + * @since 1.5 + * + * @param int $post_id Post ID. + * @return void + */ + public function set_default_language( $post_id ) { + if ( ! $this->model->post->get_language( $post_id ) ) { + if ( ! empty( $_GET['new_lang'] ) && $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // Defined only on admin. + $this->model->post->set_language( $post_id, $lang ); + } elseif ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // Testing $this->pref_lang makes this test pass only on admin. + $this->model->post->set_language( $post_id, $lang ); + } elseif ( ( $parent_id = wp_get_post_parent_id( $post_id ) ) && $parent_lang = $this->model->post->get_language( $parent_id ) ) { + $this->model->post->set_language( $post_id, $parent_lang ); + } elseif ( isset( $this->pref_lang ) ) { + // Always defined on admin, never defined on frontend. + $this->model->post->set_language( $post_id, $this->pref_lang ); + } elseif ( ! empty( $this->curlang ) ) { + // Only on frontend due to the previous test always true on admin. + $this->model->post->set_language( $post_id, $this->curlang ); + } else { + // In all other cases set to default language. + $this->model->post->set_language( $post_id, $this->options['default_lang'] ); + } + } + } + + /** + * Called when a post ( or page ) is saved, published or updated. + * + * @since 0.1 + * @since 2.3 Does not save the language and translations anymore, unless the post has no language yet. + * + * @param int $post_id Post id of the post being saved. + * @param WP_Post $post The post being saved. + * @return void + */ + public function save_post( $post_id, $post ) { + // Does nothing except on post types which are filterable. + if ( $this->model->is_translated_post_type( $post->post_type ) ) { + if ( $id = wp_is_post_revision( $post_id ) ) { + $post_id = $id; + } + + $lang = $this->model->post->get_language( $post_id ); + + if ( empty( $lang ) ) { + $this->set_default_language( $post_id ); + } + + /** + * Fires after the post language and translations are saved. + * + * @since 1.2 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param int[] $translations The list of translations post ids. + */ + do_action( 'pll_save_post', $post_id, $post, $this->model->post->get_translations( $post_id ) ); + } + } + + /** + * Makes sure that saved terms are in the right language. + * + * @since 2.3 + * + * @param int $object_id Object ID. + * @param int[]|string[] $terms An array of object term IDs or slugs. + * @param int[] $tt_ids An array of term taxonomy IDs. + * @param string $taxonomy Taxonomy slug. + * @return void + */ + public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy ) { + static $avoid_recursion; + + if ( $avoid_recursion || empty( $terms ) || ! is_array( $terms ) || ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return; + } + + $lang = $this->model->post->get_language( $object_id ); + + if ( empty( $lang ) ) { + return; + } + + // Use the term_taxonomy_ids to get all the requested terms in 1 query. + $new_terms = get_terms( + array( + 'taxonomy' => $taxonomy, + 'term_taxonomy_id' => array_map( 'intval', $tt_ids ), + 'lang' => '', + ) + ); + + if ( empty( $new_terms ) || ! is_array( $new_terms ) ) { + // Terms not found. + return; + } + + $new_term_ids_translated = $this->translate_terms( $new_terms, $taxonomy, $lang ); + + // Query the object's term. + $orig_terms = get_terms( + array( + 'taxonomy' => $taxonomy, + 'object_ids' => $object_id, + 'lang' => '', + ) + ); + + if ( is_array( $orig_terms ) ) { + $orig_term_ids = wp_list_pluck( $orig_terms, 'term_id' ); + $orig_term_ids_translated = $this->translate_terms( $orig_terms, $taxonomy, $lang ); + + // Terms that are not in the translated list. + $remove_term_ids = array_diff( $orig_term_ids, $orig_term_ids_translated ); + + if ( ! empty( $remove_term_ids ) ) { + wp_remove_object_terms( $object_id, $remove_term_ids, $taxonomy ); + } + } else { + $orig_term_ids = array(); + $orig_term_ids_translated = array(); + } + + // Terms to add. + $add_term_ids = array_unique( array_merge( $orig_term_ids_translated, $new_term_ids_translated ) ); + $add_term_ids = array_diff( $add_term_ids, $orig_term_ids ); + + if ( ! empty( $add_term_ids ) ) { + $avoid_recursion = true; + wp_set_object_terms( $object_id, $add_term_ids, $taxonomy, true ); // Append. + $avoid_recursion = false; + } + } + + /** + * Make sure that the post parent is in the correct language. + * + * @since 1.8 + * + * @param int $post_parent Post parent ID. + * @param int $post_id Post ID. + * @return int + */ + public function wp_insert_post_parent( $post_parent, $post_id ) { + $lang = $this->model->post->get_language( $post_id ); + $parent_post_type = $post_parent > 0 ? get_post_type( $post_parent ) : null; + // Dont break the hierarchy in case the post has no language + if ( ! empty( $lang ) && ! empty( $parent_post_type ) && $this->model->is_translated_post_type( $parent_post_type ) ) { + $post_parent = $this->model->post->get_translation( $post_parent, $lang ); + } + + return $post_parent; + } + + /** + * Called when a post, page or media is deleted + * Don't delete translations if this is a post revision thanks to AndyDeGroo who caught this bug + * http://wordpress.org/support/topic/plugin-polylang-quick-edit-still-breaks-translation-linking-of-pages-in-072 + * + * @since 0.1 + * + * @param int $post_id Post ID. + * @return void + */ + public function delete_post( $post_id ) { + if ( ! wp_is_post_revision( $post_id ) ) { + $this->model->post->delete_translation( $post_id ); + } + } + + /** + * Prevents WP deleting files when there are still media using them. + * + * @since 0.9 + * + * @param string $file Path to the file to delete. + * @return string Empty or unmodified path. + */ + public function wp_delete_file( $file ) { + global $wpdb; + + $uploadpath = wp_upload_dir(); + + // Get the main attached file. + $attached_file = substr_replace( $file, '', 0, strlen( trailingslashit( $uploadpath['basedir'] ) ) ); + $attached_file = preg_replace( '#-\d+x\d+\.([a-z]+)$#', '.$1', $attached_file ); + + $ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT post_id FROM $wpdb->postmeta + WHERE meta_key = '_wp_attached_file' AND meta_value = %s", + $attached_file + ) + ); + + if ( ! empty( $ids ) ) { + return ''; // Prevent deleting the file. + } + + return $file; + } + + /** + * Creates a media translation + * + * @since 1.8 + * + * @param int $post_id Original attachment id. + * @param string|object $lang New translation language. + * @return int Attachment id of the translated media. + */ + public function create_media_translation( $post_id, $lang ) { + if ( empty( $post_id ) ) { + return 0; + } + + $post = get_post( $post_id, ARRAY_A ); + + if ( empty( $post ) ) { + return 0; + } + + $lang = $this->model->get_language( $lang ); // Make sure we get a valid language slug. + + if ( empty( $lang ) ) { + return 0; + } + + // Create a new attachment ( translate attachment parent if exists ). + add_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Avoid a conflict with automatic duplicate at upload. + unset( $post['ID'] ); // Will force the creation. + if ( ! empty( $post['post_parent'] ) ) { + $post['post_parent'] = (int) $this->model->post->get_translation( $post['post_parent'], $lang->slug ); + } + $post['tax_input'] = array( 'language' => array( $lang->slug ) ); // Assigns the language. + $tr_id = wp_insert_attachment( wp_slash( $post ) ); + remove_filter( 'pll_enable_duplicate_media', '__return_false', 99 ); // Restore automatic duplicate at upload. + + // Copy metadata. + $data = wp_get_attachment_metadata( $post_id, true ); // Unfiltered. + if ( is_array( $data ) ) { + wp_update_attachment_metadata( $tr_id, wp_slash( $data ) ); // Directly uses update_post_meta, so expects slashed. + } + + // Copy attached file. + if ( $file = get_attached_file( $post_id, true ) ) { // Unfiltered. + update_attached_file( $tr_id, wp_slash( $file ) ); // Directly uses update_post_meta, so expects slashed. + } + + // Copy alternative text. Direct use of the meta as there is no filtered wrapper to manipulate it. + if ( $text = get_post_meta( $post_id, '_wp_attachment_image_alt', true ) ) { + add_post_meta( $tr_id, '_wp_attachment_image_alt', wp_slash( $text ) ); + } + + $this->model->post->set_language( $tr_id, $lang ); + + $translations = $this->model->post->get_translations( $post_id ); + $translations[ $lang->slug ] = $tr_id; + $this->model->post->save_translations( $tr_id, $translations ); + + /** + * Fires after a media translation is created + * + * @since 1.6.4 + * + * @param int $post_id Post id of the source media. + * @param int $tr_id Post id of the new media translation. + * @param string $slug Language code of the new translation. + */ + do_action( 'pll_translate_media', $post_id, $tr_id, $lang->slug ); + return $tr_id; + } + + /** + * Ensure that tags are in the correct language when a post is updated, due to `tags_input` parameter being removed in `wp_update_post()`. + * + * @since 3.4.5 + * + * @param int $post_id Post ID, unused. + * @param WP_Post $post_after Post object following the update. + * @param WP_Post $post_before Post object before the update. + * @return void + */ + public function force_tags_translation( $post_id, $post_after, $post_before ) { + if ( ! is_object_in_taxonomy( $post_before->post_type, 'post_tag' ) ) { + return; + } + + $terms = get_the_terms( $post_before, 'post_tag' ); + + if ( empty( $terms ) || ! is_array( $terms ) ) { + return; + } + + $term_ids = wp_list_pluck( $terms, 'term_id' ); + + // Let's ensure that `PLL_CRUD_Posts::set_object_terms()` will do its job. + wp_set_post_terms( $post_id, $term_ids, 'post_tag' ); + } + + /** + * Makes sure that all terms in the given list are in the given language. + * If not the case, the terms are translated or created (for a hierarchical taxonomy, terms are created recursively). + * + * @since 3.5 + * + * @param WP_Term[] $terms List of terms to translate. + * @param string $taxonomy The terms' taxonomy. + * @param PLL_Language $language The language to translate the terms into. + * @return int[] List of `term_id`s. + * + * @phpstan-return array + */ + private function translate_terms( array $terms, string $taxonomy, PLL_Language $language ): array { + $term_ids_translated = array(); + + foreach ( $terms as $term ) { + $term_ids_translated[] = $this->translate_term( $term, $taxonomy, $language ); + } + + return array_filter( $term_ids_translated ); + } + + /** + * Translates the given term into the given language. + * If the translation doesn't exist, it is created (for a hierarchical taxonomy, terms are created recursively). + * + * @since 3.5 + * + * @param WP_Term $term The term to translate. + * @param string $taxonomy The term's taxonomy. + * @param PLL_Language $language The language to translate the term into. + * @return int A `term_id` on success, `0` on failure. + * + * @phpstan-return int<0, max> + */ + private function translate_term( WP_Term $term, string $taxonomy, PLL_Language $language ): int { + // Check if the term is in the correct language or if a translation exists. + $tr_term_id = $this->model->term->get( $term->term_id, $language ); + + if ( ! empty( $tr_term_id ) ) { + // Already in the correct language. + return $tr_term_id; + } + + // Or choose the correct language for tags (initially defined by name). + $tr_term_id = $this->model->term_exists( $term->name, $taxonomy, $term->parent, $language ); + + if ( ! empty( $tr_term_id ) ) { + return $tr_term_id; + } + + // Or create the term in the correct language. + $tr_parent_term_id = 0; + + if ( $term->parent > 0 && is_taxonomy_hierarchical( $taxonomy ) ) { + $parent = get_term( $term->parent, $taxonomy ); + + if ( $parent instanceof WP_Term ) { + // Translate the parent recursively. + $tr_parent_term_id = $this->translate_term( $parent, $taxonomy, $language ); + } + } + + $lang_callback = function ( $lang, $tax, $slug ) use ( $language, $term, $taxonomy ) { + if ( ! $lang instanceof PLL_Language && $tax === $taxonomy && $slug === $term->slug ) { + return $language; + } + return $lang; + }; + $parent_callback = function ( $parent_id, $tax, $slug ) use ( $tr_parent_term_id, $term, $taxonomy ) { + if ( empty( $parent_id ) && $tax === $taxonomy && $slug === $term->slug ) { + return $tr_parent_term_id; + } + return $parent_id; + }; + add_filter( 'pll_inserted_term_language', $lang_callback, 10, 3 ); + add_filter( 'pll_inserted_term_parent', $parent_callback, 10, 3 ); + $new_term_info = wp_insert_term( + $term->name, + $taxonomy, + array( + 'parent' => $tr_parent_term_id, + 'slug' => $term->slug, // Useless but prevents the use of `sanitize_title()` and for consistency with `$lang_callback`. + ) + ); + remove_filter( 'pll_inserted_term_language', $lang_callback ); + remove_filter( 'pll_inserted_term_parent', $parent_callback ); + + if ( is_wp_error( $new_term_info ) ) { + // Term creation failed. + return 0; + } + + $tr_term_id = max( 0, (int) $new_term_info['term_id'] ); + + if ( empty( $tr_term_id ) ) { + return 0; + } + + $this->model->term->set_language( $tr_term_id, $language ); + + $trs = $this->model->term->get_translations( $term->term_id ); + + $trs[ $language->slug ] = $tr_term_id; + + $this->model->term->save_translations( $term->term_id, $trs ); + + return $tr_term_id; + } +} diff --git a/wp-content/plugins/polylang/include/crud-terms.php b/wp-content/plugins/polylang/include/crud-terms.php new file mode 100644 index 0000000000..85680b4a93 --- /dev/null +++ b/wp-content/plugins/polylang/include/crud-terms.php @@ -0,0 +1,346 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + $this->curlang = &$polylang->curlang; + $this->filter_lang = &$polylang->filter_lang; + $this->pref_lang = &$polylang->pref_lang; + + // Saving terms + add_action( 'create_term', array( $this, 'save_term' ), 999, 3 ); + add_action( 'edit_term', array( $this, 'save_term' ), 999, 3 ); // After PLL_Admin_Filters_Term + add_filter( 'pre_term_name', array( $this, 'set_pre_term_name' ) ); + add_filter( 'pre_term_slug', array( $this, 'set_pre_term_slug' ), 10, 2 ); + + // Adds cache domain when querying terms + add_filter( 'get_terms_args', array( $this, 'get_terms_args' ), 10, 2 ); + + // Filters terms by language + add_filter( 'terms_clauses', array( $this, 'terms_clauses' ), 10, 3 ); + add_action( 'pre_get_posts', array( $this, 'set_tax_query_lang' ), 999 ); + add_action( 'posts_selection', array( $this, 'unset_tax_query_lang' ), 0 ); + + // Deleting terms + add_action( 'pre_delete_term', array( $this, 'delete_term' ), 10, 2 ); + } + + /** + * Allows to set a language by default for terms if it has no language yet. + * + * @since 1.5.4 + * + * @param int $term_id Term ID. + * @param string $taxonomy Taxonomy name. + * @return void + */ + protected function set_default_language( $term_id, $taxonomy ) { + if ( ! $this->model->term->get_language( $term_id ) ) { + if ( ! isset( $this->pref_lang ) && ! empty( $_REQUEST['lang'] ) && $lang = $this->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // Testing $this->pref_lang makes this test pass only on frontend. + $this->model->term->set_language( $term_id, $lang ); + } elseif ( ( $term = get_term( $term_id, $taxonomy ) ) && ! empty( $term->parent ) && $parent_lang = $this->model->term->get_language( $term->parent ) ) { + // Sets language from term parent if exists thanks to Scott Kingsley Clark + $this->model->term->set_language( $term_id, $parent_lang ); + } elseif ( isset( $this->pref_lang ) ) { + // Always defined on admin, never defined on frontend + $this->model->term->set_language( $term_id, $this->pref_lang ); + } elseif ( ! empty( $this->curlang ) ) { + // Only on frontend due to the previous test always true on admin + $this->model->term->set_language( $term_id, $this->curlang ); + } else { + // In all other cases set to default language. + $this->model->term->set_language( $term_id, $this->options['default_lang'] ); + } + } + } + + /** + * Called when a category or post tag is created or edited. + * Does nothing except on taxonomies which are filterable. + * + * @since 0.1 + * + * @param int $term_id Term id of the term being saved. + * @param int $tt_id Term taxonomy id. + * @param string $taxonomy Taxonomy name. + * @return void + */ + public function save_term( $term_id, $tt_id, $taxonomy ) { + if ( $this->model->is_translated_taxonomy( $taxonomy ) ) { + + $lang = $this->model->term->get_language( $term_id ); + + if ( empty( $lang ) ) { + $this->set_default_language( $term_id, $taxonomy ); + } + + /** + * Fires after the term language and translations are saved. + * + * @since 1.2 + * + * @param int $term_id Term id. + * @param string $taxonomy Taxonomy name. + * @param int[] $translations The list of translations term ids. + */ + do_action( 'pll_save_term', $term_id, $taxonomy, $this->model->term->get_translations( $term_id ) ); + } + } + + /** + * Get the language(s) to filter WP_Term_Query. + * + * @since 1.7.6 + * + * @param string[] $taxonomies Queried taxonomies. + * @param array $args WP_Term_Query arguments. + * @return PLL_Language|string|false The language(s) to use in the filter, false otherwise. + */ + protected function get_queried_language( $taxonomies, $args ) { + global $pagenow; + + // Does nothing except on taxonomies which are filterable + // Since WP 4.7, make sure not to filter wp_get_object_terms() + if ( ! $this->model->is_translated_taxonomy( $taxonomies ) || ! empty( $args['object_ids'] ) ) { + return false; + } + + // If get_terms is queried with a 'lang' parameter + if ( isset( $args['lang'] ) ) { + return $args['lang']; + } + + // On tags page, everything should be filtered according to the admin language filter except the parent dropdown + if ( 'edit-tags.php' === $pagenow && empty( $args['class'] ) ) { + return $this->filter_lang; + } + + return $this->curlang; + } + + /** + * Adds language dependent cache domain when querying terms. + * Useful as the 'lang' parameter is not included in cache key by WordPress. + * + * @since 1.3 + * + * @param array $args WP_Term_Query arguments. + * @param string[] $taxonomies Queried taxonomies. + * @return array Modified arguments. + */ + public function get_terms_args( $args, $taxonomies ) { + // Don't break _get_term_hierarchy(). + if ( 'all' === $args['get'] && 'id' === $args['orderby'] && 'id=>parent' === $args['fields'] ) { + $args['lang'] = ''; + } + + if ( isset( $this->tax_query_lang ) ) { + $args['lang'] = empty( $this->tax_query_lang ) && ! empty( $this->curlang ) && ! empty( $args['slug'] ) ? $this->curlang->slug : $this->tax_query_lang; + } + + if ( $lang = $this->get_queried_language( $taxonomies, $args ) ) { + $lang = is_string( $lang ) && strpos( $lang, ',' ) ? explode( ',', $lang ) : $lang; + $key = '_' . ( is_array( $lang ) ? implode( ',', $lang ) : $this->model->get_language( $lang )->slug ); + $args['cache_domain'] = empty( $args['cache_domain'] ) ? 'pll' . $key : $args['cache_domain'] . $key; + } + return $args; + } + + /** + * Filters categories and post tags by language(s) when needed on admin side + * + * @since 0.2 + * + * @param string[] $clauses List of sql clauses. + * @param string[] $taxonomies List of taxonomies. + * @param array $args WP_Term_Query arguments. + * @return string[] Modified sql clauses. + */ + public function terms_clauses( $clauses, $taxonomies, $args ) { + $lang = $this->get_queried_language( $taxonomies, $args ); + return $this->model->terms_clauses( $clauses, $lang ); + } + + /** + * Sets the WP_Term_Query language when doing a WP_Query. + * Needed since WP 4.9. + * + * @since 2.3.2 + * + * @param WP_Query $query WP_Query object. + * @return void + */ + public function set_tax_query_lang( $query ) { + $this->tax_query_lang = isset( $query->query_vars['lang'] ) ? $query->query_vars['lang'] : ''; + } + + /** + * Removes the WP_Term_Query language filter for WP_Query. + * Needed since WP 4.9. + * + * @since 2.3.2 + * + * @return void + */ + public function unset_tax_query_lang() { + unset( $this->tax_query_lang ); + } + + /** + * Called when a category or post tag is deleted + * Deletes language and translations + * + * @since 0.1 + * + * @param int $term_id Id of the term to delete. + * @param string $taxonomy Name of the taxonomy. + * @return void + */ + public function delete_term( $term_id, $taxonomy ) { + if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return; + } + + // Delete translation and relationships only if the term is translatable. + $this->model->term->delete_translation( $term_id ); + $this->model->term->delete_language( $term_id ); + } + + /** + * Stores the term name for use in pre_term_slug + * + * @since 0.9.5 + * + * @param string $name term name + * @return string unmodified term name + */ + public function set_pre_term_name( $name ) { + return $this->pre_term_name = $name; + } + + /** + * Appends language slug to the term slug if needed. + * + * @since 3.3 + * + * @param string $slug Term slug. + * @param string $taxonomy Term taxonomy. + * @return string Slug with a language suffix if found. + */ + public function set_pre_term_slug( $slug, $taxonomy ) { + if ( ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return $slug; + } + + if ( ! $slug ) { + $slug = sanitize_title( $this->pre_term_name ); + } + + if ( ! term_exists( $slug, $taxonomy ) ) { + return $slug; + } + + /** + * Filters the subsequently inserted term language. + * + * @since 3.3 + * + * @param PLL_Language|null $lang Found language object, null otherwise. + * @param string $taxonomy Term taonomy. + * @param string $slug Term slug + */ + $lang = apply_filters( 'pll_inserted_term_language', null, $taxonomy, $slug ); + + if ( ! $lang instanceof PLL_Language ) { + return $slug; + } + + $parent = 0; + if ( is_taxonomy_hierarchical( $taxonomy ) ) { + /** + * Filters the subsequently inserted term parent. + * + * @since 3.3 + * + * @param int $parent Parent term ID, 0 if none. + * @param string $taxonomy Term taxonomy. + * @param string $slug Term slug + */ + $parent = apply_filters( 'pll_inserted_term_parent', 0, $taxonomy, $slug ); + } + + $term_id = (int) $this->model->term_exists_by_slug( $slug, $lang, $taxonomy, $parent ); + + // If no term exist in the given language with that slug, it can be created. + if ( ! $term_id ) { + $slug .= '-' . $lang->slug; + } + + return $slug; + } +} diff --git a/wp-content/plugins/polylang/include/db-tools.php b/wp-content/plugins/polylang/include/db-tools.php new file mode 100644 index 0000000000..be1fc5d2b8 --- /dev/null +++ b/wp-content/plugins/polylang/include/db-tools.php @@ -0,0 +1,48 @@ +prepare( '%s', $value ); + } + + return (int) $value; + } +} diff --git a/wp-content/plugins/polylang/include/filter-rest-routes.php b/wp-content/plugins/polylang/include/filter-rest-routes.php new file mode 100644 index 0000000000..ca598a79e9 --- /dev/null +++ b/wp-content/plugins/polylang/include/filter-rest-routes.php @@ -0,0 +1,178 @@ + + */ + private $filtered_entities = array(); + + /** + * Other REST routes filterable by language. + * + * @var string[] + * @phpstan-var array + */ + private $filtered_routes = array(); + + /** + * @var PLL_Model + */ + private $model; + + /** + * Constructor. + * + * @since 3.5 + * + * @param PLL_Model $model Shared instance of the current PLL_Model. + */ + public function __construct( PLL_Model $model ) { + $this->model = $model; + + // Adds search REST endpoint. + $this->filtered_routes['search'] = 'wp/v2/search'; + } + + /** + * Adds query parameters to preload paths. + * + * @since 3.5 + * + * @param (string|string[])[] $preload_paths Array of paths to preload. + * @param array $args Array of query strings to add paired by key/value. + * @return (string|string[])[] + */ + public function add_query_parameters( array $preload_paths, array $args ): array { + foreach ( $preload_paths as $k => $path ) { + if ( empty( $path ) ) { + continue; + } + + $query_params = array(); + // If the method request is OPTIONS, $path is an array and the first element is the path + if ( is_array( $path ) ) { + $temp_path = $path[0]; + } else { + $temp_path = $path; + } + + $path_parts = wp_parse_url( $temp_path ); + + if ( ! isset( $path_parts['path'] ) || ! $this->is_filtered( $path_parts['path'] ) ) { + continue; + } + + if ( ! empty( $path_parts['query'] ) ) { + parse_str( $path_parts['query'], $query_params ); + } + + // Add params in query params + foreach ( $args as $key => $value ) { + $query_params[ $key ] = $value; + } + + // Sort query params to put it in the same order as the preloading middleware does + ksort( $query_params ); + + // Replace the key by the correct path with query params reordered + $sorted_path = add_query_arg( urlencode_deep( $query_params ), $path_parts['path'] ); + + if ( is_array( $path ) ) { + $preload_paths[ $k ][0] = $sorted_path; + } else { + $preload_paths[ $k ] = $sorted_path; + } + } + + return $preload_paths; + } + + /** + * Adds inline script to declare filtered REST route on client side. + * + * @since 3.5 + * + * @param string $script_handle Name of the script to add the inline script to. + * @return void + */ + public function add_inline_script( string $script_handle ) { + $script_var = 'let pllFilteredRoutes = ' . (string) wp_json_encode( $this->get() ); + + wp_add_inline_script( $script_handle, $script_var, 'before' ); + } + + /** + * Returns filtered REST routes by entity type (e.g. post type or taxonomy). + * + * @since 3.5 + * + * @return string[] REST routes. + * @phpstan-return array + */ + private function get(): array { + if ( ! empty( $this->filtered_entities ) ) { + return array_merge( $this->filtered_entities, $this->filtered_routes ); + } + + $translatable_post_types = $this->model->get_translated_post_types(); + $translatable_taxonomies = $this->model->get_translated_taxonomies(); + + $post_types = get_post_types( array( 'show_in_rest' => true ), 'objects' ); + $taxonomies = get_taxonomies( array( 'show_in_rest' => true ), 'objects' ); + + $this->extract_filtered_rest_entities( + array_merge( $post_types, $taxonomies ), + array_merge( $translatable_post_types, $translatable_taxonomies ) + ); + + return array_merge( $this->filtered_entities, $this->filtered_routes ); + } + + /** + * Tells if a given route is fileterable by language. + * + * @since 3.5 + * + * @param string $rest_route Route to test. + * @return bool Whether the route is filterable or not. + */ + private function is_filtered( string $rest_route ): bool { + $rest_route = trim( $rest_route ); + + return ! preg_match( '/\d+$/', $rest_route ) && in_array( trim( $rest_route, '/' ), $this->get(), true ); + } + + /** + * Extracts filterable REST route from an array of entity objects + * from a list of translatable entities (e.g. post types or taxonomies). + * + * @since 3.5 + * + * @param object[] $rest_entities Array of post type or taxonomy objects. + * @param string[] $translatable_entities Array of translatable entity names. + * @return void + * @phpstan-param array $rest_entities + */ + private function extract_filtered_rest_entities( array $rest_entities, array $translatable_entities ) { + $this->filtered_entities = array(); + foreach ( $rest_entities as $rest_entity ) { + if ( in_array( $rest_entity->name, $translatable_entities, true ) ) { + $rest_base = empty( $rest_entity->rest_base ) ? $rest_entity->name : $rest_entity->rest_base; + $rest_namespace = empty( $rest_entity->rest_namespace ) ? 'wp/v2' : $rest_entity->rest_namespace; + + $this->filtered_entities[ $rest_entity->name ] = "{$rest_namespace}/{$rest_base}"; + } + } + } +} diff --git a/wp-content/plugins/polylang/include/filters-links.php b/wp-content/plugins/polylang/include/filters-links.php new file mode 100644 index 0000000000..b13d9f8cf1 --- /dev/null +++ b/wp-content/plugins/polylang/include/filters-links.php @@ -0,0 +1,202 @@ +links = &$polylang->links; + $this->links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + $this->curlang = &$polylang->curlang; + + // Low priority on links filters to come after any other modifications. + if ( $this->options['force_lang'] ) { + add_filter( 'post_link', array( $this, 'post_type_link' ), 20, 2 ); + add_filter( '_get_page_link', array( $this, '_get_page_link' ), 20, 2 ); + } + + add_filter( 'post_type_link', array( $this, 'post_type_link' ), 20, 2 ); + add_filter( 'term_link', array( $this, 'term_link' ), 20, 3 ); + + if ( $this->options['force_lang'] > 0 ) { + add_filter( 'attachment_link', array( $this, 'attachment_link' ), 20, 2 ); + } + + // Keeps the preview post link on default domain when using multiple domains and SSO is not available. + if ( 3 === $this->options['force_lang'] && ! class_exists( 'PLL_Xdata_Domain' ) ) { + add_filter( 'preview_post_link', array( $this, 'preview_post_link' ), 20 ); + } + + // Rewrites post types archives links to filter them by language. + add_filter( 'post_type_archive_link', array( $this, 'post_type_archive_link' ), 20, 2 ); + } + + /** + * Modifies page links + * + * @since 1.7 + * + * @param string $link post link + * @param int $post_id post ID + * @return string modified post link + */ + public function _get_page_link( $link, $post_id ) { + // /!\ WP does not use pretty permalinks for preview + return false !== strpos( $link, 'preview=true' ) && false !== strpos( $link, 'page_id=' ) ? $link : $this->links_model->switch_language_in_link( $link, $this->model->post->get_language( $post_id ) ); + } + + /** + * Modifies attachment links + * + * @since 1.6.2 + * + * @param string $link attachment link + * @param int $post_id attachment link + * @return string modified attachment link + */ + public function attachment_link( $link, $post_id ) { + return wp_get_post_parent_id( $post_id ) ? $link : $this->links_model->switch_language_in_link( $link, $this->model->post->get_language( $post_id ) ); + } + + /** + * Modifies custom posts links. + * + * @since 1.6 + * + * @param string $link Post link. + * @param WP_Post $post Post object. + * @return string Modified post link. + */ + public function post_type_link( $link, $post ) { + // /!\ WP does not use pretty permalinks for preview + if ( ( false === strpos( $link, 'preview=true' ) || false === strpos( $link, 'p=' ) ) && $this->model->is_translated_post_type( $post->post_type ) ) { + $lang = $this->model->post->get_language( $post->ID ); + $link = $this->options['force_lang'] ? $this->links_model->switch_language_in_link( $link, $lang ) : $link; + + /** + * Filters a post or custom post type link. + * + * @since 1.6 + * + * @param string $link The post link. + * @param PLL_Language $lang The current language. + * @param WP_Post $post The post object. + */ + $link = apply_filters( 'pll_post_type_link', $link, $lang, $post ); + } + + return $link; + } + + /** + * Modifies term links. + * + * @since 0.7 + * + * @param string $link Term link. + * @param WP_Term $term Term object. + * @param string $tax Taxonomy name; + * @return string Modified term link. + */ + public function term_link( $link, $term, $tax ) { + if ( $this->model->is_translated_taxonomy( $tax ) ) { + $lang = $this->model->term->get_language( $term->term_id ); + $link = $this->options['force_lang'] ? $this->links_model->switch_language_in_link( $link, $lang ) : $link; + + /** + * Filter a term link + * + * @since 1.6 + * + * @param string $link The term link. + * @param PLL_Language $lang The current language. + * @param WP_Term $term The term object. + */ + return apply_filters( 'pll_term_link', $link, $lang, $term ); + } + + // In case someone calls get_term_link for the 'language' taxonomy. + if ( 'language' === $tax ) { + $lang = $this->model->get_language( $term->term_id ); + if ( $lang ) { + return $this->links_model->home_url( $lang ); + } + } + + return $link; + } + + /** + * Keeps the preview post link on default domain when using multiple domains. + * + * @since 1.6.1 + * + * @param string $url URL used for the post preview. + * @return string The modified url. + */ + public function preview_post_link( $url ) { + return $this->links_model->remove_language_from_link( $url ); + } + + /** + * Modifies the post type archive links to add the language parameter + * only if the post type is translated. + * + * The filter was originally only on frontend but is needed on admin too for + * compatibility with the archive link of the ACF link field since ACF 5.4.0. + * + * @since 1.7.6 + * + * @param string $link The post type archive permalink. + * @param string $post_type Post type name. + * @return string + */ + public function post_type_archive_link( $link, $post_type ) { + return $this->model->is_translated_post_type( $post_type ) && 'post' !== $post_type ? $this->links_model->switch_language_in_link( $link, $this->curlang ) : $link; + } +} diff --git a/wp-content/plugins/polylang/include/filters-sanitization.php b/wp-content/plugins/polylang/include/filters-sanitization.php new file mode 100644 index 0000000000..1bdd2a1233 --- /dev/null +++ b/wp-content/plugins/polylang/include/filters-sanitization.php @@ -0,0 +1,106 @@ +locale = $locale; + + // We need specific filters for some languages like German and Danish + $specific_locales = array( 'da_DK', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'ca', 'sr_RS', 'bs_BA' ); + if ( in_array( $locale, $specific_locales ) ) { + add_filter( 'sanitize_title', array( $this, 'sanitize_title' ), 10, 3 ); + add_filter( 'sanitize_user', array( $this, 'sanitize_user' ), 10, 3 ); + } + } + + /** + * Retrieve the locale code of the language. + * + * @since 2.0 + * + * @return string + */ + public function get_locale() { + return $this->locale; + } + + /** + * Maybe fix the result of sanitize_title() in case the languages include German or Danish + * Without this filter, if language of the title being sanitized is different from the language + * used for the admin interface and if one this language is German or Danish, some specific + * characters such as ä, ö, ü, ß are incorrectly sanitized. + * + * All the process is done by the remove_accents() WordPress function based on the locale value + * + * @link https://github.com/WordPress/WordPress/blob/5.5.1/wp-includes/formatting.php#L1920-L1944 + * + * @since 2.0 + * + * @param string $title Sanitized title. + * @param string $raw_title The title prior to sanitization. + * @param string $context The context for which the title is being sanitized. + * @return string + */ + public function sanitize_title( $title, $raw_title, $context ) { + static $once = false; + + if ( ! $once && 'save' == $context && ! empty( $title ) ) { + $once = true; + add_filter( 'locale', array( $this, 'get_locale' ), 20 ); // After the filter for the admin interface + $title = sanitize_title( $raw_title, '', $context ); + remove_filter( 'locale', array( $this, 'get_locale' ), 20 ); + $once = false; + } + return $title; + } + + /** + * Maybe fix the result of sanitize_user() in case the languages include German or Danish + * + * All the process is done by the remove_accents() WordPress function based on the locale value + * + * @link https://github.com/WordPress/WordPress/blob/5.5-branch/wp-includes/formatting.php#L1920-L1944 + * + * @since 2.0 + * + * @param string $username Sanitized username. + * @param string $raw_username The username prior to sanitization. + * @param bool $strict Whether to limit the sanitization to specific characters. Default false. + * @return string + */ + public function sanitize_user( $username, $raw_username, $strict ) { + static $once = false; + + if ( ! $once ) { + $once = true; + add_filter( 'locale', array( $this, 'get_locale' ), 20 ); // After the filter for the admin interface + $username = sanitize_user( $raw_username, $strict ); + remove_filter( 'locale', array( $this, 'get_locale' ), 20 ); + $once = false; + } + return $username; + } +} diff --git a/wp-content/plugins/polylang/include/filters-widgets-options.php b/wp-content/plugins/polylang/include/filters-widgets-options.php new file mode 100644 index 0000000000..52b9d3d012 --- /dev/null +++ b/wp-content/plugins/polylang/include/filters-widgets-options.php @@ -0,0 +1,92 @@ +model = $polylang->model; + + add_action( 'in_widget_form', array( $this, 'in_widget_form' ), 10, 3 ); + add_filter( 'widget_update_callback', array( $this, 'widget_update_callback' ), 10, 2 ); + } + + /** + * Add the language filter field to the widgets options form. + * + * @since 3.0 Moved PLL_Admin_Filters. + * @since 3.1 Rename lang_choice field name and id to pll_lang as the widget setting. + * + * @param WP_Widget $widget The widget instance (passed by reference). + * @param null $return Return null if new fields are added. + * @param array $instance An array of the widget's settings. + * @return void + */ + public function in_widget_form( $widget, $return, $instance ) { + $dropdown = new PLL_Walker_Dropdown(); + + $dropdown_html = $dropdown->walk( + array_merge( + array( (object) array( 'slug' => 0, 'name' => __( 'All languages', 'polylang' ) ) ), + $this->model->get_languages_list() + ), + -1, + array( + 'id' => $widget->get_field_id( 'pll_lang' ), + 'name' => $widget->get_field_name( 'pll_lang' ), + 'class' => 'tags-input pll-lang-choice', + 'selected' => empty( $instance['pll_lang'] ) ? '' : $instance['pll_lang'], + ) + ); + + printf( + '

', + esc_attr( $widget->get_field_id( 'pll_lang' ) ), + esc_html__( 'The widget is displayed for:', 'polylang' ), + $dropdown_html // phpcs:ignore WordPress.Security.EscapeOutput + ); + } + + /** + * Called when widget options are saved. + * Saves the language associated to the widget. + * + * @since 0.3 + * @since 3.0 Moved from PLL_Admin_Filters. + * @since 3.1 Remove unused $old_instance and $widget parameters. + * + * @param array $instance The current Widget's options. + * @param array $new_instance The new Widget's options. + * @return array Widget options. + */ + public function widget_update_callback( $instance, $new_instance ) { + if ( ! empty( $new_instance['pll_lang'] ) && $lang = $this->model->get_language( $new_instance['pll_lang'] ) ) { + $instance['pll_lang'] = $lang->slug; + } else { + unset( $instance['pll_lang'] ); + } + + return $instance; + } +} diff --git a/wp-content/plugins/polylang/include/filters.php b/wp-content/plugins/polylang/include/filters.php new file mode 100644 index 0000000000..2dc81748e4 --- /dev/null +++ b/wp-content/plugins/polylang/include/filters.php @@ -0,0 +1,472 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + $this->curlang = &$polylang->curlang; + + // Deletes our cache for sticky posts when the list is updated. + add_action( 'update_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) ); + add_action( 'add_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) ); + add_action( 'delete_option_sticky_posts', array( $this, 'delete_sticky_posts_cache' ) ); + + // Filters the comments according to the current language + add_action( 'parse_comment_query', array( $this, 'parse_comment_query' ) ); + add_filter( 'comments_clauses', array( $this, 'comments_clauses' ), 10, 2 ); + + // Filters the get_pages function according to the current language + if ( version_compare( $wp_version, '6.3-alpha', '<' ) ) { + // Backward compatibility with WP < 6.3. + add_filter( 'get_pages', array( $this, 'get_pages' ), 10, 2 ); + } + add_filter( 'get_pages_query_args', array( $this, 'get_pages_query_args' ), 10, 2 ); + + // Rewrites next and previous post links to filter them by language + add_filter( 'get_previous_post_join', array( $this, 'posts_join' ), 10, 5 ); + add_filter( 'get_next_post_join', array( $this, 'posts_join' ), 10, 5 ); + add_filter( 'get_previous_post_where', array( $this, 'posts_where' ), 10, 5 ); + add_filter( 'get_next_post_where', array( $this, 'posts_where' ), 10, 5 ); + + // Converts the locale to a valid W3C locale + add_filter( 'language_attributes', array( $this, 'language_attributes' ) ); + + // Translate the site title in emails sent to users + add_filter( 'password_change_email', array( $this, 'translate_user_email' ) ); + add_filter( 'email_change_email', array( $this, 'translate_user_email' ) ); + + // Translates the privacy policy page + add_filter( 'option_wp_page_for_privacy_policy', array( $this, 'translate_page_for_privacy_policy' ), 20 ); // Since WP 4.9.6 + add_filter( 'map_meta_cap', array( $this, 'fix_privacy_policy_page_editing' ), 10, 4 ); + + // Personal data exporter + add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ), 0 ); // Since WP 4.9.6 + + // Fix for `term_exists()`. + add_filter( 'term_exists_default_query_args', array( $this, 'term_exists_default_query_args' ), 0, 3 ); // Since WP 6.0.0. + } + + /** + * Deletes the cache for multilingual sticky posts. + * + * @since 2.8.4 + * + * @return void + */ + public function delete_sticky_posts_cache() { + wp_cache_delete( 'sticky_posts', 'options' ); + } + + /** + * Get the language to filter a comments query. + * + * @since 2.0 + * @since 3.1 Always returns an array. Renamed from get_comments_queried_language(). + * + * @param WP_Comment_Query $query WP_Comment_Query object. + * @return PLL_Language[] The languages to use in the filter. + */ + protected function get_comments_queried_languages( $query ) { + // Don't filter comments if comment ids or post ids are specified. + $plucked = wp_array_slice_assoc( $query->query_vars, array( 'comment__in', 'parent', 'post_id', 'post__in', 'post_parent' ) ); + $fields = array_filter( $plucked ); + if ( ! empty( $fields ) ) { + return array(); + } + + // Don't filter comments if a non translated post type is specified. + if ( ! empty( $query->query_vars['post_type'] ) && ! $this->model->is_translated_post_type( $query->query_vars['post_type'] ) ) { + return array(); + } + + // If comments are queried with a 'lang' parameter, keeps only language codes. + if ( isset( $query->query_vars['lang'] ) ) { + $languages = is_string( $query->query_vars['lang'] ) ? explode( ',', $query->query_vars['lang'] ) : $query->query_vars['lang']; + if ( is_array( $languages ) ) { + $languages = array_map( array( $this->model, 'get_language' ), $languages ); + return array_filter( $languages ); + } + } + + if ( ! empty( $this->curlang ) ) { + return array( $this->curlang ); + } + + return array(); + } + + /** + * Adds a language dependent cache domain when querying comments. + * Useful as the 'lang' parameter is not included in cache key by WordPress. + * Needed since WP 4.6 as comments have been added to persistent cache. See #36906, #37419. + * + * @since 2.0 + * + * @param WP_Comment_Query $query WP_Comment_Query object. + * @return void + */ + public function parse_comment_query( $query ) { + $lang = $this->get_comments_queried_languages( $query ); + if ( ! empty( $lang ) ) { + $lang = wp_list_pluck( $lang, 'slug' ); + $key = '_' . implode( ',', $lang ); + $query->query_vars['cache_domain'] = empty( $query->query_vars['cache_domain'] ) ? 'pll' . $key : $query->query_vars['cache_domain'] . $key; + } + } + + /** + * Filters the comments according to the current language. + * Used by the recent comments widget and admin language filter. + * + * @since 0.2 + * + * @param string[] $clauses SQL clauses. + * @param WP_Comment_Query $query WP_Comment_Query object. + * @return string[] Modified $clauses. + */ + public function comments_clauses( $clauses, $query ) { + global $wpdb; + + $lang = $this->get_comments_queried_languages( $query ); + + if ( ! empty( $lang ) ) { + $lang = wp_list_pluck( $lang, 'slug' ); + + // If this clause is not already added by WP. + if ( ! strpos( $clauses['join'], '.ID' ) ) { + $clauses['join'] .= " JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID"; + } + + $clauses['join'] .= $this->model->post->join_clause(); + $clauses['where'] .= $this->model->post->where_clause( $lang ); + } + return $clauses; + } + + /** + * Filters get_pages() per language. + * + * @since 1.4 + * + * @param WP_Post[] $pages An array of pages already queried. + * @param array $args Array of get_pages() arguments. + * @return WP_Post[] Modified list of pages. + */ + public function get_pages( $pages, $args ) { + if ( isset( $args['lang'] ) && empty( $args['lang'] ) ) { + return $pages; + } + + $language = empty( $args['lang'] ) ? $this->curlang : $this->model->get_language( $args['lang'] ); + + if ( empty( $language ) || empty( $pages ) || ! $this->model->is_translated_post_type( $args['post_type'] ) ) { + return $pages; + } + + static $once = false; + + if ( ! empty( $args['number'] ) && ! $once ) { + // We are obliged to redo the get_pages() query if we want to get the right number. + $once = true; // Avoid infinite loop. + + // Take care that 'exclude' argument accepts integer or strings too. + $args['exclude'] = array_merge( wp_parse_id_list( $args['exclude'] ), $this->get_related_page_ids( $language, 'NOT IN', $args ) ); // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude + $numbered_pages = get_pages( $args ); + $pages = ! $numbered_pages ? $pages : $numbered_pages; + } + + $ids = wp_list_pluck( $pages, 'ID' ); + + if ( ! $once ) { + // Filters the queried list of pages by language. + $ids = array_intersect( $ids, $this->get_related_page_ids( $language, 'IN', $args ) ); + + foreach ( $pages as $key => $page ) { + if ( ! in_array( $page->ID, $ids ) ) { + unset( $pages[ $key ] ); + } + } + + $pages = array_values( $pages ); // In case 3rd parties suppose the existence of $pages[0]. + } + + // Not done by WP but extremely useful for performance when manipulating taxonomies. + update_object_term_cache( $ids, $args['post_type'] ); + + $once = false; // In case get_pages() is called another time. + return $pages; + } + + /** + * Filters the WP_Query in get_pages() per language. + * + * @since 3.4.3 + * + * @param array $query_args Array of arguments passed to WP_Query. + * @param array $parsed_args Array of get_pages() arguments. + * @return array Array of arguments passed to WP_Query with the language. + */ + public function get_pages_query_args( $query_args, $parsed_args ) { + if ( isset( $parsed_args['lang'] ) ) { + $query_args['lang'] = $parsed_args['lang']; + } + + return $query_args; + } + + /** + * Get page ids related to a get_pages() in or not in a given language. + * + * @since 3.2 + * + * @param PLL_Language $language The language to use in the relationship + * @param string $relation 'IN' or 'NOT IN'. + * @param array $args Array of get_pages() arguments. + * @return int[] + */ + protected function get_related_page_ids( $language, $relation, $args ) { + $r = array( + 'lang' => '', // Ensure this query is not filtered. + 'numberposts' => -1, + 'nopaging' => true, + 'post_type' => $args['post_type'], + 'post_status' => $args['post_status'], + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => 'language', + 'field' => 'term_taxonomy_id', // Since WP 3.5. + 'terms' => $language->get_tax_prop( 'language', 'term_taxonomy_id' ), + 'operator' => $relation, + ), + ), + ); + + return get_posts( $r ); + } + + /** + * Modifies the sql request for get_adjacent_post to filter by the current language. + * + * @since 0.1 + * + * @param string $sql The JOIN clause in the SQL. + * @param bool $in_same_term Whether post should be in a same taxonomy term. + * @param int[] $excluded_terms Array of excluded term IDs. + * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true. + * @param WP_Post $post WP_Post object. + * @return string Modified JOIN clause. + */ + public function posts_join( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) { + return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->join_clause( 'p' ) : $sql; + } + + /** + * Modifies the sql request for wp_get_archives and get_adjacent_post to filter by the current language. + * + * @since 0.1 + * + * @param string $sql The WHERE clause in the SQL. + * @param bool $in_same_term Whether post should be in a same taxonomy term. + * @param int[] $excluded_terms Array of excluded term IDs. + * @param string $taxonomy Taxonomy. Used to identify the term used when `$in_same_term` is true. + * @param WP_Post $post WP_Post object. + * @return string Modified WHERE clause. + */ + public function posts_where( $sql, $in_same_term, $excluded_terms, $taxonomy, $post ) { + return $this->model->is_translated_post_type( $post->post_type ) && ! empty( $this->curlang ) ? $sql . $this->model->post->where_clause( $this->curlang ) : $sql; + } + + /** + * Converts WordPress locale to valid W3 locale in html language attributes + * + * @since 1.8 + * + * @param string $output language attributes + * @return string + */ + public function language_attributes( $output ) { + if ( $language = $this->model->get_language( is_admin() ? get_user_locale() : get_locale() ) ) { + $output = str_replace( '"' . get_bloginfo( 'language' ) . '"', '"' . $language->get_locale( 'display' ) . '"', $output ); + } + return $output; + } + + /** + * Translates the site title in emails sent to the user (change email, reset password) + * It is necessary to filter the email because WP evaluates the site title before calling switch_to_locale() + * + * @since 2.1.3 + * + * @param string[] $email Email contents. + * @return string[] Translated email contents. + */ + public function translate_user_email( $email ) { + $blog_name = wp_specialchars_decode( pll__( get_option( 'blogname' ) ), ENT_QUOTES ); + $email['subject'] = sprintf( $email['subject'], $blog_name ); + $email['message'] = str_replace( '###SITENAME###', $blog_name, $email['message'] ); + return $email; + } + + /** + * Translates the privacy policy page, on both frontend and admin + * + * @since 2.3.6 + * + * @param int $id Privacy policy page id + * @return int + */ + public function translate_page_for_privacy_policy( $id ) { + return empty( $this->curlang ) ? $id : $this->model->post->get( $id, $this->curlang ); + } + + /** + * Prevents edit and delete links for the translations of the privacy policy page for non admin + * + * @since 2.3.7 + * + * @param array $caps The user's actual capabilities. + * @param string $cap Capability name. + * @param int $user_id The user ID. + * @param array $args Adds the context to the cap. The category id. + * @return array + */ + public function fix_privacy_policy_page_editing( $caps, $cap, $user_id, $args ) { + if ( in_array( $cap, array( 'edit_page', 'edit_post', 'delete_page', 'delete_post' ) ) ) { + $privacy_page = get_option( 'wp_page_for_privacy_policy' ); + if ( $privacy_page && array_intersect( $args, $this->model->post->get_translations( $privacy_page ) ) ) { + $caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) ); + } + } + + return $caps; + } + + /** + * Register our personal data exporter + * + * @since 2.3.6 + * + * @param array $exporters Personal data exporters + * @return array + */ + public function register_personal_data_exporter( $exporters ) { + $exporters[] = array( + 'exporter_friendly_name' => __( 'Translated user descriptions', 'polylang' ), + 'callback' => array( $this, 'user_data_exporter' ), + ); + return $exporters; + } + + /** + * Export translated user description as WP exports only the description in the default language + * + * @since 2.3.6 + * + * @param string $email_address User email address + * @return array Personal data + */ + public function user_data_exporter( $email_address ) { + $email_address = trim( $email_address ); + $data_to_export = array(); + $user_data_to_export = array(); + + if ( $user = get_user_by( 'email', $email_address ) ) { + foreach ( $this->model->get_languages_list() as $lang ) { + if ( ! $lang->is_default && $value = get_user_meta( $user->ID, 'description_' . $lang->slug, true ) ) { + $user_data_to_export[] = array( + /* translators: %s is a language native name */ + 'name' => sprintf( __( 'User description - %s', 'polylang' ), $lang->name ), + 'value' => $value, + ); + } + } + + if ( ! empty( $user_data_to_export ) ) { + $data_to_export[] = array( + 'group_id' => 'user', + 'group_label' => __( 'User', 'polylang' ), + 'item_id' => "user-{$user->ID}", + 'data' => $user_data_to_export, + ); + } + } + + return array( + 'data' => $data_to_export, + 'done' => true, + ); + } + + /** + * Filters default query arguments for checking if a term exists. + * In `term_exists()`, WP 6.0 uses `get_terms()`, which is filtered by language by Polylang. + * This filter prevents `term_exists()` to be filtered by language. + * + * @since 3.2 + * + * @param array $defaults An array of arguments passed to get_terms(). + * @param int|string $term The term to check. Accepts term ID, slug, or name. + * @param string $taxonomy The taxonomy name to use. An empty string indicates the search is against all taxonomies. + * @return array + */ + public function term_exists_default_query_args( $defaults, $term, $taxonomy ) { + if ( ! empty( $taxonomy ) && ! $this->model->is_translated_taxonomy( $taxonomy ) ) { + return $defaults; + } + + if ( ! is_array( $defaults ) ) { + $defaults = array(); + } + + if ( ! isset( $defaults['lang'] ) ) { + $defaults['lang'] = ''; + } + + return $defaults; + } +} diff --git a/wp-content/plugins/polylang/include/functions.php b/wp-content/plugins/polylang/include/functions.php new file mode 100644 index 0000000000..a85feee127 --- /dev/null +++ b/wp-content/plugins/polylang/include/functions.php @@ -0,0 +1,237 @@ +has_errors() ) { + return; + } + + foreach ( $error->get_error_codes() as $error_code ) { + // Extract the "error" type. + $data = $error->get_error_data( $error_code ); + $type = empty( $data ) || ! is_string( $data ) ? 'error' : $data; + + $message = wp_kses( + implode( '
', $error->get_error_messages( $error_code ) ), + array( + 'a' => array( 'href' ), + 'br' => array(), + 'code' => array(), + 'em' => array(), + ) + ); + + add_settings_error( 'polylang', $error_code, $message, $type ); + } +} diff --git a/wp-content/plugins/polylang/include/language-deprecated.php b/wp-content/plugins/polylang/include/language-deprecated.php new file mode 100644 index 0000000000..043c3a0a86 --- /dev/null +++ b/wp-content/plugins/polylang/include/language-deprecated.php @@ -0,0 +1,256 @@ + array( 'language', 'term_taxonomy_id' ), + 'count' => array( 'language', 'count' ), + 'tl_term_id' => array( 'term_language', 'term_id' ), + 'tl_term_taxonomy_id' => array( 'term_language', 'term_taxonomy_id' ), + 'tl_count' => array( 'term_language', 'count' ), + ); + + /** + * List of deprecated URL properties and related getter to use. + * + * @private + * + * @var string[] + */ + const DEPRECATED_URL_PROPERTIES = array( + 'home_url' => 'get_home_url', + 'search_url' => 'get_search_url', + ); + + /** + * Returns a language term property value (term ID, term taxonomy ID, or count). + * + * @since 3.4 + * + * @param string $taxonomy_name Name of the taxonomy. + * @param string $prop_name Name of the property: 'term_taxonomy_id', 'term_id', 'count'. + * @return int + * + * @phpstan-param non-empty-string $taxonomy_name + * @phpstan-param 'term_taxonomy_id'|'term_id'|'count' $prop_name + * @phpstan-return int<0, max> + */ + abstract public function get_tax_prop( $taxonomy_name, $prop_name ); + + /** + * Returns language's home URL. Takes care to render it dynamically if no cache is allowed. + * + * @since 3.4 + * + * @return string Language home URL. + * + * @phpstan-return non-empty-string + */ + abstract public function get_home_url(); + + /** + * Returns language's search URL. Takes care to render it dynamically if no cache is allowed. + * + * @since 3.4 + * + * @return string Language search URL. + * + * @phpstan-return non-empty-string + */ + abstract public function get_search_url(); + + /** + * Throws a depreciation notice if someone tries to get one of the following properties: + * `term_taxonomy_id`, `count`, `tl_term_id`, `tl_term_taxonomy_id` or `tl_count`. + * + * Backward compatibility with Polylang < 3.4. + * + * @since 3.4 + * + * @param string $property Property to get. + * @return mixed Required property value. + */ + public function __get( $property ) { + // Deprecated property. + if ( $this->is_deprecated_term_property( $property ) ) { + $this->deprecated_property( + $property, + sprintf( + "get_tax_prop( '%s', '%s' )", + self::DEPRECATED_TERM_PROPERTIES[ $property ][0], + self::DEPRECATED_TERM_PROPERTIES[ $property ][1] + ) + ); + + return $this->get_deprecated_term_property( $property ); + } + + if ( $this->is_deprecated_url_property( $property ) ) { + $this->deprecated_property( $property, "get_{$property}()" ); + + return $this->get_deprecated_url_property( $property ); + } + + // Undefined property. + if ( ! property_exists( $this, $property ) ) { + return null; + } + + // The property is defined. + $ref = new ReflectionProperty( $this, $property ); + + // Public property. + if ( $ref->isPublic() ) { + return $this->{$property}; + } + + // Protected or private property. + $visibility = $ref->isPrivate() ? 'private' : 'protected'; + $trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection, WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace + $file = isset( $trace[0]['file'] ) ? $trace[0]['file'] : ''; + $line = isset( $trace[0]['line'] ) ? $trace[0]['line'] : 0; + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + esc_html( + sprintf( + "Cannot access %s property %s::$%s in %s on line %d.\nError handler", + $visibility, + get_class( $this ), + $property, + $file, + $line + ) + ), + E_USER_ERROR + ); + } + + /** + * Checks for a deprecated property. + * Is triggered by calling `isset()` or `empty()` on inaccessible (protected or private) or non-existing properties. + * + * Backward compatibility with Polylang < 3.4. + * + * @since 3.4 + * + * @param string $property A property name. + * @return bool + */ + public function __isset( $property ) { + return $this->is_deprecated_term_property( $property ) || $this->is_deprecated_url_property( $property ); + } + + /** + * Tells if the given term property is deprecated. + * + * @since 3.4 + * @see PLL_Language::DEPRECATED_TERM_PROPERTIES for the list of deprecated properties. + * + * @param string $property A property name. + * @return bool + * + * @phpstan-assert-if-true key-of $property + */ + protected function is_deprecated_term_property( $property ) { + return array_key_exists( $property, self::DEPRECATED_TERM_PROPERTIES ); + } + + /** + * Returns a deprecated term property's value. + * + * @since 3.4 + * @see PLL_Language::DEPRECATED_TERM_PROPERTIES for the list of deprecated properties. + * + * @param string $property A property name. + * @return int + * + * @phpstan-param key-of $property + * @phpstan-return int<0, max> + */ + protected function get_deprecated_term_property( $property ) { + return $this->get_tax_prop( + self::DEPRECATED_TERM_PROPERTIES[ $property ][0], + self::DEPRECATED_TERM_PROPERTIES[ $property ][1] + ); + } + + /** + * Tells if the given URL property is deprecated. + * + * @since 3.4 + * @see PLL_Language::DEPRECATED_URL_PROPERTIES for the list of deprecated properties. + * + * @param string $property A property name. + * @return bool + * + * @phpstan-assert-if-true key-of $property + */ + protected function is_deprecated_url_property( $property ) { + return array_key_exists( $property, self::DEPRECATED_URL_PROPERTIES ); + } + + /** + * Returns a deprecated URL property's value. + * + * @since 3.4 + * @see PLL_Language::DEPRECATED_URL_PROPERTIES for the list of deprecated properties. + * + * @param string $property A property name. + * @return string + * + * @phpstan-param key-of $property + * @phpstan-return non-empty-string + */ + protected function get_deprecated_url_property( $property ) { + return $this->{self::DEPRECATED_URL_PROPERTIES[ $property ]}(); + } + + /** + * Triggers a deprecated an error for a deprecated property. + * + * @since 3.4 + * + * @param string $property Deprecated property name. + * @param string $replacement Method or property name to use instead. + * @return void + */ + private function deprecated_property( $property, $replacement ) { + /** + * Filters whether to trigger an error for deprecated properties. + * + * The filter name is intentionally not prefixed to use the same as WordPress + * in case it is added in the future. + * + * @since 3.4 + * + * @param bool $trigger Whether to trigger the error for deprecated properties. Default true. + */ + if ( ! WP_DEBUG || ! apply_filters( 'deprecated_property_trigger_error', true ) ) { + return; + } + + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error + sprintf( + "Class property %1\$s::\$%2\$s is deprecated, use %1\$s::%3\$s instead.\nError handler", + esc_html( get_class( $this ) ), + esc_html( $property ), + esc_html( $replacement ) + ), + E_USER_DEPRECATED + ); + } +} diff --git a/wp-content/plugins/polylang/include/language-factory.php b/wp-content/plugins/polylang/include/language-factory.php new file mode 100644 index 0000000000..c8e2fc5745 --- /dev/null +++ b/wp-content/plugins/polylang/include/language-factory.php @@ -0,0 +1,338 @@ +> + */ + private static $languages; + + /** + * Polylang's options. + * + * @var array + */ + private $options; + + /** + * Constructor. + * + * @since 3.4 + * + * @param array $options Array of Poylang's options passed by reference. + * @return void + */ + public function __construct( &$options ) { + $this->options = &$options; + } + + /** + * Returns a language object matching the given data, looking up in cached transient. + * + * @since 3.4 + * + * @param array $language_data Language object properties stored as an array. See `PLL_Language::__construct()` + * for information on accepted properties. + * + * @return PLL_Language A language object if given data pass sanitization. + * + * @phpstan-param LanguageData $language_data + */ + public function get( $language_data ) { + return new PLL_Language( $this->sanitize_data( $language_data ) ); + } + + /** + * Returns a language object based on terms. + * + * @since 3.4 + * + * @param WP_Term[] $terms List of language terms, with the language taxonomy names as array keys. + * `language` is a mandatory key for the object to be created, + * `term_language` should be too in a fully operational environment. + * @return PLL_Language|null Language object on success, `null` on failure. + * + * @phpstan-param array{language?:WP_Term}&array $terms + */ + public function get_from_terms( array $terms ) { + if ( ! isset( $terms['language'] ) ) { + return null; + } + + $languages = $this->get_languages(); + $data = array( + 'name' => $terms['language']->name, + 'slug' => $terms['language']->slug, + 'term_group' => $terms['language']->term_group, + 'term_props' => array(), + 'is_default' => $this->options['default_lang'] === $terms['language']->slug, + ); + + foreach ( $terms as $term ) { + $data['term_props'][ $term->taxonomy ] = array( + 'term_id' => $term->term_id, + 'term_taxonomy_id' => $term->term_taxonomy_id, + 'count' => $term->count, + ); + } + + // The description fields can contain any property. + $description = maybe_unserialize( $terms['language']->description ); + + if ( is_array( $description ) ) { + $description = array_intersect_key( + $description, + array( 'locale' => null, 'rtl' => null, 'flag_code' => null, 'active' => null, 'fallbacks' => null ) + ); + + foreach ( $description as $prop => $value ) { + if ( 'rtl' === $prop ) { + $data['is_rtl'] = $value; + } else { + $data[ $prop ] = $value; + } + } + } + + if ( ! empty( $data['locale'] ) ) { + if ( isset( $languages[ $data['locale'] ]['w3c'] ) ) { + $data['w3c'] = $languages[ $data['locale'] ]['w3c']; + } else { + $data['w3c'] = str_replace( '_', '-', $data['locale'] ); + } + + if ( isset( $languages[ $data['locale'] ]['facebook'] ) ) { + $data['facebook'] = $languages[ $data['locale'] ]['facebook']; + } + } + + $flag_props = $this->get_flag( $data['flag_code'], $data['name'], $data['slug'], $data['locale'] ); + $data = array_merge( $data, $flag_props ); + + $additional_data = array(); + /** + * Filters additional data to add to the language before it is created. + * + * `home_url`, `search_url`, `page_on_front` and `page_for_posts` are only allowed. + * + * @since 3.4 + * + * @param array $additional_data. + * @param array $data Language data. + * + * @phpstan-param array $additional_data + * @phpstan-param non-empty-array $data + */ + $additional_data = apply_filters( 'pll_additional_language_data', $additional_data, $data ); + + $allowed_additional_data = array( + 'home_url' => '', + 'search_url' => '', + 'page_on_front' => 0, + 'page_for_posts' => 0, + ); + + $data = array_merge( $data, array_intersect_key( $additional_data, $allowed_additional_data ) ); + + return new PLL_Language( $this->sanitize_data( $data ) ); + } + + /** + * Sanitizes data, to be ready to be used in the constructor. + * This doesn't verify that the language terms exist. + * + * @since 3.4 + * + * @param array $data Data to process. See `PLL_Language::__construct()` for information on accepted data. + * @return array Sanitized Data. + * + * @phpstan-return LanguageData + */ + private function sanitize_data( array $data ) { + foreach ( $data['term_props'] as $tax => $props ) { + $data['term_props'][ $tax ] = array_map( 'absint', $props ); + } + + $data['is_rtl'] = ! empty( $data['is_rtl'] ) ? 1 : 0; + + $positive_fields = array( 'term_group', 'page_on_front', 'page_for_posts' ); + + foreach ( $positive_fields as $field ) { + $data[ $field ] = ! empty( $data[ $field ] ) ? absint( $data[ $field ] ) : 0; + } + + $data['active'] = isset( $data['active'] ) ? (bool) $data['active'] : true; + + if ( array_key_exists( 'fallbacks', $data ) && ! is_array( $data['fallbacks'] ) ) { + unset( $data['fallbacks'] ); + } + + /** + * @var LanguageData + */ + return $data; + } + + /** + * Returns predefined languages data. + * + * @since 3.4 + * + * @return array[] + * + * @phpstan-return array> + */ + private function get_languages() { + if ( empty( self::$languages ) ) { + self::$languages = include POLYLANG_DIR . '/settings/languages.php'; + } + + return self::$languages; + } + + + /** + * Creates flag_url and flag language properties. Also takes care of custom flag. + * + * @since 1.2 + * @since 3.4 Moved from `PLL_Language`to `PLL_Language_Factory` and renamed + * in favor of `get_flag()` (formerly `set_flag()`). + * + * @param string $flag_code Flag code. + * @param string $name Language name. + * @param string $slug Language slug. + * @param string $locale Language locale. + * @return array { + * Array of the flag properties. + * @type string $flag_url URL of the flag. + * @type string $flag HTML markup of the flag. + * @type string $custom_flag_url Optional. URL of the custom flag if it exists. + * @type string $custom_flag Optional. HTML markup of the custom flag if it exists. + * } + * + * @phpstan-return array{ + * flag_url: string, + * flag: string, + * custom_flag_url?: non-empty-string, + * custom_flag?: non-empty-string + * } + */ + private function get_flag( $flag_code, $name, $slug, $locale ) { + $flags = array( + 'flag' => PLL_Language::get_flag_informations( $flag_code ), + ); + + // Custom flags? + $directories = array( + PLL_LOCAL_DIR, + get_stylesheet_directory() . '/polylang', + get_template_directory() . '/polylang', + ); + + foreach ( $directories as $dir ) { + if ( is_readable( $file = "{$dir}/{$locale}.png" ) || is_readable( $file = "{$dir}/{$locale}.jpg" ) || is_readable( $file = "{$dir}/{$locale}.jpeg" ) || is_readable( $file = "{$dir}/{$locale}.svg" ) ) { + $flags['custom_flag'] = array( + 'url' => content_url( '/' . str_replace( WP_CONTENT_DIR, '', $file ) ), + ); + break; + } + } + + /** + * Filters the custom flag information. + * + * @since 2.4 + * + * @param array|null $flag { + * Information about the custom flag. + * + * @type string $url Flag url. + * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded. + * @type int $width Optional, flag width in pixels. + * @type int $height Optional, flag height in pixels. + * } + * @param string $code Flag code. + */ + $flags['custom_flag'] = apply_filters( 'pll_custom_flag', empty( $flags['custom_flag'] ) ? null : $flags['custom_flag'], $flag_code ); + + if ( ! empty( $flags['custom_flag']['url'] ) ) { + if ( empty( $flags['custom_flag']['src'] ) ) { + $flags['custom_flag']['src'] = esc_url( set_url_scheme( $flags['custom_flag']['url'], 'relative' ) ); + } + + $flags['custom_flag']['url'] = esc_url_raw( $flags['custom_flag']['url'] ); + } else { + unset( $flags['custom_flag'] ); + } + + /** + * Filters the flag title attribute. + * Defaults to the language name. + * + * @since 0.7 + * + * @param string $title The flag title attribute. + * @param string $slug The language code. + * @param string $locale The language locale. + */ + $title = apply_filters( 'pll_flag_title', $name, $slug, $locale ); + $return = array(); + + /** + * @var array{ + * flag: array{ + * url: string, + * src: string, + * width?: positive-int, + * height?: positive-int + * }, + * custom_flag?: array{ + * url: non-empty-string, + * src: non-empty-string, + * width?: positive-int, + * height?: positive-int + * } + * } $flags + */ + foreach ( $flags as $key => $flag ) { + $return[ "{$key}_url" ] = $flag['url']; + + /** + * Filters the html markup of a flag. + * + * @since 1.0.2 + * + * @param string $flag Html markup of the flag or empty string. + * @param string $slug Language code. + */ + $return[ $key ] = apply_filters( + 'pll_get_flag', + PLL_Language::get_flag_html( $flag, $title, $name ), + $slug + ); + } + + /** + * @var array{ + * flag_url: string, + * flag: string, + * custom_flag_url?: non-empty-string, + * custom_flag?: non-empty-string + * } $return + */ + return $return; + } +} diff --git a/wp-content/plugins/polylang/include/language.php b/wp-content/plugins/polylang/include/language.php new file mode 100644 index 0000000000..db0bf21623 --- /dev/null +++ b/wp-content/plugins/polylang/include/language.php @@ -0,0 +1,675 @@ + + * } + * @phpstan-type LanguageData array{ + * term_props: array{ + * language: LanguagePropData, + * }&array, + * name: non-empty-string, + * slug: non-empty-string, + * locale: non-empty-string, + * w3c: non-empty-string, + * flag_code: non-empty-string, + * term_group: int, + * is_rtl: int<0, 1>, + * facebook?: string, + * home_url: non-empty-string, + * search_url: non-empty-string, + * host: non-empty-string, + * flag_url: non-empty-string, + * flag: non-empty-string, + * custom_flag_url?: string, + * custom_flag?: string, + * page_on_front: int<0, max>, + * page_for_posts: int<0, max>, + * active: bool, + * fallbacks?: array, + * is_default: bool + * } + */ +class PLL_Language extends PLL_Language_Deprecated { + + /** + * Language name. Ex: English. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $name; + + /** + * Language code used in URL. Ex: en. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $slug; + + /** + * Order of the language when displayed in a list of languages. + * + * @var int + */ + public $term_group; + + /** + * ID of the term in 'language' taxonomy. + * Duplicated from `$this->term_props['language']['term_id'], + * but kept to facilitate the use of it. + * + * @var int + * + * @phpstan-var int<1, max> + */ + public $term_id; + + /** + * WordPress language locale. Ex: en_US. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $locale; + + /** + * 1 if the language is rtl, 0 otherwise. + * + * @var int + * + * @phpstan-var int<0, 1> + */ + public $is_rtl; + + /** + * W3C locale. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $w3c; + + /** + * Facebook locale. + * + * @var string + */ + public $facebook = ''; + + /** + * Home URL in this language. + * + * @var string + * + * @phpstan-var non-empty-string + */ + private $home_url; + + /** + * Home URL to use in search forms. + * + * @var string + * + * @phpstan-var non-empty-string + */ + private $search_url; + + /** + * Host corresponding to this language. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $host; + + /** + * ID of the page on front in this language (set from pll_additional_language_data filter). + * + * @var int + * + * @phpstan-var int<0, max> + */ + public $page_on_front = 0; + + /** + * ID of the page for posts in this language (set from pll_additional_language_data filter). + * + * @var int + * + * @phpstan-var int<0, max> + */ + public $page_for_posts = 0; + + /** + * Code of the flag. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $flag_code; + + /** + * URL of the flag. Always set to the main domain. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $flag_url; + + /** + * HTML markup of the flag. + * + * @var string + * + * @phpstan-var non-empty-string + */ + public $flag; + + /** + * URL of the custom flag if it exists. Always set to the main domain. + * + * @var string + */ + public $custom_flag_url = ''; + + /** + * HTML markup of the custom flag if it exists. + * + * @var string + */ + public $custom_flag = ''; + + /** + * Whether or not the language is active. Default `true`. + * + * @var bool + */ + public $active = true; + + /** + * List of WordPress language locales. Ex: array( 'en_GB' ). + * + * @var string[] + * + * @phpstan-var list + */ + public $fallbacks = array(); + + /** + * Whether the language is the default one. + * + * @var bool + */ + public $is_default; + + /** + * Stores language term properties (like term IDs and counts) for each language taxonomy (`language`, + * `term_language`, etc). + * This stores the values of the properties `$term_id` + `$term_taxonomy_id` + `$count` (`language`), `$tl_term_id` + * + `$tl_term_taxonomy_id` + `$tl_count` (`term_language`), and the `term_id` + `term_taxonomy_id` + `count` for + * other language taxonomies. + * + * @var array[] Array keys are language term names. + * + * @example array( + * 'language' => array( + * 'term_id' => 7, + * 'term_taxonomy_id' => 8, + * 'count' => 11, + * ), + * 'term_language' => array( + * 'term_id' => 11, + * 'term_taxonomy_id' => 12, + * 'count' => 6, + * ), + * 'foo_language' => array( + * 'term_id' => 33, + * 'term_taxonomy_id' => 34, + * 'count' => 0, + * ), + * ) + * + * @phpstan-var array{ + * language: LanguagePropData, + * } + * &array + */ + protected $term_props; + + /** + * Constructor: builds a language object given the corresponding data. + * + * @since 1.2 + * @since 3.4 Only accepts one argument. + * + * @param array $language_data { + * Language object properties stored as an array. + * + * @type array[] $term_props An array of language term properties. Array keys are language taxonomy names + * (`language` and `term_language` are mandatory), array values are arrays of + * language term properties (`term_id`, `term_taxonomy_id`, and `count`). + * @type string $name Language name. Ex: English. + * @type string $slug Language code used in URL. Ex: en. + * @type string $locale WordPress language locale. Ex: en_US. + * @type string $w3c W3C locale. + * @type string $flag_code Code of the flag. + * @type int $term_group Order of the language when displayed in a list of languages. + * @type int $is_rtl `1` if the language is rtl, `0` otherwise. + * @type string $facebook Optional. Facebook locale. + * @type string $home_url Home URL in this language. + * @type string $search_url Home URL to use in search forms. + * @type string $host Host corresponding to this language. + * @type string $flag_url URL of the flag. + * @type string $flag HTML markup of the flag. + * @type string $custom_flag_url Optional. URL of the custom flag if it exists. + * @type string $custom_flag Optional. HTML markup of the custom flag if it exists. + * @type int $page_on_front Optional. ID of the page on front in this language. + * @type int $page_for_posts Optional. ID of the page for posts in this language. + * @type bool $active Whether or not the language is active. Default `true`. + * @type string[] $fallbacks List of WordPress language locales. Ex: array( 'en_GB' ). + * @type bool $is_default Whether or not the language is the default one. + * } + * + * @phpstan-param LanguageData $language_data + */ + public function __construct( array $language_data ) { + foreach ( $language_data as $prop => $value ) { + $this->$prop = $value; + } + + $this->term_id = $this->term_props['language']['term_id']; + } + + /** + * Returns a language term property value (term ID, term taxonomy ID, or count). + * + * @since 3.4 + * + * @param string $taxonomy_name Name of the taxonomy. + * @param string $prop_name Name of the property: 'term_taxonomy_id', 'term_id', 'count'. + * @return int + * + * @phpstan-param non-empty-string $taxonomy_name + * @phpstan-param 'term_taxonomy_id'|'term_id'|'count' $prop_name + * @phpstan-return int<0, max> + */ + public function get_tax_prop( $taxonomy_name, $prop_name ) { + return isset( $this->term_props[ $taxonomy_name ][ $prop_name ] ) ? $this->term_props[ $taxonomy_name ][ $prop_name ] : 0; + } + + /** + * Returns the language term props for all content types. + * + * @since 3.4 + * + * @param string $property Name of the field to return. An empty string to return them all. + * @return (int[]|int)[] Array keys are taxonomy names, array values depend of `$property`. + * + * @phpstan-param 'term_taxonomy_id'|'term_id'|'count'|'' $property + * @phpstan-return array : + * positive-int + * ) : + * LanguagePropData + * )> + */ + public function get_tax_props( $property = '' ) { + if ( empty( $property ) ) { + return $this->term_props; + } + + $term_props = array(); + + foreach ( $this->term_props as $taxonomy_name => $props ) { + $term_props[ $taxonomy_name ] = $props[ $property ]; + } + + return $term_props; + } + + /** + * Returns the flag information. + * + * @since 2.6 + * + * @param string $code Flag code. + * @return array { + * Flag information. + * + * @type string $url Flag url. + * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded. + * @type int $width Optional, flag width in pixels. + * @type int $height Optional, flag height in pixels. + * } + * + * @phpstan-return array{ + * url: string, + * src: string, + * width?: positive-int, + * height?: positive-int + * } + */ + public static function get_flag_informations( $code ) { + $default_flag = array( + 'url' => '', + 'src' => '', + ); + + // Polylang builtin flags. + if ( ! empty( $code ) && is_readable( POLYLANG_DIR . ( $file = '/flags/' . $code . '.png' ) ) ) { + $default_flag['url'] = plugins_url( $file, POLYLANG_FILE ); + + // If base64 encoded flags are preferred. + if ( pll_get_constant( 'PLL_ENCODED_FLAGS', true ) ) { + $imagesize = getimagesize( POLYLANG_DIR . $file ); + if ( is_array( $imagesize ) ) { + list( $default_flag['width'], $default_flag['height'] ) = $imagesize; + } + $file_contents = file_get_contents( POLYLANG_DIR . $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $default_flag['src'] = 'data:image/png;base64,' . base64_encode( $file_contents ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + } + } + + /** + * Filters flag information: + * + * @since 2.4 + * + * @param array $flag { + * Information about the flag. + * + * @type string $url Flag url. + * @type string $src Optional, src attribute value if different of the url, for example if base64 encoded. + * @type int $width Optional, flag width in pixels. + * @type int $height Optional, flag height in pixels. + * } + * @param string $code Flag code. + */ + $flag = apply_filters( 'pll_flag', $default_flag, $code ); + + $flag['url'] = esc_url_raw( $flag['url'] ); + + if ( empty( $flag['src'] ) || ( $flag['src'] === $default_flag['src'] && $flag['url'] !== $default_flag['url'] ) ) { + $flag['src'] = esc_url( set_url_scheme( $flag['url'], 'relative' ) ); + } + + return $flag; + } + + /** + * Returns HTML code for flag. + * + * @since 2.7 + * + * @param array $flag Flag properties: src, width and height. + * @param string $title Optional title attribute. + * @param string $alt Optional alt attribute. + * @return string + * + * @phpstan-param array{ + * src: string, + * width?: int|numeric-string, + * height?: int|numeric-string + * } $flag + */ + public static function get_flag_html( $flag, $title = '', $alt = '' ) { + if ( empty( $flag['src'] ) ) { + return ''; + } + + $alt_attr = empty( $alt ) ? '' : sprintf( ' alt="%s"', esc_attr( $alt ) ); + $width_attr = empty( $flag['width'] ) ? '' : sprintf( ' width="%s"', (int) $flag['width'] ); + $height_attr = empty( $flag['height'] ) ? '' : sprintf( ' height="%s"', (int) $flag['height'] ); + + $style = ''; + $sizes = array_intersect_key( $flag, array_flip( array( 'width', 'height' ) ) ); + + if ( ! empty( $sizes ) ) { + array_walk( + $sizes, + function ( &$value, $key ) { + $value = sprintf( '%s: %dpx;', esc_attr( $key ), (int) $value ); + } + ); + $style = sprintf( ' style="%s"', implode( ' ', $sizes ) ); + } + + return sprintf( + '', + $flag['src'], + $alt_attr, + $width_attr, + $height_attr, + $style + ); + } + + /** + * Returns the html of the custom flag if any, or the default flag otherwise. + * + * @since 2.8 + * @since 3.5.3 Added the `$alt` parameter. + * + * @param string $alt Whether or not the alternative text should be set. Accepts 'alt' and 'no-alt'. + * + * @return string + * + * @phpstan-param 'alt'|'no-alt' $alt + */ + public function get_display_flag( $alt = 'alt' ) { + $flag = empty( $this->custom_flag ) ? $this->flag : $this->custom_flag; + + if ( 'alt' === $alt ) { + return $flag; + } + + return (string) preg_replace( '/(?<=\salt=\")([^"]+)(?=\")/', '', $flag ); + } + + /** + * Returns the url of the custom flag if any, or the default flag otherwise. + * + * @since 2.8 + * + * @return string + */ + public function get_display_flag_url() { + $flag_url = empty( $this->custom_flag_url ) ? $this->flag_url : $this->custom_flag_url; + + /** + * Filters `flag_url` property. + * + * @since 3.4.4 + * + * @param string $flag_url Flag URL. + * @param PLL_Language $language Current `PLL_language` instance. + */ + return apply_filters( 'pll_language_flag_url', $flag_url, $this ); + } + + /** + * Updates post and term count. + * + * @since 1.2 + * + * @return void + */ + public function update_count() { + foreach ( $this->term_props as $taxonomy => $props ) { + wp_update_term_count( $props['term_taxonomy_id'], $taxonomy ); + } + } + + /** + * Returns the language locale. + * Converts WP locales to W3C valid locales for display. + * + * @since 1.8 + * + * @param string $filter Either 'display' or 'raw', defaults to raw. + * @return string + * + * @phpstan-param 'display'|'raw' $filter + * @phpstan-return non-empty-string + */ + public function get_locale( $filter = 'raw' ) { + return 'display' === $filter ? $this->w3c : $this->locale; + } + + /** + * Returns the values of this instance's properties, which can be filtered if required. + * + * @since 3.4 + * + * @param string $context Whether or not properties should be filtered. Accepts `db` or `display`. + * Default to `display` which filters some properties. + * + * @return array Array of language object properties. + * + * @phpstan-return LanguageData + */ + public function to_array( $context = 'display' ) { + $language = get_object_vars( $this ); + + if ( 'db' !== $context ) { + $language['home_url'] = $this->get_home_url(); + $language['search_url'] = $this->get_search_url(); + } + + /** @phpstan-var LanguageData $language */ + return $language; + } + + /** + * Converts current `PLL_language` into a `stdClass` object. Mostly used to allow dynamic properties. + * + * @since 3.4 + * + * @return stdClass Converted `PLL_Language` object. + */ + public function to_std_class() { + return (object) $this->to_array(); + } + + /** + * Returns a predefined HTML flag. + * + * @since 3.4 + * + * @param string $flag_code Flag code to render. + * @return string HTML code for the flag. + */ + public static function get_predefined_flag( $flag_code ) { + $flag = self::get_flag_informations( $flag_code ); + + return self::get_flag_html( $flag ); + } + + /** + * Returns language's home URL. Takes care to render it dynamically if no cache is allowed. + * + * @since 3.4 + * + * @return string Language home URL. + */ + public function get_home_url() { + if ( ! pll_get_constant( 'PLL_CACHE_LANGUAGES', true ) || ! pll_get_constant( 'PLL_CACHE_HOME_URL', true ) ) { + /** + * Filters current `PLL_Language` instance `home_url` property. + * + * @since 3.4.4 + * + * @param string $home_url The `home_url` prop. + * @param array $language Current Array of `PLL_Language` properties. + */ + return apply_filters( 'pll_language_home_url', $this->home_url, $this->to_array( 'db' ) ); + } + + return $this->home_url; + } + + /** + * Returns language's search URL. Takes care to render it dynamically if no cache is allowed. + * + * @since 3.4 + * + * @return string Language search URL. + */ + public function get_search_url() { + if ( ! pll_get_constant( 'PLL_CACHE_LANGUAGES', true ) || ! pll_get_constant( 'PLL_CACHE_HOME_URL', true ) ) { + /** + * Filters current `PLL_Language` instance `search_url` property. + * + * @since 3.4.4 + * + * @param string $search_url The `search_url` prop. + * @param array $language Current Array of `PLL_Language` properties. + */ + return apply_filters( 'pll_language_search_url', $this->search_url, $this->to_array( 'db' ) ); + } + + return $this->search_url; + } + + /** + * Returns the value of a language property. + * This is handy to get a property's value without worrying about triggering a deprecation warning or anything. + * + * @since 3.4 + * + * @param string $property A property name. A composite value can be used for language term property values, in the + * form of `{language_taxonomy_name}:{property_name}` (see {@see PLL_Language::get_tax_prop()} + * for the possible values). Ex: `term_language:term_taxonomy_id`. + * @return string|int|bool|string[] The requested property for the language, `false` if the property doesn't exist. + * + * @phpstan-return ( + * $property is 'slug' ? non-empty-string : string|int|bool|list + * ) + */ + public function get_prop( $property ) { + // Deprecated property. + if ( $this->is_deprecated_term_property( $property ) ) { + return $this->get_deprecated_term_property( $property ); + } + + if ( $this->is_deprecated_url_property( $property ) ) { + return $this->get_deprecated_url_property( $property ); + } + + // Composite property like 'term_language:term_taxonomy_id'. + if ( preg_match( '/^(?.{1,32}):(?term_id|term_taxonomy_id|count)$/', $property, $matches ) ) { + /** @var array{tax:non-empty-string, field:'term_id'|'term_taxonomy_id'|'count'} $matches */ + return $this->get_tax_prop( $matches['tax'], $matches['field'] ); + } + + // Any other public property. + if ( isset( $this->$property ) ) { + return $this->$property; + } + + return false; + } +} diff --git a/wp-content/plugins/polylang/include/license.php b/wp-content/plugins/polylang/include/license.php new file mode 100644 index 0000000000..94c3ed30bb --- /dev/null +++ b/wp-content/plugins/polylang/include/license.php @@ -0,0 +1,355 @@ +id = sanitize_title( $item_name ); + $this->file = $file; + $this->name = $item_name; + $this->version = $version; + $this->author = $author; + $this->api_url = empty( $api_url ) ? $this->api_url : $api_url; + + $licenses = (array) get_option( 'polylang_licenses', array() ); + $license = isset( $licenses[ $this->id ] ) && is_array( $licenses[ $this->id ] ) ? $licenses[ $this->id ] : array(); + $this->license_key = ! empty( $license['key'] ) ? (string) $license['key'] : ''; + + if ( ! empty( $license['data'] ) ) { + $this->license_data = (object) $license['data']; + } + + // Updater + $this->auto_updater(); + + // Register settings + add_filter( 'pll_settings_licenses', array( $this, 'settings' ) ); + + // Weekly schedule + if ( ! wp_next_scheduled( 'polylang_check_licenses' ) ) { + wp_schedule_event( time(), 'weekly', 'polylang_check_licenses' ); + } + + add_action( 'polylang_check_licenses', array( $this, 'check_license' ) ); + } + + /** + * Auto updater + * + * @since 1.9 + * + * @return void + */ + public function auto_updater() { + $args = array( + 'version' => $this->version, + 'license' => $this->license_key, + 'author' => $this->author, + 'item_name' => $this->name, + ); + + // Setup the updater + new PLL_Plugin_Updater( $this->api_url, $this->file, $args ); + } + + /** + * Registers the licence in the Settings. + * + * @since 1.9 + * + * @param PLL_License[] $items Array of objects allowing to manage a license. + * @return PLL_License[] + */ + public function settings( $items ) { + $items[ $this->id ] = $this; + return $items; + } + + /** + * Activates the license key. + * + * @since 1.9 + * + * @param string $license_key Activation key. + * @return PLL_License Updated PLL_License object. + */ + public function activate_license( $license_key ) { + $this->license_key = $license_key; + $this->api_request( 'activate_license' ); + + // Tell WordPress to look for updates. + delete_site_transient( 'update_plugins' ); + return $this; + } + + + /** + * Deactivates the license key. + * + * @since 1.9 + * + * @return PLL_License Updated PLL_License object. + */ + public function deactivate_license() { + $this->api_request( 'deactivate_license' ); + return $this; + } + + /** + * Checks if the license key is valid. + * + * @since 1.9 + * + * @return void + */ + public function check_license() { + $this->api_request( 'check_license' ); + } + + /** + * Sends an api request to check, activate or deactivate the license + * Updates the licenses option according to the status + * + * @since 1.9 + * + * @param string $request check_license | activate_license | deactivate_license + * @return void + */ + private function api_request( $request ) { + $licenses = get_option( 'polylang_licenses' ); + + if ( is_array( $licenses ) ) { + unset( $licenses[ $this->id ] ); + } else { + $licenses = array(); + } + unset( $this->license_data ); + + if ( ! empty( $this->license_key ) ) { + // Data to send in our API request + $api_params = array( + 'edd_action' => $request, + 'license' => $this->license_key, + 'item_name' => urlencode( $this->name ), + 'url' => home_url(), + ); + + // Call the API + $response = wp_remote_post( + $this->api_url, + array( + 'timeout' => 3, + 'sslverify' => false, + 'body' => $api_params, + ) + ); + + // Update the option only if we got a response + if ( is_wp_error( $response ) ) { + return; + } + + // Save new license info + $licenses[ $this->id ] = array( 'key' => $this->license_key ); + $data = (object) json_decode( wp_remote_retrieve_body( $response ) ); + + if ( isset( $data->license ) && 'deactivated' !== $data->license ) { + $licenses[ $this->id ]['data'] = $this->license_data = $data; + } + } + + update_option( 'polylang_licenses', $licenses ); // FIXME called multiple times when saving all licenses + } + + /** + * Get the html form field in a table row (one per license key) for display + * + * @since 2.7 + * + * @return string + */ + public function get_form_field() { + if ( ! empty( $this->license_data ) ) { + $license = $this->license_data; + } + + $class = 'license-null'; + $message = ''; + + $out = sprintf( + '' . + '', + esc_attr( $this->id ), + esc_attr( $this->name ), + esc_html( $this->license_key ) + ); + + if ( ! empty( $license ) && is_object( $license ) ) { + $now = time(); + $expiration = isset( $license->expires ) ? strtotime( $license->expires ) : false; + + // Special case: the license expired after the last check + if ( $license->success && $expiration && $expiration < $now ) { + $license->success = false; + $license->error = 'expired'; + } + + if ( false === $license->success ) { + $class = 'notice-error notice-alt'; + + switch ( $license->error ) { + case 'expired': + $message = sprintf( + /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */ + esc_html__( 'Your license key expired on %1$s. Please %2$srenew your license key%3$s.', 'polylang' ), + esc_html( date_i18n( get_option( 'date_format' ), $expiration ) ), + sprintf( '
', 'https://polylang.pro/account/' ), + '' + ); + break; + + case 'disabled': + case 'revoked': + $message = esc_html__( 'Your license key has been disabled.', 'polylang' ); + break; + + case 'missing': + $message = sprintf( + /* translators: %1$s is link start tag, %2$s is link end tag. */ + esc_html__( 'Invalid license. Please %1$svisit your account page%2$s and verify it.', 'polylang' ), + sprintf( '', 'https://polylang.pro/account/' ), + '' + ); + break; + + case 'invalid': + case 'site_inactive': + $message = sprintf( + /* translators: %1$s is a product name, %2$s is link start tag, %3$s is link end tag. */ + esc_html__( 'Your %1$s license key is not active for this URL. Please %2$svisit your account page%3$s to manage your license key URLs.', 'polylang' ), + esc_html( $this->name ), + sprintf( '', 'https://polylang.pro/account/' ), + '' + ); + break; + + case 'item_name_mismatch': + /* translators: %s is a product name */ + $message = sprintf( esc_html__( 'This is not a %s license key.', 'polylang' ), esc_html( $this->name ) ); + break; + + case 'no_activations_left': + $message = sprintf( + /* translators: %1$s is link start tag, %2$s is link end tag */ + esc_html__( 'Your license key has reached its activation limit. %1$sView possible upgrades%2$s now.', 'polylang' ), + sprintf( '', 'https://polylang.pro/account/' ), + '' + ); + break; + } + } else { + $class = 'license-valid'; + + $out .= sprintf( '', esc_attr( $this->id ), esc_html__( 'Deactivate', 'polylang' ) ); + + if ( 'lifetime' === $license->expires ) { + $message = esc_html__( 'The license key never expires.', 'polylang' ); + } elseif ( $expiration > $now && $expiration - $now < ( DAY_IN_SECONDS * 30 ) ) { + $class = 'notice-warning notice-alt'; + $message = sprintf( + /* translators: %1$s is a date, %2$s is link start tag, %3$s is link end tag. */ + esc_html__( 'Your license key will expire soon! Precisely, it will expire on %1$s. %2$sRenew your license key today!%3$s', 'polylang' ), + esc_html( date_i18n( get_option( 'date_format' ), $expiration ) ), + sprintf( '', 'https://polylang.pro/account/' ), + '' + ); + } else { + $message = sprintf( + /* translators: %s is a date */ + esc_html__( 'Your license key expires on %s.', 'polylang' ), + esc_html( date_i18n( get_option( 'date_format' ), $expiration ) ) + ); + } + } + } + + if ( ! empty( $message ) ) { + $out .= '

' . $message . '

'; + } + + return sprintf( '%s', esc_attr( $this->id ), $class, $out ); + } +} diff --git a/wp-content/plugins/polylang/include/links-abstract-domain.php b/wp-content/plugins/polylang/include/links-abstract-domain.php new file mode 100644 index 0000000000..d17fa5e25c --- /dev/null +++ b/wp-content/plugins/polylang/include/links-abstract-domain.php @@ -0,0 +1,114 @@ +get_hosts() ); + + return is_string( $lang ) ? $lang : ''; + } + + /** + * Modifies an url to use the domain associated to the current language. + * + * @since 1.8 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function site_url( $url ) { + $lang = $this->get_language_from_url(); + + $lang = $this->model->get_language( $lang ); + + return $this->add_language_to_link( $url, $lang ); + } + + /** + * Fixes the domain for the upload directory. + * + * @since 2.0.6 + * + * @param array $uploads Array of information about the upload directory. @see wp_upload_dir(). + * @return array + */ + public function upload_dir( $uploads ) { + $lang = $this->get_language_from_url(); + $lang = $this->model->get_language( $lang ); + $uploads['url'] = $this->add_language_to_link( $uploads['url'], $lang ); + $uploads['baseurl'] = $this->add_language_to_link( $uploads['baseurl'], $lang ); + return $uploads; + } + + /** + * Adds home and search URLs to language data before the object is created. + * + * @since 3.4.1 + * + * @param array $additional_data Array of language additional data. + * @param array $language Language data. + * @return array Language data with home and search URLs added. + */ + public function set_language_home_urls( $additional_data, $language ) { + $language = array_merge( $language, $additional_data ); + $additional_data['search_url'] = $this->home_url( $language['slug'] ); + $additional_data['home_url'] = $additional_data['search_url']; + return $additional_data; + } + + /** + * Returns language home URL property according to the current domain. + * + * @since 3.4.4 + * + * @param string $url Home URL. + * @param array $language Array of language props. + * @return string Filtered home URL. + */ + public function set_language_home_url( $url, $language ) { + return $this->home_url( $language['slug'] ); + } +} diff --git a/wp-content/plugins/polylang/include/links-default.php b/wp-content/plugins/polylang/include/links-default.php new file mode 100644 index 0000000000..687715f341 --- /dev/null +++ b/wp-content/plugins/polylang/include/links-default.php @@ -0,0 +1,116 @@ +slug; + } + + return empty( $language ) || ( $this->options['hide_default'] && $this->options['default_lang'] === $language ) ? $url : add_query_arg( 'lang', $language, $url ); + } + + /** + * Removes the language information from an url. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_language_from_link( $url ) { + return remove_query_arg( 'lang', $url ); + } + + /** + * Returns the link to the first page. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_paged_from_link( $url ) { + return remove_query_arg( 'paged', $url ); + } + + /** + * Returns the link to the paged page. + * + * @since 1.5 + * + * @param string $url The url to modify. + * @param int $page The page number. + * @return string The modified url. + */ + public function add_paged_to_link( $url, $page ) { + return add_query_arg( array( 'paged' => $page ), $url ); + } + + /** + * Gets the language slug from the url if present. + * + * @since 1.2 + * @since 2.0 Add the $url argument. + * + * @param string $url Optional, defaults to the current url. + * @return string Language slug. + */ + public function get_language_from_url( $url = '' ) { + if ( empty( $url ) ) { + $url = pll_get_requested_url(); + } + + $pattern = sprintf( + '#[?&]lang=(?%s)(?:$|&)#', + implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) + ); + return preg_match( $pattern, $url, $matches ) ? $matches['lang'] : ''; // $matches['lang'] is the slug of the requested language. + } + + /** + * Returns the static front page url in the given language. + * + * @since 1.8 + * @since 3.4 Accepts now an array of language properties. + * + * @param PLL_Language|array $language Language object or array of language properties. + * @return string The static front page url. + */ + public function front_page_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->to_array(); + } + + if ( $this->options['hide_default'] && $language['is_default'] ) { + return trailingslashit( $this->home ); + } + $url = home_url( '/?page_id=' . $language['page_on_front'] ); + return $this->options['force_lang'] ? $this->add_language_to_link( $url, $language['slug'] ) : $url; + } +} diff --git a/wp-content/plugins/polylang/include/links-directory.php b/wp-content/plugins/polylang/include/links-directory.php new file mode 100644 index 0000000000..7ffd6360d7 --- /dev/null +++ b/wp-content/plugins/polylang/include/links-directory.php @@ -0,0 +1,295 @@ +home_relative = home_url( '/', 'relative' ); + } + + /** + * Adds hooks for rewrite rules. + * + * @since 1.6 + * + * @return void + */ + public function init() { + add_action( 'pll_prepare_rewrite_rules', array( $this, 'prepare_rewrite_rules' ) ); // Ensure it's hooked before `self::do_prepare_rewrite_rules()` is called. + + parent::init(); + } + + /** + * Adds the language code in a url. + * + * @since 1.2 + * @since 3.4 Accepts now a language slug. + * + * @param string $url The url to modify. + * @param PLL_Language|string|false $language Language object or slug. + * @return string The modified url. + */ + public function add_language_to_link( $url, $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + if ( ! empty( $language ) ) { + $base = $this->options['rewrite'] ? '' : 'language/'; + $slug = $this->options['default_lang'] === $language && $this->options['hide_default'] ? '' : $base . $language . '/'; + $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root ); + + if ( false === strpos( $url, $new = $root . $slug ) ) { + $pattern = preg_quote( $root, '#' ); + $pattern = '#' . $pattern . '#'; + return preg_replace( $pattern, $new, $url, 1 ); // Only once. + } + } + return $url; + } + + /** + * Returns the url without the language code. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_language_from_link( $url ) { + $languages = $this->model->get_languages_list( + array( + 'hide_default' => $this->options['hide_default'], + 'fields' => 'slug', + ) + ); + + if ( ! empty( $languages ) ) { + $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : preg_replace( '#^https?://#', '://', $this->home . '/' . $this->root ); + + $pattern = preg_quote( $root, '@' ); + $pattern = '@' . $pattern . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')(([?#])|(/|$))@'; + $url = preg_replace( $pattern, $root . '$3', $url ); + } + return $url; + } + + /** + * Returns the language based on the language code in the url. + * + * @since 1.2 + * @since 2.0 Add the $url argument. + * + * @param string $url Optional, defaults to the current url. + * @return string The language slug. + */ + public function get_language_from_url( $url = '' ) { + if ( empty( $url ) ) { + $url = pll_get_requested_url(); + } + + $path = (string) wp_parse_url( $url, PHP_URL_PATH ); + $root = ( false === strpos( $url, '://' ) ) ? $this->home_relative . $this->root : $this->home . '/' . $this->root; + + $pattern = (string) wp_parse_url( $root . ( $this->options['rewrite'] ? '' : 'language/' ), PHP_URL_PATH ); + $pattern = preg_quote( $pattern, '#' ); + $pattern = '#^' . $pattern . '(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')(/|$)#'; + return preg_match( $pattern, trailingslashit( $path ), $matches ) ? $matches[1] : ''; // $matches[1] is the slug of the requested language. + } + + /** + * Returns the home url in a given language. + * + * @since 1.3.1 + * @since 3.4 Accepts now a language slug. + * + * @param PLL_Language|string $language Language object or slug. + * @return string + */ + public function home_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + $base = $this->options['rewrite'] ? '' : 'language/'; + $slug = $this->options['default_lang'] === $language && $this->options['hide_default'] ? '' : '/' . $this->root . $base . $language; + return trailingslashit( $this->home . $slug ); + } + + /** + * Prepares the rewrite rules filters. + * + * @since 0.8.1 + * @since 3.5 Hooked to `pll_prepare_rewrite_rules` and remove `$pre` parameter. + * + * @return void + */ + public function prepare_rewrite_rules() { + /* + * Don't modify the rules if there is no languages created yet and make sure + * to add the filters only once and if all custom post types and taxonomies + * have been registered. + */ + if ( ! $this->model->has_languages() || ! did_action( 'wp_loaded' ) || ! self::$can_filter_rewrite_rules ) { + return; + } + + foreach ( $this->get_rewrite_rules_filters_with_callbacks() as $rule => $callback ) { + add_filter( $rule, $callback ); + } + } + + /** + * The rewrite rules ! + * + * Always make sure that the default language is at the end in case the language information is hidden for default language. + * Thanks to brbrbr http://wordpress.org/support/topic/plugin-polylang-rewrite-rules-not-correct. + * + * @since 0.8.1 + * + * @param string[] $rules Rewrite rules. + * @return string[] Modified rewrite rules. + */ + public function rewrite_rules( $rules ) { + $filter = str_replace( '_rewrite_rules', '', current_filter() ); + + global $wp_rewrite; + $newrules = array(); + + $languages = $this->model->get_languages_list( array( 'fields' => 'slug' ) ); + if ( $this->options['hide_default'] ) { + $languages = array_diff( $languages, array( $this->options['default_lang'] ) ); + } + + if ( ! empty( $languages ) ) { + $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '' : 'language/' ) . '(' . implode( '|', $languages ) . ')/'; + } + + // For custom post type archives. + $cpts = array_intersect( $this->model->get_translated_post_types(), get_post_types( array( '_builtin' => false ) ) ); + $cpts = $cpts ? '#post_type=(' . implode( '|', $cpts ) . ')#' : ''; + + foreach ( $rules as $key => $rule ) { + if ( ! is_string( $rule ) || ! is_string( $key ) ) { + // Protection against a bug in Sendinblue for WooCommerce. See: https://wordpress.org/support/topic/bug-introduced-in-rewrite-rules/ + continue; + } + + // Special case for translated post types and taxonomies to allow canonical redirection. + if ( $this->options['force_lang'] && in_array( $filter, array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies() ) ) ) { + + /** + * Filters the rewrite rules to modify. + * + * @since 1.9.1 + * + * @param bool $modify Whether to modify or not the rule, defaults to true. + * @param array $rule Original rewrite rule. + * @param string $filter Current set of rules being modified. + * @param string|bool $archive Custom post post type archive name or false if it is not a cpt archive. + */ + if ( isset( $slug ) && apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, false ) ) { + $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace( + array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ), + array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ), + $rule + ); // Should be enough! + } + + $newrules[ $key ] = $rule; + } + + // Rewrite rules filtered by language. + elseif ( in_array( $filter, $this->always_rewrite ) || in_array( $filter, $this->model->get_filtered_taxonomies() ) || ( $cpts && preg_match( $cpts, $rule, $matches ) && ! strpos( $rule, 'name=' ) ) || ( 'rewrite_rules_array' != $filter && $this->options['force_lang'] ) ) { + + /** This filter is documented in include/links-directory.php */ + if ( apply_filters( 'pll_modify_rewrite_rule', true, array( $key => $rule ), $filter, empty( $matches[1] ) ? false : $matches[1] ) ) { + if ( isset( $slug ) ) { + $newrules[ $slug . str_replace( $wp_rewrite->root, '', ltrim( $key, '^' ) ) ] = str_replace( + array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ), + array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ), + $rule + ); // Should be enough! + } + + if ( $this->options['hide_default'] ) { + $newrules[ $key ] = str_replace( '?', '?lang=' . $this->options['default_lang'] . '&', $rule ); + } + } else { + $newrules[ $key ] = $rule; + } + } + + // Unmodified rules. + else { + $newrules[ $key ] = $rule; + } + } + + // The home rewrite rule. + if ( 'root' == $filter && isset( $slug ) ) { + $newrules[ $slug . '?$' ] = $wp_rewrite->index . '?lang=$matches[1]'; + } + + return $newrules; + } + + /** + * Removes hooks to filter rewrite rules, called when switching blog @see {PLL_Base::switch_blog()}. + * See `self::prepare_rewrite_rules()` for added hooks. + * + * @since 3.5 + * + * @return void + */ + public function remove_filters() { + parent::remove_filters(); + + foreach ( $this->get_rewrite_rules_filters_with_callbacks() as $rule => $callback ) { + remove_filter( $rule, $callback ); + } + } + + /** + * Returns *all* rewrite rules filters with their associated callbacks. + * + * @since 3.5 + * + * @return callable[] Array of hook names as key and callbacks as values. + */ + protected function get_rewrite_rules_filters_with_callbacks() { + $filters = array( + 'rewrite_rules_array' => array( $this, 'rewrite_rules' ), // Needed for post type archives. + ); + + foreach ( $this->get_rewrite_rules_filters() as $type ) { + $filters[ $type . '_rewrite_rules' ] = array( $this, 'rewrite_rules' ); + } + + return $filters; + } +} diff --git a/wp-content/plugins/polylang/include/links-domain.php b/wp-content/plugins/polylang/include/links-domain.php new file mode 100644 index 0000000000..e8b7805fdc --- /dev/null +++ b/wp-content/plugins/polylang/include/links-domain.php @@ -0,0 +1,121 @@ +hosts = $this->get_hosts(); + + // Filters the site url (mainly to get the correct login form). + add_filter( 'site_url', array( $this, 'site_url' ) ); + } + + + /** + * Switches the primary domain to a secondary domain in the url. + * + * @since 1.2 + * @since 3.4 Accepts now a language slug. + * + * @param string $url The url to modify. + * @param PLL_Language|string|false $language Language object or slug. + * @return string The modified url. + */ + public function add_language_to_link( $url, $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + if ( ! empty( $language ) && ! empty( $this->hosts[ $language ] ) ) { + $url = preg_replace( '#://(' . wp_parse_url( $this->home, PHP_URL_HOST ) . ')($|/.*)#', '://' . $this->hosts[ $language ] . '$2', $url ); + } + return $url; + } + + /** + * Returns the url with the primary domain. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_language_from_link( $url ) { + if ( ! empty( $this->hosts ) ) { + $url = preg_replace( '#://(' . implode( '|', $this->hosts ) . ')($|/.*)#', '://' . wp_parse_url( $this->home, PHP_URL_HOST ) . '$2', $url ); + } + return $url; + } + + /** + * Returns the home url in a given language. + * + * @since 1.3.1 + * @since 3.4 Accepts now a language slug. + * + * @param PLL_Language|string $language Language object or slug. + * @return string + */ + public function home_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + return trailingslashit( empty( $this->options['domains'][ $language ] ) ? $this->home : $this->options['domains'][ $language ] ); + } + + /** + * Get the hosts managed on the website. + * + * @since 1.5 + * + * @return string[] List of hosts. + */ + public function get_hosts() { + $hosts = array(); + foreach ( $this->options['domains'] as $lang => $domain ) { + $host = wp_parse_url( $domain, PHP_URL_HOST ); + + if ( ! is_string( $host ) ) { + continue; + } + + // The function idn_to_ascii() is much faster than the WordPress method. + if ( function_exists( 'idn_to_ascii' ) && defined( 'INTL_IDNA_VARIANT_UTS46' ) ) { + $hosts[ $lang ] = idn_to_ascii( $host, 0, INTL_IDNA_VARIANT_UTS46 ); + } elseif ( class_exists( 'WpOrg\Requests\IdnaEncoder' ) ) { + // Since WP 6.2. + $hosts[ $lang ] = \WpOrg\Requests\IdnaEncoder::encode( $host ); + } else { + // Backward compatibility with WP < 6.2. + $hosts[ $lang ] = Requests_IDNAEncoder::encode( $host ); + } + } + + return $hosts; + } +} diff --git a/wp-content/plugins/polylang/include/links-model.php b/wp-content/plugins/polylang/include/links-model.php new file mode 100644 index 0000000000..ae8602b80a --- /dev/null +++ b/wp-content/plugins/polylang/include/links-model.php @@ -0,0 +1,264 @@ +model = &$model; + $this->options = &$model->options; + + $this->home = home_url(); + + // Hooked with normal priority because it needs to be run after static pages is set in language data. Must be done early (before languages objects are created). + add_filter( 'pll_additional_language_data', array( $this, 'set_language_home_urls' ), 10, 2 ); + + // Adds our domains or subdomains to allowed hosts for safe redirection. + add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) ); + + // Allows secondary domains for home and search URLs in `PLL_Language`. + add_filter( 'pll_language_home_url', array( $this, 'set_language_home_url' ), 10, 2 ); + add_filter( 'pll_language_search_url', array( $this, 'set_language_search_url' ), 10, 2 ); + + if ( did_action( 'pll_init' ) ) { + $this->init(); + } else { + add_action( 'pll_init', array( $this, 'init' ) ); + } + } + + /** + * Initializes the links model. + * Does nothing by default. + * + * @since 3.5 + * + * @return void + */ + public function init() {} + + /** + * Adds the language code in url. + * + * @since 1.2 + * @since 3.4 Accepts now a language slug. + * + * @param string $url The url to modify. + * @param PLL_Language|string|false $lang Language object or slug. + * @return string The modified url. + */ + abstract public function add_language_to_link( $url, $lang ); + + /** + * Returns the url without language code. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + abstract public function remove_language_from_link( $url ); + + /** + * Returns the link to the first page. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + abstract public function remove_paged_from_link( $url ); + + /** + * Returns the link to a paged page. + * + * @since 1.5 + * + * @param string $url The url to modify. + * @param int $page The page number. + * @return string The modified url. + */ + abstract public function add_paged_to_link( $url, $page ); + + /** + * Returns the language based on the language code in the url. + * + * @since 1.2 + * @since 2.0 Add the $url argument. + * + * @param string $url Optional, defaults to the current url. + * @return string The language slug. + */ + abstract public function get_language_from_url( $url = '' ); + + /** + * Returns the static front page url in a given language. + * + * @since 1.8 + * @since 3.4 Accepts now an array of language properties. + * + * @param PLL_Language|array $language Language object or array of language properties. + * @return string The static front page url. + */ + abstract public function front_page_url( $language ); + + /** + * Changes the language code in url. + * + * @since 1.5 + * + * @param string $url The url to modify. + * @param PLL_Language $lang The language object. + * @return string The modified url. + */ + public function switch_language_in_link( $url, $lang ) { + $url = $this->remove_language_from_link( $url ); + return $this->add_language_to_link( $url, $lang ); + } + + /** + * Get the hosts managed on the website. + * + * @since 1.5 + * + * @return string[] The list of hosts. + */ + public function get_hosts() { + return array( wp_parse_url( $this->home, PHP_URL_HOST ) ); + } + + /** + * Returns the home url in a given language. + * + * @since 1.3.1 + * @since 3.4 Accepts now a language slug. + * + * @param PLL_Language|string $language Language object or slug. + * @return string + */ + public function home_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + $url = trailingslashit( $this->home ); + + return $this->options['hide_default'] && $language === $this->options['default_lang'] ? $url : $this->add_language_to_link( $url, $language ); + } + + /** + * Adds home and search URLs to language data before the object is created. + * + * @since 3.4 + * + * @param array $additional_data Array of language additional data. + * @param array $language Language data. + * @return array Language data with home and search URLs added. + */ + public function set_language_home_urls( $additional_data, $language ) { + $language = array_merge( $language, $additional_data ); + $additional_data['search_url'] = $this->set_language_search_url( '', $language ); + $additional_data['home_url'] = $this->set_language_home_url( '', $language ); + + return $additional_data; + } + + /** + * Adds our domains or subdomains to allowed hosts for safe redirect. + * + * @since 1.4.3 + * + * @param string[] $hosts Allowed hosts. + * @return string[] Modified list of allowed hosts. + */ + public function allowed_redirect_hosts( $hosts ) { + return array_unique( array_merge( $hosts, array_values( $this->get_hosts() ) ) ); + } + + /** + * Returns language home URL property according to the current domain. + * + * @since 3.4.4 + * + * @param string $url Home URL. + * @param array $language Array of language props. + * @return string Filtered home URL. + */ + public function set_language_home_url( $url, $language ) { + if ( empty( $language['page_on_front'] ) || $this->options['redirect_lang'] ) { + return $this->home_url( $language['slug'] ); + } + + return $this->front_page_url( $language ); + } + + /** + * Returns language search URL property according to the current domain. + * + * @since 3.4.4 + * + * @param string $url Search URL. + * @param array $language Array of language props. + * @return string Filtered search URL. + */ + public function set_language_search_url( $url, $language ) { + return $this->home_url( $language['slug'] ); + } + + /** + * Used to remove hooks in child classes, called when switching blog @see {PLL_Base::switch_blog()}. + * Does nothing by default. + * + * @since 3.5 + * + * @return void + */ + public function remove_filters() { + self::$can_filter_rewrite_rules = false; + } +} diff --git a/wp-content/plugins/polylang/include/links-permalinks.php b/wp-content/plugins/polylang/include/links-permalinks.php new file mode 100644 index 0000000000..c79f9dd5d2 --- /dev/null +++ b/wp-content/plugins/polylang/include/links-permalinks.php @@ -0,0 +1,219 @@ +root = preg_match( '#^/*' . $this->index . '#', $permalink_structure ) ? $this->index . '/' : ''; + $this->use_trailing_slashes = ( '/' == substr( $permalink_structure, -1, 1 ) ); + } + + /** + * Initializes permalinks. + * + * @since 3.5 + * + * @return void + */ + public function init() { + parent::init(); + + if ( did_action( 'wp_loaded' ) ) { + $this->do_prepare_rewrite_rules(); + } else { + add_action( 'wp_loaded', array( $this, 'do_prepare_rewrite_rules' ), 9 ); // Just before WordPress callback `WP_Rewrite::flush_rules()`. + } + } + + /** + * Fires our own action telling Polylang plugins + * and third parties are able to prepare rewrite rules. + * + * @since 3.5 + * + * @return void + */ + public function do_prepare_rewrite_rules() { + self::$can_filter_rewrite_rules = true; + + /** + * Tells when Polylang is able to prepare rewrite rules filters. + * Action fired right after `wp_loaded` and just before WordPress `WP_Rewrite::flush_rules()` callback. + * + * @since 3.5 + * + * @param PLL_Links_Permalinks $links Current links object. + */ + do_action( 'pll_prepare_rewrite_rules', $this ); + } + + /** + * Returns the link to the first page when using pretty permalinks. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_paged_from_link( $url ) { + /** + * Filters an url after the paged part has been removed. + * + * @since 2.0.6 + * + * @param string $modified_url The link to the first page. + * @param string $original_url The link to the original paged page. + */ + return apply_filters( 'pll_remove_paged_from_link', preg_replace( '#/page/[0-9]+/?#', $this->use_trailing_slashes ? '/' : '', $url ), $url ); + } + + /** + * Returns the link to the paged page when using pretty permalinks. + * + * @since 1.5 + * + * @param string $url The url to modify. + * @param int $page The page number. + * @return string The modified url. + */ + public function add_paged_to_link( $url, $page ) { + /** + * Filters an url after the paged part has been added. + * + * @since 2.0.6 + * + * @param string $modified_url The link to the paged page. + * @param string $original_url The link to the original first page. + * @param int $page The page number. + */ + return apply_filters( 'pll_add_paged_to_link', user_trailingslashit( trailingslashit( $url ) . 'page/' . $page, 'paged' ), $url, $page ); + } + + /** + * Returns the home url in a given language. + * + * @since 1.3.1 + * @since 3.4 Accepts now a language slug. + * + * @param PLL_Language|string $language Language object or slug. + * @return string + */ + public function home_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + return trailingslashit( parent::home_url( $language ) ); + } + + /** + * Returns the static front page url. + * + * @since 1.8 + * @since 3.4 Accepts now an array of language properties. + * + * @param PLL_Language|array $language Language object or array of language properties. + * @return string The static front page url. + */ + public function front_page_url( $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->to_array(); + } + + if ( $this->options['hide_default'] && $language['is_default'] ) { + return trailingslashit( $this->home ); + } + $url = home_url( $this->root . get_page_uri( $language['page_on_front'] ) ); + $url = $this->use_trailing_slashes ? trailingslashit( $url ) : untrailingslashit( $url ); + return $this->options['force_lang'] ? $this->add_language_to_link( $url, $language['slug'] ) : $url; + } + + /** + * Prepares rewrite rules filters. + * + * @since 1.6 + * + * @return string[] + */ + public function get_rewrite_rules_filters() { + // Make sure that we have the right post types and taxonomies. + $types = array_values( array_merge( $this->model->get_translated_post_types(), $this->model->get_translated_taxonomies(), $this->model->get_filtered_taxonomies() ) ); + $types = array_merge( $this->always_rewrite, $types ); + + /** + * Filters the list of rewrite rules filters to be used by Polylang. + * + * @since 0.8.1 + * + * @param array $types The list of filters (without '_rewrite_rules' at the end). + */ + return apply_filters( 'pll_rewrite_rules', $types ); + } + + /** + * Removes hooks to filter rewrite rules, called when switching blog @see {PLL_Base::switch_blog()}. + * + * @since 3.5 + * + * @return void + */ + public function remove_filters() { + parent::remove_filters(); + + remove_all_actions( 'pll_prepare_rewrite_rules' ); + } +} diff --git a/wp-content/plugins/polylang/include/links-subdomain.php b/wp-content/plugins/polylang/include/links-subdomain.php new file mode 100644 index 0000000000..5958ac2364 --- /dev/null +++ b/wp-content/plugins/polylang/include/links-subdomain.php @@ -0,0 +1,92 @@ +www = ( false === strpos( $this->home, '://www.' ) ) ? '://' : '://www.'; + } + + /** + * Adds the language code in a url. + * + * @since 1.2 + * @since 3.4 Accepts now a language slug. + * + * @param string $url The url to modify. + * @param PLL_Language|string|false $language Language object or slug. + * @return string The modified url. + */ + public function add_language_to_link( $url, $language ) { + if ( $language instanceof PLL_Language ) { + $language = $language->slug; + } + + if ( ! empty( $language ) && false === strpos( $url, '://' . $language . '.' ) ) { + $url = $this->options['default_lang'] === $language && $this->options['hide_default'] ? $url : str_replace( $this->www, '://' . $language . '.', $url ); + } + return $url; + } + + /** + * Returns the url without the language code. + * + * @since 1.2 + * + * @param string $url The url to modify. + * @return string The modified url. + */ + public function remove_language_from_link( $url ) { + $languages = $this->model->get_languages_list( + array( + 'hide_default' => $this->options['hide_default'], + 'fields' => 'slug', + ) + ); + + if ( ! empty( $languages ) ) { + $url = preg_replace( '#://(' . implode( '|', $languages ) . ')\.#', $this->www, $url ); + } + + return $url; + } + + /** + * Get the hosts managed on the website. + * + * @since 1.5 + * + * @return string[] The list of hosts. + */ + public function get_hosts() { + $hosts = array(); + foreach ( $this->model->get_languages_list() as $lang ) { + $host = wp_parse_url( $this->home_url( $lang ), PHP_URL_HOST ); + $hosts[ $lang->slug ] = $host ? $host : ''; + } + return $hosts; + } +} diff --git a/wp-content/plugins/polylang/include/links.php b/wp-content/plugins/polylang/include/links.php new file mode 100644 index 0000000000..c026d423eb --- /dev/null +++ b/wp-content/plugins/polylang/include/links.php @@ -0,0 +1,71 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + } + + /** + * Returns the home url in the requested language. + * + * @since 1.3 + * + * @param PLL_Language|string $language The language. + * @param bool $is_search Optional, whether we need the home url for a search form, defaults to false. + * @return string + */ + public function get_home_url( $language, $is_search = false ) { + if ( ! $language instanceof PLL_Language ) { + $language = $this->model->get_language( $language ); + } + + if ( empty( $language ) ) { + return home_url( '/' ); + } + + return $is_search ? $language->get_search_url() : $language->get_home_url(); + } +} diff --git a/wp-content/plugins/polylang/include/mo.php b/wp-content/plugins/polylang/include/mo.php new file mode 100644 index 0000000000..6900f86673 --- /dev/null +++ b/wp-content/plugins/polylang/include/mo.php @@ -0,0 +1,77 @@ +entries as $entry ) { + if ( '' !== $entry->singular ) { + $strings[] = wp_slash( array( $entry->singular, $this->translate( $entry->singular ) ) ); + } + } + + update_term_meta( $lang->term_id, '_pll_strings_translations', $strings ); + } + + /** + * Reads a PLL_MO object from the term meta. + * + * @since 1.2 + * @since 3.4 Reads a PLL_MO from the term meta. + * + * @param PLL_Language $lang The language in which we want to get strings. + * @return void + */ + public function import_from_db( $lang ) { + $this->set_header( 'Language', $lang->slug ); + + $strings = get_term_meta( $lang->term_id, '_pll_strings_translations', true ); + if ( empty( $strings ) || ! is_array( $strings ) ) { + return; + } + + foreach ( $strings as $msg ) { + $entry = $this->make_entry( $msg[0], $msg[1] ); + + if ( '' !== $entry->singular ) { + $this->add_entry( $entry ); + } + } + } + + /** + * Deletes a string + * + * @since 2.9 + * + * @param string $string The source string to remove from the translations. + * @return void + */ + public function delete_entry( $string ) { + unset( $this->entries[ $string ] ); + } +} diff --git a/wp-content/plugins/polylang/include/model.php b/wp-content/plugins/polylang/include/model.php new file mode 100644 index 0000000000..e8cfabfb83 --- /dev/null +++ b/wp-content/plugins/polylang/include/model.php @@ -0,0 +1,1050 @@ + + */ + public $cache; + + /** + * Stores the plugin options. + * + * @var array + */ + public $options; + + /** + * Translatable objects registry. + * + * @since 3.4 + * + * @var PLL_Translatable_Objects + */ + public $translatable_objects; + + /** + * Translated post model. + * + * @var PLL_Translated_Post + */ + public $post; + + /** + * Translated term model. + * + * @var PLL_Translated_Term + */ + public $term; + + /** + * Flag set to true during the language objects creation. + * + * @var bool + */ + private $is_creating_language_objects = false; + + /** + * Tells if {@see PLL_Model::get_languages_list()} can be used. + * + * @var bool + */ + private $languages_ready = false; + + /** + * Constructor. + * Setups translated objects sub models. + * Setups filters and actions. + * + * @since 1.2 + * + * @param array $options Polylang options. + */ + public function __construct( &$options ) { + $this->options = &$options; + + $this->cache = new PLL_Cache(); + $this->translatable_objects = new PLL_Translatable_Objects(); + $this->post = $this->translatable_objects->register( new PLL_Translated_Post( $this ) ); // Translated post sub model. + $this->term = $this->translatable_objects->register( new PLL_Translated_Term( $this ) ); // Translated term sub model. + + // We need to clean languages cache when editing a language and when modifying the permalink structure. + add_action( 'edited_term_taxonomy', array( $this, 'clean_languages_cache' ), 10, 2 ); + add_action( 'update_option_permalink_structure', array( $this, 'clean_languages_cache' ) ); + add_action( 'update_option_siteurl', array( $this, 'clean_languages_cache' ) ); + add_action( 'update_option_home', array( $this, 'clean_languages_cache' ) ); + + add_filter( 'get_terms_args', array( $this, 'get_terms_args' ) ); + + // Just in case someone would like to display the language description ;). + add_filter( 'language_description', '__return_empty_string' ); + } + + /** + * Checks if there are languages or not. + * + * @since 3.3 + * + * @return bool True if there are, false otherwise. + */ + public function has_languages() { + if ( ! empty( $this->cache->get( 'languages' ) ) ) { + return true; + } + + if ( ! empty( get_transient( 'pll_languages_list' ) ) ) { + return true; + } + + return ! empty( $this->get_language_terms() ); + } + + /** + * Returns the list of available languages. + * - Stores the list in a db transient (except flags), unless `PLL_CACHE_LANGUAGES` is set to false. + * - Caches the list (with flags) in a `PLL_Cache` object. + * + * @since 0.1 + * + * @param array $args { + * @type bool $hide_empty Hides languages with no posts if set to `true` (defaults to `false`). + * @type bool $hide_default Hides default language from the list (default to `false`). + * @type string $fields Returns only that field if set; {@see PLL_Language} for a list of fields. + * } + * @return array List of PLL_Language objects or PLL_Language object properties. + */ + public function get_languages_list( $args = array() ) { + if ( ! $this->are_languages_ready() ) { + _doing_it_wrong( + __METHOD__ . '()', + "It must not be called before the hook 'pll_pre_init'.", + '3.4' + ); + } + + $languages = $this->cache->get( 'languages' ); + + if ( ! is_array( $languages ) ) { + // Bail out early if languages are currently created to avoid an infinite loop. + if ( $this->is_creating_language_objects ) { + return array(); + } + + $this->is_creating_language_objects = true; + + if ( ! pll_get_constant( 'PLL_CACHE_LANGUAGES', true ) ) { + // Create the languages from taxonomies. + $languages = $this->get_languages_from_taxonomies(); + } else { + $languages = get_transient( 'pll_languages_list' ); + + if ( empty( $languages ) || ! is_array( $languages ) || empty( reset( $languages )['term_props'] ) ) { // Test `term_props` in case we got a transient older than 3.4. + // Create the languages from taxonomies. + $languages = $this->get_languages_from_taxonomies(); + } else { + // Create the languages directly from arrays stored in the transient. + $languages = array_map( + array( new PLL_Language_Factory( $this->options ), 'get' ), + $languages + ); + + // Remove potential empty language. + $languages = array_filter( $languages ); + + // Re-index. + $languages = array_values( $languages ); + } + } + + /** + * Filters the list of languages *after* it is stored in the persistent cache. + * /!\ This filter is fired *before* the $polylang object is available. + * + * @since 1.8 + * @since 3.4 Deprecated. If you used this hook to filter URLs, you may hook `'site_url'` instead. + * @deprecated + * + * @param PLL_Language[] $languages The list of language objects. + */ + $languages = apply_filters_deprecated( 'pll_after_languages_cache', array( $languages ), '3.4' ); + + if ( $this->are_languages_ready() ) { + $this->cache->set( 'languages', $languages ); + } + + $this->is_creating_language_objects = false; + } + + $languages = array_filter( + $languages, + function ( $lang ) use ( $args ) { + $keep_empty = empty( $args['hide_empty'] ) || $lang->get_tax_prop( 'language', 'count' ); + $keep_default = empty( $args['hide_default'] ) || ! $lang->is_default; + return $keep_empty && $keep_default; + } + ); + + $languages = array_values( $languages ); // Re-index. + + return empty( $args['fields'] ) ? $languages : wp_list_pluck( $languages, $args['fields'] ); + } + + /** + * Tells if {@see PLL_Model::get_languages_list()} can be used. + * + * @since 3.4 + * + * @return bool + */ + public function are_languages_ready() { + return $this->languages_ready; + } + + /** + * Sets the internal property `$languages_ready` to `true`, telling that {@see PLL_Model::get_languages_list()} can be used. + * + * @since 3.4 + * + * @return void + */ + public function set_languages_ready() { + $this->languages_ready = true; + } + + /** + * Cleans language cache + * can be called directly with no parameter + * called by the 'edited_term_taxonomy' filter with 2 parameters when count needs to be updated + * + * @since 1.2 + * + * @param int $term not used + * @param string $taxonomy taxonomy name + * @return void + */ + public function clean_languages_cache( $term = 0, $taxonomy = null ) { + if ( empty( $taxonomy ) || 'language' === $taxonomy ) { + delete_transient( 'pll_languages_list' ); + $this->cache->clean(); + } + } + + /** + * Don't query term metas when only our taxonomies are queried + * + * @since 2.3 + * + * @param array $args WP_Term_Query arguments + * @return array + */ + public function get_terms_args( $args ) { + $taxonomies = $this->translatable_objects->get_taxonomy_names(); + + if ( isset( $args['taxonomy'] ) && ! array_diff( (array) $args['taxonomy'], $taxonomies ) ) { + $args['update_term_meta_cache'] = false; + } + return $args; + } + + /** + * Returns the language by its term_id, tl_term_id, slug or locale. + * + * @since 0.1 + * @since 3.4 Allow to get a language by `term_taxonomy_id`. + * + * @param mixed $value `term_id`, `term_taxonomy_id`, `slug`, `locale`, or `w3c` of the queried language. + * `term_id` and `term_taxonomy_id` can be fetched for any language taxonomy. + * /!\ For the `term_taxonomy_id`, prefix the ID by `tt:` (ex: `"tt:{$tt_id}"`), + * this is to prevent confusion between `term_id` and `term_taxonomy_id`. + * @return PLL_Language|false Language object, false if no language found. + */ + public function get_language( $value ) { + if ( is_object( $value ) ) { + return $value instanceof PLL_Language ? $value : $this->get_language( $value->term_id ); // Will force cast to PLL_Language. + } + + $return = $this->cache->get( 'language:' . $value ); + + if ( $return instanceof PLL_Language ) { + return $return; + } + + foreach ( $this->get_languages_list() as $lang ) { + foreach ( $lang->get_tax_props() as $props ) { + $this->cache->set( 'language:' . $props['term_id'], $lang ); + $this->cache->set( 'language:tt:' . $props['term_taxonomy_id'], $lang ); + } + $this->cache->set( 'language:' . $lang->slug, $lang ); + $this->cache->set( 'language:' . $lang->locale, $lang ); + $this->cache->set( 'language:' . $lang->w3c, $lang ); + } + + /** @var PLL_Language|false */ + return $this->cache->get( 'language:' . $value ); + } + + /** + * Returns the default language. + * + * @since 3.4 + * + * @return PLL_Language|false Default language object, `false` if no language found. + */ + public function get_default_language() { + if ( empty( $this->options['default_lang'] ) ) { + return false; + } + + return $this->get_language( $this->options['default_lang'] ); + } + + /** + * Adds terms clauses to the term query to filter them by languages. + * + * @since 1.2 + * + * @param string[] $clauses The list of sql clauses in terms query. + * @param PLL_Language|false $lang PLL_Language object. + * @return string[] Modified list of clauses. + */ + public function terms_clauses( $clauses, $lang ) { + if ( ! empty( $lang ) && false === strpos( $clauses['join'], 'pll_tr' ) ) { + $clauses['join'] .= $this->term->join_clause(); + $clauses['where'] .= $this->term->where_clause( $lang ); + } + return $clauses; + } + + /** + * Returns post types that need to be translated. + * The post types list is cached for better better performance. + * The method waits for 'after_setup_theme' to apply the cache + * to allow themes adding the filter in functions.php. + * + * @since 1.2 + * + * @param bool $filter True if we should return only valid registered post types. + * @return string[] Post type names for which Polylang manages languages and translations. + */ + public function get_translated_post_types( $filter = true ) { + return $this->translatable_objects->get( 'post' )->get_translated_object_types( $filter ); + } + + /** + * Returns true if Polylang manages languages and translations for this post type. + * + * @since 1.2 + * + * @param string|string[] $post_type Post type name or array of post type names. + * @return bool + */ + public function is_translated_post_type( $post_type ) { + if ( empty( array_filter( (array) $post_type ) ) ) { + return false; + } + + /** @var non-empty-array|non-empty-string $post_type */ + return $this->translatable_objects->get( 'post' )->is_translated_object_type( $post_type ); + } + + /** + * Returns taxonomies that need to be translated. + * The taxonomies list is cached for better better performance. + * The method waits for 'after_setup_theme' to apply the cache + * to allow themes adding the filter in functions.php. + * + * @since 1.2 + * + * @param bool $filter True if we should return only valid registered taxonomies. + * @return string[] Array of registered taxonomy names for which Polylang manages languages and translations. + */ + public function get_translated_taxonomies( $filter = true ) { + return $this->translatable_objects->get( 'term' )->get_translated_object_types( $filter ); + } + + /** + * Returns true if Polylang manages languages and translations for this taxonomy. + * + * @since 1.2 + * + * @param string|string[] $tax Taxonomy name or array of taxonomy names. + * @return bool + */ + public function is_translated_taxonomy( $tax ) { + if ( empty( array_filter( (array) $tax ) ) ) { + return false; + } + + /** @var non-empty-array|non-empty-string $tax */ + return $this->translatable_objects->get( 'term' )->is_translated_object_type( $tax ); + } + + /** + * Return taxonomies that need to be filtered (post_format like). + * + * @since 1.7 + * + * @param bool $filter True if we should return only valid registered taxonomies. + * @return string[] Array of registered taxonomy names. + */ + public function get_filtered_taxonomies( $filter = true ) { + if ( did_action( 'after_setup_theme' ) ) { + static $taxonomies = null; + } + + if ( empty( $taxonomies ) ) { + $taxonomies = array( 'post_format' => 'post_format' ); + + /** + * Filters the list of taxonomies not translatable but filtered by language. + * Includes only the post format by default + * The filter must be added soon in the WordPress loading process: + * in a function hooked to ‘plugins_loaded’ or directly in functions.php for themes. + * + * @since 1.7 + * + * @param string[] $taxonomies List of taxonomy names. + * @param bool $is_settings True when displaying the list of custom taxonomies in Polylang settings. + */ + $taxonomies = apply_filters( 'pll_filtered_taxonomies', $taxonomies, false ); + } + + return $filter ? array_intersect( $taxonomies, get_taxonomies() ) : $taxonomies; + } + + /** + * Returns true if Polylang filters this taxonomy per language. + * + * @since 1.7 + * + * @param string|string[] $tax Taxonomy name or array of taxonomy names. + * @return bool + */ + public function is_filtered_taxonomy( $tax ) { + $taxonomies = $this->get_filtered_taxonomies( false ); + return ( is_array( $tax ) && array_intersect( $tax, $taxonomies ) || in_array( $tax, $taxonomies ) ); + } + + /** + * Returns the query vars of all filtered taxonomies. + * + * @since 1.7 + * + * @return string[] + */ + public function get_filtered_taxonomies_query_vars() { + $query_vars = array(); + foreach ( $this->get_filtered_taxonomies() as $filtered_tax ) { + $tax = get_taxonomy( $filtered_tax ); + if ( ! empty( $tax ) && is_string( $tax->query_var ) ) { + $query_vars[] = $tax->query_var; + } + } + return $query_vars; + } + + /** + * It is possible to have several terms with the same name in the same taxonomy ( one per language ) + * but the native term_exists() will return true even if only one exists. + * So here the function adds the language parameter. + * + * @since 1.4 + * + * @param string $term_name The term name. + * @param string $taxonomy Taxonomy name. + * @param int $parent Parent term id. + * @param string|PLL_Language $language The language slug or object. + * @return int The `term_id` of the found term. 0 otherwise. + * + * @phpstan-return int<0, max> + */ + public function term_exists( $term_name, $taxonomy, $parent, $language ) { + global $wpdb; + + $language = $this->get_language( $language ); + if ( empty( $language ) ) { + return 0; + } + + $term_name = trim( wp_unslash( $term_name ) ); + $term_name = _wp_specialchars( $term_name ); + + $select = "SELECT t.term_id FROM $wpdb->terms AS t"; + $join = " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id"; + $join .= $this->term->join_clause(); + $where = $wpdb->prepare( ' WHERE tt.taxonomy = %s AND t.name = %s', $taxonomy, $term_name ); + $where .= $this->term->where_clause( $language ); + + if ( $parent > 0 ) { + $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent ); + } + + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $term_id = $wpdb->get_var( $select . $join . $where ); + return max( 0, (int) $term_id ); + } + + /** + * Checks if a term slug exists in a given language, taxonomy, hierarchy. + * + * @since 1.9 + * @since 2.8 Moved from PLL_Share_Term_Slug::term_exists() to PLL_Model::term_exists_by_slug(). + * + * @param string $slug The term slug to test. + * @param string|PLL_Language $language The language slug or object. + * @param string $taxonomy Optional taxonomy name. + * @param int $parent Optional parent term id. + * @return int The `term_id` of the found term. 0 otherwise. + */ + public function term_exists_by_slug( $slug, $language, $taxonomy = '', $parent = 0 ) { + global $wpdb; + + $language = $this->get_language( $language ); + if ( empty( $language ) ) { + return 0; + } + + $select = "SELECT t.term_id FROM {$wpdb->terms} AS t"; + $join = " INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id"; + $join .= $this->term->join_clause(); + $where = $wpdb->prepare( ' WHERE t.slug = %s', $slug ); + $where .= $this->term->where_clause( $language ); + + if ( ! empty( $taxonomy ) ) { + $where .= $wpdb->prepare( ' AND tt.taxonomy = %s', $taxonomy ); + } + + if ( $parent > 0 ) { + $where .= $wpdb->prepare( ' AND tt.parent = %d', $parent ); + } + + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + return $wpdb->get_var( $select . $join . $where ); + } + + + /** + * Returns the number of posts per language in a date, author or post type archive. + * + * @since 1.2 + * + * @param PLL_Language $lang PLL_Language instance. + * @param array $q { + * WP_Query arguments: + * + * @type string|string[] $post_type Post type or array of post types. + * @type int $m Combination YearMonth. Accepts any four-digit year and month. + * @type int $year Four-digit year. + * @type int $monthnum Two-digit month. + * @type int $day Day of the month. + * @type int $author Author id. + * @type string $author_name User 'user_nicename'. + * @type string $post_format Post format. + * @type string $post_status Post status. + * } + * @return int + * + * @phpstan-param array{ + * post_type?: non-falsy-string|array, + * post_status?: non-falsy-string, + * m?: numeric-string, + * year?: positive-int, + * monthnum?: int<1, 12>, + * day?: int<1, 31>, + * author?: int<1, max>, + * author_name?: non-falsy-string, + * post_format?: non-falsy-string + * } $q + * @phpstan-return int<0, max> + */ + public function count_posts( $lang, $q = array() ) { + global $wpdb; + + $q = array_merge( array( 'post_type' => 'post', 'post_status' => 'publish' ), $q ); + + if ( ! is_array( $q['post_type'] ) ) { + $q['post_type'] = array( $q['post_type'] ); + } + + foreach ( $q['post_type'] as $key => $type ) { + if ( ! post_type_exists( $type ) ) { + unset( $q['post_type'][ $key ] ); + } + } + + if ( empty( $q['post_type'] ) ) { + $q['post_type'] = array( 'post' ); // We *need* a post type. + } + + $cache_key = $this->cache->get_unique_key( 'pll_count_posts_', $q ); + $counts = wp_cache_get( $cache_key, 'counts' ); + + if ( ! is_array( $counts ) ) { + $counts = array(); + $select = "SELECT pll_tr.term_taxonomy_id, COUNT( * ) AS num_posts FROM {$wpdb->posts}"; + $join = $this->post->join_clause(); + $where = sprintf( " WHERE post_status = '%s'", esc_sql( $q['post_status'] ) ); + $where .= sprintf( " AND {$wpdb->posts}.post_type IN ( '%s' )", implode( "', '", esc_sql( $q['post_type'] ) ) ); + $where .= $this->post->where_clause( $this->get_languages_list() ); + $groupby = ' GROUP BY pll_tr.term_taxonomy_id'; + + if ( ! empty( $q['m'] ) ) { + $q['m'] = '' . preg_replace( '|[^0-9]|', '', $q['m'] ); + $where .= $wpdb->prepare( " AND YEAR( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 0, 4 ) ); + if ( strlen( $q['m'] ) > 5 ) { + $where .= $wpdb->prepare( " AND MONTH( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 4, 2 ) ); + } + if ( strlen( $q['m'] ) > 7 ) { + $where .= $wpdb->prepare( " AND DAYOFMONTH( {$wpdb->posts}.post_date ) = %d", substr( $q['m'], 6, 2 ) ); + } + } + + if ( ! empty( $q['year'] ) ) { + $where .= $wpdb->prepare( " AND YEAR( {$wpdb->posts}.post_date ) = %d", $q['year'] ); + } + + if ( ! empty( $q['monthnum'] ) ) { + $where .= $wpdb->prepare( " AND MONTH( {$wpdb->posts}.post_date ) = %d", $q['monthnum'] ); + } + + if ( ! empty( $q['day'] ) ) { + $where .= $wpdb->prepare( " AND DAYOFMONTH( {$wpdb->posts}.post_date ) = %d", $q['day'] ); + } + + if ( ! empty( $q['author_name'] ) ) { + $author = get_user_by( 'slug', sanitize_title_for_query( $q['author_name'] ) ); + if ( $author ) { + $q['author'] = $author->ID; + } + } + + if ( ! empty( $q['author'] ) ) { + $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_author = %d", $q['author'] ); + } + + // Filtered taxonomies ( post_format ). + foreach ( $this->get_filtered_taxonomies_query_vars() as $tax_qv ) { + + if ( ! empty( $q[ $tax_qv ] ) ) { + $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = {$wpdb->posts}.ID"; + $join .= " INNER JOIN {$wpdb->term_taxonomy} AS tt ON tt.term_taxonomy_id = tr.term_taxonomy_id"; + $join .= " INNER JOIN {$wpdb->terms} AS t ON t.term_id = tt.term_id"; + $where .= $wpdb->prepare( ' AND t.slug = %s', $q[ $tax_qv ] ); + } + } + + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $res = $wpdb->get_results( $select . $join . $where . $groupby, ARRAY_A ); + foreach ( (array) $res as $row ) { + $counts[ $row['term_taxonomy_id'] ] = $row['num_posts']; + } + + wp_cache_set( $cache_key, $counts, 'counts' ); + } + + $term_taxonomy_id = $lang->get_tax_prop( 'language', 'term_taxonomy_id' ); + return empty( $counts[ $term_taxonomy_id ] ) ? 0 : $counts[ $term_taxonomy_id ]; + } + + /** + * Setup the links model based on options. + * + * @since 1.2 + * + * @return PLL_Links_Model + */ + public function get_links_model() { + $c = array( 'Directory', 'Directory', 'Subdomain', 'Domain' ); + $class = get_option( 'permalink_structure' ) ? 'PLL_Links_' . $c[ $this->options['force_lang'] ] : 'PLL_Links_Default'; + + /** + * Filters the links model class to use. + * /!\ this filter is fired *before* the $polylang object is available. + * + * @since 2.1.1 + * + * @param string $class A class name: PLL_Links_Default, PLL_Links_Directory, PLL_Links_Subdomain, PLL_Links_Domain. + */ + $class = apply_filters( 'pll_links_model', $class ); + + return new $class( $this ); + } + + /** + * Returns a list of object IDs without language (used in settings and wizard). + * + * @since 0.9 + * @since 2.2.6 Added the `$limit` parameter. + * @since 3.4 Added the `$types` parameter. + * + * @param int $limit Optional. Max number of IDs to return. Defaults to -1 (no limit). + * @param string[] $types Optional. Types to handle (@see PLL_Translatable_Object::get_type()). Defaults to + * an empty array (all types). + * @return int[][]|false { + * IDs of objects without language. + * + * @type int[] $posts Array of post ids. + * @type int[] $terms Array of term ids. + * } + * + * @phpstan-param -1|positive-int $limit + */ + public function get_objects_with_no_lang( $limit = -1, array $types = array() ) { + /** + * Filters the max number of IDs to return when searching objects with no language. + * This filter can be used to decrease the memory usage in case the number of objects + * without language is too big. Using a negative value is equivalent to have no limit. + * + * @since 2.2.6 + * @since 3.4 Added the `$types` parameter. + * + * @param int $limit Max number of IDs to retrieve from the database. + * @param string[] $types Types to handle (@see PLL_Translatable_Object::get_type()). An empty array means all + * types. + */ + $limit = apply_filters( 'get_objects_with_no_lang_limit', $limit, $types ); + $limit = $limit < 1 ? -1 : max( (int) $limit, 1 ); + $objects = array(); + + foreach ( $this->translatable_objects as $type => $object ) { + if ( ! empty( $types ) && ! in_array( $type, $types, true ) ) { + continue; + } + + $ids = $object->get_objects_with_no_lang( $limit ); + + if ( empty( $ids ) ) { + continue; + } + + // The trailing 's' in the array key is for backward compatibility. + $objects[ "{$type}s" ] = $ids; + } + + $objects = ! empty( $objects ) ? $objects : false; + + /** + * Filters the list of IDs of untranslated objects. + * + * @since 0.9 + * @since 3.4 Added the `$limit` and `$types` parameters. + * + * @param int[][]|false $objects List of lists of object IDs, `false` if no IDs found. + * @param int $limit Max number of IDs to retrieve from the database. + * @param string[] $types Types to handle (@see PLL_Translatable_Object::get_type()). An empty array + * means all types. + */ + return apply_filters( 'pll_get_objects_with_no_lang', $objects, $limit, $types ); + } + + /** + * Returns ids of post without language. + * + * @since 3.1 + * + * @param string|string[] $post_types A translated post type or an array of translated post types. + * @param int $limit Max number of objects to return. `-1` to return all of them. + * @return int[] + * + * @phpstan-param -1|positive-int $limit + * @phpstan-return list + */ + public function get_posts_with_no_lang( $post_types, $limit ) { + return $this->translatable_objects->get( 'post' )->get_objects_with_no_lang( $limit, (array) $post_types ); + } + + /** + * Returns ids of terms without language. + * + * @since 3.1 + * + * @param string|string[] $taxonomies A translated taxonomy or an array of taxonomies post types. + * @param int $limit Max number of objects to return. `-1` to return all of them. + * @return int[] + * + * @phpstan-param -1|positive-int $limit + * @phpstan-return list + */ + public function get_terms_with_no_lang( $taxonomies, $limit ) { + return $this->translatable_objects->get( 'term' )->get_objects_with_no_lang( $limit, (array) $taxonomies ); + } + + /** + * Assigns the default language to objects in mass. + * + * @since 1.2 + * @since 3.4 Moved from PLL_Admin_Model class. + * Removed `$limit` parameter, added `$lang` and `$types` parameters. + * + * @param PLL_Language|null $lang Optional. The language to assign to objects. Defaults to `null` (default language). + * @param string[] $types Optional. Types to handle (@see PLL_Translatable_Object::get_type()). Defaults + * to an empty array (all types). + * @return void + */ + public function set_language_in_mass( $lang = null, array $types = array() ) { + if ( ! $lang instanceof PLL_Language ) { + $lang = $this->get_default_language(); + + if ( empty( $lang ) ) { + return; + } + } + + // 1000 is an arbitrary value that will be filtered by `get_objects_with_no_lang_limit`. + $nolang = $this->get_objects_with_no_lang( 1000, $types ); + + if ( empty( $nolang ) ) { + return; + } + + /** + * Keep track of types where we set the language: + * those are types where we may have more items to process if we have more than 1000 items in total. + * This will prevent unnecessary SQL queries in the next recursion: if we have 0 items in this recursion for + * a type, we'll still have 0 in the next one, no need for a new query. + */ + $types_with_objects = array(); + + foreach ( $this->translatable_objects as $type => $object ) { + if ( empty( $nolang[ "{$type}s" ] ) ) { + continue; + } + + if ( ! empty( $types ) && ! in_array( $type, $types, true ) ) { + continue; + } + + $object->set_language_in_mass( $nolang[ "{$type}s" ], $lang ); + $types_with_objects[] = $type; + } + + if ( empty( $types_with_objects ) ) { + return; + } + + $this->set_language_in_mass( $lang, $types_with_objects ); + } + + /** + * Filters the ORDERBY clause of the languages query. + * + * This allows to order languages terms by `taxonomy` first then by `term_group` and `term_id`. + * Ordering terms by taxonomy allows not to mix terms between all language taxomonomies. + * Having the "language' taxonomy first is important for {@see PLL_Admin_Model:delete_language()}. + * + * @since 3.2.3 + * + * @param string $orderby `ORDERBY` clause of the terms query. + * @param array $args An array of term query arguments. + * @param string[] $taxonomies An array of taxonomy names. + * @return string + */ + public function filter_language_terms_orderby( $orderby, $args, $taxonomies ) { + $allowed_taxonomies = $this->translatable_objects->get_taxonomy_names( array( 'language' ) ); + + if ( ! is_array( $taxonomies ) || ! empty( array_diff( $taxonomies, $allowed_taxonomies ) ) ) { + return $orderby; + } + + if ( empty( $orderby ) || ! is_string( $orderby ) ) { + return $orderby; + } + + if ( ! preg_match( '@^(?[^.]+)\.term_group$@', $orderby, $matches ) ) { + return $orderby; + } + + return sprintf( 'tt.taxonomy = \'language\' DESC, %1$s.term_group, %1$s.term_id', $matches['alias'] ); + } + + /** + * Maybe adds the missing language terms for 3rd party language taxonomies. + * + * @since 3.4 + * + * @return void + */ + public function maybe_create_language_terms() { + $registered_taxonomies = array_diff( + $this->translatable_objects->get_taxonomy_names( array( 'language' ) ), + // Exclude the post and term language taxonomies from the list. + array( $this->post->get_tax_language(), $this->term->get_tax_language() ) + ); + + if ( empty( $registered_taxonomies ) ) { + // No 3rd party language taxonomies. + return; + } + + // We have at least one 3rd party language taxonomy. + $known_taxonomies = ! empty( $this->options['language_taxonomies'] ) && is_array( $this->options['language_taxonomies'] ) ? $this->options['language_taxonomies'] : array(); + $new_taxonomies = array_diff( $registered_taxonomies, $known_taxonomies ); + + if ( empty( $new_taxonomies ) ) { + // No new 3rd party language taxonomies. + return; + } + + // We have at least one unknown 3rd party language taxonomy. + foreach ( $this->get_languages_list() as $language ) { + $this->update_secondary_language_terms( $language->slug, $language->name, $language, $new_taxonomies ); + } + + // Clear the cache, so the new `term_id` and `term_taxonomy_id` appear in the languages list. + $this->clean_languages_cache(); + + // Keep the previous values, so this is triggered only once per taxonomy. + $this->options['language_taxonomies'] = array_merge( $known_taxonomies, $new_taxonomies ); + update_option( 'polylang', $this->options ); + } + + /** + * Updates or adds new terms for a secondary language taxonomy (aka not 'language'). + * + * @since 3.4 + * + * @param string $slug Language term slug (with or without the `pll_` prefix). + * @param string $name Language name (label). + * @param PLL_Language|null $language Optional. A language object. Required to update the existing terms. + * @param string[] $taxonomies Optional. List of language taxonomies to deal with. An empty value means + * all of them. Defaults to all taxonomies. + * @return void + * + * @phpstan-param non-empty-string $slug + * @phpstan-param non-empty-string $name + * @phpstan-param array $taxonomies + */ + protected function update_secondary_language_terms( $slug, $name, PLL_Language $language = null, array $taxonomies = array() ) { + $slug = 0 === strpos( $slug, 'pll_' ) ? $slug : "pll_$slug"; + + foreach ( $this->translatable_objects->get_secondary_translatable_objects() as $object ) { + if ( ! empty( $taxonomies ) && ! in_array( $object->get_tax_language(), $taxonomies, true ) ) { + // Not in the list. + continue; + } + + if ( ! empty( $language ) ) { + $term_id = $language->get_tax_prop( $object->get_tax_language(), 'term_id' ); + } else { + $term_id = 0; + } + + if ( empty( $term_id ) ) { + // Attempt to repair the language if a term has been deleted by a database cleaning tool. + wp_insert_term( $name, $object->get_tax_language(), array( 'slug' => $slug ) ); + continue; + } + + /** @var PLL_Language $language */ + if ( "pll_{$language->slug}" !== $slug || $language->name !== $name ) { + // Something has changed. + wp_update_term( $term_id, $object->get_tax_language(), array( 'slug' => $slug, 'name' => $name ) ); + } + } + } + + /** + * Returns the list of available languages, based on the language taxonomy terms. + * Stores the list in a db transient and in a `PLL_Cache` object. + * + * @since 3.4 + * + * @return PLL_Language[] An array of `PLL_Language` objects, array keys are the type. + * + * @phpstan-return list + */ + protected function get_languages_from_taxonomies() { + $terms_by_slug = array(); + + foreach ( $this->get_language_terms() as $term ) { + // Except for language taxonomy term slugs, remove 'pll_' prefix from the other language taxonomy term slugs. + $key = 'language' === $term->taxonomy ? $term->slug : substr( $term->slug, 4 ); + $terms_by_slug[ $key ][ $term->taxonomy ] = $term; + } + + /** + * @var ( + * array{ + * string: array{ + * language: WP_Term, + * }&array + * } + * ) $terms_by_slug + */ + $languages = array_filter( + array_map( + array( new PLL_Language_Factory( $this->options ), 'get_from_terms' ), + array_values( $terms_by_slug ) + ) + ); + + /** + * Filters the list of languages *before* it is stored in the persistent cache. + * /!\ This filter is fired *before* the $polylang object is available. + * + * @since 1.7.5 + * @since 3.4 Deprecated. + * @deprecated + * + * @param PLL_Language[] $languages The list of language objects. + * @param PLL_Model $model PLL_Model object. + */ + $languages = apply_filters_deprecated( 'pll_languages_list', array( $languages, $this ), '3.4', 'pll_additional_language_data' ); + + if ( ! $this->are_languages_ready() ) { + // Do not cache an incomplete list. + /** @var list $languages */ + return $languages; + } + + /** + * Don't store directly objects as it badly break with some hosts ( GoDaddy ) due to race conditions when using object cache. + * Thanks to captin411 for catching this! + * + * @see https://wordpress.org/support/topic/fatal-error-pll_model_languages_list?replies=8#post-6782255 + */ + $languages_data = array_map( + function ( $language ) { + return $language->to_array( 'db' ); + }, + $languages + ); + + set_transient( 'pll_languages_list', $languages_data ); + + /** @var list $languages */ + return $languages; + } + + /** + * Returns the list of existing language terms. + * - Returns all terms, that are or not assigned to posts. + * - Terms are ordered by `term_group` and `term_id` (see `PLL_Model->filter_language_terms_orderby()`). + * + * @since 3.2.3 + * + * @return WP_Term[] + */ + protected function get_language_terms() { + add_filter( 'get_terms_orderby', array( $this, 'filter_language_terms_orderby' ), 10, 3 ); + $terms = get_terms( + array( + 'taxonomy' => $this->translatable_objects->get_taxonomy_names( array( 'language' ) ), + 'orderby' => 'term_group', + 'hide_empty' => false, + ) + ); + remove_filter( 'get_terms_orderby', array( $this, 'filter_language_terms_orderby' ) ); + + return empty( $terms ) || is_wp_error( $terms ) ? array() : $terms; + } +} diff --git a/wp-content/plugins/polylang/include/nav-menu.php b/wp-content/plugins/polylang/include/nav-menu.php new file mode 100644 index 0000000000..0331c91420 --- /dev/null +++ b/wp-content/plugins/polylang/include/nav-menu.php @@ -0,0 +1,177 @@ +model = &$polylang->model; + $this->options = &$polylang->options; + + $this->theme = get_option( 'stylesheet' ); + + add_filter( 'wp_setup_nav_menu_item', array( $this, 'wp_setup_nav_menu_item' ) ); + + // Integration with WP customizer + add_action( 'customize_register', array( $this, 'create_nav_menu_locations' ), 5 ); + + // Filter _wp_auto_add_pages_to_menu by language + add_action( 'transition_post_status', array( $this, 'auto_add_pages_to_menu' ), 5, 3 ); // before _wp_auto_add_pages_to_menu + } + + /** + * Assigns the title and label to the language switcher menu items + * + * @since 2.6 + * + * @param stdClass $item Menu item. + * @return stdClass + */ + public function wp_setup_nav_menu_item( $item ) { + if ( isset( $item->url ) && '#pll_switcher' === $item->url ) { + $item->post_title = __( 'Languages', 'polylang' ); + $item->type_label = __( 'Language switcher', 'polylang' ); + } + return $item; + } + + /** + * Create temporary nav menu locations ( one per location and per language ) for all non-default language + * to do only one time + * + * @since 1.2 + * + * @return void + */ + public function create_nav_menu_locations() { + static $once; + global $_wp_registered_nav_menus; + + $arr = array(); + + if ( isset( $_wp_registered_nav_menus ) && ! $once ) { + foreach ( $_wp_registered_nav_menus as $loc => $name ) { + foreach ( $this->model->get_languages_list() as $lang ) { + $arr[ $this->combine_location( $loc, $lang ) ] = $name . ' ' . $lang->name; + } + } + + $_wp_registered_nav_menus = $arr; + $once = true; + } + } + + /** + * Creates a temporary nav menu location from a location and a language + * + * @since 1.8 + * + * @param string $loc Nav menu location. + * @param PLL_Language $lang Language object. + * @return string + */ + public function combine_location( $loc, $lang ) { + return $loc . ( strpos( $loc, '___' ) || $lang->is_default ? '' : '___' . $lang->slug ); + } + + /** + * Get nav menu locations and language from a temporary location. + * + * @since 1.8 + * + * @param string $loc Temporary location. + * @return string[] { + * @type string $location Nav menu location. + * @type string $lang Language code. + * } + */ + public function explode_location( $loc ) { + $infos = explode( '___', $loc ); + if ( 1 == count( $infos ) ) { + $infos[] = $this->options['default_lang']; + } + return array_combine( array( 'location', 'lang' ), $infos ); + } + + /** + * Filters the option nav_menu_options for auto added pages to menu. + * + * @since 0.9.4 + * + * @param array $options Options stored in the option nav_menu_options. + * @return array Modified options. + */ + public function nav_menu_options( $options ) { + $options['auto_add'] = array_intersect( $options['auto_add'], $this->auto_add_menus ); + return $options; + } + + /** + * Filters _wp_auto_add_pages_to_menu by language. + * + * @since 0.9.4 + * + * @param string $new_status Transition to this post status. + * @param string $old_status Previous post status. + * @param WP_Post $post Post object. + * @return void + */ + public function auto_add_pages_to_menu( $new_status, $old_status, $post ) { + if ( 'publish' != $new_status || 'publish' == $old_status || 'page' != $post->post_type || ! empty( $post->post_parent ) ) { + return; + } + + if ( ! empty( $this->options['nav_menus'][ $this->theme ] ) ) { + $lang = $this->model->post->get_language( $post->ID ); + $lang = empty( $lang ) ? $this->options['default_lang'] : $lang->slug; // If the page has no language yet, the default language will be assigned + + // Get all the menus in the page language + foreach ( $this->options['nav_menus'][ $this->theme ] as $loc ) { + if ( ! empty( $loc[ $lang ] ) ) { + $this->auto_add_menus[] = $loc[ $lang ]; + } + } + + add_filter( 'option_nav_menu_options', array( $this, 'nav_menu_options' ) ); + } + } +} diff --git a/wp-content/plugins/polylang/include/olt-manager.php b/wp-content/plugins/polylang/include/olt-manager.php new file mode 100644 index 0000000000..6616338427 --- /dev/null +++ b/wp-content/plugins/polylang/include/olt-manager.php @@ -0,0 +1,254 @@ +default_locale = get_locale(); + + // Filters for text domain management + add_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ), 10, 2 ); + add_filter( 'gettext', array( $this, 'gettext' ), 10, 3 ); + add_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ), 10, 4 ); + + // Loads text domains + add_action( 'pll_language_defined', array( $this, 'load_textdomains' ), 2 ); // After PLL_Frontend::pll_language_defined + add_action( 'pll_no_language_defined', array( $this, 'load_textdomains' ) ); + } + + /** + * Access to the single instance of the class + * + * @since 1.7 + * + * @return PLL_OLT_Manager + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Loads text domains + * + * @since 0.1 + * + * @return void + */ + public function load_textdomains() { + // Our load_textdomain_mofile filter has done its job. let's remove it before calling load_textdomain + remove_filter( 'load_textdomain_mofile', array( $this, 'load_textdomain_mofile' ) ); + remove_filter( 'gettext', array( $this, 'gettext' ) ); + remove_filter( 'gettext_with_context', array( $this, 'gettext_with_context' ) ); + $new_locale = get_locale(); + + // Don't try to save time for en_US as some users have theme written in another language + // Now we can load all overridden text domains with the right language + if ( ! empty( $this->list_textdomains ) ) { + foreach ( $this->list_textdomains as $textdomain ) { + // Since WP 4.6, plugins translations are first loaded from wp-content/languages + if ( ! load_textdomain( $textdomain['domain'], str_replace( "{$this->default_locale}.mo", "$new_locale.mo", $textdomain['mo'] ) ) ) { + // Since WP 3.5 themes may store languages files in /wp-content/languages/themes + if ( ! load_textdomain( $textdomain['domain'], WP_LANG_DIR . "/themes/{$textdomain['domain']}-$new_locale.mo" ) ) { + // Since WP 3.7 plugins may store languages files in /wp-content/languages/plugins + load_textdomain( $textdomain['domain'], WP_LANG_DIR . "/plugins/{$textdomain['domain']}-$new_locale.mo" ); + } + } + } + } + + // First remove taxonomies and post_types labels that we don't need to translate + $taxonomies = get_taxonomies( array( '_pll' => true ) ); + $post_types = get_post_types( array( '_pll' => true ) ); + + // We don't need to translate core taxonomies and post types labels when setting the language from the url + // As they will be translated when registered the second time + if ( ! did_action( 'setup_theme' ) ) { + $taxonomies = array_merge( get_taxonomies( array( '_builtin' => true ) ), $taxonomies ); + $post_types = array_merge( get_post_types( array( '_builtin' => true ) ), $post_types ); + } + + // Translate labels of post types and taxonomies + foreach ( array_diff_key( $GLOBALS['wp_taxonomies'], array_flip( $taxonomies ) ) as $tax ) { + $this->translate_labels( $tax ); + } + foreach ( array_diff_key( $GLOBALS['wp_post_types'], array_flip( $post_types ) ) as $pt ) { + $this->translate_labels( $pt ); + } + + // Act only if the language has not been set early ( before default textdomain loading and $wp_locale creation ) + if ( did_action( 'after_setup_theme' ) ) { + // Reinitializes wp_locale for weekdays and months + unset( $GLOBALS['wp_locale'] ); + $GLOBALS['wp_locale'] = new WP_Locale(); + } + + /** + * Fires after the post types and taxonomies labels have been translated + * This allows plugins to translate text the same way we do for post types and taxonomies labels + * + * @since 1.2 + * + * @param array $labels list of strings to trnaslate + */ + do_action_ref_array( 'pll_translate_labels', array( &$this->labels ) ); + + // Free memory. + $this->default_locale = null; + $this->list_textdomains = array(); + $this->labels = array(); + } + + /** + * Saves all text domains in a table for later usage. + * It replaces the 'override_load_textdomain' filter previously used. + * + * @since 2.0.4 + * + * @param string $mofile The translation file name. + * @param string $domain The text domain name. + * @return string + */ + public function load_textdomain_mofile( $mofile, $domain ) { + // On multisite, 2 files are sharing the same domain so we need to distinguish them. + if ( 'default' === $domain && false !== strpos( $mofile, '/ms-' ) ) { + $this->list_textdomains['ms-default'] = array( 'mo' => $mofile, 'domain' => $domain ); + } else { + $this->list_textdomains[ $domain ] = array( 'mo' => $mofile, 'domain' => $domain ); + } + return ''; // Hack to prevent WP loading text domains as we will load them all later. + } + + /** + * Saves post types and taxonomies labels for a later usage + * + * @since 0.9 + * + * @param string $translation not used + * @param string $text string to translate + * @param string $domain text domain + * @return string unmodified $translation + */ + public function gettext( $translation, $text, $domain ) { + if ( is_string( $text ) ) { // Avoid a warning with some buggy plugins which pass an array + $this->labels[ $text ] = array( 'domain' => $domain ); + } + return $translation; + } + + /** + * Saves post types and taxonomies labels for a later usage + * + * @since 0.9 + * + * @param string $translation not used + * @param string $text string to translate + * @param string $context some comment to describe the context of string to translate + * @param string $domain text domain + * @return string unmodified $translation + */ + public function gettext_with_context( $translation, $text, $context, $domain ) { + $this->labels[ $text ] = array( 'domain' => $domain, 'context' => $context ); + return $translation; + } + + /** + * Translates post types and taxonomies labels once the language is known. + * + * @since 0.9 + * + * @param WP_Post_Type|WP_Taxonomy $type Either a post type or a taxonomy. + * @return void + */ + public function translate_labels( $type ) { + // Use static array to avoid translating several times the same ( default ) labels + static $translated = array(); + + foreach ( (array) $type->labels as $key => $label ) { + if ( is_string( $label ) && isset( $this->labels[ $label ] ) ) { + if ( empty( $translated[ $label ] ) ) { + // PHPCS:disable WordPress.WP.I18n + $type->labels->$key = $translated[ $label ] = isset( $this->labels[ $label ]['context'] ) ? + _x( $label, $this->labels[ $label ]['context'], $this->labels[ $label ]['domain'] ) : + __( $label, $this->labels[ $label ]['domain'] ); + // PHPCS:enable + } + else { + $type->labels->$key = $translated[ $label ]; + } + } + } + } + + /** + * Allows Polylang to be the first plugin loaded ;-). + * + * @since 1.2 + * + * @param string[] $plugins List of active plugins. + * @return string[] List of active plugins. + */ + public function make_polylang_first( $plugins ) { + if ( $key = array_search( POLYLANG_BASENAME, $plugins ) ) { + unset( $plugins[ $key ] ); + array_unshift( $plugins, POLYLANG_BASENAME ); + } + return $plugins; + } +} diff --git a/wp-content/plugins/polylang/include/query.php b/wp-content/plugins/polylang/include/query.php new file mode 100644 index 0000000000..19e75038a1 --- /dev/null +++ b/wp-content/plugins/polylang/include/query.php @@ -0,0 +1,234 @@ +query = &$query; + $this->model = &$model; + } + + /** + * Checks if the query already includes a language taxonomy. + * + * @since 3.0 + * + * @param array $qvars WP_Query query vars. + * @return bool + */ + protected function is_already_filtered( $qvars ) { + if ( isset( $qvars['lang'] ) ) { + return true; + } + + if ( ! empty( $qvars['tax_query'] ) && is_array( $qvars['tax_query'] ) ) { + foreach ( $qvars['tax_query'] as $tax_query ) { + if ( isset( $tax_query['taxonomy'] ) && 'language' === $tax_query['taxonomy'] ) { + return true; + } + } + } + + return false; + } + + /** + * Check if translated taxonomy is queried + * Compatible with nested queries introduced in WP 4.1 + * + * @see https://wordpress.org/support/topic/tax_query-bug + * + * @since 1.7 + * + * @param array $tax_queries An array of tax queries. + * @return bool + */ + protected function have_translated_taxonomy( $tax_queries ) { + if ( is_array( $tax_queries ) ) { + foreach ( $tax_queries as $tax_query ) { + if ( isset( $tax_query['taxonomy'] ) && $this->model->is_translated_taxonomy( $tax_query['taxonomy'] ) && ! ( isset( $tax_query['operator'] ) && 'NOT IN' === $tax_query['operator'] ) ) { + return true; + } + + // Nested queries + elseif ( is_array( $tax_query ) && $this->have_translated_taxonomy( $tax_query ) ) { + return true; + } + } + } + + return false; + } + + /** + * Get queried taxonomies + * + * @since 2.2 + * + * @return array queried taxonomies + */ + public function get_queried_taxonomies() { + return ! empty( $this->query->tax_query->queried_terms ) ? array_keys( wp_list_filter( $this->query->tax_query->queried_terms, array( 'operator' => 'NOT IN' ), 'NOT' ) ) : array(); + } + + /** + * Sets the language in query. + * Optimized for (and requires) WP 3.5+. + * + * @since 2.2 + * @since 3.3 Accepts now an array of languages. + * + * @param PLL_Language|PLL_Language[] $languages Language object(s). + * @return void + */ + public function set_language( $languages ) { + if ( ! is_array( $languages ) ) { + $languages = array( $languages ); + } + + $tt_ids = array(); + foreach ( $languages as $language ) { + $tt_ids[] = $language->get_tax_prop( 'language', 'term_taxonomy_id' ); + } + + // Defining directly the tax_query (rather than setting 'lang' avoids transforming the query by WP). + $lang_query = array( + 'taxonomy' => 'language', + 'field' => 'term_taxonomy_id', // Since WP 3.5 + 'terms' => $tt_ids, + 'operator' => 'IN', + ); + + $tax_query = &$this->query->query_vars['tax_query']; + + if ( isset( $tax_query['relation'] ) && 'OR' === $tax_query['relation'] ) { + $tax_query = array( + $lang_query, + $tax_query, + 'relation' => 'AND', + ); + } elseif ( is_array( $tax_query ) ) { + // The tax query is expected to be *always* an array, but it seems that 3rd parties fill it with a string + // Causing a fatal error if we don't check it. + // See https://wordpress.org/support/topic/fatal-error-2947/ + $tax_query[] = $lang_query; + } elseif ( empty( $tax_query ) ) { + // Supposing the tax query has been wrongly filled with an empty string + $tax_query = array( $lang_query ); + } + } + + /** + * Adds the language in the query after it has checked that it won't conflict with other query vars. + * + * @since 2.2 + * + * @param PLL_Language|false $lang Language. + * @return void + */ + public function filter_query( $lang ) { + $qvars = &$this->query->query_vars; + + if ( ! $this->is_already_filtered( $qvars ) ) { + $taxonomies = array_intersect( $this->model->get_translated_taxonomies(), get_taxonomies( array( '_builtin' => false ) ) ); + + foreach ( $taxonomies as $tax ) { + $tax_object = get_taxonomy( $tax ); + if ( ! empty( $tax_object ) && ! empty( $qvars[ $tax_object->query_var ] ) ) { + return; + } + } + + if ( ! empty( $qvars['tax_query'] ) && $this->have_translated_taxonomy( $qvars['tax_query'] ) ) { + return; + } + + // Filter queries according to the requested language + if ( ! empty( $lang ) ) { + $taxonomies = $this->get_queried_taxonomies(); + + if ( $taxonomies && ( empty( $qvars['post_type'] ) || 'any' === $qvars['post_type'] ) ) { + foreach ( $taxonomies as $taxonomy ) { + $tax_object = get_taxonomy( $taxonomy ); + if ( ! empty( $tax_object ) && $this->model->is_translated_post_type( $tax_object->object_type ) ) { + $this->set_language( $lang ); + break; + } + } + } elseif ( empty( $qvars['post_type'] ) || $this->model->is_translated_post_type( $qvars['post_type'] ) ) { + $this->set_language( $lang ); + } + } + } else { + $this->maybe_set_language_for_or_relation(); + + // Do not filter untranslatable post types such as nav_menu_item + if ( isset( $qvars['post_type'] ) && ! $this->model->is_translated_post_type( $qvars['post_type'] ) && ( empty( $qvars['tax_query'] ) || ! $this->have_translated_taxonomy( $qvars['tax_query'] ) ) ) { + unset( $qvars['lang'] ); + } + + // Unset 'all' query var (mainly for admin language filter). + if ( isset( $qvars['lang'] ) && 'all' === $qvars['lang'] ) { + unset( $qvars['lang'] ); + } + } + } + + /** + * Sets the language correctly if the current query is a 'OR' relation, + * since WordPress merges the language with the other query vars when the relation is OR. + * + * @since 3.3 + * + * @return void + */ + protected function maybe_set_language_for_or_relation() { + if ( ! $this->query->tax_query instanceof WP_Tax_Query ) { + return; + } + + if ( 'OR' !== $this->query->tax_query->relation ) { + return; + } + + if ( ! isset( $this->query->tax_query->queried_terms['language'] ) ) { + return; + } + + $langs = $this->query->tax_query->queried_terms['language']['terms']; + if ( is_string( $langs ) ) { + $langs = explode( ',', $langs ); + } + $langs = array_map( array( $this->model, 'get_language' ), $langs ); + $langs = array_filter( $langs ); + + if ( ! empty( $langs ) ) { + $this->set_language( $langs ); + unset( $this->query->query_vars['lang'] ); // Unset the language query var otherwise WordPress would add the language query by slug in WP_Query::parse_tax_query(). + } + } +} diff --git a/wp-content/plugins/polylang/include/rest-request.php b/wp-content/plugins/polylang/include/rest-request.php new file mode 100644 index 0000000000..534fff3707 --- /dev/null +++ b/wp-content/plugins/polylang/include/rest-request.php @@ -0,0 +1,125 @@ +static_pages = new PLL_Static_Pages( $this ); + } + + $this->model->set_languages_ready(); + } + + /** + * Setup filters. + * + * @since 2.6 + * + * @return void + */ + public function init() { + parent::init(); + + if ( ! $this->model->has_languages() ) { + return; + } + + add_filter( 'rest_pre_dispatch', array( $this, 'set_language' ), 10, 3 ); + + $this->filters_links = new PLL_Filters_Links( $this ); + $this->filters = new PLL_Filters( $this ); + $this->filters_widgets_options = new PLL_Filters_Widgets_Options( $this ); + + $this->links = new PLL_Admin_Links( $this ); + $this->nav_menu = new PLL_Frontend_Nav_Menu( $this ); // For auto added pages to menu. + } + + /** + * Sets the current language during a REST request if sent. + * + * @since 3.3 + * + * @param mixed $result Response to replace the requested version with. Remains untouched. + * @param WP_REST_Server $server Server instance. + * @param WP_REST_Request $request Request used to generate the response. + * @return mixed Untouched $result. + * + * @phpstan-param WP_REST_Request $request + */ + public function set_language( $result, $server, $request ) { + $lang = $request->get_param( 'lang' ); + + if ( ! empty( $lang ) && is_string( $lang ) ) { + $this->curlang = $this->model->get_language( sanitize_key( $lang ) ); + + if ( empty( $this->curlang ) && ! empty( $this->options['default_lang'] ) && is_string( $this->options['default_lang'] ) ) { + // A lang has been requested but it is invalid, let's fall back to the default one. + $this->curlang = $this->model->get_language( sanitize_key( $this->options['default_lang'] ) ); + } + } + + if ( ! empty( $this->curlang ) ) { + /** This action is documented in frontend/choose-lang.php */ + do_action( 'pll_language_defined', $this->curlang->slug, $this->curlang ); + } else { + /** This action is documented in include/class-polylang.php */ + do_action( 'pll_no_language_defined' ); // To load overridden textdomains. + } + + return $result; + } +} diff --git a/wp-content/plugins/polylang/include/static-pages.php b/wp-content/plugins/polylang/include/static-pages.php new file mode 100644 index 0000000000..2954510a74 --- /dev/null +++ b/wp-content/plugins/polylang/include/static-pages.php @@ -0,0 +1,230 @@ +model = &$polylang->model; + $this->curlang = &$polylang->curlang; + + $this->init(); + + add_filter( 'pll_additional_language_data', array( $this, 'set_static_pages' ), 5, 2 ); // Before PLL_Links_Model. + + // Clean the languages cache when editing page of front, page for posts. + add_action( 'update_option_show_on_front', array( $this, 'clean_cache' ) ); + add_action( 'update_option_page_on_front', array( $this, 'clean_cache' ) ); + add_action( 'update_option_page_for_posts', array( $this, 'clean_cache' ) ); + + // Refresh rewrite rules when the page on front is modified. + add_action( 'update_option_page_on_front', 'flush_rewrite_rules' ); + + // Add option filters when the current language is defined + add_action( 'pll_language_defined', array( $this, 'pll_language_defined' ) ); + + // Modifies the page link in case the front page is not in the default language. + add_filter( 'page_link', array( $this, 'page_link' ), 20, 2 ); + + // OEmbed. + add_filter( 'oembed_request_post_id', array( $this, 'oembed_request_post_id' ), 10, 2 ); + } + + /** + * Stores the page on front and page for posts ids. + * + * @since 1.8 + * + * @return void + */ + public function init() { + $this->page_on_front = 0; + $this->page_for_posts = 0; + + if ( 'page' !== get_option( 'show_on_front' ) ) { + return; + } + + $page_on_front = get_option( 'page_on_front' ); + if ( is_numeric( $page_on_front ) ) { + $this->page_on_front = (int) $page_on_front; + } + + $page_for_posts = get_option( 'page_for_posts' ); + if ( is_numeric( $page_for_posts ) ) { + $this->page_for_posts = (int) $page_for_posts; + } + } + + /** + * Returns the ID of the static page translation. + * + * @since 3.4 + * + * @param string $static_page Static page option name; `page_on_front` or `page_for_posts`. + * @param array $language Language data. + * @return int + */ + protected function get_translation( $static_page, $language ) { + $translations = $this->model->post->get_raw_translations( $this->$static_page ); + + // When the current static page doesn't have any translation, we must return itself for its language. + if ( empty( $translations ) ) { + $page_lang = $this->model->post->get_object_term( $this->$static_page, $this->model->post->get_tax_language() ); + + if ( ! empty( $page_lang ) && $page_lang->slug === $language['slug'] ) { + return $this->$static_page; + } + } + + if ( ! isset( $translations[ $language['slug'] ] ) ) { + return 0; + } + + return $translations[ $language['slug'] ]; + } + + /** + * Adds `page_on_front` and `page_for_posts` properties to language data before the object is created. + * + * @since 3.4 + * + * @param array $additional_data Array of language additional data. + * @param array $language Language data. + * @return array Language data with additional `page_on_front` and `page_for_posts` options added. + */ + public function set_static_pages( $additional_data, $language ) { + $additional_data['page_on_front'] = $this->get_translation( 'page_on_front', $language ); + $additional_data['page_for_posts'] = $this->get_translation( 'page_for_posts', $language ); + + return $additional_data; + } + + /** + * Cleans the language cache and resets the internal properties when options are updated. + * + * @since 3.4 + * + * @return void + */ + public function clean_cache() { + $this->model->clean_languages_cache(); + $this->init(); + } + + /** + * Init the hooks that filter the "page on front" and "page for posts" options. + * + * @since 3.3 + * + * @return void + */ + public function pll_language_defined() { + // Translates page for posts and page on front. + add_filter( 'option_page_on_front', array( $this, 'translate_page_id' ), 10, 2 ); + add_filter( 'option_page_for_posts', array( $this, 'translate_page_id' ), 10, 2 ); + } + + /** + * Translates the page on front or page for posts option. + * + * @since 3.6 Replaces `translate_page_on_front()` and `translate_page_on_front()` methods. + * + * @param int $page_id ID of the page on front or page for posts. + * @param string $option Option name: `page_on_front` or `page_for_posts`. + * @return int + */ + public function translate_page_id( $page_id, $option ) { + + if ( empty( $this->curlang->{$option} ) ) { + return $page_id; + } + + if ( doing_action( "update_option_{$option}" ) || doing_action( 'switch_blog' ) || doing_action( 'before_delete_post' ) || doing_action( 'wp_trash_post' ) ) { + /* + * Don't attempt to translate in a 'switch_blog' action as there is a risk to call this function while initializing the languages cache. + * Don't translate while deleting a post or it will mess up `_reset_front_page_settings_for_post()`. + * Don't translate while updating the option itself. + */ + return $page_id; + } + + return $this->curlang->{$option}; + } + + /** + * Modifies the page link in case the front page is not in the default language. + * + * @since 0.7.2 + * + * @param string $link The link to the page. + * @param int $id The post ID of the page. + * @return string Modified link. + */ + public function page_link( $link, $id ) { + $lang = $this->model->post->get_language( $id ); + + if ( $lang && $id == $lang->page_on_front ) { + return $lang->get_home_url(); + } + return $link; + } + + /** + * Fixes the oembed for the translated static front page + * when the language page is redirected to the front page. + * + * @since 2.6 + * + * @param int $post_id The post ID. + * @param string $url The requested URL. + * @return int + */ + public function oembed_request_post_id( $post_id, $url ) { + foreach ( $this->model->get_languages_list() as $lang ) { + if ( is_string( $lang->get_home_url() ) && trailingslashit( $url ) === trailingslashit( $lang->get_home_url() ) ) { + return (int) $lang->page_on_front; + } + } + + return $post_id; + } +} diff --git a/wp-content/plugins/polylang/include/switcher.php b/wp-content/plugins/polylang/include/switcher.php new file mode 100644 index 0000000000..74d6b9dec7 --- /dev/null +++ b/wp-content/plugins/polylang/include/switcher.php @@ -0,0 +1,293 @@ + 0, // Display as list and not as dropdown. + 'echo' => 1, // Echoes the list. + 'hide_if_empty' => 1, // Hides languages with no posts (or pages). + 'show_flags' => 0, // Don't show flags. + 'show_names' => 1, // Show language names. + 'display_names_as' => 'name', // Display the language name. + 'force_home' => 0, // Tries to find a translation. + 'hide_if_no_translation' => 0, // Don't hide the link if there is no translation. + 'hide_current' => 0, // Don't hide the current language. + 'post_id' => null, // Link to the translations of the current page. + 'raw' => 0, // Build the language switcher. + 'item_spacing' => 'preserve', // Preserve whitespace between list items. + 'admin_render' => 0, // Make the switcher in a frontend context. + 'admin_current_lang' => null, // Use the global current language. + ); + + /** + * @var PLL_Links|null + */ + protected $links; + + /** + * Returns options available for the language switcher - menu or widget + * either strings to display the options or default values + * + * @since 0.7 + * + * @param string $type optional either 'menu', 'widget' or 'block', defaults to 'widget' + * @param string $key optional either 'string' or 'default', defaults to 'string' + * @return array list of switcher options strings or default values + */ + public static function get_switcher_options( $type = 'widget', $key = 'string' ) { + $options = array( + 'dropdown' => array( 'string' => __( 'Displays as a dropdown', 'polylang' ), 'default' => 0 ), + 'show_names' => array( 'string' => __( 'Displays language names', 'polylang' ), 'default' => 1 ), + 'show_flags' => array( 'string' => __( 'Displays flags', 'polylang' ), 'default' => 0 ), + 'force_home' => array( 'string' => __( 'Forces link to front page', 'polylang' ), 'default' => 0 ), + 'hide_current' => array( 'string' => __( 'Hides the current language', 'polylang' ), 'default' => 0 ), + 'hide_if_no_translation' => array( 'string' => __( 'Hides languages with no translation', 'polylang' ), 'default' => 0 ), + ); + return wp_list_pluck( $options, $key ); + } + + /** + * Returns the current language code. + * + * @since 3.0 + * + * @param array $args Arguments passed to {@see PLL_Switcher::the_languages()}. + * @return string + */ + protected function get_current_language( $args ) { + if ( $args['admin_current_lang'] ) { + return $args['admin_current_lang']; + } + + if ( isset( $this->links->curlang ) ) { + return $this->links->curlang->slug; + } + + return $this->links->options['default_lang']; + } + + /** + * Returns the link for a given language. + * + * @since 3.0 + * + * @param PLL_Language $language Language. + * @param array $args Arguments passed to {@see PLL_Switcher::the_languages()}. + * @return string|null + */ + protected function get_link( $language, $args ) { + global $post; + + // Priority to the post passed in parameters. + if ( null !== $args['post_id'] ) { + $tr_id = $this->links->model->post->get( $args['post_id'], $language ); + if ( $tr_id && $this->links->model->post->current_user_can_read( $tr_id ) ) { + return get_permalink( $tr_id ); + } + } + + // If we are on frontend. + if ( $this->links instanceof PLL_Frontend_Links ) { + return $this->links->get_translation_url( $language ); + } + + // For blocks in posts in REST requests. + if ( $post instanceof WP_Post ) { + $tr_id = $this->links->model->post->get( $post->ID, $language ); + if ( $tr_id && $this->links->model->post->current_user_can_read( $tr_id ) ) { + return get_permalink( $tr_id ); + } + } + + return null; + } + + /** + * Get the language elements for use in a walker + * + * @since 1.2 + * + * @param array $args Arguments passed to {@see PLL_Switcher::the_languages()}. + * @return array Language switcher elements. + */ + protected function get_elements( $args ) { + $first = true; + $out = array(); + + foreach ( $this->links->model->get_languages_list( array( 'hide_empty' => $args['hide_if_empty'] ) ) as $language ) { + $id = (int) $language->term_id; + $order = (int) $language->term_group; + $slug = $language->slug; + $locale = $language->get_locale( 'display' ); + $item_classes = array( 'lang-item', 'lang-item-' . $id, 'lang-item-' . esc_attr( $slug ) ); + $classes = isset( $args['classes'] ) && is_array( $args['classes'] ) ? + array_merge( + $item_classes, + $args['classes'] + ) : + $item_classes; + $link_classes = isset( $args['link_classes'] ) ? $args['link_classes'] : array(); + $current_lang = $this->get_current_language( $args ) === $slug; + + if ( $current_lang ) { + if ( $args['hide_current'] && ! ( $args['dropdown'] && ! $args['raw'] ) ) { + continue; // Hide current language except for dropdown + } else { + $classes[] = 'current-lang'; + } + } + + $url = $this->get_link( $language, $args ); + + if ( $no_translation = empty( $url ) ) { + $classes[] = 'no-translation'; + } + + /** + * Filter the link in the language switcher + * + * @since 0.7 + * + * @param string|null $url The link, null if no translation was found. + * @param string $slug The language code. + * @param string $locale The language locale + */ + $url = apply_filters( 'pll_the_language_link', $url, $slug, $language->locale ); + + // Hide if no translation exists + if ( empty( $url ) && $args['hide_if_no_translation'] ) { + continue; + } + + $url = empty( $url ) || $args['force_home'] ? $this->links->get_home_url( $language ) : $url; // If the page is not translated, link to the home page + + $name = $args['show_names'] || ! $args['show_flags'] || $args['raw'] ? ( 'slug' == $args['display_names_as'] ? $slug : $language->name ) : ''; + + if ( $args['raw'] && ! $args['show_flags'] ) { + $flag = $language->get_display_flag_url(); + } elseif ( $args['show_flags'] ) { + $flag = $language->get_display_flag( empty( $args['show_names'] ) ? 'alt' : 'no-alt' ); + } else { + $flag = ''; + } + + if ( $first ) { + $classes[] = 'lang-item-first'; + $first = false; + } + + $out[ $slug ] = compact( 'id', 'order', 'slug', 'locale', 'name', 'url', 'flag', 'current_lang', 'no_translation', 'classes', 'link_classes' ); + } + + return $out; + } + + /** + * Displays a language switcher + * or returns the raw elements to build a custom language switcher. + * + * @since 0.1 + * + * @param PLL_Links $links Instance of PLL_Links. + * @param array $args { + * Optional array of arguments. + * + * @type int $dropdown The list is displayed as dropdown if set, defaults to 0. + * @type int $echo Echoes the list if set to 1, defaults to 1. + * @type int $hide_if_empty Hides languages with no posts ( or pages ) if set to 1, defaults to 1. + * @type int $show_flags Displays flags if set to 1, defaults to 0. + * @type int $show_names Shows language names if set to 1, defaults to 1. + * @type string $display_names_as Whether to display the language name or its slug, valid options are 'slug' and 'name', defaults to name. + * @type int $force_home Will always link to home in translated language if set to 1, defaults to 0. + * @type int $hide_if_no_translation Hides the link if there is no translation if set to 1, defaults to 0. + * @type int $hide_current Hides the current language if set to 1, defaults to 0. + * @type int $post_id Returns links to the translations of the post defined by post_id if set, defaults not set. + * @type int $raw Return a raw array instead of html markup if set to 1, defaults to 0. + * @type string $item_spacing Whether to preserve or discard whitespace between list items, valid options are 'preserve' and 'discard', defaults to 'preserve'. + * @type int $admin_render Allows to force the current language code in an admin context if set, default to 0. Need to set the admin_current_lang argument below. + * @type string $admin_current_lang The current language code in an admin context. Need to set the admin_render to 1, defaults not set. + * @type string[] $classes A list of CSS classes to set to each elements outputted. + * @type string[] $link_classes A list of CSS classes to set to each link outputted. + * } + * @return string|array either the html markup of the switcher or the raw elements to build a custom language switcher + */ + public function the_languages( $links, $args = array() ) { + + $this->links = $links; + $args = wp_parse_args( $args, self::DEFAULTS ); + + /** + * Filter the arguments of the 'pll_the_languages' template tag + * + * @since 1.5 + * + * @param array $args + */ + $args = apply_filters( 'pll_the_languages_args', $args ); + + // Force not to hide the language for the widget preview even if the option is checked. + if ( $this->links instanceof PLL_Admin_Links ) { + $args['hide_if_no_translation'] = 0; + } + + // Prevents showing empty options in `' . "\n" . '%5$s' . "\n" . '' . "\n", + esc_attr( $args['name'] ), + isset( $args['id'] ) && ! $args['id'] ? '' : ' id="' . ( empty( $args['id'] ) ? esc_attr( $args['name'] ) : esc_attr( $args['id'] ) ) . '"', + empty( $args['class'] ) ? '' : ' class="' . esc_attr( $args['class'] ) . '"', + disabled( empty( $args['disabled'] ), false, false ), + parent::walk( $elements, $max_depth, $args ) + ); + + return $output; + } +} diff --git a/wp-content/plugins/polylang/include/walker-list.php b/wp-content/plugins/polylang/include/walker-list.php new file mode 100644 index 0000000000..49494dfbe2 --- /dev/null +++ b/wp-content/plugins/polylang/include/walker-list.php @@ -0,0 +1,64 @@ + 'parent', 'id' => 'id' ); + + /** + * Outputs one element + * + * @since 1.2 + * + * @param string $output Passed by reference. Used to append additional content. + * @param stdClass $element The data object. + * @param int $depth Depth of the item. + * @param array $args An array of additional arguments. + * @param int $current_object_id ID of the current item. + * @return void + */ + public function start_el( &$output, $element, $depth = 0, $args = array(), $current_object_id = 0 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $output .= sprintf( + '%6$s
  • %4$s%5$s
  • %7$s', + esc_attr( implode( ' ', $element->classes ) ), + esc_attr( $element->locale ), + esc_url( $element->url ), + $element->flag, + $args['show_flags'] && $args['show_names'] ? sprintf( '%2$s', is_rtl() ? 'right' : 'left', esc_html( $element->name ) ) : esc_html( $element->name ), + 'discard' === $args['item_spacing'] ? '' : "\t", + 'discard' === $args['item_spacing'] ? '' : "\n", + empty( $element->link_classes ) ? '' : 'class="' . esc_attr( implode( ' ', $element->link_classes ) ) . '"' + ); + } + + /** + * Overrides Walker:walk to set depth argument + * + * @since 1.2 + * @since 2.6.7 Use $max_depth and ...$args parameters to follow the move of WP 5.3 + * + * @param array $elements An array of elements. + * @param int $max_depth The maximum hierarchical depth. + * @param mixed ...$args Additional arguments. + * @return string The hierarchical item output. + */ + public function walk( $elements, $max_depth, ...$args ) { // phpcs:ignore WordPressVIPMinimum.Classes.DeclarationCompatibility.DeclarationCompatibility + $this->maybe_fix_walk_args( $max_depth, $args ); + + return parent::walk( $elements, $max_depth, $args ); + } +} diff --git a/wp-content/plugins/polylang/include/walker.php b/wp-content/plugins/polylang/include/walker.php new file mode 100644 index 0000000000..d1f44464ef --- /dev/null +++ b/wp-content/plugins/polylang/include/walker.php @@ -0,0 +1,73 @@ + 'parent', 'id' => 'id' ); + + /** + * Overrides Walker::display_element as it expects an object with a parent property. + * + * @since 1.2 + * @since 3.4 Refactored and moved in `PLL_Walker`. + * + * @param PLL_Language|stdClass $element Data object. `PLL_language` in our case. + * @param array $children_elements List of elements to continue traversing. + * @param int $max_depth Max depth to traverse. + * @param int $depth Depth of current element. + * @param array $args An array of arguments. + * @param string $output Passed by reference. Used to append additional content. + * @return void + */ + public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { + if ( $element instanceof PLL_Language ) { + $element = $element->to_std_class(); + } + + $element->parent = $element->id = 0; // Don't care about this. + + parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output ); + } + + /** + * Sets `PLL_Walker::walk()` arguments as it should + * and triggers an error in case of misuse of them. + * + * @since 3.4 + * + * @param array|int $max_depth The maximum hierarchical depth. Passed by reference. + * @param array $args Additional arguments. Passed by reference. + * @return void + */ + protected function maybe_fix_walk_args( &$max_depth, &$args ) { + if ( ! is_array( $max_depth ) ) { + $args = isset( $args[0] ) ? $args[0] : array(); + return; + } + + // Backward compatibility with Polylang < 2.6.7 + _doing_it_wrong( + __CLASS__ . '::walk()', + 'The method expects an integer as second parameter.', + '2.6.7' + ); + $args = $max_depth; + $max_depth = -1; + } +} diff --git a/wp-content/plugins/polylang/include/widget-calendar.php b/wp-content/plugins/polylang/include/widget-calendar.php new file mode 100644 index 0000000000..26668a63f0 --- /dev/null +++ b/wp-content/plugins/polylang/include/widget-calendar.php @@ -0,0 +1,282 @@ + not very efficient (add 4 to 5 sql queries). + * Method used since 0.5: remove the WP widget and replace it by our own -> our language filter will not work if get_calendar is called directly by a theme. + * + * @since 0.5 + */ +class PLL_Widget_Calendar extends WP_Widget_Calendar { + protected static $pll_instance = 0; // Can't use $instance of WP_Widget_Calendar as it's private :/. + + /** + * Outputs the content for the current Calendar widget instance. + * Modified version of the parent function to call our own get_calendar() method. + * + * @since 0.5 + * + * @param array $args Display arguments including 'before_title', 'after_title', + * 'before_widget', and 'after_widget'. + * @param array $instance The settings for the particular instance of the widget. + */ + public function widget( $args, $instance ) { + $title = ! empty( $instance['title'] ) ? $instance['title'] : ''; + + /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ + $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); + + echo $args['before_widget']; + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; + } + if ( 0 === self::$pll_instance ) { #modified# + echo '
    '; + } else { + echo '
    '; + } + empty( PLL()->curlang ) ? get_calendar() : self::get_calendar(); #modified# + echo '
    '; + echo $args['after_widget']; + + ++self::$pll_instance; #modified# + } + + /** + * Modified version of the WP get_calendar() function to filter the queries. + * + * @since 0.5 + * + * @param bool $initial Optional, default is true. Use initial calendar names. + * @param bool $display Optional, default is true. Set to false for return. + * @return void|string Void if `$display` argument is true, calendar HTML if `$display` is false. + */ + static public function get_calendar( $initial = true, $display = true ) { + global $wpdb, $m, $monthnum, $year, $wp_locale, $posts; + + $join_clause = PLL()->model->post->join_clause(); #added# + $where_clause = PLL()->model->post->where_clause( PLL()->curlang ); #added# + + $key = md5( PLL()->curlang->slug . $m . $monthnum . $year ); #modified# + $cache = wp_cache_get( 'get_calendar', 'calendar' ); + + if ( $cache && is_array( $cache ) && isset( $cache[ $key ] ) ) { + /** This filter is documented in wp-includes/general-template.php */ + $output = apply_filters( 'get_calendar', $cache[ $key ] ); + + if ( $display ) { + echo $output; + return; + } + + return $output; + } + + if ( ! is_array( $cache ) ) { + $cache = array(); + } + + // Quick check. If we have no posts at all, abort! + if ( ! $posts ) { + $gotsome = $wpdb->get_var( "SELECT 1 as test FROM $wpdb->posts WHERE post_type = 'post' AND post_status = 'publish' LIMIT 1" ); + if ( ! $gotsome ) { + $cache[ $key ] = ''; + wp_cache_set( 'get_calendar', $cache, 'calendar' ); + return; + } + } + + if ( isset( $_GET['w'] ) ) { + $w = (int) $_GET['w']; + } + // week_begins = 0 stands for Sunday. + $week_begins = (int) get_option( 'start_of_week' ); + + // Let's figure out when we are. + if ( ! empty( $monthnum ) && ! empty( $year ) ) { + $thismonth = zeroise( (int) $monthnum, 2 ); + $thisyear = (int) $year; + } elseif ( ! empty( $w ) ) { + // We need to get the month from MySQL. + $thisyear = (int) substr( $m, 0, 4 ); + // It seems MySQL's weeks disagree with PHP's. + $d = ( ( $w - 1 ) * 7 ) + 6; + $thismonth = $wpdb->get_var( "SELECT DATE_FORMAT((DATE_ADD('{$thisyear}0101', INTERVAL $d DAY) ), '%m')" ); + } elseif ( ! empty( $m ) ) { + $thisyear = (int) substr( $m, 0, 4 ); + if ( strlen( $m ) < 6 ) { + $thismonth = '01'; + } else { + $thismonth = zeroise( (int) substr( $m, 4, 2 ), 2 ); + } + } else { + $thisyear = current_time( 'Y' ); + $thismonth = current_time( 'm' ); + } + + $unixmonth = mktime( 0, 0, 0, $thismonth, 1, $thisyear ); + $last_day = gmdate( 't', $unixmonth ); + + // Get the next and previous month and year with at least one post. + $previous = $wpdb->get_row( + "SELECT MONTH(post_date) AS month, YEAR(post_date) AS year + FROM $wpdb->posts $join_clause + WHERE post_date < '$thisyear-$thismonth-01' + AND post_type = 'post' AND post_status = 'publish' $where_clause + ORDER BY post_date DESC + LIMIT 1" + ); #modified# + $next = $wpdb->get_row( + "SELECT MONTH(post_date) AS month, YEAR(post_date) AS year + FROM $wpdb->posts $join_clause + WHERE post_date > '$thisyear-$thismonth-{$last_day} 23:59:59' + AND post_type = 'post' AND post_status = 'publish' $where_clause + ORDER BY post_date ASC + LIMIT 1" + ); #modified# + + /* translators: Calendar caption: 1: Month name, 2: 4-digit year. */ + $calendar_caption = _x( '%1$s %2$s', 'calendar caption' ); + $calendar_output = ' + + + '; + + $myweek = array(); + + for ( $wdcount = 0; $wdcount <= 6; $wdcount++ ) { + $myweek[] = $wp_locale->get_weekday( ( $wdcount + $week_begins ) % 7 ); + } + + foreach ( $myweek as $wd ) { + $day_name = $initial ? $wp_locale->get_weekday_initial( $wd ) : $wp_locale->get_weekday_abbrev( $wd ); + $wd = esc_attr( $wd ); + $calendar_output .= "\n\t\t"; + } + + $calendar_output .= ' + + + + '; + + $daywithpost = array(); + + // Get days with posts. + $dayswithposts = $wpdb->get_results( + "SELECT DISTINCT DAYOFMONTH(post_date) + FROM $wpdb->posts $join_clause WHERE post_date >= '{$thisyear}-{$thismonth}-01 00:00:00' + AND post_type = 'post' AND post_status = 'publish' + AND post_date <= '{$thisyear}-{$thismonth}-{$last_day} 23:59:59' $where_clause", + ARRAY_N + ); #modified# + + if ( $dayswithposts ) { + foreach ( (array) $dayswithposts as $daywith ) { + $daywithpost[] = (int) $daywith[0]; + } + } + + // See how much we should pad in the beginning. + $pad = calendar_week_mod( gmdate( 'w', $unixmonth ) - $week_begins ); + if ( 0 != $pad ) { + $calendar_output .= "\n\t\t" . ''; + } + + $newrow = false; + $daysinmonth = (int) gmdate( 't', $unixmonth ); + + for ( $day = 1; $day <= $daysinmonth; ++$day ) { + if ( isset( $newrow ) && $newrow ) { + $calendar_output .= "\n\t\n\t\n\t\t"; + } + $newrow = false; + + if ( current_time( 'j' ) == $day && + current_time( 'm' ) == $thismonth && + current_time( 'Y' ) == $thisyear ) { + $calendar_output .= ''; + + if ( 6 == calendar_week_mod( gmdate( 'w', mktime( 0, 0, 0, $thismonth, $day, $thisyear ) ) - $week_begins ) ) { + $newrow = true; + } + } + + $pad = 7 - calendar_week_mod( gmdate( 'w', mktime( 0, 0, 0, $thismonth, $day, $thisyear ) ) - $week_begins ); + if ( 0 != $pad && 7 != $pad ) { + $calendar_output .= "\n\t\t" . ''; + } + + $calendar_output .= "\n\t\n\t"; + + $calendar_output .= "\n\t
    ' . sprintf( + $calendar_caption, + $wp_locale->get_month( $thismonth ), + gmdate( 'Y', $unixmonth ) + ) . '
    $day_name
     
    '; + } else { + $calendar_output .= ''; + } + + if ( in_array( $day, $daywithpost, true ) ) { + // Any posts today? + $date_format = gmdate( _x( 'F j, Y', 'daily archives date format' ), strtotime( "{$thisyear}-{$thismonth}-{$day}" ) ); + /* translators: Post calendar label. %s: Date. */ + $label = sprintf( __( 'Posts published on %s' ), $date_format ); + $calendar_output .= sprintf( + '%s', + get_day_link( $thisyear, $thismonth, $day ), + esc_attr( $label ), + $day + ); + } else { + $calendar_output .= $day; + } + + $calendar_output .= ' 
    "; + + $calendar_output .= ''; + + $cache[ $key ] = $calendar_output; + wp_cache_set( 'get_calendar', $cache, 'calendar' ); + + if ( $display ) { + /** This filter is documented in wp-includes/general-template.php */ + echo apply_filters( 'get_calendar', $calendar_output ); + return; + } + /** This filter is documented in wp-includes/general-template.php */ + return apply_filters( 'get_calendar', $calendar_output ); + } +} diff --git a/wp-content/plugins/polylang/include/widget-languages.php b/wp-content/plugins/polylang/include/widget-languages.php new file mode 100644 index 0000000000..5483216ca2 --- /dev/null +++ b/wp-content/plugins/polylang/include/widget-languages.php @@ -0,0 +1,141 @@ + __( 'Displays a language switcher', 'polylang' ), + 'customize_selective_refresh' => true, + ) + ); + } + + /** + * Displays the widget + * + * @since 0.1 + * + * @param array $args Display arguments including before_title, after_title, before_widget, and after_widget. + * @param array $instance The settings for the particular instance of the widget + * @return void + */ + public function widget( $args, $instance ) { + // Sets a unique id for dropdown. + $instance['dropdown'] = empty( $instance['dropdown'] ) ? 0 : $this->id; + $instance['echo'] = 0; + $instance['raw'] = 0; + $list = pll_the_languages( $instance ); + + if ( $list ) { + $title = empty( $instance['title'] ) ? '' : $instance['title']; + + /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ + $title = apply_filters( 'widget_title', $title, $instance, $this->id_base ); + + echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput + + if ( $title ) { + echo $args['before_title'] . $title . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput + } + + // The title may be filtered: Strip out HTML and make sure the aria-label is never empty. + $aria_label = trim( wp_strip_all_tags( $title ) ); + if ( ! $aria_label ) { + $aria_label = __( 'Choose a language', 'polylang' ); + } + + if ( $instance['dropdown'] ) { + echo ''; + echo $list; // phpcs:ignore WordPress.Security.EscapeOutput + } else { + $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml'; + + /** This filter is documented in wp-includes/widgets/class-wp-nav-menu-widget.php */ + $format = apply_filters( 'navigation_widgets_format', $format ); + + if ( 'html5' === $format ) { + echo ''; + } + } + + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Updates the widget options + * + * @since 0.4 + * + * @param array $new_instance New settings for this instance as input by the user via form() + * @param array $old_instance Old settings for this instance + * @return array Settings to save or bool false to cancel saving + */ + public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $instance = array( 'title' => sanitize_text_field( $new_instance['title'] ) ); + foreach ( array_keys( PLL_Switcher::get_switcher_options( 'widget' ) ) as $key ) { + $instance[ $key ] = ! empty( $new_instance[ $key ] ) ? 1 : 0; + } + + return $instance; + } + + /** + * Displays the widget form. + * + * @since 0.4 + * + * @param array $instance Current settings. + * @return string + */ + public function form( $instance ) { + // Default values + $instance = wp_parse_args( (array) $instance, array_merge( array( 'title' => '' ), PLL_Switcher::get_switcher_options( 'widget', 'default' ) ) ); + + // Title + printf( + '

    ', + esc_attr( $this->get_field_id( 'title' ) ), + esc_html__( 'Title:', 'polylang' ), + esc_attr( $this->get_field_name( 'title' ) ), + esc_attr( $instance['title'] ) + ); + + foreach ( PLL_Switcher::get_switcher_options( 'widget' ) as $key => $str ) { + printf( + '
    ', + esc_attr( $this->get_field_id( $key ) ), + esc_attr( $this->get_field_name( $key ) ), + checked( $instance[ $key ], true, false ), + esc_html( $str ), + in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? sprintf( ' class="no-dropdown-%s"', esc_attr( $this->id ) ) : '', + ( ! empty( $instance['dropdown'] ) && in_array( $key, array( 'show_names', 'show_flags', 'hide_current' ) ) ? ' style="display:none;"' : '' ), + esc_attr( 'pll-' . $key ) + ); + } + + return ''; // Because the parent class returns a string, however not used. + } +} diff --git a/wp-content/plugins/polylang/install/install-base.php b/wp-content/plugins/polylang/install/install-base.php new file mode 100644 index 0000000000..eb2b2b637d --- /dev/null +++ b/wp-content/plugins/polylang/install/install-base.php @@ -0,0 +1,134 @@ +plugin_basename = $plugin_basename; + + // Manages plugin activation and deactivation + register_activation_hook( $plugin_basename, array( $this, 'activate' ) ); + register_deactivation_hook( $plugin_basename, array( $this, 'deactivate' ) ); + + // Site creation on multisite. + add_action( 'wp_initialize_site', array( $this, 'new_site' ), 50 ); // After WP (prio 10). + } + + /** + * Allows to detect plugin deactivation + * + * @since 1.7 + * + * @return bool true if the plugin is currently being deactivated + */ + public function is_deactivation() { + return isset( $_GET['action'], $_GET['plugin'] ) && 'deactivate' === $_GET['action'] && $this->plugin_basename === $_GET['plugin']; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Activation or deactivation for all blogs. + * + * @since 1.2 + * + * @param string $what Either 'activate' or 'deactivate'. + * @param bool $networkwide Whether the plugin is (de)activated for all sites in the network or just the current site. + * @return void + */ + protected function do_for_all_blogs( $what, $networkwide ) { + // Network + if ( is_multisite() && $networkwide ) { + global $wpdb; + + foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { + switch_to_blog( $blog_id ); + 'activate' == $what ? $this->_activate() : $this->_deactivate(); + } + restore_current_blog(); + } + + // Single blog + else { + 'activate' == $what ? $this->_activate() : $this->_deactivate(); + } + } + + /** + * Plugin activation for multisite. + * + * @since 1.7 + * + * @param bool $networkwide Whether the plugin is activated for all sites in the network or just the current site. + * @return void + */ + public function activate( $networkwide ) { + $this->do_for_all_blogs( 'activate', $networkwide ); + } + + /** + * Plugin activation + * + * @since 0.5 + * + * @return void + */ + protected function _activate() { + // Can be overridden in child class + } + + /** + * Plugin deactivation for multisite. + * + * @since 0.1 + * + * @param bool $networkwide Whether the plugin is deactivated for all sites in the network or just the current site. + * @return void + */ + public function deactivate( $networkwide ) { + $this->do_for_all_blogs( 'deactivate', $networkwide ); + } + + /** + * Plugin deactivation + * + * @since 0.5 + * + * @return void + */ + protected function _deactivate() { + // Can be overridden in child class + } + + /** + * Site creation on multisite ( to set default options ) + * + * @since 2.6.8 + * + * @param WP_Site $new_site New site object. + * @return void + */ + public function new_site( $new_site ) { + switch_to_blog( $new_site->id ); + $this->_activate(); + restore_current_blog(); + } +} diff --git a/wp-content/plugins/polylang/install/install.php b/wp-content/plugins/polylang/install/install.php new file mode 100644 index 0000000000..680ab2af32 --- /dev/null +++ b/wp-content/plugins/polylang/install/install.php @@ -0,0 +1,148 @@ +

    %s

    ', + sprintf( + /* translators: 1: Plugin name 2: Current PHP version 3: Required PHP version */ + esc_html__( '%1$s has deactivated itself because you are using an old version of PHP. You are using using PHP %2$s. %1$s requires PHP %3$s.', 'polylang' ), + esc_html( POLYLANG ), + PHP_VERSION, + esc_html( PLL_MIN_PHP_VERSION ) + ) + ); + } + + /** + * Displays a notice if WP min version is not met. + * + * @since 2.6.7 + * + * @return void + */ + public function wp_version_notice() { + global $wp_version; + + load_plugin_textdomain( 'polylang' ); // Plugin i18n. + + printf( + '

    %s

    ', + sprintf( + /* translators: 1: Plugin name 2: Current WordPress version 3: Required WordPress version */ + esc_html__( '%1$s has deactivated itself because you are using an old version of WordPress. You are using using WordPress %2$s. %1$s requires at least WordPress %3$s.', 'polylang' ), + esc_html( POLYLANG ), + esc_html( $wp_version ), + esc_html( PLL_MIN_WP_VERSION ) + ) + ); + } + + /** + * Get default Polylang options. + * + * @since 1.8 + * + * @return array + */ + public static function get_default_options() { + return array( + 'browser' => 0, // Default language for the front page is not set by browser preference (was the opposite before 3.1). + 'rewrite' => 1, // Remove /language/ in permalinks (was the opposite before 0.7.2). + 'hide_default' => 1, // Remove URL language information for default language (was the opposite before 2.1.5). + 'force_lang' => 1, // Add URL language information (was 0 before 1.7). + 'redirect_lang' => 0, // Do not redirect the language page to the homepage. + 'media_support' => 0, // Do not support languages and translation for media by default (was the opposite before 3.1). + 'uninstall' => 0, // Do not remove data when uninstalling Polylang. + 'sync' => array(), // Synchronisation is disabled by default (was the opposite before 1.2). + 'post_types' => array(), + 'taxonomies' => array(), + 'domains' => array(), + 'version' => POLYLANG_VERSION, + 'first_activation' => time(), + ); + } + + /** + * Plugin activation + * + * @since 0.5 + * + * @return void + */ + protected function _activate() { + if ( $options = get_option( 'polylang' ) ) { + // Check if we will be able to upgrade + if ( version_compare( $options['version'], POLYLANG_VERSION, '<' ) ) { + $upgrade = new PLL_Upgrade( $options ); + $upgrade->can_activate(); + } + } + // Defines default values for options in case this is the first installation + else { + update_option( 'polylang', self::get_default_options() ); + } + + // Avoid 1 query on every pages if no wpml strings is registered + if ( ! get_option( 'polylang_wpml_strings' ) ) { + update_option( 'polylang_wpml_strings', array() ); + } + + // Don't use flush_rewrite_rules at network activation. See #32471 + // Thanks to RavanH for the trick. See https://polylang.wordpress.com/2015/06/10/polylang-1-7-6-and-multisite/ + // Rewrite rules are created at next page load :) + delete_option( 'rewrite_rules' ); + } + + /** + * Plugin deactivation + * + * @since 0.5 + * + * @return void + */ + protected function _deactivate() { + delete_option( 'rewrite_rules' ); // Don't use flush_rewrite_rules at network activation. See #32471 + } +} diff --git a/wp-content/plugins/polylang/install/plugin-updater.php b/wp-content/plugins/polylang/install/plugin-updater.php new file mode 100644 index 0000000000..b0a330f040 --- /dev/null +++ b/wp-content/plugins/polylang/install/plugin-updater.php @@ -0,0 +1,645 @@ +api_url = trailingslashit( $_api_url ); + $this->api_data = $_api_data; + $this->plugin_file = $_plugin_file; + $this->name = plugin_basename( $_plugin_file ); + $this->slug = basename( $_plugin_file, '.php' ); + $this->version = $_api_data['version']; + $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false; + $this->beta = ! empty( $this->api_data['beta'] ) ? true : false; + $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url ); + + $edd_plugin_data[ $this->slug ] = $this->api_data; + + /** + * Fires after the $edd_plugin_data is setup. + * + * @since x.x.x + * + * @param array $edd_plugin_data Array of EDD SL plugin data. + */ + do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data ); + + // Set up hooks. + $this->init(); + + } + + /** + * Set up WordPress filters to hook into WP's update process. + * + * @uses add_filter() + * + * @return void + */ + public function init() { + + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) ); + add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 ); + add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 ); + add_action( 'admin_init', array( $this, 'show_changelog' ) ); + + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update API just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @uses api_request() + * + * @param array $_transient_data Update array build by WordPress. + * @return array Modified update array with custom plugin data. + */ + public function check_update( $_transient_data ) { + + global $pagenow; + + if ( ! is_object( $_transient_data ) ) { + $_transient_data = new stdClass(); + } + + if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) { + return $_transient_data; + } + + $current = $this->get_repo_api_data(); + if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) { + if ( version_compare( $this->version, $current->new_version, '<' ) ) { + $_transient_data->response[ $this->name ] = $current; + } else { + // Populating the no_update information is required to support auto-updates in WordPress 5.5. + $_transient_data->no_update[ $this->name ] = $current; + } + } + $_transient_data->last_checked = time(); + $_transient_data->checked[ $this->name ] = $this->version; + + return $_transient_data; + } + + /** + * Get repo API data from store. + * Save to cache. + * + * @return \stdClass + */ + public function get_repo_api_data() { + $version_info = $this->get_cached_version_info(); + + if ( false === $version_info ) { + $version_info = $this->api_request( + 'plugin_latest_version', + array( + 'slug' => $this->slug, + 'beta' => $this->beta, + ) + ); + if ( ! $version_info ) { + return false; + } + + // This is required for your plugin to support auto-updates in WordPress 5.5. + $version_info->plugin = $this->name; + $version_info->id = $this->name; + + $this->set_version_info_cache( $version_info ); + } + + return $version_info; + } + + /** + * Show the update notification on multisite subsites. + * + * @param string $file + * @param array $plugin + */ + public function show_update_notification( $file, $plugin ) { + + // Return early if in the network admin, or if this is not a multisite install. + if ( is_network_admin() || ! is_multisite() ) { + return; + } + + // Allow single site admins to see that an update is available. + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + if ( $this->name !== $file ) { + return; + } + + // Do not print any message if update does not exist. + $update_cache = get_site_transient( 'update_plugins' ); + + if ( ! isset( $update_cache->response[ $this->name ] ) ) { + if ( ! is_object( $update_cache ) ) { + $update_cache = new stdClass(); + } + $update_cache->response[ $this->name ] = $this->get_repo_api_data(); + } + + // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin. + if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) { + return; + } + + printf( + '', + $this->slug, + $file, + in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive' + ); + + echo ''; + echo '

    '; + + $changelog_link = ''; + if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) { + $changelog_link = add_query_arg( + array( + 'edd_sl_action' => 'view_plugin_changelog', + 'plugin' => urlencode( $this->name ), + 'slug' => urlencode( $this->slug ), + 'TB_iframe' => 'true', + 'width' => 77, + 'height' => 911, + ), + self_admin_url( 'index.php' ) + ); + } + $update_link = add_query_arg( + array( + 'action' => 'upgrade-plugin', + 'plugin' => urlencode( $this->name ), + ), + self_admin_url( 'update.php' ) + ); + + printf( + /* translators: the plugin name. */ + esc_html__( 'There is a new version of %1$s available.', 'polylang' ), + esc_html( $plugin['Name'] ) + ); + + if ( ! current_user_can( 'update_plugins' ) ) { + echo ' '; + esc_html_e( 'Contact your network administrator to install the update.', 'polylang' ); + } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */ + __( '%1$sView version %2$s details%3$s.', 'polylang' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '' + ); + } elseif ( ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. and 4. are opening anchor tags 2. the new plugin version 3. and 5. are closing anchor tags. */ + __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'polylang' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '', + '', + '' + ); + } else { + printf( + ' %1$s%2$s%3$s', + '', + esc_html__( 'Update now.', 'polylang' ), + '' + ); + } + + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin ); + + echo '

    '; + } + + /** + * Gets the plugins active in a multisite network. + * + * @return array + */ + private function get_active_plugins() { + $active_plugins = (array) get_option( 'active_plugins' ); + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' ); + + return array_merge( $active_plugins, array_keys( $active_network_plugins ) ); + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @uses api_request() + * + * @param mixed $_data + * @param string $_action + * @param object $_args + * @return object $_data + */ + public function plugins_api_filter( $_data, $_action = '', $_args = null ) { + + if ( 'plugin_information' !== $_action ) { + + return $_data; + + } + + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) { + + return $_data; + + } + + $to_send = array( + 'slug' => $this->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => array(), + 'reviews' => false, + 'icons' => array(), + ), + ); + + // Get the transient where we store the api request for this plugin for 24 hours + $edd_api_request_transient = $this->get_cached_version_info(); + + //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now. + if ( empty( $edd_api_request_transient ) ) { + + $api_response = $this->api_request( 'plugin_information', $to_send ); + + // Expires in 3 hours + $this->set_version_info_cache( $api_response ); + + if ( false !== $api_response ) { + $_data = $api_response; + } + } else { + $_data = $edd_api_request_transient; + } + + // Convert sections into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) { + $_data->sections = $this->convert_object_to_array( $_data->sections ); + } + + // Convert banners into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) { + $_data->banners = $this->convert_object_to_array( $_data->banners ); + } + + // Convert icons into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) { + $_data->icons = $this->convert_object_to_array( $_data->icons ); + } + + // Convert contributors into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) { + $_data->contributors = $this->convert_object_to_array( $_data->contributors ); + } + + if ( ! isset( $_data->plugin ) ) { + $_data->plugin = $this->name; + } + + return $_data; + } + + /** + * Convert some objects to arrays when injecting data into the update API + * + * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON + * decoding, they are objects. This method allows us to pass in the object and return an associative array. + * + * @since 3.6.5 + * + * @param stdClass $data + * + * @return array + */ + private function convert_object_to_array( $data ) { + if ( ! is_array( $data ) && ! is_object( $data ) ) { + return array(); + } + $new_data = array(); + foreach ( $data as $key => $value ) { + $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value; + } + + return $new_data; + } + + /** + * Disable SSL verification in order to prevent download update failures + * + * @param array $args + * @param string $url + * @return object $array + */ + public function http_request_args( $args, $url ) { + + if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) { + $args['sslverify'] = $this->verify_ssl(); + } + return $args; + + } + + /** + * Calls the API and, if successful, returns the object delivered by the API. + * + * @uses get_bloginfo() + * @uses wp_remote_post() + * @uses is_wp_error() + * + * @param string $_action The requested action. + * @param array $_data Parameters for the API action. + * @return false|object|void + */ + private function api_request( $_action, $_data ) { + $data = array_merge( $this->api_data, $_data ); + + if ( $data['slug'] !== $this->slug ) { + return; + } + + // Don't allow a plugin to ping itself + if ( trailingslashit( home_url() ) === $this->api_url ) { + return false; + } + + if ( $this->request_recently_failed() ) { + return false; + } + + return $this->get_version_from_remote(); + } + + /** + * Determines if a request has recently failed. + * + * @since 1.9.1 + * + * @return bool + */ + private function request_recently_failed() { + $failed_request_details = get_option( $this->failed_request_cache_key ); + + // Request has never failed. + if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) { + return false; + } + + /* + * Request previously failed, but the timeout has expired. + * This means we're allowed to try again. + */ + if ( time() > $failed_request_details ) { + delete_option( $this->failed_request_cache_key ); + + return false; + } + + return true; + } + + /** + * Logs a failed HTTP request for this API URL. + * We set a timestamp for 1 hour from now. This prevents future API requests from being + * made to this domain for 1 hour. Once the timestamp is in the past, API requests + * will be allowed again. This way if the site is down for some reason we don't bombard + * it with failed API requests. + * + * @see EDD_SL_Plugin_Updater::request_recently_failed + * + * @since 1.9.1 + */ + private function log_failed_request() { + update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) ); + } + + /** + * If available, show the changelog for sites in a multisite install. + */ + public function show_changelog() { + + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) { + return; + } + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) { + return; + } + + if ( ! current_user_can( 'update_plugins' ) ) { + wp_die( esc_html__( 'You do not have permission to install plugin updates', 'polylang' ), esc_html__( 'Error', 'polylang' ), array( 'response' => 403 ) ); + } + + $version_info = $this->get_repo_api_data(); + if ( isset( $version_info->sections ) ) { + $sections = $this->convert_object_to_array( $version_info->sections ); + if ( ! empty( $sections['changelog'] ) ) { + echo '
    ' . wp_kses_post( $sections['changelog'] ) . '
    '; + } + } + + exit; + } + + /** + * Gets the current version information from the remote site. + * + * @return array|false + */ + private function get_version_from_remote() { + $api_params = array( + 'edd_action' => 'get_version', + 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '', + 'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false, + 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false, + 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false, + 'slug' => $this->slug, + 'author' => $this->api_data['author'], + 'url' => home_url(), + 'beta' => $this->beta, + 'php_version' => phpversion(), + 'wp_version' => get_bloginfo( 'version' ), + ); + + /** + * Filters the parameters sent in the API request. + * + * @param array $api_params The array of data sent in the request. + * @param array $this->api_data The array of data set up in the class constructor. + * @param string $this->plugin_file The full path and filename of the file. + */ + $api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file ); + + $request = wp_remote_post( + $this->api_url, + array( + 'timeout' => 15, + 'sslverify' => $this->verify_ssl(), + 'body' => $api_params, + ) + ); + + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) { + $this->log_failed_request(); + + return false; + } + + $request = json_decode( wp_remote_retrieve_body( $request ) ); + + if ( $request && isset( $request->sections ) ) { + $request->sections = maybe_unserialize( $request->sections ); + } else { + $request = false; + } + + if ( $request && isset( $request->banners ) ) { + $request->banners = maybe_unserialize( $request->banners ); + } + + if ( $request && isset( $request->icons ) ) { + $request->icons = maybe_unserialize( $request->icons ); + } + + if ( ! empty( $request->sections ) ) { + foreach ( $request->sections as $key => $section ) { + $request->$key = (array) $section; + } + } + + return $request; + } + + /** + * Get the version info from the cache, if it exists. + * + * @param string $cache_key + * @return object + */ + public function get_cached_version_info( $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $cache = get_option( $cache_key ); + + // Cache is expired + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { + return false; + } + + // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point. + $cache['value'] = json_decode( $cache['value'] ); + if ( ! empty( $cache['value']->icons ) ) { + $cache['value']->icons = (array) $cache['value']->icons; + } + + return $cache['value']; + + } + + /** + * Adds the plugin version information to the database. + * + * @param string $value + * @param string $cache_key + */ + public function set_version_info_cache( $value = '', $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $data = array( + 'timeout' => strtotime( '+3 hours', time() ), + 'value' => wp_json_encode( $value ), + ); + + update_option( $cache_key, $data, 'no' ); + + // Delete the duplicate option + delete_option( 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) ) ); + } + + /** + * Returns if the SSL of the store should be verified. + * + * @since 1.6.13 + * @return bool + */ + private function verify_ssl() { + return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this ); + } + + /** + * Gets the unique key (option name) for a plugin. + * + * @since 1.9.0 + * @return string + */ + private function get_cache_key() { + $string = $this->slug . $this->api_data['license'] . $this->beta; + + return 'edd_sl_' . md5( serialize( $string ) ); + } + +} diff --git a/wp-content/plugins/polylang/install/t15s.php b/wp-content/plugins/polylang/install/t15s.php new file mode 100644 index 0000000000..3354fc20b4 --- /dev/null +++ b/wp-content/plugins/polylang/install/t15s.php @@ -0,0 +1,237 @@ +slug = $slug; + $this->api_url = $api_url; + + add_action( 'init', array( __CLASS__, 'register_clean_translations_cache' ), 9999 ); + add_filter( 'translations_api', array( $this, 'translations_api' ), 10, 3 ); + add_filter( 'site_transient_update_plugins', array( $this, 'site_transient_update_plugins' ) ); + } + + /** + * Short-circuits translations API requests for private projects. + * + * @since 2.6 + * + * @param bool|array $result The result object. Default false. + * @param string $requested_type The type of translations being requested. + * @param object $args Translation API arguments. + * @return bool|array + */ + public function translations_api( $result, $requested_type, $args ) { + if ( 'plugins' === $requested_type && $this->slug === $args['slug'] ) { + return self::get_translations( $args['slug'], $this->api_url ); + } + + return $result; + } + + /** + * Filters the translations transients to include the private plugin or theme. + * + * @see wp_get_translation_updates() + * + * @since 2.6 + * + * @param bool|array $value The transient value. + * @return bool|array + */ + public function site_transient_update_plugins( $value ) { + if ( ! $value ) { + $value = new stdClass(); + } + + if ( ! isset( $value->translations ) ) { + $value->translations = array(); + } + + $translations = self::get_translations( $this->slug, $this->api_url ); + + if ( ! isset( $translations['translations'] ) ) { + return $value; + } + + $installed_translations = self::get_installed_translations(); + + foreach ( (array) $translations['translations'] as $translation ) { + if ( in_array( $translation['language'], self::get_available_languages() ) ) { + if ( isset( $installed_translations[ $this->slug ][ $translation['language'] ] ) && $translation['updated'] ) { + $local = new DateTime( $installed_translations[ $this->slug ][ $translation['language'] ]['PO-Revision-Date'] ); + $remote = new DateTime( $translation['updated'] ); + + if ( $local >= $remote ) { + continue; + } + } + + $translation['type'] = 'plugin'; + $translation['slug'] = $this->slug; + + $value->translations[] = $translation; + } + } + + return $value; + } + + /** + * Registers actions for clearing translation caches. + * + * @since 2.6 + * + * @return void + */ + public static function register_clean_translations_cache() { + add_action( 'set_site_transient_update_plugins', array( __CLASS__, 'clean_translations_cache' ) ); + add_action( 'delete_site_transient_update_plugins', array( __CLASS__, 'clean_translations_cache' ) ); + } + + /** + * Clears existing translation cache. + * + * @since 2.6 + * + * @return void + */ + public static function clean_translations_cache() { + $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN ); + + if ( ! is_object( $translations ) ) { + return; + } + + /* + * Don't delete the cache if the transient gets changed multiple times + * during a single request. Set cache lifetime to maximum 15 seconds. + */ + $cache_lifespan = 15; + $time_not_changed = isset( $translations->_last_checked ) && ( time() - $translations->_last_checked ) > $cache_lifespan; + + if ( ! $time_not_changed ) { + return; + } + + delete_site_transient( self::TRANSIENT_KEY_PLUGIN ); + } + + /** + * Gets the translations for a given project. + * + * @since 2.6 + * + * @param string $slug Project directory slug. + * @param string $url Full GlotPress API URL for the project. + * @return array Translation data. + */ + private static function get_translations( $slug, $url ) { + $translations = get_site_transient( self::TRANSIENT_KEY_PLUGIN ); + + if ( ! is_object( $translations ) ) { + $translations = new stdClass(); + } + + if ( isset( $translations->{$slug} ) && is_array( $translations->{$slug} ) ) { + return $translations->{$slug}; + } + + $result = json_decode( wp_remote_retrieve_body( wp_remote_get( $url, array( 'timeout' => 3 ) ) ), true ); + + // Nothing found. + if ( ! is_array( $result ) ) { + $result = array(); + } + + $translations->{$slug} = $result; + $translations->_last_checked = time(); + + set_site_transient( self::TRANSIENT_KEY_PLUGIN, $translations ); + return $result; + } + + /** + * Returns installed translations. + * + * Used to cache the result of wp_get_installed_translations() as it is very expensive. + * + * @since 2.8 + * + * @return array + */ + private static function get_installed_translations() { + if ( null === self::$installed_translations ) { + self::$installed_translations = wp_get_installed_translations( 'plugins' ); + } + return self::$installed_translations; + } + + /** + * Returns available languages. + * + * Used to cache the result of get_available_languages() as it is very expensive. + * + * @since 2.8 + * + * @return array + */ + private static function get_available_languages() { + if ( null === self::$available_languages ) { + self::$available_languages = get_available_languages(); + } + return self::$available_languages; + } +} diff --git a/wp-content/plugins/polylang/install/upgrade.php b/wp-content/plugins/polylang/install/upgrade.php new file mode 100644 index 0000000000..d6a267b5c3 --- /dev/null +++ b/wp-content/plugins/polylang/install/upgrade.php @@ -0,0 +1,288 @@ +options = &$options; + } + + /** + * Check if upgrade is possible otherwise die to avoid activation + * + * @since 1.2 + * + * @return void + */ + public function can_activate() { + if ( ! $this->can_upgrade() ) { + ob_start(); + $this->admin_notices(); // FIXME the error message is displayed two times + die( ob_get_contents() ); // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Upgrades if possible otherwise returns false to stop Polylang loading + * + * @since 1.2 + * + * @return bool true if upgrade is possible, false otherwise + */ + public function upgrade() { + if ( ! $this->can_upgrade() ) { + add_action( 'all_admin_notices', array( $this, 'admin_notices' ) ); + return false; + } + + delete_transient( 'pll_languages_list' ); + add_action( 'admin_init', array( $this, '_upgrade' ) ); + return true; + } + + + /** + * Check if we the previous version is not too old + * Upgrades if OK + * /!\ never start any upgrade before admin_init as it is likely to conflict with some other plugins + * + * @since 1.2 + * + * @return bool true if upgrade is possible, false otherwise + */ + public function can_upgrade() { + // Don't manage upgrade from version < 1.8 + return version_compare( $this->options['version'], '1.8', '>=' ); + } + + /** + * Displays a notice when ugrading from a too old version + * + * @since 1.0 + * + * @return void + */ + public function admin_notices() { + load_plugin_textdomain( 'polylang' ); + printf( + '

    %s

    %s

    ', + esc_html__( 'Polylang has been deactivated because you upgraded from a too old version.', 'polylang' ), + sprintf( + /* translators: %1$s and %2$s are Polylang version numbers */ + esc_html__( 'Before upgrading to %2$s, please upgrade to %1$s.', 'polylang' ), + '2.9', + POLYLANG_VERSION // phpcs:ignore WordPress.Security.EscapeOutput + ) + ); + } + + /** + * Upgrades the plugin depending on the previous version + * + * @since 1.2 + * + * @return void + */ + public function _upgrade() { + foreach ( array( '2.0.8', '2.1', '2.7', '3.4' ) as $version ) { + if ( version_compare( $this->options['version'], $version, '<' ) ) { + $method_to_call = array( $this, 'upgrade_' . str_replace( '.', '_', $version ) ); + if ( is_callable( $method_to_call ) ) { + call_user_func( $method_to_call ); + } + } + } + + $this->options['previous_version'] = $this->options['version']; // Remember the previous version of Polylang since v1.7.7 + $this->options['version'] = POLYLANG_VERSION; + update_option( 'polylang', $this->options ); + } + + /** + * Upgrades if the previous version is < 2.0.8 + * Changes the user meta 'user_lang' to 'locale' to match WP 4.7 choice + * + * @since 2.0.8 + * + * @return void + */ + protected function upgrade_2_0_8() { + global $wpdb; + $wpdb->update( $wpdb->usermeta, array( 'meta_key' => 'locale' ), array( 'meta_key' => 'user_lang' ) ); + } + + /** + * Upgrades if the previous version is < 2.1. + * Moves strings translations from polylang_mo post_content to post meta _pll_strings_translations. + * + * @since 2.1 + * + * @return void + */ + protected function upgrade_2_1() { + $posts = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + ) + ); + + if ( is_array( $posts ) ) { + foreach ( $posts as $post ) { + $meta = get_post_meta( $post->ID, '_pll_strings_translations', true ); + + if ( empty( $meta ) ) { + $strings = maybe_unserialize( $post->post_content ); + if ( is_array( $strings ) ) { + update_post_meta( $post->ID, '_pll_strings_translations', $strings ); + } + } + } + } + } + + /** + * Upgrades if the previous version is < 2.7 + * Replace numeric keys by hashes in WPML registered strings + * Dismiss the wizard notice for existing sites + * + * @since 2.7 + * + * @return void + */ + protected function upgrade_2_7() { + $strings = get_option( 'polylang_wpml_strings' ); + if ( is_array( $strings ) ) { + $new_strings = array(); + + foreach ( $strings as $string ) { + $context = $string['context']; + $name = $string['name']; + + $key = md5( "$context | $name" ); + $new_strings[ $key ] = $string; + } + update_option( 'polylang_wpml_strings', $new_strings ); + } + + PLL_Admin_Notices::dismiss( 'wizard' ); + } + + /** + * Upgrades if the previous version is < 3.4.0. + * + * @since 3.4 + * + * @return void + */ + protected function upgrade_3_4() { + $this->migrate_locale_fallback_to_language_description(); + + $this->migrate_strings_translations(); + } + + /** + * Moves strings translations from post meta to term meta _pll_strings_translations. + * + * @since 3.4 + * + * @return void + */ + protected function migrate_strings_translations() { + $posts = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + ) + ); + + if ( ! is_array( $posts ) ) { + return; + } + + foreach ( $posts as $post ) { + $meta = get_post_meta( $post->ID, '_pll_strings_translations', true ); + + $term_id = (int) substr( $post->post_title, 12 ); + + // Do not delete post metas in case a user needs to rollback to Polylang < 3.4. + + if ( empty( $meta ) || ! is_array( $meta ) ) { + continue; + } + + update_term_meta( $term_id, '_pll_strings_translations', wp_slash( $meta ) ); + } + } + + /** + * Migrate locale fallback to language term description. + * + * @since 3.4 + * + * @return void + */ + protected function migrate_locale_fallback_to_language_description() { + // Migrate locale fallbacks from term metas to language term description. + $terms = get_terms( + array( + 'taxonomy' => 'language', + 'hide_empty' => false, + 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + array( + 'key' => 'fallback', + 'compare_key' => 'EXISTS', + ), + ), + ) + ); + + if ( ! is_array( $terms ) ) { + return; + } + + foreach ( $terms as $term ) { + $fallbacks = get_term_meta( $term->term_id, 'fallback', true ); + + delete_term_meta( $term->term_id, 'fallback' ); + + if ( empty( $fallbacks ) || ! is_array( $fallbacks ) ) { + // Empty or invalid value, should not happen. + continue; + } + + $description = maybe_unserialize( $term->description ); + $description = is_array( $description ) ? $description : array(); + + $description['fallbacks'] = $fallbacks; + /** @var string */ + $description = maybe_serialize( $description ); + + wp_update_term( $term->term_id, 'language', array( 'description' => $description ) ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php b/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php new file mode 100644 index 0000000000..99f9307799 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/aqua-resizer/aqua-resizer.php @@ -0,0 +1,32 @@ + 'aq_resize' ) ) ); + } +} diff --git a/wp-content/plugins/polylang/integrations/aqua-resizer/load.php b/wp-content/plugins/polylang/integrations/aqua-resizer/load.php new file mode 100644 index 0000000000..1325476182 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/aqua-resizer/load.php @@ -0,0 +1,13 @@ +aq_resizer = new PLL_Aqua_Resizer(); +PLL_Integrations::instance()->aq_resizer->init(); diff --git a/wp-content/plugins/polylang/integrations/cache/cache-compat.php b/wp-content/plugins/polylang/integrations/cache/cache-compat.php new file mode 100644 index 0000000000..8f9d1fb45a --- /dev/null +++ b/wp-content/plugins/polylang/integrations/cache/cache-compat.php @@ -0,0 +1,105 @@ +options['force_lang'] ) ? wp_parse_url( PLL()->links_model->home, PHP_URL_HOST ) : COOKIE_DOMAIN; + $samesite = ( 3 === PLL()->options['force_lang'] ) ? 'None' : 'Lax'; + + /** This filter is documented in include/cookie.php */ + $expiration = (int) apply_filters( 'pll_cookie_expiration', YEAR_IN_SECONDS ); + + if ( 0 !== $expiration ) { + $format = 'var expirationDate = new Date(); + expirationDate.setTime( expirationDate.getTime() + %7$d * 1000 ); + document.cookie = "%1$s=%2$s; expires=" + expirationDate.toUTCString() + "; path=%3$s%4$s%5$s%6$s";'; + } else { + $format = 'document.cookie = "%1$s=%2$s; path=%3$s%4$s%5$s%6$s";'; + } + + $js = sprintf( + "(function() { + {$format} + }());\n", + esc_js( PLL_COOKIE ), + esc_js( pll_current_language() ), + esc_js( COOKIEPATH ), + $domain ? '; domain=' . esc_js( $domain ) : '', + is_ssl() ? '; secure' : '', + '; SameSite=' . $samesite, + esc_js( $expiration ) + ); + + $type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"'; + + echo "\n{$js}\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput + } + + /** + * Informs cache plugins not to cache the home in the default language + * When the detection of the browser preferred language is active + * + * @since 2.3 + */ + public function do_not_cache_site_home() { + if ( ! defined( 'DONOTCACHEPAGE' ) && PLL()->options['browser'] && PLL()->options['hide_default'] && is_front_page() && pll_current_language() === pll_default_language() ) { + define( 'DONOTCACHEPAGE', true ); + } + } + + /** + * Allows cache plugins to clean the right post type archive cache when cleaning a post cache. + * + * @since 3.0.5 + * + * @param int $post_id Post id. + */ + public function clean_post_cache( $post_id ) { + $lang = PLL()->model->post->get_language( $post_id ); + + if ( $lang ) { + $filter_callback = function ( $link, $post_type ) use ( $lang ) { + return pll_is_translated_post_type( $post_type ) && 'post' !== $post_type ? PLL()->links_model->switch_language_in_link( $link, $lang ) : $link; + }; + add_filter( 'post_type_archive_link', $filter_callback, 99, 2 ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/cache/load.php b/wp-content/plugins/polylang/integrations/cache/load.php new file mode 100644 index 0000000000..3042f81a48 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/cache/load.php @@ -0,0 +1,20 @@ +cache_compat = new PLL_Cache_Compat(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/custom-field-template/cft.php b/wp-content/plugins/polylang/integrations/custom-field-template/cft.php new file mode 100644 index 0000000000..1ce9a6bf18 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/custom-field-template/cft.php @@ -0,0 +1,35 @@ +ID; + } + } +} diff --git a/wp-content/plugins/polylang/integrations/custom-field-template/load.php b/wp-content/plugins/polylang/integrations/custom-field-template/load.php new file mode 100644 index 0000000000..cb5780941e --- /dev/null +++ b/wp-content/plugins/polylang/integrations/custom-field-template/load.php @@ -0,0 +1,21 @@ +cft = new PLL_Cft(); + PLL_Integrations::instance()->cft->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php b/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php new file mode 100644 index 0000000000..2f0aef39a2 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/domain-mapping/domain-mapping.php @@ -0,0 +1,82 @@ + 1 ) { + // Don't go further if we stopped loading the plugin early ( for example when deactivate-polylang=1 ). + if ( ! function_exists( 'PLL' ) ) { + return; + } + + // Don't redirect the main site + if ( is_main_site() ) { + return; + } + + // Don't redirect post previews + if ( isset( $_GET['preview'] ) && 'true' === $_GET['preview'] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + // Don't redirect theme customizer + if ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && 'on' === $_POST['customize'] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + // If we can't associate the requested domain to a language, redirect to the default domain + $requested_url = pll_get_requested_url(); + $requested_host = wp_parse_url( $requested_url, PHP_URL_HOST ); + + $hosts = PLL()->links_model->get_hosts(); + $lang = array_search( $requested_host, $hosts ); + + if ( empty( $lang ) ) { + $status = get_site_option( 'dm_301_redirect' ) ? '301' : '302'; // Honor status redirect option + $redirect = str_replace( '://' . $requested_host, '://' . $hosts[ $options['default_lang'] ], $requested_url ); + wp_safe_redirect( $redirect, $status ); + exit; + } + } else { + // Otherwise rely on MU Domain Mapping + redirect_to_mapped_domain(); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/domain-mapping/load.php b/wp-content/plugins/polylang/integrations/domain-mapping/load.php new file mode 100644 index 0000000000..ff6d61037a --- /dev/null +++ b/wp-content/plugins/polylang/integrations/domain-mapping/load.php @@ -0,0 +1,12 @@ +dm = new PLL_Domain_Mapping(); diff --git a/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php b/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php new file mode 100644 index 0000000000..f73873507a --- /dev/null +++ b/wp-content/plugins/polylang/integrations/duplicate-post/duplicate-post.php @@ -0,0 +1,37 @@ +duplicate_post = new PLL_Duplicate_Post(); + PLL_Integrations::instance()->duplicate_post->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/integrations.php b/wp-content/plugins/polylang/integrations/integrations.php new file mode 100644 index 0000000000..3d9882f98b --- /dev/null +++ b/wp-content/plugins/polylang/integrations/integrations.php @@ -0,0 +1,50 @@ +is_active() || false !== $featured_ids ) { + return $featured_ids; + } + + $settings = Featured_Content::get_setting(); + + if ( ! $term = get_term_by( 'name', $settings['tag-name'], 'post_tag' ) ) { + return $featured_ids; + } + + // Get featured tag translations + $tags = PLL()->model->term->get_translations( $term->term_id ); + $ids = array(); + + // Query for featured posts in all languages + // One query per language to get the correct number of posts per language + foreach ( $tags as $tag ) { + $args = array( + 'lang' => 0, // Avoid language filters. + 'fields' => 'ids', + 'numberposts' => Featured_Content::$max_posts, + 'tax_query' => array( + array( + 'taxonomy' => 'post_tag', + 'terms' => (int) $tag, + ), + ), + ); + + // Available in Jetpack, but not in Twenty Fourteen. + if ( isset( Featured_Content::$post_types ) ) { + $args['post_type'] = Featured_Content::$post_types; + } + + $_ids = get_posts( $args ); + $ids = array_merge( $ids, $_ids ); + } + + $ids = array_map( 'absint', $ids ); + set_transient( 'featured_content_ids', $ids ); + + return $ids; + } + + /** + * Translates the featured tag id in featured content settings + * Mainly to allow hiding it when requested in featured content options + * Acts only on frontend + * + * @since 1.4 + * + * @param array $settings featured content settings + * @return array modified $settings + */ + public function option_featured_content( $settings ) { + if ( $this->is_active() && PLL() instanceof PLL_Frontend && $settings['tag-id'] && $tr = pll_get_term( $settings['tag-id'] ) ) { + $settings['tag-id'] = $tr; + } + + return $settings; + } +} diff --git a/wp-content/plugins/polylang/integrations/jetpack/jetpack.php b/wp-content/plugins/polylang/integrations/jetpack/jetpack.php new file mode 100644 index 0000000000..a4f9897727 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/jetpack/jetpack.php @@ -0,0 +1,137 @@ +options['force_lang'] > 1 ) { + add_filter( 'infinite_scroll_ajax_url', array( PLL()->links_model, 'site_url' ) ); + add_filter( 'infinite_scroll_js_settings', array( $this, 'jetpack_infinite_scroll_js_settings' ) ); + } + } + + /** + * Filter the Top Posts and Pages by language. + * Adapted from the same function in jetpack-3.0.2/3rd-party/wpml.php + * + * @since 1.5.4 + * + * @param array $posts Array of the most popular posts. + * @return array + */ + public function jetpack_widget_get_top_posts( $posts ) { + foreach ( $posts as $k => $post ) { + if ( pll_current_language() !== pll_get_post_language( $post['post_id'] ) ) { + unset( $posts[ $k ] ); + } + } + + return $posts; + } + + /** + * Filter the HTML of the Contact Form and output the one requested by language. + * Adapted from the same function in jetpack-3.0.2/3rd-party/wpml.php + * Keeps using 'icl_translate' as the function registers the string. + * + * @since 1.5.4 + * + * @param string $r Contact Form HTML output. + * @param string $field_label Field label. + * @return string + */ + public function grunion_contact_form_field_html_filter( $r, $field_label ) { + if ( function_exists( 'icl_translate' ) ) { + if ( pll_current_language() !== pll_default_language() ) { + $label_translation = icl_translate( 'jetpack ', $field_label . '_label', $field_label ); + $r = str_replace( $field_label, $label_translation, $r ); + } + } + + return $r; + } + + /** + * Adds opengraph support for locale and translations. + * + * @since 1.6 + * + * @param array $tags Opengraph tags to output. + * @return array + */ + public function jetpack_ogp( $tags ) { + if ( did_action( 'pll_init' ) ) { + foreach ( PLL()->model->get_languages_list() as $language ) { + if ( PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) { + $tags['og:locale:alternate'][] = $language->facebook; + } + if ( PLL()->curlang->slug === $language->slug && isset( $language->facebook ) ) { + $tags['og:locale'] = $language->facebook; + } + } + } + return $tags; + } + + /** + * Allows to make sure that related posts are in the correct language. + * + * @since 1.8 + * + * @param array $filters Array of ElasticSearch filters based on the post_id and args. + * @param string $post_id Post ID of the post for which we are retrieving Related Posts. + * @return array + */ + public function jetpack_relatedposts_filter_filters( $filters, $post_id ) { + $slug = sanitize_title( pll_get_post_language( $post_id, 'slug' ) ); + $filters[] = array( 'term' => array( 'taxonomy.language.slug' => $slug ) ); + return $filters; + } + + /** + * Fixes the settings history host for infinite scroll when using subdomains or multiple domains. + * + * @since 2.1 + * + * @param array $settings Infinite scroll JS settings outputted in the head. + * @return array + */ + public function jetpack_infinite_scroll_js_settings( $settings ) { + $settings['history']['host'] = wp_parse_url( pll_home_url(), PHP_URL_HOST ); // Jetpack uses get_option( 'home' ). + return $settings; + } +} diff --git a/wp-content/plugins/polylang/integrations/jetpack/load.php b/wp-content/plugins/polylang/integrations/jetpack/load.php new file mode 100644 index 0000000000..9ed976697e --- /dev/null +++ b/wp-content/plugins/polylang/integrations/jetpack/load.php @@ -0,0 +1,14 @@ +jetpack = new PLL_Jetpack(); // Must be loaded before the plugin is active. +add_action( 'pll_init', array( PLL_Integrations::instance()->featured_content = new PLL_Featured_Content(), 'init' ) ); diff --git a/wp-content/plugins/polylang/integrations/no-category-base/load.php b/wp-content/plugins/polylang/integrations/no-category-base/load.php new file mode 100644 index 0000000000..6cc3722e88 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/no-category-base/load.php @@ -0,0 +1,13 @@ +no_category_base = new PLL_No_Category_Base(); +PLL_Integrations::instance()->no_category_base->init(); diff --git a/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php b/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php new file mode 100644 index 0000000000..703ac6bf71 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/no-category-base/no-category-base.php @@ -0,0 +1,36 @@ +twenty_seventeen = new PLL_Twenty_Seventeen(), 'init' ) ); diff --git a/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php b/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php new file mode 100644 index 0000000000..a75540fa9e --- /dev/null +++ b/wp-content/plugins/polylang/integrations/twenty-seventeen/twenty-seven-teen.php @@ -0,0 +1,30 @@ + 1 ), array( 'context' => 'Twenty Seventeen' ) ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-importer/load.php b/wp-content/plugins/polylang/integrations/wp-importer/load.php new file mode 100644 index 0000000000..fca8ee3e8f --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/load.php @@ -0,0 +1,12 @@ +wp_importer = new PLL_WordPress_Importer(); diff --git a/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php b/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php new file mode 100644 index 0000000000..7d1e9f2a55 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/wordpress-importer.php @@ -0,0 +1,70 @@ +getFileName() ) ) . '/languages' ); + + $GLOBALS['wp_import'] = new PLL_WP_Import(); + register_importer( 'wordpress', 'WordPress', __( 'Import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.', 'polylang' ), array( $GLOBALS['wp_import'], 'dispatch' ) ); // phpcs:ignore WordPress.WP.CapitalPDangit.MisspelledInText + } + + /** + * Sets the flag when importing a language and the file has been exported with Polylang < 1.8. + * + * @since 1.8 + * + * @param array $terms An array of arrays containing terms information form the WXR file. + * @return array + */ + public function wp_import_terms( $terms ) { + $languages = include POLYLANG_DIR . '/settings/languages.php'; + + foreach ( $terms as $key => $term ) { + if ( 'language' === $term['term_taxonomy'] ) { + $description = maybe_unserialize( $term['term_description'] ); + if ( empty( $description['flag_code'] ) && isset( $languages[ $description['locale'] ] ) ) { + $description['flag_code'] = $languages[ $description['locale'] ]['flag']; + $terms[ $key ]['term_description'] = maybe_serialize( $description ); + } + } + } + return $terms; + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php b/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php new file mode 100644 index 0000000000..8e7f36930a --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-importer/wp-import.php @@ -0,0 +1,204 @@ +terms. + foreach ( $this->terms as $term ) { + if ( 'post_translations' == $term['term_taxonomy'] ) { + $this->post_translations[] = $term; + } + if ( 'term_translations' == $term['term_taxonomy'] ) { + $term_translations[] = $term; + } + } + + parent::process_terms(); + + // First reset the core terms cache as WordPress Importer calls wp_suspend_cache_invalidation( true ); + wp_cache_set( 'last_changed', microtime(), 'terms' ); + + // Assign the default language in case the importer created the first language. + if ( empty( PLL()->options['default_lang'] ) ) { + $languages = get_terms( array( 'taxonomy' => 'language', 'hide_empty' => false, 'orderby' => 'term_id' ) ); + $default_lang = reset( $languages ); + PLL()->options['default_lang'] = $default_lang->slug; + update_option( 'polylang', PLL()->options ); + } + + // Clean languages cache in case some of them were created during import. + PLL()->model->clean_languages_cache(); + + $this->remap_terms_relations( $term_translations ); + $this->remap_translations( $term_translations, $this->processed_terms ); + } + + /** + * Overrides WP_Import::process_post to remap posts translations + * Also merges strings translations from the WXR file to the existing ones + * + * @since 1.2 + */ + public function process_posts() { + $menu_items = $mo_posts = array(); + + // Store this for future usage as parent function unset $this->posts + foreach ( $this->posts as $post ) { + if ( 'nav_menu_item' == $post['post_type'] ) { + $menu_items[] = $post; + } + + if ( 0 === strpos( $post['post_title'], 'polylang_mo_' ) ) { + $mo_posts[] = $post; + } + } + + if ( ! empty( $mo_posts ) ) { + new PLL_MO(); // Just to register the polylang_mo post type before processing posts + } + + parent::process_posts(); + + PLL()->model->clean_languages_cache(); // To update the posts count in ( cached ) languages list + + $this->remap_translations( $this->post_translations, $this->processed_posts ); + unset( $this->post_translations ); + + // Language switcher menu items + foreach ( $menu_items as $item ) { + foreach ( $item['postmeta'] as $meta ) { + if ( '_pll_menu_item' == $meta['key'] ) { + update_post_meta( $this->processed_menu_items[ $item['post_id'] ], '_pll_menu_item', maybe_unserialize( $meta['value'] ) ); + } + } + } + + // Merge strings translations + foreach ( $mo_posts as $post ) { + $lang_id = (int) substr( $post['post_title'], 12 ); + + if ( ! empty( $this->processed_terms[ $lang_id ] ) ) { + if ( $strings = maybe_unserialize( $post['post_content'] ) ) { + $mo = new PLL_MO(); + $mo->import_from_db( $this->processed_terms[ $lang_id ] ); + foreach ( $strings as $msg ) { + $mo->add_entry_or_merge( $mo->make_entry( $msg[0], $msg[1] ) ); + } + $mo->export_to_db( $this->processed_terms[ $lang_id ] ); + } + } + // Delete the now useless imported post + wp_delete_post( $this->processed_posts[ $post['post_id'] ], true ); + } + } + + /** + * Remaps terms languages + * + * @since 1.2 + * + * @param array $terms array of terms in 'term_translations' taxonomy + */ + protected function remap_terms_relations( &$terms ) { + global $wpdb; + + $trs = array(); + + foreach ( $terms as $term ) { + $translations = maybe_unserialize( $term['term_description'] ); + foreach ( $translations as $slug => $old_id ) { + if ( $old_id && ! empty( $this->processed_terms[ $old_id ] ) && $lang = PLL()->model->get_language( $slug ) ) { + // Language relationship + $trs[] = $wpdb->prepare( '( %d, %d )', $this->processed_terms[ $old_id ], $lang->get_tax_prop( 'term_language', 'term_taxonomy_id' ) ); + + // Translation relationship + $trs[] = $wpdb->prepare( '( %d, %d )', $this->processed_terms[ $old_id ], get_term( $this->processed_terms[ $term['term_id'] ], 'term_translations' )->term_taxonomy_id ); + } + } + } + + // Insert term_relationships + if ( ! empty( $trs ) ) { + $trs = array_unique( $trs ); + + // Make sure we don't attempt to insert already existing term relationships + $existing_trs = $wpdb->get_results( + "SELECT tr.object_id, tr.term_taxonomy_id FROM {$wpdb->term_relationships} AS tr + INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id + WHERE tt.taxonomy IN ( 'term_language', 'term_translations' )" + ); + + foreach ( $existing_trs as $key => $tr ) { + $existing_trs[ $key ] = $wpdb->prepare( '( %d, %d )', $tr->object_id, $tr->term_taxonomy_id ); + } + + $trs = array_diff( $trs, $existing_trs ); + + if ( ! empty( $trs ) ) { + // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( "INSERT INTO {$wpdb->term_relationships} ( object_id, term_taxonomy_id ) VALUES " . implode( ',', $trs ) ); + } + } + } + + /** + * Remaps translations for both posts and terms + * + * @since 1.2 + * + * @param array $terms array of terms in 'post_translations' or 'term_translations' taxonomies + * @param array $processed_objects array of posts or terms processed by WordPress Importer + */ + protected function remap_translations( &$terms, &$processed_objects ) { + global $wpdb; + + $u = array(); + + foreach ( $terms as $term ) { + $translations = maybe_unserialize( $term['term_description'] ); + $new_translations = array(); + + foreach ( $translations as $slug => $old_id ) { + if ( $old_id && ! empty( $processed_objects[ $old_id ] ) ) { + $new_translations[ $slug ] = $processed_objects[ $old_id ]; + } + } + + if ( ! empty( $new_translations ) ) { + $u['case'][] = $wpdb->prepare( 'WHEN %d THEN %s', $this->processed_terms[ $term['term_id'] ], maybe_serialize( $new_translations ) ); + $u['in'][] = (int) $this->processed_terms[ $term['term_id'] ]; + } + } + + if ( ! empty( $u ) ) { + // PHPCS:disable WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( + "UPDATE {$wpdb->term_taxonomy} + SET description = ( CASE term_id " . implode( ' ', $u['case'] ) . ' END ) + WHERE term_id IN ( ' . implode( ',', $u['in'] ) . ' )' + ); + // PHPCS:enable + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php b/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php new file mode 100644 index 0000000000..33d5518def --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-offload-media/as3cf.php @@ -0,0 +1,72 @@ +is_media_translated[ $post_id ] = ( count( pll_get_post_translations( $post_id ) ) > 1 ); + } + + /** + * Deletes the WP Offload Media information from the attachment being deleted. + * That way WP Offload Media won't delete the file stored in the cloud. + * Done after Polylang has deleted the translations information, to avoid the synchronization of the deletion + * and of course before WP Offload Media deletes the file, normally at priority 20. + * + * @since 2.6 + * + * @param int $post_id Id of the attachment being deleted. + */ + public function prevent_file_deletion( $post_id ) { + if ( ! empty( $this->is_media_translated[ $post_id ] ) ) { + delete_post_meta( $post_id, 'amazonS3_info' ); + delete_post_meta( $post_id, 'as3cf_filesize_total' ); + } + } +} diff --git a/wp-content/plugins/polylang/integrations/wp-offload-media/load.php b/wp-content/plugins/polylang/integrations/wp-offload-media/load.php new file mode 100644 index 0000000000..15acfd091f --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-offload-media/load.php @@ -0,0 +1,20 @@ +as3cf = new PLL_AS3CF(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wp-sweep/load.php b/wp-content/plugins/polylang/integrations/wp-sweep/load.php new file mode 100644 index 0000000000..0ce0e17661 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-sweep/load.php @@ -0,0 +1,21 @@ +wp_sweep = new PLL_WP_Sweep(); + PLL_Integrations::instance()->wp_sweep->init(); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php b/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php new file mode 100644 index 0000000000..b9a195d6d5 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wp-sweep/wp-sweep.php @@ -0,0 +1,62 @@ +model->get_languages_list() as $language ) { + $excluded_term_ids = array_merge( + $excluded_term_ids, + array_values( $language->get_tax_props( 'term_id' ) ) + ); + } + + return array_unique( $excluded_term_ids ); + } +} diff --git a/wp-content/plugins/polylang/integrations/wpseo/load.php b/wp-content/plugins/polylang/integrations/wpseo/load.php new file mode 100644 index 0000000000..3965a23c32 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/load.php @@ -0,0 +1,20 @@ +wpseo = new PLL_WPSEO(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php b/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php new file mode 100644 index 0000000000..4b6e701628 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/wpseo-ogp.php @@ -0,0 +1,54 @@ +locale = $locale; + } + + /** + * Returns the meta Opengraph alternate locale meta tag + * + * @since 2.7.3 + * + * @return string + */ + public function present() { + return sprintf( '', esc_attr( $this->get() ) ); + } + + /** + * Returns the alternate locale + * + * @since 2.7.3 + * + * @return string + */ + public function get() { + return $this->locale; + } +} diff --git a/wp-content/plugins/polylang/integrations/wpseo/wpseo.php b/wp-content/plugins/polylang/integrations/wpseo/wpseo.php new file mode 100644 index 0000000000..08b4b68091 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/wpseo/wpseo.php @@ -0,0 +1,517 @@ +options['force_lang'] > 1 ) { + add_filter( 'wpseo_enable_xml_sitemap_transient_caching', '__return_false' ); // Disable cache! otherwise WPSEO keeps only one domain (thanks to Junaid Bhura) + add_filter( 'home_url', array( $this, 'wpseo_home_url' ), 10, 2 ); // Fix home_url + add_action( 'setup_theme', array( $this, 'maybe_deactivate_sitemap' ) ); // Deactivate sitemaps for inactive languages. + } else { + // Get all terms in all languages when the language is set from the content or directory name + add_filter( 'get_terms_args', array( $this, 'wpseo_remove_terms_filter' ) ); + add_action( 'pre_get_posts', array( $this, 'before_sitemap' ), 0 ); // Needs to be fired before WPSEO_Sitemaps::redirect() + } + + add_filter( 'pll_home_url_white_list', array( $this, 'wpseo_home_url_white_list' ) ); + add_filter( 'wpseo_frontend_presenters', array( $this, 'wpseo_frontend_presenters' ) ); + add_filter( 'wpseo_canonical', array( $this, 'wpseo_canonical' ) ); + add_filter( 'wpseo_frontend_presentation', array( $this, 'frontend_presentation' ) ); + add_filter( 'wpseo_breadcrumb_indexables', array( $this, 'breadcrumb_indexables' ) ); + } else { + add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 10, 4 ); + add_filter( 'pll_translate_post_meta', array( $this, 'translate_post_meta' ), 10, 3 ); + add_filter( 'pll_post_metas_to_export', array( $this, 'export_post_metas' ) ); + + // Yoast SEO adds the columns hooks only for the 'inline-save' action. We need them for 'pll_update_post_rows' too. + if ( wp_doing_ajax() && isset( $_POST['action'] ) && 'pll_update_post_rows' === $_POST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $GLOBALS['wpseo_meta_columns'] = new WPSEO_Meta_Columns(); + } + } + } + + /** + * Registers options for translation. + * + * @since 2.9 + */ + public function wpseo_translate_options() { + if ( method_exists( 'WPSEO_Options', 'clear_cache' ) ) { + WPSEO_Options::clear_cache(); + } + + $keys = array( + 'title-*', + 'metadesc-*', + 'bctitle-*', + 'breadcrumbs-sep', + 'breadcrumbs-home', + 'breadcrumbs-prefix', + 'breadcrumbs-archiveprefix', + 'breadcrumbs-searchprefix', + 'breadcrumbs-404crumb', + 'company_name', + 'rssbefore', + 'rssafter', + 'social-title-*', + 'social-description-*', + ); + + new PLL_Translate_Option( 'wpseo_titles', array_fill_keys( $keys, 1 ), array( 'context' => 'wordpress-seo' ) ); + + $keys = array( + 'og_frontpage_title', + 'og_frontpage_desc', + ); + + new PLL_Translate_Option( 'wpseo_social', array_fill_keys( $keys, 1 ), array( 'context' => 'wordpress-seo' ) ); + } + + /** + * Fixes the home url as well as the stylesheet url, + * only when using multiple domains or subdomains. + * + * @since 1.6.4 + * + * @param string $url The complete URL including scheme and path. + * @param string $path Path relative to the home URL. + * @return $url + */ + public function wpseo_home_url( $url, $path ) { + if ( empty( $path ) ) { + $path = ltrim( (string) wp_parse_url( pll_get_requested_url(), PHP_URL_PATH ), '/' ); + } + + if ( preg_match( '#sitemap(_index)?\.xml|([^\/]+?)-?sitemap([0-9]+)?\.xml|([a-z]+)?-?sitemap\.xsl#', $path ) ) { + $url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang ); + } + + return $url; + } + + /** + * Get active languages for the sitemaps + * + * @since 2.0 + * + * @return array list of active language slugs, empty if all languages are active + */ + protected function wpseo_get_active_languages() { + $languages = PLL()->model->get_languages_list(); + if ( wp_list_filter( $languages, array( 'active' => false ) ) ) { + return wp_list_pluck( wp_list_filter( $languages, array( 'active' => false ), 'NOT' ), 'slug' ); + } + return array(); + } + + /** + * Modifies the sql request for posts sitemaps. + * + * @since 1.6.4 + * + * @param string $sql JOIN clause. + * @param string $post_type Post type. + * @return string + */ + public function wpseo_posts_join( $sql, $post_type ) { + return pll_is_translated_post_type( $post_type ) ? $sql . PLL()->model->post->join_clause() : $sql; + } + + /** + * Modifies the sql request for posts sitemaps. + * + * @since 1.6.4 + * + * @param string $sql WHERE clause. + * @param string $post_type Post type. + * @return string + */ + public function wpseo_posts_where( $sql, $post_type ) { + if ( ! pll_is_translated_post_type( $post_type ) ) { + return $sql; + } + + if ( PLL()->options['force_lang'] > 1 && PLL()->curlang instanceof PLL_Language ) { + return $sql . PLL()->model->post->where_clause( PLL()->curlang ); + } + + $languages = $this->wpseo_get_active_languages(); + + if ( empty( $languages ) ) { // Empty when all languages are active. + $languages = pll_languages_list(); + } + + return $sql . PLL()->model->post->where_clause( $languages ); + } + + /** + * Removes the language filter (and remove inactive languages) for the taxonomy sitemaps + * Only when the language is set from the content or directory name + * + * @since 1.0.3 + * + * @param array $args get_terms arguments + * @return array modified list of arguments + */ + public function wpseo_remove_terms_filter( $args ) { + if ( isset( $GLOBALS['wp_query']->query['sitemap'] ) ) { + $args['lang'] = implode( ',', $this->wpseo_get_active_languages() ); + } + return $args; + } + + /** + * Deactivates the sitemap for inactive languages when using subdomains or multiple domains + * + * @since 2.6.1 + */ + public function maybe_deactivate_sitemap() { + global $wpseo_sitemaps; + + if ( isset( $wpseo_sitemaps ) ) { + $active_languages = $this->wpseo_get_active_languages(); + if ( ! empty( $active_languages ) && ! in_array( pll_current_language(), $active_languages ) ) { + remove_action( 'pre_get_posts', array( $wpseo_sitemaps, 'redirect' ), 1 ); + } + } + } + + /** + * Add filters before the sitemap is evaluated and outputted. + * + * @since 2.6 + * + * @param WP_Query $query Instance of WP_Query being filtered. + */ + public function before_sitemap( $query ) { + $type = $query->get( 'sitemap' ); + + // Add the post post type archives in all languages to the sitemap + // Add the homepages for all languages to the sitemap when the front page displays posts + if ( $type && pll_is_translated_post_type( $type ) && ( 'post' !== $type || ! get_option( 'page_on_front' ) ) ) { + add_filter( "wpseo_sitemap_{$type}_content", array( $this, 'add_post_type_archive' ) ); + } + } + + /** + * Generates a post type archive sitemap url + * + * @since 2.6.1 + * + * @param string $link The url. + * @param string $post_type The post type name. + * @return string Formatted sitemap url. + */ + protected function format_sitemap_url( $link, $post_type ) { + global $wpseo_sitemaps; + + return $wpseo_sitemaps->renderer->sitemap_url( + array( + 'loc' => $link, + 'mod' => WPSEO_Sitemaps::get_last_modified_gmt( $post_type ), + 'pri' => 1, + 'chf' => 'daily', + ) + ); + } + + /** + * Adds the home and post type archives urls for all (active) languages to the sitemap + * + * @since 2.6 + * + * @param string $str additional urls to sitemap post + * @return string + */ + public function add_post_type_archive( $str ) { + $post_type = substr( substr( current_filter(), 14 ), 0, -8 ); + $post_type_obj = get_post_type_object( $post_type ); + $languages = wp_list_filter( PLL()->model->get_languages_list(), array( 'active' => false ), 'NOT' ); + + if ( 'post' === $post_type ) { + if ( ! empty( PLL()->options['hide_default'] ) ) { + // The home url is of course already added by WPSEO. + $languages = wp_list_filter( $languages, array( 'slug' => pll_default_language() ), 'NOT' ); + } + + foreach ( $languages as $lang ) { + $str .= $this->format_sitemap_url( pll_home_url( $lang->slug ), $post_type ); + } + } elseif ( $post_type_obj->has_archive ) { + // Exclude cases where a post type archive is attached to a page (ex: WooCommerce). + $slug = ( true === $post_type_obj->has_archive ) ? $post_type_obj->rewrite['slug'] : $post_type_obj->has_archive; + + if ( ! wpcom_vip_get_page_by_path( $slug ) ) { + // The post type archive in the current language is already added by WPSEO. + $languages = wp_list_filter( $languages, array( 'slug' => pll_current_language() ), 'NOT' ); + + foreach ( $languages as $lang ) { + PLL()->curlang = $lang; // Switch the language to get the correct archive link. + $link = get_post_type_archive_link( $post_type ); + $str .= $this->format_sitemap_url( $link, $post_type ); + } + } + } + + return $str; + } + + /** + * Filters the home url. + * + * @since 1.1.2 + * + * @param array $arr The list of files or functions for which `home_url()` must be filtered. + * @return array + */ + public function wpseo_home_url_white_list( $arr ) { + return array_merge( $arr, array( array( 'file' => 'wordpress-seo' ) ) ); + } + + /** + * Get alternate language codes for Opengraph. + * + * @since 2.7.3 + * + * @return string[] + */ + protected function get_ogp_alternate_languages() { + $alternates = array(); + + foreach ( PLL()->model->get_languages_list() as $language ) { + if ( isset( PLL()->curlang ) && PLL()->curlang->slug !== $language->slug && PLL()->links->get_translation_url( $language ) && isset( $language->facebook ) ) { + $alternates[] = $language->facebook; + } + } + + // There is a risk that 2 languages have the same Facebook locale. So let's make sure to output each locale only once. + return array_unique( $alternates ); + } + + /** + * Adds opengraph support for translations + * + * @since 2.7.3 + * + * @param array $presenters An array of objects implementing Abstract_Indexable_Presenter + * @return array + */ + public function wpseo_frontend_presenters( $presenters ) { + $_presenters = array(); + + foreach ( $presenters as $presenter ) { + $_presenters[] = $presenter; + if ( $presenter instanceof Yoast\WP\SEO\Presenters\Open_Graph\Locale_Presenter ) { + foreach ( $this->get_ogp_alternate_languages() as $lang ) { + $_presenters[] = new PLL_WPSEO_OGP( $lang ); + } + } + } + return $_presenters; + } + + /** + * Fixes the canonical front page url as unlike WP, WPSEO does not add a trailing slash to the canonical front page url. + * + * @since 1.7.10 + * + * @param string $url The canonical URL evaluated by Yoast SEO. + * @return $url + */ + public function wpseo_canonical( $url ) { + return is_front_page( $url ) && get_option( 'permalink_structure' ) ? trailingslashit( $url ) : $url; + } + + /** + * Fixes the links and strings stored in the indexable table since Yoast SEO 14.0 + * + * @since 2.8.2 + * + * @param object $presentation The indexable presentation. + * @return object + */ + public function frontend_presentation( $presentation ) { + switch ( $presentation->model->object_type ) { + case 'home-page': + $presentation->model->title = WPSEO_Options::get( 'title-home-wpseo' ); + $presentation->model->description = WPSEO_Options::get( 'metadesc-home-wpseo' ); + $presentation->model->open_graph_title = WPSEO_Options::get( 'og_frontpage_title' ); + $presentation->model->open_graph_description = WPSEO_Options::get( 'og_frontpage_desc' ); + break; + + case 'post-type-archive': + if ( pll_is_translated_post_type( $presentation->model->object_sub_type ) ) { + $presentation->model->title = WPSEO_Options::get( 'title-ptarchive-' . $presentation->model->object_sub_type ); + $presentation->model->description = WPSEO_Options::get( 'metadesc-ptarchive-' . $presentation->model->object_sub_type ); + } + break; + + case 'system-page': + switch ( $presentation->model->object_sub_type ) { + case '404': + $presentation->model->title = WPSEO_Options::get( 'title-404-wpseo' ); + break; + case 'search-result': + $presentation->model->title = WPSEO_Options::get( 'title-search-wpseo' ); + break; + } + break; + } + + return $presentation; + } + + /** + * Fixes the breadcrumb links and strings stored in the indexable table since Yoast SEO 14.0. + * + * In version 17.0, the breadcrumb links do not honor the filter `wpseo_dynamic_permalinks_enabled`. + * + * @since 2.8.3 + * + * @param array $indexables An array of Indexable objects. + * @return array + */ + public function breadcrumb_indexables( $indexables ) { + foreach ( $indexables as &$indexable ) { + if ( 'home-page' === $indexable->object_type || ( 'post' === $indexable->object_type && 'page' === $indexable->object_sub_type && get_option( 'page_on_front' ) === $indexable->object_id ) ) { + // Handles both when the front page displays the list of posts or a static page. + $indexable->permalink = pll_home_url(); + $indexable->breadcrumb_title = pll__( WPSEO_Options::get( 'breadcrumbs-home' ) ); + } elseif ( 'post' === $indexable->object_type && 'page' === $indexable->object_sub_type && get_option( 'page_for_posts' ) === $indexable->object_id ) { + $indexable->permalink = get_permalink( $indexable->object_id ); + } elseif ( 'post-type-archive' === $indexable->object_type && pll_is_translated_post_type( $indexable->object_sub_type ) ) { + $indexable->permalink = get_post_type_archive_link( $indexable->object_sub_type ); + $breadcrumb_title = WPSEO_Options::get( 'bctitle-ptarchive-' . $indexable->object_sub_type ); + $breadcrumb_title = $breadcrumb_title ? $breadcrumb_title : $indexable->breadcrumb_title; // The option may be empty. + $indexable->breadcrumb_title = pll__( $breadcrumb_title ); + } elseif ( 'term' === $indexable->object_type && pll_is_translated_taxonomy( $indexable->object_sub_type ) ) { + $indexable->permalink = get_term_link( $indexable->object_id ); + } + } + + return $indexables; + } + + /** + * Copies or synchronizes the metas. + * + * @since 2.3.3 + * + * @param string[] $keys List of custom fields names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @return array + */ + public function copy_post_metas( $keys, $sync, $from, $to ) { + if ( ! $sync ) { + // Text requiring translation. + $keys = array_merge( $keys, $this->get_translatable_meta_keys() ); + + // Copy the image urls. + $keys[] = '_yoast_wpseo_opengraph-image'; + $keys[] = '_yoast_wpseo_twitter-image'; + + $keys[] = '_yoast_wpseo_meta-robots-noindex'; + $keys[] = '_yoast_wpseo_meta-robots-nofollow'; + $keys[] = '_yoast_wpseo_meta-robots-adv'; + } + + $taxonomies = get_taxonomies( + array( + 'hierarchical' => true, + 'public' => true, + ) + ); + + $sync_taxonomies = PLL()->sync->taxonomies->get_taxonomies_to_copy( $sync, $from, $to ); + + $taxonomies = array_intersect( $taxonomies, $sync_taxonomies ); + + foreach ( $taxonomies as $taxonomy ) { + $keys[] = '_yoast_wpseo_primary_' . $taxonomy; + } + + return $keys; + } + + /** + * Translate the primary term during the synchronization process + * + * @since 2.3.3 + * + * @param int $value Meta value. + * @param string $key Meta key. + * @param string $lang Language of target. + * @return int + */ + public function translate_post_meta( $value, $key, $lang ) { + if ( 0 !== strpos( $key, '_yoast_wpseo_primary_' ) ) { + return $value; + } + + $taxonomy = str_replace( '_yoast_wpseo_primary_', '', $key ); + if ( ! PLL()->model->is_translated_taxonomy( $taxonomy ) ) { + return $value; + } + + return pll_get_term( $value, $lang ); + } + + /** + * Adds the yoast translatable metas to export. + * + * @param array $metas An array of post metas (keyed with meta keys) to export. + * @return array The modified array of post metas to export. + */ + public function export_post_metas( $metas ) { + $metas_to_export = array_fill_keys( $this->get_translatable_meta_keys(), 1 ); + + return array_merge( $metas, $metas_to_export ); + } + + /** + * Returns the meta keys with translatable text. + * + * @since 3.3 + * + * @return string[] + */ + protected function get_translatable_meta_keys() { + return array( + '_yoast_wpseo_title', + '_yoast_wpseo_metadesc', + '_yoast_wpseo_bctitle', + '_yoast_wpseo_focuskw', + '_yoast_wpseo_opengraph-title', + '_yoast_wpseo_opengraph-description', + '_yoast_wpseo_twitter-title', + '_yoast_wpseo_twitter-description', + ); + } +} diff --git a/wp-content/plugins/polylang/integrations/yarpp/load.php b/wp-content/plugins/polylang/integrations/yarpp/load.php new file mode 100644 index 0000000000..68d54841c0 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/yarpp/load.php @@ -0,0 +1,20 @@ +yarpp = new PLL_Yarpp(), 'init' ) ); + } + }, + 0 +); diff --git a/wp-content/plugins/polylang/integrations/yarpp/yarpp.php b/wp-content/plugins/polylang/integrations/yarpp/yarpp.php new file mode 100644 index 0000000000..11e66d6887 --- /dev/null +++ b/wp-content/plugins/polylang/integrations/yarpp/yarpp.php @@ -0,0 +1,20 @@ +yarpp_support = 1; + } +} diff --git a/wp-content/plugins/polylang/js/build/admin.js b/wp-content/plugins/polylang/js/build/admin.js new file mode 100644 index 0000000000..091a95e9a9 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/admin.js @@ -0,0 +1,427 @@ +/** + * @package Polylang + */ + +jQuery( + function( $ ) { + + // languages list table + // accessibility to row actions on focus + // mainly copy paste of WP code from common.js + var transitionTimeout; + $( 'table.languages' ).on( + { // restricted to languages list table + focusin: function() { + clearTimeout( transitionTimeout ); + var focusedRowActions = $( this ).find( '.row-actions' ); + // transitionTimeout is necessary for Firefox, but Chrome won't remove the CSS class without a little help. + $( '.row-actions' ).not( this ).removeClass( 'visible' ); + focusedRowActions.addClass( 'visible' ); + }, + focusout: function() { + // Tabbing between post title and .row-actions links needs a brief pause, otherwise + // the .row-actions div gets hidden in transit in some browsers ( ahem, Firefox ). + transitionTimeout = setTimeout( + function() { + focusedRowActions.removeClass( 'visible' ); + }, + 30 + ); + } + }, + 'tr' + ); // acts on the whole tr instead of single td as we have actions links in several columns + + /** + * Common functions and variables for overriding languages and flags dropdown list by a jQuery UI selectmenu widget. + */ + + // Add a boolean variable to be able to check jQuery UI >= 1.12 which is introduced in WP 5.6. + // Backward compatibility WP < 5.6 + var isJqueryUImin112 = $.ui.version >= '1.12.0'; + // Allow to check if a flag list dropdown is present. Not present in the Wizard steps or other settings page. + var flagListExist = $( "#flag_list" ).length; + // Allow to check if a language list dropdown is present. Not present in other settings page. + var langListExist = $( "#lang_list" ).length; + // jQuery UI selectmenu widget width option + var defaultSelectmenuWidth = '95%'; + var wizardSelectmenuWidth = '100%'; + + // Inject flag image when jQuery UI selectmenu is created or an item is selected. + // jQuery UI 1.12 introduce a wrapper inside de li tag which is necessary to selectmenu widget to work correctly. + // Mainly copy from the original jQuery UI 1.12 selectmenu widget _renderItem method. + // Note this code works fine with jQuery UI 1.11.4 too. + var selectmenuRenderItem = function( ul, item ) { + var li = $( '
  • ' ); + var wrapper = $( '
    '); + + if ( item.disabled ) { + this._addClass( li, null, "ui-state-disabled" ); + } + this._setText( wrapper, item.label ); + + // Add the flag from the data attribute in the selected element. + wrapper.prepend( $( item.element ).data( 'flag-html' ) ); + wrapper.children( 'img' ).addClass( 'ui-icon' ); + + return li.append( wrapper ).appendTo( ul ); + }; + // Override selected item to inject flag for jQuery UI less than 1.12. + var selectmenuRefreshButtonText = function( selectElement ) { + var buttonText = $( selectElement ).selectmenu( 'instance' ).buttonText; + buttonText.prepend( $( selectElement ).children( ':selected' ).data( 'flag-html' ) ); + buttonText.children( 'img' ).addClass( 'ui-icon' ); + }; + // Override selected item since jQuery UI 1.12 which introduces extension point method _renderButtonItem. + // @see https://api.jqueryui.com/1.12/selectmenu/#method-_renderButtonItem _renderButtonItem documentation. + var selectmenuRenderButtonItem = function ( selectElement ) { + var buttonItem = $( '' ); + this._setText( buttonItem, selectElement.label ); + this._addClass( buttonItem, "ui-selectmenu-text" ); + + // Add the flag from the data attribute in the selected element. + buttonItem.prepend( $( selectElement.element ).data( 'flag-html' ) ); + buttonItem.children( 'img' ).addClass( 'ui-icon' ); + + return buttonItem; + } + + /** + * Initialize a jQuery UI selectmenu widget on a DOM element + * + * @param {*} element - The jQuery object representing the DOM element to attach the widget with. + * @param {*} config - All the parameters - options and callbacks - necessary to configure the jQuery UI selectmenu widget. + * @return {Object} - The jQuery UI selectmenu widget object instance. + */ + function initializeSelectmenuWidget( element, config ) { + // Create the jQuery UI selectmenu widget for flags list dropdown and return its instance. + var selectmenuWidgetInstance = element.selectmenu( config ).selectmenu( 'instance' ); + // Overrides each item in the jQuery UI selectmenu list by injecting flag image. + selectmenuWidgetInstance._renderItem = selectmenuRenderItem; + // Override the selected item rendering for jQuery UI 1.12 + if ( isJqueryUImin112 ) { + selectmenuWidgetInstance._renderButtonItem = selectmenuRenderButtonItem; + // Need to refresh to take in account the new button item rendering method after the selectmenu widget instanciaion. + selectmenuWidgetInstance.refresh(); + } + return selectmenuWidgetInstance + } + /** + * Selectmenu widget common parameters for its configuration: options and callbacks. + */ + + // Selectmenu widget options + var selectmenuOptions = { + width: defaultSelectmenuWidth, + classes: { + 'ui-selectmenu-menu': 'pll-selectmenu-menu', + 'ui-selectmenu-button': 'pll-selectmenu-button', + } + }; + + // Selectmenu widget callbacks + var selectmenuFlagListCallbacks = {}; + // Callbacks when Selectmenu widget create or select event is triggered. + var createSelectCallback = function( event, ui ) { + selectmenuRefreshButtonText( event.target ); + } + + /** + * Overrides the flag dropdown list with our customized jquery ui selectmenu. + */ + + // Callbacks when Selectmenu widget change or open event is triggered. + // Needed to correctly refresh the selected element in the list when editing an existing language or when the value change is triggered by the language choice. + // jQuery UI 1.11 callback version. + var changeOpenCallback = function( event, ui ){ + selectmenuRefreshButtonText( $( event.target ).selectmenu( 'refresh' ) ); + } + // jQueryUI 1.12 callback version. + var changeOpenCallbackjQueryUI112 = function( event, ui ){ + // Just a refresh of the menu is needed with jQuery UI 1.12 because _renderButtonItem is triggered and then inject correctly the flag. + $( event.target ).selectmenu( 'refresh' ); + } + // There is no need of create and select callbacks with jQuery UI 1.12 because overriding _renderButtonItem method do the job. + if ( isJqueryUImin112 ) { + selectmenuFlagListCallbacks = + { + change: changeOpenCallbackjQueryUI112, + open: changeOpenCallbackjQueryUI112, + }; + } else { + selectmenuFlagListCallbacks = { + create: createSelectCallback, + select: createSelectCallback, + change: changeOpenCallback, + open: changeOpenCallback, + }; + } + + // Create the selectmenu widget only if the field is present. + if ( flagListExist ) { + // Create the jQuery UI selectmenu widget for flags list dropdown and return its instance. + var selectmenuFlagList = initializeSelectmenuWidget( $( '#flag_list' ), Object.assign( {}, selectmenuOptions, selectmenuFlagListCallbacks ) ); + $( '#lang_list' ).on( + 'languageChanged', + function( event, flag ) { + // Refresh the flag field + selectmenuFlagList.element.val( flag ); + selectmenuFlagList._trigger( 'change' ); + } + ); + } + + /** + * Language choice in predefined languages in Polylang Languages settings page and wizard. + * Overrides the predefined language dropdown list with our customized jQuery ui selectmenu widget. + */ + + /** + * Fill the other language form fields from the language element selected in the language list dropdown. + * + * @param {Object} language - language object of the selected element in the language list dropdown. + */ + function fillLanguageFields( language ) { + $( '#lang_slug' ).val( language.slug ); + $( '#lang_locale' ).val( language.locale ); + $( 'input[name="rtl"]' ).val( language.rtl ); + $( '#lang_name' ).val( language.name ); + } + + /** + * Parse selected language element in the language list dropdown. + * + * @param {object} event - jQuery triggered event. + * @return {object} The language object with its named properties. + */ + function parseSelectedLanguage( event ) { + var selectedElement = $('option:selected', event.target); + var values = selectedElement.val().split(':') + return { + slug: values[0], + locale: values[1], + rtl: [values[2]], + flag: values[3], + name: selectedElement.text().split(' - ')[0] // At the moment there is no need of the 2nd part because it corresponds on the locale which is already known by splitting the selected element value + }; + } + + // Callback when selectmenu widget change event is triggered. + var changeCallback = function( event, ui ) { + var language = parseSelectedLanguage( event ); + + fillLanguageFields( language ); + + $( event.target ).trigger( 'languageChanged', language.flag ); + }; + + // Create the jQuery UI selectmenu widget languages list dropdown and return its instance. + var selectmenuLangListCallbacks = {}; + // For the wizard we need a 100% width. So we override the previous defined value of selectmenuOptions. + if( $( '#lang_list' ).closest( '.pll-wizard-content' ).length > 0 ) { + selectmenuOptions = Object.assign( selectmenuOptions, { width: wizardSelectmenuWidth } ); + } + + // There is no need of create and select callbacks with jQuery UI 1.12 because overrinding _renderButtonItem method do the job. + if ( isJqueryUImin112 ) { + selectmenuLangListCallbacks = { + change: changeCallback, + }; + } else { + selectmenuLangListCallbacks = { + create: createSelectCallback, + select: createSelectCallback, + change: changeCallback, + }; + } + if ( langListExist ) { + initializeSelectmenuWidget( $( '#lang_list' ), Object.assign( {}, selectmenuOptions, selectmenuLangListCallbacks ) ); + } + + // strings translations + // save translations when pressing enter + $( '.translation input' ).on( + 'keydown', + function( event ){ + if ( 'Enter' === event.key ) { + event.preventDefault(); + $( '#submit' ).trigger( 'click' ); + } + } + ); + + // settings page + // click on configure link + $( '#the-list' ).on( + 'click', + '.configure>a', + function(){ + $( '.pll-configure' ).hide().prev().show(); + $( this ).closest( 'tr' ).hide().next().show(); + return false; + } + ); + + // cancel + $( '#the-list' ).on( + 'click', + '.cancel', + function(){ + $( this ).closest( 'tr' ).hide().prev().show(); + } + ); + + // save settings + $( '#the-list' ).on( + 'click', + '.save', + function(){ + var tr = $( this ).closest( 'tr' ); + var parts = tr.attr( 'id' ).split( '-' ); + + var data = { + action: 'pll_save_options', + pll_ajax_settings: true, + module: parts[parts.length - 1], + _pll_nonce: $( '#_pll_nonce' ).val() + }; + + data = tr.find( ':input' ).serialize() + '&' + $.param( data ); + + $.post( + ajaxurl, + data, + function( response ) { + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function() { + /** + * Fires after saving the settings, before applying changes to the DOM. + * + * @since 3.6.0 + * + * @param {Object} response The response from the AJAX call. + * @param {HTMLElement} tr The HTML element containing the fields. + */ + wp.hooks.doAction( 'pll_settings_saved', this, tr.get( 0 ) ); + + switch ( this.what ) { + case 'license-update': + $( '#pll-license-' + this.data ).replaceWith( this.supplemental.html ); + break; + case 'success': + tr.hide().prev().show(); // close only if there is no error + case 'error': + $( '.settings-error' ).remove(); // remove previous messages if any + $( 'h1' ).after( this.data ); + + // Make notices dismissible + // copy paste of common.js from WP 4.2.2 + $( '.notice.is-dismissible' ).each( + function() { + var $this = $( this ), + $button = $( '' ), + btnText = pll_admin.dismiss_notice || ''; + + // Ensure plain text + $button.find( '.screen-reader-text' ).text( btnText ); + + // Whitelist because of how the button is built. See above + $this.append( $button ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + $button.on( + 'click.wp-dismiss-notice', + function( event ) { + event.preventDefault(); + $this.fadeTo( + 100, + 0, + function() { + $( this ).slideUp( + 100, + function() { + $( this ).remove(); + } + ); + } + ); + } + ); + } + ); + break; + } + } + ); + } + ); + } + ); + + // act when pressing enter or esc in configurations + $( '.pll-configure' ).on( + 'keydown', + function( event ){ + if ( 'Enter' === event.key ) { + event.preventDefault(); + $( this ).find( '.save' ).trigger( 'click' ); + } + + if ( 'Escape' === event.key ) { + event.preventDefault(); + $( this ).find( '.cancel' ).trigger( 'click' ); + } + } + ); + + // settings URL modifications + // manages visibility of fields + $( "input[name='force_lang']" ).on( + 'change', + function() { + function pll_toggle( a, test ) { + test ? a.show() : a.hide(); + } + + var value = $( this ).val(); + pll_toggle( $( '#pll-domains-table' ), 3 == value ); + pll_toggle( $( "#pll-hide-default" ), 3 > value ); + pll_toggle( $( "#pll-rewrite" ), 2 > value ); + pll_toggle( $( "#pll-redirect-lang" ), 2 > value ); + } + ); + + // settings license + // deactivate button + $( '.pll-deactivate-license' ).on( + 'click', + function() { + var data = { + action: 'pll_deactivate_license', + pll_ajax_settings: true, + id: $( this ).attr( 'id' ), + _pll_nonce: $( '#_pll_nonce' ).val() + }; + $.post( + ajaxurl, + data, + function( response ){ + $( '#pll-license-' + response.id ).replaceWith( response.html ); + } + ); + } + ); + + // Manage closing the metabox. + // close postboxes that should be closed + $( '.if-js-closed' ).removeClass( 'if-js-closed' ).addClass( 'closed' ); + // postboxes setup + if ( 'undefined' !== typeof postboxes ) { + postboxes.add_postbox_toggles( pagenow ); + } + } +); + + diff --git a/wp-content/plugins/polylang/js/build/admin.min.js b/wp-content/plugins/polylang/js/build/admin.min.js new file mode 100644 index 0000000000..9919e052b9 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/admin.min.js @@ -0,0 +1 @@ +jQuery((function(e){var t;e("table.languages").on({focusin:function(){clearTimeout(t);var n=e(this).find(".row-actions");e(".row-actions").not(this).removeClass("visible"),n.addClass("visible")},focusout:function(){t=setTimeout((function(){focusedRowActions.removeClass("visible")}),30)}},"tr");var n=e.ui.version>="1.12.0",l=e("#flag_list").length,s=e("#lang_list").length,i=function(t,n){var l=e("
  • "),s=e("
    ");return n.disabled&&this._addClass(l,null,"ui-state-disabled"),this._setText(s,n.label),s.prepend(e(n.element).data("flag-html")),s.children("img").addClass("ui-icon"),l.append(s).appendTo(t)},a=function(t){var n=e(t).selectmenu("instance").buttonText;n.prepend(e(t).children(":selected").data("flag-html")),n.children("img").addClass("ui-icon")},c=function(t){var n=e("");return this._setText(n,t.label),this._addClass(n,"ui-selectmenu-text"),n.prepend(e(t.element).data("flag-html")),n.children("img").addClass("ui-icon"),n};function o(e,t){var l=e.selectmenu(t).selectmenu("instance");return l._renderItem=i,n&&(l._renderButtonItem=c,l.refresh()),l}var r={width:"95%",classes:{"ui-selectmenu-menu":"pll-selectmenu-menu","ui-selectmenu-button":"pll-selectmenu-button"}},u={},d=function(e,t){a(e.target)},p=function(t,n){a(e(t.target).selectmenu("refresh"))},g=function(t,n){e(t.target).selectmenu("refresh")};if(u=n?{change:g,open:g}:{create:d,select:d,change:p,open:p},l){var h=o(e("#flag_list"),Object.assign({},r,u));e("#lang_list").on("languageChanged",(function(e,t){h.element.val(t),h._trigger("change")}))}var f=function(t,n){var l=function(t){var n=e("option:selected",t.target),l=n.val().split(":");return{slug:l[0],locale:l[1],rtl:[l[2]],flag:l[3],name:n.text().split(" - ")[0]}}(t);!function(t){e("#lang_slug").val(t.slug),e("#lang_locale").val(t.locale),e('input[name="rtl"]').val(t.rtl),e("#lang_name").val(t.name)}(l),e(t.target).trigger("languageChanged",l.flag)},v={};e("#lang_list").closest(".pll-wizard-content").length>0&&(r=Object.assign(r,{width:"100%"})),v=n?{change:f}:{create:d,select:d,change:f},s&&o(e("#lang_list"),Object.assign({},r,v)),e(".translation input").on("keydown",(function(t){"Enter"===t.key&&(t.preventDefault(),e("#submit").trigger("click"))})),e("#the-list").on("click",".configure>a",(function(){return e(".pll-configure").hide().prev().show(),e(this).closest("tr").hide().next().show(),!1})),e("#the-list").on("click",".cancel",(function(){e(this).closest("tr").hide().prev().show()})),e("#the-list").on("click",".save",(function(){var t=e(this).closest("tr"),n=t.attr("id").split("-"),l={action:"pll_save_options",pll_ajax_settings:!0,module:n[n.length-1],_pll_nonce:e("#_pll_nonce").val()};l=t.find(":input").serialize()+"&"+e.param(l),e.post(ajaxurl,l,(function(n){var l=wpAjax.parseAjaxResponse(n,"pll-ajax-response");e.each(l.responses,(function(){switch(wp.hooks.doAction("pll_settings_saved",this,t.get(0)),this.what){case"license-update":e("#pll-license-"+this.data).replaceWith(this.supplemental.html);break;case"success":t.hide().prev().show();case"error":e(".settings-error").remove(),e("h1").after(this.data),e(".notice.is-dismissible").each((function(){var t=e(this),n=e(''),l=pll_admin.dismiss_notice||"";n.find(".screen-reader-text").text(l),t.append(n),n.on("click.wp-dismiss-notice",(function(n){n.preventDefault(),t.fadeTo(100,0,(function(){e(this).slideUp(100,(function(){e(this).remove()}))}))}))}))}}))}))})),e(".pll-configure").on("keydown",(function(t){"Enter"===t.key&&(t.preventDefault(),e(this).find(".save").trigger("click")),"Escape"===t.key&&(t.preventDefault(),e(this).find(".cancel").trigger("click"))})),e("input[name='force_lang']").on("change",(function(){function t(e,t){t?e.show():e.hide()}var n=e(this).val();t(e("#pll-domains-table"),3==n),t(e("#pll-hide-default"),3>n),t(e("#pll-rewrite"),2>n),t(e("#pll-redirect-lang"),2>n)})),e(".pll-deactivate-license").on("click",(function(){var t={action:"pll_deactivate_license",pll_ajax_settings:!0,id:e(this).attr("id"),_pll_nonce:e("#_pll_nonce").val()};e.post(ajaxurl,t,(function(t){e("#pll-license-"+t.id).replaceWith(t.html)}))})),e(".if-js-closed").removeClass("if-js-closed").addClass("closed"),"undefined"!=typeof postboxes&&postboxes.add_postbox_toggles(pagenow)})); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/block-editor.js b/wp-content/plugins/polylang/js/build/block-editor.js new file mode 100644 index 0000000000..ef87ebf00b --- /dev/null +++ b/wp-content/plugins/polylang/js/build/block-editor.js @@ -0,0 +1,405 @@ +/******/ "use strict"; + +;// ./js/src/lib/confirmation-modal.js +/** + * @package Polylang + */ + +const languagesList = jQuery( '.post_lang_choice' ); + +// Dialog box for alerting the user about a risky changing. +const initializeConfirmationModal = () => { + // We can't use underscore or lodash in this common code because it depends of the context classic or block editor. + // Classic editor underscore is loaded, Block editor lodash is loaded. + const { __ } = wp.i18n; + + // Create dialog container. + const dialogContainer = jQuery( + '
    ', + { + id: 'pll-dialog', + style: 'display:none;' + } + ).text( __( 'Are you sure you want to change the language of the current content?', 'polylang' ) ); + + // Put it after languages list dropdown. + // PHPCS ignore dialogContainer is a new safe HTML code generated above. + languagesList.after( dialogContainer ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + + const dialogResult = new Promise( + ( confirm, cancel ) => { + const confirmDialog = ( what ) => { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + switch ( what ) { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + case 'yes': + // Confirm the new language. + languagesList.data( 'old-value', languagesList.children( ':selected' ).first().val() ); + confirm(); + break; + case 'no': + // Revert to the old language. + languagesList.val( languagesList.data( 'old-value' ) ); + cancel( 'Cancel' ); + break; + } + dialogContainer.dialog( 'close' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + } // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + + // Initialize dialog box in the case a language is selected but not added in the list. + const dialogOptions = { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: __( 'Change language', 'polylang' ), + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( jQuery( 'body' ).hasClass( 'rtl' ) ) { + jQuery( this ).parent().css( + { + right: jQuery( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + }, + close: function ( event, ui ) { + // When we're closing the dialog box we need to cancel the language change as we click on Cancel button. + confirmDialog( 'no' ); + }, + buttons: [ + { + text: __( 'OK', 'polylang' ), + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: __( 'Cancel', 'polylang' ), + click: function ( event ) { + confirmDialog( 'no' ); + } + } + ] + }; + + if ( jQuery.ui.version >= '1.12.0' ) { + Object.assign( dialogOptions, { classes: { 'ui-dialog': 'pll-confirmation-modal' } } ); + } else { + Object.assign( dialogOptions, { dialogClass: 'pll-confirmation-modal' } ); // jQuery UI 1.11.4 - WP < 5.6 + } + + dialogContainer.dialog( dialogOptions ); + } + ); + return { dialogContainer, dialogResult }; +} + +const initializeLanguageOldValue = () => { + // Keep the old language value to be able to compare to the new one and revert to it if necessary. + languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() ); +}; + +;// ./js/src/lib/metabox-autocomplete.js +/** + * @package Polylang + */ + +// Translations autocomplete input box. +function initMetaboxAutoComplete() { + jQuery('.tr_lang').each( + function () { + var tr_lang = jQuery(this).attr('id').substring(8); + var td = jQuery(this).parent().parent().siblings('.pll-edit-column'); + + jQuery(this).autocomplete( + { + minLength: 0, + source: ajaxurl + '?action=pll_posts_not_translated' + + '&post_language=' + jQuery('.post_lang_choice').val() + + '&translation_language=' + tr_lang + + '&post_type=' + jQuery('#post_type').val() + + '&_pll_nonce=' + jQuery('#_pll_nonce').val(), + select: function (event, ui) { + jQuery('#htr_lang_' + tr_lang).val(ui.item.id); + // ui.item.link is built and come from server side and is well escaped when necessary + td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + }, + } + ); + + // when the input box is emptied + jQuery(this).on( + 'blur', + function () { + if ( ! jQuery(this).val() ) { + jQuery('#htr_lang_' + tr_lang).val(0); + // Value is retrieved from HTML already generated server side + td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + } + ); + } + ); +} + +;// ./js/src/lib/filter-path-middleware.js +/** + * @package Polylang + */ + +/** + * Filters requests for translatable entities. + * This logic is shared across all Polylang plugins. + * + * @since 3.5 + * + * @param {APIFetchOptions} options + * @param {Array} filteredRoutes + * @param {CallableFunction} filter + * @returns {APIFetchOptions} + */ +const filterPathMiddleware = ( options, filteredRoutes, filter ) => { + const cleanPath = options.path.split( '?' )[0].replace(/^\/+|\/+$/g, ''); // Get path without query parameters and trim '/'. + + return Object.values( filteredRoutes ).find( ( path ) => cleanPath === path ) ? filter( options ) : options; +} + +/* harmony default export */ const filter_path_middleware = (filterPathMiddleware); + +;// ./js/src/block-editor.js +/** + * @package Polylang + */ + + + + + + + +/** + * Filter REST API requests to add the language in the request + * + * @since 2.5 + */ +wp.apiFetch.use( + function ( options, next ) { + /* + * If options.url is defined, this is not a REST request but a direct call to post.php for legacy metaboxes. + * If `filteredRoutes` is not defined, return early. + */ + if ( 'undefined' !== typeof options.url || 'undefined' === typeof pllFilteredRoutes ) { + return next( options ); + } + + return next( filter_path_middleware( options, pllFilteredRoutes, addLanguageParameter ) ); + } +); + +/** + * Gets the language of the currently edited post, fallback to default language if none is found. + * + * @since 2.5 + * + * @return {Element.value} + */ +function getCurrentLanguage() { + const lang = document.querySelector( '[name=post_lang_choice]' ); + + if ( null === lang ) { + return pllDefaultLanguage; + } + + return lang.value; +} + +/** + * Adds language parameter according to the current one (query string for GET, body for PUT and POST). + * + * @since 3.5 + * + * @param {APIFetchOptions} options + * @returns {APIFetchOptions} + */ +function addLanguageParameter( options ) { + if ( 'undefined' === typeof options.data || null === options.data ) { + // GET + options.path += ( ( options.path.indexOf( '?' ) >= 0 ) ? '&lang=' : '?lang=' ) + getCurrentLanguage(); + } else { + // PUT, POST + options.data.lang = getCurrentLanguage(); + } + + return options; +} + +/** + * Handles internals of the metabox: + * Language select, autocomplete input field. + * + * @since 1.5 + * + * Save post after lang choice is done and redirect to the same page for refreshing all the data. + * + * @since 2.5 + * + * Link post saving after refreshing the metabox. + * + * @since 3.0 + */ +jQuery( + function ( $ ) { + // Initialize current language to be able to compare if it changes. + initializeLanguageOldValue(); + + + // Ajax for changing the post's language in the languages metabox + $( '.post_lang_choice' ).on( + 'change', + function ( event ) { + const { select, dispatch, subscribe } = wp.data; + const emptyPost = isEmptyPost(); + const { addQueryArgs } = wp.url; + + // Initialize the confirmation dialog box. + const confirmationModal = initializeConfirmationModal(); + const { dialogContainer : dialog } = confirmationModal; + let { dialogResult } = confirmationModal; + const selectedOption = event.target; // The selected option in the dropdown list. + + // Specific case for empty posts. + // Place at the beginning because window.location change triggers automatically page reloading. + if ( location.pathname.match( /post-new.php/gi ) && emptyPost ) { + reloadPageForEmptyPost( selectedOption.value ); + } + + // Otherwise send an ajax request to refresh the legacy metabox and set the post language with the new language. + // It needs a confirmation of the user before changing the language. + // Need to wait the ajax response before triggering the block editor post save action. + if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! emptyPost ) { + dialog.dialog( 'open' ); + } else { + // Update the old language with the new one to be able to compare it in the next change. + // Because the page isn't reloaded in this case. + initializeLanguageOldValue(); + dialogResult = Promise.resolve(); + } + + dialogResult.then( + () => { + let data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + action: 'post_lang_choice', + lang: selectedOption.value, + post_type: $( '#post_type' ).val(), + post_id: $( '#post_ID' ).val(), + _pll_nonce: $( '#_pll_nonce' ).val() + } + + // Update post language in database as soon as possible. + // Because, in addition of the block editor save process, the legacy metabox uses a post.php process to update the language and is too late compared to the page reload. + $.post( + ajaxurl, + data, + function () { + blockEditorSavePostAndReloadPage(); + } + ); + }, + () => {} // Do nothing when promise is rejected by clicking the Cancel dialog button. + ); + + function isEmptyPost() { + const editor = select( 'core/editor' ); + + return ! editor.getEditedPostAttribute( 'title' )?.trim() && ! editor.getEditedPostContent() && ! editor.getEditedPostAttribute( 'excerpt' )?.trim(); + } + + /** + * Reload the block editor page for empty posts. + * + * @param {string} lang The target language code. + */ + function reloadPageForEmptyPost( lang ) { + // Change the new_lang parameter with the new language value for reloading the page + // WPCS location.search is never written in the page, just used to reload page with the right value of new_lang + // new_lang input is controlled server side in PHP. The value come from the dropdown list of language returned and escaped server side. + // Notice that window.location changing triggers automatically page reloading. + if ( -1 != location.search.indexOf( 'new_lang' ) ) { + // use regexp non capturing group to replace new_lang parameter no matter where it is and capture other parameters which can be behind it + window.location.search = window.location.search.replace( /(?:new_lang=[^&]*)(&)?(.*)/, 'new_lang=' + lang + '$1$2' ); // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment + } else { + window.location.search = window.location.search + ( ( -1 != window.location.search.indexOf( '?' ) ) ? '&' : '?' ) + 'new_lang=' + lang; // phpcs:ignore WordPressVIPMinimum.JS.Window.location, WordPressVIPMinimum.JS.Window.VarAssignment + } + }; + + /** + * Triggers block editor post save and reload the block editor page when everything is ok. + */ + function blockEditorSavePostAndReloadPage() { + + let unsubscribe = null; + const previousPost = select( 'core/editor').getCurrentPost(); + + // Listen if the savePost is completely done by subscribing to its events. + const savePostIsDone = new Promise( + function ( resolve, reject ) { + unsubscribe = subscribe( + function () { + const post = select( 'core/editor').getCurrentPost(); + const { id, status, type } = post; + const error = select( 'core' ) + .getLastEntitySaveError( + 'postType', + type, + id + ); + + if ( error ) { + reject(); + } + + if ( previousPost.modified !== post.modified ) { + + if ( location.pathname.match( /post-new.php/gi ) && status !== 'auto-draft' && id ) { + window.history.replaceState( + { id }, + 'Post ' + id, + addQueryArgs( 'post.php', { post: id, action: 'edit' } ) + ); + } + resolve(); + } + } + ); + } + ); + + // Triggers the post save. + dispatch( 'core/editor' ).savePost(); + + // Process + savePostIsDone.then( + function () { + // If the post is well saved, we can reload the page + window.location.reload(); + }, + function () { + // If the post save failed + unsubscribe(); + } + ).catch( + function () { + // If an exception is thrown + unsubscribe(); + } + ); + }; + } + ); + + initMetaboxAutoComplete(); + } +); + diff --git a/wp-content/plugins/polylang/js/build/block-editor.min.js b/wp-content/plugins/polylang/js/build/block-editor.min.js new file mode 100644 index 0000000000..6c5480a803 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/block-editor.min.js @@ -0,0 +1 @@ +"use strict";const languagesList=jQuery(".post_lang_choice"),initializeConfirmationModal=()=>{const{__:t}=wp.i18n,e=jQuery("
    ",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(e);const a=new Promise(((a,n)=>{const i=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),a();break;case"no":languagesList.val(languagesList.data("old-value")),n("Cancel")}e.dialog("close")},l={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,e){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,e){i("no")},buttons:[{text:t("OK","polylang"),click:function(t){i("yes")}},{text:t("Cancel","polylang"),click:function(t){i("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(l,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(l,{dialogClass:"pll-confirmation-modal"}),e.dialog(l)}));return{dialogContainer:e,dialogResult:a}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each((function(){var t=jQuery(this).attr("id").substring(8),e=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(a,n){jQuery("#htr_lang_"+t).val(n.item.id),e.html(n.item.link)}}),jQuery(this).on("blur",(function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),e.html(e.siblings(".hidden").children().clone()))}))}))}const filterPathMiddleware=(t,e,a)=>{const n=t.path.split("?")[0].replace(/^\/+|\/+$/g,"");return Object.values(e).find((t=>n===t))?a(t):t},filter_path_middleware=filterPathMiddleware;function getCurrentLanguage(){const t=document.querySelector("[name=post_lang_choice]");return null===t?pllDefaultLanguage:t.value}function addLanguageParameter(t){return void 0===t.data||null===t.data?t.path+=(t.path.indexOf("?")>=0?"&lang=":"?lang=")+getCurrentLanguage():t.data.lang=getCurrentLanguage(),t}wp.apiFetch.use((function(t,e){return void 0!==t.url||"undefined"==typeof pllFilteredRoutes?e(t):e(filter_path_middleware(t,pllFilteredRoutes,addLanguageParameter))})),jQuery((function(t){initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(e){const{select:a,dispatch:n,subscribe:i}=wp.data,l=function(){const t=a("core/editor");return!t.getEditedPostAttribute("title")?.trim()&&!t.getEditedPostContent()&&!t.getEditedPostAttribute("excerpt")?.trim()}(),{addQueryArgs:o}=wp.url,r=initializeConfirmationModal(),{dialogContainer:s}=r;let{dialogResult:u}=r;const c=e.target;var d;location.pathname.match(/post-new.php/gi)&&l&&(d=c.value,-1!=location.search.indexOf("new_lang")?window.location.search=window.location.search.replace(/(?:new_lang=[^&]*)(&)?(.*)/,"new_lang="+d+"$1$2"):window.location.search=window.location.search+(-1!=window.location.search.indexOf("?")?"&":"?")+"new_lang="+d),t(this).data("old-value")===c.value||l?(initializeLanguageOldValue(),u=Promise.resolve()):s.dialog("open"),u.then((()=>{let e={action:"post_lang_choice",lang:c.value,post_type:t("#post_type").val(),post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,e,(function(){!function(){let t=null;const e=a("core/editor").getCurrentPost(),l=new Promise((function(n,l){t=i((function(){const t=a("core/editor").getCurrentPost(),{id:i,status:r,type:s}=t;a("core").getLastEntitySaveError("postType",s,i)&&l(),e.modified!==t.modified&&(location.pathname.match(/post-new.php/gi)&&"auto-draft"!==r&&i&&window.history.replaceState({id:i},"Post "+i,o("post.php",{post:i,action:"edit"})),n())}))}));n("core/editor").savePost(),l.then((function(){window.location.reload()}),(function(){t()})).catch((function(){t()}))}()}))}),(()=>{}))})),initMetaboxAutoComplete()})); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/classic-editor.js b/wp-content/plugins/polylang/js/build/classic-editor.js new file mode 100644 index 0000000000..6706eb13ef --- /dev/null +++ b/wp-content/plugins/polylang/js/build/classic-editor.js @@ -0,0 +1,451 @@ +/******/ "use strict"; + +;// ./js/src/lib/confirmation-modal.js +/** + * @package Polylang + */ + +const languagesList = jQuery( '.post_lang_choice' ); + +// Dialog box for alerting the user about a risky changing. +const initializeConfirmationModal = () => { + // We can't use underscore or lodash in this common code because it depends of the context classic or block editor. + // Classic editor underscore is loaded, Block editor lodash is loaded. + const { __ } = wp.i18n; + + // Create dialog container. + const dialogContainer = jQuery( + '
    ', + { + id: 'pll-dialog', + style: 'display:none;' + } + ).text( __( 'Are you sure you want to change the language of the current content?', 'polylang' ) ); + + // Put it after languages list dropdown. + // PHPCS ignore dialogContainer is a new safe HTML code generated above. + languagesList.after( dialogContainer ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + + const dialogResult = new Promise( + ( confirm, cancel ) => { + const confirmDialog = ( what ) => { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + switch ( what ) { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + case 'yes': + // Confirm the new language. + languagesList.data( 'old-value', languagesList.children( ':selected' ).first().val() ); + confirm(); + break; + case 'no': + // Revert to the old language. + languagesList.val( languagesList.data( 'old-value' ) ); + cancel( 'Cancel' ); + break; + } + dialogContainer.dialog( 'close' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + } // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + + // Initialize dialog box in the case a language is selected but not added in the list. + const dialogOptions = { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: __( 'Change language', 'polylang' ), + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( jQuery( 'body' ).hasClass( 'rtl' ) ) { + jQuery( this ).parent().css( + { + right: jQuery( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + }, + close: function ( event, ui ) { + // When we're closing the dialog box we need to cancel the language change as we click on Cancel button. + confirmDialog( 'no' ); + }, + buttons: [ + { + text: __( 'OK', 'polylang' ), + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: __( 'Cancel', 'polylang' ), + click: function ( event ) { + confirmDialog( 'no' ); + } + } + ] + }; + + if ( jQuery.ui.version >= '1.12.0' ) { + Object.assign( dialogOptions, { classes: { 'ui-dialog': 'pll-confirmation-modal' } } ); + } else { + Object.assign( dialogOptions, { dialogClass: 'pll-confirmation-modal' } ); // jQuery UI 1.11.4 - WP < 5.6 + } + + dialogContainer.dialog( dialogOptions ); + } + ); + return { dialogContainer, dialogResult }; +} + +const initializeLanguageOldValue = () => { + // Keep the old language value to be able to compare to the new one and revert to it if necessary. + languagesList.attr( 'data-old-value', languagesList.children( ':selected' ).first().val() ); +}; + +;// ./js/src/lib/metabox-autocomplete.js +/** + * @package Polylang + */ + +// Translations autocomplete input box. +function initMetaboxAutoComplete() { + jQuery('.tr_lang').each( + function () { + var tr_lang = jQuery(this).attr('id').substring(8); + var td = jQuery(this).parent().parent().siblings('.pll-edit-column'); + + jQuery(this).autocomplete( + { + minLength: 0, + source: ajaxurl + '?action=pll_posts_not_translated' + + '&post_language=' + jQuery('.post_lang_choice').val() + + '&translation_language=' + tr_lang + + '&post_type=' + jQuery('#post_type').val() + + '&_pll_nonce=' + jQuery('#_pll_nonce').val(), + select: function (event, ui) { + jQuery('#htr_lang_' + tr_lang).val(ui.item.id); + // ui.item.link is built and come from server side and is well escaped when necessary + td.html(ui.item.link); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + }, + } + ); + + // when the input box is emptied + jQuery(this).on( + 'blur', + function () { + if ( ! jQuery(this).val() ) { + jQuery('#htr_lang_' + tr_lang).val(0); + // Value is retrieved from HTML already generated server side + td.html(td.siblings('.hidden').children().clone()); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + } + ); + } + ); +} + +;// ./js/src/classic-editor.js +/** + * @package Polylang + */ + + + + + +// tag suggest in metabox +jQuery( + function ( $ ) { + $.ajaxPrefilter( + function ( options, originalOptions, jqXHR ) { + var lang = $( '.post_lang_choice' ).val(); + if ( 'string' === typeof options.data && -1 !== options.url.indexOf( 'action=ajax-tag-search' ) && lang ) { + options.data = 'lang=' + lang + '&' + options.data; + } + } + ); + } +); + +// overrides tagBox.get +jQuery( + function ( $ ) { + // overrides function to add the language + tagBox.get = function ( id ) { + var tax = id.substr( id.indexOf( '-' ) + 1 ); + + // add the language in the $_POST variable + var data = { + action: 'get-tagcloud', + lang: $( '.post_lang_choice' ).val(), + tax: tax + } + + $.post( + ajaxurl, + data, + function ( r, stat ) { + if ( 0 == r || 'success' != stat ) { + r = wpAjax.broken; + } + + // @see code from WordPress core https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/js/tags-box.js#L291 + // @see wp_generate_tag_cloud function which generate the escaped HTML https://github.com/WordPress/WordPress/blob/a02b5cc2a8eecb8e076fbb7cf4de7bd2ec8a8eb1/wp-includes/category-template.php#L966-L975 + r = $( '
    ' ).addClass( 'the-tagcloud' ).attr( 'id', 'tagcloud-' + tax ).html( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + $( 'a', r ).on( + 'click', + function () { + tagBox.flushTags( $( this ).closest( '.inside' ).children( '.tagsdiv' ), this ); + return false; + } + ); + + var tagCloud = $( '#tagcloud-' + tax ); + // add an if else condition to allow modifying the tags outputted when switching the language + var v = tagCloud.css( 'display' ); + if ( v ) { + // See the comment above when r variable is created. + $( '#tagcloud-' + tax ).replaceWith( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + $( '#tagcloud-' + tax ).css( 'display', v ); + } + else { + // See the comment above when r variable is created. + $( '#' + id ).after( r ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.after + } + } + ); + } + } +); + +jQuery( + function ( $ ) { + // collect taxonomies - code partly copied from WordPress + var taxonomies = new Array(); + $( '.categorydiv' ).each( + function () { + var this_id = $( this ).attr( 'id' ), taxonomyParts, taxonomy; + + taxonomyParts = this_id.split( '-' ); + taxonomyParts.shift(); + taxonomy = taxonomyParts.join( '-' ); + taxonomies.push( taxonomy ); // store the taxonomy for future use + + // add our hidden field in the new category form - for each hierarchical taxonomy + // to set the language when creating a new category + // html code inserted come from html code itself. + $( '#' + taxonomy + '-add-submit' ).before( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.before + $( '' ).attr( 'type', 'hidden' ) + .attr( 'id', taxonomy + '-lang' ) + .attr( 'name', 'term_lang_choice' ) + .attr( 'value', $( '.post_lang_choice' ).val() ) + ); + } + ); + + // Initialize current language to be able to compare if it changes. + initializeLanguageOldValue(); + + // ajax for changing the post's language in the languages metabox + $( '.post_lang_choice' ).on( + 'change', + function ( event ) { + // Initialize the confirmation dialog box. + const confirmationModal = initializeConfirmationModal(); + const { dialogContainer: dialog } = confirmationModal; + let { dialogResult } = confirmationModal; + // The selected option in the dropdown list. + const selectedOption = event.target; + + if ( $( this ).data( 'old-value' ) !== selectedOption.value && ! isEmptyPost() ) { + dialog.dialog( 'open' ); + } else { + dialogResult = Promise.resolve(); + } + + // phpcs:disable PEAR.Functions.FunctionCallSignature.EmptyLine + dialogResult.then( + () => { + var lang = selectedOption.options[selectedOption.options.selectedIndex].lang; // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + var dir = $( '.pll-translation-column > span[lang="' + lang + '"]' ).attr( 'dir' ); // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + + var data = { // phpcs:ignore PEAR.Functions.FunctionCallSignature.Indent + action: 'post_lang_choice', + lang: selectedOption.value, + post_type: $( '#post_type' ).val(), + taxonomies: taxonomies, + post_id: $( '#post_ID' ).val(), + _pll_nonce: $( '#_pll_nonce' ).val() + } + + $.post( + ajaxurl, + data, + function ( response ) { + // Target a non existing WP HTML id to avoid a conflict with WP ajax requests. + var res = wpAjax.parseAjaxResponse( response, 'pll-ajax-response' ); + $.each( + res.responses, + function () { + switch ( this.what ) { + case 'translations': // translations fields + // Data is built and come from server side and is well escaped when necessary + $( '.translations' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + initMetaboxAutoComplete(); + break; + case 'taxonomy': // categories metabox for posts + var tax = this.data; + // @see wp_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L175 + // @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/class-walker-category-checklist.php#L89-L111 + $( '#' + tax + 'checklist' ).html( this.supplemental.all ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // @see wp_popular_terms_checklist https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/template.php#L236 + $( '#' + tax + 'checklist-pop' ).html( this.supplemental.populars ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // @see wp_dropdown_categories https://github.com/WordPress/WordPress/blob/5.5.1/wp-includes/category-template.php#L336 + // which is called by PLL_Admin_Classic_Editor::post_lang_choice to generate supplemental.dropdown + $( '#new' + tax + '_parent' ).replaceWith( this.supplemental.dropdown ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.replaceWith + $( '#' + tax + '-lang' ).val( $( '.post_lang_choice' ).val() ); // hidden field + break; + case 'pages': // parent dropdown list for pages + // @see wp_dropdown_pages https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/post-template.php#L1186-L1208 + // @see https://github.com/WordPress/WordPress/blob/5.2.2/wp-includes/class-walker-page-dropdown.php#L88 + $( '#parent_id' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + break; + case 'flag': // flag in front of the select dropdown + // Data is built and come from server side and is well escaped when necessary + $( '.pll-select-flag' ).html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + break; + case 'permalink': // Sample permalink + var div = $( '#edit-slug-box' ); + if ( '-1' != this.data && div.children().length ) { + // @see get_sample_permalink_html https://github.com/WordPress/WordPress/blob/5.2.2/wp-admin/includes/post.php#L1425-L1454 + div.html( this.data ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } + break; + } + } + ); + + // Update the old language with the new one to be able to compare it in the next changing. + initializeLanguageOldValue(); + // modifies the language in the tag cloud + $( '.tagcloud-link' ).each( + function () { + var id = $( this ).attr( 'id' ); + tagBox.get( id ); + } + ); + + // Modifies the text direction + $( 'body' ).removeClass( 'pll-dir-rtl' ).removeClass( 'pll-dir-ltr' ).addClass( 'pll-dir-' + dir ); + $( '#content_ifr' ).contents().find( 'html' ).attr( 'lang', lang ).attr( 'dir', dir ); + $( '#content_ifr' ).contents().find( 'body' ).attr( 'dir', dir ); + + pll.media.resetAllAttachmentsCollections(); + } + ) + }, + () => {} // Do nothing when promise is rejected by clicking the Cancel dialog button. + ); + // phpcs:enable PEAR.Functions.FunctionCallSignature.EmptyLine + + function isEmptyPost() { + const title = $( 'input#title' ).val(); + const content = $( 'textarea#content' ).val(); + const excerpt = $( 'textarea#excerpt' ).val(); + + return ! title && ! content && ! excerpt; + } + } + ); + + initMetaboxAutoComplete(); + } +); + +/** + * @since 3.0 + * + * @namespace pll + */ +var pll = window.pll || {}; + +/** + * @since 3.0 + * + * @namespace pll.media + */ +_.extend( pll, { media: {} } ); + +/** + * @since 3.0 + * + * @alias pll.media + * @memberOf pll + * @namespace + */ +var media = _.extend( + pll.media, /** @lends pll.media.prototype */ + { + /** + * TODO: Find a way to delete references to Attachments collections that are not used anywhere else. + * + * @type {wp.media.model.Attachments} + */ + attachmentsCollections : [], + + /** + * Imitates { @see wp.media.query } but log all Attachments collections created. + * + * @param {Object} [props] + * @return {wp.media.model.Attachments} + */ + query: function ( props ) { + var attachments = pll.media.query.delegate( props ); + + pll.media.attachmentsCollections.push( attachments ); + + return attachments; + }, + + resetAllAttachmentsCollections: function () { + this.attachmentsCollections.forEach( + function ( attachmentsCollection ) { + /** + * First reset the { @see wp.media.model.Attachments } collection. + * Then, if it is mirroring a { @see wp.media.model.Query } collection, + * refresh this one too, so it will fetch new data from the server, + * and then the wp.media.model.Attachments collection will synchronize with the new data. + */ + attachmentsCollection.reset(); + if (attachmentsCollection.mirroring) { + attachmentsCollection.mirroring._hasMore = true; + attachmentsCollection.mirroring.reset(); + } + } + ); + } + } +); + +if ( 'undefined' !== typeof wp && 'undefined' !== typeof wp.media ) { + + /** + * @since 3.0 + * + * @memberOf pll.media + */ + media.query = _.extend( + media.query, /** @lends pll.media.query prototype */ + { + /** + * @type Function References WordPress { @see wp.media.query } constructor + */ + delegate: wp.media.query + } + ) + + // Substitute WordPress media query shortcut with our decorated function. + wp.media.query = media.query + +} + diff --git a/wp-content/plugins/polylang/js/build/classic-editor.min.js b/wp-content/plugins/polylang/js/build/classic-editor.min.js new file mode 100644 index 0000000000..e2ce487492 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/classic-editor.min.js @@ -0,0 +1 @@ +"use strict";const languagesList=jQuery(".post_lang_choice"),initializeConfirmationModal=()=>{const{__:t}=wp.i18n,a=jQuery("
    ",{id:"pll-dialog",style:"display:none;"}).text(t("Are you sure you want to change the language of the current content?","polylang"));languagesList.after(a);const e=new Promise(((e,l)=>{const n=t=>{switch(t){case"yes":languagesList.data("old-value",languagesList.children(":selected").first().val()),e();break;case"no":languagesList.val(languagesList.data("old-value")),l("Cancel")}a.dialog("close")},i={autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:t("Change language","polylang"),minWidth:600,maxWidth:"100%",open:function(t,a){jQuery("body").hasClass("rtl")&&jQuery(this).parent().css({right:jQuery(this).parent().css("left"),left:"auto"})},close:function(t,a){n("no")},buttons:[{text:t("OK","polylang"),click:function(t){n("yes")}},{text:t("Cancel","polylang"),click:function(t){n("no")}}]};jQuery.ui.version>="1.12.0"?Object.assign(i,{classes:{"ui-dialog":"pll-confirmation-modal"}}):Object.assign(i,{dialogClass:"pll-confirmation-modal"}),a.dialog(i)}));return{dialogContainer:a,dialogResult:e}},initializeLanguageOldValue=()=>{languagesList.attr("data-old-value",languagesList.children(":selected").first().val())};function initMetaboxAutoComplete(){jQuery(".tr_lang").each((function(){var t=jQuery(this).attr("id").substring(8),a=jQuery(this).parent().parent().siblings(".pll-edit-column");jQuery(this).autocomplete({minLength:0,source:ajaxurl+"?action=pll_posts_not_translated&post_language="+jQuery(".post_lang_choice").val()+"&translation_language="+t+"&post_type="+jQuery("#post_type").val()+"&_pll_nonce="+jQuery("#_pll_nonce").val(),select:function(e,l){jQuery("#htr_lang_"+t).val(l.item.id),a.html(l.item.link)}}),jQuery(this).on("blur",(function(){jQuery(this).val()||(jQuery("#htr_lang_"+t).val(0),a.html(a.siblings(".hidden").children().clone()))}))}))}jQuery((function(t){t.ajaxPrefilter((function(a,e,l){var n=t(".post_lang_choice").val();"string"==typeof a.data&&-1!==a.url.indexOf("action=ajax-tag-search")&&n&&(a.data="lang="+n+"&"+a.data)}))})),jQuery((function(t){tagBox.get=function(a){var e=a.substr(a.indexOf("-")+1),l={action:"get-tagcloud",lang:t(".post_lang_choice").val(),tax:e};t.post(ajaxurl,l,(function(l,n){0!=l&&"success"==n||(l=wpAjax.broken),l=t("
    ").addClass("the-tagcloud").attr("id","tagcloud-"+e).html(l),t("a",l).on("click",(function(){return tagBox.flushTags(t(this).closest(".inside").children(".tagsdiv"),this),!1}));var i=t("#tagcloud-"+e).css("display");i?(t("#tagcloud-"+e).replaceWith(l),t("#tagcloud-"+e).css("display",i)):t("#"+a).after(l)}))}})),jQuery((function(t){var a=new Array;t(".categorydiv").each((function(){var e,l;(e=t(this).attr("id").split("-")).shift(),l=e.join("-"),a.push(l),t("#"+l+"-add-submit").before(t("").attr("type","hidden").attr("id",l+"-lang").attr("name","term_lang_choice").attr("value",t(".post_lang_choice").val()))})),initializeLanguageOldValue(),t(".post_lang_choice").on("change",(function(e){const l=initializeConfirmationModal(),{dialogContainer:n}=l;let{dialogResult:i}=l;const o=e.target;t(this).data("old-value")===o.value||function(){const a=t("input#title").val(),e=t("textarea#content").val(),l=t("textarea#excerpt").val();return!a&&!e&&!l}()?i=Promise.resolve():n.dialog("open"),i.then((()=>{var e=o.options[o.options.selectedIndex].lang,l=t('.pll-translation-column > span[lang="'+e+'"]').attr("dir"),n={action:"post_lang_choice",lang:o.value,post_type:t("#post_type").val(),taxonomies:a,post_id:t("#post_ID").val(),_pll_nonce:t("#_pll_nonce").val()};t.post(ajaxurl,n,(function(a){var n=wpAjax.parseAjaxResponse(a,"pll-ajax-response");t.each(n.responses,(function(){switch(this.what){case"translations":t(".translations").html(this.data),initMetaboxAutoComplete();break;case"taxonomy":var a=this.data;t("#"+a+"checklist").html(this.supplemental.all),t("#"+a+"checklist-pop").html(this.supplemental.populars),t("#new"+a+"_parent").replaceWith(this.supplemental.dropdown),t("#"+a+"-lang").val(t(".post_lang_choice").val());break;case"pages":t("#parent_id").html(this.data);break;case"flag":t(".pll-select-flag").html(this.data);break;case"permalink":var e=t("#edit-slug-box");"-1"!=this.data&&e.children().length&&e.html(this.data)}})),initializeLanguageOldValue(),t(".tagcloud-link").each((function(){var a=t(this).attr("id");tagBox.get(a)})),t("body").removeClass("pll-dir-rtl").removeClass("pll-dir-ltr").addClass("pll-dir-"+l),t("#content_ifr").contents().find("html").attr("lang",e).attr("dir",l),t("#content_ifr").contents().find("body").attr("dir",l),pll.media.resetAllAttachmentsCollections()}))}),(()=>{}))})),initMetaboxAutoComplete()}));var pll=window.pll||{};_.extend(pll,{media:{}});var media=_.extend(pll.media,{attachmentsCollections:[],query:function(t){var a=pll.media.query.delegate(t);return pll.media.attachmentsCollections.push(a),a},resetAllAttachmentsCollections:function(){this.attachmentsCollections.forEach((function(t){t.reset(),t.mirroring&&(t.mirroring._hasMore=!0,t.mirroring.reset())}))}});"undefined"!=typeof wp&&void 0!==wp.media&&(media.query=_.extend(media.query,{delegate:wp.media.query}),wp.media.query=media.query); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/languages-step.js b/wp-content/plugins/polylang/js/build/languages-step.js new file mode 100644 index 0000000000..8077fa908a --- /dev/null +++ b/wp-content/plugins/polylang/js/build/languages-step.js @@ -0,0 +1,303 @@ +/** + * @package Polylang + */ + +jQuery( + function ( $ ) { + var addLanguageForm = $( '.languages-step' ); // Form element. + var languageFields = $( '#language-fields' ); // Element where to append hidden fields for creating language. + var languagesTable = $( '#languages' ); // Table element contains languages list to create. + var languagesListTable = $( '#languages tbody' ); // Table rows with languages list to create. + var definedLanguagesListTable = $( '#defined-languages tbody' ); // Table rows with already defined languages list. + var languagesList = $( '#lang_list' ); // Select form element with predefined languages without already created languages. + var nextStepButton = $( '[name="save_step"]' ); // The button for continuing to the next step. + var messagesContainer = $( '#messages' ); // Element where to display error messages. + var languagesMap = new Map(); // Languages map object for managing the languages to create. + var dialog = $( '#dialog' ); // Dialog box for alerting the language selected has not been added to the list. + + /** + * Add a language in the list to create it in Polylang settings + * + * @param {object} language The language object + */ + function addLanguage( language ) { + // language properties come from the select dropdown which is built server side and well escaped. + // see template view-wizard-step-languages.php. + var languageValueHtml = $( '' ).text( language.text ).prepend( language.flagUrl ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageTrashIconHtml = $( '' ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'dashicons dashicons-trash' ) + .attr( 'data-language', language.locale ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'screen-reader-text' ) + .text( pll_wizard_params.i18n_remove_language_icon ) + ) + ); + // see the comment and the hardcoded code above. languageTrashIconHtml and languageValueHtml are safe. + var languageLineHtml = $( '' ).prepend( languageTrashIconHtml ).prepend( languageValueHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageFieldHtml = $( '' ).attr( + { + type: 'hidden', + name: 'languages[]' + } + ).val( language.locale ); + + languagesList.val( '' ); + languagesList.selectmenu( 'refresh' ); // Refresh jQuery selectmenu widget after changing the value. + + languagesMap.set( language.locale, language ); + + // see above how languageLineHtml is built. + languagesListTable.append( languageLineHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + // Bind click event on trash icon. + languagesListTable.on( + 'click', + 'span[data-language=' + language.locale + ']', + function ( event ) { + event.preventDefault(); + // Remove line in languages table. + $( this ).parents( 'tr' ).remove(); + // Remove input field. + var languageField = languageFields.children( 'input[value=' + $( this ).data( 'language' ) + ']' ).remove(); + // If there is no more languages hide languages table. + if ( languagesListTable.children().length <= 0 ) { + languagesTable.hide(); + } + // Remove language from the Map. + languagesMap.delete( $( this ).data( 'language' ) ); + // Hide error message. + hideError(); + } + ); + // see above how languageFieldHtml is built. + // Add hidden input field for posting the form. + languageFields.append( languageFieldHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + } + + /** + * Display an error message + * + * @param {string} message The message to display + */ + function showError( message ) { + messagesContainer.empty(); + // html is hardcoded and use of jQuery text method which is safe to add message value. + // In addition message is i18n value which is initialized server side in PLL_Wizard::add_step_languages and correctly escaped. + messagesContainer.prepend( $( '

    ' ).addClass( 'error' ).text( message ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + + /** + * Hide all error messages and fields in error + */ + function hideError() { + messagesContainer.empty(); + addLanguageForm.find( '.error' ).removeClass( 'error field-in-error' ); + } + + /** + * Style the field to indicate where the error is + * + * @param {object} field The jQuery element which is in error + */ + function showFieldInError( field ) { + field.addClass( 'error field-in-error' ); + } + + /** + * Focus on a specific element + * + * @param {object} field The jQuery element which will be focused + */ + function focusOnField( field ) { + field.trigger( 'focus' ); + } + + /** + * Disable a specific button + * + * @param {object} button + */ + function disableButton( button ){ + button.prop( 'disabled', true ); + // Because the button is disabled we need to add the value of the button to ensure it will pass in the request. + addLanguageForm.append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ).prop( + { + type: 'hidden', + name: button.prop( 'name' ), + value: button.prop( 'value' ) + } + ) + ); + } + + /** + * Remove error when a new selection is done in languages list. + */ + languagesList.on( + 'selectmenuchange', + function () { + hideError();; + } + ); + /** + * Bind click event on "Add language" button + */ + $( '#add-language' ).on( + 'click', + function ( event ) { + hideError(); + var selectedOption = event.currentTarget.form.lang_list.options[event.currentTarget.form.lang_list.selectedIndex]; + if ( '' !== selectedOption.value && ! languagesMap.has( selectedOption.value ) ) { + addLanguage( + { + locale: selectedOption.value, + text: selectedOption.innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + // Show table of languages. + languagesTable.show(); + // Put back the focus on the select language field after clicking on "Add language button". + focusOnField( $( '#lang_list-button' ) ); + } else { + var message = pll_wizard_params.i18n_no_language_selected; + if ( languagesMap.has( selectedOption.value ) ) { + message = pll_wizard_params.i18n_language_already_added; + } + showError( message ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + + } + } + ); + + /** + * Bind submit event on "add_lang" form + */ + addLanguageForm.on( + 'submit', + function ( event ) { + // Verify if there is at least one language. + var isLanguagesAlreadyDefined = definedLanguagesListTable.children().length > 0; + var selectedLanguage = $( '#lang_list' ).val(); + if ( languagesMap.size <= 0 && ! isLanguagesAlreadyDefined ) { + if ( '' === selectedLanguage ) { + showError( pll_wizard_params.i18n_no_language_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } else { + showError( pll_wizard_params.i18n_add_language_needed ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#add-language' ) ); // Put the focus on the "Add language" button. + } + return false; + } + // Verify if the language has been added in the list otherwise display a dialog box to confirm what to do. + if ( '' !== selectedLanguage ) { + // Verify we don't add a duplicate language before opening the dialog box otherwise display an error message. + if ( ! languagesMap.has( selectedLanguage ) ) { + dialog.dialog( 'open' ); + } else { + showError( pll_wizard_params.i18n_language_already_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } + return false; + } + disableButton( nextStepButton ); + } + ); + + // Is there an error return by PHP ? + var searchParams = new URLSearchParams( document.location.search ); + if ( searchParams.has( 'activate_error' ) ) { + // If the error code exists, display it. + if ( undefined !== pll_wizard_params[ searchParams.get( 'activate_error' ) ] ) { + showError( pll_wizard_params[ searchParams.get( 'activate_error' ) ] ); + } + } + + function confirmDialog( what ) { + switch ( what ) { + case 'yes': + var selectedOption = $( '#lang_list' ).children( ':selected' ); + addLanguage( + { + locale: selectedOption[0].value, + text: selectedOption[0].innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + break; + case 'no': + // Empty select form field and submit again the form. + languagesList.val( '' ); + break; + case 'ignore': + } + dialog.dialog( 'close' ); + if ( 'ignore' === what ) { + focusOnField( $( '#lang_list-button' ) ); + } else { + addLanguageForm.submit(); + } + } + + // Initialize dialog box in the case a language is selected but not added in the list. + dialog.dialog( + { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: pll_wizard_params.i18n_dialog_title, + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( $( 'body' ).hasClass( 'rtl' ) ) { + $( this ).parent().css( + { + right: $( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + // Display language name and flag information in dialog box. + $( this ).find( '#dialog-language' ).text( $( '#lang_list' ).children( ':selected' ).first().text() ); + // language properties come from the select dropdown #lang_list which is built server side and well escaped. + // see template view-wizard-step-languages.php. + $( this ).find( '#dialog-language-flag' ).empty().prepend( $( '#lang_list' ).children( ':selected' ).data( 'flag-html' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + }, + buttons: [ + { + text: pll_wizard_params.i18n_dialog_yes_button, + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_no_button, + click: function ( event ) { + confirmDialog( 'no' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_ignore_button, + click: function ( event ) { + confirmDialog( 'ignore' ); + } + } + ] + } + ) + } +); + diff --git a/wp-content/plugins/polylang/js/build/languages-step.min.js b/wp-content/plugins/polylang/js/build/languages-step.min.js new file mode 100644 index 0000000000..64ce94e2b3 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/languages-step.min.js @@ -0,0 +1 @@ +jQuery((function(a){var e=a(".languages-step"),n=a("#language-fields"),t=a("#languages"),l=a("#languages tbody"),i=a("#defined-languages tbody"),r=a("#lang_list"),d=a('[name="save_step"]'),s=a("#messages"),o=new Map,g=a("#dialog");function u(e){var i=a("").text(e.text).prepend(e.flagUrl),d=a("").append(a("").addClass("dashicons dashicons-trash").attr("data-language",e.locale).append(a("").addClass("screen-reader-text").text(pll_wizard_params.i18n_remove_language_icon))),s=a("").prepend(d).prepend(i),g=a("").attr({type:"hidden",name:"languages[]"}).val(e.locale);r.val(""),r.selectmenu("refresh"),o.set(e.locale,e),l.append(s),l.on("click","span[data-language="+e.locale+"]",(function(e){e.preventDefault(),a(this).parents("tr").remove();n.children("input[value="+a(this).data("language")+"]").remove();l.children().length<=0&&t.hide(),o.delete(a(this).data("language")),c()})),n.append(g)}function p(e){s.empty(),s.prepend(a("

    ").addClass("error").text(e))}function c(){s.empty(),e.find(".error").removeClass("error field-in-error")}function _(a){a.addClass("error field-in-error")}function m(a){a.trigger("focus")}r.on("selectmenuchange",(function(){c()})),a("#add-language").on("click",(function(e){c();var n=e.currentTarget.form.lang_list.options[e.currentTarget.form.lang_list.selectedIndex];if(""===n.value||o.has(n.value)){var l=pll_wizard_params.i18n_no_language_selected;o.has(n.value)&&(l=pll_wizard_params.i18n_language_already_added),p(l),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))}else u({locale:n.value,text:n.innerText,name:a(n).data("language-name"),flagUrl:a(n).data("flag-html")}),t.show(),m(a("#lang_list-button"))})),e.on("submit",(function(n){var t,l=i.children().length>0,s=a("#lang_list").val();return o.size<=0&&!l?(""===s?(p(pll_wizard_params.i18n_no_language_added),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))):(p(pll_wizard_params.i18n_add_language_needed),_(r.next("span.ui-selectmenu-button")),m(a("#add-language"))),!1):""!==s?(o.has(s)?(p(pll_wizard_params.i18n_language_already_added),_(r.next("span.ui-selectmenu-button")),m(a("#lang_list-button"))):g.dialog("open"),!1):((t=d).prop("disabled",!0),void e.append(a("").prop({type:"hidden",name:t.prop("name"),value:t.prop("value")})))}));var f=new URLSearchParams(document.location.search);function h(n){switch(n){case"yes":var t=a("#lang_list").children(":selected");u({locale:t[0].value,text:t[0].innerText,name:a(t).data("language-name"),flagUrl:a(t).data("flag-html")});break;case"no":r.val("")}g.dialog("close"),"ignore"===n?m(a("#lang_list-button")):e.submit()}f.has("activate_error")&&void 0!==pll_wizard_params[f.get("activate_error")]&&p(pll_wizard_params[f.get("activate_error")]),g.dialog({autoOpen:!1,modal:!0,draggable:!1,resizable:!1,title:pll_wizard_params.i18n_dialog_title,minWidth:600,maxWidth:"100%",open:function(e,n){a("body").hasClass("rtl")&&a(this).parent().css({right:a(this).parent().css("left"),left:"auto"}),a(this).find("#dialog-language").text(a("#lang_list").children(":selected").first().text()),a(this).find("#dialog-language-flag").empty().prepend(a("#lang_list").children(":selected").data("flag-html"))},buttons:[{text:pll_wizard_params.i18n_dialog_yes_button,click:function(a){h("yes")}},{text:pll_wizard_params.i18n_dialog_no_button,click:function(a){h("no")}},{text:pll_wizard_params.i18n_dialog_ignore_button,click:function(a){h("ignore")}}]})})); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/nav-menu.js b/wp-content/plugins/polylang/js/build/nav-menu.js new file mode 100644 index 0000000000..723ebe01e7 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/nav-menu.js @@ -0,0 +1,105 @@ +/** + * Handles the options in the language switcher nav menu metabox. + * + * @package Polylang + */ + +jQuery( + function ( $ ) { + $( '#update-nav-menu' ).on( + 'click', + function ( e ) { + if ( e.target && e.target.className && -1 != e.target.className.indexOf( 'item-edit' ) ) { + $( "input[value='#pll_switcher'][type=text]" ).parent().parent().parent().each( + function () { + var item = $( this ).attr( 'id' ).substring( 19 ); + $( this ).children( 'p:not( .field-move )' ).remove(); // remove default fields we don't need + + // item is a number part of id of parent menu item built by WordPress + // pll_data is built server side with i18n strings without HTML and data retrieved from post meta + // the usage of attr method is safe before append call. + h = $( '' ).attr( + { + type: 'hidden', + id: 'edit-menu-item-title-' + item, + name: 'menu-item-title[' + item + ']', + value: pll_data.title + } + ); + $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + h = $( '' ).attr( + { + type: 'hidden', + id: 'edit-menu-item-url-' + item, + name: 'menu-item-url[' + item + ']', + value: '#pll_switcher' + } + ); + $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + // a hidden field which exits only if our jQuery code has been executed + h = $( '' ).attr( + { + type: 'hidden', + id: 'edit-menu-item-pll-detect-' + item, + name: 'menu-item-pll-detect[' + item + ']', + value: 1 + } + ); + $( this ).append( h ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + ids = Array( 'hide_if_no_translation', 'hide_current', 'force_home', 'show_flags', 'show_names', 'dropdown' ); // reverse order + + // add the fields + for ( var i = 0, idsLength = ids.length; i < idsLength; i++ ) { + p = $( '

    ' ).attr( 'class', 'description' ); + // p is hardcoded just above by using attr method which is safe. + $( this ).prepend( p ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + // item is a number part of id of parent menu item built by WordPress + // pll_data is built server side with i18n strings without HTML + label = $( '

    ' ).text( lang[1] ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + td.append( desc ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + } + ); + + td.append( '
    ' ); + // Whitelist because description come from html code generated by WordPress + td.append( span ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + } +); + diff --git a/wp-content/plugins/polylang/js/build/user.min.js b/wp-content/plugins/polylang/js/build/user.min.js new file mode 100644 index 0000000000..5ffd380a5d --- /dev/null +++ b/wp-content/plugins/polylang/js/build/user.min.js @@ -0,0 +1 @@ +jQuery((function(e){var n=e("#description").parent(),i=e("#description").clone(),t=n.children(".description").clone();n.children().remove(),e(".biography").each((function(){lang=e(this).attr("name").split("___"),desc=i.clone(),desc.attr("name","description_"+lang[0]),desc.attr("id","description_"+lang[0]),desc.html(e(this).val()),n.append(e("
    ").text(lang[1])),n.append(desc)})),n.append("
    "),n.append(t)})); \ No newline at end of file diff --git a/wp-content/plugins/polylang/js/build/widgets.js b/wp-content/plugins/polylang/js/build/widgets.js new file mode 100644 index 0000000000..3d2ec2c1b4 --- /dev/null +++ b/wp-content/plugins/polylang/js/build/widgets.js @@ -0,0 +1,152 @@ +/** + * Adds a flag to the widgets filtered by a language. + * + * @package Polylang + */ + +jQuery( + function ( $ ) { + var widgets_container, + widgets_selector, + flags, + isBlockEditor = 'undefined' !== typeof wp.blockEditor; + + if ( 'undefined' !== typeof pll_widgets && pll_widgets.hasOwnProperty( 'flags' ) ) { + flags = pll_widgets.flags; + } + + /** + * Prepend widget titles with a flag once a language is selected. + * + * @param {object} widget The widget element. + * @return {void} Nothing. + */ + function add_flag( widget ) { + if ( ! flags ) { + return; + } + widget = $( widget ); + var title = isBlockEditor ? widget.prev('h3') : $( '.widget-top .widget-title h3', widget ), + locale = $( '.pll-lang-choice option:selected', widget ).val(), + // Icon is HTML built and come from server side and is well escaped when necessary + icon = ( locale && flags.hasOwnProperty( locale ) ) ? flags[ locale ] : null; + + if ( icon ) { + icon += '   '; + var current = $( '.pll-lang', title ); + if ( current.length ) { + current.html( icon ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + } else { + flag = $( '' ).addClass( 'pll-lang' ).html( icon ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + // See the comment above about the icon which is safe. So it is also safe to prepend flag which uses icon. + title.prepend( flag ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + } else { + $( '.pll-lang', title ).remove(); + } + } + + if ( isBlockEditor ) { + + widgets_container = $( '.edit-widgets-main-block-list' ); + widgets_selector = '.widget'; + + // Update flags when we click on the legacy widget to display its form. + widgets_container.on( + 'click', + '.wp-block-legacy-widget', + function () { + add_flag( $( this ).find( '.widget' ) ); + } + ); + + } else { + if ( 'undefined' !== typeof wp.customize ) { + + widgets_container = $( '#customize-controls' ); + widgets_selector = '.customize-control .widget'; + + /** + * WP Customizer add control listener. + * + * @link https://wordpress.stackexchange.com/questions/256536/callback-after-wordpress-customizer-complete-loading + * + * @param {object} control The control type. + * @return {void} Nothing. + */ + function customize_add_flag( control ) { + if ( ! control.extended( wp.customize.Widgets.WidgetControl ) ) { + return; + } + + /* + * Make sure the widget's contents are embedded; normally this is done + * when the control is expanded, for DOM performance reasons. + */ + control.embedWidgetContent(); + + // Now we know for sure the widget is fully embedded. + add_flag( control.container.find( '.widget' ) ); + } + wp.customize.control.each( customize_add_flag ); + wp.customize.control.bind( 'add', customize_add_flag ); + + } else { + + widgets_container = $( '#widgets-right' ); + widgets_selector = '.widget'; + + } + + // Add flags on load. + $( widgets_selector, widgets_container ).each( + function () { + add_flag( this ); + } + ); + } + + // Update flags. + widgets_container.on( + 'change', + '.pll-lang-choice', + function () { + add_flag( $( this ).parents( '.widget' ) ); + } + ); + + function pll_toggle( a, test ) { + test ? a.show() : a.hide(); + } + + // Remove all options if dropdown is checked. + $( '.widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list' ).on( + 'change', + '.pll-dropdown', + function () { + var this_id = $( this ).parent().parent().parent().children( '.widget-id' ).attr( 'value' ); + pll_toggle( $( '.no-dropdown-' + this_id ), true != $( this ).prop( 'checked' ) ); + } + ); + + // Disallow unchecking both show names and show flags. + var options = ['-show_flags', '-show_names']; + $.each( + options, + function ( i, v ) { + $( '.widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list' ).on( + 'change', + '.pll' + v, + function () { + var this_id = $( this ).parent().parent().parent().children( '.widget-id' ).attr( 'value' ); + if ( true != $( this ).prop( 'checked' ) ) { + $( '#widget-' + this_id + options[ 1 - i ] ).prop( 'checked', true ); + } + } + ); + } + ); + + } +); + diff --git a/wp-content/plugins/polylang/js/build/widgets.min.js b/wp-content/plugins/polylang/js/build/widgets.min.js new file mode 100644 index 0000000000..50d281224c --- /dev/null +++ b/wp-content/plugins/polylang/js/build/widgets.min.js @@ -0,0 +1 @@ +jQuery((function(e){var t,i,n,o=void 0!==wp.blockEditor;function l(t){if(n){t=e(t);var i=o?t.prev("h3"):e(".widget-top .widget-title h3",t),l=e(".pll-lang-choice option:selected",t).val(),d=l&&n.hasOwnProperty(l)?n[l]:null;if(d){d+="   ";var s=e(".pll-lang",i);s.length?s.html(d):(flag=e("").addClass("pll-lang").html(d),i.prepend(flag))}else e(".pll-lang",i).remove()}}if("undefined"!=typeof pll_widgets&&pll_widgets.hasOwnProperty("flags")&&(n=pll_widgets.flags),o)i=".widget",(t=e(".edit-widgets-main-block-list")).on("click",".wp-block-legacy-widget",(function(){l(e(this).find(".widget"))}));else{if(void 0!==wp.customize){function d(e){e.extended(wp.customize.Widgets.WidgetControl)&&(e.embedWidgetContent(),l(e.container.find(".widget")))}t=e("#customize-controls"),i=".customize-control .widget",wp.customize.control.each(d),wp.customize.control.bind("add",d)}else t=e("#widgets-right"),i=".widget";e(i,t).each((function(){l(this)}))}t.on("change",".pll-lang-choice",(function(){l(e(this).parents(".widget"))})),e(".widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list").on("change",".pll-dropdown",(function(){var t,i=e(this).parent().parent().parent().children(".widget-id").attr("value");t=e(".no-dropdown-"+i),1!=e(this).prop("checked")?t.show():t.hide()}));var s=["-show_flags","-show_names"];e.each(s,(function(t,i){e(".widgets-sortables,.control-section-sidebar,.edit-widgets-main-block-list").on("change",".pll"+i,(function(){var i=e(this).parent().parent().parent().children(".widget-id").attr("value");1!=e(this).prop("checked")&&e("#widget-"+i+s[1-t]).prop("checked",!0)}))}))})); \ No newline at end of file diff --git a/wp-content/plugins/polylang/modules/machine-translation/load.php b/wp-content/plugins/polylang/modules/machine-translation/load.php new file mode 100644 index 0000000000..ea9e04fbf0 --- /dev/null +++ b/wp-content/plugins/polylang/modules/machine-translation/load.php @@ -0,0 +1,18 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Machine_Translation'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php b/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php new file mode 100644 index 0000000000..96514330da --- /dev/null +++ b/wp-content/plugins/polylang/modules/machine-translation/settings-preview-machine-translation.php @@ -0,0 +1,46 @@ + 'machine_translation', + 'title' => __( 'Machine Translation', 'polylang' ), + 'description' => __( 'Allows linkage to DeepL Translate.', 'polylang' ), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } +} diff --git a/wp-content/plugins/polylang/modules/share-slug/load.php b/wp-content/plugins/polylang/modules/share-slug/load.php new file mode 100644 index 0000000000..771939c6fd --- /dev/null +++ b/wp-content/plugins/polylang/modules/share-slug/load.php @@ -0,0 +1,20 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Share_Slug'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php b/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php new file mode 100644 index 0000000000..17f6913779 --- /dev/null +++ b/wp-content/plugins/polylang/modules/share-slug/settings-preview-share-slug.php @@ -0,0 +1,56 @@ + 'share-slugs', + 'title' => __( 'Share slugs', 'polylang' ), + 'description' => $this->get_description(), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } + + /** + * Returns the module description. + * + * @since 3.1 + * + * @return string + */ + protected function get_description() { + return __( 'Allows to share the same URL slug across languages for posts and terms.', 'polylang' ); + } +} diff --git a/wp-content/plugins/polylang/modules/site-health/admin-site-health.php b/wp-content/plugins/polylang/modules/site-health/admin-site-health.php new file mode 100644 index 0000000000..c71d93f67c --- /dev/null +++ b/wp-content/plugins/polylang/modules/site-health/admin-site-health.php @@ -0,0 +1,520 @@ +model = &$polylang->model; + $this->static_pages = &$polylang->static_pages; + + // Information tab. + add_filter( 'debug_information', array( $this, 'info_options' ), 15 ); + add_filter( 'debug_information', array( $this, 'info_languages' ), 15 ); + add_filter( 'debug_information', array( $this, 'info' ), 15 ); + + // Tests Tab. + add_filter( 'site_status_tests', array( $this, 'status_tests' ) ); + add_filter( 'site_status_test_php_modules', array( $this, 'site_status_test_php_modules' ) ); // Require simplexml in Site health. + } + + /** + * Returns a list of keys to exclude from the site health information. + * + * @since 2.8 + * + * @return string[] List of option keys to ignore. + */ + protected function exclude_options_keys() { + return array( + 'uninstall', + 'first_activation', + ); + } + + /** + * Returns a list of keys to exclude from the site health information. + * + * @since 2.8 + * + * @return string[] List of language keys to ignore. + */ + protected function exclude_language_keys() { + return array( + 'flag', + 'host', + 'taxonomy', + 'description', + 'parent', + 'filter', + 'custom_flag', + ); + } + + /** + * Formats an array to display in options information. + * + * @since 2.8 + * + * @param array $array An array of formatted data. + * @return string + */ + protected function format_array( $array ) { + array_walk( + $array, + function ( &$value, $key ) { + if ( is_array( $value ) ) { + $ids = implode( ' , ', $value ); + $value = "$key => $ids"; + } else { + $value = "$key => $value"; + } + } + ); + return implode( ' | ', $array ); + } + + /** + * Transforms the option value to readable human sentence. + * + * @since 3.3 + * + * @param string $key Option name. + * @param mixed $value Option value. + * @return mixed Option value. + */ + public function format_value( $key, $value ) { + switch ( $key ) { + case 'browser': + if ( ! $value ) { + $value = '0: ' . esc_html__( 'Detect browser language deactivated', 'polylang' ); + break; + } + $value = '1: ' . esc_html__( 'Detect browser language activated', 'polylang' ); + break; + case 'rewrite': + if ( $value ) { + $value = '1: ' . esc_html__( 'Remove /language/ in pretty permalinks', 'polylang' ); + break; + } + $value = '0: ' . esc_html__( 'Keep /language/ in pretty permalinks', 'polylang' ); + break; + case 'hide_default': + if ( $value ) { + $value = '1: ' . esc_html__( 'Hide URL language information for default language', 'polylang' ); + break; + } + $value = '0: ' . esc_html__( 'Display URL language information for default language', 'polylang' ); + break; + case 'force_lang': + switch ( $value ) { + case '0': + $value = '0: ' . esc_html__( 'The language is set from content', 'polylang' ); + break; + case '1': + $value = '1: ' . esc_html__( 'The language is set from the directory name in pretty permalinks', 'polylang' ); + break; + case '2': + $value = '2: ' . esc_html__( 'The language is set from the subdomain name in pretty permalinks', 'polylang' ); + break; + case '3': + $value = '3: ' . esc_html__( 'The language is set from different domains', 'polylang' ); + break; + } + break; + case 'redirect_lang': + if ( $value ) { + $value = '1: ' . esc_html__( 'The front page URL contains the language code instead of the page name or page id', 'polylang' ); + break; + } + $value = '0: ' . esc_html__( 'The front page URL contains the page name or page id instead of the language code', 'polylang' ); + + break; + case 'media_support': + if ( ! $value ) { + $value = '0: ' . esc_html__( 'The media are not translated', 'polylang' ); + break; + } + $value = '1: ' . esc_html__( 'The media are translated', 'polylang' ); + break; + + case 'sync': + if ( empty( $value ) ) { + $value = '0: ' . esc_html__( 'Synchronization disabled', 'polylang' ); + } + break; + } + + return $value; + } + + /** + * Add Polylang Options to Site Health Information tab. + * + * @since 2.8 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info_options( $debug_info ) { + $fields = array(); + + foreach ( $this->model->options as $key => $value ) { + if ( in_array( $key, $this->exclude_options_keys() ) ) { + continue; + } + + $value = $this->format_value( $key, $value ); + + switch ( $key ) { + case 'domains': + if ( 3 === $this->model->options['force_lang'] ) { + $value = is_array( $value ) ? $value : array(); + $value = $this->format_array( $value ); + + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = $value; + } + break; + + case 'nav_menus': + $current_theme = get_stylesheet(); + if ( is_array( $value ) && isset( $value[ $current_theme ] ) ) { + foreach ( $value[ $current_theme ] as $location => $lang ) { + $lang = is_array( $lang ) ? $lang : array(); + + $fields[ $location ]['label'] = sprintf( 'menu: %s', $location ); + $fields[ $location ]['value'] = $this->format_array( $lang ); + } + } + break; + + case 'media': + $value = is_array( $value ) ? $value : array(); + foreach ( $value as $sub_key => $sub_value ) { + $fields[ "$key-$sub_key" ]['label'] = "$key $sub_key"; + $fields[ "$key-$sub_key" ]['value'] = $sub_value; + } + break; + + case 'post_types': + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_post_types() ); + break; + + case 'taxonomies': + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = implode( ', ', $this->model->get_translated_taxonomies() ); + break; + + default: + $fields[ $key ]['label'] = $key; + $fields[ $key ]['value'] = empty( $value ) ? '0' : $value; + break; + } + } + + $debug_info['pll_options'] = array( + /* translators: placeholder is the plugin name */ + 'label' => sprintf( __( '%s options', 'polylang' ), POLYLANG ), + 'fields' => $fields, + ); + + return $debug_info; + } + + /** + * Adds Polylang Languages settings to Site Health Information tab. + * + * @since 2.8 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info_languages( $debug_info ) { + foreach ( $this->model->get_languages_list() as $language ) { + $fields = array(); + + foreach ( $language->to_array() as $key => $value ) { + if ( in_array( $key, $this->exclude_language_keys(), true ) ) { + continue; + } + + if ( empty( $value ) ) { + $value = '0'; + } + + $fields[ $key ]['label'] = $key; + + if ( 'term_props' === $key && is_array( $value ) ) { + $fields[ $key ]['value'] = $this->get_info_term_props( $value ); + } else { + $fields[ $key ]['value'] = $value; + } + + if ( 'term_group' === $key ) { + $fields[ $key ]['label'] = 'order'; // Changed for readability but not translated as other keys are not. + } + } + + $debug_info[ 'pll_language_' . $language->slug ] = array( + /* translators: placeholder is the language name */ + 'label' => sprintf( __( 'Language: %s', 'polylang' ), $language->name ), + /* translators: placeholder is the flag image */ + 'description' => sprintf( esc_html__( 'Flag used in the language switcher: %s', 'polylang' ), $this->get_flag( $language ) ), + 'fields' => $fields, + ); + } + + return $debug_info; + } + + /** + * Adds term props data to the info languages array. + * + * @since 3.4 + * + * @param array $value The term props data. + * @return array The term props data formatted for the info languages tab. + */ + protected function get_info_term_props( $value ) { + $return_value = array(); + + foreach ( $value as $language_taxonomy => $item ) { + $language_taxonomy_array = array_fill( 0, count( $item ), $language_taxonomy ); + + $keys_with_language_taxonomy = array_map( + function ( $key, $language_taxonomy ) { + return "{$language_taxonomy}/{$key}"; + }, + array_keys( $item ), + $language_taxonomy_array + ); + + $value = array_combine( $keys_with_language_taxonomy, $item ); + if ( is_array( $value ) ) { + $return_value = array_merge( $return_value, $value ); + } + } + return $return_value; + } + + /** + * Returns the flag used in the language switcher. + * + * @since 2.8 + * + * @param PLL_Language $language Language object. + * @return string + */ + protected function get_flag( $language ) { + $flag = $language->get_display_flag(); + return empty( $flag ) ? '' . esc_html__( 'Undefined', 'polylang' ) . '' : $flag; + } + + /** + * Add a Site Health test on homepage translation. + * + * @since 2.8 + * + * @param array $tests Array with tests declaration data. + * @return array + */ + public function status_tests( $tests ) { + // Add the test only if the homepage displays static page. + if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) { + $tests['direct']['pll_homepage'] = array( + 'label' => esc_html__( 'Homepage translated', 'polylang' ), + 'test' => array( $this, 'homepage_test' ), + ); + } + return $tests; + } + + /** + * Test if the home page is translated or not. + * + * @since 2.8 + * + * @return array $result Array with test results. + */ + public function homepage_test() { + $result = array( + 'label' => esc_html__( 'All languages have a translated homepage', 'polylang' ), + 'status' => 'good', + 'badge' => array( + 'label' => POLYLANG, + 'color' => 'blue', + ), + 'description' => sprintf( + '

    %s

    ', + esc_html__( 'It is mandatory to translate the static front page in all languages.', 'polylang' ) + ), + 'actions' => '', + 'test' => 'pll_homepage', + ); + + $message = $this->static_pages->get_must_translate_message(); + + if ( ! empty( $message ) ) { + $result['status'] = 'critical'; + $result['label'] = esc_html__( 'The homepage is not translated in all languages', 'polylang' ); + $result['description'] = sprintf( '

    %s

    ', $message ); + } + return $result; + } + + /** + * Add Polylang Warnings to Site Health Information tab. + * + * @since 3.1 + * + * @param array $debug_info The debug information to be added to the core information page. + * @return array + */ + public function info( $debug_info ) { + $fields = array(); + + // Add Post Types without languages. + $posts_no_lang = $this->get_post_ids_without_lang(); + + if ( ! empty( $posts_no_lang ) ) { + $fields['post-no-lang']['label'] = __( 'Posts without language', 'polylang' ); + $fields['post-no-lang']['value'] = $this->format_array( $posts_no_lang ); + } + + $terms_no_lang = $this->get_term_ids_without_lang(); + + if ( ! empty( $terms_no_lang ) ) { + $fields['term-no-lang']['label'] = __( 'Terms without language', 'polylang' ); + $fields['term-no-lang']['value'] = $this->format_array( $terms_no_lang ); + } + + // Add WPML files. + $wpml_files = PLL_WPML_Config::instance()->get_files(); + if ( ! empty( $wpml_files ) ) { + $fields['wpml']['label'] = 'wpml-config.xml files'; + $fields['wpml']['value'] = $wpml_files; + + if ( ! extension_loaded( 'simplexml' ) ) { + $fields['simplexml']['label'] = __( 'PHP SimpleXML extension', 'polylang' ); + $fields['simplexml']['value'] = __( 'Not loaded. Contact your host provider.', 'polylang' ); + } + } + + // Create the section. + if ( ! empty( $fields ) ) { + $debug_info['pll_warnings'] = array( + /* translators: placeholder is the plugin name */ + 'label' => sprintf( __( '%s information', 'polylang' ), POLYLANG ), + 'fields' => $fields, + ); + } + + return $debug_info; + } + + /** + * Get an array with post_type as key and post ids as value. + * + * @since 3.1 + * + * @param int $limit Max number of posts to show per post type. `-1` to return all of them. Default is 5. + * @return int[][] Array containing an array of post ids. + * + * @phpstan-param -1|positive-int $limit + */ + public function get_post_ids_without_lang( $limit = 5 ) { + $posts = array(); + + foreach ( $this->model->get_translated_post_types() as $post_type ) { + $post_ids_with_no_language = $this->model->get_posts_with_no_lang( $post_type, $limit ); + + if ( ! empty( $post_ids_with_no_language ) ) { + foreach ( $post_ids_with_no_language as $id ) { + $posts[ $post_type ][] = $id; + } + } + } + + return $posts; + } + + /** + * Get an array with taxonomy as key and term ids as value. + * + * @since 3.1 + * + * @param int $limit Max number of terms to show per post type. `-1` to return all of them. Default is 5. + * @return int[][] Array containing an array of term ids. + * + * @phpstan-param -1|positive-int $limit + */ + public function get_term_ids_without_lang( $limit = 5 ) { + $terms = array(); + + foreach ( $this->model->get_translated_taxonomies() as $taxonomy ) { + $term_ids_with_no_language = $this->model->get_terms_with_no_lang( $taxonomy, $limit ); + + if ( ! empty( $term_ids_with_no_language ) ) { + foreach ( $term_ids_with_no_language as $id ) { + $terms[ $taxonomy ][] = $id; + } + } + } + + return $terms; + } + + /** + * Requires the simplexml PHP module when a wpml-config.xml has been found. + * + * @since 3.1 + * @since 3.2 Moved from PLL_WPML_Config + * + * @param array $modules An associative array of modules to test for. + * @return array + */ + public function site_status_test_php_modules( $modules ) { + $files = PLL_WPML_Config::instance()->get_files(); + if ( ! empty( $files ) ) { + $modules['simplexml'] = array( + 'extension' => 'simplexml', + 'required' => true, + ); + } + return $modules; + } +} diff --git a/wp-content/plugins/polylang/modules/site-health/load.php b/wp-content/plugins/polylang/modules/site-health/load.php new file mode 100644 index 0000000000..09edf1f2a7 --- /dev/null +++ b/wp-content/plugins/polylang/modules/site-health/load.php @@ -0,0 +1,14 @@ +model->has_languages() ) { + $polylang->site_health = new PLL_Admin_Site_Health( $polylang ); +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php b/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php new file mode 100644 index 0000000000..a74e60e38b --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/abstract-sitemaps.php @@ -0,0 +1,37 @@ + 'class-wp-sitemaps-posts' ); + return $whitelist; + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/load.php b/wp-content/plugins/polylang/modules/sitemaps/load.php new file mode 100644 index 0000000000..9185676aa6 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/load.php @@ -0,0 +1,17 @@ +model->has_languages() ) { + if ( $polylang->links_model instanceof PLL_Links_Abstract_Domain ) { + $polylang->sitemaps = new PLL_Sitemaps_Domain( $polylang ); + } else { + $polylang->sitemaps = new PLL_Sitemaps( $polylang ); + } + $polylang->sitemaps->init(); +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php b/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php new file mode 100644 index 0000000000..535adc79c5 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/multilingual-sitemaps-provider.php @@ -0,0 +1,218 @@ +name = $provider->name; + $this->object_type = $provider->object_type; + + $this->provider = $provider; + $this->links_model = &$links_model; + $this->model = &$links_model->model; + } + + /** + * Gets a URL list for a sitemap. + * + * @since 2.8 + * + * @param int $page_num Page of results. + * @param string $object_subtype Optional. Object subtype name. Default empty. + * @return array Array of URLs for a sitemap. + */ + public function get_url_list( $page_num, $object_subtype = '' ) { + return $this->provider->get_url_list( $page_num, $object_subtype ); + } + + /** + * Gets the max number of pages available for the object type. + * + * @since 2.8 + * + * @param string $object_subtype Optional. Object subtype. Default empty. + * @return int Total number of pages. + */ + public function get_max_num_pages( $object_subtype = '' ) { + return $this->provider->get_max_num_pages( $object_subtype ); + } + + /** + * Filters the query arguments to add the language. + * + * @since 2.8 + * + * @param array $args Sitemap provider WP_Query or WP_Term_Query arguments. + * @return array + */ + public static function query_args( $args ) { + if ( ! empty( self::$filter_lang ) ) { + $args['lang'] = self::$filter_lang; + } + return $args; + } + + /** + * Gets data for a given sitemap type. + * + * @since 2.8 + * + * @param string $object_subtype_name Object subtype name if any. + * @param string $lang Optional language name. + * @return array + */ + protected function get_sitemap_data( $object_subtype_name, $lang = '' ) { + $object_subtype_name = (string) $object_subtype_name; + + if ( ! empty( $lang ) ) { + self::$filter_lang = $lang; + } + + $return = array( + 'name' => implode( '-', array_filter( array( $object_subtype_name, $lang ) ) ), + 'pages' => $this->get_max_num_pages( $object_subtype_name ), + ); + + self::$filter_lang = ''; + return $return; + } + + /** + * Gets data about each sitemap type. + * + * @since 2.8 + * + * @return array[] Array of sitemap types including object subtype name and number of pages. + */ + public function get_sitemap_type_data() { + $sitemap_data = array(); + + add_filter( 'wp_sitemaps_posts_query_args', array( __CLASS__, 'query_args' ) ); + add_filter( 'wp_sitemaps_taxonomies_query_args', array( __CLASS__, 'query_args' ) ); + + $object_subtypes = $this->get_object_subtypes(); + + if ( empty( $object_subtypes ) ) { + foreach ( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) as $language ) { + $sitemap_data[] = $this->get_sitemap_data( '', $language ); + } + } + + switch ( $this->provider->name ) { + case 'posts': + $func = array( $this->model, 'is_translated_post_type' ); + break; + case 'taxonomies': + $func = array( $this->model, 'is_translated_taxonomy' ); + break; + default: + return $sitemap_data; + } + + foreach ( array_keys( $object_subtypes ) as $object_subtype_name ) { + if ( call_user_func( $func, $object_subtype_name ) ) { + foreach ( $this->model->get_languages_list( array( 'fields' => 'slug' ) ) as $language ) { + $sitemap_data[] = $this->get_sitemap_data( $object_subtype_name, $language ); + } + } else { + $sitemap_data[] = $this->get_sitemap_data( $object_subtype_name ); + } + } + + return $sitemap_data; + } + + /** + * Gets the URL of a sitemap entry. + * + * @since 2.8 + * + * @param string $name The name of the sitemap. + * @param int $page The page of the sitemap. + * @return string The composed URL for a sitemap entry. + */ + public function get_sitemap_url( $name, $page ) { + // Check if a language was added in $name. + $pattern = '#(' . implode( '|', $this->model->get_languages_list( array( 'fields' => 'slug' ) ) ) . ')$#'; + if ( preg_match( $pattern, $name, $matches ) ) { + $lang = $this->model->get_language( $matches[1] ); + + if ( ! empty( $lang ) ) { + $name = preg_replace( '#(-?' . $lang->slug . ')$#', '', $name ); + $url = $this->provider->get_sitemap_url( $name, $page ); + return $this->links_model->add_language_to_link( $url, $lang ); + } + } + + // If no language is present in $name, we may attempt to get the current sitemap url (e.g. in redirect_canonical() ). + if ( get_query_var( 'lang' ) ) { + $lang = $this->model->get_language( get_query_var( 'lang' ) ); + $url = $this->provider->get_sitemap_url( $name, $page ); + return $this->links_model->add_language_to_link( $url, $lang ); + } + + return $this->provider->get_sitemap_url( $name, $page ); + } + + /** + * Returns the list of supported object subtypes exposed by the provider. + * + * @since 2.8 + * + * @return array List of object subtypes objects keyed by their name. + */ + public function get_object_subtypes() { + return $this->provider->get_object_subtypes(); + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php b/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php new file mode 100644 index 0000000000..62945471d0 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/sitemaps-domain.php @@ -0,0 +1,71 @@ +links_model = &$polylang->links_model; + } + + /** + * Setups actions and filters. + * + * @since 3.0 + * + * @return void + */ + public function init() { + parent::init(); + + add_filter( 'wp_sitemaps_index_entry', array( $this, 'index_entry' ) ); + add_filter( 'wp_sitemaps_stylesheet_url', array( $this->links_model, 'site_url' ) ); + add_filter( 'wp_sitemaps_stylesheet_index_url', array( $this->links_model, 'site_url' ) ); + add_filter( 'home_url', array( $this, 'sitemap_url' ) ); + } + + /** + * Filters the sitemap index entries for subdomains and multiple domains. + * + * @since 2.8 + * + * @param array $sitemap_entry Sitemap entry for the post. + * @return array + */ + public function index_entry( $sitemap_entry ) { + $sitemap_entry['loc'] = $this->links_model->site_url( $sitemap_entry['loc'] ); + return $sitemap_entry; + } + + /** + * Makes sure that the sitemap urls are always evaluated on the current domain. + * + * @since 2.8.4 + * + * @param string $url A sitemap url. + * @return string + */ + public function sitemap_url( $url ) { + if ( false !== strpos( $url, '/wp-sitemap' ) ) { + $url = $this->links_model->site_url( $url ); + } + return $url; + } +} diff --git a/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php b/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php new file mode 100644 index 0000000000..799f614979 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sitemaps/sitemaps.php @@ -0,0 +1,128 @@ +links_model = &$polylang->links_model; + $this->model = &$polylang->model; + $this->options = &$polylang->options; + } + + /** + * Setups actions and filters. + * + * @since 2.8 + * + * @return void + */ + public function init() { + parent::init(); + + add_filter( 'pll_set_language_from_query', array( $this, 'set_language_from_query' ), 10, 2 ); + add_filter( 'rewrite_rules_array', array( $this, 'rewrite_rules' ) ); + add_filter( 'wp_sitemaps_add_provider', array( $this, 'replace_provider' ) ); + } + + /** + * Assigns the current language to the default language when the sitemap url + * doesn't include any language. + * + * @since 2.8 + * + * @param string|bool $lang Current language code, false if not set yet. + * @param WP_Query $query Main WP query object. + * @return string|bool + */ + public function set_language_from_query( $lang, $query ) { + if ( isset( $query->query['sitemap'] ) && empty( $query->query['lang'] ) ) { + $lang = $this->options['default_lang']; + } + return $lang; + } + + /** + * Filters the sitemaps rewrite rules to take the languages into account. + * + * @since 2.8 + * + * @param string[] $rules Rewrite rules. + * @return string[] Modified rewrite rules. + */ + public function rewrite_rules( $rules ) { + global $wp_rewrite; + + $languages = $this->model->get_languages_list( + array( + 'fields' => 'slug', + 'hide_default' => $this->options['hide_default'], + ) + ); + + if ( empty( $languages ) ) { + return $rules; + } + + $slug = $wp_rewrite->root . ( $this->options['rewrite'] ? '^' : '^language/' ) . '(' . implode( '|', $languages ) . ')/'; + + $newrules = array(); + + foreach ( $rules as $key => $rule ) { + if ( false !== strpos( $rule, 'sitemap=$matches[1]' ) ) { + $newrules[ str_replace( '^wp-sitemap', $slug . 'wp-sitemap', $key ) ] = str_replace( + array( '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '[1]', '?' ), + array( '[9]', '[8]', '[7]', '[6]', '[5]', '[4]', '[3]', '[2]', '?lang=$matches[1]&' ), + $rule + ); // Should be enough! + } + + $newrules[ $key ] = $rule; + } + return $newrules; + } + + /** + * Replaces a sitemap provider by our decorator. + * + * @since 2.8 + * + * @param WP_Sitemaps_Provider $provider Instance of a WP_Sitemaps_Provider. + * @return WP_Sitemaps_Provider + */ + public function replace_provider( $provider ) { + if ( $provider instanceof WP_Sitemaps_Provider ) { + $provider = new PLL_Multilingual_Sitemaps_Provider( $provider, $this->links_model ); + } + return $provider; + } +} diff --git a/wp-content/plugins/polylang/modules/sync/admin-sync.php b/wp-content/plugins/polylang/modules/sync/admin-sync.php new file mode 100644 index 0000000000..5d55e3284b --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/admin-sync.php @@ -0,0 +1,233 @@ +model->post->get_translation( $id, sanitize_key( $_GET['new_lang'] ) ) ) { + $post_parent = $parent; + } + } + return $post_parent; + } + + /** + * Copy menu order, comment, ping status and optionally the date when creating a new translation + * + * @since 2.5 + * + * @param array $data An array of slashed post data. + * @return array + */ + public function wp_insert_post_data( $data ) { + if ( isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] && $this->model->is_translated_post_type( $data['post_type'] ) ) { + check_admin_referer( 'new-post-translation' ); + + $from_post_id = (int) $_GET['from_post']; + $from_post = get_post( $from_post_id ); + + if ( $from_post instanceof WP_Post ) { + foreach ( array( 'menu_order', 'comment_status', 'ping_status' ) as $property ) { + $data[ $property ] = $from_post->$property; + } + + // Copy the date only if the synchronization is activated + if ( in_array( 'post_date', $this->options['sync'] ) ) { + $data['post_date'] = $from_post->post_date; + $data['post_date_gmt'] = $from_post->post_date_gmt; + } + } + } + + return $data; + } + + /** + * Copy post metas, and taxonomies when using "Add new" ( translation ) + * + * @since 2.5 + * @since 3.1 Use of use_block_editor_for_post filter instead of rest_api_init which is triggered too early in WP 5.8. + * + * @param bool $is_block_editor Whether the post can be edited or not. + * @return bool + */ + public function new_post_translation( $is_block_editor ) { + global $post; + static $done = array(); + + if ( ! empty( $post ) && isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] && $this->model->is_translated_post_type( $post->post_type ) ) { + check_admin_referer( 'new-post-translation' ); + + // Capability check already done in post-new.php + $from_post_id = (int) $_GET['from_post']; + $lang = $this->model->get_language( sanitize_key( $_GET['new_lang'] ) ); + + if ( ! $from_post_id || ! $lang || ! empty( $done[ $from_post_id ] ) ) { + return $is_block_editor; + } + + $done[ $from_post_id ] = true; // Avoid a second duplication in the block editor. Using an array only to allow multiple phpunit tests. + + $this->taxonomies->copy( $from_post_id, $post->ID, $lang->slug ); + $this->post_metas->copy( $from_post_id, $post->ID, $lang->slug ); + + if ( is_sticky( $from_post_id ) ) { + stick_post( $post->ID ); + } + } + + return $is_block_editor; + } + + /** + * Get post fields to synchronize. + * + * @since 2.4 + * + * @param WP_Post $post Post object. + * @return array Fields to synchronize. + */ + protected function get_fields_to_sync( $post ) { + global $wpdb; + + $postarr = parent::get_fields_to_sync( $post ); + + // For new drafts, save the date now otherwise it is overridden by WP. Thanks to JoryHogeveen. See #32. + if ( in_array( 'post_date', $this->options['sync'] ) && isset( $GLOBALS['pagenow'], $_GET['from_post'], $_GET['new_lang'] ) && 'post-new.php' === $GLOBALS['pagenow'] ) { + check_admin_referer( 'new-post-translation' ); + + unset( $postarr['post_date'] ); + unset( $postarr['post_date_gmt'] ); + + $original = get_post( (int) $_GET['from_post'] ); + + if ( $original instanceof WP_Post ) { + $wpdb->update( + $wpdb->posts, + array( + 'post_date' => $original->post_date, + 'post_date_gmt' => $original->post_date_gmt, + ), + array( 'ID' => $post->ID ) + ); + } + } + + if ( isset( $GLOBALS['post_type'] ) ) { + $post_type = $GLOBALS['post_type']; + } elseif ( isset( $_REQUEST['post_type'] ) ) { + $post_type = sanitize_key( $_REQUEST['post_type'] ); // 2nd case for quick edit + } + + // Make sure not to impact media translations when creating them at the same time as post + if ( in_array( 'post_parent', $this->options['sync'] ) && ( ! isset( $post_type ) || $post_type !== $post->post_type ) ) { + unset( $postarr['post_parent'] ); + } + + return $postarr; + } + + /** + * Synchronizes post fields in translations. + * + * @since 1.2 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param int[] $translations Post translations. + */ + public function pll_save_post( $post_id, $post, $translations ) { + parent::pll_save_post( $post_id, $post, $translations ); + + // Sticky posts + if ( in_array( 'sticky_posts', $this->options['sync'] ) ) { + $stickies = get_option( 'sticky_posts' ); + if ( isset( $_REQUEST['sticky'] ) && 'sticky' === $_REQUEST['sticky'] ) { // phpcs:ignore WordPress.Security.NonceVerification + $stickies = array_merge( $stickies, array_values( $translations ) ); + } else { + $stickies = array_diff( $stickies, array_values( $translations ) ); + } + update_option( 'sticky_posts', array_unique( $stickies ) ); + } + } + + /** + * Some backward compatibility with Polylang < 2.3 + * allows to call PLL()->sync->copy_post_metas() and PLL()->sync->copy_taxonomies() + * used for example in Polylang for WooCommerce + * the compatibility is however only partial as the 4th argument $sync is lost + * + * @since 2.3 + * + * @param string $func Function name + * @param array $args Function arguments + * @return mixed|void + */ + public function __call( $func, $args ) { + $obj = substr( $func, 5 ); + + if ( is_object( $this->$obj ) && method_exists( $this->$obj, 'copy' ) ) { + if ( WP_DEBUG ) { + $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions + $i = 1 + empty( $debug[1]['line'] ); // The file and line are in $debug[2] if the function was called using call_user_func + + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions + sprintf( + '%1$s was called incorrectly in %3$s on line %4$s: the call to PLL()->sync->%1$s() has been deprecated in Polylang 2.3, use PLL()->sync->%2$s->copy() instead.' . "\nError handler", + esc_html( $func ), + esc_html( $obj ), + esc_html( $debug[ $i ]['file'] ), + absint( $debug[ $i ]['line'] ) + ) + ); + } + return call_user_func_array( array( $this->$obj, 'copy' ), $args ); + } + + $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions + trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions + sprintf( + 'Call to undefined function PLL()->sync->%1$s() in %2$s on line %3$s' . "\nError handler", + esc_html( $func ), + esc_html( $debug[0]['file'] ), + absint( $debug[0]['line'] ) + ), + E_USER_ERROR + ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/load.php b/wp-content/plugins/polylang/modules/sync/load.php new file mode 100644 index 0000000000..6978cd6f8c --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/load.php @@ -0,0 +1,26 @@ +model->has_languages() ) { + if ( $polylang instanceof PLL_Admin_Base ) { + $polylang->sync = new PLL_Admin_Sync( $polylang ); + } else { + $polylang->sync = new PLL_Sync( $polylang ); + } + + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Sync'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/sync/settings-sync.php b/wp-content/plugins/polylang/modules/sync/settings-sync.php new file mode 100644 index 0000000000..4f00285695 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/settings-sync.php @@ -0,0 +1,115 @@ + 'sync', + 'title' => __( 'Synchronization', 'polylang' ), + 'description' => __( 'The synchronization options allow to maintain exact same values (or translations in the case of taxonomies and page parent) of meta content between the translations of a post or page.', 'polylang' ), + ) + ); + } + + /** + * Deactivates the module + * + * @since 1.8 + */ + public function deactivate() { + $this->options['sync'] = array(); + update_option( 'polylang', $this->options ); + } + + /** + * Displays the settings form + * + * @since 1.8 + */ + protected function form() { + ?> +
      + $str ) { + printf( + '
    • ', + esc_attr( $key ), + checked( in_array( $key, $this->options['sync'] ), true, false ), + esc_html( $str ) + ); + } + ?> +
    + empty( $options['sync'] ) ? array() : array_keys( $options['sync'], 1 ) ); + return $newoptions; // Take care to return only validated options. + } + + /** + * Get the row actions. + * + * @since 1.8 + * + * @return string[] Row actions. + */ + protected function get_actions() { + return empty( $this->options['sync'] ) ? array( 'configure' ) : array( 'configure', 'deactivate' ); + } + + /** + * Get the list of synchronization settings. + * + * @since 1.0 + * + * @return string[] Array synchronization options. + */ + public static function list_metas_to_sync() { + return array( + 'taxonomies' => __( 'Taxonomies', 'polylang' ), + 'post_meta' => __( 'Custom fields', 'polylang' ), + 'comment_status' => __( 'Comment status', 'polylang' ), + 'ping_status' => __( 'Ping status', 'polylang' ), + 'sticky_posts' => __( 'Sticky posts', 'polylang' ), + 'post_date' => __( 'Published date', 'polylang' ), + 'post_format' => __( 'Post format', 'polylang' ), + 'post_parent' => __( 'Page parent', 'polylang' ), + '_wp_page_template' => __( 'Page template', 'polylang' ), + 'menu_order' => __( 'Page order', 'polylang' ), + '_thumbnail_id' => __( 'Featured image', 'polylang' ), + ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-metas.php b/wp-content/plugins/polylang/modules/sync/sync-metas.php new file mode 100644 index 0000000000..5a77213a43 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-metas.php @@ -0,0 +1,406 @@ +model = &$polylang->model; + + add_filter( "add_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + add_filter( "update_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + add_filter( "delete_{$this->meta_type}_metadata", array( $this, 'can_synchronize_metadata' ), 1, 3 ); + + $this->add_all_meta_actions(); + + add_action( "pll_save_{$this->meta_type}", array( $this, 'save_object' ), 10, 3 ); + } + + /** + * Removes "added_{$this->meta_type}_meta" action + * + * @since 2.3 + * + * @return void + */ + protected function remove_add_meta_action() { + remove_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ) ); + } + + /** + * Removes all meta synchronization actions and filters + * + * @since 2.3 + * + * @return void + */ + public function remove_all_meta_actions() { + $this->remove_add_meta_action(); + + remove_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999 ); + remove_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ) ); + + remove_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ) ); + remove_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ) ); + } + + /** + * Adds "added_{$this->meta_type}_meta" action + * + * @since 2.3 + * + * @return void + */ + protected function restore_add_meta_action() { + add_action( "added_{$this->meta_type}_meta", array( $this, 'add_meta' ), 10, 4 ); + } + + /** + * Adds meta synchronization actions and filters + * + * @since 2.3 + * + * @return void + */ + public function add_all_meta_actions() { + $this->restore_add_meta_action(); + + add_filter( "update_{$this->meta_type}_metadata", array( $this, 'update_metadata' ), 999, 5 ); // Very late in case a filter prevents the meta to be updated + add_action( "update_{$this->meta_type}_meta", array( $this, 'update_meta' ), 10, 4 ); + + add_action( "delete_{$this->meta_type}_meta", array( $this, 'store_metas_to_sync' ), 10, 2 ); + add_action( "deleted_{$this->meta_type}_meta", array( $this, 'delete_meta' ), 10, 4 ); + } + + /** + * Maybe modify ("translate") a meta value when it is copied or synchronized + * + * @since 2.3 + * + * @param mixed $value Meta value + * @param string $key Meta key + * @param int $from Id of the source + * @param int $to Id of the target + * @param string $lang Language of target + * @return mixed + */ + protected function maybe_translate_value( $value, $key, $from, $to, $lang ) { + /** + * Filter a meta value before is copied or synchronized + * + * @since 2.3 + * + * @param mixed $value Meta value + * @param string $key Meta key + * @param string $lang Language of target + * @param int $from Id of the source + * @param int $to Id of the target + */ + return apply_filters( "pll_translate_{$this->meta_type}_meta", maybe_unserialize( $value ), $key, $lang, $from, $to ); + } + + /** + * Get the custom fields to copy or synchronize. + * + * @since 2.3 + * + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @return string[] List of meta keys. + */ + protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) { + /** + * Filters the custom fields to copy or synchronize. + * + * @since 0.6 + * @since 1.9.2 The `$from`, `$to`, `$lang` parameters were added. + * + * @param string[] $keys List of custom fields names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + */ + return array_unique( apply_filters( "pll_copy_{$this->meta_type}_metas", array(), $sync, $from, $to, $lang ) ); + } + + /** + * Disallow modifying synchronized meta if the current user can not modify translations + * + * @since 2.6 + * + * @param null|bool $check Whether to allow adding/updating/deleting metadata. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @return null|bool + */ + public function can_synchronize_metadata( $check, $id, $meta_key ) { + if ( ! $this->model->{$this->meta_type}->current_user_can_synchronize( $id ) ) { + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + if ( in_array( $meta_key, $to_copy ) ) { + return false; + } + } + } + } + return $check; + } + + /** + * Synchronize added metas across translations + * + * @since 2.3 + * + * @param int $mid Meta id. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @return void + */ + public function add_meta( $mid, $id, $meta_key, $meta_value ) { + static $avoid_recursion = false; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + $to_copy = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + if ( in_array( $meta_key, $to_copy ) ) { + $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang ); + add_metadata( $this->meta_type, $tr_id, wp_slash( $meta_key ), is_object( $meta_value ) ? $meta_value : wp_slash( $meta_value ) ); + } + } + } + + $avoid_recursion = false; + } + } + + /** + * Stores the previous value when updating metas + * + * @since 2.3 + * + * @param null|bool $r Not used + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @param mixed $prev_value If specified, only update existing metadata entries with the specified value. + * @return null|bool Unchanged + */ + public function update_metadata( $r, $id, $meta_key, $meta_value, $prev_value ) { + if ( null === $r ) { + $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) ); + $this->prev_value[ $hash ] = $prev_value; + } + return $r; + } + + /** + * Synchronize updated metas across translations + * + * @since 2.3 + * + * @param int $mid Meta id. + * @param int $id Object ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. Must be serializable if non-scalar. + * @return void + */ + public function update_meta( $mid, $id, $meta_key, $meta_value ) { + static $avoid_recursion = false; + $id = (int) $id; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + $hash = md5( "$id|$meta_key|" . maybe_serialize( $meta_value ) ); + + $prev_meta = get_metadata_by_mid( $this->meta_type, $mid ); + + if ( $prev_meta ) { + $this->remove_add_meta_action(); // We don't want to sync back the new metas + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id && in_array( $meta_key, $this->get_metas_to_copy( $id, $tr_id, $lang, true ) ) ) { + if ( empty( $this->prev_value[ $hash ] ) || $this->prev_value[ $hash ] === $prev_meta->meta_value ) { + $prev_value = $this->maybe_translate_value( $prev_meta->meta_value, $meta_key, $id, $tr_id, $lang ); + $meta_value = $this->maybe_translate_value( $meta_value, $meta_key, $id, $tr_id, $lang ); + update_metadata( $this->meta_type, $tr_id, wp_slash( $meta_key ), is_object( $meta_value ) ? $meta_value : wp_slash( $meta_value ), $prev_value ); + } + } + } + $this->restore_add_meta_action(); + } + + unset( $this->prev_value[ $hash ] ); + $avoid_recursion = false; + } + } + + /** + * Store metas to synchronize before deleting them. + * + * @since 2.3 + * + * @param int[] $mids Not used. + * @param int $id Object ID. + * @return void + */ + public function store_metas_to_sync( $mids, $id ) { + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + $this->to_copy[ $id ][ $tr_id ] = $this->get_metas_to_copy( $id, $tr_id, $lang, true ); + } + } + + /** + * Synchronizes deleted meta across translations. + * + * @since 2.3 + * + * @param int[] $mids Not used. + * @param int $id Object ID. + * @param string $key Meta key. + * @param mixed $value Meta value. + * @return void + */ + public function delete_meta( $mids, $id, $key, $value ) { + static $avoid_recursion = false; + + if ( ! $avoid_recursion ) { + $avoid_recursion = true; + + $tr_ids = $this->model->{$this->meta_type}->get_translations( $id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id != $id ) { + if ( in_array( $key, $this->to_copy[ $id ][ $tr_id ] ) ) { + if ( '' !== $value && null !== $value && false !== $value ) { // Same test as WP + $value = $this->maybe_translate_value( $value, $key, $id, $tr_id, $lang ); + } + delete_metadata( $this->meta_type, $tr_id, wp_slash( $key ), is_object( $value ) ? $value : wp_slash( $value ) ); + } + } + } + } + + $avoid_recursion = false; + } + + /** + * Copy or synchronize metas + * + * @since 2.3 + * + * @param int $from Id of the source object + * @param int $to Id of the target object + * @param string $lang Language code of the target object + * @param bool $sync Optional, defaults to true. True if it is synchronization, false if it is a copy + * @return void + */ + public function copy( $from, $to, $lang, $sync = false ) { + $this->remove_all_meta_actions(); + + $to_copy = $this->get_metas_to_copy( $from, $to, $lang, $sync ); + $metas = get_metadata( $this->meta_type, $from ); + $metas = is_array( $metas ) ? $metas : array(); + $tr_metas = get_metadata( $this->meta_type, $to ); + $tr_metas = is_array( $tr_metas ) ? $tr_metas : array(); + + foreach ( $to_copy as $key ) { + if ( empty( $metas[ $key ] ) ) { + if ( ! empty( $tr_metas[ $key ] ) ) { + // If the meta key is not present in the source object, delete all values + delete_metadata( $this->meta_type, $to, wp_slash( $key ) ); + } + } elseif ( ! empty( $tr_metas[ $key ] ) && 1 === count( $metas[ $key ] ) && 1 === count( $tr_metas[ $key ] ) ) { + // One custom field to update + $value = reset( $metas[ $key ] ); + $value = maybe_unserialize( $value ); + $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang ); + update_metadata( $this->meta_type, $to, wp_slash( $key ), is_object( $to_value ) ? $to_value : wp_slash( $to_value ) ); + } else { + // Multiple custom fields, either in the source or the target + if ( ! empty( $tr_metas[ $key ] ) ) { + // The synchronization of multiple values custom fields is easier if we delete all metas first + delete_metadata( $this->meta_type, $to, wp_slash( $key ) ); + } + + foreach ( $metas[ $key ] as $value ) { + $value = maybe_unserialize( $value ); + $to_value = $this->maybe_translate_value( $value, $key, $from, $to, $lang ); + add_metadata( $this->meta_type, $to, wp_slash( $key ), is_object( $to_value ) ? $to_value : wp_slash( $to_value ) ); + } + } + } + + $this->add_all_meta_actions(); + } + + /** + * If synchronized custom fields were previously not synchronized, it is expected + * that saving a post (or term) will synchronize them. + * + * @since 2.3 + * + * @param int $object_id Id of the object being saved. + * @param object $obj Not used. + * @param int[] $translations The list of translations object ids. + * @return void + */ + public function save_object( $object_id, $obj, $translations ) { + foreach ( $translations as $tr_lang => $tr_id ) { + if ( $tr_id != $object_id ) { + $this->copy( $object_id, $tr_id, $tr_lang, true ); + } + } + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-post-metas.php b/wp-content/plugins/polylang/modules/sync/sync-post-metas.php new file mode 100644 index 0000000000..47c66cc97e --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-post-metas.php @@ -0,0 +1,94 @@ +meta_type = 'post'; + + parent::__construct( $polylang ); + + $this->options = &$polylang->options; + + add_filter( 'pll_translate_post_meta', array( $this, 'translate_thumbnail_id' ), 10, 3 ); + } + + /** + * Get the custom fields to copy or synchronize. + * + * @since 2.3 + * + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @return string[] List of meta keys. + */ + protected function get_metas_to_copy( $from, $to, $lang, $sync = false ) { + $keys = array(); + + // Get public meta keys ( including from translated post in case we just deleted a custom field ). + if ( ! $sync || in_array( 'post_meta', $this->options['sync'] ) ) { + $from_keys = (array) get_post_custom_keys( $from ); + $to_keys = (array) get_post_custom_keys( $to ); + + $keys = array_unique( array_merge( $from_keys, $to_keys ) ); + foreach ( $keys as $k => $meta_key ) { + if ( is_protected_meta( $meta_key ) ) { + unset( $keys[ $k ] ); + } + } + } + + // Add page template and featured image. + foreach ( array( '_wp_page_template', '_thumbnail_id' ) as $meta ) { + if ( ! $sync || in_array( $meta, $this->options['sync'] ) ) { + $keys[] = $meta; + } + } + + if ( $this->options['media_support'] ) { + $keys[] = '_wp_attached_file'; + $keys[] = '_wp_attachment_metadata'; + $keys[] = '_wp_attachment_backup_sizes'; + $keys[] = '_wp_attachment_is_custom_header'; // Random header image. + } + + /** This filter is documented in modules/sync/sync-metas.php */ + return array_unique( apply_filters( 'pll_copy_post_metas', $keys, $sync, $from, $to, $lang ) ); + } + + /** + * Translates the thumbnail id. + * + * @since 2.3 + * + * @param int $value Thumbnail id. + * @param string $key Meta key. + * @param string $lang Language code. + * @return int + */ + public function translate_thumbnail_id( $value, $key, $lang ) { + return ( $this->options['media_support'] && '_thumbnail_id' === $key && $to_value = $this->model->post->get_translation( $value, $lang ) ) ? $to_value : $value; + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-tax.php b/wp-content/plugins/polylang/modules/sync/sync-tax.php new file mode 100644 index 0000000000..4d969eb6a7 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-tax.php @@ -0,0 +1,310 @@ +model = &$polylang->model; + $this->options = &$polylang->options; + + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + add_action( 'pll_save_term', array( $this, 'create_term' ), 10, 3 ); + add_action( 'pre_delete_term', array( $this, 'pre_delete_term' ) ); + add_action( 'delete_term', array( $this, 'delete_term' ) ); + } + + /** + * Get the list of taxonomies to copy or to synchronize. + * + * @since 1.7 + * @since 2.1 The `$from`, `$to`, `$lang` parameters were added. + * @since 3.2 Changed visibility from protected to public. + * + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information, optional, defaults to null. + * @param int $to Id of the post to which we paste information, optional, defaults to null. + * @param string $lang Language slug, optional, defaults to null. + * @return string[] List of taxonomy names. + */ + public function get_taxonomies_to_copy( $sync, $from = null, $to = null, $lang = null ) { + $taxonomies = ! $sync || in_array( 'taxonomies', $this->options['sync'] ) ? $this->model->get_translated_taxonomies() : array(); + if ( ! $sync || in_array( 'post_format', $this->options['sync'] ) ) { + $taxonomies[] = 'post_format'; + } + + /** + * Filters the taxonomies to copy or synchronize. + * + * @since 1.7 + * @since 2.1 The `$from`, `$to`, `$lang` parameters were added. + * + * @param string[] $taxonomies List of taxonomy names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int $from Id of the post from which we copy information. + * @param int $to Id of the post to which we paste information. + * @param string $lang Language slug. + */ + return array_unique( apply_filters( 'pll_copy_taxonomies', $taxonomies, $sync, $from, $to, $lang ) ); + } + + /** + * When copying or synchronizing terms, translate terms in translatable taxonomies + * + * @since 2.3 + * + * @param int $object_id Object ID. + * @param int[] $terms List of terms ids assigned to the source post. + * @param string $taxonomy Taxonomy name. + * @param string $lang Language slug. + * @return int[] List of terms ids to assign to the target post. + */ + protected function maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ) { + if ( is_array( $terms ) && $this->model->is_translated_taxonomy( $taxonomy ) ) { + $newterms = array(); + + // Convert to term ids if we got tag names + $strings = array_map( 'is_string', $terms ); + if ( in_array( true, $strings, true ) ) { + $terms = get_the_terms( $object_id, $taxonomy ); + $terms = wp_list_pluck( $terms, 'term_id' ); + } + + foreach ( $terms as $term ) { + /** + * Filter the translated term when a post translation is created or synchronized + * + * @since 2.3 + * + * @param int $tr_term Translated term id + * @param int $term Source term id + * @param string $lang Language slug + */ + if ( $term_id = apply_filters( 'pll_maybe_translate_term', (int) $this->model->term->get_translation( $term, $lang ), $term, $lang ) ) { + $newterms[] = (int) $term_id; // Cast is important otherwise we get 'numeric' tags + } + } + + return $newterms; + } + + return $terms; // Empty $terms or untranslated taxonomy + } + + /** + * Maybe copy taxonomy terms from one post to the other. + * + * @since 2.6 + * + * @param int $object_id Source object ID. + * @param int $tr_id Target object ID. + * @param string $lang Target language. + * @param array $terms An array of object terms. + * @param string $taxonomy Taxonomy slug. + * @param bool $append Whether to append new terms to the old terms. + * @return void + */ + protected function copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ) { + $to_copy = $this->get_taxonomies_to_copy( true, $object_id, $tr_id, $lang ); + + if ( in_array( $taxonomy, $to_copy ) ) { + $newterms = $this->maybe_translate_terms( $object_id, $terms, $taxonomy, $lang ); + + // For some reasons, the user may have untranslated terms in the translation. Don't forget them. + if ( $this->model->is_translated_taxonomy( $taxonomy ) ) { + $tr_terms = get_the_terms( $tr_id, $taxonomy ); + if ( is_array( $tr_terms ) ) { + foreach ( $tr_terms as $term ) { + if ( ! $this->model->term->get_translation( $term->term_id, $this->model->post->get_language( $object_id ) ) ) { + $newterms[] = (int) $term->term_id; + } + } + } + } + + wp_set_object_terms( $tr_id, $newterms, $taxonomy, $append ); + } + } + + /** + * When assigning terms to a post, assign translated terms to the translated posts (synchronisation). + * + * @since 2.3 + * + * @param int $object_id Object ID. + * @param array $terms An array of object terms. + * @param int[] $tt_ids An array of term taxonomy IDs. + * @param string $taxonomy Taxonomy slug. + * @param bool $append Whether to append new terms to the old terms. + * @return void + */ + public function set_object_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) { + static $avoid_recursion = false; + $taxonomy_object = get_taxonomy( $taxonomy ); + + // Make sure that the taxonomy is registered for a post type + if ( ! $avoid_recursion && ! empty( $taxonomy_object ) && array_filter( $taxonomy_object->object_type, 'post_type_exists' ) ) { + $avoid_recursion = true; + + $tr_ids = $this->model->post->get_translations( $object_id ); + + foreach ( $tr_ids as $lang => $tr_id ) { + if ( $tr_id !== $object_id ) { + if ( $this->model->post->current_user_can_synchronize( $object_id ) ) { + $this->copy_object_terms( $object_id, $tr_id, $lang, $terms, $taxonomy, $append ); + } else { + // No permission to synchronize, so let's synchronize in reverse order + $orig_lang = array_search( $object_id, $tr_ids ); + $tr_terms = get_the_terms( $tr_id, $taxonomy ); + + if ( false === $tr_terms ) { + $tr_terms = array(); + } + + if ( is_string( $orig_lang ) && is_array( $tr_terms ) ) { + $tr_terms = wp_list_pluck( $tr_terms, 'term_id' ); + $this->copy_object_terms( $tr_id, $object_id, $orig_lang, $tr_terms, $taxonomy, $append ); + } + break; + } + } + } + + $avoid_recursion = false; + } + } + + /** + * Copy terms from one post to a translation, does not sync + * + * @since 2.3 + * + * @param int $from Id of the source post + * @param int $to Id of the target post + * @param string $lang Language slug + * @return void + */ + public function copy( $from, $to, $lang ) { + remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) ); + + // Get taxonomies to sync for this post type + $taxonomies = array_intersect( get_post_taxonomies( $from ), $this->get_taxonomies_to_copy( false, $from, $to, $lang ) ); + + // Update the term cache to reduce the number of queries in the loop + update_object_term_cache( array( $from ), get_post_type( $from ) ); + + // Copy + foreach ( $taxonomies as $tax ) { + if ( $terms = get_the_terms( $from, $tax ) ) { + $terms = array_map( 'intval', wp_list_pluck( $terms, 'term_id' ) ); + $newterms = $this->maybe_translate_terms( $from, $terms, $tax, $lang ); + + if ( ! empty( $newterms ) ) { + wp_set_object_terms( $to, $newterms, $tax ); + } + } + } + + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + } + + /** + * When creating a new term, associate it to posts having translations associated to the translated terms. + * + * @since 2.3 + * + * @param int $term_id Id of the created term. + * @param string $taxonomy Taxonomy. + * @param int[] $translations Ids of the translations of the created term. + * @return void + */ + public function create_term( $term_id, $taxonomy, $translations ) { + if ( doing_action( 'create_term' ) && in_array( $taxonomy, $this->get_taxonomies_to_copy( true ) ) ) { + // Get all posts associated to the translated terms + $tr_posts = get_posts( + array( + 'numberposts' => -1, + 'nopaging' => true, + 'post_type' => 'any', + 'post_status' => 'any', + 'fields' => 'ids', + 'tax_query' => array( + array( + 'taxonomy' => $taxonomy, + 'field' => 'id', + 'terms' => array_merge( array( $term_id ), array_values( $translations ) ), + 'include_children' => false, + ), + ), + ) + ); + + $lang = $this->model->term->get_language( $term_id ); // Language of the created term + $posts = array(); + + foreach ( $tr_posts as $post_id ) { + $post = $this->model->post->get_translation( $post_id, $lang ); + + if ( $post ) { + $posts[] = $post; + } + } + + $posts = array_unique( $posts ); + + foreach ( $posts as $post_id ) { + if ( current_user_can( 'assign_term', $term_id ) ) { + wp_set_object_terms( $post_id, $term_id, $taxonomy, true ); + } + } + } + } + + /** + * Deactivate the synchronization of terms before deleting a term + * to avoid translated terms to be removed from translated posts + * + * @since 2.3.2 + * + * @return void + */ + public function pre_delete_term() { + remove_action( 'set_object_terms', array( $this, 'set_object_terms' ) ); + } + + /** + * Re-activate the synchronization of terms after a term is deleted + * + * @since 2.3.2 + * + * @return void + */ + public function delete_term() { + add_action( 'set_object_terms', array( $this, 'set_object_terms' ), 10, 5 ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync-term-metas.php b/wp-content/plugins/polylang/modules/sync/sync-term-metas.php new file mode 100644 index 0000000000..e26088fad9 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync-term-metas.php @@ -0,0 +1,25 @@ +meta_type = 'term'; + + parent::__construct( $polylang ); + } +} diff --git a/wp-content/plugins/polylang/modules/sync/sync.php b/wp-content/plugins/polylang/modules/sync/sync.php new file mode 100644 index 0000000000..6fe7a65ff0 --- /dev/null +++ b/wp-content/plugins/polylang/modules/sync/sync.php @@ -0,0 +1,268 @@ +model = &$polylang->model; + $this->options = &$polylang->options; + + $this->taxonomies = new PLL_Sync_Tax( $polylang ); + $this->post_metas = new PLL_Sync_Post_Metas( $polylang ); + $this->term_metas = new PLL_Sync_Term_Metas( $polylang ); + + add_filter( 'wp_insert_post_parent', array( $this, 'can_sync_post_parent' ), 10, 3 ); + add_filter( 'wp_insert_post_data', array( $this, 'can_sync_post_data' ), 10, 2 ); + + add_action( 'pll_save_post', array( $this, 'pll_save_post' ), 10, 3 ); + add_action( 'created_term', array( $this, 'sync_term_parent' ), 10, 3 ); + add_action( 'edited_term', array( $this, 'sync_term_parent' ), 10, 3 ); + + add_action( 'pll_duplicate_term', array( $this->term_metas, 'copy' ), 10, 3 ); + + if ( $this->options['media_support'] ) { + add_action( 'pll_translate_media', array( $this->taxonomies, 'copy' ), 10, 3 ); + add_action( 'pll_translate_media', array( $this->post_metas, 'copy' ), 10, 3 ); + add_action( 'edit_attachment', array( $this, 'edit_attachment' ) ); + } + + add_filter( 'pre_update_option_sticky_posts', array( $this, 'sync_sticky_posts' ), 10, 2 ); + } + + /** + * Get post fields to synchronize. + * + * @since 2.4 + * + * @param WP_Post $post Post object. + * @return array + */ + protected function get_fields_to_sync( $post ) { + $postarr = array(); + + foreach ( array( 'comment_status', 'ping_status', 'menu_order' ) as $property ) { + if ( in_array( $property, $this->options['sync'] ) ) { + $postarr[ $property ] = $post->$property; + } + } + + if ( in_array( 'post_date', $this->options['sync'] ) ) { + $postarr['post_date'] = $post->post_date; + $postarr['post_date_gmt'] = $post->post_date_gmt; + } + + if ( in_array( 'post_parent', $this->options['sync'] ) ) { + $postarr['post_parent'] = wp_get_post_parent_id( $post->ID ); + } + + return $postarr; + } + + /** + * Prevents synchronized post parent modification if the current user hasn't enough rights + * + * @since 2.6 + * + * @param int $post_parent Post parent ID + * @param int $post_id Post ID, unused + * @param array $postarr Array of parsed post data + * @return int + */ + public function can_sync_post_parent( $post_parent, $post_id, $postarr ) { + if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) { + $tr_ids = $this->model->post->get_translations( $postarr['ID'] ); + foreach ( $tr_ids as $tr_id ) { + if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) { + $post_parent = $post->post_parent; + break; + } + } + } + return $post_parent; + } + + /** + * Prevents synchronized post data modification if the current user hasn't enough rights + * + * @since 2.6 + * + * @param array $data An array of slashed post data. + * @param array $postarr An array of sanitized, but otherwise unmodified post data. + * @return array + */ + public function can_sync_post_data( $data, $postarr ) { + if ( ! empty( $postarr['ID'] ) && ! $this->model->post->current_user_can_synchronize( $postarr['ID'] ) ) { + foreach ( $this->model->post->get_translations( $postarr['ID'] ) as $tr_id ) { + if ( $tr_id !== $postarr['ID'] && $post = get_post( $tr_id ) ) { + $to_sync = $this->get_fields_to_sync( $post ); + $data = array_merge( $data, $to_sync ); + break; + } + } + } + return $data; + } + + /** + * Synchronizes post fields in translations. + * + * @since 2.4 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param int[] $translations Post translations. + * @return void + */ + public function pll_save_post( $post_id, $post, $translations ) { + global $wpdb; + + if ( $this->model->post->current_user_can_synchronize( $post_id ) ) { + $postarr = $this->get_fields_to_sync( $post ); + + if ( ! empty( $postarr ) ) { + foreach ( $translations as $lang => $tr_id ) { + if ( ! $tr_id || $tr_id === $post_id ) { + continue; + } + + $tr_arr = $postarr; + unset( $tr_arr['post_parent'] ); + + // Do not update the translation parent if the user set a parent with no translation. + if ( isset( $postarr['post_parent'] ) ) { + $post_parent = $postarr['post_parent'] ? $this->model->post->get_translation( $postarr['post_parent'], $lang ) : 0; + if ( ! ( $postarr['post_parent'] && ! $post_parent ) ) { + $tr_arr['post_parent'] = $post_parent; + } + } + + // Update all the rows at once. + if ( ! empty( $tr_arr ) ) { + // Don't use wp_update_post to avoid infinite loop. + $wpdb->update( $wpdb->posts, $tr_arr, array( 'ID' => $tr_id ) ); + clean_post_cache( $tr_id ); + } + } + } + } + } + + /** + * Synchronize term parent in translations + * Calling clean_term_cache *after* this is mandatory otherwise the $taxonomy_children option is not correctly updated + * + * @since 2.3 + * + * @param int $term_id Term id. + * @param int $tt_id Term taxonomy id, not used. + * @param string $taxonomy Taxonomy name. + * @return void + */ + public function sync_term_parent( $term_id, $tt_id, $taxonomy ) { + global $wpdb; + + if ( is_taxonomy_hierarchical( $taxonomy ) && $this->model->is_translated_taxonomy( $taxonomy ) ) { + $term = get_term( $term_id ); + + if ( $term instanceof WP_Term ) { + $translations = $this->model->term->get_translations( $term_id ); + + foreach ( $translations as $lang => $tr_id ) { + if ( $tr_id !== $term_id ) { + $tr_parent = $this->model->term->get_translation( $term->parent, $lang ); + $tr_term = get_term( (int) $tr_id, $taxonomy ); + + if ( $tr_term instanceof WP_Term && ! ( $term->parent && empty( $tr_parent ) ) ) { + $wpdb->update( + $wpdb->term_taxonomy, + array( 'parent' => $tr_parent ? $tr_parent : 0 ), + array( 'term_taxonomy_id' => $tr_term->term_taxonomy_id ) + ); + + clean_term_cache( $tr_id, $taxonomy ); // OK since WP 3.9. + } + } + } + } + } + } + + /** + * Synchronizes terms and metas in translations for media + * + * @since 1.8 + * + * @param int $post_id post id + * @return void + */ + public function edit_attachment( $post_id ) { + $this->pll_save_post( $post_id, get_post( $post_id ), $this->model->post->get_translations( $post_id ) ); + } + + /** + * Synchronize sticky posts. + * + * @since 2.3 + * + * @param int[] $value New option value. + * @param int[] $old_value Old option value. + * @return int[] + */ + public function sync_sticky_posts( $value, $old_value ) { + if ( in_array( 'sticky_posts', $this->options['sync'] ) ) { + // Stick post + if ( $sticked = array_diff( $value, $old_value ) ) { + $translations = $this->model->post->get_translations( reset( $sticked ) ); + $value = array_unique( array_merge( $value, array_values( $translations ) ) ); + } + + // Unstick post + if ( $unsticked = array_diff( $old_value, $value ) ) { + $translations = $this->model->post->get_translations( reset( $unsticked ) ); + $value = array_unique( array_diff( $value, array_values( $translations ) ) ); + } + } + + return $value; + } +} diff --git a/wp-content/plugins/polylang/modules/translate-slugs/load.php b/wp-content/plugins/polylang/modules/translate-slugs/load.php new file mode 100644 index 0000000000..0a14423365 --- /dev/null +++ b/wp-content/plugins/polylang/modules/translate-slugs/load.php @@ -0,0 +1,20 @@ +model->has_languages() ) { + add_filter( + 'pll_settings_modules', + function ( $modules ) { + $modules[] = 'PLL_Settings_Preview_Translate_Slugs'; + return $modules; + } + ); +} diff --git a/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php b/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php new file mode 100644 index 0000000000..a17d7415a9 --- /dev/null +++ b/wp-content/plugins/polylang/modules/translate-slugs/settings-preview-translate-slugs.php @@ -0,0 +1,56 @@ + 'translate-slugs', + 'title' => __( 'Translate slugs', 'polylang' ), + 'description' => $this->get_description(), + 'active_option' => 'preview', + ); + + parent::__construct( $polylang, array_merge( $default, $args ) ); + } + + /** + * Returns the module description. + * + * @since 3.1 + * + * @return string + */ + protected function get_description() { + return __( 'Allows to translate custom post types and taxonomies slugs in URLs.', 'polylang' ); + } +} diff --git a/wp-content/plugins/polylang/modules/wizard/css/wizard.css b/wp-content/plugins/polylang/modules/wizard/css/wizard.css new file mode 100644 index 0000000000..b6c6a62a9c --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/css/wizard.css @@ -0,0 +1,951 @@ +@charset "UTF-8"; +body { + margin: 65px auto 24px; + box-shadow: none; + background: #f1f1f1; + padding: 0; + border: 0; /* fix-pro #856 override WP install.css */ +} + +#pll-logo { + border: 0; + margin: 0 0 24px; + padding: 0; + text-align: center; + font-family: sans-serif; + font-size: 64px; + text-transform: uppercase; + color: #000; + line-height: normal; +} +#pll-logo a { + display: flex; + justify-content: center; + color: #000; + text-decoration: none; +} + +#pll-logo img { + max-width: 100%; + margin-right: 16px; +} +.rtl #pll-logo img { + margin-right: 0; + margin-left: 16px; +} + +.pll-wizard-footer { + text-align: center +} + +.pll-wizard .select2-container { + text-align: left; + width: auto +} + +.pll-wizard .hidden { + display: none +} + +.pll-wizard-content { + box-shadow: 0 1px 3px rgba(0, 0, 0, .13); + padding: 2em; + margin: 0 0 20px; + background: #fff; + overflow: hidden; + zoom: 1; + text-align: left; +} +.rtl .pll-wizard-content{ + text-align: right; +} + +.pll-wizard-content h1, +.pll-wizard-content h2, +.pll-wizard-content h3, +.pll-wizard-content table { + margin: 0 0 20px; + border: 0; + padding: 0; + color: #666; + clear: none; + font-weight: 500 +} + +.pll-wizard-content p { + margin: 20px 0; + font-size: 1em; + line-height: 1.75em; + color: #666 +} + +.pll-wizard-content table { + font-size: 1em; + line-height: 1.75em; + color: #666; + width: 100%; + margin-top: 20px; +} +.pll-wizard-content table td span{ + display: inline-block; +} + +.pll-wizard-content table caption { + caption-side: bottom; + font-style: italic; + text-align: right; +} +.rtl .pll-wizard-content table caption { + text-align: left; +} + +.pll-wizard-content table caption .icon-default-lang{ + font-style: normal; +} + +.pll-wizard-content a { + color: #a03f3f; +} + +.pll-wizard-content a:focus, +.pll-wizard-content a:hover, +.pll-wizard-footer-links:hover { + color: #dd5454 +} + +.pll-wizard-content .pll-wizard-next-steps { + overflow: hidden; + margin: 0 0 24px; + padding-bottom: 2px +} + +.pll-wizard-content .pll-wizard-next-steps h2 { + margin-bottom: 12px +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-first { + float: left; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps .pll-wizard-next-steps-last { + float: right; + width: 50%; + box-sizing: border-box +} + +.pll-wizard-content .pll-wizard-next-steps ul { + padding: 0 2em 0 0; + list-style: none outside; + margin: 0 +} + +.pll-wizard-content .pll-wizard-next-steps ul li a { + display: block; + padding: 0 0 .75em +} + +.pll-wizard-content .pll-wizard-next-steps ul li a::before { + color: #82878c; + font: normal 20px/1 dashicons; + speak: none; + display: inline-block; + padding: 0 10px 0 0; + top: 1px; + position: relative; + text-decoration: none!important; + vertical-align: top +} + +.pll-wizard-steps { + padding: 0 0 24px; + margin: 0; + list-style: none outside; + overflow: hidden; + color: #ccc; + width: 100%; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: inline-flex +} + +.pll-wizard-steps li { + width: 100%; + float: left; + padding: 0 0 .8em; + margin: 0; + text-align: center; + position: relative; + border-bottom: 4px solid #ccc; + line-height: 1.4em +} + +.pll-wizard-steps li a { + color: #a03f3f; + text-decoration: none; + padding: 1.5em; + margin: -1.5em; + position: relative; + z-index: 1 +} + +.pll-wizard-steps li a:focus, +.pll-wizard-steps li a:hover { + color: #dd5454; + text-decoration: underline +} + +.pll-wizard-steps li::before { + content: ""; + border: 4px solid #ccc; + border-radius: 100%; + width: 4px; + height: 4px; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -6px; + margin-bottom: -8px; + background: #fff +} + +.pll-wizard-steps li.active { + border-color: #a03f3f; + color: #a03f3f; + font-weight: 700 +} + +.pll-wizard-steps li.active::before { + border-color: #a03f3f +} + +.pll-wizard-steps li.done { + border-color: #a03f3f; + color: #a03f3f +} + +.pll-wizard-steps li.done::before { + border-color: #a03f3f; + background: #a03f3f +} + +.pll-wizard .pll-wizard-actions { + overflow: hidden; + margin: 20px 0 0; + position: relative +} + +.pll-wizard .pll-wizard-actions .button { + font-size: 16px; + font-weight: 300; + padding: 1em 2em; + line-height: 1em; + margin-right: .5em; + margin-bottom: 2px; + margin-top: 10px; + height: auto; + border-radius: 4px; + box-shadow: none; + min-width: auto; + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button { + border-color: #a03f3f; + color: #a03f3f; +} + +.pll-wizard .pll-wizard-content .button-primary, +.pll-wizard .pll-wizard-actions .button-primary { + background-color: #a03f3f; + border-color: #a03f3f; + color: #fff; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #a03f3f; + text-shadow: 0 -1px 1px #a03f3f, 1px 0 1px #a03f3f, 0 1px 1px #a03f3f, -1px 0 1px #a03f3f; + margin: 0; + opacity: 1 +} + +.pll-wizard .pll-wizard-content .button-small .dashicons { + font-size: 15px; + height: auto; + vertical-align: middle; +} + +.pll-wizard .button-primary:active, +.pll-wizard .button-primary:focus, +.pll-wizard input[type="checkbox"]:focus + label.button-primary, +.pll-wizard .button-primary:hover { + background: #dd5454; + border-color: #dd5454; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #dd5454 +} +.pll-wizard .pll-wizard-actions .button-primary[disabled], +.pll-wizard .pll-wizard-actions .button-primary:disabled, +.pll-wizard .pll-wizard-actions .button-primary.disabled { + cursor: wait; + background-color: #bb5454 !important; + border-color: #bb5454 !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 0 #bb5454 !important; + text-shadow: 0 -1px 1px #bb5454, 1px 0 1px #bb5454, 0 1px 1px #bb5454, -1px 0 1px #bb5454 !important; + color: #ffa3a3 !important; +} +.pll-wizard-content p:last-child { + margin-bottom: 0 +} + +.pll-wizard-footer-links { + font-size: .85em; + color: #7b7b7b; + margin: 1.18em auto; + display: inline-block; + text-align: center +} + +.pll-wizard-services { + border: 1px solid #eee; + padding: 0; + margin: 0 0 1em; + list-style: none outside; + border-radius: 4px; + overflow: hidden +} + +.pll-wizard-services p { + margin: 0 0 1em 0; + padding: 0; + font-size: 1em; + line-height: 1.5em +} + +.pll-wizard-service-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + padding: 0; + border-bottom: 1px solid #eee; + color: #666; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.media-step .pll-wizard-service-item{ + border: 0; +} + +.media-step .pll-wizard-service-item:last-child{ + display: block; +} + +.media-step .pll-wizard-service-item .pll-wizard-service-enable{ + padding-bottom: 0; +} + +.pll-wizard-service-item:last-child { + border-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-name { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 160px; + text-align: center; + font-weight: 700; + padding: 2em 0; + -webkit-align-self: stretch; + align-self: stretch; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: baseline; + -webkit-align-items: baseline; + align-items: baseline +} + +.pll-wizard-service-item .pll-wizard-service-name img { + max-width: 75px +} + +.pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + padding: 20px +} + +.pll-wizard-service-item .pll-wizard-service-example { + padding: 0 20px 20px +} + +.pll-wizard-service-item .pll-wizard-service-example p{ + text-align: right; +} +.rtl .pll-wizard-service-item .pll-wizard-service-example p{ + text-align: left; +} + +.pll-wizard-service-item .pll-wizard-service-description p { + margin-bottom: 1em +} + +.pll-wizard-service-item .pll-wizard-service-description p:last-child { + margin-bottom: 0 +} + +.pll-wizard-service-item .pll-wizard-service-description .pll-wizard-service-settings-description { + display: block; + font-style: italic; + color: #999 +} + +.pll-wizard-service-item .pll-wizard-service-enable { + -webkit-flex-basis: 0; + flex-basis: 0; + min-width: 75px; + text-align: center; + cursor: pointer; + padding: 2em 0; + position: relative; + max-height: 1.5em; + -webkit-align-self: flex-start; + align-self: flex-start; + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 +} + +.pll-wizard-service-item .pll-wizard-service-toggle { + position: relative +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] { + position:absolute; + opacity: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label { + position: relative; + display: inline-block; + width: 44px; + height: 20px; + border-radius: 10em; + cursor: pointer; + text-indent: -9999px; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:focus + label { + border:1px dashed #777; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before, +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + content: ''; + position: absolute; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::before { + left: 0; + top: 0; + width: 44px; + height: 20px; + background: #ddd; + border-radius: 10em; + transition: background-color .2s; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox] + label::after { + width: 16px; + height: 16px; + transition: all .2s; + border-radius: 50%; + background: #fff; + margin: 2px; + top: 0; + left: 0; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::before { + background:#a03f3f; +} + +.pll-wizard-service-item .pll-wizard-service-toggle input[type=checkbox]:checked + label::after { + right: 0; + left:auto; +} + +.pll-wizard-service-item .pll-wizard-service-settings { + display: none; + margin-top: .75em; + margin-bottom: 0; + cursor: default +} + +.pll-wizard-service-item .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings { + display: inline-block +} + +.pll-wizard-service-item.checked .pll-wizard-service-settings.hide { + display: none +} + +.pll-wizard-service-item.closed { + border-bottom: 0 +} + +.step { + text-align: center +} + +.pll-wizard .button .dashicons{ + vertical-align: middle; +} +.rtl .dashicons-arrow-right-alt2:before { + content: "\f341"; +} +.pll-wizard .pll-wizard-actions .button:active, +.pll-wizard .pll-wizard-actions .button:focus, +.pll-wizard .pll-wizard-actions .button:hover { + box-shadow: none +} + +.pll-wizard-next-steps { + border: 1px solid #eee; + border-radius: 4px; + list-style: none; + padding: 0 +} + +.pll-wizard-next-steps li { + padding: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-item { + display: -webkit-box; + display: -webkit-flex; + display: flex; + border-top: 1px solid #eee +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border, +.pll-wizard-next-steps .pll-wizard-next-step-item:first-child { + border-top: 0 +} + +.pll-wizard-next-steps .pll-wizard-next-step-description { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + margin: 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-action { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center +} + +.pll-wizard-next-steps .pll-wizard-next-step-action .button { + margin: 1em 1.5em +} + +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-description, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-actions, +.pll-wizard-next-steps .pll-wizard-next-step-item.no-border .pll-wizard-next-step-action .button{ + margin-top: 0; +} + + +.pll-wizard-next-steps p.next-step-heading { + margin: 0; + font-size: .95em; + font-weight: 400; + font-variant: all-petite-caps +} + +.pll-wizard-next-steps p.next-step-extra-info { + margin: 0 +} + +.pll-wizard-next-steps h3.next-step-description { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps { + border-top: 1px solid #eee; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-next-step-description { + margin-bottom: 0 +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions { + margin: 0 0 1.5em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + font-size: 15px; + margin: 1em 0 1em 1.5em; +} +.rtl .pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button { + margin: 1em 1.5em 1em 0; +} + +.pll-wizard-next-steps .pll-wizard-additional-steps .pll-wizard-actions .button::last-child { + margin-right: 1.5em; +} + +.pll-wizard-content img{ + max-width: 100%; + margin-right: 0.5em; +} +.rtl .pll-wizard-content img{ + margin-left: 0.5em; +} + +.pll-wizard-content .form-field label{ + margin-bottom: 5px; + display: block; +} + +.pll-wizard-content .form-field select{ + padding: 3px; +} + +.pll-wizard-content .languages-step select, +.pll-wizard-content .untranslated-contents-step select{ + width: 100%; +} + +.languages-step .form-field .button{ + margin-left: 15px; +} +.languages-step .form-field .button > span{ + margin-right: 0.3em; +} +.rtl .languages-step .form-field .button{ + margin-left: 0; + margin-right: 15px; +} +.rtl .languages-step .form-field .button > span{ + margin-left: 0.3em; + margin-right: 0; +} + +.pll-wizard-content .languages-step .select-language-field{ + display: flex; +} + +.pll-wizard-content #languages{ + display: none; +} +.pll-wizard-content #languages tr th:first-child{ + width: 80%; +} +.pll-wizard-content #languages .dashicons{ + color: #a03f3f; +} +.pll-wizard-content #languages img{ + margin-right: 5px; +} +.pll-wizard-content .error{ + color: #a03f3f; + font-weight: bold; +} +.pll-wizard-content #messages .error{ + background: #fccfcf; + padding: 0.5rem; + border: 1px solid #a03f3f; + margin-bottom: 0.5rem; +} + +.pll-wizard-content #slide-toggle{ + position:absolute; + opacity: 0; +} + +.pll-wizard-content #slide-toggle + label{ + position:relative; +} +.pll-wizard-content #slide-toggle + label + span{ + display: block; +} + +.pll-wizard-content #slide-toggle + label .dashicons{ + margin-right: 0.3em; +} +.rtl .pll-wizard-content #slide-toggle + label .dashicons{ + margin-left: 0.3em; + margin-right: 0; +} +.pll-wizard-content #slide-toggle ~ #screenshot > img { + max-height: 500px; + margin-top: 10px; + -webkit-transition: all .5s cubic-bezier(0, 1, 0.5, 1); + transition: all .5s cubic-bezier(0, 1, 0.5, 1); +} +.pll-wizard-content #slide-toggle:checked ~ #screenshot > img { + max-height: 0; +} +.hide { + display: none; +} + +input[type="text"].field-in-error, +input[type="password"].field-in-error, +input[type="checkbox"].field-in-error, +input[type="color"].field-in-error, +input[type="date"].field-in-error, +input[type="datetime"].field-in-error, +input[type="datetime-local"].field-in-error, +input[type="email"].field-in-error, +input[type="month"].field-in-error, +input[type="number"].field-in-error, +input[type="search"].field-in-error, +input[type="radio"].field-in-error, +input[type="tel"].field-in-error, +input[type="text"].field-in-error, +input[type="time"].field-in-error, +input[type="url"].field-in-error, +input[type="week"].field-in-error, +select.field-in-error, +textarea.field-in-error, +span.field-in-error, +.field-in-error{ + border-color: #a03f3f; +} + +input[type="text"].field-in-error:focus, +input[type="password"].field-in-error:focus, +input[type="checkbox"].field-in-error:focus, +input[type="color"].field-in-error:focus, +input[type="date"].field-in-error:focus, +input[type="datetime"].field-in-error:focus, +input[type="datetime-local"].field-in-error:focus, +input[type="email"].field-in-error:focus, +input[type="month"].field-in-error:focus, +input[type="number"].field-in-error:focus, +input[type="search"].field-in-error:focus, +input[type="radio"].field-in-error:focus, +input[type="tel"].field-in-error:focus, +input[type="text"].field-in-error:focus, +input[type="time"].field-in-error:focus, +input[type="url"].field-in-error:focus, +input[type="week"].field-in-error:focus, +select.field-in-error:focus, +textarea.field-in-error:focus, +span.field-in-error:focus, +.field-in-error:focus{ + border: 1px solid #a03f3f; + box-shadow: 0 0 2px rgba(160, 63, 63, 0.8); + outline-color: #a03f3f; + outline-style: auto; + outline-width: thin; +} + +/* override install styles by returning back to forms styles */ +.form-table input.regular-text{ + width: 25em; +} +.form-table input.field-in-error{ + border-color: #a03f3f; +} +#pll-licenses-table td{ + padding: 10px 9px; +} +#pll-licenses-table .license-valid td p{ + min-width: 35em; +} +#pll-licenses-table .pll-deactivate-license{ + margin: 0 0 0 20px; +} +.rtl #pll-licenses-table .pll-deactivate-license{ + margin: 0 10px 0 0; +} +.pll-wizard-content .documentation { + padding: 24px 24px 0; + margin: 0 0 24px; + overflow: hidden; + background: #f5f5f5 +} + +.pll-wizard-content .documentation p { + padding: 0; + margin: 0 0 12px; +} +.documentation-container { + display: -webkit-box; + display: -webkit-flex; + display: flex; + justify-content: flex-end; +} + +.documentation-container .documentation-button-container { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + flex-grow: 0; +} + +.wc-setup .wc-setup-actions .button.documentation-button { + height: 42px; + padding: 0 1em; + margin: 0; +} +#dialog{ + display: none; +} +.pll-wizard .ui-dialog.ui-widget-content{ + max-height: none; +} +.pll-wizard .ui-dialog-title::before{ + content: "\f534"; + font-family: dashicons; + display: inline-block; + line-height: 1; + font-weight: 400; + font-style: normal; + speak: none; + text-decoration: inherit; + text-transform: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + width: 20px; + height: 20px; + font-size: 20px; + vertical-align: middle; + text-align: center; + margin: 0 5px 5px 0; + transition: color 0.1s ease-in; +} +.rtl.pll-wizard .ui-dialog-title::before{ + margin-right: 0; + margin-left: 5px; +} +.pll-wizard .ui-dialog ul{ + list-style: disc; + padding-left: 20px; +} +.rtl.pll-wizard .ui-dialog ul{ + padding-left: 0; + padding-right: 20px; +} +.pll-wizard li{ + margin-bottom: 0; +} +#translations{ + border-collapse: collapse; +} +#translations tbody:nth-child(odd){ + background-color: #f9f9f9; +} +#translations.striped > tbody > :nth-child(odd) { + background-color: transparent; /* Override common WordPress style */ +} +.pll-wizard-content mark{ + background: transparent none; +} +.pll-wizard-content mark{ + color: #7ad03a; +} +@media screen and (max-width: 782px) { + /* Override WordPress button css rules */ + .languages-step .form-field .button{ + font-size: 13px; + line-height: 26px; + height: 28px; + padding: 0 10px 1px; + vertical-align: top; + } + + #pll-licenses-table .pll-deactivate-license{ + margin: 10px 0 5px; + } +} +@media only screen and (max-width:620px) { + /* Override dialog width rule */ + .ui-dialog{ + width: 100% !important; + } + +} +@media only screen and (max-width:500px) { + #pll-logo a, + .select-language-field{ + flex-direction: column; + } + .select-language-field .action-buttons{ + display: flex; + justify-content: flex-end; + } + .languages-step .form-field .button{ + margin: 5px 0 0; + } +} +@media only screen and (max-width:400px) { + #pll-logo { + font-size: 56px; + } + .pll-wizard-steps { + display: none + } + .pll-wizard-service-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-service-item .pll-wizard-service-enable { + -webkit-box-ordinal-group: 3; + -webkit-order: 2; + order: 2; + padding: 20px 0 0 + } + .pll-wizard-service-item .pll-wizard-service-description { + -webkit-box-ordinal-group: 4; + -webkit-order: 3; + order: 3 + } + .pll-wizard-service-item .pll-wizard-service-name { + padding: 20px 20px 0; + text-align: left; + -webkit-box-pack: justify!important; + -webkit-justify-content: space-between!important; + justify-content: space-between!important + } + .pll-wizard-service-item .pll-wizard-service-name img { + margin: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item { + -webkit-flex-wrap: wrap; + flex-wrap: wrap + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-description { + margin-bottom: 0 + } + .pll-wizard-next-steps .pll-wizard-next-step-item .pll-wizard-next-step-action p { + margin: 0 + } +} +@media only screen and (max-width:360px) { + #pll-logo { + font-size: 48px; + } +} diff --git a/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php b/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php new file mode 100644 index 0000000000..d2ef5a948b --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/html-wizard-notice.php @@ -0,0 +1,49 @@ + 'mlang_wizard', + ), + admin_url( 'admin.php' ) +); +?> +

    + + + + +

    +

    + + + + +

    diff --git a/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png b/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png new file mode 100644 index 0000000000..293e18f4f9 Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/media-screen-rtl.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/images/media-screen.png b/wp-content/plugins/polylang/modules/wizard/images/media-screen.png new file mode 100644 index 0000000000..a90bf5c565 Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/media-screen.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png b/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png new file mode 100644 index 0000000000..55f67c2e43 Binary files /dev/null and b/wp-content/plugins/polylang/modules/wizard/images/polylang-logo.png differ diff --git a/wp-content/plugins/polylang/modules/wizard/js/languages-step.js b/wp-content/plugins/polylang/modules/wizard/js/languages-step.js new file mode 100644 index 0000000000..0ad845ebf5 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/js/languages-step.js @@ -0,0 +1,302 @@ +/** + * @package Polylang + */ + +jQuery( + function ( $ ) { + var addLanguageForm = $( '.languages-step' ); // Form element. + var languageFields = $( '#language-fields' ); // Element where to append hidden fields for creating language. + var languagesTable = $( '#languages' ); // Table element contains languages list to create. + var languagesListTable = $( '#languages tbody' ); // Table rows with languages list to create. + var definedLanguagesListTable = $( '#defined-languages tbody' ); // Table rows with already defined languages list. + var languagesList = $( '#lang_list' ); // Select form element with predefined languages without already created languages. + var nextStepButton = $( '[name="save_step"]' ); // The button for continuing to the next step. + var messagesContainer = $( '#messages' ); // Element where to display error messages. + var languagesMap = new Map(); // Languages map object for managing the languages to create. + var dialog = $( '#dialog' ); // Dialog box for alerting the language selected has not been added to the list. + + /** + * Add a language in the list to create it in Polylang settings + * + * @param {object} language The language object + */ + function addLanguage( language ) { + // language properties come from the select dropdown which is built server side and well escaped. + // see template view-wizard-step-languages.php. + var languageValueHtml = $( '' ).text( language.text ).prepend( language.flagUrl ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageTrashIconHtml = $( '' ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'dashicons dashicons-trash' ) + .attr( 'data-language', language.locale ) + .append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ) + .addClass( 'screen-reader-text' ) + .text( pll_wizard_params.i18n_remove_language_icon ) + ) + ); + // see the comment and the hardcoded code above. languageTrashIconHtml and languageValueHtml are safe. + var languageLineHtml = $( '' ).prepend( languageTrashIconHtml ).prepend( languageValueHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + var languageFieldHtml = $( '' ).attr( + { + type: 'hidden', + name: 'languages[]' + } + ).val( language.locale ); + + languagesList.val( '' ); + languagesList.selectmenu( 'refresh' ); // Refresh jQuery selectmenu widget after changing the value. + + languagesMap.set( language.locale, language ); + + // see above how languageLineHtml is built. + languagesListTable.append( languageLineHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + // Bind click event on trash icon. + languagesListTable.on( + 'click', + 'span[data-language=' + language.locale + ']', + function ( event ) { + event.preventDefault(); + // Remove line in languages table. + $( this ).parents( 'tr' ).remove(); + // Remove input field. + var languageField = languageFields.children( 'input[value=' + $( this ).data( 'language' ) + ']' ).remove(); + // If there is no more languages hide languages table. + if ( languagesListTable.children().length <= 0 ) { + languagesTable.hide(); + } + // Remove language from the Map. + languagesMap.delete( $( this ).data( 'language' ) ); + // Hide error message. + hideError(); + } + ); + // see above how languageFieldHtml is built. + // Add hidden input field for posting the form. + languageFields.append( languageFieldHtml ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + + } + + /** + * Display an error message + * + * @param {string} message The message to display + */ + function showError( message ) { + messagesContainer.empty(); + // html is hardcoded and use of jQuery text method which is safe to add message value. + // In addition message is i18n value which is initialized server side in PLL_Wizard::add_step_languages and correctly escaped. + messagesContainer.prepend( $( '

    ' ).addClass( 'error' ).text( message ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + } + + /** + * Hide all error messages and fields in error + */ + function hideError() { + messagesContainer.empty(); + addLanguageForm.find( '.error' ).removeClass( 'error field-in-error' ); + } + + /** + * Style the field to indicate where the error is + * + * @param {object} field The jQuery element which is in error + */ + function showFieldInError( field ) { + field.addClass( 'error field-in-error' ); + } + + /** + * Focus on a specific element + * + * @param {object} field The jQuery element which will be focused + */ + function focusOnField( field ) { + field.trigger( 'focus' ); + } + + /** + * Disable a specific button + * + * @param {object} button + */ + function disableButton( button ){ + button.prop( 'disabled', true ); + // Because the button is disabled we need to add the value of the button to ensure it will pass in the request. + addLanguageForm.append( // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.append + $( '' ).prop( + { + type: 'hidden', + name: button.prop( 'name' ), + value: button.prop( 'value' ) + } + ) + ); + } + + /** + * Remove error when a new selection is done in languages list. + */ + languagesList.on( + 'selectmenuchange', + function () { + hideError();; + } + ); + /** + * Bind click event on "Add language" button + */ + $( '#add-language' ).on( + 'click', + function ( event ) { + hideError(); + var selectedOption = event.currentTarget.form.lang_list.options[event.currentTarget.form.lang_list.selectedIndex]; + if ( '' !== selectedOption.value && ! languagesMap.has( selectedOption.value ) ) { + addLanguage( + { + locale: selectedOption.value, + text: selectedOption.innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + // Show table of languages. + languagesTable.show(); + // Put back the focus on the select language field after clicking on "Add language button". + focusOnField( $( '#lang_list-button' ) ); + } else { + var message = pll_wizard_params.i18n_no_language_selected; + if ( languagesMap.has( selectedOption.value ) ) { + message = pll_wizard_params.i18n_language_already_added; + } + showError( message ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + + } + } + ); + + /** + * Bind submit event on "add_lang" form + */ + addLanguageForm.on( + 'submit', + function ( event ) { + // Verify if there is at least one language. + var isLanguagesAlreadyDefined = definedLanguagesListTable.children().length > 0; + var selectedLanguage = $( '#lang_list' ).val(); + if ( languagesMap.size <= 0 && ! isLanguagesAlreadyDefined ) { + if ( '' === selectedLanguage ) { + showError( pll_wizard_params.i18n_no_language_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } else { + showError( pll_wizard_params.i18n_add_language_needed ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#add-language' ) ); // Put the focus on the "Add language" button. + } + return false; + } + // Verify if the language has been added in the list otherwise display a dialog box to confirm what to do. + if ( '' !== selectedLanguage ) { + // Verify we don't add a duplicate language before opening the dialog box otherwise display an error message. + if ( ! languagesMap.has( selectedLanguage ) ) { + dialog.dialog( 'open' ); + } else { + showError( pll_wizard_params.i18n_language_already_added ); + showFieldInError( languagesList.next( 'span.ui-selectmenu-button' ) ); + focusOnField( $( '#lang_list-button' ) ); + } + return false; + } + disableButton( nextStepButton ); + } + ); + + // Is there an error return by PHP ? + var searchParams = new URLSearchParams( document.location.search ); + if ( searchParams.has( 'activate_error' ) ) { + // If the error code exists, display it. + if ( undefined !== pll_wizard_params[ searchParams.get( 'activate_error' ) ] ) { + showError( pll_wizard_params[ searchParams.get( 'activate_error' ) ] ); + } + } + + function confirmDialog( what ) { + switch ( what ) { + case 'yes': + var selectedOption = $( '#lang_list' ).children( ':selected' ); + addLanguage( + { + locale: selectedOption[0].value, + text: selectedOption[0].innerText, + name: $( selectedOption ).data( 'language-name' ), + flagUrl: $( selectedOption ).data( 'flag-html' ) + } + ); + break; + case 'no': + // Empty select form field and submit again the form. + languagesList.val( '' ); + break; + case 'ignore': + } + dialog.dialog( 'close' ); + if ( 'ignore' === what ) { + focusOnField( $( '#lang_list-button' ) ); + } else { + addLanguageForm.submit(); + } + } + + // Initialize dialog box in the case a language is selected but not added in the list. + dialog.dialog( + { + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + title: pll_wizard_params.i18n_dialog_title, + minWidth: 600, + maxWidth: '100%', + open: function ( event, ui ) { + // Change dialog box position for rtl language + if ( $( 'body' ).hasClass( 'rtl' ) ) { + $( this ).parent().css( + { + right: $( this ).parent().css( 'left' ), + left: 'auto' + } + ); + } + // Display language name and flag information in dialog box. + $( this ).find( '#dialog-language' ).text( $( '#lang_list' ).children( ':selected' ).first().text() ); + // language properties come from the select dropdown #lang_list which is built server side and well escaped. + // see template view-wizard-step-languages.php. + $( this ).find( '#dialog-language-flag' ).empty().prepend( $( '#lang_list' ).children( ':selected' ).data( 'flag-html' ) ); // phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.prepend + }, + buttons: [ + { + text: pll_wizard_params.i18n_dialog_yes_button, + click: function ( event ) { + confirmDialog( 'yes' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_no_button, + click: function ( event ) { + confirmDialog( 'no' ); + } + }, + { + text: pll_wizard_params.i18n_dialog_ignore_button, + click: function ( event ) { + confirmDialog( 'ignore' ); + } + } + ] + } + ) + } +); diff --git a/wp-content/plugins/polylang/modules/wizard/load.php b/wp-content/plugins/polylang/modules/wizard/load.php new file mode 100644 index 0000000000..0febfd3403 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/load.php @@ -0,0 +1,14 @@ +wizard = new PLL_Wizard( $polylang ); +} diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php new file mode 100644 index 0000000000..30a7eec6a4 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-page.php @@ -0,0 +1,108 @@ + + +> + + + + + <?php + printf( + /* translators: %s is the plugin name */ + esc_html__( '%s › Setup', 'polylang' ), + esc_html( POLYLANG ) + ); + ?> + + + + steps[ $this->step ]['scripts'] ); ?> + styles, $this->steps[ $this->step ]['styles'] ) ); ?> + + + +

    + + + + +

    +
      + steps as $step_key => $step ) { + $is_completed = array_search( $this->step, array_keys( $this->steps ), true ) > array_search( $step_key, array_keys( $this->steps ), true ); + + if ( $step_key === $this->step ) { + ?> +
    1. + +
    2. + + + +
    3. + +
    4. + +
    +
    +
    step}-step" ); ?>"> + steps[ $this->step ]['view'] ) ) { + call_user_func( $this->steps[ $this->step ]['view'], $this ); + } + ?> + step ) : ?> +

    + +

    + +
    +
    + + + diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php new file mode 100644 index 0000000000..16f7ac347d --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-home-page.php @@ -0,0 +1,135 @@ +model->get_languages_list(); +$default_language = count( $languages ) > 0 ? $this->options['default_lang'] : null; +$home_page_id = get_option( 'page_on_front' ); +$translations = $this->model->post->get_translations( $home_page_id ); +$untranslated_languages = array(); +$home_page = $home_page_id > 0 ? get_post( $home_page_id ) : null; +$home_page_language = $this->model->post->get_language( $home_page_id ); + +foreach ( $languages as $language ) { + if ( ! $this->model->post->get( $home_page_id, $language ) ) { + $untranslated_languages[] = $language; + } +} +?> + + + + + +

    +

    + ' . esc_html( $home_page->post_title ) . '' + ); + ?> +
    + flag . ' ' . esc_html( $home_page_language->name ) . ' ' . esc_html( $home_page_language->locale ) . '' //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ) + ?> +
    + +

    +

    + +

    + + + + + + + + + model->get_language( $lang ); + ?> + + + + + +
    + flag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ' ' . esc_html( $language->name ) . ' ' . esc_html( $language->locale ) . ' '; + ?> + is_default ) : ?> + + + + + + + +
    + + + + + + + = 1 ) : ?> + + + + + + + + + + + + + + + + + + + +
    + + +
    + flag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo ' ' . esc_html( $lg->name ) . ' ' . esc_html( $lg->locale ) . ' '; + ?> + is_default ) : ?> + + + + + + + +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php new file mode 100644 index 0000000000..501fc182f3 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-languages.php @@ -0,0 +1,136 @@ +model->get_languages_list(); +$default_language = count( $existing_languages ) > 0 ? $this->options['default_lang'] : null; +$languages_list = array_diff_key( + PLL_Settings::get_predefined_languages(), + wp_list_pluck( $existing_languages, 'locale', 'locale' ) +); +?> +
    +

    + +

    +

    + +

    +

    +
    +
    +
    + +
    + +
    + +
    +
    +
    + + + + + + + + + +
    + + + + + + + + + + + ' . "\n", + esc_attr( $lg->locale ), + esc_html( $lg->name ), + $lg->flag, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $lg->is_default ? ' ' . esc_html__( 'Default language', 'polylang' ) . '' : '' + ); + } + ?> + +
    %3$s%2$s - %1$s %4$s
    + +
    +

    + ', + '' + ); + ?> +

    +

    + +

    +
      +
    • + ' . esc_html__( 'Yes', 'polylang' ) . '' + ); + ?> +
    • +
    • + ' . esc_html__( 'No', 'polylang' ) . '' + ); + ?> +
    • +
    • + ' . esc_html__( 'Ignore', 'polylang' ) . '' + ); + ?> +
    • +
    +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php new file mode 100644 index 0000000000..497fe4756b --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-last.php @@ -0,0 +1,114 @@ + +

    + +
    +

    +
    +

    + + + +

    +
    +
    + +
      +
    • +
      +

      +

      +

      + +

      +
      +
      +

      + + + +

      +
      +
    • +
    • +
      +

      +

      +

      +
      +
      +

      + + + +

      +
      +
    • + +
    • +
      +

      +

      +

      + +

      +
      +
      +

      + + + +

      +
      +
    • + + +
    • +
      +

      +

      +

      + ' . esc_html__( 'Polylang Business Pack', 'polylang' ) . '' + ); + ?> +

      +
      +
      +

      + + + +

      +
      +
    • + +
    • +
      +

      + + + +

      +
      +
    • +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php new file mode 100644 index 0000000000..c7db239ff7 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-licenses.php @@ -0,0 +1,38 @@ + +

    + + +

    +

    +
    + +

    + +
    +
    + + + get_form_field(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> + +
    +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php new file mode 100644 index 0000000000..cf221b46e9 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-media.php @@ -0,0 +1,62 @@ + +

    +

    + + + +

    +

    + +

    +
      +
    • +
      + + + /> + +
      +
      +

      + +

      +
      +
    • +
    • +
      +

      + + + + + +

      +
      +
    • +
    diff --git a/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php new file mode 100644 index 0000000000..eef8e8e7b4 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/view-wizard-step-untranslated-contents.php @@ -0,0 +1,37 @@ +model->get_languages_list(); +?> +

    +

    +
    +
    + +

    +
    + + +
    diff --git a/wp-content/plugins/polylang/modules/wizard/wizard.php b/wp-content/plugins/polylang/modules/wizard/wizard.php new file mode 100644 index 0000000000..a900ca14c0 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wizard/wizard.php @@ -0,0 +1,855 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + + // Display Wizard page before any other action to ensure displaying it outside the WordPress admin context. + // Hooked on admin_init with priority 40 to ensure PLL_Wizard_Pro is correctly initialized. + add_action( 'admin_init', array( $this, 'setup_wizard_page' ), 40 ); + // Add Wizard submenu. + add_filter( 'pll_settings_tabs', array( $this, 'settings_tabs' ), 10, 1 ); + // Add filter to select screens where to display the notice. + add_filter( 'pll_can_display_notice', array( $this, 'can_display_notice' ), 10, 2 ); + + // Default steps. + add_filter( 'pll_wizard_steps', array( $this, 'add_step_licenses' ), 100 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_languages' ), 200 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_media' ), 300 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_untranslated_contents' ), 400 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_home_page' ), 500 ); + add_filter( 'pll_wizard_steps', array( $this, 'add_step_last' ), 999 ); + } + + /** + * Save an activation transient when Polylang is activating to redirect to the wizard + * + * @since 2.7 + * + * @param bool $network_wide if activated for all sites in the network. + * @return void + */ + public static function start_wizard( $network_wide ) { + $options = get_option( 'polylang' ); + + if ( wp_doing_ajax() || $network_wide || ! empty( $options ) ) { + return; + } + set_transient( 'pll_activation_redirect', 1, 30 ); + } + + /** + * Redirect to the wizard depending on the context + * + * @since 2.7 + * + * @return void + */ + public function redirect_to_wizard() { + if ( get_transient( 'pll_activation_redirect' ) ) { + $do_redirect = true; + if ( ( isset( $_GET['page'] ) && 'mlang_wizard' === sanitize_key( $_GET['page'] ) || isset( $_GET['activate-multi'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + delete_transient( 'pll_activation_redirect' ); + $do_redirect = false; + } + + if ( $do_redirect ) { + wp_safe_redirect( + esc_url_raw( + add_query_arg( + array( + 'page' => 'mlang_wizard', + ), + admin_url( 'admin.php' ) + ) + ) + ); + exit; + } + } + } + + /** + * Add an admin Polylang submenu to access the wizard + * + * @since 2.7 + * + * @param string[] $tabs Submenus list. + * @return string[] Submenus list updated. + */ + public function settings_tabs( $tabs ) { + $tabs['wizard'] = esc_html__( 'Setup', 'polylang' ); + return $tabs; + } + + /** + * Returns true if the media step is displayable, false otherwise. + * + * @since 2.7 + * + * @param PLL_Language[] $languages List of language objects. + * @return bool + */ + public function is_media_step_displayable( $languages ) { + $media = array(); + // If there is no language or only one the media step is displayable. + if ( ! $languages || count( $languages ) < 2 ) { + return true; + } + foreach ( $languages as $language ) { + $media[ $language->slug ] = $this->model->count_posts( + $language, + array( + 'post_type' => array( 'attachment' ), + 'post_status' => 'inherit', + ) + ); + } + return count( array_filter( $media ) ) === 0; + } + + /** + * Check if the licenses step is displayable + * + * @since 2.7 + * + * @return bool + */ + public function is_licenses_step_displayable() { + $licenses = apply_filters( 'pll_settings_licenses', array() ); + return count( $licenses ) > 0; + } + + /** + * Setup the wizard page + * + * @since 2.7 + * + * @return void + */ + public function setup_wizard_page() { + + PLL_Admin_Notices::add_notice( 'wizard', $this->wizard_notice() ); + + $this->redirect_to_wizard(); + if ( ! Polylang::is_wizard() ) { + return; + } + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'Sorry, you are not allowed to manage options for this site.', 'polylang' ) ); + } + + // Enqueue scripts and styles especially for the wizard. + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + $this->steps = apply_filters( 'pll_wizard_steps', $this->steps ); + $step = isset( $_GET['step'] ) ? sanitize_key( $_GET['step'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification + + $this->step = $step && array_key_exists( $step, $this->steps ) ? $step : current( array_keys( $this->steps ) ); + + $has_languages = $this->model->has_languages(); + + if ( ! $has_languages && ! in_array( $this->step, array( 'licenses', 'languages' ) ) ) { + wp_safe_redirect( esc_url_raw( $this->get_step_link( 'languages' ) ) ); + exit; + } + + if ( $has_languages && $this->model->get_objects_with_no_lang( 1 ) && ! in_array( $this->step, array( 'licenses', 'languages', 'media', 'untranslated-contents' ) ) ) { + wp_safe_redirect( esc_url_raw( $this->get_step_link( 'untranslated-contents' ) ) ); + exit; + } + + // Call the handler of the step for going to the next step. + // Be careful nonce verification with check_admin_referer must be done in each handler. + if ( ! empty( $_POST['save_step'] ) && isset( $this->steps[ $this->step ]['handler'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + call_user_func( $this->steps[ $this->step ]['handler'] ); + } + + $this->display_wizard_page(); + // Ensure nothing is done after including the page. + exit; + } + + /** + * Adds some admin screens where to display the wizard notice + * + * @since 2.7 + * + * @param bool $can_display_notice Whether the notice can be displayed. + * @param string $notice The notice name. + * @return bool + */ + public function can_display_notice( $can_display_notice, $notice ) { + if ( ! $can_display_notice && 'wizard' === $notice ) { + $screen = get_current_screen(); + $can_display_notice = ! empty( $screen ) && in_array( + $screen->base, + array( + 'edit', + 'upload', + 'options-general', + ) + ); + } + return $can_display_notice; + } + + /** + * Return html code of the wizard notice + * + * @since 2.7 + * + * @return string + */ + public function wizard_notice() { + ob_start(); + include __DIR__ . '/html-wizard-notice.php'; + return ob_get_clean(); + } + + /** + * Display the wizard page + * + * @since 2.7 + * + * @return void + */ + public function display_wizard_page() { + set_current_screen( 'pll-wizard' ); + include __DIR__ . '/view-wizard-page.php'; + } + + /** + * Enqueue scripts and styles for the wizard + * + * @since 2.7 + * + * @return void + */ + public function enqueue_scripts() { + wp_enqueue_style( 'polylang_admin', plugins_url( '/css/build/admin' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array(), POLYLANG_VERSION ); + wp_enqueue_style( 'pll-wizard', plugins_url( '/css/build/wizard' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common', 'forms' ), POLYLANG_VERSION ); + + $this->styles = array( 'polylang_admin', 'pll-wizard' ); + } + + /** + * Get the suffix to enqueue non minified files in a Debug context + * + * @since 2.7 + * + * @return string Empty when SCRIPT_DEBUG equal to true + * otherwise .min + */ + public function get_suffix() { + return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + } + + /** + * Get the URL for the step's screen. + * + * @since 2.7 + * + * @param string $step slug (default: current step). + * @return string URL for the step if it exists. + * Empty string on failure. + */ + public function get_step_link( $step = '' ) { + if ( ! $step ) { + $step = $this->step; + } + + $keys = array_keys( $this->steps ); + + $step_index = array_search( $step, $keys, true ); + if ( false === $step_index ) { + return ''; + } + + return add_query_arg( 'step', $keys[ $step_index ], remove_query_arg( 'activate_error' ) ); + } + + /** + * Get the URL for the next step's screen. + * + * @since 2.7 + * + * @param string $step slug (default: current step). + * @return string URL for next step if a next step exists. + * Admin URL if it's the last step. + * Empty string on failure. + */ + public function get_next_step_link( $step = '' ) { + if ( ! $step ) { + $step = $this->step; + } + + $keys = array_keys( $this->steps ); + if ( end( $keys ) === $step ) { + return admin_url(); + } + + $step_index = array_search( $step, $keys, true ); + if ( false === $step_index ) { + return ''; + } + + return add_query_arg( 'step', $keys[ $step_index + 1 ], remove_query_arg( 'activate_error' ) ); + } + + /** + * Add licenses step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_licenses( $steps ) { + // Add ajax action on deactivate button in licenses step. + add_action( 'wp_ajax_pll_deactivate_license', array( $this, 'deactivate_license' ) ); + + // Be careful pll_admin script is enqueued here without dependency except jquery because only code useful for deactivate license button is needed. + // To be really loaded the script need to be passed to the $steps['licenses']['scripts'] array below with the same handle than in wp_enqueue_script(). + wp_enqueue_script( 'pll_admin', plugins_url( '/js/build/admin' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_admin', 'pll_admin', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + + if ( $this->is_licenses_step_displayable() ) { + $steps['licenses'] = array( + 'name' => esc_html__( 'Licenses', 'polylang' ), + 'view' => array( $this, 'display_step_licenses' ), + 'handler' => array( $this, 'save_step_licenses' ), + 'scripts' => array( 'pll_admin' ), // Polylang admin script used by deactivate license button. + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the languages step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_licenses() { + include __DIR__ . '/view-wizard-step-licenses.php'; + } + + /** + * Execute the languages step + * + * @since 2.7 + * + * @return void + */ + public function save_step_licenses() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $redirect = $this->get_next_step_link(); + $licenses = apply_filters( 'pll_settings_licenses', array() ); + + foreach ( $licenses as $license ) { + if ( ! empty( $_POST['licenses'][ $license->id ] ) ) { + $updated_license = $license->activate_license( sanitize_key( $_POST['licenses'][ $license->id ] ) ); + if ( ! empty( $updated_license->license_data ) && false === $updated_license->license_data->success ) { + // Stay on this step with an error. + $redirect = add_query_arg( + array( + 'step' => $this->step, + 'activate_error' => 'i18n_license_key_error', + ) + ); + } + } + } + + wp_safe_redirect( esc_url_raw( $redirect ) ); + exit; + } + + /** + * Ajax method to deactivate a license + * + * @since 2.7 + * + * @return void + */ + public function deactivate_license() { + check_ajax_referer( 'pll-wizard', '_pll_nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( ! isset( $_POST['id'] ) ) { + wp_die( 0 ); + } + + $id = substr( sanitize_text_field( wp_unslash( $_POST['id'] ) ), 11 ); + $licenses = apply_filters( 'pll_settings_licenses', array() ); + $license = $licenses[ $id ]; + $license->deactivate_license(); + + wp_send_json( + array( + 'id' => $id, + 'html' => $license->get_form_field(), + ) + ); + } + + /** + * Add languages step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_languages( $steps ) { + wp_deregister_script( 'pll_admin' ); // Deregister after the licenses step enqueue to update jquery-ui-selectmenu dependency. + // The wp-ajax-response and postbox dependencies is useless in wizard steps especially postbox which triggers a javascript error otherwise. + // To be really loaded the script need to be passed to the $steps['languages']['scripts'] array below with the same handle than in wp_enqueue_script(). + wp_enqueue_script( 'pll_admin', plugins_url( '/js/build/admin' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-selectmenu' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_admin', 'pll_admin', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + wp_register_script( 'pll-wizard-languages', plugins_url( '/js/build/languages-step' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-dialog' ), POLYLANG_VERSION, true ); + wp_localize_script( + 'pll-wizard-languages', + 'pll_wizard_params', + array( + 'i18n_no_language_selected' => esc_html__( 'You need to select a language to be added.', 'polylang' ), + 'i18n_language_already_added' => esc_html__( 'You already added this language.', 'polylang' ), + 'i18n_no_language_added' => esc_html__( 'You need to add at least one language.', 'polylang' ), + 'i18n_add_language_needed' => esc_html__( 'You selected a language, however, to be able to continue, you need to add it.', 'polylang' ), + 'i18n_pll_add_language' => esc_html__( 'Impossible to add the language.', 'polylang' ), + 'i18n_pll_invalid_locale' => esc_html__( 'Enter a valid WordPress locale', 'polylang' ), + 'i18n_pll_invalid_slug' => esc_html__( 'The language code contains invalid characters', 'polylang' ), + 'i18n_pll_non_unique_slug' => esc_html__( 'The language code must be unique', 'polylang' ), + 'i18n_pll_invalid_name' => esc_html__( 'The language must have a name', 'polylang' ), + 'i18n_pll_invalid_flag' => esc_html__( 'The flag does not exist', 'polylang' ), + 'i18n_dialog_title' => esc_html__( "A language wasn't added.", 'polylang' ), + 'i18n_dialog_yes_button' => esc_html__( 'Yes', 'polylang' ), + 'i18n_dialog_no_button' => esc_html__( 'No', 'polylang' ), + 'i18n_dialog_ignore_button' => esc_html__( 'Ignore', 'polylang' ), + 'i18n_remove_language_icon' => esc_html__( 'Remove this language', 'polylang' ), + ) + ); + wp_enqueue_script( 'pll-wizard-languages' ); + wp_enqueue_style( 'pll-wizard-selectmenu', plugins_url( '/css/build/selectmenu' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common', 'wp-jquery-ui-dialog' ), POLYLANG_VERSION ); + $steps['languages'] = array( + 'name' => esc_html__( 'Languages', 'polylang' ), + 'view' => array( $this, 'display_step_languages' ), + 'handler' => array( $this, 'save_step_languages' ), + 'scripts' => array( 'pll-wizard-languages', 'pll_admin' ), + 'styles' => array( 'pll-wizard-selectmenu' ), + ); + return $steps; + } + + /** + * Display the languages step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_languages() { + include __DIR__ . '/view-wizard-step-languages.php'; + } + + /** + * Execute the languages step + * + * @since 2.7 + * + * @return void + */ + public function save_step_languages() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $all_languages = include POLYLANG_DIR . '/settings/languages.php'; + $languages = isset( $_POST['languages'] ) && is_array( $_POST['languages'] ) ? array_map( 'sanitize_locale_name', $_POST['languages'] ) : false; + $saved_languages = array(); + + // If there is no language added or defined. + if ( empty( $languages ) && ! $this->model->has_languages() ) { + // Stay on this step with an error. + wp_safe_redirect( + esc_url_raw( + add_query_arg( + array( + 'step' => $this->step, + 'activate_error' => 'i18n_no_language_added', + ) + ) + ) + ); + exit; + } + + // Otherwise process the languages to add or skip the step if no language has been added. + if ( ! empty( $languages ) ) { + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + // Remove duplicate values. + $languages = array_unique( $languages ); + // For each language add it in Polylang settings. + foreach ( $languages as $locale ) { + $saved_languages = $all_languages[ $locale ]; + + $saved_languages['slug'] = $saved_languages['code']; + $saved_languages['rtl'] = (int) ( 'rtl' === $saved_languages['dir'] ); + $saved_languages['term_group'] = 0; // Default term_group. + + $language_added = $this->model->add_language( $saved_languages ); + + if ( $language_added instanceof WP_Error && array_key_exists( 'pll_non_unique_slug', $language_added->errors ) ) { + // Get the slug from the locale : lowercase and dash instead of underscore. + $saved_languages['slug'] = strtolower( str_replace( '_', '-', $saved_languages['locale'] ) ); + $language_added = $this->model->add_language( $saved_languages ); + } + + if ( $language_added instanceof WP_Error ) { + // Stay on this step with an error. + $error_keys = array_keys( $language_added->errors ); + wp_safe_redirect( + esc_url_raw( + add_query_arg( + array( + 'step' => $this->step, + 'activate_error' => 'i18n_' . reset( $error_keys ), + ) + ) + ) + ); + exit; + } + + if ( 'en_US' !== $locale && current_user_can( 'install_languages' ) ) { + wp_download_language_pack( $locale ); + } + } + } + wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add the media step to the wizard. + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_media( $steps ) { + $languages = $this->model->get_languages_list(); + + if ( $this->is_media_step_displayable( $languages ) ) { + $steps['media'] = array( + 'name' => esc_html__( 'Media', 'polylang' ), + 'view' => array( $this, 'display_step_media' ), + 'handler' => array( $this, 'save_step_media' ), + 'scripts' => array(), + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the media step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_media() { + include __DIR__ . '/view-wizard-step-media.php'; + } + + /** + * Execute the media step + * + * @since 2.7 + * + * @return void + */ + public function save_step_media() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $media_support = isset( $_POST['media_support'] ) ? sanitize_key( $_POST['media_support'] ) === 'yes' : false; + + $this->options['media_support'] = $media_support; + + update_option( 'polylang', $this->options ); + + wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add untranslated contents step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_untranslated_contents( $steps ) { + if ( ! $this->model->has_languages() || $this->model->get_objects_with_no_lang( 1 ) ) { + // Even if pll_admin is already enqueued with the same dependencies by the languages step, it is interesting to keep that it's also useful for the untranslated-contents step. + // To be really loaded the script need to be passed to the $steps['untranslated-contents']['scripts'] array below with the same handle than in wp_enqueue_script(). + wp_enqueue_script( 'pll_admin', plugins_url( '/js/build/admin' . $this->get_suffix() . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'jquery-ui-selectmenu' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_admin', 'pll_admin', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + wp_enqueue_style( 'pll-wizard-selectmenu', plugins_url( '/css/build/selectmenu' . $this->get_suffix() . '.css', POLYLANG_ROOT_FILE ), array( 'dashicons', 'install', 'common' ), POLYLANG_VERSION ); + $steps['untranslated-contents'] = array( + 'name' => esc_html__( 'Content', 'polylang' ), + 'view' => array( $this, 'display_step_untranslated_contents' ), + 'handler' => array( $this, 'save_step_untranslated_contents' ), + 'scripts' => array( 'pll_admin' ), + 'styles' => array( 'pll-wizard-selectmenu' ), + ); + } + return $steps; + } + + /** + * Display the untranslated contents step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_untranslated_contents() { + include __DIR__ . '/view-wizard-step-untranslated-contents.php'; + } + + /** + * Execute the untranslated contents step + * + * @since 2.7 + * + * @return void + */ + public function save_step_untranslated_contents() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $lang = ! empty( $_POST['language'] ) && is_string( $_POST['language'] ) ? sanitize_locale_name( $_POST['language'] ) : false; + + if ( empty( $lang ) ) { + $lang = $this->options['default_lang']; + } + + $language = $this->model->get_language( $lang ); + + if ( $language instanceof PLL_Language ) { + $this->model->set_language_in_mass( $language ); + } + + wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) ); + exit; + } + + /** + * Add home page step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_home_page( $steps ) { + $languages = $this->model->get_languages_list(); + $home_page_id = get_option( 'page_on_front' ); + + $translations = $this->model->post->get_translations( $home_page_id ); + + if ( $home_page_id > 0 && ( ! $languages || count( $languages ) === 1 || count( $translations ) !== count( $languages ) ) ) { + $steps['home-page'] = array( + 'name' => esc_html__( 'Homepage', 'polylang' ), + 'view' => array( $this, 'display_step_home_page' ), + 'handler' => array( $this, 'save_step_home_page' ), + 'scripts' => array(), + 'styles' => array(), + ); + } + return $steps; + } + + /** + * Display the home page step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_home_page() { + include __DIR__ . '/view-wizard-step-home-page.php'; + } + + /** + * Execute the home page step + * + * @since 2.7 + * + * @return void + */ + public function save_step_home_page() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + $default_language = $this->model->has_languages() ? $this->options['default_lang'] : null; + $home_page = isset( $_POST['home_page'] ) ? sanitize_key( $_POST['home_page'] ) : false; + $home_page_title = isset( $_POST['home_page_title'] ) ? sanitize_text_field( wp_unslash( $_POST['home_page_title'] ) ) : esc_html__( 'Homepage', 'polylang' ); + $home_page_language = isset( $_POST['home_page_language'] ) ? sanitize_key( $_POST['home_page_language'] ) : false; + + $untranslated_languages = isset( $_POST['untranslated_languages'] ) ? array_map( 'sanitize_key', $_POST['untranslated_languages'] ) : array(); + + call_user_func( + apply_filters( 'pll_wizard_create_home_page_translations', array( $this, 'create_home_page_translations' ) ), + $default_language, + $home_page, + $home_page_title, + $home_page_language, + $untranslated_languages + ); + + $this->model->clean_languages_cache(); + + wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) ); + exit; + } + + /** + * Create home page translations for each language defined. + * + * @since 2.7 + * + * @param string $default_language Slug of the default language; null if no default language is defined. + * @param int $home_page Post ID of the home page if it's defined, false otherwise. + * @param string $home_page_title Home page title if it's defined, 'Homepage' otherwise. + * @param string $home_page_language Slug of the home page if it's defined, false otherwise. + * @param string[] $untranslated_languages Array of languages which needs to have a home page translated. + * @return void + */ + public function create_home_page_translations( $default_language, $home_page, $home_page_title, $home_page_language, $untranslated_languages ) { + $translations = $this->model->post->get_translations( $home_page ); + + foreach ( $untranslated_languages as $language ) { + $language_properties = $this->model->get_language( $language ); + $id = wp_insert_post( + array( + 'post_title' => $home_page_title . ' - ' . $language_properties->name, + 'post_type' => 'page', + 'post_status' => 'publish', + ) + ); + $translations[ $language ] = $id; + pll_set_post_language( $id, $language ); + } + pll_save_post_translations( $translations ); + } + + /** + * Add last step to the wizard + * + * @since 2.7 + * + * @param array $steps List of steps. + * @return array List of steps updated. + */ + public function add_step_last( $steps ) { + $steps['last'] = array( + 'name' => esc_html__( 'Ready!', 'polylang' ), + 'view' => array( $this, 'display_step_last' ), + 'handler' => array( $this, 'save_step_last' ), + 'scripts' => array(), + 'styles' => array(), + ); + return $steps; + } + + /** + * Display the last step form + * + * @since 2.7 + * + * @return void + */ + public function display_step_last() { + // We ran the wizard once. So we can dismiss its notice. + PLL_Admin_Notices::dismiss( 'wizard' ); + include __DIR__ . '/view-wizard-step-last.php'; + } + + /** + * Execute the last step + * + * @since 2.7 + * + * @return void + */ + public function save_step_last() { + check_admin_referer( 'pll-wizard', '_pll_nonce' ); + + wp_safe_redirect( esc_url_raw( $this->get_next_step_link() ) ); + exit; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/load.php b/wp-content/plugins/polylang/modules/wpml/load.php new file mode 100644 index 0000000000..0cd81b0a7e --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/load.php @@ -0,0 +1,17 @@ +model->has_languages() ) { + if ( ! defined( 'PLL_WPML_COMPAT' ) || PLL_WPML_COMPAT ) { + PLL_WPML_Compat::instance(); // WPML API. + PLL_WPML_Config::instance(); // wpml-config.xml. + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-api.php b/wp-content/plugins/polylang/modules/wpml/wpml-api.php new file mode 100644 index 0000000000..ac827c620a --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-api.php @@ -0,0 +1,495 @@ + not applicable. + add_filter( 'wpml_current_language', 'pll_current_language', 10, 0 ); + add_filter( 'wpml_default_language', 'pll_default_language', 10, 0 ); + // wpml_add_language_selector => not implemented. + // wpml_footer_language_selector => not applicable. + add_action( 'wpml_add_language_form_field', array( $this, 'wpml_add_language_form_field' ) ); + add_filter( 'wpml_language_is_active', array( $this, 'wpml_language_is_active' ), 10, 2 ); + add_filter( 'wpml_is_rtl', array( $this, 'wpml_is_rtl' ) ); + // wpml_language_form_input_field => See wpml_add_language_form_field + // wpml_language_has_switched => See wpml_switch_language + add_filter( 'wpml_element_trid', array( $this, 'wpml_element_trid' ), 10, 3 ); + add_filter( 'wpml_get_element_translations', array( $this, 'wpml_get_element_translations' ), 10, 3 ); + // wpml_language_switcher => not implemented. + // wpml_browser_redirect_language_params => not implemented. + // wpml_enqueue_browser_redirect_language => not applicable. + // wpml_enqueued_browser_redirect_language => not applicable. + // wpml_encode_string => not applicable. + // wpml_decode_string => not applicable. + + /* + * Retrieving Language Information for Content. + */ + add_filter( 'wpml_post_language_details', 'wpml_get_language_information', 10, 2 ); + add_action( 'wpml_switch_language', array( __CLASS__, 'wpml_switch_language' ), 10, 2 ); + add_filter( 'wpml_element_language_code', array( $this, 'wpml_element_language_code' ), 10, 2 ); + // wpml_element_language_details => not applicable. + + /* + * Retrieving Localized Content. + */ + add_filter( 'wpml_home_url', 'pll_home_url', 10, 0 ); + add_filter( 'wpml_element_link', 'icl_link_to_element', 10, 7 ); + add_filter( 'wpml_object_id', 'icl_object_id', 10, 4 ); + add_filter( 'wpml_translate_single_string', array( $this, 'wpml_translate_single_string' ), 10, 4 ); + // wpml_translate_string => not applicable. + // wpml_unfiltered_admin_string => not implemented. + add_filter( 'wpml_permalink', array( $this, 'wpml_permalink' ), 10, 2 ); + // wpml_elements_without_translations => not implemented. + add_filter( 'wpml_get_translated_slug', array( $this, 'wpml_get_translated_slug' ), 10, 3 ); + + /* + * Finding the Translation State of Content. + */ + // wpml_element_translation_type => not implemented. + add_filter( 'wpml_element_has_translations', array( $this, 'wpml_element_has_translations' ), 10, 3 ); + // wpml_master_post_from_duplicate => not applicable. + // wpml_post_duplicates => not applicable. + + /* + * Inserting Content. + */ + // wpml_admin_make_post_duplicates => not applicable. + // wpml_make_post_duplicates => not applicable. + add_action( 'wpml_register_single_string', 'icl_register_string', 10, 3 ); + // wpml_register_string => not applicable. + // wpml_register_string_packages => not applicable. + // wpml_delete_package_action => not applicable. + // wpml_show_package_language_ui => not applicable. + // wpml_set_element_language_details => not implemented. + // wpml_multilingual_options => not applicable. + + /* + * Miscellaneous + */ + // wpml_element_type => not applicable. + // wpml_setting => not applicable. + // wpml_sub_setting => not applicable. + // wpml_editor_cf_to_display => not applicable. + // wpml_tm_save_translation_cf => not implemented. + // wpml_tm_xliff_export_translated_cf => not applicable. + // wpml_tm_xliff_export_original_cf => not applicable. + // wpml_duplicate_generic_string => not applicable. + // wpml_translatable_user_meta_fields => not implemented. + // wpml_cross_domain_language_data => not applicable. + // wpml_get_cross_domain_language_data => not applicable. + // wpml_loaded => not applicable. + // wpml_st_loaded => not applicable. + // wpml_tm_loaded => not applicable. + // wpml_hide_management_column (3.4.1) => not applicable. + // wpml_ls_directories_to_scan => not applicable. + // wpml_ls_model_css_classes => not applicable. + // wpml_ls_model_language_css_classes => not applicable. + // wpml_tf_feedback_open_link => not applicable. + // wpml_sync_custom_field => not implemented. + // wpml_sync_all_custom_fields => not implemented. + // wpml_is_redirected => not implemented. + + /* + * Updating Content + */ + // wpml_set_translation_mode_for_post_type => not implemented. + + /* + * Undocumented + */ + add_filter( 'wpml_is_translated_post_type', array( $this, 'wpml_is_translated_post_type' ), 10, 2 ); + add_filter( 'wpml_is_translated_taxonomy', array( $this, 'wpml_is_translated_taxonomy' ), 10, 2 ); + } + + /** + * Get a list of the languages enabled for a site. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param array| string $args See arguments of icl_get_languages(). + * @return array Array of arrays per language. + */ + public function wpml_active_languages( $null, $args = '' ) { + return icl_get_languages( $args ); + } + + /** + * In WPML, get a language's native and translated name for display in a custom language switcher + * Since Polylang does not implement the translated name, always returns only the native name, + * so the 3rd, 4th and 5th parameters are not used. + * + * @since 2.2 + * + * @param mixed $null Not used. + * @param string $native_name The language native name. + * @return string + */ + public function wpml_display_language_names( $null, $native_name ) { + return $native_name; + } + + /** + * Returns an HTML hidden input field with name=”lang” and as value the current language. + * + * @since 2.0 + * + * @return void + */ + public function wpml_add_language_form_field() { + $lang = pll_current_language(); + $field = sprintf( '', esc_attr( $lang ) ); + $field = apply_filters( 'wpml_language_form_input_field', $field, $lang ); + echo $field; // phpcs:ignore WordPress.Security.EscapeOutput + } + + /** + * Find out if a specific language is enabled for the site. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param string $slug Language code. + * @return bool + */ + public function wpml_language_is_active( $null, $slug ) { + $language = PLL()->model->get_language( $slug ); + return ! empty( $language ) && $language->active; + } + + /** + * Find out whether the current language text direction is RTL or not. + * + * @since 2.0 + * + * @return bool + */ + public function wpml_is_rtl() { + return pll_current_language( 'is_rtl' ); + } + + /** + * Returns the id of the translation group of a translated element. + * + * @since 3.4 + * + * @param mixed $empty_value Not used. + * @param int $element_id The id of the item, post id for posts, term_taxonomy_id for terms. + * @param string $element_type Optional. The type of an element. + * @return int + */ + public function wpml_element_trid( $empty_value, $element_id, $element_type = 'post_post' ) { + if ( 0 === strpos( $element_type, 'tax_' ) ) { + $element = get_term_by( 'term_taxonomy_id', $element_id ); + if ( $element instanceof WP_Term ) { + $tr_term = PLL()->model->term->get_object_term( $element->term_id, 'term_translations' ); + } + } + + if ( 0 === strpos( $element_type, 'post_' ) ) { + $tr_term = PLL()->model->post->get_object_term( $element_id, 'post_translations' ); + } + + if ( isset( $tr_term ) && $tr_term instanceof WP_Term ) { + return $tr_term->term_id; + } + + return 0; + } + + /** + * Returns the element translations info using the ID of the translation group. + * + * @since 3.4 + * + * @param mixed $empty_value Not used. + * @param int $trid The ID of the translation group. + * @param string $element_type Optional. The type of an element. + * @return stdClass[] + */ + public function wpml_get_element_translations( $empty_value, $trid, $element_type = 'post_post' ) { + $return = array(); + + if ( 0 === strpos( $element_type, 'tax_' ) ) { + $translations = PLL()->model->term->get_translations_from_term_id( $trid ); + if ( empty( $translations ) ) { + return array(); + } + + $original = min( $translations ); // We suppose that the original is the first term created. + $source_lang = array_search( $original, $translations ); + + $args = array( + 'include' => $translations, + 'hide_empty' => false, + ); + $_terms = get_terms( $args ); + + if ( ! is_array( $_terms ) ) { + return array(); + } + + $terms = array(); + foreach ( $_terms as $term ) { + $terms[ $term->term_id ] = $term; + } + + foreach ( $translations as $lang => $term_id ) { + if ( empty( $terms[ $term_id ] ) ) { + continue; + } + + /* + * It seems that WPML fills the `instances` property with the total number of posts + * related to this term, while `WP_Term::$count` includes only *published* posts. + * We intentionnally accept this difference to avoid extra DB queries. + */ + $return[ $lang ] = (object) array( + 'translation_id' => '0', // We have nothing equivalent. + 'language_code' => $lang, + 'element_id' => (string) $terms[ $term_id ]->term_taxonomy_id, + 'source_language_code' => $source_lang === $lang ? null : $source_lang, + 'element_type' => $element_type, + 'original' => $original === $term_id ? '1' : '0', + 'name' => $terms[ $term_id ]->name, + 'term_id' => (string) $term_id, + 'instances' => (string) $terms[ $term_id ]->count, + ); + } + } + + if ( 0 === strpos( $element_type, 'post_' ) ) { + $translations = PLL()->model->post->get_translations_from_term_id( $trid ); + if ( empty( $translations ) ) { + return array(); + } + + $original = min( $translations ); // We suppose that the original is the first post created. + $source_lang = array_search( $original, $translations ); + + $args = array( + 'post__in' => $translations, + 'no_paging' => true, + 'posts_per_page' => -1, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'lang' => '', + ); + $_posts = get_posts( $args ); + + $posts = array(); + foreach ( $_posts as $post ) { + $posts[ $post->ID ] = $post; + } + + foreach ( $translations as $lang => $post_id ) { + if ( empty( $posts[ $post_id ] ) ) { + continue; + } + + $return[ $lang ] = (object) array( + 'translation_id' => '0', // We have nothing equivalent. + 'language_code' => $lang, + 'element_id' => (string) $post_id, + 'source_language_code' => $source_lang === $lang ? null : $source_lang, + 'element_type' => $element_type, + 'original' => $original === $post_id ? '1' : '0', + 'post_title' => $posts[ $post_id ]->post_title, + 'post_status' => $posts[ $post_id ]->post_status, + ); + } + } + + return $return; + } + + /** + * Switches whole site to the given language or restores the language that was set when first calling this function. + * Unlike the WPML original action, it is not possible to set the current language and the cookie to different values. + * + * @since 2.7 + * + * @param null|string $lang Language code to switch into, restores the original language if null. + * @param bool|string $cookie Optionally also switches the cookie. + * @return void + */ + public static function wpml_switch_language( $lang = null, $cookie = false ) { + if ( null === self::$original_language ) { + self::$original_language = PLL()->curlang; + } + + if ( empty( $lang ) ) { + PLL()->curlang = self::$original_language; + } elseif ( 'all' === $lang ) { + PLL()->curlang = null; + } elseif ( in_array( $lang, pll_languages_list() ) ) { + PLL()->curlang = PLL()->model->get_language( $lang ); + } + + if ( $cookie && isset( PLL()->choose_lang ) ) { + PLL()->choose_lang->maybe_setcookie(); + } + + do_action( 'wpml_language_has_switched', $lang, $cookie, self::$original_language ); + } + + /** + * Get the language code for a translatable element. + * + * @since 2.0 + * + * @param mixed $language_code A 2-letter language code. + * @param array $args An array with two keys element_id => post_id or term_taxonomy_id, element_type => post type or taxonomy + * @return string|null + */ + public function wpml_element_language_code( $language_code, $args ) { + $type = $args['element_type']; + $id = $args['element_id']; + + if ( 'post' === $type || pll_is_translated_post_type( $type ) ) { + $language = pll_get_post_language( $id ); + return is_string( $language ) ? $language : null; + } + + if ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) { + $term = get_term_by( 'term_taxonomy_id', $id ); + if ( $term instanceof WP_Term ) { + $id = $term->term_id; + } + $language = pll_get_term_language( $id ); + return is_string( $language ) ? $language : null; + } + + return null; + } + + /** + * Translates a string. + * + * @since 2.0 + * + * @param string $string The string's original value. + * @param string $context The string's registered context. + * @param string $name The string's registered name. + * @param null|string $lang Optional, return the translation in this language, defaults to current language. + * @return string The translated string. + */ + public function wpml_translate_single_string( $string, $context, $name, $lang = null ) { + $has_translation = null; // Passed by reference. + return icl_translate( $context, $name, $string, false, $has_translation, $lang ); + } + + /** + * Converts a permalink to a language specific permalink. + * + * @since 2.2 + * + * @param string $url The url to filter. + * @param null|string $lang Language code, optional, defaults to the current language. + * @return string + */ + public function wpml_permalink( $url, $lang = '' ) { + $lang = PLL()->model->get_language( $lang ); + + if ( empty( $lang ) && ! empty( PLL()->curlang ) ) { + $lang = PLL()->curlang; + } + + return empty( $lang ) ? $url : PLL()->links_model->switch_language_in_link( $url, $lang ); + } + + /** + * Translates a post type slug. + * + * @since 2.2 + * + * @param string $slug Post type slug. + * @param string $post_type Post type name. + * @param string $lang Optional language code (defaults to current language). + * @return string + */ + public function wpml_get_translated_slug( $slug, $post_type, $lang = null ) { + if ( isset( PLL()->translate_slugs ) ) { + if ( empty( $lang ) ) { + $lang = pll_current_language(); + } + + $slug = PLL()->translate_slugs->slugs_model->get_translated_slug( $post_type, $lang ); + } + return $slug; + } + + /** + * Find out whether a post type or a taxonomy term is translated. + * + * @since 2.0 + * + * @param mixed $null Not used. + * @param int $id The post_id or term_id. + * @param string $type The post type or taxonomy. + * @return bool + */ + public function wpml_element_has_translations( $null, $id, $type ) { + if ( 'post' === $type || pll_is_translated_post_type( $type ) ) { + return count( pll_get_post_translations( $id ) ) > 1; + } elseif ( 'term' === $type || pll_is_translated_taxonomy( $type ) ) { + return count( pll_get_term_translations( $id ) ) > 1; + } + + return false; + } + + /** + * Returns true if languages and translations are managed for this post type. + * + * @since 3.4 + * + * @param mixed $value Not used. + * @param string $post_type The post type name. + * @return bool + */ + public function wpml_is_translated_post_type( $value, $post_type ) { + return pll_is_translated_post_type( $post_type ); + } + + /** + * Returns true if languages and translations are managed for this taxonomy. + * + * @since 3.4 + * + * @param mixed $value Not used. + * @param string $taxonomy The taxonomy name. + * @return bool + */ + public function wpml_is_translated_taxonomy( $value, $taxonomy ) { + return pll_is_translated_taxonomy( $taxonomy ); + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-compat.php b/wp-content/plugins/polylang/modules/wpml/wpml-compat.php new file mode 100644 index 0000000000..a828d47547 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-compat.php @@ -0,0 +1,193 @@ +api = new PLL_WPML_API(); + + self::$strings = get_option( 'polylang_wpml_strings', array() ); + + if ( ! is_array( self::$strings ) ) { + self::$strings = array(); // In case the serialized option is corrupted. + } + + add_action( 'pll_language_defined', array( $this, 'define_constants' ) ); + add_action( 'pll_no_language_defined', array( $this, 'define_constants' ) ); + add_filter( 'pll_get_strings', array( $this, 'get_strings' ) ); + } + + /** + * Access to the single instance of the class + * + * @since 1.7 + * + * @return PLL_WPML_Compat + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Defines two WPML constants once the language has been defined + * The compatibility with WPML is not perfect on admin side as the constants are defined + * in 'setup_theme' by Polylang (based on user info) and 'plugins_loaded' by WPML (based on cookie). + * + * @since 0.9.5 + * + * @return void + */ + public function define_constants() { + if ( ! empty( PLL()->curlang ) ) { + if ( ! defined( 'ICL_LANGUAGE_CODE' ) ) { + define( 'ICL_LANGUAGE_CODE', PLL()->curlang->slug ); + } + + if ( ! defined( 'ICL_LANGUAGE_NAME' ) ) { + define( 'ICL_LANGUAGE_NAME', PLL()->curlang->name ); + } + } elseif ( ! PLL() instanceof PLL_Frontend ) { + if ( ! defined( 'ICL_LANGUAGE_CODE' ) ) { + define( 'ICL_LANGUAGE_CODE', 'all' ); + } + + if ( ! defined( 'ICL_LANGUAGE_NAME' ) ) { + define( 'ICL_LANGUAGE_NAME', '' ); + } + } + } + + /** + * Unlike pll_register_string, icl_register_string stores the string in database + * so we need to do the same as some plugins or themes may expect this. + * We use a serialized option to store these strings. + * + * @since 1.0.2 + * + * @param string|string[] $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @param string $string The string to register. + * @return void + */ + public function register_string( $context, $name, $string ) { + if ( ! $string || ! is_scalar( $string ) ) { + return; + } + + /* + * WPML accepts arrays as context and internally converts them to strings. + * See WPML_Register_String_Filter::truncate_name_and_context(). + * This possibility is used by Types. + */ + if ( is_array( $context ) ) { + $name = isset( $context['context'] ) ? $name . $context['context'] : $name; + $context = $context['domain'] ?? ''; + } + + // If a string has already been registered with the same name and context, let's replace it. + $exist_string = $this->get_string_by_context_and_name( $context, $name ); + if ( $exist_string && $exist_string !== $string ) { + $languages = PLL()->model->get_languages_list(); + + // Assign translations of the old string to the new string, except for the default language. + foreach ( $languages as $language ) { + if ( $language->is_default ) { + continue; + } + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + $mo->add_entry( $mo->make_entry( $string, $mo->translate( $exist_string ) ) ); + $mo->export_to_db( $language ); + } + $this->unregister_string( $context, $name ); + } + + // Registers the string if it does not exist yet (multiline as in WPML). + $to_register = array( 'context' => $context, 'name' => $name, 'string' => $string, 'multiline' => true, 'icl' => true ); + if ( ! in_array( $to_register, self::$strings ) ) { + $key = md5( "$context | $name" ); + self::$strings[ $key ] = $to_register; + update_option( 'polylang_wpml_strings', self::$strings ); + } + } + + /** + * Removes a string from the registered strings list + * + * @since 1.0.2 + * + * @param string $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @return void + */ + public function unregister_string( $context, $name ) { + $key = md5( "$context | $name" ); + if ( isset( self::$strings[ $key ] ) ) { + unset( self::$strings[ $key ] ); + update_option( 'polylang_wpml_strings', self::$strings ); + } + } + + /** + * Adds strings registered by icl_register_string to those registered by pll_register_string + * + * @since 1.0.2 + * + * @param array $strings existing registered strings + * @return array registered strings with added strings through WPML API + */ + public function get_strings( $strings ) { + return empty( self::$strings ) ? $strings : array_merge( $strings, self::$strings ); + } + + /** + * Get a registered string by its context and name + * + * @since 2.0 + * + * @param string $context The group in which the string is registered. + * @param string $name A unique name for the string. + * @return bool|string The registered string, false if none was found. + */ + public function get_string_by_context_and_name( $context, $name ) { + $key = md5( "$context | $name" ); + return isset( self::$strings[ $key ] ) ? self::$strings[ $key ]['string'] : false; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-config.php b/wp-content/plugins/polylang/modules/wpml/wpml-config.php new file mode 100644 index 0000000000..9ba2662d14 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-config.php @@ -0,0 +1,1048 @@ + + */ +class PLL_WPML_Config { + /** + * Singleton instance + * + * @var PLL_WPML_Config|null + */ + protected static $instance; + + /** + * The content of all read xml files. + * + * @var SimpleXMLElement[] + */ + protected $xmls = array(); + + /** + * The list of xml file paths. + * + * @var string[]|null + * + * @phpstan-var array|null + */ + protected $files; + + /** + * List of rules to extract strings to translate from blocks. + * + * @var array + * + * @phpstan-var array{ + * xpath?: array>, + * key?: array> + * }|null + */ + protected $parsing_rules = null; + + /** + * Contains the list of path in `open_basedir`. + * + * @var string[]|null + */ + private $open_basedir_paths; + + /** + * Cache for parsed metas. + * + * @var array + * + * @phpstan-var array + */ + private $parsed_metas = array(); + + /** + * Constructor + * + * @since 1.0 + */ + public function __construct() { + if ( extension_loaded( 'simplexml' ) ) { + $this->init(); + } + } + + /** + * Access to the single instance of the class + * + * @since 1.7 + * + * @return PLL_WPML_Config + */ + public static function instance() { + if ( empty( self::$instance ) ) { + self::$instance = new self(); + } + return self::$instance; + } + + /** + * Finds the wpml-config.xml files to parse and setup filters + * + * @since 1.0 + * + * @return void + */ + public function init() { + $this->xmls = array(); + $files = $this->get_files(); + + if ( empty( $files ) ) { + return; + } + + if ( ! extension_loaded( 'simplexml' ) ) { + return; + } + + // Read all files. + foreach ( $files as $context => $file ) { + $xml = simplexml_load_file( $file ); + if ( false !== $xml ) { + $this->xmls[ $context ] = $xml; + } + } + + if ( empty( $this->xmls ) ) { + return; + } + + add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ), 20, 2 ); + add_filter( 'pll_copy_term_metas', array( $this, 'copy_term_metas' ), 20, 2 ); + add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 ); + add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 ); + + // Export. + add_filter( 'pll_post_metas_to_export', array( $this, 'post_metas_to_export' ) ); + add_filter( 'pll_term_metas_to_export', array( $this, 'term_metas_to_export' ) ); + add_filter( 'pll_post_meta_encodings', array( $this, 'add_post_meta_encodings' ), 20 ); + add_filter( 'pll_term_meta_encodings', array( $this, 'add_term_meta_encodings' ), 20 ); + add_filter( 'pll_blocks_xpath_rules', array( $this, 'translate_blocks' ) ); + add_filter( 'pll_blocks_rules_for_attributes', array( $this, 'translate_blocks_attributes' ) ); + + foreach ( $this->xmls as $context => $xml ) { + $keys = $xml->xpath( 'admin-texts/key' ); + + if ( ! is_array( $keys ) ) { + continue; + } + + foreach ( $keys as $key ) { + $name = $this->get_field_attribute( $key, 'name' ); + + if ( false === strpos( $name, '*' ) ) { + $this->register_or_translate_option( $context, $name, $key ); + continue; + } + + $pattern = '#^' . str_replace( '*', '(?:.+)', $name ) . '$#'; + $names = preg_grep( $pattern, array_keys( wp_load_alloptions() ) ); + + if ( ! is_array( $names ) ) { + continue; + } + + foreach ( $names as $_name ) { + $this->register_or_translate_option( $context, $_name, $key ); + } + } + } + } + + /** + * Returns all wpml-config.xml files in MU plugins, plugins, theme, child theme, and Polylang custom directory. + * + * @since 3.1 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + public function get_files() { + if ( is_array( $this->files ) ) { + return $this->files; + } + + $this->files = array_merge( + // Plugins. + $this->get_plugin_files(), + // Theme and child theme. + $this->get_theme_files(), + // MU Plugins. + $this->get_mu_plugin_files(), + // Custom. + $this->get_custom_files() + ); + + return $this->files; + } + + /** + * Adds post metas to the list of metas to copy when creating a new translation. + * + * @since 1.0 + * + * @param string[] $metas The list of post metas to copy or synchronize. + * @param bool $sync True for sync, false for copy. + * @return string[] The list of post metas to copy or synchronize. + * + * @phpstan-param array $metas + */ + public function copy_post_metas( $metas, $sync ) { + return $this->filter_metas_to_copy( (array) $metas, 'custom-fields/custom-field', (bool) $sync ); + } + + /** + * Adds term metas to the list of metas to copy when creating a new translation. + * + * @since 2.6 + * + * @param string[] $metas The list of term metas to copy or synchronize. + * @param bool $sync True for sync, false for copy. + * @return string[] The list of term metas to copy or synchronize. + * + * @phpstan-param array $metas + */ + public function copy_term_metas( $metas, $sync ) { + return $this->filter_metas_to_copy( (array) $metas, 'custom-term-fields/custom-term-field', (bool) $sync ); + } + + /** + * Adds post meta keys to export. + * + * @since 3.3 + * @see PLL_Export_Metas + * + * @param array $keys { + * A recursive array containing nested meta sub-keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => array( + * 'sub_key_to_translate_1' => 1, + * 'sub_key_to_translate_2' => array( + * 'sub_sub_key_to_translate_1' => 1, + * ), + * ), + * ) + * } + * @return array + * + * @phpstan-param array $keys + * @phpstan-return array + */ + public function post_metas_to_export( $keys ) { + // Add keys that have the `action` attribute set to `translate`. + $keys = $this->add_metas_to_export( (array) $keys, 'custom-fields/custom-field' ); + + // Deal with sub-field translations. + foreach ( $this->xmls as $xml ) { + $fields = $xml->xpath( 'custom-fields-texts/key' ); + + if ( ! is_array( $fields ) ) { + // No 'custom-fields-texts' nodes. + continue; + } + + foreach ( $fields as $field ) { + $name = $this->get_field_attribute( $field, 'name' ); + + if ( '' === $name ) { + // Wrong configuration: empty `name` attribute (meta name). + continue; + } + + if ( ! array_key_exists( $name, $keys ) ) { + // Wrong configuration: the field is not in `custom-fields/custom-field`. + continue; + } + + $keys = $this->xml_to_array( $field, $keys, 1 ); + } + } + + return $keys; + } + + /** + * Adds term meta keys to export. + * Note: sub-key translations are not currently supported by WPML. + * + * @since 3.3 + * @see PLL_Export_Metas + * + * @param array $keys { + * An array containing meta keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => 1, + * ) + * } + * @return array + * + * @phpstan-param array $keys + * @phpstan-return array + */ + public function term_metas_to_export( $keys ) { + // Add keys that have the `action` attribute set to `translate`. + return $this->add_metas_to_export( (array) $keys, 'custom-term-fields/custom-term-field' ); + } + + /** + * Specifies the encoding for post metas. + * + * @since 3.6 + * + * @param string[] $metas An array containing meta names as array keys, and their encoding as array values. + * @return string[] + * + * @phpstan-param array $metas + */ + public function add_post_meta_encodings( $metas ) { + return $this->add_metas_encodings( (array) $metas, 'custom-fields/custom-field' ); + } + + /** + * Specifies the encoding for term metas. + * + * @since 3.6 + * + * @param string[] $metas An array containing meta names as array keys, and their encoding as array values. + * @return string[] + * + * @phpstan-param array $metas + */ + public function add_term_meta_encodings( $metas ) { + return $this->add_metas_encodings( (array) $metas, 'custom-term-fields/custom-term-field' ); + } + + /** + * Language and translation management for custom post types. + * + * @since 1.0 + * + * @param string[] $types The list of post type names for which Polylang manages language and translations. + * @param bool $hide True when displaying the list in Polylang settings. + * @return string[] The list of post type names for which Polylang manages language and translations. + */ + public function translate_types( $types, $hide ) { + foreach ( $this->xmls as $xml ) { + $pts = $xml->xpath( 'custom-types/custom-type' ); + + if ( ! is_array( $pts ) ) { + continue; + } + + foreach ( $pts as $pt ) { + $translate = $this->get_field_attribute( $pt, 'translate' ); + + if ( '1' === $translate && ! $hide ) { + $types[ (string) $pt ] = (string) $pt; + } else { + unset( $types[ (string) $pt ] ); // The theme/plugin author decided what to do with the post type so don't allow the user to change this + } + } + } + + return $types; + } + + /** + * Language and translation management for custom taxonomies. + * + * @since 1.0 + * + * @param string[] $taxonomies The list of taxonomy names for which Polylang manages language and translations. + * @param bool $hide True when displaying the list in Polylang settings. + * @return string[] The list of taxonomy names for which Polylang manages language and translations. + */ + public function translate_taxonomies( $taxonomies, $hide ) { + foreach ( $this->xmls as $xml ) { + $taxos = $xml->xpath( 'taxonomies/taxonomy' ); + + if ( ! is_array( $taxos ) ) { + continue; + } + + foreach ( $taxos as $tax ) { + $translate = $this->get_field_attribute( $tax, 'translate' ); + + if ( '1' === $translate && ! $hide ) { + $taxonomies[ (string) $tax ] = (string) $tax; + } else { + unset( $taxonomies[ (string) $tax ] ); // the theme/plugin author decided what to do with the taxonomy so don't allow the user to change this + } + } + } + + return $taxonomies; + } + + /** + * Translation management for strings in blocks content. + * + * @since 3.3 + * + * @param string[][] $parsing_rules Rules as Xpath expressions to evaluate in the blocks content. + * @return string[][] Rules completed with ones from wpml-config file. + * + * @phpstan-param array> $parsing_rules + * @phpstan-return array> + */ + public function translate_blocks( $parsing_rules ) { + return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'xpath' ) ); + } + + /** + * Translation management for blocks attributes. + * + * @since 3.3 + * @since 3.6 Format changed from `array` to `array`. + * + * @param array $parsing_rules Rules for blocks attributes to translate. + * @return array Rules completed with ones from wpml-config file. + * + * @phpstan-param array $parsing_rules + * @phpstan-return array + */ + public function translate_blocks_attributes( $parsing_rules ) { + return array_merge( $parsing_rules, $this->get_blocks_parsing_rules( 'key' ) ); + } + + /** + * Returns rules to extract translatable strings from blocks. + * + * @since 3.3 + * + * @param string $rule_tag Tag name to extract. + * @return string[][] The rules. + * + * @phpstan-param 'xpath'|'key' $rule_tag + * @phpstan-return ( + * $rule_tag is 'xpath' ? array> : array> + * ) + */ + protected function get_blocks_parsing_rules( $rule_tag ) { + + if ( null === $this->parsing_rules ) { + $this->parsing_rules = $this->extract_blocks_parsing_rules(); + } + + return isset( $this->parsing_rules[ $rule_tag ] ) ? $this->parsing_rules[ $rule_tag ] : array(); + } + + /** + * Extract all rules from WPML config file to translate strings for blocks. + * + * @since 3.3 + * + * @return string[][][] Rules completed with ones from wpml-config file. + * + * @phpstan-return array{ + * xpath?: array>, + * key?: array> + * } + */ + protected function extract_blocks_parsing_rules() { + $parsing_rules = array(); + + foreach ( $this->xmls as $xml ) { + $blocks = $xml->xpath( 'gutenberg-blocks/gutenberg-block' ); + + if ( ! is_array( $blocks ) ) { + continue; + } + + foreach ( $blocks as $block ) { + $translate = $this->get_field_attribute( $block, 'translate' ); + + if ( '1' !== $translate ) { + continue; + } + + $block_name = $this->get_field_attribute( $block, 'type' ); + + if ( '' === $block_name ) { + continue; + } + + foreach ( $block->children() as $child ) { + $rule = ''; + $child_tag = $child->getName(); + + switch ( $child_tag ) { + case 'xpath': + $rule = trim( (string) $child ); + + if ( '' !== $rule ) { + $parsing_rules['xpath'][ $block_name ][] = $rule; + } + break; + + case 'key': + $rule = $this->get_field_attributes( $child ); + + if ( empty( $rule ) ) { + break; + } + + if ( isset( $parsing_rules['key'][ $block_name ] ) ) { + $parsing_rules['key'][ $block_name ] = $this->array_merge_recursive( $parsing_rules['key'][ $block_name ], $rule ); + } else { + $parsing_rules['key'][ $block_name ] = $rule; + } + break; + } + } + } + } + + return $parsing_rules; + } + + /** + * Merge two arrays recursively. + * Unlike `array_merge_recursive()`, this method doesn't change the type of the values. + * + * @since 3.6 + * + * @param array $array1 Array to merge into. + * @param array $array2 Array to merge. + * @return array + */ + protected function array_merge_recursive( array $array1, array $array2 ): array { + foreach ( $array2 as $key => $value ) { + if ( is_array( $value ) && isset( $array1[ $key ] ) && is_array( $array1[ $key ] ) ) { + $array1[ $key ] = $this->array_merge_recursive( $array1[ $key ], $value ); + } else { + $array1[ $key ] = $value; + } + } + + return $array1; + } + + /** + * Registers or translates the strings for an option + * + * @since 2.8 + * + * @param string $context The group in which the strings will be registered. + * @param string $name Option name. + * @param SimpleXMLElement $key XML node. + * @return void + */ + protected function register_or_translate_option( $context, $name, $key ) { + $option_keys = $this->xml_to_array( $key ); + new PLL_Translate_Option( $name, reset( $option_keys ), array( 'context' => $context ) ); + } + + /** + * Recursively transforms xml nodes to an array, ready for PLL_Translate_Option. + * + * @since 2.9 + * @since 3.3 Type-hinted the parameters `$key` and `$arr`. + * @since 3.3 `$arr` is not passed by reference anymore. + * @since 3.3 Added the parameter `$fill_value`. + * + * @param SimpleXMLElement $key XML node. + * @param array $arr Array of option keys to translate. + * @param mixed $fill_value Value to use when filling entries. Default is true. + * @return array + */ + protected function xml_to_array( SimpleXMLElement $key, array $arr = array(), $fill_value = true ) { + $name = $this->get_field_attribute( $key, 'name' ); + + if ( '' === $name ) { + return $arr; + } + + $children = $key->children(); + + if ( count( $children ) ) { + foreach ( $children as $child ) { + if ( ! isset( $arr[ $name ] ) || ! is_array( $arr[ $name ] ) ) { + $arr[ $name ] = array(); + } + + $arr[ $name ] = $this->xml_to_array( $child, $arr[ $name ], $fill_value ); + } + } else { + $arr[ $name ] = $fill_value; // Multiline as in WPML. + } + + return $arr; + } + + /** + * Get the value of an attribute. + * + * @since 3.3 + * + * @param SimpleXMLElement $field A XML node. + * @param string $attribute_name Node of the attribute. + * @return string + */ + private function get_field_attribute( SimpleXMLElement $field, $attribute_name ) { + $attributes = $field->attributes(); + + if ( empty( $attributes ) || ! isset( $attributes[ $attribute_name ] ) ) { + return ''; + } + + return trim( (string) $attributes[ $attribute_name ] ); + } + + /** + * Gets attributes values recursively. + * + * @since 3.6 + * + * @param SimpleXMLElement $field A XML node. + * @return array An array of attributes. + * + * @phpstan-return array + */ + private function get_field_attributes( SimpleXMLElement $field ): array { + $name = $this->get_field_attribute( $field, 'name' ); + + if ( '' === $name ) { + return array(); + } + + $children = $field->children(); + + if ( 0 === $children->count() ) { + return array( $name => true ); + } + + $sub_attributes = array(); + + foreach ( $children as $child ) { + $sub = $this->get_field_attributes( $child ); + + if ( empty( $sub ) ) { + continue; + } + + $sub_attributes[ $name ] = array_merge( $sub_attributes[ $name ] ?? array(), $sub ); + } + + return $sub_attributes; + } + + /** + * Returns all wpml-config.xml files in MU plugins. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_mu_plugin_files() { + if ( ! is_readable( WPMU_PLUGIN_DIR ) || ! is_dir( WPMU_PLUGIN_DIR ) ) { + return array(); + } + + $files = array(); + + // Search for top level wpml-config.xml file. + $file_path = WPMU_PLUGIN_DIR . '/wpml-config.xml'; + + if ( is_readable( $file_path ) ) { + $files['mu-plugins'] = $file_path; + } + + // Search in proxy loaded MU plugins. + foreach ( new DirectoryIterator( WPMU_PLUGIN_DIR ) as $file_info ) { + if ( ! $this->is_dir( $file_info ) ) { + continue; + } + + $file_path = $file_info->getPathname() . '/wpml-config.xml'; + + if ( is_readable( $file_path ) ) { + $files[ 'mu-plugins/' . $file_info->getFilename() ] = $file_path; + } + } + + return $files; + } + + /** + * Returns all wpml-config.xml files in plugins. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_plugin_files() { + $files = array(); + $plugins = array(); + + if ( is_multisite() ) { + // Don't forget sitewide active plugins thanks to Reactorshop http://wordpress.org/support/topic/polylang-and-yoast-seo-plugin/page/2?replies=38#post-4801829. + $sitewide_plugins = get_site_option( 'active_sitewide_plugins', array() ); + + if ( ! empty( $sitewide_plugins ) && is_array( $sitewide_plugins ) ) { + $plugins = array_keys( $sitewide_plugins ); + } + } + + // By-site plugins. + $active_plugins = get_option( 'active_plugins', array() ); + + if ( ! empty( $active_plugins ) && is_array( $active_plugins ) ) { + $plugins = array_merge( $plugins, $active_plugins ); + } + + $plugin_path = trailingslashit( WP_PLUGIN_DIR ) . '%s/wpml-config.xml'; + + foreach ( $plugins as $plugin ) { + if ( ! is_string( $plugin ) || '' === $plugin ) { + continue; + } + + $file_dir = dirname( $plugin ); + $file_path = sprintf( $plugin_path, $file_dir ); + + if ( is_readable( $file_path ) ) { + $files[ "plugins/{$file_dir}" ] = $file_path; + } + } + + return $files; + } + + /** + * Returns all wpml-config.xml files in theme and child theme. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_theme_files() { + $files = array(); + + // Theme. + $template_path = get_template_directory(); + $file_path = "{$template_path}/wpml-config.xml"; + + if ( is_readable( $file_path ) ) { + $files[ 'themes/' . get_template() ] = $file_path; + } + + // Child theme. + $stylesheet_path = get_stylesheet_directory(); + $file_path = "{$stylesheet_path}/wpml-config.xml"; + + if ( $stylesheet_path !== $template_path && is_readable( $file_path ) ) { + $files[ 'themes/' . get_stylesheet() ] = $file_path; + } + + return $files; + } + + /** + * Returns the wpml-config.xml file in Polylang custom directory. + * + * @since 3.3 + * + * @return string[] A context identifier as array key, a file path as array value. + * + * @phpstan-return array + */ + private function get_custom_files() { + $file_path = PLL_LOCAL_DIR . '/wpml-config.xml'; + + if ( ! is_readable( $file_path ) ) { + return array(); + } + + return array( + 'Polylang' => $file_path, + ); + } + + /** + * Tells if the given "file info" object represents a directory. + * This takes care of not triggering a `open_basedir` restriction error when the file symlinks a file that is not in + * `open_basedir`. + * + * @see https://wordpress.org/support/topic/fatal-error-open_basedir-restricton/ + * + * @since 3.5.1 + * + * @param DirectoryIterator $file_info A "file info" object that we know its path (but maybe not its real path) is + * in `open_basedir`. + * @return bool + */ + private function is_dir( DirectoryIterator $file_info ): bool { + if ( $file_info->isDot() ) { + return false; + } + + if ( $file_info->getPathname() === $file_info->getRealPath() ) { + // Not a symlink: not going to trigger a `open_basedir` restriction error. + return $file_info->isDir(); + } + + /* + * Symlink: make sure the file's real path is in `open_basedir` before checking it is a dir. + * Which means that the `open_basedir` check is done only for symlinked files. + */ + return $this->is_allowed_dir( $file_info->getRealPath() ) && $file_info->isDir(); + } + + /** + * Checks whether access to a given directory is allowed. + * This takes into account the PHP `open_basedir` restrictions, so that Polylang does not try to access directories + * it is not allowed to. + * + * Inspired by `WP_Automatic_Updater::is_allowed_dir()` and `wp-includes/ID3/getid3.php`. + * + * @since 3.5.1 + * + * @param string $dir The directory to check. + * @return bool True if access to the directory is allowed, false otherwise. + */ + private function is_allowed_dir( string $dir ): bool { + $dir = trim( $dir ); + + if ( '' === $dir ) { + return false; + } + + $open_basedir_paths = $this->get_open_basedir_paths(); + + if ( empty( $open_basedir_paths ) ) { + return true; + } + + $dir = $this->normalize_path( $dir ); + + foreach ( $open_basedir_paths as $path ) { + if ( str_starts_with( $dir, $path ) ) { + return true; + } + } + + return false; + } + + /** + * Returns the list of paths in `open_basedir`. The purpose is to compare a formatted path to this list. + * Note: all paths are suffixed by `DIRECTORY_SEPARATOR`, even paths to files. + * + * @since 3.5.1 + * + * @return string[] An array of formatted paths. + */ + private function get_open_basedir_paths(): array { + if ( is_array( $this->open_basedir_paths ) ) { + return $this->open_basedir_paths; + } + + $this->open_basedir_paths = array(); + $open_basedir = ini_get( 'open_basedir' ); // Can be `false` or an empty string. + + if ( empty( $open_basedir ) ) { + return $this->open_basedir_paths; + } + + $open_basedir_list = explode( PATH_SEPARATOR, $open_basedir ); + + foreach ( $open_basedir_list as $basedir ) { + $basedir = trim( $basedir ); + + if ( '' === $basedir ) { + continue; + } + + $this->open_basedir_paths[] = $this->normalize_path( $basedir ); + } + + $this->open_basedir_paths = array_unique( $this->open_basedir_paths ); + + return $this->open_basedir_paths; + } + + /** + * Formats a path for string comparison. + * 1. Slashes and back-slashes are replaced by `DIRECTORY_SEPARATOR`. + * 2. The path is suffixed by `DIRECTORY_SEPARATOR` (even non-directory elements). + * + * @since 3.5.1 + * + * @param string $path A file path. + * @return string + * + * @phpstan-param non-empty-string $path + * @phpstan-return non-empty-string + */ + private function normalize_path( string $path ): string { + $path = str_replace( array( '/', '\\' ), DIRECTORY_SEPARATOR, $path ); + + if ( substr( $path, -1, 1 ) !== DIRECTORY_SEPARATOR ) { + $path .= DIRECTORY_SEPARATOR; + } + + return $path; + } + + /** + * Adds (or removes) meta names to the list of metas to copy or synchronize. + * + * @since 3.6 + * + * @param string[] $metas The list of meta names to copy or synchronize. + * @param string $xpath Xpath to the meta fields in the xml files. + * @param bool $sync Either sync is enabled or not. + * @return string[] + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + */ + private function filter_metas_to_copy( array $metas, string $xpath, bool $sync ): array { + $parsed_metas = $this->parse_xml_metas( $xpath ); + $metas_to_remove = array(); + + foreach ( $parsed_metas as $name => $parsed_meta ) { + if ( 'copy' === $parsed_meta['action'] || ( ! $sync && in_array( $parsed_meta['action'], array( 'translate', 'copy-once' ), true ) ) ) { + $metas[] = $name; + } else { + $metas_to_remove[] = $name; + } + } + + return array_diff( $metas, $metas_to_remove ); + } + + /** + * Adds meta keys to export. + * + * @since 3.6 + * + * @param array $metas { + * An array containing meta keys to translate. + * Ex: array( + * 'meta_to_translate_1' => 1, + * 'meta_to_translate_2' => 1, + * 'meta_to_translate_3' => array( ... ), + * ) + * } + * @param string $xpath Xpath to the meta fields in the xml files. + * @return array + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + * @phpstan-return array + */ + private function add_metas_to_export( array $metas, string $xpath ) { + $fields = $this->parse_xml_metas( $xpath ); + + foreach ( $fields as $name => $field ) { + if ( 'translate' === $field['action'] ) { + $metas[ $name ] = 1; + } + } + + return $metas; + } + + /** + * Adds encoding of metas. + * + * @since 3.6 + * + * @param string[] $metas The list of encodings for each metas. Meta names are array keys, encodings are array values. + * @param string $xpath Xpath to the meta fields in the xml files. + * @return string[] + * + * @phpstan-param array $metas + * @phpstan-param non-falsy-string $xpath + */ + private function add_metas_encodings( array $metas, string $xpath ): array { + $parsed_metas = $this->parse_xml_metas( $xpath ); + + foreach ( $parsed_metas as $name => $parsed_meta ) { + if ( ! empty( $parsed_meta['encoding'] ) ) { + $metas[ $name ] = $parsed_meta['encoding']; + } + } + + return $metas; + } + + /** + * Parses all xml files for metas. + * Results are cached for each `$xpath`. + * + * @since 3.6 + * + * @param string $xpath Xpath to the meta fields in the xml files. + * @return array + * + * @phpstan-param non-falsy-string $xpath + * @phpstan-return ParsedMetas + */ + private function parse_xml_metas( string $xpath ): array { + if ( isset( $this->parsed_metas[ $xpath ] ) ) { + return $this->parsed_metas[ $xpath ]; + } + + $this->parsed_metas[ $xpath ] = array(); + + foreach ( $this->xmls as $xml ) { + $custom_fields = $xml->xpath( $xpath ); + + if ( ! is_array( $custom_fields ) ) { + continue; + } + + foreach ( $custom_fields as $custom_field ) { + $name = (string) $custom_field; + + if ( empty( $name ) ) { + continue; + } + + $data = array( + 'action' => $this->get_field_attribute( $custom_field, 'action' ), + 'encoding' => $this->get_field_attribute( $custom_field, 'encoding' ), + ); + + $data['encoding'] = 'json' === $data['encoding'] ? 'json' : ''; // Only JSON is supported for now. + + $this->parsed_metas[ $xpath ][ $name ] = $data; + } + } + + return $this->parsed_metas[ $xpath ]; + } +} diff --git a/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php b/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php new file mode 100644 index 0000000000..18fba07174 --- /dev/null +++ b/wp-content/plugins/polylang/modules/wpml/wpml-legacy-api.php @@ -0,0 +1,418 @@ + whether to skip missing translation or not, 0 or 1, defaults to 0 + * orderby => 'id', 'code', 'name', defaults to 'id' + * order => 'ASC' or 'DESC', defaults to 'ASC' + * link_empty_to => link to use when the translation is missing {$lang} is replaced by the language code + * + * List of parameters returned per language: + * + * id => the language id + * active => whether this is the active language or no, 0 or 1 + * native_name => the language name + * missing => whether the translation is missing or not, 0 or 1 + * translated_name => empty, does not exist in Polylang + * language_code => the language code ( slug ) + * country_flag_url => the url of the flag + * url => the url of the translation + * + * @since 1.0 + * + * @param string|array $args optional + * @return array array of arrays per language + */ + function icl_get_languages( $args = '' ) { + $args = wp_parse_args( $args, array( 'skip_missing' => 0, 'orderby' => 'id', 'order' => 'ASC' ) ); + $orderby = ( isset( $args['orderby'] ) && 'code' == $args['orderby'] ) ? 'slug' : ( isset( $args['orderby'] ) && 'name' == $args['orderby'] ? 'name' : 'id' ); + $order = ( ! empty( $args['order'] ) && 'desc' == $args['order'] ) ? 'DESC' : 'ASC'; + + $arr = array(); + + // NB: When 'skip_missing' is false, WPML returns all languages even if there is no content + $languages = PLL()->model->get_languages_list( array( 'hide_empty' => $args['skip_missing'] ) ); + $languages = wp_list_sort( $languages, $orderby, $order ); // Since WP 4.7 + + foreach ( $languages as $lang ) { + // We can find a translation only on frontend once the global $wp_query object has been instantiated + if ( method_exists( PLL()->links, 'get_translation_url' ) && ! empty( $GLOBALS['wp_query'] ) ) { + $url = PLL()->links->get_translation_url( $lang ); + } + + // It seems that WPML does not bother of skip_missing parameter on admin side and before the $wp_query object has been filled + if ( empty( $url ) && ! empty( $args['skip_missing'] ) && ! is_admin() && did_action( 'parse_query' ) ) { + continue; + } + + $arr[ $lang->slug ] = array( + 'id' => $lang->term_id, + 'active' => isset( PLL()->curlang->slug ) && PLL()->curlang->slug == $lang->slug ? 1 : 0, + 'native_name' => $lang->name, + 'missing' => empty( $url ) ? 1 : 0, + 'translated_name' => '', // Does not exist in Polylang + 'language_code' => $lang->slug, + 'country_flag_url' => $lang->get_display_flag_url(), + 'url' => ! empty( $url ) ? $url : + ( empty( $args['link_empty_to'] ) ? PLL()->links->get_home_url( $lang ) : + str_replace( '{$lang}', $lang->slug, $args['link_empty_to'] ) ), + ); + } + + // Apply undocumented WPML filter + $arr = apply_filters( 'icl_ls_languages', $arr ); + + return $arr; + } +} + +if ( ! function_exists( 'icl_link_to_element' ) ) { + /** + * Used for creating language dependent links in themes + * + * @since 1.0 + * @since 2.0 add support for arguments 6 and 7 + * + * @param int $id object id + * @param string $type optional, post type or taxonomy name of the object, defaults to 'post' + * @param string $text optional, the link text. If not specified will produce the name of the element in the current language + * @param array $args optional, an array of arguments to add to the link, defaults to empty + * @param string $anchor optional, the anchor to add to the link, defaults to empty + * @param bool $echo optional, whether to echo the link, defaults to true + * @param bool $return_original_if_missing optional, whether to return a value if the translation is missing + * @return string a language dependent link + */ + function icl_link_to_element( $id, $type = 'post', $text = '', $args = array(), $anchor = '', $echo = true, $return_original_if_missing = true ) { + if ( 'tag' == $type ) { + $type = 'post_tag'; + } + + $pll_type = ( 'post' == $type || pll_is_translated_post_type( $type ) ) ? 'post' : ( 'term' == $type || pll_is_translated_taxonomy( $type ) ? 'term' : false ); + if ( $pll_type && ( $lang = pll_current_language() ) && ( $tr_id = PLL()->model->$pll_type->get_translation( $id, $lang ) ) && ( 'term' === $pll_type || PLL()->model->post->current_user_can_read( $tr_id ) ) ) { + $id = $tr_id; + } elseif ( ! $return_original_if_missing ) { + return ''; + } + + if ( post_type_exists( $type ) ) { + $link = get_permalink( $id ); + if ( empty( $text ) ) { + $text = get_the_title( $id ); + } + } elseif ( taxonomy_exists( $type ) ) { + $link = get_term_link( $id, $type ); + if ( empty( $text ) && ( $term = get_term( $id, $type ) ) && $term instanceof WP_Term ) { + $text = $term->name; + } + } + + if ( empty( $link ) || is_wp_error( $link ) ) { + return ''; + } + + if ( ! empty( $args ) ) { + $link .= ( false === strpos( $link, '?' ) ? '?' : '&' ) . http_build_query( $args ); + } + + if ( ! empty( $anchor ) ) { + $link .= '#' . $anchor; + } + + $link = sprintf( '%s', esc_url( $link ), esc_html( $text ) ); + + if ( $echo ) { + echo $link; // phpcs:ignore WordPress.Security.EscapeOutput + } + + return $link; + } +} + +if ( ! function_exists( 'icl_object_id' ) ) { + /** + * Returns an element’s ID in the current language or in another specified language. + * + * @since 0.9.5 + * + * @param int $element_id Object id. + * @param string $element_type Optional, post type or taxonomy name of the object, defaults to 'post'. + * @param bool $return_original_if_missing Optional, true if Polylang should return the original id if the translation is missing, defaults to false. + * @param string|null $ulanguage_code Optional, language code, defaults to the current language. + * @return int|null The object id of the translation, null if the translation is missing and $return_original_if_missing set to false. + */ + function icl_object_id( $element_id, $element_type = 'post', $return_original_if_missing = false, $ulanguage_code = null ) { + if ( empty( $element_id ) ) { + return null; + } + + $element_id = (int) $element_id; + + if ( 'any' === $element_type ) { + $element_type = get_post_type( $element_id ); + } + + if ( empty( $element_type ) ) { + return null; + } + + if ( empty( $ulanguage_code ) ) { + $ulanguage_code = pll_current_language(); + } + + if ( 'nav_menu' === $element_type ) { + $tr_id = false; + $theme = get_option( 'stylesheet' ); + if ( isset( PLL()->options['nav_menus'][ $theme ] ) ) { + foreach ( PLL()->options['nav_menus'][ $theme ] as $menu ) { + if ( array_search( $element_id, $menu ) && ! empty( $menu[ $ulanguage_code ] ) ) { + $tr_id = $menu[ $ulanguage_code ]; + break; + } + } + } + } elseif ( pll_is_translated_post_type( $element_type ) ) { + $tr_id = PLL()->model->post->get_translation( $element_id, $ulanguage_code ); + } elseif ( pll_is_translated_taxonomy( $element_type ) ) { + $tr_id = PLL()->model->term->get_translation( $element_id, $ulanguage_code ); + } + + if ( ! isset( $tr_id ) ) { + return $element_id; // WPML doesn't honor $return_original_if_missing if the post type or taxonomy is not translated. + } + + if ( empty( $tr_id ) ) { + return $return_original_if_missing ? $element_id : null; + } + + return (int) $tr_id; + } +} + +if ( ! function_exists( 'wpml_object_id_filter' ) ) { + /** + * Undocumented alias of `icl_object_id` introduced in WPML 3.2, used by Yith WooCommerce compare + * + * @since 2.2.4 + * + * @param int $id object id + * @param string $type optional, post type or taxonomy name of the object, defaults to 'post' + * @param bool $return_original_if_missing optional, true if Polylang should return the original id if the translation is missing, defaults to false + * @param string $lang optional, language code, defaults to current language + * @return int|null the object id of the translation, null if the translation is missing and $return_original_if_missing set to false + */ + function wpml_object_id_filter( $id, $type = 'post', $return_original_if_missing = false, $lang = null ) { + return icl_object_id( $id, $type, $return_original_if_missing, $lang ); + } +} + +if ( ! function_exists( 'wpml_get_language_information' ) ) { + /** + * Undocumented function used by the theme Maya + * returns the post language + * + * @see https://wpml.org/forums/topic/canonical-urls-for-wpml-duplicated-posts/#post-52198 for the original WPML code + * + * @since 1.8 + * + * @param null $empty optional, not used + * @param int $post_id optional, post id, defaults to current post + * @return array + */ + function wpml_get_language_information( $empty = null, $post_id = null ) { + if ( empty( $post_id ) ) { + $post_id = get_the_ID(); + } + + // FIXME WPML may return a WP_Error object + return false === ( $lang = PLL()->model->post->get_language( $post_id ) ) ? array() : array( + 'language_code' => $lang->slug, + 'locale' => $lang->locale, + 'text_direction' => (bool) $lang->is_rtl, + 'display_name' => $lang->name, // Seems to be the post language name displayed in the current language, not a feature in Polylang + 'native_name' => $lang->name, + 'different_language' => pll_current_language() !== $lang->slug, + ); + } +} + +if ( ! function_exists( 'icl_register_string' ) ) { + /** + * Registers a string for translation in the "strings translation" panel + * + * The 4th and 5th parameters $allow_empty_value and $source_lang are not used by Polylang. + * + * @since 0.9.3 + * + * @param string $context the group in which the string is registered, defaults to 'polylang' + * @param string $name a unique name for the string + * @param string $string the string to register + * @return void + */ + function icl_register_string( $context, $name, $string ) { + PLL_WPML_Compat::instance()->register_string( $context, $name, $string ); + } +} + +if ( ! function_exists( 'icl_unregister_string' ) ) { + /** + * Removes a string from the "strings translation" panel + * + * @since 1.0.2 + * + * @param string $context the group in which the string is registered, defaults to 'polylang' + * @param string $name a unique name for the string + * @return void + */ + function icl_unregister_string( $context, $name ) { + PLL_WPML_Compat::instance()->unregister_string( $context, $name ); + } +} + +if ( ! function_exists( 'icl_t' ) ) { + /** + * Gets the translated value of a string ( previously registered with icl_register_string or pll_register_string ) + * + * @since 0.9.3 + * @since 1.9.2 argument 3 is optional + * @since 2.0 add support for arguments 4 to 6 + * + * @param string $context the group in which the string is registered + * @param string $name a unique name for the string + * @param string $string the string to translate, optional for strings registered with icl_register_string + * @param bool|null $has_translation optional, not supported in Polylang + * @param bool $bool optional, not used + * @param string|null $lang optional, return the translation in this language, defaults to current language + * @return string the translated string + */ + function icl_t( $context, $name, $string = '', &$has_translation = null, $bool = false, $lang = null ) { + return icl_translate( $context, $name, $string, false, $has_translation, $lang ); + } +} + +if ( ! function_exists( 'icl_translate' ) ) { + /** + * Undocumented function used by NextGen Gallery + * used in PLL_Plugins_Compat for Jetpack with only 3 arguments + * + * @since 1.0.2 + * @since 2.0 add support for arguments 5 and 6, strings are no more automatically registered + * + * @param string $context the group in which the string is registered + * @param string $name a unique name for the string + * @param string $string the string to translate, optional for strings registered with icl_register_string + * @param bool $bool optional, not used + * @param bool|null $has_translation optional, not supported in Polylang + * @param string|null $lang optional, return the translation in this language, defaults to current language + * @return string the translated string + */ + function icl_translate( $context, $name, $string = '', $bool = false, &$has_translation = null, $lang = null ) { + // FIXME WPML can automatically registers the string based on an option + if ( empty( $string ) ) { + $string = PLL_WPML_Compat::instance()->get_string_by_context_and_name( $context, $name ); + } + return empty( $lang ) ? pll__( $string ) : pll_translate_string( $string, $lang ); + } +} + +if ( ! function_exists( 'wpml_get_copied_fields_for_post_edit' ) ) { + /** + * Undocumented function used by Types + * FIXME: tested only with Types + * probably incomplete as Types locks the custom fields for a new post, but not when edited + * This is probably linked to the fact that WPML has always an original post in the default language and not Polylang :) + * + * @since 1.1.2 + * + * @return array + */ + function wpml_get_copied_fields_for_post_edit() { + if ( empty( $_GET['from_post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + return array(); + } + + $arr = array( 'original_post_id' => (int) $_GET['from_post'] ); // phpcs:ignore WordPress.Security.NonceVerification + + // Don't know what WPML does but Polylang does copy all public meta keys by default. + $keys = get_post_custom_keys( $arr['original_post_id'] ); + if ( is_array( $keys ) ) { + foreach ( $keys as $k => $meta_key ) { + if ( is_protected_meta( $meta_key ) ) { + unset( $keys[ $k ] ); + } + } + } + + // Apply our filter and fill the expected output ( see /types/embedded/includes/fields-post.php ) + /** This filter is documented in modules/sync/admin-sync.php */ + $arr['fields'] = array_unique( apply_filters( 'pll_copy_post_metas', empty( $keys ) ? array() : $keys, false ) ); + return $arr; + } +} + +if ( ! function_exists( 'icl_get_default_language' ) ) { + /** + * Undocumented function used by Warp 6 by Yootheme + * + * @since 1.0.5 + * + * @return string default language code + */ + function icl_get_default_language() { + return pll_default_language(); + } +} + +if ( ! function_exists( 'wpml_get_default_language' ) ) { + /** + * Undocumented function reported to be used by Table Rate Shipping for WooCommerce + * + * @see https://wordpress.org/support/topic/add-wpml-compatibility-function + * + * @since 1.8.2 + * + * @return string default language code + */ + function wpml_get_default_language() { + return pll_default_language(); + } +} + +if ( ! function_exists( 'icl_get_current_language' ) ) { + /** + * Undocumented function used by Ultimate Member + * + * @since 2.2.4 + * + * @return string Current language code + */ + function icl_get_current_language() { + return pll_current_language(); + } +} diff --git a/wp-content/plugins/polylang/polylang.php b/wp-content/plugins/polylang/polylang.php new file mode 100644 index 0000000000..ca076099ab --- /dev/null +++ b/wp-content/plugins/polylang/polylang.php @@ -0,0 +1,78 @@ +. + */ + +if ( ! defined( 'ABSPATH' ) ) { + exit; // Don't access directly. +} + +if ( defined( 'POLYLANG_VERSION' ) ) { + // The user is attempting to activate a second plugin instance, typically Polylang and Polylang Pro. + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + require_once ABSPATH . 'wp-includes/pluggable.php'; + if ( is_plugin_active( plugin_basename( __FILE__ ) ) ) { + deactivate_plugins( plugin_basename( __FILE__ ) ); // Deactivate this plugin. + // WP does not allow us to send a custom meaningful message, so just tell the plugin has been deactivated. + wp_safe_redirect( add_query_arg( 'deactivate', 'true', remove_query_arg( 'activate' ) ) ); + exit; + } +} else { + // Go on loading the plugin + define( 'POLYLANG_VERSION', '3.6.5' ); + define( 'PLL_MIN_WP_VERSION', '6.2' ); + define( 'PLL_MIN_PHP_VERSION', '7.0' ); + + define( 'POLYLANG_FILE', __FILE__ ); + define( 'POLYLANG_DIR', __DIR__ ); + + // Whether we are using Polylang or Polylang Pro, get the filename of the plugin in use. + if ( ! defined( 'POLYLANG_ROOT_FILE' ) ) { + define( 'POLYLANG_ROOT_FILE', __FILE__ ); + } + + if ( ! defined( 'POLYLANG_BASENAME' ) ) { + define( 'POLYLANG_BASENAME', plugin_basename( __FILE__ ) ); // Plugin name as known by WP. + require __DIR__ . '/vendor/autoload.php'; + } + + define( 'POLYLANG', ucwords( str_replace( '-', ' ', dirname( POLYLANG_BASENAME ) ) ) ); + + if ( empty( $_GET['deactivate-polylang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + new Polylang(); + } +} diff --git a/wp-content/plugins/polylang/readme.txt b/wp-content/plugins/polylang/readme.txt new file mode 100644 index 0000000000..7f02bcd007 --- /dev/null +++ b/wp-content/plugins/polylang/readme.txt @@ -0,0 +1,167 @@ +=== Polylang === +Contributors: Chouby, manooweb, raaaahman, marianne38, sebastienserre, greglone, hugod +Donate link: https://polylang.pro +Tags: multilingual, translate, translation, language, localization +Requires at least: 6.2 +Tested up to: 6.7 +Requires PHP: 7.0 +Stable tag: 3.6.5 +License: GPLv3 or later +License URI: https://www.gnu.org/licenses/gpl-3.0.html + +Go multilingual in a simple and efficient way. Keep writing posts and taxonomy terms as usual while defining their languages all at once. + +== Description == + +With Polylang fully integrated to WordPress and using only its built-in core features (taxonomies), keep steady performances on your site and create a multilingual site featuring from just one extra language to 10 or more depending on your needs. There is no limit in the number of languages added and WordPress’ language packs are automatically downloaded when ready. + += Features = + +Depending on the type of site you have built or are planning to build, a combination of plugins from the list below might be of interest. +All plugins include a wizard allowing to setup them in just a few clicks. + +### Polylang + +Polylang and [Polylang Pro](https://polylang.pro) share the same core providing features such as: + +* Translating posts, pages, media, categories, post tags, custom post types and taxonomies, RSS feeds; RTL scripts are supported. +* The language is either set by the language code in URL, or you can use a different sub-domain or domain per language. +* Automatic copy of categories, post tags and other metas when creating a new post or page translation. +* Translating classic menus and classic widgets. Also accessible with [Site Editor Classic Features](https://wordpress.org/plugins/fse-classic/) in block themes. +* Customizable language switcher available as a classic widget or a classic navigation menu item. +* Compatibility with Yoast SEO. + +### Polylang Pro + +Helps optimizing the time spent translating your site with some very useful extra features such as: + +* Better integration in the new Block Editor. +* Language switcher available as a block. +* Language options available in the widget block editor. +* Template parts translatable in the site editor (FSE). +* Duplicate and/or synchronize content across post translations. +* Improved compatibility with other plugins such as [ACF Pro](https://polylang.pro/doc/working-with-acf-pro/). +* Share the same URL slug for posts or terms across languages. +* [Translate URL slugs](https://polylang.pro/doc/translating-urls-slugs/) for categories, author bases, custom post types and more... +* Machine translation with DeepL. +* Export and import of content in XLIFF format for outsourced professional translation. +* **Access to a Premium Support for personalized assistance.** + +### Polylang for WooCommerce + +[Add-on](https://polylang.pro/downloads/polylang-for-woocommerce/) for the compatibility with WooCommerce which provides features such as: + +* Translating WooCommerce pages (shop, check-out, cart, my account), product categories and global attribute terms directly in the WooCommerce interface. +* Translating WooCommerce e-mails and sending them to customers in their language. +* Products metadata synchronization. +* Compatibility with the native WooCommerce CSV import & export tool. +* Compatibility with popular plugins such as WooCommerce Subscriptions, Product Bundles, WooCommerce Bookings, Shipment Tracking and more. +* Ability to use the WooCommerce REST API (available with Polylang Pro). +* **Access to a Premium Support for personalized assistance.** + +Neither of them will allow to do automated translation. + += Our other free plugins = + +* [WPML to Polylang](https://wordpress.org/plugins/wpml-to-polylang/) allows migrating from WPML to Polylang. +* [DynaMo](https://wordpress.org/plugins/dynamo/) speeds up the translation of WordPress for all non-English sites. +* [Site Editor Classic Features](https://wordpress.org/plugins/fse-classic/) allows to use classic widgets (including the Polylang language switcher) and menus in the site editor (FSE). + += Credits = + +Thanks a lot to all translators who [help translating Polylang](https://translate.wordpress.org/projects/wp-plugins/polylang). +Thanks a lot to [Alex Lopez](http://www.alexlopez.rocks/) for the design of the logo. +Most of the flags included with Polylang are coming from [famfamfam](http://famfamfam.com/) and are public domain. +Wherever third party code has been used, credit has been given in the code’s comments. + +== Installation == + +1. Make sure you are using WordPress 6.2 or later and that your server is running PHP 7.0 or later (same requirement as WordPress itself). +1. If you tried other multilingual plugins, deactivate them before activating Polylang, otherwise, you may get unexpected results! +1. Install and activate the plugin as usual from the 'Plugins' menu in WordPress. +1. The [setup wizard](https://polylang.pro/doc/setup-wizard/) is automatically launched to help you get started more easily with Polylang by configuring the main features. + +== Frequently Asked Questions == + += Where to find help ? = + +* First time users should read [Polylang - Getting started](https://polylang.pro/doc-category/getting-started/), which explains the basics and includes a lot of screenshots. +* Read the [documentation](https://polylang.pro/doc/). It includes a [FAQ](https://polylang.pro/doc-category/faq/) and the [documentation for developers](https://polylang.pro/doc-category/developers/). +* Search the [community support forum](https://wordpress.org/search/). You will probably find your answers here. +* Read the sticky posts in the [community support forum](http://wordpress.org/support/plugin/polylang). +* If you still have a problem, open a new thread in the [community support forum](http://wordpress.org/support/plugin/polylang). +* [Polylang Pro and Polylang for WooCommerce](https://polylang.pro) users have access to our premium support through helpdesk. + += Is Polylang compatible with WooCommerce? = + +* You need [Polylang for WooCommerce](https://polylang.pro/downloads/polylang-for-woocommerce/), premium addon described above, which will make both plugins work together. + +== Screenshots == + +1. The Polylang languages admin panel +2. The Strings translations admin panel +3. Multilingual media library +4. The Edit Post screen with the Languages metabox + +== Changelog == + += 3.6.5 (2024-11-05) = + +* Add compatibility with WP 6.7 +* Pro: Prevent infinite loop when the locale fallbacks reference each other +* Pro: Set canResegment attribute to no in XLIFF files +* Fix empty notice displayed if the plugin upgrade notice is set but empty + += 3.6.4 (2024-07-29) = + +* Pro: Fix infinite loop with WP 6.6 when the locale fallbacks include the main locale of a language +* Pro: Prevent saving the main locale among the locale fallbacks of a language +* Pro: Hide the characters consumption graph when the DeepL cost control is deactivated +* Add Yoast SEO social title and social description to the strings translations +* Fix incorrect page on front and page for posts translations when the option is saved with admin language filter active + += 3.6.3 (2024-06-18) = + +* Pro: Fix locale fallback for translations loaded just in time (requires WP 6.6) +* Allow to pass an array as context to icl_register_string() #1497 +* Fix admin bar search menu in WP 6.6 #1496 +* Fix a regression in the usage of the filter pll_flag #1489 + += 3.6.2 (2024-06-03) = + +* Pro: Fix XLIFF files not correctly imported when exported from older version than 3.6 +* Pro: Fix translated categories not assigned to translated post when using machine translation +* Pro: Fix 'lang' param not applied for secondary queries during a REST request +* Pro: Fix newlines for content edited in classic editor and translated with DeepL +* Pro: Fix a conflict with the Stream plugin on multisite + += 3.6.1 (2024-04-09) = + +* Pro: Fix ACF fields not shown after a post was translated with DeepL +* Remove rewrite when registering the language taxonomy #1457 +* Fix search block not filtered when displayed as button only #1459 +* Fix current language not kept when using switch_to_blog() in multisite #1458 + += 3.6 (2024-03-18) = + +* Requires WP 6.2 as minimum version +* Add compatibility with WP 6.5 +* Pro: Add DeepL machine translation for posts +* Pro: Add export and import in XLIFF 2.0/2.1 formats +* Pro: Improve translator comments in exported PO files +* Pro: Allow to export JSON encoded post and term metas in XLIFF files +* Pro: Allow to export block sub-attributes in XLIFF files +* Pro: Add footer notes block to XLIFF files +* Pro: Single files are now exported directly instead of inside a zip +* Pro: Reworked the language switcher navigation block +* Pro: Fix language switcher navigation block justification not aligned with core settings in overlay menu (requires WP 6.5) +* Pro: Fix a race condition which could lead to display a notice to the wrong user +* Pro: Fix a conflict with ACF when rewrite rules are flushed with WP-CLI on a multisite +* Pro: Fix import of several metas with same sources but different translations +* Add filter `pll_cookie_args` to filter the Polylang cookie arguments #1406 +* Fix wrong translated post types and taxononies after a `switch_to_blog()` #1415 +* Fix a minor performance issue for the page for posts #1412 +* Fix a JS errors after quick edit. Props @mcguffin #1435, #1444 +* Fix a possible warning in view-translations-post.php #1439 + +See [changelog.txt](https://plugins.svn.wordpress.org/polylang/trunk/changelog.txt) for older changelog diff --git a/wp-content/plugins/polylang/settings/flags.php b/wp-content/plugins/polylang/settings/flags.php new file mode 100644 index 0000000000..239ac50fb2 --- /dev/null +++ b/wp-content/plugins/polylang/settings/flags.php @@ -0,0 +1,276 @@ + __( 'Andorra', 'polylang' ), + 'ae' => __( 'United Arab Emirates', 'polylang' ), + 'af' => __( 'Afghanistan', 'polylang' ), + 'ag' => __( 'Antigua and Barbuda', 'polylang' ), + 'ai' => __( 'Anguilla', 'polylang' ), + 'al' => __( 'Albania', 'polylang' ), + 'am' => __( 'Armenia', 'polylang' ), + 'an' => __( 'Netherlands Antilles', 'polylang' ), + 'ao' => __( 'Angola', 'polylang' ), + 'ar' => __( 'Argentina', 'polylang' ), + 'arab' => __( 'Arab league', 'polylang' ), + 'as' => __( 'American Samoa', 'polylang' ), + 'at' => __( 'Austria', 'polylang' ), + 'au' => __( 'Australia', 'polylang' ), + 'aw' => __( 'Aruba', 'polylang' ), + 'ax' => __( 'Åland Islands', 'polylang' ), + 'az' => __( 'Azerbaijan', 'polylang' ), + 'ba' => __( 'Bosnia and Herzegovina', 'polylang' ), + 'basque' => __( 'Basque Country', 'polylang' ), + 'bb' => __( 'Barbados', 'polylang' ), + 'bd' => __( 'Bangladesh', 'polylang' ), + 'be' => __( 'Belgium', 'polylang' ), + 'bf' => __( 'Burkina Faso', 'polylang' ), + 'bg' => __( 'Bulgaria', 'polylang' ), + 'bh' => __( 'Bahrain', 'polylang' ), + 'bi' => __( 'Burundi', 'polylang' ), + 'bj' => __( 'Benin', 'polylang' ), + 'bm' => __( 'Bermuda', 'polylang' ), + 'bn' => __( 'Brunei', 'polylang' ), + 'bo' => __( 'Bolivia', 'polylang' ), + 'br' => __( 'Brazil', 'polylang' ), + 'bs' => __( 'Bahamas', 'polylang' ), + 'bt' => __( 'Bhutan', 'polylang' ), + 'bw' => __( 'Botswana', 'polylang' ), + 'by' => __( 'Belarus', 'polylang' ), + 'bz' => __( 'Belize', 'polylang' ), + 'ca' => __( 'Canada', 'polylang' ), + 'catalonia' => __( 'Catalonia', 'polylang' ), + 'cc' => __( 'Cocos', 'polylang' ), + 'cd' => __( 'Democratic Republic of the Congo', 'polylang' ), + 'cf' => __( 'Central African Republic', 'polylang' ), + 'cg' => __( 'Congo', 'polylang' ), + 'ch' => __( 'Switzerland', 'polylang' ), + 'ci' => __( 'Ivory Coast', 'polylang' ), + 'ck' => __( 'Cook Islands', 'polylang' ), + 'cl' => __( 'Chile', 'polylang' ), + 'cm' => __( 'Cameroon', 'polylang' ), + 'cn' => __( 'China', 'polylang' ), + 'co' => __( 'Colombia', 'polylang' ), + 'cr' => __( 'Costa Rica', 'polylang' ), + 'cu' => __( 'Cuba', 'polylang' ), + 'cv' => __( 'Cape Verde', 'polylang' ), + 'cx' => __( 'Christmas Island', 'polylang' ), + 'cy' => __( 'Cyprus', 'polylang' ), + 'cz' => __( 'Czech Republic', 'polylang' ), + 'de' => __( 'Germany', 'polylang' ), + 'dj' => __( 'Djibouti', 'polylang' ), + 'dk' => __( 'Denmark', 'polylang' ), + 'dm' => __( 'Dominica', 'polylang' ), + 'do' => __( 'Dominican Republic', 'polylang' ), + 'dz' => __( 'Algeria', 'polylang' ), + 'ec' => __( 'Ecuador', 'polylang' ), + 'ee' => __( 'Estonia', 'polylang' ), + 'eg' => __( 'Egypt', 'polylang' ), + 'eh' => __( 'Western Sahara', 'polylang' ), + 'england' => __( 'England', 'polylang' ), + 'er' => __( 'Eritrea', 'polylang' ), + 'es' => __( 'Spain', 'polylang' ), + 'esperanto' => __( 'Esperanto', 'polylang' ), + 'et' => __( 'Ethiopia', 'polylang' ), + 'fi' => __( 'Finland', 'polylang' ), + 'fj' => __( 'Fiji', 'polylang' ), + 'fk' => __( 'Falkland Islands', 'polylang' ), + 'fm' => __( 'Micronesia', 'polylang' ), + 'fo' => __( 'Faroe Islands', 'polylang' ), + 'fr' => __( 'France', 'polylang' ), + 'ga' => __( 'Gabon', 'polylang' ), + 'galicia' => __( 'Galicia', 'polylang' ), + 'gb' => __( 'United Kingdom', 'polylang' ), + 'gd' => __( 'Grenada', 'polylang' ), + 'ge' => __( 'Georgia', 'polylang' ), + 'gh' => __( 'Ghana', 'polylang' ), + 'gi' => __( 'Gibraltar', 'polylang' ), + 'gl' => __( 'Greenland', 'polylang' ), + 'gm' => __( 'Gambia', 'polylang' ), + 'gn' => __( 'Guinea', 'polylang' ), + 'gp' => __( 'Guadeloupe', 'polylang' ), + 'gq' => __( 'Equatorial Guinea', 'polylang' ), + 'gr' => __( 'Greece', 'polylang' ), + 'gs' => __( 'South Georgia and the South Sandwich Islands', 'polylang' ), + 'gt' => __( 'Guatemala', 'polylang' ), + 'gu' => __( 'Guam', 'polylang' ), + 'gw' => __( 'Guinea-Bissau', 'polylang' ), + 'gy' => __( 'Guyana', 'polylang' ), + 'hk' => __( 'Hong Kong', 'polylang' ), + 'hm' => __( 'Heard Island and McDonald Islands', 'polylang' ), + 'hn' => __( 'Honduras', 'polylang' ), + 'hr' => __( 'Croatia', 'polylang' ), + 'ht' => __( 'Haiti', 'polylang' ), + 'hu' => __( 'Hungary', 'polylang' ), + 'id' => __( 'Indonesia', 'polylang' ), + 'ie' => __( 'Republic of Ireland', 'polylang' ), + 'il' => __( 'Israel', 'polylang' ), + 'in' => __( 'India', 'polylang' ), + 'io' => __( 'British Indian Ocean Territory', 'polylang' ), + 'iq' => __( 'Iraq', 'polylang' ), + 'ir' => __( 'Iran', 'polylang' ), + 'is' => __( 'Iceland', 'polylang' ), + 'it' => __( 'Italy', 'polylang' ), + 'jm' => __( 'Jamaica', 'polylang' ), + 'jo' => __( 'Jordan', 'polylang' ), + 'jp' => __( 'Japan', 'polylang' ), + 'ke' => __( 'Kenya', 'polylang' ), + 'kg' => __( 'Kyrgyzstan', 'polylang' ), + 'kh' => __( 'Cambodia', 'polylang' ), + 'ki' => __( 'Kiribati', 'polylang' ), + 'km' => __( 'Comoros', 'polylang' ), + 'kn' => __( 'Saint Kitts and Nevis', 'polylang' ), + 'kp' => __( 'North Korea', 'polylang' ), + 'kr' => __( 'South Korea', 'polylang' ), + 'kurdistan' => __( 'Kurdistan', 'polylang' ), + 'kw' => __( 'Kuwait', 'polylang' ), + 'ky' => __( 'Cayman Islands', 'polylang' ), + 'kz' => __( 'Kazakhstan', 'polylang' ), + 'la' => __( 'Laos', 'polylang' ), + 'lb' => __( 'Lebanon', 'polylang' ), + 'lc' => __( 'Saint Lucia', 'polylang' ), + 'li' => __( 'Liechtenstein', 'polylang' ), + 'lk' => __( 'Sri Lanka', 'polylang' ), + 'lr' => __( 'Liberia', 'polylang' ), + 'ls' => __( 'Lesotho', 'polylang' ), + 'lt' => __( 'Lithuania', 'polylang' ), + 'lu' => __( 'Luxembourg', 'polylang' ), + 'lv' => __( 'Latvia', 'polylang' ), + 'ly' => __( 'Libya', 'polylang' ), + 'ma' => __( 'Morocco', 'polylang' ), + 'mc' => __( 'Monaco', 'polylang' ), + 'md' => __( 'Moldova', 'polylang' ), + 'me' => __( 'Montenegro', 'polylang' ), + 'mg' => __( 'Madagascar', 'polylang' ), + 'mh' => __( 'Marshall Islands', 'polylang' ), + 'mk' => __( 'North Macedonia', 'polylang' ), + 'ml' => __( 'Mali', 'polylang' ), + 'mm' => __( 'Myanmar', 'polylang' ), + 'mn' => __( 'Mongolia', 'polylang' ), + 'mo' => __( 'Macao', 'polylang' ), + 'mp' => __( 'Northern Mariana Islands', 'polylang' ), + 'mq' => __( 'Martinique', 'polylang' ), + 'mr' => __( 'Mauritania', 'polylang' ), + 'ms' => __( 'Montserrat', 'polylang' ), + 'mt' => __( 'Malta', 'polylang' ), + 'mu' => __( 'Mauritius', 'polylang' ), + 'mv' => __( 'Maldives', 'polylang' ), + 'mw' => __( 'Malawi', 'polylang' ), + 'mx' => __( 'Mexico', 'polylang' ), + 'my' => __( 'Malaysia', 'polylang' ), + 'mz' => __( 'Mozambique', 'polylang' ), + 'na' => __( 'Namibia', 'polylang' ), + 'nc' => __( 'New Caledonia', 'polylang' ), + 'ne' => __( 'Niger', 'polylang' ), + 'nf' => __( 'Norfolk Island', 'polylang' ), + 'ng' => __( 'Nigeria', 'polylang' ), + 'ni' => __( 'Nicaragua', 'polylang' ), + 'nl' => __( 'Netherlands', 'polylang' ), + 'no' => __( 'Norway', 'polylang' ), + 'np' => __( 'Nepal', 'polylang' ), + 'nr' => __( 'Nauru', 'polylang' ), + 'nu' => __( 'Niue', 'polylang' ), + 'nz' => __( 'New Zealand', 'polylang' ), + 'occitania' => __( 'Occitania', 'polylang' ), + 'om' => __( 'Oman', 'polylang' ), + 'pa' => __( 'Panama', 'polylang' ), + 'pe' => __( 'Peru', 'polylang' ), + 'pf' => __( 'French Polynesia', 'polylang' ), + 'pg' => __( 'Papua New Guinea', 'polylang' ), + 'ph' => __( 'Philippines', 'polylang' ), + 'pk' => __( 'Pakistan', 'polylang' ), + 'pl' => __( 'Poland', 'polylang' ), + 'pm' => __( 'Saint Pierre and Miquelon', 'polylang' ), + 'pn' => __( 'Pitcairn', 'polylang' ), + 'pr' => __( 'Puerto Rico', 'polylang' ), + 'ps' => __( 'Palestinian Territory', 'polylang' ), + 'pt' => __( 'Portugal', 'polylang' ), + 'pw' => __( 'Belau', 'polylang' ), + 'py' => __( 'Paraguay', 'polylang' ), + 'qa' => __( 'Qatar', 'polylang' ), + 'quebec' => __( 'Quebec', 'polylang' ), + 'ro' => __( 'Romania', 'polylang' ), + 'rs' => __( 'Serbia', 'polylang' ), + 'ru' => __( 'Russia', 'polylang' ), + 'rw' => __( 'Rwanda', 'polylang' ), + 'sa' => __( 'Saudi Arabia', 'polylang' ), + 'sb' => __( 'Solomon Islands', 'polylang' ), + 'sc' => __( 'Seychelles', 'polylang' ), + 'scotland' => __( 'Scotland', 'polylang' ), + 'sd' => __( 'Sudan', 'polylang' ), + 'se' => __( 'Sweden', 'polylang' ), + 'sg' => __( 'Singapore', 'polylang' ), + 'sh' => __( 'Saint Helena', 'polylang' ), + 'si' => __( 'Slovenia', 'polylang' ), + 'sk' => __( 'Slovakia', 'polylang' ), + 'sl' => __( 'Sierra Leone', 'polylang' ), + 'sm' => __( 'San Marino', 'polylang' ), + 'sn' => __( 'Senegal', 'polylang' ), + 'so' => __( 'Somalia', 'polylang' ), + 'sr' => __( 'Suriname', 'polylang' ), + 'ss' => __( 'South Sudan', 'polylang' ), + 'st' => __( 'São Tomé and Príncipe', 'polylang' ), + 'sv' => __( 'El Salvador', 'polylang' ), + 'sy' => __( 'Syria', 'polylang' ), + 'sz' => __( 'Swaziland', 'polylang' ), + 'tc' => __( 'Turks and Caicos Islands', 'polylang' ), + 'td' => __( 'Chad', 'polylang' ), + 'tf' => __( 'French Southern Territories', 'polylang' ), + 'tg' => __( 'Togo', 'polylang' ), + 'th' => __( 'Thailand', 'polylang' ), + 'tibet' => __( 'Tibet', 'polylang' ), + 'tj' => __( 'Tajikistan', 'polylang' ), + 'tk' => __( 'Tokelau', 'polylang' ), + 'tl' => __( 'Timor-Leste', 'polylang' ), + 'tm' => __( 'Turkmenistan', 'polylang' ), + 'tn' => __( 'Tunisia', 'polylang' ), + 'to' => __( 'Tonga', 'polylang' ), + 'tr' => __( 'Turkey', 'polylang' ), + 'tt' => __( 'Trinidad and Tobago', 'polylang' ), + 'tv' => __( 'Tuvalu', 'polylang' ), + 'tw' => __( 'Taiwan', 'polylang' ), + 'tz' => __( 'Tanzania', 'polylang' ), + 'ua' => __( 'Ukraine', 'polylang' ), + 'ug' => __( 'Uganda', 'polylang' ), + 'us' => __( 'United States', 'polylang' ), + 'uy' => __( 'Uruguay', 'polylang' ), + 'uz' => __( 'Uzbekistan', 'polylang' ), + 'va' => __( 'Vatican', 'polylang' ), + 'vc' => __( 'Saint Vincent and the Grenadines', 'polylang' ), + 've' => __( 'Venezuela', 'polylang' ), + 'veneto' => __( 'Veneto', 'polylang' ), + 'vg' => __( 'British Virgin Islands', 'polylang' ), + 'vi' => __( 'United States Virgin Islands', 'polylang' ), + 'vn' => __( 'Vietnam', 'polylang' ), + 'vu' => __( 'Vanuatu', 'polylang' ), + 'wales' => __( 'Wales', 'polylang' ), + 'wf' => __( 'Wallis and Futuna', 'polylang' ), + 'ws' => __( 'Western Samoa', 'polylang' ), + 'ye' => __( 'Yemen', 'polylang' ), + 'yt' => __( 'Mayotte', 'polylang' ), + 'za' => __( 'South Africa', 'polylang' ), + 'zm' => __( 'Zambia', 'polylang' ), + 'zw' => __( 'Zimbabwe', 'polylang' ), +); + +/** + * Filter the list of predefined flags + * + * @since 1.8 + * + * @param array $flags + */ +return apply_filters( 'pll_predefined_flags', $flags ); diff --git a/wp-content/plugins/polylang/settings/languages.php b/wp-content/plugins/polylang/settings/languages.php new file mode 100644 index 0000000000..3b5a7468ae --- /dev/null +++ b/wp-content/plugins/polylang/settings/languages.php @@ -0,0 +1,1292 @@ + ISO 639-1 language code + * [locale] => WordPress locale + * [name] => name + * [dir] => text direction + * [flag] => flag code + * [w3c] => W3C locale + * [facebook] => Facebook locale + * + * Facebook locales without equivalent WordPress locale: + * 'ay_BO' (Aymara) + * 'bp_IN' (Bhojpuri) + * 'ck_US' (Cherokee) + * 'en_IN' (English India) + * 'gx_GR' (Classical Greek) + * 'ig_NG' (Igbo) + * 'ik_US' (Inupiak) + * 'iu_CA' (Inuktitut) + * 'ja_KS' (Japanese Kansai) + * 'ks_IN' (Cachemiri) + * 'lg_UG' (Ganda) + * 'nd_ZW' (Ndebele) + * 'nr_ZA' (Southern Ndebele) + * 'ns_ZA' (Northern Sotho) + * 'ny_MW' (Chewa) + * 'qc_GT' (Quiché) + * 'qu_PE' (Quechua) + * 'se_NO' (Northern Sami) + * 'ss_SZ' (Swazi) + * 'st_ZA' (Southern Sotho) + * 'tl_ST' (Klingon) + * 'tn_BW' (Tswana) + * 'ts_ZA' (Tsonga) + * 've_ZA' (Venda) + * 'wo_SN' (Wolof) + * 'yi_DE' (Yiddish) + * 'zu_ZA' (Zulu) + * 'zz_TR' (Zazaki) + */ +return array( + 'af' => array( + 'code' => 'af', + 'locale' => 'af', + 'name' => 'Afrikaans', + 'dir' => 'ltr', + 'flag' => 'za', + 'facebook' => 'af_ZA', + ), + 'ak' => array( + 'facebook' => 'ak_GH', + ), + 'am' => array( + 'code' => 'am', + 'locale' => 'am', + 'name' => 'አማርኛ', + 'dir' => 'ltr', + 'flag' => 'et', + 'facebook' => 'am_ET', + ), + 'ar' => array( + 'code' => 'ar', + 'locale' => 'ar', + 'name' => 'العربية', + 'dir' => 'rtl', + 'flag' => 'arab', + 'facebook' => 'ar_AR', + 'deepl' => 'AR', + ), + 'arg' => array( + 'code' => 'an', + 'locale' => 'arg', + 'name' => 'Aragonés', + 'dir' => 'ltr', + 'flag' => 'es', + ), + 'arq' => array( + 'facebook' => 'ar_AR', + ), + 'ary' => array( + 'code' => 'ar', + 'locale' => 'ary', + 'name' => 'العربية المغربية', + 'dir' => 'rtl', + 'flag' => 'ma', + 'facebook' => 'ar_AR', + 'deepl' => 'AR', + ), + 'as' => array( + 'code' => 'as', + 'locale' => 'as', + 'name' => 'অসমীয়া', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'as_IN', + ), + 'az' => array( + 'code' => 'az', + 'locale' => 'az', + 'name' => 'Azərbaycan', + 'dir' => 'ltr', + 'flag' => 'az', + 'facebook' => 'az_AZ', + ), + 'azb' => array( + 'code' => 'az', + 'locale' => 'azb', + 'name' => 'گؤنئی آذربایجان', + 'dir' => 'rtl', + 'flag' => 'az', + ), + 'bel' => array( + 'code' => 'be', + 'locale' => 'bel', + 'name' => 'Беларуская мова', + 'dir' => 'ltr', + 'flag' => 'by', + 'w3c' => 'be', + 'facebook' => 'be_BY', + ), + 'bg_BG' => array( + 'code' => 'bg', + 'locale' => 'bg_BG', + 'name' => 'български', + 'dir' => 'ltr', + 'flag' => 'bg', + 'facebook' => 'bg_BG', + 'deepl' => 'BG', + ), + 'bn_BD' => array( + 'code' => 'bn', + 'locale' => 'bn_BD', + 'name' => 'বাংলা', + 'dir' => 'ltr', + 'flag' => 'bd', + 'facebook' => 'bn_IN', + ), + 'bo' => array( + 'code' => 'bo', + 'locale' => 'bo', + 'name' => 'བོད་ཡིག', + 'dir' => 'ltr', + 'flag' => 'tibet', + ), + 'bre' => array( + 'w3c' => 'br', + 'facebook' => 'br_FR', + ), + 'bs_BA' => array( + 'code' => 'bs', + 'locale' => 'bs_BA', + 'name' => 'Bosanski', + 'dir' => 'ltr', + 'flag' => 'ba', + 'facebook' => 'bs_BA', + ), + 'ca' => array( + 'code' => 'ca', + 'locale' => 'ca', + 'name' => 'Català', + 'dir' => 'ltr', + 'flag' => 'catalonia', + 'facebook' => 'ca_ES', + ), + 'ceb' => array( + 'code' => 'ceb', + 'locale' => 'ceb', + 'name' => 'Cebuano', + 'dir' => 'ltr', + 'flag' => 'ph', + 'facebook' => 'cx_PH', + ), + 'ckb' => array( + 'code' => 'ku', + 'locale' => 'ckb', + 'name' => 'کوردی', + 'dir' => 'rtl', + 'flag' => 'kurdistan', + 'facebook' => 'cb_IQ', + ), + 'co' => array( + 'facebook' => 'co_FR', + ), + 'cs_CZ' => array( + 'code' => 'cs', + 'locale' => 'cs_CZ', + 'name' => 'Čeština', + 'dir' => 'ltr', + 'flag' => 'cz', + 'facebook' => 'cs_CZ', + 'deepl' => 'CS', + ), + 'cy' => array( + 'code' => 'cy', + 'locale' => 'cy', + 'name' => 'Cymraeg', + 'dir' => 'ltr', + 'flag' => 'wales', + 'facebook' => 'cy_GB', + ), + 'da_DK' => array( + 'code' => 'da', + 'locale' => 'da_DK', + 'name' => 'Dansk', + 'dir' => 'ltr', + 'flag' => 'dk', + 'facebook' => 'da_DK', + 'deepl' => 'DA', + ), + 'de_AT' => array( + 'code' => 'de', + 'locale' => 'de_AT', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'at', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_CH' => array( + 'code' => 'de', + 'locale' => 'de_CH', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'ch', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_CH_informal' => array( + 'code' => 'de', + 'locale' => 'de_CH_informal', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'ch', + 'w3c' => 'de-CH', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_DE' => array( + 'code' => 'de', + 'locale' => 'de_DE', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'de', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'de_DE_formal' => array( + 'code' => 'de', + 'locale' => 'de_DE_formal', + 'name' => 'Deutsch', + 'dir' => 'ltr', + 'flag' => 'de', + 'w3c' => 'de-DE', + 'facebook' => 'de_DE', + 'deepl' => 'DE', + ), + 'dsb' => array( + 'code' => 'dsb', + 'locale' => 'dsb', + 'name' => 'Dolnoserbšćina', + 'dir' => 'ltr', + 'flag' => 'de', + ), + 'dzo' => array( + 'code' => 'dz', + 'locale' => 'dzo', + 'name' => 'རྫོང་ཁ', + 'dir' => 'ltr', + 'flag' => 'bt', + 'w3c' => 'dz', + ), + 'el' => array( + 'code' => 'el', + 'locale' => 'el', + 'name' => 'Ελληνικά', + 'dir' => 'ltr', + 'flag' => 'gr', + 'facebook' => 'el_GR', + 'deepl' => 'EL', + ), + 'en_AU' => array( + 'code' => 'en', + 'locale' => 'en_AU', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'au', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_CA' => array( + 'code' => 'en', + 'locale' => 'en_CA', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'ca', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_GB' => array( + 'code' => 'en', + 'locale' => 'en_GB', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'gb', + 'facebook' => 'en_GB', + 'deepl' => 'EN-GB', + ), + 'en_NZ' => array( + 'code' => 'en', + 'locale' => 'en_NZ', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'nz', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_US' => array( + 'code' => 'en', + 'locale' => 'en_US', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'us', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'en_ZA' => array( + 'code' => 'en', + 'locale' => 'en_ZA', + 'name' => 'English', + 'dir' => 'ltr', + 'flag' => 'za', + 'facebook' => 'en_US', + 'deepl' => 'EN-US', + ), + 'eo' => array( + 'code' => 'eo', + 'locale' => 'eo', + 'name' => 'Esperanto', + 'dir' => 'ltr', + 'flag' => 'esperanto', + 'facebook' => 'eo_EO', + ), + 'es_AR' => array( + 'code' => 'es', + 'locale' => 'es_AR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'ar', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_CL' => array( + 'code' => 'es', + 'locale' => 'es_CL', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'cl', + 'facebook' => 'es_CL', + 'deepl' => 'ES', + ), + 'es_CO' => array( + 'code' => 'es', + 'locale' => 'es_CO', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'co', + 'facebook' => 'es_CO', + 'deepl' => 'ES', + ), + 'es_CR' => array( + 'code' => 'es', + 'locale' => 'es_CR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'cr', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_DO' => array( + 'code' => 'es', + 'locale' => 'es_DO', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'do', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_EC' => array( + 'code' => 'es', + 'locale' => 'es_EC', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'ec', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_ES' => array( + 'code' => 'es', + 'locale' => 'es_ES', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'es', + 'facebook' => 'es_ES', + 'deepl' => 'ES', + ), + 'es_GT' => array( + 'code' => 'es', + 'locale' => 'es_GT', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'gt', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_MX' => array( + 'code' => 'es', + 'locale' => 'es_MX', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'mx', + 'facebook' => 'es_MX', + 'deepl' => 'ES', + ), + 'es_PE' => array( + 'code' => 'es', + 'locale' => 'es_PE', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'pe', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_PR' => array( + 'code' => 'es', + 'locale' => 'es_PR', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'pr', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_UY' => array( + 'code' => 'es', + 'locale' => 'es_UY', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 'uy', + 'facebook' => 'es_LA', + 'deepl' => 'ES', + ), + 'es_VE' => array( + 'code' => 'es', + 'locale' => 'es_VE', + 'name' => 'Español', + 'dir' => 'ltr', + 'flag' => 've', + 'facebook' => 'es_VE', + 'deepl' => 'ES', + ), + 'et' => array( + 'code' => 'et', + 'locale' => 'et', + 'name' => 'Eesti', + 'dir' => 'ltr', + 'flag' => 'ee', + 'facebook' => 'et_EE', + 'deepl' => 'ET', + ), + 'eu' => array( + 'code' => 'eu', + 'locale' => 'eu', + 'name' => 'Euskara', + 'dir' => 'ltr', + 'flag' => 'basque', + 'facebook' => 'eu_ES', + ), + 'fa_AF' => array( + 'code' => 'fa', + 'locale' => 'fa_AF', + 'name' => 'فارسی', + 'dir' => 'rtl', + 'flag' => 'af', + 'facebook' => 'fa_IR', + ), + 'fa_IR' => array( + 'code' => 'fa', + 'locale' => 'fa_IR', + 'name' => 'فارسی', + 'dir' => 'rtl', + 'flag' => 'ir', + 'facebook' => 'fa_IR', + ), + 'fi' => array( + 'code' => 'fi', + 'locale' => 'fi', + 'name' => 'Suomi', + 'dir' => 'ltr', + 'flag' => 'fi', + 'facebook' => 'fi_FI', + 'deepl' => 'FI', + ), + 'fo' => array( + 'code' => 'fo', + 'locale' => 'fo', + 'name' => 'Føroyskt', + 'dir' => 'ltr', + 'flag' => 'fo', + 'facebook' => 'fo_FO', + ), + 'fr_BE' => array( + 'code' => 'fr', + 'locale' => 'fr_BE', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'be', + 'facebook' => 'fr_FR', + 'deepl' => 'FR', + ), + 'fr_CA' => array( + 'code' => 'fr', + 'locale' => 'fr_CA', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'quebec', + 'facebook' => 'fr_CA', + 'deepl' => 'FR', + ), + 'fr_FR' => array( + 'code' => 'fr', + 'locale' => 'fr_FR', + 'name' => 'Français', + 'dir' => 'ltr', + 'flag' => 'fr', + 'facebook' => 'fr_FR', + 'deepl' => 'FR', + ), + 'fuc' => array( + 'facebook' => 'ff_NG', + ), + 'fur' => array( + 'code' => 'fur', + 'locale' => 'fur', + 'name' => 'Furlan', + 'dir' => 'ltr', + 'flag' => 'it', + ), + 'fy' => array( + 'code' => 'fy', + 'locale' => 'fy', + 'name' => 'Frysk', + 'dir' => 'ltr', + 'flag' => 'nl', + 'facebook' => 'fy_NL', + ), + 'ga' => array( + 'facebook' => 'ga_IE', + ), + 'gax' => array( + 'facebook' => 'om_ET', + ), + 'gd' => array( + 'code' => 'gd', + 'locale' => 'gd', + 'name' => 'Gàidhlig', + 'dir' => 'ltr', + 'flag' => 'scotland', + ), + 'gl_ES' => array( + 'code' => 'gl', + 'locale' => 'gl_ES', + 'name' => 'Galego', + 'dir' => 'ltr', + 'flag' => 'galicia', + 'facebook' => 'gl_ES', + ), + 'gn' => array( + 'facebook' => 'gn_PY', + ), + 'gu' => array( + 'code' => 'gu', + 'locale' => 'gu', + 'name' => 'ગુજરાતી', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'gu_IN', + ), + 'hat' => array( + 'facebook' => 'ht_HT', + ), + 'hau' => array( + 'facebook' => 'ha_NG', + ), + 'haz' => array( + 'code' => 'haz', + 'locale' => 'haz', + 'name' => 'هزاره گی', + 'dir' => 'rtl', + 'flag' => 'af', + ), + 'he_IL' => array( + 'code' => 'he', + 'locale' => 'he_IL', + 'name' => 'עברית', + 'dir' => 'rtl', + 'flag' => 'il', + 'facebook' => 'he_IL', + ), + 'hi_IN' => array( + 'code' => 'hi', + 'locale' => 'hi_IN', + 'name' => 'हिन्दी', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'hi_IN', + ), + 'hr' => array( + 'code' => 'hr', + 'locale' => 'hr', + 'name' => 'Hrvatski', + 'dir' => 'ltr', + 'flag' => 'hr', + 'facebook' => 'hr_HR', + ), + 'hu_HU' => array( + 'code' => 'hu', + 'locale' => 'hu_HU', + 'name' => 'Magyar', + 'dir' => 'ltr', + 'flag' => 'hu', + 'facebook' => 'hu_HU', + 'deepl' => 'HU', + ), + 'hsb' => array( + 'code' => 'hsb', + 'locale' => 'hsb', + 'name' => 'Hornjoserbšćina', + 'dir' => 'ltr', + 'flag' => 'de', + ), + 'hy' => array( + 'code' => 'hy', + 'locale' => 'hy', + 'name' => 'Հայերեն', + 'dir' => 'ltr', + 'flag' => 'am', + 'facebook' => 'hy_AM', + ), + 'id_ID' => array( + 'code' => 'id', + 'locale' => 'id_ID', + 'name' => 'Bahasa Indonesia', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'id_ID', + 'deepl' => 'ID', + ), + 'ido' => array( + 'w3c' => 'io', + ), + 'is_IS' => array( + 'code' => 'is', + 'locale' => 'is_IS', + 'name' => 'Íslenska', + 'dir' => 'ltr', + 'flag' => 'is', + 'facebook' => 'is_IS', + ), + 'it_IT' => array( + 'code' => 'it', + 'locale' => 'it_IT', + 'name' => 'Italiano', + 'dir' => 'ltr', + 'flag' => 'it', + 'facebook' => 'it_IT', + 'deepl' => 'IT', + ), + 'ja' => array( + 'code' => 'ja', + 'locale' => 'ja', + 'name' => '日本語', + 'dir' => 'ltr', + 'flag' => 'jp', + 'facebook' => 'ja_JP', + 'deepl' => 'JA', + ), + 'jv_ID' => array( + 'code' => 'jv', + 'locale' => 'jv_ID', + 'name' => 'Basa Jawa', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'jv_ID', + ), + 'ka_GE' => array( + 'code' => 'ka', + 'locale' => 'ka_GE', + 'name' => 'ქართული', + 'dir' => 'ltr', + 'flag' => 'ge', + 'facebook' => 'ka_GE', + ), + 'kab' => array( + 'code' => 'kab', + 'locale' => 'kab', + 'name' => 'Taqbaylit', + 'dir' => 'ltr', + 'flag' => 'dz', + ), + 'kin' => array( + 'w3c' => 'rw', + 'facebook' => 'rw_RW', + ), + 'kir' => array( + 'code' => 'ky', + 'locale' => 'kir', + 'name' => 'Кыргызча', + 'dir' => 'ltr', + 'flag' => 'kg', + ), + 'kk' => array( + 'code' => 'kk', + 'locale' => 'kk', + 'name' => 'Қазақ тілі', + 'dir' => 'ltr', + 'flag' => 'kz', + 'facebook' => 'kk_KZ', + ), + 'km' => array( + 'code' => 'km', + 'locale' => 'km', + 'name' => 'ភាសាខ្មែរ', + 'dir' => 'ltr', + 'flag' => 'kh', + 'facebook' => 'km_KH', + ), + 'kn' => array( + 'code' => 'kn', + 'locale' => 'kn', + 'name' => 'ಕನ್ನಡ', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'kn_IN', + ), + 'ko_KR' => array( + 'code' => 'ko', + 'locale' => 'ko_KR', + 'name' => '한국어', + 'dir' => 'ltr', + 'flag' => 'kr', + 'facebook' => 'ko_KR', + 'deepl' => 'KO', + ), + 'ku' => array( + 'facebook' => 'ku_TR', + ), + 'ky_KY' => array( + 'facebook' => 'ky_KG', + ), + 'la' => array( + 'facebook' => 'la_VA', + ), + 'li' => array( + 'facebook' => 'li_NL', + ), + 'lin' => array( + 'facebook' => 'ln_CD', + ), + 'lo' => array( + 'code' => 'lo', + 'locale' => 'lo', + 'name' => 'ພາສາລາວ', + 'dir' => 'ltr', + 'flag' => 'la', + 'facebook' => 'lo_LA', + ), + 'lt_LT' => array( + 'code' => 'lt', + 'locale' => 'lt_LT', + 'name' => 'Lietuviškai', + 'dir' => 'ltr', + 'flag' => 'lt', + 'facebook' => 'lt_LT', + 'deepl' => 'LT', + ), + 'lv' => array( + 'code' => 'lv', + 'locale' => 'lv', + 'name' => 'Latviešu valoda', + 'dir' => 'ltr', + 'flag' => 'lv', + 'facebook' => 'lv_LV', + 'deepl' => 'LV', + ), + 'mg_MG' => array( + 'facebook' => 'mg_MG', + ), + 'mk_MK' => array( + 'code' => 'mk', + 'locale' => 'mk_MK', + 'name' => 'македонски јазик', + 'dir' => 'ltr', + 'flag' => 'mk', + 'facebook' => 'mk_MK', + ), + 'ml_IN' => array( + 'code' => 'ml', + 'locale' => 'ml_IN', + 'name' => 'മലയാളം', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'ml_IN', + ), + 'mlt' => array( + 'facebook' => 'mt_MT', + ), + 'mn' => array( + 'code' => 'mn', + 'locale' => 'mn', + 'name' => 'Монгол хэл', + 'dir' => 'ltr', + 'flag' => 'mn', + 'facebook' => 'mn_MN', + ), + 'mr' => array( + 'code' => 'mr', + 'locale' => 'mr', + 'name' => 'मराठी', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'mr_IN', + ), + 'mri' => array( + 'w3c' => 'mi', + 'facebook' => 'mi_NZ', + ), + 'ms_MY' => array( + 'code' => 'ms', + 'locale' => 'ms_MY', + 'name' => 'Bahasa Melayu', + 'dir' => 'ltr', + 'flag' => 'my', + 'facebook' => 'ms_MY', + ), + 'my_MM' => array( + 'code' => 'my', + 'locale' => 'my_MM', + 'name' => 'ဗမာစာ', + 'dir' => 'ltr', + 'flag' => 'mm', + 'facebook' => 'my_MM', + ), + 'nb_NO' => array( + 'code' => 'nb', + 'locale' => 'nb_NO', + 'name' => 'Norsk Bokmål', + 'dir' => 'ltr', + 'flag' => 'no', + 'facebook' => 'nb_NO', + 'deepl' => 'NB', + ), + 'ne_NP' => array( + 'code' => 'ne', + 'locale' => 'ne_NP', + 'name' => 'नेपाली', + 'dir' => 'ltr', + 'flag' => 'np', + 'facebook' => 'ne_NP', + ), + 'nl_BE' => array( + 'code' => 'nl', + 'locale' => 'nl_BE', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'be', + 'facebook' => 'nl_BE', + 'deepl' => 'NL', + ), + 'nl_NL' => array( + 'code' => 'nl', + 'locale' => 'nl_NL', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'nl', + 'facebook' => 'nl_NL', + 'deepl' => 'NL', + ), + 'nl_NL_formal' => array( + 'code' => 'nl', + 'locale' => 'nl_NL_formal', + 'name' => 'Nederlands', + 'dir' => 'ltr', + 'flag' => 'nl', + 'w3c' => 'nl-NL', + 'facebook' => 'nl_NL', + 'deepl' => 'NL', + ), + 'nn_NO' => array( + 'code' => 'nn', + 'locale' => 'nn_NO', + 'name' => 'Norsk Nynorsk', + 'dir' => 'ltr', + 'flag' => 'no', + 'facebook' => 'nn_NO', + ), + 'oci' => array( + 'code' => 'oc', + 'locale' => 'oci', + 'name' => 'Occitan', + 'dir' => 'ltr', + 'flag' => 'occitania', + 'w3c' => 'oc', + ), + 'ory' => array( + 'facebook' => 'or_IN', + ), + 'pa_IN' => array( + 'code' => 'pa', + 'locale' => 'pa_IN', + 'name' => 'ਪੰਜਾਬੀ', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'pa_IN', + ), + 'pl_PL' => array( + 'code' => 'pl', + 'locale' => 'pl_PL', + 'name' => 'Polski', + 'dir' => 'ltr', + 'flag' => 'pl', + 'facebook' => 'pl_PL', + 'deepl' => 'PL', + ), + 'ps' => array( + 'code' => 'ps', + 'locale' => 'ps', + 'name' => 'پښتو', + 'dir' => 'rtl', + 'flag' => 'af', + 'facebook' => 'ps_AF', + ), + 'pt_AO' => array( + 'code' => 'pt', + 'locale' => 'pt_AO', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'ao', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'pt_BR' => array( + 'code' => 'pt', + 'locale' => 'pt_BR', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'br', + 'facebook' => 'pt_BR', + 'deepl' => 'PT-BR', + ), + 'pt_PT' => array( + 'code' => 'pt', + 'locale' => 'pt_PT', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'pt', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'pt_PT_ao90' => array( + 'code' => 'pt', + 'locale' => 'pt_PT_ao90', + 'name' => 'Português', + 'dir' => 'ltr', + 'flag' => 'pt', + 'facebook' => 'pt_PT', + 'deepl' => 'PT-PT', + ), + 'rhg' => array( + 'code' => 'rhg', + 'locale' => 'rhg', + 'name' => 'Ruáinga', + 'dir' => 'ltr', + 'flag' => 'mm', + ), + 'ro_RO' => array( + 'code' => 'ro', + 'locale' => 'ro_RO', + 'name' => 'Română', + 'dir' => 'ltr', + 'flag' => 'ro', + 'facebook' => 'ro_RO', + 'deepl' => 'RO', + ), + 'roh' => array( + 'w3c' => 'rm', + 'facebook' => 'rm_CH', + ), + 'ru_RU' => array( + 'code' => 'ru', + 'locale' => 'ru_RU', + 'name' => 'Русский', + 'dir' => 'ltr', + 'flag' => 'ru', + 'facebook' => 'ru_RU', + 'deepl' => 'RU', + ), + 'sa_IN' => array( + 'facebook' => 'sa_IN', + ), + 'sah' => array( + 'code' => 'sah', + 'locale' => 'sah', + 'name' => 'Сахалыы', + 'dir' => 'ltr', + 'flag' => 'ru', + ), + 'si_LK' => array( + 'code' => 'si', + 'locale' => 'si_LK', + 'name' => 'සිංහල', + 'dir' => 'ltr', + 'flag' => 'lk', + 'facebook' => 'si_LK', + ), + 'sk_SK' => array( + 'code' => 'sk', + 'locale' => 'sk_SK', + 'name' => 'Slovenčina', + 'dir' => 'ltr', + 'flag' => 'sk', + 'facebook' => 'sk_SK', + 'deepl' => 'SK', + ), + 'skr' => array( + 'code' => 'skr', + 'locale' => 'skr', + 'name' => 'سرائیکی', + 'dir' => 'rtl', + 'flag' => 'pk', + ), + 'sl_SI' => array( + 'code' => 'sl', + 'locale' => 'sl_SI', + 'name' => 'Slovenščina', + 'dir' => 'ltr', + 'flag' => 'si', + 'facebook' => 'sl_SI', + 'deepl' => 'SL', + ), + 'sna' => array( + 'facebook' => 'sn_ZW', + ), + 'snd' => array( + 'code' => 'sd', + 'locale' => 'snd', + 'name' => 'سنڌي', + 'dir' => 'rtl', + 'flag' => 'pk', + ), + 'so_SO' => array( + 'code' => 'so', + 'locale' => 'so_SO', + 'name' => 'Af-Soomaali', + 'dir' => 'ltr', + 'flag' => 'so', + 'facebook' => 'so_SO', + ), + 'sq' => array( + 'code' => 'sq', + 'locale' => 'sq', + 'name' => 'Shqip', + 'dir' => 'ltr', + 'flag' => 'al', + 'facebook' => 'sq_AL', + ), + 'sr_RS' => array( + 'code' => 'sr', + 'locale' => 'sr_RS', + 'name' => 'Српски језик', + 'dir' => 'ltr', + 'flag' => 'rs', + 'facebook' => 'sr_RS', + ), + 'srd' => array( + 'w3c' => 'sc', + 'facebook' => 'sc_IT', + ), + 'su_ID' => array( + 'code' => 'su', + 'locale' => 'su_ID', + 'name' => 'Basa Sunda', + 'dir' => 'ltr', + 'flag' => 'id', + 'facebook' => 'su_ID', + ), + 'sv_SE' => array( + 'code' => 'sv', + 'locale' => 'sv_SE', + 'name' => 'Svenska', + 'dir' => 'ltr', + 'flag' => 'se', + 'facebook' => 'sv_SE', + 'deepl' => 'SV', + ), + 'sw' => array( + 'code' => 'sw', + 'locale' => 'sw', + 'name' => 'Kiswahili', + 'dir' => 'ltr', + 'flag' => 'ke', + 'facebook' => 'sw_KE', + ), + 'syr' => array( + 'facebook' => 'sy_SY', + ), + 'szl' => array( + 'code' => 'szl', + 'locale' => 'szl', + 'name' => 'Ślōnskŏ gŏdka', + 'dir' => 'ltr', + 'flag' => 'pl', + 'facebook' => 'sz_PL', + ), + 'ta_IN' => array( + 'code' => 'ta', + 'locale' => 'ta_IN', + 'name' => 'தமிழ்', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'ta_IN', + ), + 'ta_LK' => array( + 'code' => 'ta', + 'locale' => 'ta_LK', + 'name' => 'தமிழ்', + 'dir' => 'ltr', + 'flag' => 'lk', + 'facebook' => 'ta_IN', + ), + 'tah' => array( + 'code' => 'ty', + 'locale' => 'tah', + 'name' => 'Reo Tahiti', + 'dir' => 'ltr', + 'flag' => 'pf', + ), + 'te' => array( + 'code' => 'te', + 'locale' => 'te', + 'name' => 'తెలుగు', + 'dir' => 'ltr', + 'flag' => 'in', + 'facebook' => 'te_IN', + ), + 'tg' => array( + 'facebook' => 'tg_TJ', + ), + 'th' => array( + 'code' => 'th', + 'locale' => 'th', + 'name' => 'ไทย', + 'dir' => 'ltr', + 'flag' => 'th', + 'facebook' => 'th_TH', + ), + 'tl' => array( + 'code' => 'tl', + 'locale' => 'tl', + 'name' => 'Tagalog', + 'dir' => 'ltr', + 'flag' => 'ph', + 'facebook' => 'tl_PH', + ), + 'tr_TR' => array( + 'code' => 'tr', + 'locale' => 'tr_TR', + 'name' => 'Türkçe', + 'dir' => 'ltr', + 'flag' => 'tr', + 'facebook' => 'tr_TR', + 'deepl' => 'TR', + ), + 'tt_RU' => array( + 'code' => 'tt', + 'locale' => 'tt_RU', + 'name' => 'Татар теле', + 'dir' => 'ltr', + 'flag' => 'ru', + 'facebook' => 'tt_RU', + ), + 'tuk' => array( + 'w3c' => 'tk', + 'facebook' => 'tk_TM', + ), + 'tzm' => array( + 'facebook' => 'tz_MA', + ), + 'ug_CN' => array( + 'code' => 'ug', + 'locale' => 'ug_CN', + 'name' => 'Uyƣurqə', + 'dir' => 'ltr', + 'flag' => 'cn', + ), + 'uk' => array( + 'code' => 'uk', + 'locale' => 'uk', + 'name' => 'Українська', + 'dir' => 'ltr', + 'flag' => 'ua', + 'facebook' => 'uk_UA', + 'deepl' => 'UK', + ), + 'ur' => array( + 'code' => 'ur', + 'locale' => 'ur', + 'name' => 'اردو', + 'dir' => 'rtl', + 'flag' => 'pk', + 'facebook' => 'ur_PK', + ), + 'uz_UZ' => array( + 'code' => 'uz', + 'locale' => 'uz_UZ', + 'name' => 'Oʻzbek', + 'dir' => 'ltr', + 'flag' => 'uz', + 'facebook' => 'uz_UZ', + ), + 'vec' => array( + 'code' => 'vec', + 'locale' => 'vec', + 'name' => 'Vèneto', + 'dir' => 'ltr', + 'flag' => 'veneto', + ), + 'vi' => array( + 'code' => 'vi', + 'locale' => 'vi', + 'name' => 'Tiếng Việt', + 'dir' => 'ltr', + 'flag' => 'vn', + 'facebook' => 'vi_VN', + ), + 'xho' => array( + 'facebook' => 'xh_ZA', + ), + 'yor' => array( + 'facebook' => 'yo_NG', + ), + 'zh_CN' => array( + 'code' => 'zh', + 'locale' => 'zh_CN', + 'name' => '中文 (中国)', + 'dir' => 'ltr', + 'flag' => 'cn', + 'facebook' => 'zh_CN', + 'deepl' => 'ZH', + ), + 'zh_HK' => array( + 'code' => 'zh', + 'locale' => 'zh_HK', + 'name' => '中文 (香港)', + 'dir' => 'ltr', + 'flag' => 'hk', + 'facebook' => 'zh_HK', + 'deepl' => 'ZH', + ), + 'zh_TW' => array( + 'code' => 'zh', + 'locale' => 'zh_TW', + 'name' => '中文 (台灣)', + 'dir' => 'ltr', + 'flag' => 'tw', + 'facebook' => 'zh_TW', + 'deepl' => 'ZH', + ), +); diff --git a/wp-content/plugins/polylang/settings/settings-browser.php b/wp-content/plugins/polylang/settings/settings-browser.php new file mode 100644 index 0000000000..80b4e30852 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-browser.php @@ -0,0 +1,107 @@ +is_available()`, which is used before calling the parent's constructor. + $this->options = &$polylang->options; + + parent::__construct( + $polylang, + array( + 'module' => 'browser', + 'title' => __( 'Detect browser language', 'polylang' ), + 'description' => __( 'When the front page is visited, redirects to itself in the browser preferred language. As this doesn\'t work if it is cached, Polylang will attempt to disable the front page cache for known cache plugins.', 'polylang' ), + 'active_option' => $this->is_available() ? 'browser' : 'none', + ) + ); + + if ( ! class_exists( 'PLL_Xdata_Domain', true ) ) { + add_action( 'admin_print_footer_scripts', array( $this, 'print_js' ) ); + } + } + + /** + * Tells if the option is available + * + * @since 2.0 + * + * @return bool + */ + protected function is_available() { + return ( 3 > $this->options['force_lang'] ) || class_exists( 'PLL_Xdata_Domain', true ); + } + + /** + * Tells if the module is active + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return $this->is_available() ? parent::is_active() : false; + } + + /** + * Displays the javascript to handle dynamically the change in url modifications + * as the preferred browser language is not used when the language is set from different domains + * + * @since 1.8 + * + * @return void + */ + public function print_js() { + wp_enqueue_script( 'jquery' ); + + if ( parent::is_active() && 3 > $this->options['force_lang'] ) { + $func = 'removeClass( "inactive" ).addClass( "active" )'; + $link = sprintf( '%s', $this->action_links['deactivate'] ); + } + else { + $func = 'removeClass( "active" ).addClass( "inactive" )'; + $link = sprintf( '%s', $this->action_links['activate'] ); + } + + $deactivated = sprintf( '%s', $this->action_links['deactivated'] ); + + ?> + + 'cpt', + 'title' => __( 'Custom post types and Taxonomies', 'polylang' ), + 'description' => __( 'Activate languages and translations management for the custom post types and the taxonomies.', 'polylang' ), + ) + ); + + $public_post_types = get_post_types( array( 'public' => true, '_builtin' => false ) ); + /** This filter is documented in include/model.php */ + $this->post_types = array_unique( apply_filters( 'pll_get_post_types', $public_post_types, true ) ); + + /** This filter is documented in include/model.php */ + $programmatically_active_post_types = array_unique( apply_filters( 'pll_get_post_types', array(), false ) ); + $this->disabled_post_types = array_intersect( $programmatically_active_post_types, $this->post_types ); + + $public_taxonomies = get_taxonomies( array( 'public' => true, '_builtin' => false ) ); + $public_taxonomies = array_diff( $public_taxonomies, get_taxonomies( array( '_pll' => true ) ) ); + /** This filter is documented in include/model.php */ + $this->taxonomies = array_unique( apply_filters( 'pll_get_taxonomies', $public_taxonomies, true ) ); + + /** This filter is documented in include/model.php */ + $programmatically_active_taxonomies = array_unique( apply_filters( 'pll_get_taxonomies', array(), false ) ); + $this->disabled_taxonomies = array_intersect( $programmatically_active_taxonomies, $this->taxonomies ); + } + + /** + * Tells if the module is active + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return ! empty( $this->post_types ) || ! empty( $this->taxonomies ); + } + + /** + * Displays the settings form + * + * @since 1.8 + */ + protected function form() { + if ( ! empty( $this->post_types ) ) {?> +

    +
      + post_types as $post_type ) { + $pt = get_post_type_object( $post_type ); + if ( ! empty( $pt ) ) { + $disabled = in_array( $post_type, $this->disabled_post_types ); + printf( + '
    • ', + esc_attr( $post_type ), + checked( $disabled || in_array( $post_type, $this->options['post_types'], true ), true, false ), + disabled( $disabled, true, false ), + esc_html( + sprintf( + /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */ + _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ), + $pt->labels->name, + $pt->name + ) + ) + ); + } + } + ?> +
    +

    + taxonomies ) ) { + ?> +

    +
      + taxonomies as $taxonomy ) { + $tax = get_taxonomy( $taxonomy ); + if ( ! empty( $tax ) ) { + $disabled = in_array( $taxonomy, $this->disabled_taxonomies ); + printf( + '
    • ', + esc_attr( $taxonomy ), + checked( $disabled || in_array( $taxonomy, $this->options['taxonomies'], true ), true, false ), + disabled( $disabled, true, false ), + esc_html( + sprintf( + /* translators: 1 is a post type or taxonomy label, 2 is a post type or taxonomy key. */ + _x( '%1$s (%2$s)', 'content type setting choice', 'polylang' ), + $tax->labels->name, + $tax->name + ) + ) + ); + } + } + ?> +
    +

    + 'licenses', + 'title' => __( 'License keys', 'polylang' ), + 'description' => __( 'Manage licenses for Polylang Pro and add-ons.', 'polylang' ), + ) + ); + + $this->buttons['cancel'] = sprintf( '', __( 'Close', 'polylang' ) ); + + $this->items = apply_filters( 'pll_settings_licenses', array() ); + + add_action( 'wp_ajax_pll_deactivate_license', array( $this, 'deactivate_license' ) ); + } + + /** + * Tells if the module is active + * + * @since 1.9 + * + * @return bool + */ + public function is_active() { + return ! empty( $this->items ); + } + + /** + * Displays the settings form + * + * @since 1.9 + */ + protected function form() { + if ( ! empty( $this->items ) ) { ?> + + items as $item ) { + echo $this->get_row( $item ); // phpcs:ignore WordPress.Security.EscapeOutput + } + ?> +
    + get_form_field(); + } + + /** + * Ajax method to save the license keys and activate the licenses at the same time + * Overrides parent's method + * + * @since 1.9 + */ + public function save_options() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] && ! empty( $_POST['licenses'] ) ) { + $x = new WP_Ajax_Response(); + foreach ( $this->items as $item ) { + if ( ! empty( $_POST['licenses'][ $item->id ] ) ) { + $updated_item = $item->activate_license( sanitize_key( $_POST['licenses'][ $item->id ] ) ); + $x->Add( array( 'what' => 'license-update', 'data' => $item->id, 'supplemental' => array( 'html' => $this->get_row( $updated_item ) ) ) ); + } + } + + // Updated message + pll_add_notice( new WP_Error( 'settings_updated', __( 'Settings saved.', 'polylang' ), 'success' ) ); + ob_start(); + settings_errors( 'polylang' ); + $x->Add( array( 'what' => 'success', 'data' => ob_get_clean() ) ); + $x->send(); + } + } + + /** + * Ajax method to deactivate a license + * + * @since 1.9 + * + * @return void + */ + public function deactivate_license() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( ! isset( $_POST['id'] ) ) { + wp_die( 0 ); + } + + $id = substr( sanitize_text_field( wp_unslash( $_POST['id'] ) ), 11 ); + wp_send_json( + array( + 'id' => $id, + 'html' => $this->get_row( $this->items[ $id ]->deactivate_license() ), + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/settings-media.php b/wp-content/plugins/polylang/settings/settings-media.php new file mode 100644 index 0000000000..1f7ea61592 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-media.php @@ -0,0 +1,37 @@ + 'media', + 'title' => __( 'Media', 'polylang' ), + 'description' => __( 'Activate languages and translations for media only if you need to translate the text attached to the media: the title, the alternative text, the caption, the description... Note that the file is not duplicated.', 'polylang' ), + 'active_option' => 'media_support', + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/settings-module.php b/wp-content/plugins/polylang/settings/settings-module.php new file mode 100644 index 0000000000..1201559b53 --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-module.php @@ -0,0 +1,379 @@ +options = &$polylang->options; + $this->model = &$polylang->model; + $this->links_model = &$polylang->links_model; + + $args = wp_parse_args( + $args, + array( + 'title' => '', + 'description' => '', + 'active_option' => 'none', + ) + ); + + if ( empty( $args['active_option'] ) ) { + // Backward compatibility. + $args['active_option'] = 'none'; + } + + foreach ( $args as $prop => $value ) { + $this->$prop = $value; + } + + // All possible action links, even if not always a link ;-) + $this->action_links = array( + 'configure' => sprintf( + '%s', + esc_attr__( 'Configure this module', 'polylang' ), + '#', + esc_html__( 'Settings', 'polylang' ) + ), + 'deactivate' => sprintf( + '%s', + esc_attr__( 'Deactivate this module', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=deactivate&noheader=true&module=' . $this->module, 'pll_deactivate' ) ), + esc_html__( 'Deactivate', 'polylang' ) + ), + 'activate' => sprintf( + '%s', + esc_attr__( 'Activate this module', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&tab=modules&pll_action=activate&noheader=true&module=' . $this->module, 'pll_activate' ) ), + esc_html__( 'Activate', 'polylang' ) + ), + 'activated' => esc_html__( 'Activated', 'polylang' ), + 'deactivated' => esc_html__( 'Deactivated', 'polylang' ), + ); + + $this->buttons = array( + 'cancel' => sprintf( '', esc_html__( 'Cancel', 'polylang' ) ), + 'save' => sprintf( '', esc_html__( 'Save Changes', 'polylang' ) ), + ); + + // Ajax action to save options. + add_action( 'wp_ajax_pll_save_options', array( $this, 'save_options' ) ); + } + + /** + * Tells if the module is active. + * + * @since 1.8 + * + * @return bool + */ + public function is_active() { + return 'none' === $this->active_option || ( 'preview' !== $this->active_option && ! empty( $this->options[ $this->active_option ] ) ); + } + + /** + * Activates the module. + * + * @since 1.8 + * + * @return void + */ + public function activate() { + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $this->options[ $this->active_option ] = true; + update_option( 'polylang', $this->options ); + } + } + + /** + * Deactivates the module. + * + * @since 1.8 + * + * @return void + */ + public function deactivate() { + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $this->options[ $this->active_option ] = false; + update_option( 'polylang', $this->options ); + } + } + + /** + * Protected method to display a configuration form. + * + * @since 1.8 + * + * @return void + */ + protected function form() { + // Child classes can provide a form. + } + + /** + * Public method returning the form if any. + * + * @since 1.8 + * + * @return string + */ + public function get_form() { + if ( ! $this->is_active() ) { + return ''; + } + + // Read the form only once + if ( false === $this->form ) { + ob_start(); + $this->form(); + $this->form = ob_get_clean(); + } + + return $this->form; + } + + /** + * Allows child classes to validate their options before saving. + * + * @since 1.8 + * + * @param array $options Unsanitized options to save. + * @return array Options + */ + protected function update( $options ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return array(); // It's responsibility of the child class to decide what is saved. + } + + /** + * Ajax method to save the options. + * + * @since 1.8 + * + * @return void + */ + public function save_options() { + check_ajax_referer( 'pll_options', '_pll_nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( -1 ); + } + + if ( isset( $_POST['module'] ) && $this->module === $_POST['module'] ) { + // It's up to the child class to decide which options are saved, whether there are errors or not + $post = array_diff_key( $_POST, array_flip( array( 'action', 'module', 'pll_ajax_backend', '_pll_nonce' ) ) ); + $options = $this->update( $post ); + $this->options = array_merge( $this->options, $options ); + update_option( 'polylang', $this->options ); + + // Refresh language cache in case home urls have been modified + $this->model->clean_languages_cache(); + + // Refresh rewrite rules in case rewrite, hide_default, post types or taxonomies options have been modified + // Don't use flush_rewrite_rules as we don't have the right links model and permastruct + delete_option( 'rewrite_rules' ); + + + ob_start(); + + if ( empty( get_settings_errors( 'polylang' ) ) ) { + // Send update message + pll_add_notice( new WP_Error( 'settings_updated', __( 'Settings saved.', 'polylang' ), 'success' ) ); + settings_errors( 'polylang' ); + $x = new WP_Ajax_Response( array( 'what' => 'success', 'data' => ob_get_clean() ) ); + $x->send(); + } else { + // Send error messages + settings_errors( 'polylang' ); + $x = new WP_Ajax_Response( array( 'what' => 'error', 'data' => ob_get_clean() ) ); + $x->send(); + } + } + } + + /** + * Get the row actions. + * + * @since 1.8 + * + * @return string[] + */ + protected function get_actions() { + $actions = array(); + + if ( $this->is_active() && $this->get_form() ) { + $actions[] = 'configure'; + } + + if ( 'none' !== $this->active_option && 'preview' !== $this->active_option ) { + $actions[] = $this->is_active() ? 'deactivate' : 'activate'; + } + + if ( empty( $actions ) ) { + $actions[] = $this->is_active() ? 'activated' : 'deactivated'; + } + + return $actions; + } + + /** + * Get the actions links. + * + * @since 1.8 + * + * @return string[] Action links. + */ + public function get_action_links() { + return array_intersect_key( $this->action_links, array_flip( $this->get_actions() ) ); + } + + /** + * Default upgrade message (to Pro version). + * + * @since 1.9 + * + * @return string + */ + protected function default_upgrade_message() { + return sprintf( + '%s %s', + __( 'To enable this feature, you need Polylang Pro.', 'polylang' ), + 'https://polylang.pro', + __( 'Upgrade now.', 'polylang' ) + ); + } + + /** + * Allows child classes to display an upgrade message. + * + * @since 1.9 + * + * @return string + */ + public function get_upgrade_message() { + return 'preview' === $this->active_option ? $this->default_upgrade_message() : ''; + } + + /** + * Get the buttons. + * + * @since 1.9 + * + * @return string[] An array of html fragment for the buttons. + */ + public function get_buttons() { + return $this->buttons; + } +} diff --git a/wp-content/plugins/polylang/settings/settings-url.php b/wp-content/plugins/polylang/settings/settings-url.php new file mode 100644 index 0000000000..784f35eebf --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings-url.php @@ -0,0 +1,332 @@ + 'url', + 'title' => __( 'URL modifications', 'polylang' ), + 'description' => __( 'Decide how your URLs will look like.', 'polylang' ), + ) + ); + + $this->page_on_front = &$polylang->static_pages->page_on_front; + } + + /** + * Displays the fieldset to choose how the language is set + * + * @since 1.8 + * + * @return void + */ + protected function force_lang() { + ?> +

    + +

    + +

    ' . esc_html( home_url( $this->links_model->using_permalinks ? 'en/my-post/' : '?lang=en&p=1' ) ) . ''; ?>

    + +

    ' . esc_html( str_replace( array( '://', 'www.' ), array( '://en.', '' ), home_url( 'my-post/' ) ) ) . ''; ?>

    + + options['force_lang'] ? '' : 'style="display: none;"'; ?>> + model->get_languages_list() as $lg ) { + $url = isset( $this->options['domains'][ $lg->slug ] ) ? $this->options['domains'][ $lg->slug ] : ( $lg->is_default ? $this->links_model->home : '' ); + printf( + '' . + '', + esc_attr( $lg->slug ), + esc_attr( $lg->name ), + esc_url( $url ) + ); + } + ?> +
    + + + + +

    ' . esc_html( home_url( 'en/' ) ) . ''; ?>

    + +

    ' . esc_html( home_url( 'language/en/' ) ) . ''; ?>

    + + +

    + model->post->get_language( $this->page_on_front ); + /** @var PLL_Language $lang */ + $lang = $lang ? $lang : $this->model->get_default_language(); + printf( + /* translators: %1$s example url when the option is active. %2$s example url when the option is not active */ + esc_html__( 'Example: %1$s instead of %2$s', 'polylang' ), + '' . esc_html( $this->links_model->home_url( $lang ) ) . '', + '' . esc_html( _get_page_link( $this->page_on_front ) ) . '' + ); + ?> +

    + +
    +
    + force_lang(); ?> +
    +
    + +
    +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + hide_default(); ?> +
    + links_model->using_permalinks ) { + ?> +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + rewrite(); ?> +
    + page_on_front ) { + ?> +
    $this->options['force_lang'] ? '' : 'style="display: none;"'; ?>> + redirect_lang(); ?> +
    + +
    + $domain ) { + if ( empty( $domain ) ) { + $lang = $this->model->get_language( $key ); + pll_add_notice( + new WP_Error( + sprintf( 'pll_invalid_domain_%s', $key ), + sprintf( + /* translators: %s is a native language name */ + __( 'Please enter a valid URL for %s.', 'polylang' ), + $lang->name + ) + ) + ); + } + else { + $newoptions['domains'][ $key ] = esc_url_raw( trim( $domain ) ); + } + } + } + + foreach ( array( 'hide_default', 'redirect_lang' ) as $key ) { + $newoptions[ $key ] = isset( $options[ $key ] ) ? 1 : 0; + } + + if ( 3 == $options['force_lang'] ) { + if ( ! class_exists( 'PLL_Xdata_Domain', true ) ) { + $newoptions['browser'] = 0; + } + $newoptions['hide_default'] = 0; + } + + // Check if domains exist + if ( $newoptions['force_lang'] > 1 ) { + $this->check_domains( $newoptions ); + } + + return $newoptions; // Take care to return only validated options + } + + /** + * Check if subdomains or domains are accessible + * + * @since 1.8 + * + * @param array $options new set of options to test + * @return void + */ + protected function check_domains( $options ) { + $options = array_merge( $this->options, $options ); + $model = new PLL_Model( $options ); + $links_model = $model->get_links_model(); + foreach ( $this->model->get_languages_list() as $lang ) { + $url = add_query_arg( 'deactivate-polylang', 1, $links_model->home_url( $lang ) ); + // Don't redefine vip_safe_wp_remote_get() as it has not the same signature as wp_remote_get() + $response = function_exists( 'vip_safe_wp_remote_get' ) ? vip_safe_wp_remote_get( esc_url_raw( $url ) ) : wp_remote_get( esc_url_raw( $url ) ); + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( 200 != $response_code ) { + pll_add_notice( + new WP_Error( + sprintf( 'pll_invalid_domain_%s', $lang->slug ), + sprintf( + /* translators: %s is an url */ + __( 'Polylang was unable to access the %s URL. Please check that the URL is valid.', 'polylang' ), + $url + ) + ) + ); + } + } + } +} diff --git a/wp-content/plugins/polylang/settings/settings.php b/wp-content/plugins/polylang/settings/settings.php new file mode 100644 index 0000000000..fbdb57e3ff --- /dev/null +++ b/wp-content/plugins/polylang/settings/settings.php @@ -0,0 +1,414 @@ +active_tab = 'mlang' === $_GET['page'] ? 'lang' : substr( sanitize_key( $_GET['page'] ), 6 ); // phpcs:ignore WordPress.Security.NonceVerification + } + + PLL_Admin_Strings::init(); + + add_action( 'admin_init', array( $this, 'register_settings_modules' ) ); + + // Adds screen options and the about box in the languages admin panel. + add_action( 'load-toplevel_page_mlang', array( $this, 'load_page' ) ); + add_action( 'load-languages_page_mlang_strings', array( $this, 'load_page_strings' ) ); + + // Saves the per-page value in screen options. + add_filter( 'set_screen_option_pll_lang_per_page', array( $this, 'set_screen_option' ), 10, 3 ); + add_filter( 'set_screen_option_pll_strings_per_page', array( $this, 'set_screen_option' ), 10, 3 ); + } + + /** + * Initializes the modules + * + * @since 1.8 + * + * @return void + */ + public function register_settings_modules() { + $modules = array(); + + if ( $this->model->has_languages() ) { + $modules = array( + 'PLL_Settings_Url', + 'PLL_Settings_Browser', + 'PLL_Settings_Media', + 'PLL_Settings_CPT', + ); + } + + $modules[] = 'PLL_Settings_Licenses'; + + /** + * Filter the list of setting modules + * + * @since 1.8 + * + * @param array $modules the list of module classes + */ + $modules = apply_filters( 'pll_settings_modules', $modules ); + + foreach ( $modules as $key => $class ) { + $key = is_numeric( $key ) ? strtolower( str_replace( 'PLL_Settings_', '', $class ) ) : $key; + $this->modules[ $key ] = new $class( $this ); + } + } + + /** + * Loads the about metabox + * + * @since 0.8 + * + * @return void + */ + public function metabox_about() { + include __DIR__ . '/view-about.php'; + } + + /** + * Adds screen options and the about box in the languages admin panel + * + * @since 0.9.5 + * + * @return void + */ + public function load_page() { + if ( ! defined( 'PLL_DISPLAY_ABOUT' ) || PLL_DISPLAY_ABOUT ) { + add_meta_box( + 'pll-about-box', + __( 'About Polylang', 'polylang' ), + array( $this, 'metabox_about' ), + 'toplevel_page_mlang', + 'normal' + ); + } + + add_screen_option( + 'per_page', + array( + 'label' => __( 'Languages', 'polylang' ), + 'default' => 10, + 'option' => 'pll_lang_per_page', + ) + ); + + add_action( 'admin_notices', array( $this, 'notice_objects_with_no_lang' ) ); + } + + /** + * Adds screen options in the strings translations admin panel + * + * @since 2.1 + * + * @return void + */ + public function load_page_strings() { + add_screen_option( + 'per_page', + array( + 'label' => __( 'Strings translations', 'polylang' ), + 'default' => 10, + 'option' => 'pll_strings_per_page', + ) + ); + } + + /** + * Saves the number of rows in the languages or strings table set by this user. + * + * @since 0.9.5 + * + * @param mixed $screen_option False or value returned by a previous filter, not used. + * @param string $option The name of the option, not used. + * @param int $value The new value of the option to save. + * @return int The new value of the option. + */ + public function set_screen_option( $screen_option, $option, $value ) { + return (int) $value; + } + + /** + * Manages the user input for the languages pages. + * + * @since 1.9 + * + * @param string $action The action name. + * @return void + */ + public function handle_actions( $action ) { + switch ( $action ) { + case 'add': + check_admin_referer( 'add-lang', '_wpnonce_add-lang' ); + $errors = $this->model->add_language( $_POST ); + + if ( is_wp_error( $errors ) ) { + pll_add_notice( $errors ); + } else { + pll_add_notice( new WP_Error( 'pll_languages_created', __( 'Language added.', 'polylang' ), 'success' ) ); + $locale = sanitize_locale_name( $_POST['locale'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + + if ( 'en_US' !== $locale && current_user_can( 'install_languages' ) ) { + // Attempts to install the language pack + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + if ( ! wp_download_language_pack( $locale ) ) { + pll_add_notice( new WP_Error( 'pll_download_mo', __( 'The language was created, but the WordPress language file was not downloaded. Please install it manually.', 'polylang' ), 'warning' ) ); + } + + // Force checking for themes and plugins translations updates + wp_clean_themes_cache(); + wp_clean_plugins_cache(); + } + } + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'delete': + check_admin_referer( 'delete-lang' ); + + if ( ! empty( $_GET['lang'] ) && $this->model->delete_language( (int) $_GET['lang'] ) ) { + pll_add_notice( new WP_Error( 'pll_languages_deleted', __( 'Language deleted.', 'polylang' ), 'success' ) ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'update': + check_admin_referer( 'add-lang', '_wpnonce_add-lang' ); + $errors = $this->model->update_language( $_POST ); + + if ( is_wp_error( $errors ) ) { + pll_add_notice( $errors ); + } else { + pll_add_notice( new WP_Error( 'pll_languages_updated', __( 'Language updated.', 'polylang' ), 'success' ) ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'default-lang': + check_admin_referer( 'default-lang' ); + + if ( $lang = $this->model->get_language( (int) $_GET['lang'] ) ) { + $this->model->update_default_lang( $lang->slug ); + } + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'content-default-lang': + check_admin_referer( 'content-default-lang' ); + + $this->model->set_language_in_mass(); + + self::redirect(); // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + break; + + case 'activate': + check_admin_referer( 'pll_activate' ); + if ( isset( $_GET['module'] ) ) { + $module = sanitize_key( $_GET['module'] ); + if ( isset( $this->modules[ $module ] ) ) { + $this->modules[ $module ]->activate(); + } + } + self::redirect(); + break; + + case 'deactivate': + check_admin_referer( 'pll_deactivate' ); + if ( isset( $_GET['module'] ) ) { + $module = sanitize_key( $_GET['module'] ); + if ( isset( $this->modules[ $module ] ) ) { + $this->modules[ $module ]->deactivate(); + } + } + self::redirect(); + break; + + default: + /** + * Fires when a non default action has been sent to Polylang settings + * + * @since 1.8 + */ + do_action( "mlang_action_$action" ); + break; + } + } + + /** + * Displays the 3 tabs pages: languages, strings translations, settings + * Also manages user input for these pages + * + * @since 0.1 + * + * @return void + */ + public function languages_page() { + switch ( $this->active_tab ) { + case 'lang': + // Prepare the list table of languages + $list_table = new PLL_Table_Languages(); + $list_table->prepare_items( $this->model->get_languages_list() ); + break; + + case 'strings': + $string_table = new PLL_Table_String( $this->model->get_languages_list() ); + $string_table->prepare_items(); + break; + } + + // Handle user input + $action = isset( $_REQUEST['pll_action'] ) ? sanitize_key( $_REQUEST['pll_action'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + if ( 'edit' === $action && ! empty( $_GET['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + // phpcs:ignore WordPress.Security.NonceVerification, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $edit_lang = $this->model->get_language( (int) $_GET['lang'] ); + } else { + $this->handle_actions( $action ); + } + + // Displays the page + include __DIR__ . '/view-languages.php'; + } + + /** + * Enqueues scripts and styles + * + * @return void + */ + public function admin_enqueue_scripts() { + parent::admin_enqueue_scripts(); + + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + wp_enqueue_script( 'pll_admin', plugins_url( '/js/build/admin' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'jquery', 'wp-ajax-response', 'postbox', 'jquery-ui-selectmenu', 'wp-hooks' ), POLYLANG_VERSION, true ); + wp_localize_script( 'pll_admin', 'pll_admin', array( 'dismiss_notice' => esc_html__( 'Dismiss this notice.', 'polylang' ) ) ); + + wp_enqueue_style( 'pll_selectmenu', plugins_url( '/css/build/selectmenu' . $suffix . '.css', POLYLANG_ROOT_FILE ), array(), POLYLANG_VERSION ); + } + + /** + * Displays a notice when there are objects with no language assigned + * + * @since 1.8 + * + * @return void + */ + public function notice_objects_with_no_lang() { + if ( ! empty( $this->options['default_lang'] ) && $this->model->get_objects_with_no_lang( 1 ) ) { + printf( + '

    %s %s

    ', + esc_html__( 'There are posts, pages, categories or tags without language.', 'polylang' ), + esc_url( wp_nonce_url( '?page=mlang&pll_action=content-default-lang&noheader=true', 'content-default-lang' ) ), + esc_html__( 'You can set them all to the default language.', 'polylang' ) + ); + } + } + + /** + * Redirects to language page ( current active tab ) + * saves error messages in a transient for reuse in redirected page + * + * @since 1.5 + * + * @param array $args query arguments to add to the url + * @return void + */ + public static function redirect( $args = array() ) { + $errors = get_settings_errors( 'polylang' ); + if ( ! empty( $errors ) ) { + set_transient( 'settings_errors', $errors, 30 ); + $args['settings-updated'] = 1; + } + // Remove possible 'pll_action' and 'lang' query args from the referer before redirecting + wp_safe_redirect( add_query_arg( $args, remove_query_arg( array( 'pll_action', 'lang' ), wp_get_referer() ) ) ); + exit; + } + + /** + * Get the list of predefined languages + * + * @since 2.3 + * + * @return string[] { + * @type string $code ISO 639-1 language code. + * @type string $locale WordPress locale. + * @type string $name Native language name. + * @type string $dir Text direction: 'ltr' or 'rtl'. + * @type string $flag Flag code, generally the country code. + * @type string $w3c W3C locale. + * @type string $facebook Facebook locale. + * } + */ + public static function get_predefined_languages() { + require_once ABSPATH . 'wp-admin/includes/translation-install.php'; + + $languages = include __DIR__ . '/languages.php'; + $translations = wp_get_available_translations(); + + // Keep only languages with existing WP language pack + // Unless the transient has expired and we don't have an internet connection to refresh it + if ( ! empty( $translations ) ) { + $translations['en_US'] = ''; // Languages packs don't include en_US + $languages = array_intersect_key( $languages, $translations ); + } + + /** + * Filter the list of predefined languages + * + * @since 1.7.10 + * @since 2.3 The languages arrays use associative keys instead of numerical keys + * @see https://github.com/polylang/polylang/blob/2.8.2/settings/languages.php the list of predefined languages + * + * @param array $languages + */ + $languages = apply_filters( 'pll_predefined_languages', $languages ); + + // Keep only languages with all necessary information + foreach ( $languages as $k => $lang ) { + if ( ! isset( $lang['code'], $lang['locale'], $lang['name'], $lang['dir'], $lang['flag'] ) ) { + unset( $languages[ $k ] ); + } + } + + return $languages; + } +} diff --git a/wp-content/plugins/polylang/settings/table-languages.php b/wp-content/plugins/polylang/settings/table-languages.php new file mode 100644 index 0000000000..c27b5b9938 --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-languages.php @@ -0,0 +1,277 @@ + 'Languages', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + } + + /** + * Generates content for a single row of the table. + * + * @since 1.8 + * + * @param PLL_Language $item The language item. + * @return void + */ + public function single_row( $item ) { + /** + * Filter the list of classes assigned a row in the languages list table + * + * @since 1.8 + * + * @param array $classes The list of class names. + * @param PLL_Language $item The language item. + */ + $classes = apply_filters( 'pll_languages_row_classes', array(), $item ); + echo '' : ' class="' . esc_attr( implode( ' ', $classes ) ) . '">' ); + $this->single_row_columns( $item ); + echo ''; + } + + /** + * Displays the item information in a column ( default case ). + * + * @since 0.1 + * + * @param PLL_Language $item The language item. + * @param string $column_name The column name. + * @return string|int + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'locale': + case 'slug': + return esc_html( $item->$column_name ); + + case 'term_group': + return (int) $item->$column_name; + + case 'count': + return $item->get_tax_prop( 'language', $column_name ); + + default: + return $item->$column_name; // Flag. + } + } + + /** + * Displays the item information in the column 'name' + * Displays the edit and delete action links + * + * @since 0.1 + * + * @param PLL_Language $item The language item. + * @return string + */ + public function column_name( $item ) { + return sprintf( + '%s', + esc_attr__( 'Edit this language', 'polylang' ), + esc_url( admin_url( 'admin.php?page=mlang&pll_action=edit&lang=' . $item->term_id ) ), + esc_html( $item->name ) + ); + } + + /** + * Displays the item information in the default language + * Displays the 'make default' action link + * + * @since 1.8 + * + * @param PLL_Language $item The language item. + * @return string + */ + public function column_default_lang( $item ) { + if ( ! $item->is_default ) { + $s = sprintf( + '
    + %3$s +
    ', + esc_attr__( 'Select as default language', 'polylang' ), + wp_nonce_url( '?page=mlang&pll_action=default-lang&noheader=true&lang=' . $item->term_id, 'default-lang' ), + /* translators: accessibility text, %s is a native language name */ + esc_html( sprintf( __( 'Choose %s as default language', 'polylang' ), $item->name ) ) + ); + + /** + * Filters the default language row action in the languages list table. + * + * @since 1.8 + * + * @param string $s The html markup of the action. + * @param PLL_Language $item The language item. + */ + $s = apply_filters( 'pll_default_lang_row_action', $s, $item ); + } else { + $s = sprintf( + '%1$s', + /* translators: accessibility text */ + esc_html__( 'Default language', 'polylang' ) + ); + } + + return $s; + } + + /** + * Gets the list of columns + * + * @since 0.1 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'name' => esc_html__( 'Full name', 'polylang' ), + 'locale' => esc_html__( 'Locale', 'polylang' ), + 'slug' => esc_html__( 'Code', 'polylang' ), + 'default_lang' => sprintf( '%2$s', esc_attr__( 'Default language', 'polylang' ), esc_html__( 'Default language', 'polylang' ) ), + 'term_group' => esc_html__( 'Order', 'polylang' ), + 'flag' => esc_html__( 'Flag', 'polylang' ), + 'count' => esc_html__( 'Posts', 'polylang' ), + ); + } + + /** + * Gets the list of sortable columns + * + * @since 0.1 + * + * @return array + */ + public function get_sortable_columns() { + return array( + 'name' => array( 'name', true ), // sorted by name by default + 'locale' => array( 'locale', false ), + 'slug' => array( 'slug', false ), + 'term_group' => array( 'term_group', false ), + 'count' => array( 'count', false ), + ); + } + + /** + * Gets the name of the default primary column. + * + * @since 2.1 + * + * @return string Name of the default primary column, in this case, 'name'. + */ + protected function get_default_primary_column_name() { + return 'name'; + } + + /** + * Generates and display row actions links for the list table. + * + * @since 1.8 + * + * @param PLL_Language $item The language item being acted upon. + * @param string $column_name Current column name. + * @param string $primary Primary column name. + * @return string The row actions output. + */ + protected function handle_row_actions( $item, $column_name, $primary ) { + if ( $primary !== $column_name ) { + return ''; + } + + $actions = array( + 'edit' => sprintf( + '%s', + esc_attr__( 'Edit this language', 'polylang' ), + esc_url( admin_url( 'admin.php?page=mlang&pll_action=edit&lang=' . $item->term_id ) ), + esc_html__( 'Edit', 'polylang' ) + ), + 'delete' => sprintf( + '%s', + esc_attr__( 'Delete this language and all its associated data', 'polylang' ), + wp_nonce_url( '?page=mlang&pll_action=delete&noheader=true&lang=' . $item->term_id, 'delete-lang' ), + esc_js( __( 'You are about to permanently delete this language. Are you sure?', 'polylang' ) ), + esc_html__( 'Delete', 'polylang' ) + ), + ); + + /** + * Filters the list of row actions in the languages list table. + * + * @since 1.8 + * + * @param array $actions A list of html markup actions. + * @param PLL_Language $item The language item. + */ + $actions = apply_filters( 'pll_languages_row_actions', $actions, $item ); + + return $this->row_actions( $actions ); + } + + /** + * Sorts language items. + * + * @since 0.1 + * + * @param PLL_Language $a The first language to compare. + * @param PLL_Language $b The second language to compare. + * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b. + */ + protected function usort_reorder( $a, $b ) { + $orderby = ! empty( $_GET['orderby'] ) ? sanitize_key( $_GET['orderby'] ) : 'name'; // phpcs:ignore WordPress.Security.NonceVerification + // Determine sort order + if ( is_numeric( $a->$orderby ) ) { + $result = $a->$orderby > $b->$orderby ? 1 : -1; + } else { + $result = strcmp( $a->$orderby, $b->$orderby ); + } + // Send final sort direction to usort. + return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Prepares the list of languages for display. + * + * @since 0.1 + * + * @param PLL_Language[] $data The list of languages. + * @return void + */ + public function prepare_items( $data = array() ) { + $per_page = $this->get_items_per_page( 'pll_lang_per_page' ); + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); + + usort( $data, array( $this, 'usort_reorder' ) ); + + $total_items = count( $data ); + $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page ); + + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => (int) ceil( $total_items / $per_page ), + ) + ); + } +} diff --git a/wp-content/plugins/polylang/settings/table-settings.php b/wp-content/plugins/polylang/settings/table-settings.php new file mode 100644 index 0000000000..e11a89e6b8 --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-settings.php @@ -0,0 +1,194 @@ + 'Settings', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + } + + /** + * Get the table classes for styling + * + * @since 1.8 + */ + protected function get_table_classes() { + return array( 'wp-list-table', 'widefat', 'plugins', 'pll-settings' ); // get the style of the plugins list table + one specific class + } + + /** + * Displays a single row. + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @return void + */ + public function single_row( $item ) { + // Classes to reuse css from the plugins list table + $classes = $item->is_active() ? 'active' : 'inactive'; + if ( $message = $item->get_upgrade_message() ) { + $classes .= ' update'; + } + + // Display the columns + printf( '', esc_attr( $item->module ), esc_attr( $classes ) ); + $this->single_row_columns( $item ); + echo ''; + + // Display an upgrade message if there is any, reusing css from the plugins updates + if ( $message = $item->get_upgrade_message() ) { + printf( + ' + %s + ', + sprintf( '

    %s

    ', $message ) // phpcs:ignore WordPress.Security.EscapeOutput + ); + } + + // The settings if there are + // "inactive" class to reuse css from the plugins list table + if ( $form = $item->get_form() ) { + printf( + ' + + %s + %s +

    + %s +

    + + ', + esc_attr( $item->module ), + esc_html( $item->title ), + $form, // phpcs:ignore + implode( $item->get_buttons() ) // phpcs:ignore + ); + } + } + + /** + * Generates the columns for a single row of the table. + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @return void + */ + protected function single_row_columns( $item ) { + $column_info = $this->get_column_info(); + $columns = $column_info[0]; + $primary = $column_info[3]; + + foreach ( array_keys( $columns ) as $column_name ) { + $classes = "$column_name column-$column_name"; + if ( $primary === $column_name ) { + $classes .= ' column-primary'; + } + + if ( 'cb' == $column_name ) { + echo ''; + echo $this->column_cb( $item ); // phpcs:ignore WordPress.Security.EscapeOutput + echo ''; + } else { + printf( '', esc_attr( $classes ) ); + echo $this->column_default( $item, $column_name ); // phpcs:ignore WordPress.Security.EscapeOutput + echo ''; + } + } + } + + /** + * Displays the item information in a column (default case). + * + * @since 1.8 + * + * @param PLL_Settings_Module $item Settings module item. + * @param string $column_name Column name. + * @return string The column name. + */ + protected function column_default( $item, $column_name ) { + if ( 'plugin-title' == $column_name ) { + return sprintf( '%s', esc_html( $item->title ) ) . $this->row_actions( $item->get_action_links(), true /*always visible*/ ); + } + return $item->$column_name; + } + + /** + * Gets the list of columns. + * + * @since 1.8 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'cb' => '', // For the 4px border inherited from plugins when the module is activated + 'plugin-title' => esc_html__( 'Module', 'polylang' ), // plugin-title for styling + 'description' => esc_html__( 'Description', 'polylang' ), + ); + } + + /** + * Gets the name of the primary column. + * + * @since 1.8 + * + * @return string The name of the primary column. + */ + protected function get_primary_column_name() { + return 'plugin-title'; + } + + /** + * Prepares the list of items for displaying + * + * @since 1.8 + * + * @param PLL_Settings_Module[] $items Array of settings module items. + * @return void + */ + public function prepare_items( $items = array() ) { + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns(), $this->get_primary_column_name() ); + + // Sort rows, lowest priority on top. + usort( + $items, + function ( $a, $b ) { + return $a->priority > $b->priority ? 1 : -1; + } + ); + $this->items = $items; + } + + /** + * Avoids displaying an empty tablenav + * + * @since 2.1 + * + * @param string $which 'top' or 'bottom' + * @return void + */ + protected function display_tablenav( $which ) {} // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable +} diff --git a/wp-content/plugins/polylang/settings/table-string.php b/wp-content/plugins/polylang/settings/table-string.php new file mode 100644 index 0000000000..64ef0c1a06 --- /dev/null +++ b/wp-content/plugins/polylang/settings/table-string.php @@ -0,0 +1,449 @@ + 'Strings translations', // Do not translate ( used for css class ) + 'ajax' => false, + ) + ); + + $this->languages = $languages; + $this->strings = PLL_Admin_Strings::get_strings(); + $this->groups = array_unique( wp_list_pluck( $this->strings, 'context' ) ); + + $this->selected_group = -1; + + if ( ! empty( $_GET['group'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $group = sanitize_text_field( wp_unslash( $_GET['group'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + if ( in_array( $group, $this->groups ) ) { + $this->selected_group = $group; + } + } + + add_action( 'mlang_action_string-translation', array( $this, 'save_translations' ) ); + } + + /** + * Displays the item information in a column (default case). + * + * @since 0.6 + * + * @param array $item Data related to the current string. + * @param string $column_name The current column name. + * @return string + */ + public function column_default( $item, $column_name ) { + return $item[ $column_name ]; + } + + /** + * Displays the checkbox in first column. + * + * @since 1.1 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_cb( $item ) { + return sprintf( + '', + esc_attr( $item['row'] ), + /* translators: accessibility text, %s is a string potentially in any language */ + sprintf( __( 'Select %s', 'polylang' ), format_to_edit( $item['string'] ) ), + empty( $item['icl'] ) ? 'disabled' : '' // Only strings registered with WPML API can be removed. + ); + } + + /** + * Displays the string to translate. + * + * @since 1.0 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_string( $item ) { + return format_to_edit( $item['string'] ); // Don't interpret special chars for the string column. + } + + /** + * Displays the translations to edit. + * + * @since 0.6 + * + * @param array $item Data related to the current string. + * @return string + */ + public function column_translations( $item ) { + $out = ''; + $languages = array(); + + foreach ( $this->languages as $language ) { + $languages[ $language->slug ] = $language->name; + } + + foreach ( $item['translations'] as $key => $translation ) { + $input_type = $item['multiline'] ? + '' : + ''; + $out .= sprintf( + '
    ' . $input_type . '
    ' . "\n", + esc_attr( $key ), + esc_attr( $item['row'] ), + esc_html( $languages[ $key ] ), + format_to_edit( $translation ) // Don't interpret special chars. + ); + } + + return $out; + } + + /** + * Gets the list of columns. + * + * @since 0.6 + * + * @return string[] The list of column titles. + */ + public function get_columns() { + return array( + 'cb' => '', // Checkbox. + 'string' => esc_html__( 'String', 'polylang' ), + 'name' => esc_html__( 'Name', 'polylang' ), + 'context' => esc_html__( 'Group', 'polylang' ), + 'translations' => esc_html__( 'Translations', 'polylang' ), + ); + } + + /** + * Gets the list of sortable columns + * + * @since 0.6 + * + * @return array + */ + public function get_sortable_columns() { + return array( + 'string' => array( 'string', false ), + 'name' => array( 'name', false ), + 'context' => array( 'context', false ), + ); + } + + /** + * Gets the name of the default primary column. + * + * @since 2.1 + * + * @return string Name of the default primary column, in this case, 'string'. + */ + protected function get_default_primary_column_name() { + return 'string'; + } + + /** + * Search for a string in translations. Case insensitive. + * + * @since 2.6 + * + * @param PLL_MO[] $mos An array of PLL_MO objects. + * @param string $s Searched string. + * @return string[] Found strings. + */ + protected function search_in_translations( $mos, $s ) { + $founds = array(); + + foreach ( $mos as $mo ) { + foreach ( wp_list_pluck( $mo->entries, 'translations' ) as $string => $translation ) { + if ( false !== stripos( $translation[0], $s ) ) { + $founds[] = $string; + } + } + } + + return array_unique( $founds ); + } + + /** + * Sorts registered string items. + * + * @since 0.6 + * + * @param array $a The first item to compare. + * @param array $b The second item to compare. + * @return int -1 or 1 if $a is considered to be respectively less than or greater than $b. + */ + protected function usort_reorder( $a, $b ) { + if ( ! empty( $_GET['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + $orderby = sanitize_key( $_GET['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification + if ( isset( $a[ $orderby ], $b[ $orderby ] ) ) { + $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order + return ( empty( $_GET['order'] ) || 'asc' === $_GET['order'] ) ? $result : -$result; // phpcs:ignore WordPress.Security.NonceVerification + } + } + + return 0; + } + + /** + * Prepares the list of registered strings for display. + * + * @since 0.6 + * + * @return void + */ + public function prepare_items() { + // Is admin language filter active? + if ( $lg = get_user_meta( get_current_user_id(), 'pll_filter_content', true ) ) { + $languages = wp_list_filter( $this->languages, array( 'slug' => $lg ) ); + } else { + $languages = $this->languages; + } + + // Load translations + $mo = array(); + foreach ( $languages as $language ) { + $mo[ $language->slug ] = new PLL_MO(); + $mo[ $language->slug ]->import_from_db( $language ); + } + + $data = $this->strings; + + // Filter by selected group + if ( -1 !== $this->selected_group ) { + $data = wp_list_filter( $data, array( 'context' => $this->selected_group ) ); + } + + // Filter by searched string + $s = empty( $_GET['s'] ) ? '' : wp_unslash( $_GET['s'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput + + if ( ! empty( $s ) ) { + // Search in translations + $in_translations = $this->search_in_translations( $mo, $s ); + + foreach ( $data as $key => $row ) { + if ( stripos( $row['name'], $s ) === false && stripos( $row['string'], $s ) === false && ! in_array( $row['string'], $in_translations ) ) { + unset( $data[ $key ] ); + } + } + } + + // Sorting + uasort( $data, array( $this, 'usort_reorder' ) ); + + // Paging + $per_page = $this->get_items_per_page( 'pll_strings_per_page' ); + $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() ); + + $total_items = count( $data ); + $this->items = array_slice( $data, ( $this->get_pagenum() - 1 ) * $per_page, $per_page, true ); + + $this->set_pagination_args( + array( + 'total_items' => $total_items, + 'per_page' => $per_page, + 'total_pages' => (int) ceil( $total_items / $per_page ), + ) + ); + + // Translate strings + // Kept for the end as it is a slow process + foreach ( $languages as $language ) { + foreach ( $this->items as $key => $row ) { + $this->items[ $key ]['translations'][ $language->slug ] = $mo[ $language->slug ]->translate( $row['string'] ); + $this->items[ $key ]['row'] = $key; // Store the row number for convenience + } + } + } + + /** + * Get the list of possible bulk actions. + * + * @since 1.1 + * + * @return string[] Array of bulk actions. + */ + public function get_bulk_actions() { + return array( 'delete' => __( 'Delete', 'polylang' ) ); + } + + /** + * Get the current action selected from the bulk actions dropdown. + * overrides parent function to avoid submit button to trigger bulk actions + * + * @since 1.8 + * + * @return string|false The action name or False if no action was selected + */ + public function current_action() { + return empty( $_POST['submit'] ) ? parent::current_action() : false; // phpcs:ignore WordPress.Security.NonceVerification + } + + /** + * Displays the dropdown list to filter strings per group + * + * @since 1.1 + * + * @param string $which only 'top' is supported + * @return void + */ + public function extra_tablenav( $which ) { + if ( 'top' !== $which ) { + return; + } + + echo '
    '; + printf( + '', + /* translators: accessibility text */ + esc_html__( 'Filter by group', 'polylang' ) + ); + echo '' . "\n"; + + submit_button( __( 'Filter', 'polylang' ), 'button', 'filter_action', false, array( 'id' => 'post-query-submit' ) ); + echo '
    '; + } + + /** + * Saves the strings translations in DB + * Optionally clean the DB + * + * @since 1.9 + * + * @return void + */ + public function save_translations() { + check_admin_referer( 'string-translation', '_wpnonce_string-translation' ); + + if ( ! empty( $_POST['submit'] ) ) { + foreach ( $this->languages as $language ) { + if ( empty( $_POST['translation'][ $language->slug ] ) || ! is_array( $_POST['translation'][ $language->slug ] ) ) { // In case the language filter is active ( thanks to John P. Bloch ) + continue; + } + + $translations = array_map( 'trim', (array) wp_unslash( $_POST['translation'][ $language->slug ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + $mo = new PLL_MO(); + $mo->import_from_db( $language ); + + foreach ( $translations as $key => $translation ) { + /** + * Filter the string translation before it is saved in DB + * Allows to sanitize strings registered with pll_register_string + * + * @since 1.6 + * @since 2.7 The translation passed to the filter is unslashed. + * + * @param string $translation The string translation. + * @param string $name The name as defined in pll_register_string. + * @param string $context The context as defined in pll_register_string. + */ + $translation = apply_filters( 'pll_sanitize_string_translation', $translation, $this->strings[ $key ]['name'], $this->strings[ $key ]['context'] ); + $mo->add_entry( $mo->make_entry( $this->strings[ $key ]['string'], $translation ) ); + } + + // Clean database ( removes all strings which were registered some day but are no more ) + if ( ! empty( $_POST['clean'] ) ) { + $new_mo = new PLL_MO(); + + foreach ( $this->strings as $string ) { + $new_mo->add_entry( $mo->make_entry( $string['string'], $mo->translate( $string['string'] ) ) ); + } + } + + isset( $new_mo ) ? $new_mo->export_to_db( $language ) : $mo->export_to_db( $language ); + } + + pll_add_notice( new WP_Error( 'pll_strings_translations_updated', __( 'Translations updated.', 'polylang' ), 'success' ) ); + + /** + * Fires after the strings translations are saved in DB + * + * @since 1.2 + */ + do_action( 'pll_save_strings_translations' ); + } + + // Unregisters strings registered through WPML API + if ( $this->current_action() === 'delete' && ! empty( $_POST['strings'] ) && function_exists( 'icl_unregister_string' ) ) { + foreach ( array_map( 'sanitize_key', $_POST['strings'] ) as $key ) { + icl_unregister_string( $this->strings[ $key ]['context'], $this->strings[ $key ]['name'] ); + } + } + + // To refresh the page ( possible thanks to the $_GET['noheader']=true ) + $args = array_intersect_key( $_REQUEST, array_flip( array( 's', 'paged', 'group' ) ) ); + if ( ! empty( $_GET['paged'] ) && ! empty( $_POST['submit'] ) ) { + $args['paged'] = (int) $_GET['paged']; // Don't rely on $_REQUEST['paged'] or $_POST['paged']. See #14 + } + if ( ! empty( $args['s'] ) ) { + $args['s'] = urlencode( $args['s'] ); // Searched string needs to be encoded as it comes from $_POST + } + PLL_Settings::redirect( $args ); + } +} diff --git a/wp-content/plugins/polylang/settings/view-about.php b/wp-content/plugins/polylang/settings/view-about.php new file mode 100644 index 0000000000..9bad4e9b88 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-about.php @@ -0,0 +1,30 @@ + +

    + ', + '' + ); + if ( ! defined( 'POLYLANG_PRO' ) ) { + echo ' '; + printf( + /* translators: %1$s is link start tag, %2$s is link end tag. */ + esc_html__( 'Support and extra features are available to %1$sPolylang Pro%2$s users.', 'polylang' ), + '', + '' + ); + } + ?> +

    diff --git a/wp-content/plugins/polylang/settings/view-languages.php b/wp-content/plugins/polylang/settings/view-languages.php new file mode 100644 index 0000000000..d7e5e52aaa --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-languages.php @@ -0,0 +1,35 @@ + +
    +

    + active_tab ) { + case 'lang': // Languages tab + case 'strings': // String translations tab + case 'settings': // Settings tab + include __DIR__ . '/view-tab-' . $this->active_tab . '.php'; + // Intentional fall-through to upgrade to fire the action below. + + default: + /** + * Fires when loading the active Polylang settings tab + * Allows plugins to add their own tab + * + * @since 1.5.1 + */ + do_action( 'pll_settings_active_tab_' . $this->active_tab ); + break; + } + ?> +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-lang.php b/wp-content/plugins/polylang/settings/view-tab-lang.php new file mode 100644 index 0000000000..c2fa7ecb42 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-lang.php @@ -0,0 +1,178 @@ + +
    +
    +
    + display(); + ?> +
    + +
    +
    +
    + +
    +
    + +
    +

    + +
    + + + + + + +
    + + +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->name ) : '' + ); + ?> +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->locale ) : '' + ); + ?> +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->slug ) : '' + ); + ?> +

    +
    + +
    + + %s', + checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, false, false ), + esc_html__( 'left to right', 'polylang' ) + ); + printf( + '', + checked( ! empty( $edit_lang ) && $edit_lang->is_rtl, true, false ), + esc_html__( 'right to left', 'polylang' ) + ); + ?> +

    +
    + +
    + + +

    +
    + +
    + + ', + ! empty( $edit_lang ) ? esc_attr( $edit_lang->term_group ) : '' + ); + ?> +

    +
    + +
    +
    +
    +
    +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-settings.php b/wp-content/plugins/polylang/settings/view-tab-settings.php new file mode 100644 index 0000000000..77571dd448 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-settings.php @@ -0,0 +1,19 @@ + +
    + prepare_items( $this->modules ); + $list_table->display(); + ?> +
    diff --git a/wp-content/plugins/polylang/settings/view-tab-strings.php b/wp-content/plugins/polylang/settings/view-tab-strings.php new file mode 100644 index 0000000000..f303c8f4a0 --- /dev/null +++ b/wp-content/plugins/polylang/settings/view-tab-strings.php @@ -0,0 +1,33 @@ + +
    +
    + + search_box( __( 'Search translations', 'polylang' ), 'translations' ); + wp_nonce_field( 'string-translation', '_wpnonce_string-translation' ); + $string_table->display(); + printf( '
    ', esc_html__( 'Clean strings translation database', 'polylang' ) ); + ?> +

    + +
    +
    + +
    + +
    diff --git a/wp-content/plugins/polylang/uninstall.php b/wp-content/plugins/polylang/uninstall.php new file mode 100644 index 0000000000..c2367fde98 --- /dev/null +++ b/wp-content/plugins/polylang/uninstall.php @@ -0,0 +1,136 @@ +get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { + switch_to_blog( $blog_id ); + $this->uninstall(); + } + restore_current_blog(); + } + else { + $this->uninstall(); + } + } + + /** + * Removes ALL plugin data + * only when the relevant option is active + * + * @since 0.5 + */ + public function uninstall() { + global $wpdb; + + do_action( 'pll_uninstall' ); + + // Need to register the taxonomies + $pll_taxonomies = array( 'language', 'term_language', 'post_translations', 'term_translations' ); + foreach ( $pll_taxonomies as $taxonomy ) { + register_taxonomy( $taxonomy, null, array( 'label' => false, 'public' => false, 'query_var' => false, 'rewrite' => false ) ); + } + + $languages = get_terms( array( 'taxonomy' => 'language', 'hide_empty' => false ) ); + + // Delete users options + delete_metadata( 'user', 0, 'pll_filter_content', '', true ); + delete_metadata( 'user', 0, 'pll_dismissed_notices', '', true ); // Legacy meta. + foreach ( $languages as $lang ) { + delete_metadata( 'user', 0, "description_{$lang->slug}", '', true ); + } + + // Delete menu language switchers + $ids = get_posts( + array( + 'post_type' => 'nav_menu_item', + 'numberposts' => -1, + 'nopaging' => true, + 'fields' => 'ids', + 'meta_key' => '_pll_menu_item', + ) + ); + + foreach ( $ids as $id ) { + wp_delete_post( $id, true ); + } + + /* + * Backward compatibility with Polylang < 3.4. + * Delete the legacy strings translations. + */ + register_post_type( 'polylang_mo', array( 'rewrite' => false, 'query_var' => false ) ); + $ids = get_posts( + array( + 'post_type' => 'polylang_mo', + 'post_status' => 'any', + 'numberposts' => -1, + 'nopaging' => true, + 'fields' => 'ids', + ) + ); + foreach ( $ids as $id ) { + wp_delete_post( $id, true ); + } + + // Delete all what is related to languages and translations + $term_ids = array(); + $tt_ids = array(); + + foreach ( get_terms( array( 'taxonomy' => $pll_taxonomies, 'hide_empty' => false ) ) as $term ) { + $term_ids[] = (int) $term->term_id; + $tt_ids[] = (int) $term->term_taxonomy_id; + } + + if ( ! empty( $term_ids ) ) { + $term_ids = array_unique( $term_ids ); + $wpdb->query( "DELETE FROM {$wpdb->terms} WHERE term_id IN ( " . implode( ',', $term_ids ) . ' )' ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( "DELETE FROM {$wpdb->term_taxonomy} WHERE term_id IN ( " . implode( ',', $term_ids ) . ' )' ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( "DELETE FROM {$wpdb->termmeta} WHERE term_id IN ( " . implode( ',', $term_ids ) . " ) AND meta_key='_pll_strings_translations'" ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + if ( ! empty( $tt_ids ) ) { + $tt_ids = array_unique( $tt_ids ); + $wpdb->query( "DELETE FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ( " . implode( ',', $tt_ids ) . ' )' ); // PHPCS:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + // Delete options + delete_option( 'polylang' ); + delete_option( 'widget_polylang' ); // Automatically created by WP + delete_option( 'polylang_wpml_strings' ); // Strings registered with icl_register_string + delete_option( 'polylang_licenses' ); + delete_option( 'pll_dismissed_notices' ); + + // Delete transients + delete_transient( 'pll_languages_list' ); + } +} + +new PLL_Uninstall(); diff --git a/wp-content/plugins/polylang/vendor/autoload.php b/wp-content/plugins/polylang/vendor/autoload.php new file mode 100644 index 0000000000..4d7c2db8a7 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php b/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000000..51e734a774 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/LICENSE b/wp-content/plugins/polylang/vendor/composer/LICENSE new file mode 100644 index 0000000000..f27399a042 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php b/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000000..e17efd17b5 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_classmap.php @@ -0,0 +1,140 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'PLL_AS3CF' => $baseDir . '/integrations/wp-offload-media/as3cf.php', + 'PLL_Abstract_Sitemaps' => $baseDir . '/modules/sitemaps/abstract-sitemaps.php', + 'PLL_Accept_Language' => $baseDir . '/frontend/accept-language.php', + 'PLL_Accept_Languages_Collection' => $baseDir . '/frontend/accept-languages-collection.php', + 'PLL_Admin' => $baseDir . '/admin/admin.php', + 'PLL_Admin_Base' => $baseDir . '/admin/admin-base.php', + 'PLL_Admin_Block_Editor' => $baseDir . '/admin/admin-block-editor.php', + 'PLL_Admin_Classic_Editor' => $baseDir . '/admin/admin-classic-editor.php', + 'PLL_Admin_Default_Term' => $baseDir . '/admin/admin-default-term.php', + 'PLL_Admin_Filters' => $baseDir . '/admin/admin-filters.php', + 'PLL_Admin_Filters_Columns' => $baseDir . '/admin/admin-filters-columns.php', + 'PLL_Admin_Filters_Media' => $baseDir . '/admin/admin-filters-media.php', + 'PLL_Admin_Filters_Post' => $baseDir . '/admin/admin-filters-post.php', + 'PLL_Admin_Filters_Post_Base' => $baseDir . '/admin/admin-filters-post-base.php', + 'PLL_Admin_Filters_Term' => $baseDir . '/admin/admin-filters-term.php', + 'PLL_Admin_Filters_Widgets_Options' => $baseDir . '/admin/admin-filters-widgets-options.php', + 'PLL_Admin_Links' => $baseDir . '/admin/admin-links.php', + 'PLL_Admin_Model' => $baseDir . '/admin/admin-model.php', + 'PLL_Admin_Nav_Menu' => $baseDir . '/admin/admin-nav-menu.php', + 'PLL_Admin_Notices' => $baseDir . '/admin/admin-notices.php', + 'PLL_Admin_Site_Health' => $baseDir . '/modules/site-health/admin-site-health.php', + 'PLL_Admin_Static_Pages' => $baseDir . '/admin/admin-static-pages.php', + 'PLL_Admin_Strings' => $baseDir . '/admin/admin-strings.php', + 'PLL_Admin_Sync' => $baseDir . '/modules/sync/admin-sync.php', + 'PLL_Aqua_Resizer' => $baseDir . '/integrations/aqua-resizer/aqua-resizer.php', + 'PLL_Base' => $baseDir . '/include/base.php', + 'PLL_CRUD_Posts' => $baseDir . '/include/crud-posts.php', + 'PLL_CRUD_Terms' => $baseDir . '/include/crud-terms.php', + 'PLL_Cache' => $baseDir . '/include/cache.php', + 'PLL_Cache_Compat' => $baseDir . '/integrations/cache/cache-compat.php', + 'PLL_Canonical' => $baseDir . '/frontend/canonical.php', + 'PLL_Cft' => $baseDir . '/integrations/custom-field-template/cft.php', + 'PLL_Choose_Lang' => $baseDir . '/frontend/choose-lang.php', + 'PLL_Choose_Lang_Content' => $baseDir . '/frontend/choose-lang-content.php', + 'PLL_Choose_Lang_Domain' => $baseDir . '/frontend/choose-lang-domain.php', + 'PLL_Choose_Lang_Url' => $baseDir . '/frontend/choose-lang-url.php', + 'PLL_Cookie' => $baseDir . '/include/cookie.php', + 'PLL_Db_Tools' => $baseDir . '/include/db-tools.php', + 'PLL_Domain_Mapping' => $baseDir . '/integrations/domain-mapping/domain-mapping.php', + 'PLL_Duplicate_Post' => $baseDir . '/integrations/duplicate-post/duplicate-post.php', + 'PLL_Featured_Content' => $baseDir . '/integrations/jetpack/featured-content.php', + 'PLL_Filter_REST_Routes' => $baseDir . '/include/filter-rest-routes.php', + 'PLL_Filters' => $baseDir . '/include/filters.php', + 'PLL_Filters_Links' => $baseDir . '/include/filters-links.php', + 'PLL_Filters_Sanitization' => $baseDir . '/include/filters-sanitization.php', + 'PLL_Filters_Widgets_Options' => $baseDir . '/include/filters-widgets-options.php', + 'PLL_Frontend' => $baseDir . '/frontend/frontend.php', + 'PLL_Frontend_Auto_Translate' => $baseDir . '/frontend/frontend-auto-translate.php', + 'PLL_Frontend_Filters' => $baseDir . '/frontend/frontend-filters.php', + 'PLL_Frontend_Filters_Links' => $baseDir . '/frontend/frontend-filters-links.php', + 'PLL_Frontend_Filters_Search' => $baseDir . '/frontend/frontend-filters-search.php', + 'PLL_Frontend_Filters_Widgets' => $baseDir . '/frontend/frontend-filters-widgets.php', + 'PLL_Frontend_Links' => $baseDir . '/frontend/frontend-links.php', + 'PLL_Frontend_Nav_Menu' => $baseDir . '/frontend/frontend-nav-menu.php', + 'PLL_Frontend_Static_Pages' => $baseDir . '/frontend/frontend-static-pages.php', + 'PLL_Install' => $baseDir . '/install/install.php', + 'PLL_Install_Base' => $baseDir . '/install/install-base.php', + 'PLL_Integrations' => $baseDir . '/integrations/integrations.php', + 'PLL_Jetpack' => $baseDir . '/integrations/jetpack/jetpack.php', + 'PLL_Language' => $baseDir . '/include/language.php', + 'PLL_Language_Deprecated' => $baseDir . '/include/language-deprecated.php', + 'PLL_Language_Factory' => $baseDir . '/include/language-factory.php', + 'PLL_License' => $baseDir . '/include/license.php', + 'PLL_Links' => $baseDir . '/include/links.php', + 'PLL_Links_Abstract_Domain' => $baseDir . '/include/links-abstract-domain.php', + 'PLL_Links_Default' => $baseDir . '/include/links-default.php', + 'PLL_Links_Directory' => $baseDir . '/include/links-directory.php', + 'PLL_Links_Domain' => $baseDir . '/include/links-domain.php', + 'PLL_Links_Model' => $baseDir . '/include/links-model.php', + 'PLL_Links_Permalinks' => $baseDir . '/include/links-permalinks.php', + 'PLL_Links_Subdomain' => $baseDir . '/include/links-subdomain.php', + 'PLL_MO' => $baseDir . '/include/mo.php', + 'PLL_Model' => $baseDir . '/include/model.php', + 'PLL_Multilingual_Sitemaps_Provider' => $baseDir . '/modules/sitemaps/multilingual-sitemaps-provider.php', + 'PLL_Nav_Menu' => $baseDir . '/include/nav-menu.php', + 'PLL_No_Category_Base' => $baseDir . '/integrations/no-category-base/no-category-base.php', + 'PLL_OLT_Manager' => $baseDir . '/include/olt-manager.php', + 'PLL_Plugin_Updater' => $baseDir . '/install/plugin-updater.php', + 'PLL_Query' => $baseDir . '/include/query.php', + 'PLL_REST_Request' => $baseDir . '/include/rest-request.php', + 'PLL_Settings' => $baseDir . '/settings/settings.php', + 'PLL_Settings_Browser' => $baseDir . '/settings/settings-browser.php', + 'PLL_Settings_CPT' => $baseDir . '/settings/settings-cpt.php', + 'PLL_Settings_Licenses' => $baseDir . '/settings/settings-licenses.php', + 'PLL_Settings_Media' => $baseDir . '/settings/settings-media.php', + 'PLL_Settings_Module' => $baseDir . '/settings/settings-module.php', + 'PLL_Settings_Preview_Machine_Translation' => $baseDir . '/modules/machine-translation/settings-preview-machine-translation.php', + 'PLL_Settings_Preview_Share_Slug' => $baseDir . '/modules/share-slug/settings-preview-share-slug.php', + 'PLL_Settings_Preview_Translate_Slugs' => $baseDir . '/modules/translate-slugs/settings-preview-translate-slugs.php', + 'PLL_Settings_Sync' => $baseDir . '/modules/sync/settings-sync.php', + 'PLL_Settings_Url' => $baseDir . '/settings/settings-url.php', + 'PLL_Sitemaps' => $baseDir . '/modules/sitemaps/sitemaps.php', + 'PLL_Sitemaps_Domain' => $baseDir . '/modules/sitemaps/sitemaps-domain.php', + 'PLL_Static_Pages' => $baseDir . '/include/static-pages.php', + 'PLL_Switcher' => $baseDir . '/include/switcher.php', + 'PLL_Sync' => $baseDir . '/modules/sync/sync.php', + 'PLL_Sync_Metas' => $baseDir . '/modules/sync/sync-metas.php', + 'PLL_Sync_Post_Metas' => $baseDir . '/modules/sync/sync-post-metas.php', + 'PLL_Sync_Tax' => $baseDir . '/modules/sync/sync-tax.php', + 'PLL_Sync_Term_Metas' => $baseDir . '/modules/sync/sync-term-metas.php', + 'PLL_T15S' => $baseDir . '/install/t15s.php', + 'PLL_Table_Languages' => $baseDir . '/settings/table-languages.php', + 'PLL_Table_Settings' => $baseDir . '/settings/table-settings.php', + 'PLL_Table_String' => $baseDir . '/settings/table-string.php', + 'PLL_Translatable_Object' => $baseDir . '/include/translatable-object.php', + 'PLL_Translatable_Object_With_Types_Interface' => $baseDir . '/include/translatable-object-with-types-interface.php', + 'PLL_Translatable_Object_With_Types_Trait' => $baseDir . '/include/translatable-object-with-types-trait.php', + 'PLL_Translatable_Objects' => $baseDir . '/include/translatable-objects.php', + 'PLL_Translate_Option' => $baseDir . '/include/translate-option.php', + 'PLL_Translated_Object' => $baseDir . '/include/translated-object.php', + 'PLL_Translated_Post' => $baseDir . '/include/translated-post.php', + 'PLL_Translated_Term' => $baseDir . '/include/translated-term.php', + 'PLL_Twenty_Seventeen' => $baseDir . '/integrations/twenty-seventeen/twenty-seven-teen.php', + 'PLL_Upgrade' => $baseDir . '/install/upgrade.php', + 'PLL_WPML_API' => $baseDir . '/modules/wpml/wpml-api.php', + 'PLL_WPML_Compat' => $baseDir . '/modules/wpml/wpml-compat.php', + 'PLL_WPML_Config' => $baseDir . '/modules/wpml/wpml-config.php', + 'PLL_WPSEO' => $baseDir . '/integrations/wpseo/wpseo.php', + 'PLL_WPSEO_OGP' => $baseDir . '/integrations/wpseo/wpseo-ogp.php', + 'PLL_WP_Import' => $baseDir . '/integrations/wp-importer/wp-import.php', + 'PLL_WP_Sweep' => $baseDir . '/integrations/wp-sweep/wp-sweep.php', + 'PLL_Walker' => $baseDir . '/include/walker.php', + 'PLL_Walker_Dropdown' => $baseDir . '/include/walker-dropdown.php', + 'PLL_Walker_List' => $baseDir . '/include/walker-list.php', + 'PLL_Widget_Calendar' => $baseDir . '/include/widget-calendar.php', + 'PLL_Widget_Languages' => $baseDir . '/include/widget-languages.php', + 'PLL_Wizard' => $baseDir . '/modules/wizard/wizard.php', + 'PLL_WordPress_Importer' => $baseDir . '/integrations/wp-importer/wordpress-importer.php', + 'PLL_Yarpp' => $baseDir . '/integrations/yarpp/yarpp.php', + 'Polylang' => $baseDir . '/include/class-polylang.php', +); diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php b/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000000..15a2ff3ad6 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ +register(true); + + return $loader; + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/autoload_static.php b/wp-content/plugins/polylang/vendor/composer/autoload_static.php new file mode 100644 index 0000000000..86aef19c82 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/autoload_static.php @@ -0,0 +1,150 @@ + __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'PLL_AS3CF' => __DIR__ . '/../..' . '/integrations/wp-offload-media/as3cf.php', + 'PLL_Abstract_Sitemaps' => __DIR__ . '/../..' . '/modules/sitemaps/abstract-sitemaps.php', + 'PLL_Accept_Language' => __DIR__ . '/../..' . '/frontend/accept-language.php', + 'PLL_Accept_Languages_Collection' => __DIR__ . '/../..' . '/frontend/accept-languages-collection.php', + 'PLL_Admin' => __DIR__ . '/../..' . '/admin/admin.php', + 'PLL_Admin_Base' => __DIR__ . '/../..' . '/admin/admin-base.php', + 'PLL_Admin_Block_Editor' => __DIR__ . '/../..' . '/admin/admin-block-editor.php', + 'PLL_Admin_Classic_Editor' => __DIR__ . '/../..' . '/admin/admin-classic-editor.php', + 'PLL_Admin_Default_Term' => __DIR__ . '/../..' . '/admin/admin-default-term.php', + 'PLL_Admin_Filters' => __DIR__ . '/../..' . '/admin/admin-filters.php', + 'PLL_Admin_Filters_Columns' => __DIR__ . '/../..' . '/admin/admin-filters-columns.php', + 'PLL_Admin_Filters_Media' => __DIR__ . '/../..' . '/admin/admin-filters-media.php', + 'PLL_Admin_Filters_Post' => __DIR__ . '/../..' . '/admin/admin-filters-post.php', + 'PLL_Admin_Filters_Post_Base' => __DIR__ . '/../..' . '/admin/admin-filters-post-base.php', + 'PLL_Admin_Filters_Term' => __DIR__ . '/../..' . '/admin/admin-filters-term.php', + 'PLL_Admin_Filters_Widgets_Options' => __DIR__ . '/../..' . '/admin/admin-filters-widgets-options.php', + 'PLL_Admin_Links' => __DIR__ . '/../..' . '/admin/admin-links.php', + 'PLL_Admin_Model' => __DIR__ . '/../..' . '/admin/admin-model.php', + 'PLL_Admin_Nav_Menu' => __DIR__ . '/../..' . '/admin/admin-nav-menu.php', + 'PLL_Admin_Notices' => __DIR__ . '/../..' . '/admin/admin-notices.php', + 'PLL_Admin_Site_Health' => __DIR__ . '/../..' . '/modules/site-health/admin-site-health.php', + 'PLL_Admin_Static_Pages' => __DIR__ . '/../..' . '/admin/admin-static-pages.php', + 'PLL_Admin_Strings' => __DIR__ . '/../..' . '/admin/admin-strings.php', + 'PLL_Admin_Sync' => __DIR__ . '/../..' . '/modules/sync/admin-sync.php', + 'PLL_Aqua_Resizer' => __DIR__ . '/../..' . '/integrations/aqua-resizer/aqua-resizer.php', + 'PLL_Base' => __DIR__ . '/../..' . '/include/base.php', + 'PLL_CRUD_Posts' => __DIR__ . '/../..' . '/include/crud-posts.php', + 'PLL_CRUD_Terms' => __DIR__ . '/../..' . '/include/crud-terms.php', + 'PLL_Cache' => __DIR__ . '/../..' . '/include/cache.php', + 'PLL_Cache_Compat' => __DIR__ . '/../..' . '/integrations/cache/cache-compat.php', + 'PLL_Canonical' => __DIR__ . '/../..' . '/frontend/canonical.php', + 'PLL_Cft' => __DIR__ . '/../..' . '/integrations/custom-field-template/cft.php', + 'PLL_Choose_Lang' => __DIR__ . '/../..' . '/frontend/choose-lang.php', + 'PLL_Choose_Lang_Content' => __DIR__ . '/../..' . '/frontend/choose-lang-content.php', + 'PLL_Choose_Lang_Domain' => __DIR__ . '/../..' . '/frontend/choose-lang-domain.php', + 'PLL_Choose_Lang_Url' => __DIR__ . '/../..' . '/frontend/choose-lang-url.php', + 'PLL_Cookie' => __DIR__ . '/../..' . '/include/cookie.php', + 'PLL_Db_Tools' => __DIR__ . '/../..' . '/include/db-tools.php', + 'PLL_Domain_Mapping' => __DIR__ . '/../..' . '/integrations/domain-mapping/domain-mapping.php', + 'PLL_Duplicate_Post' => __DIR__ . '/../..' . '/integrations/duplicate-post/duplicate-post.php', + 'PLL_Featured_Content' => __DIR__ . '/../..' . '/integrations/jetpack/featured-content.php', + 'PLL_Filter_REST_Routes' => __DIR__ . '/../..' . '/include/filter-rest-routes.php', + 'PLL_Filters' => __DIR__ . '/../..' . '/include/filters.php', + 'PLL_Filters_Links' => __DIR__ . '/../..' . '/include/filters-links.php', + 'PLL_Filters_Sanitization' => __DIR__ . '/../..' . '/include/filters-sanitization.php', + 'PLL_Filters_Widgets_Options' => __DIR__ . '/../..' . '/include/filters-widgets-options.php', + 'PLL_Frontend' => __DIR__ . '/../..' . '/frontend/frontend.php', + 'PLL_Frontend_Auto_Translate' => __DIR__ . '/../..' . '/frontend/frontend-auto-translate.php', + 'PLL_Frontend_Filters' => __DIR__ . '/../..' . '/frontend/frontend-filters.php', + 'PLL_Frontend_Filters_Links' => __DIR__ . '/../..' . '/frontend/frontend-filters-links.php', + 'PLL_Frontend_Filters_Search' => __DIR__ . '/../..' . '/frontend/frontend-filters-search.php', + 'PLL_Frontend_Filters_Widgets' => __DIR__ . '/../..' . '/frontend/frontend-filters-widgets.php', + 'PLL_Frontend_Links' => __DIR__ . '/../..' . '/frontend/frontend-links.php', + 'PLL_Frontend_Nav_Menu' => __DIR__ . '/../..' . '/frontend/frontend-nav-menu.php', + 'PLL_Frontend_Static_Pages' => __DIR__ . '/../..' . '/frontend/frontend-static-pages.php', + 'PLL_Install' => __DIR__ . '/../..' . '/install/install.php', + 'PLL_Install_Base' => __DIR__ . '/../..' . '/install/install-base.php', + 'PLL_Integrations' => __DIR__ . '/../..' . '/integrations/integrations.php', + 'PLL_Jetpack' => __DIR__ . '/../..' . '/integrations/jetpack/jetpack.php', + 'PLL_Language' => __DIR__ . '/../..' . '/include/language.php', + 'PLL_Language_Deprecated' => __DIR__ . '/../..' . '/include/language-deprecated.php', + 'PLL_Language_Factory' => __DIR__ . '/../..' . '/include/language-factory.php', + 'PLL_License' => __DIR__ . '/../..' . '/include/license.php', + 'PLL_Links' => __DIR__ . '/../..' . '/include/links.php', + 'PLL_Links_Abstract_Domain' => __DIR__ . '/../..' . '/include/links-abstract-domain.php', + 'PLL_Links_Default' => __DIR__ . '/../..' . '/include/links-default.php', + 'PLL_Links_Directory' => __DIR__ . '/../..' . '/include/links-directory.php', + 'PLL_Links_Domain' => __DIR__ . '/../..' . '/include/links-domain.php', + 'PLL_Links_Model' => __DIR__ . '/../..' . '/include/links-model.php', + 'PLL_Links_Permalinks' => __DIR__ . '/../..' . '/include/links-permalinks.php', + 'PLL_Links_Subdomain' => __DIR__ . '/../..' . '/include/links-subdomain.php', + 'PLL_MO' => __DIR__ . '/../..' . '/include/mo.php', + 'PLL_Model' => __DIR__ . '/../..' . '/include/model.php', + 'PLL_Multilingual_Sitemaps_Provider' => __DIR__ . '/../..' . '/modules/sitemaps/multilingual-sitemaps-provider.php', + 'PLL_Nav_Menu' => __DIR__ . '/../..' . '/include/nav-menu.php', + 'PLL_No_Category_Base' => __DIR__ . '/../..' . '/integrations/no-category-base/no-category-base.php', + 'PLL_OLT_Manager' => __DIR__ . '/../..' . '/include/olt-manager.php', + 'PLL_Plugin_Updater' => __DIR__ . '/../..' . '/install/plugin-updater.php', + 'PLL_Query' => __DIR__ . '/../..' . '/include/query.php', + 'PLL_REST_Request' => __DIR__ . '/../..' . '/include/rest-request.php', + 'PLL_Settings' => __DIR__ . '/../..' . '/settings/settings.php', + 'PLL_Settings_Browser' => __DIR__ . '/../..' . '/settings/settings-browser.php', + 'PLL_Settings_CPT' => __DIR__ . '/../..' . '/settings/settings-cpt.php', + 'PLL_Settings_Licenses' => __DIR__ . '/../..' . '/settings/settings-licenses.php', + 'PLL_Settings_Media' => __DIR__ . '/../..' . '/settings/settings-media.php', + 'PLL_Settings_Module' => __DIR__ . '/../..' . '/settings/settings-module.php', + 'PLL_Settings_Preview_Machine_Translation' => __DIR__ . '/../..' . '/modules/machine-translation/settings-preview-machine-translation.php', + 'PLL_Settings_Preview_Share_Slug' => __DIR__ . '/../..' . '/modules/share-slug/settings-preview-share-slug.php', + 'PLL_Settings_Preview_Translate_Slugs' => __DIR__ . '/../..' . '/modules/translate-slugs/settings-preview-translate-slugs.php', + 'PLL_Settings_Sync' => __DIR__ . '/../..' . '/modules/sync/settings-sync.php', + 'PLL_Settings_Url' => __DIR__ . '/../..' . '/settings/settings-url.php', + 'PLL_Sitemaps' => __DIR__ . '/../..' . '/modules/sitemaps/sitemaps.php', + 'PLL_Sitemaps_Domain' => __DIR__ . '/../..' . '/modules/sitemaps/sitemaps-domain.php', + 'PLL_Static_Pages' => __DIR__ . '/../..' . '/include/static-pages.php', + 'PLL_Switcher' => __DIR__ . '/../..' . '/include/switcher.php', + 'PLL_Sync' => __DIR__ . '/../..' . '/modules/sync/sync.php', + 'PLL_Sync_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-metas.php', + 'PLL_Sync_Post_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-post-metas.php', + 'PLL_Sync_Tax' => __DIR__ . '/../..' . '/modules/sync/sync-tax.php', + 'PLL_Sync_Term_Metas' => __DIR__ . '/../..' . '/modules/sync/sync-term-metas.php', + 'PLL_T15S' => __DIR__ . '/../..' . '/install/t15s.php', + 'PLL_Table_Languages' => __DIR__ . '/../..' . '/settings/table-languages.php', + 'PLL_Table_Settings' => __DIR__ . '/../..' . '/settings/table-settings.php', + 'PLL_Table_String' => __DIR__ . '/../..' . '/settings/table-string.php', + 'PLL_Translatable_Object' => __DIR__ . '/../..' . '/include/translatable-object.php', + 'PLL_Translatable_Object_With_Types_Interface' => __DIR__ . '/../..' . '/include/translatable-object-with-types-interface.php', + 'PLL_Translatable_Object_With_Types_Trait' => __DIR__ . '/../..' . '/include/translatable-object-with-types-trait.php', + 'PLL_Translatable_Objects' => __DIR__ . '/../..' . '/include/translatable-objects.php', + 'PLL_Translate_Option' => __DIR__ . '/../..' . '/include/translate-option.php', + 'PLL_Translated_Object' => __DIR__ . '/../..' . '/include/translated-object.php', + 'PLL_Translated_Post' => __DIR__ . '/../..' . '/include/translated-post.php', + 'PLL_Translated_Term' => __DIR__ . '/../..' . '/include/translated-term.php', + 'PLL_Twenty_Seventeen' => __DIR__ . '/../..' . '/integrations/twenty-seventeen/twenty-seven-teen.php', + 'PLL_Upgrade' => __DIR__ . '/../..' . '/install/upgrade.php', + 'PLL_WPML_API' => __DIR__ . '/../..' . '/modules/wpml/wpml-api.php', + 'PLL_WPML_Compat' => __DIR__ . '/../..' . '/modules/wpml/wpml-compat.php', + 'PLL_WPML_Config' => __DIR__ . '/../..' . '/modules/wpml/wpml-config.php', + 'PLL_WPSEO' => __DIR__ . '/../..' . '/integrations/wpseo/wpseo.php', + 'PLL_WPSEO_OGP' => __DIR__ . '/../..' . '/integrations/wpseo/wpseo-ogp.php', + 'PLL_WP_Import' => __DIR__ . '/../..' . '/integrations/wp-importer/wp-import.php', + 'PLL_WP_Sweep' => __DIR__ . '/../..' . '/integrations/wp-sweep/wp-sweep.php', + 'PLL_Walker' => __DIR__ . '/../..' . '/include/walker.php', + 'PLL_Walker_Dropdown' => __DIR__ . '/../..' . '/include/walker-dropdown.php', + 'PLL_Walker_List' => __DIR__ . '/../..' . '/include/walker-list.php', + 'PLL_Widget_Calendar' => __DIR__ . '/../..' . '/include/widget-calendar.php', + 'PLL_Widget_Languages' => __DIR__ . '/../..' . '/include/widget-languages.php', + 'PLL_Wizard' => __DIR__ . '/../..' . '/modules/wizard/wizard.php', + 'PLL_WordPress_Importer' => __DIR__ . '/../..' . '/integrations/wp-importer/wordpress-importer.php', + 'PLL_Yarpp' => __DIR__ . '/../..' . '/integrations/yarpp/yarpp.php', + 'Polylang' => __DIR__ . '/../..' . '/include/class-polylang.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->classMap = ComposerStaticInit7dc73dfbbc007ce0d677088d041ad7d4::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/wp-content/plugins/polylang/vendor/composer/installed.json b/wp-content/plugins/polylang/vendor/composer/installed.json new file mode 100644 index 0000000000..f20a6c47c6 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": false, + "dev-package-names": [] +} diff --git a/wp-content/plugins/polylang/vendor/composer/installed.php b/wp-content/plugins/polylang/vendor/composer/installed.php new file mode 100644 index 0000000000..d11dcab36c --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/installed.php @@ -0,0 +1,23 @@ + array( + 'name' => 'wpsyntex/polylang', + 'pretty_version' => '3.6.x-dev', + 'version' => '3.6.9999999.9999999-dev', + 'reference' => 'a3b0bdc67851fdf91f76ad00d4eb7f8bcaf5d87b', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'wpsyntex/polylang' => array( + 'pretty_version' => '3.6.x-dev', + 'version' => '3.6.9999999.9999999-dev', + 'reference' => 'a3b0bdc67851fdf91f76ad00d4eb7f8bcaf5d87b', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/wp-content/plugins/polylang/vendor/composer/platform_check.php b/wp-content/plugins/polylang/vendor/composer/platform_check.php new file mode 100644 index 0000000000..f79e574be7 --- /dev/null +++ b/wp-content/plugins/polylang/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +}