diff --git a/__plugins/polylang-pro-3.7.6/include/Options/Business/Machine_Translation_Enabled.php b/__plugins/polylang-pro-3.7.6/include/Options/Business/Machine_Translation_Enabled.php index d1555798be6a11c5ebce8bcd2a855e52b379fe8d..07bb1a3d2cd4e915d42533a7a353c2c3b9308c9d 100644 --- a/__plugins/polylang-pro-3.7.6/include/Options/Business/Machine_Translation_Enabled.php +++ b/__plugins/polylang-pro-3.7.6/include/Options/Business/Machine_Translation_Enabled.php @@ -1,41 +1,41 @@ , - * additionalProperties: false - * } - */ - protected function get_data_structure(): array { - $structure = array( - 'type' => 'object', // Correspond to associative array in PHP, @see{https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#primitive-types}. - 'properties' => array(), - 'additionalProperties' => false, - ); + /** + * Returns the JSON schema part specific to this option. + * + * @since 3.7 + * + * @return array Partial schema. + * + * @phpstan-return array{ + * type: 'object', + * properties: array< + * non-falsy-string, + * array{ + * type: 'object', + * properties: array, + * additionalProperties: false + * } + * >, + * additionalProperties: false + * } + */ + protected function get_data_structure(): array + { + $structure = [ + 'type' => 'object', // Correspond to associative array in PHP, @see{https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#primitive-types}. + 'properties' => [], + 'additionalProperties' => false, + ]; - foreach ( Factory::get_classnames() as $service ) { - $structure['properties'][ $service::get_slug() ] = array( - 'type' => 'object', - 'properties' => $service::get_option_schema(), - 'additionalProperties' => false, - ); - } + foreach (Factory::get_classnames() as $service) { + $structure['properties'][$service::get_slug()] = [ + 'type' => 'object', + 'properties' => $service::get_option_schema(), + 'additionalProperties' => false, + ]; + } - return $structure; - } + return $structure; + } - /** - * Returns the description used in the JSON schema. - * - * @since 3.7 - * - * @return string - */ - protected function get_description(): string { - return __( 'Settings for machine translation services: DeepL\'s API key and formality for now.', 'polylang-pro' ); - } + /** + * Returns the description used in the JSON schema. + * + * @since 3.7 + * + * @return string + */ + protected function get_description(): string + { + return __('Settings for machine translation services: DeepL\'s API key and formality for now.', 'polylang-pro'); + } } diff --git a/__plugins/polylang-pro-3.7.6/include/Options/Business/Media.php b/__plugins/polylang-pro-3.7.6/include/Options/Business/Media.php index fb948b00ddd9e61fc679304ee385f0b2db7bda5e..ee1fe8f102505c4627de9068d2c92d32fd1f9f7e 100644 --- a/__plugins/polylang-pro-3.7.6/include/Options/Business/Media.php +++ b/__plugins/polylang-pro-3.7.6/include/Options/Business/Media.php @@ -1,83 +1,85 @@ false ); - } + /** + * Returns the default value. + * + * @since 3.7 + * + * @return array + */ + protected function get_default() + { + return ['duplicate' => false]; + } - /** - * Returns the JSON schema part specific to this option. - * - * @since 3.7 - * - * @return array Partial schema. - * - * @phpstan-return array{ - * type: 'object', - * properties: array{ - * duplicate: array{ - * type: 'boolean', - * required: true - * } - * }, - * additionalProperties: false - * } - */ - protected function get_data_structure(): array { - return array( - 'type' => 'object', // Correspond to associative array in PHP, @see{https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#primitive-types}. - 'properties' => array( - 'duplicate' => array( - 'type' => 'boolean', - 'required' => true, - ), - ), - 'additionalProperties' => false, - ); - } + /** + * Returns the JSON schema part specific to this option. + * + * @since 3.7 + * + * @return array Partial schema. + * + * @phpstan-return array{ + * type: 'object', + * properties: array{ + * duplicate: array{ + * type: 'boolean', + * required: true + * } + * }, + * additionalProperties: false + * } + */ + protected function get_data_structure(): array + { + return [ + 'type' => 'object', // Correspond to associative array in PHP, @see{https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#primitive-types}. + 'properties' => [ + 'duplicate' => [ + 'type' => 'boolean', + 'required' => true, + ], + ], + 'additionalProperties' => false, + ]; + } - /** - * Returns the description used in the JSON schema. - * - * @since 3.7 - * - * @return string - */ - protected function get_description(): string { - return __( 'Automatically duplicate media in all languages when uploading a new file.', 'polylang-pro' ); - } + /** + * Returns the description used in the JSON schema. + * + * @since 3.7 + * + * @return string + */ + protected function get_description(): string + { + return __('Automatically duplicate media in all languages when uploading a new file.', 'polylang-pro'); + } } diff --git a/__plugins/polylang-pro-3.7.6/include/Options/Registry.php b/__plugins/polylang-pro-3.7.6/include/Options/Registry.php index 0d60068393cd38b25465c9cc72828cedf1c30480..99e38cecb2ac0cae54206102d0621833179da4c5 100644 --- a/__plugins/polylang-pro-3.7.6/include/Options/Registry.php +++ b/__plugins/polylang-pro-3.7.6/include/Options/Registry.php @@ -1,23 +1,21 @@ get_method(), array( 'PATCH', 'POST', 'PUT' ), true ) ) { - return true; - } +function pll_is_edit_rest_request(WP_REST_Request $request): bool +{ + if (in_array($request->get_method(), ['PATCH', 'POST', 'PUT'], true)) { + return true; + } - return 'GET' === $request->get_method() && 'edit' === $request->get_param( 'context' ); + return 'GET' === $request->get_method() && 'edit' === $request->get_param('context'); } diff --git a/__plugins/polylang-pro-3.7.6/include/pro.php b/__plugins/polylang-pro-3.7.6/include/pro.php index ce762c8a59d014349a143d2974855e7445c88ebb..adabaca327c18d8b0bbc59a26b60b1df348e0add 100644 --- a/__plugins/polylang-pro-3.7.6/include/pro.php +++ b/__plugins/polylang-pro-3.7.6/include/pro.php @@ -1,10 +1,7 @@ options; + add_action('pll_upgrade', [new Upgrade($options), 'upgrade']); - $load_scripts = glob( POLYLANG_PRO_DIR . '/integrations/*/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 - } - } - } + if ($polylang instanceof PLL_Admin_Base) { + // new PLL_License( POLYLANG_PRO_FILE, 'Polylang Pro', POLYLANG_VERSION, 'WP SYNTEX' ); + new PLL_T15S('polylang-pro', 'https://packages.translationspress.com/wp-syntex/polylang-pro/packages.json'); - /** - * Manages the Polylang Pro translations and license key. - * Loads the modules. - * - * @since 2.8 - * - * @param PLL_Base $polylang Polylang object. - * @return void - */ - public function init( &$polylang ) { - /** @var Options $options */ - $options = $polylang->options; - add_action( 'pll_upgrade', array( new Upgrade( $options ), 'upgrade' ) ); + // Download Polylang language packs. + add_filter('http_request_args', [$this, 'http_request_args'], 10, 2); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args + add_filter('pre_set_site_transient_update_plugins', [$this, 'pre_set_site_transient_update_plugins']); + } - if ( $polylang instanceof PLL_Admin_Base ) { - // new PLL_License( POLYLANG_PRO_FILE, 'Polylang Pro', POLYLANG_VERSION, 'WP SYNTEX' ); - new PLL_T15S( 'polylang-pro', 'https://packages.translationspress.com/wp-syntex/polylang-pro/packages.json' ); + // Prolylang Pro is equivalent to Polylang for plugin dependencies. + add_filter('wp_plugin_dependencies_slug', [$this, 'convert_plugin_dependency']); - // Download Polylang language packs. - add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 ); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args - add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'pre_set_site_transient_update_plugins' ) ); - } + // Loads the modules. + $load_scripts = glob(POLYLANG_PRO_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 + } + } + } - // Prolylang Pro is equivalent to Polylang for plugin dependencies. - add_filter( 'wp_plugin_dependencies_slug', array( $this, 'convert_plugin_dependency' ) ); + /** + * Hack to download Polylang languages packs. + * + * @since 1.9 + * + * @param array $args HTTP request args. + * @param string $url The url of the request. + * + * @return array + */ + public function http_request_args($args, $url) + { + if (false !== strpos($url, '//api.wordpress.org/plugins/update-check/')) { + $plugins = (array) json_decode($args['body']['plugins'], true); + if (empty($plugins['plugins']['polylang/polylang.php'])) { + $plugins['plugins']['polylang/polylang.php'] = ['Version' => POLYLANG_VERSION]; + $args['body']['plugins'] = wp_json_encode($plugins); + } + } - // Loads the modules. - $load_scripts = glob( POLYLANG_PRO_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 - } - } - } + return $args; + } - /** - * Hack to download Polylang languages packs - * - * @since 1.9 - * - * @param array $args HTTP request args. - * @param string $url The url of the request. - * @return array - */ - public function http_request_args( $args, $url ) { - if ( false !== strpos( $url, '//api.wordpress.org/plugins/update-check/' ) ) { - $plugins = (array) json_decode( $args['body']['plugins'], true ); - if ( empty( $plugins['plugins']['polylang/polylang.php'] ) ) { - $plugins['plugins']['polylang/polylang.php'] = array( 'Version' => POLYLANG_VERSION ); - $args['body']['plugins'] = wp_json_encode( $plugins ); - } - } - return $args; - } + /** + * Remove Polylang from the list of plugins to update if it is not installed. + * + * @since 2.1.1 + * + * @param stdClass $value The value stored in the update_plugins site transient. + * + * @return stdClass + */ + public function pre_set_site_transient_update_plugins($value) + { + // We encountered a 3rd party plugin setting the transient before the function get_plugins() is available. + require_once ABSPATH.'wp-admin/includes/plugin.php'; + $plugins = get_plugins(); - /** - * Remove Polylang from the list of plugins to update if it is not installed - * - * @since 2.1.1 - * - * @param stdClass $value The value stored in the update_plugins site transient. - * @return stdClass - */ - public function pre_set_site_transient_update_plugins( $value ) { - // We encountered a 3rd party plugin setting the transient before the function get_plugins() is available. - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - $plugins = get_plugins(); + if (isset($value->response)) { + if (empty($plugins['polylang/polylang.php'])) { + unset($value->response['polylang/polylang.php']); + } elseif (isset($value->response['polylang/polylang.php']->new_version) && $plugins['polylang/polylang.php']['Version'] === $value->response['polylang/polylang.php']->new_version) { + $value->no_update['polylang/polylang.php'] = $value->response['polylang/polylang.php']; + unset($value->response['polylang/polylang.php']); + } + } - if ( isset( $value->response ) ) { - if ( empty( $plugins['polylang/polylang.php'] ) ) { - unset( $value->response['polylang/polylang.php'] ); - } elseif ( isset( $value->response['polylang/polylang.php']->new_version ) && $plugins['polylang/polylang.php']['Version'] === $value->response['polylang/polylang.php']->new_version ) { - $value->no_update['polylang/polylang.php'] = $value->response['polylang/polylang.php']; - unset( $value->response['polylang/polylang.php'] ); - } - } - return $value; - } + return $value; + } - /** - * Converts the Polylang plugin slug to Polylang Pro for plugin dependencies. - * - * This allows plugins requiring Polylang to work with Polylang Pro too. - * - * @since 3.7 - * - * @param string $slug The plugin slug. - * @return string - */ - public function convert_plugin_dependency( $slug ): string { - return 'polylang' === $slug ? dirname( POLYLANG_BASENAME ) : (string) $slug; - } + /** + * Converts the Polylang plugin slug to Polylang Pro for plugin dependencies. + * + * This allows plugins requiring Polylang to work with Polylang Pro too. + * + * @since 3.7 + * + * @param string $slug The plugin slug. + * + * @return string + */ + public function convert_plugin_dependency($slug): string + { + return 'polylang' === $slug ? dirname(POLYLANG_BASENAME) : (string) $slug; + } } diff --git a/__plugins/polylang-pro-3.7.6/include/upgrade.php b/__plugins/polylang-pro-3.7.6/include/upgrade.php index d0cbb35aefb50a83278eaf654c915173811c7909..b50c7a81d8d35955e1f983d1b086560a3c013040 100644 --- a/__plugins/polylang-pro-3.7.6/include/upgrade.php +++ b/__plugins/polylang-pro-3.7.6/include/upgrade.php @@ -1,7 +1,4 @@ options = $options; - } + /** + * Constructor. + * + * @since 3.7 + * + * @param Options $options The options. + */ + public function __construct(Options $options) + { + $this->options = $options; + } - /** - * Runs upgrade process. - * - * @since 3.7 - * - * @return void - */ - public function upgrade() { - foreach ( array( '3.7' ) as $version ) { - if ( version_compare( $this->options->get( 'version' ), $version, '<' ) ) { - $method_to_call = array( $this, 'upgrade_' . str_replace( '.', '_', $version ) ); - if ( is_callable( $method_to_call ) ) { - call_user_func( $method_to_call ); - } - } - } - } + /** + * Runs upgrade process. + * + * @since 3.7 + * + * @return void + */ + public function upgrade() + { + foreach (['3.7'] as $version) { + if (version_compare($this->options->get('version'), $version, '<')) { + $method_to_call = [$this, 'upgrade_'.str_replace('.', '_', $version)]; + if (is_callable($method_to_call)) { + call_user_func($method_to_call); + } + } + } + } - /** - * Migrates translated field groups to the new ACF integration. - * Transforms language taxonomy to group location. - * - * @since 3.7 - * - * @return void - */ - private function upgrade_3_7() { - if ( ! ACF_Main::can_use() ) { - return; - } + /** + * Migrates translated field groups to the new ACF integration. + * Transforms language taxonomy to group location. + * + * @since 3.7 + * + * @return void + */ + private function upgrade_3_7() + { + if (!ACF_Main::can_use()) { + return; + } - if ( ! in_array( 'acf-field-group', $this->options->get( 'post_types' ), true ) ) { - return; - } + if (!in_array('acf-field-group', $this->options->get('post_types'), true)) { + return; + } - $this->options->set( - 'post_types', - array_diff( - $this->options->get( 'post_types' ), - array( 'acf-field-group' ) - ) - ); - $this->options->save(); + $this->options->set( + 'post_types', + array_diff( + $this->options->get('post_types'), + ['acf-field-group'] + ) + ); + $this->options->save(); - foreach ( acf_get_field_groups() as $group ) { - if ( empty( $group['ID'] ) ) { - continue; - } + foreach (acf_get_field_groups() as $group) { + if (empty($group['ID'])) { + continue; + } - $group_language = wp_get_object_terms( $group['ID'], 'language' ); + $group_language = wp_get_object_terms($group['ID'], 'language'); - if ( empty( $group_language ) || is_wp_error( $group_language ) || 1 !== count( $group_language ) ) { - continue; - } + if (empty($group_language) || is_wp_error($group_language) || 1 !== count($group_language)) { + continue; + } - $group_language = $group_language[0]; - $new_location = array( - 'param' => 'language', - 'operator' => '==', - 'value' => $group_language->slug, - ); + $group_language = $group_language[0]; + $new_location = [ + 'param' => 'language', + 'operator' => '==', + 'value' => $group_language->slug, + ]; - foreach ( $group['location'] as &$location ) { - $location[] = $new_location; - } + foreach ($group['location'] as &$location) { + $location[] = $new_location; + } - acf_update_field_group( $group ); - } - } + acf_update_field_group($group); + } + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Ajax_Lang_Choice.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Ajax_Lang_Choice.php index 8233d7d443b9e414bd4f90efbcc801305dc6aa3c..b03942f7e132ebbafd8e47a64605b4cec3d5028c 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Ajax_Lang_Choice.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Ajax_Lang_Choice.php @@ -1,7 +1,4 @@ model->is_translated_post_type( $typenow ) - ) { - $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; - wp_enqueue_script( 'pll_acf', plugins_url( '/js/build/integrations/acf' . $suffix . '.js', POLYLANG_ROOT_FILE ), array( 'wp-api-fetch', 'acf-input' ), POLYLANG_VERSION ); - } - } - - /** - * Ajax response for changing the language in the post metabox. - * - * @since 2.0 - * - * @return void - */ - public function acf_post_lang_choice() { - check_ajax_referer( 'pll_language', '_pll_nonce' ); - - if ( ! isset( $_POST['fields'], $_POST['lang'], $_POST['post_id'] ) ) { - wp_die( 0 ); - } - - $post_id = (int) $_POST['post_id']; - if ( ! current_user_can( 'edit_post', $post_id ) ) { - wp_die( -1 ); - } - - $language = PLL()->model->languages->get( sanitize_key( $_POST['lang'] ) ); - if ( ! $language ) { - wp_die( 0 ); - } - - $response = array(); - - $fields = explode( ',', sanitize_text_field( wp_unslash( $_POST['fields'] ) ) ); - foreach ( $fields as $field ) { - $field_array = acf_get_field( $field ); - - if ( false === $field_array ) { - continue; - } - - $from_value = acf_get_value( $post_id, $field_array ); - $field_array['value'] = ( new Copy() )->execute( - new Post( $post_id ), - $from_value, - $field_array, - array( - 'target_language' => $language, - 'original_value' => $from_value, - ) - ); - acf_update_value( $field_array['value'], $post_id, $field_array ); - - ob_start(); - acf_render_fields( array( $field_array ) ); - $field_wrap = ob_get_clean(); - - $response[] = array( - 'field_key' => str_replace( '_', '-', $field ), - 'field_data' => false !== $field_wrap ? $field_wrap : '', - ); - } - - wp_send_json( $response ); - } +class Ajax_Lang_Choice +{ + /** + * Setups actions. + * + * @since 3.7 + * + * @return void + */ + public function on_acf_init() + { + add_action('admin_enqueue_scripts', [$this, 'admin_enqueue_scripts']); + add_action('wp_ajax_acf_post_lang_choice', [$this, 'acf_post_lang_choice']); + add_filter('acf/fields/relationship/query', [Dispatcher::class, 'add_language_to_query'], 10, 3); + } + + /** + * Enqueues javascript to react to a language change in the post metabox. + * + * @since 2.0 + * + * @return void + */ + public function admin_enqueue_scripts() + { + global $pagenow, $typenow; + + if ( + in_array($pagenow, ['post.php', 'post-new.php'], true) + && PLL()->model->is_translated_post_type($typenow) + ) { + $suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min'; + wp_enqueue_script('pll_acf', plugins_url('/js/build/integrations/acf'.$suffix.'.js', POLYLANG_ROOT_FILE), ['wp-api-fetch', 'acf-input'], POLYLANG_VERSION); + } + } + + /** + * Ajax response for changing the language in the post metabox. + * + * @since 2.0 + * + * @return void + */ + public function acf_post_lang_choice() + { + check_ajax_referer('pll_language', '_pll_nonce'); + + if (!isset($_POST['fields'], $_POST['lang'], $_POST['post_id'])) { + wp_die(0); + } + + $post_id = (int) $_POST['post_id']; + if (!current_user_can('edit_post', $post_id)) { + wp_die(-1); + } + + $language = PLL()->model->languages->get(sanitize_key($_POST['lang'])); + if (!$language) { + wp_die(0); + } + + $response = []; + + $fields = explode(',', sanitize_text_field(wp_unslash($_POST['fields']))); + foreach ($fields as $field) { + $field_array = acf_get_field($field); + + if (false === $field_array) { + continue; + } + + $from_value = acf_get_value($post_id, $field_array); + $field_array['value'] = (new Copy())->execute( + new Post($post_id), + $from_value, + $field_array, + [ + 'target_language' => $language, + 'original_value' => $from_value, + ] + ); + acf_update_value($field_array['value'], $post_id, $field_array); + + ob_start(); + acf_render_fields([$field_array]); + $field_wrap = ob_get_clean(); + + $response[] = [ + 'field_key' => str_replace('_', '-', $field), + 'field_data' => false !== $field_wrap ? $field_wrap : '', + ]; + } + + wp_send_json($response); + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Dispatcher.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Dispatcher.php index e8cb9013a00d537b786da384b3ce5caaa7f509c1..b4f4b6a28d480ad79dc8b622eea6c4ea6ede6730 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Dispatcher.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Dispatcher.php @@ -1,25 +1,20 @@ options['media_support'] ) { - add_action( 'pll_translate_media', array( static::class, 'copy_media_fields' ), 10, 3 ); - } - } - - /** - * Initializes ACF blocks integration when blocks are registered. - * - * @since 3.7 - * - * @return void - */ - public static function on_blocks_registered(): void { - if ( ! function_exists( 'acf_get_block_types' ) || empty( acf_get_block_types() ) ) { - return; - } - - add_filter( 'pll_collect_post_ids', array( static::class, 'collect_post_ids_in_blocks' ), 10, 2 ); - add_filter( 'pll_collect_term_ids', array( static::class, 'collect_term_ids_in_blocks' ), 10, 2 ); - add_filter( 'pll_translate_blocks_with_context', array( static::class, 'copy_blocks' ), 10, 3 ); - add_filter( 'pll_filter_translated_post', array( static::class, 'translate_blocks' ), 10, 4 ); - add_action( 'pll_after_post_export', array( static::class, 'export_blocks' ), 10, 2 ); - } - - /** - * Filters the field about to be rendered. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @param int|string $acf_id ACF post ID. - * @return mixed Modified custom field. - */ - public static function render_field( $field, $acf_id ) { - $object = static::get_by_acf_id( $acf_id ); - return empty( $object ) ? $field : $object->render_field( $field ); - } - - /** - * Filters the custom field value when updated. - * - * @since 3.7 - * - * @param mixed $value Custom field value. - * @param int|string $acf_id ACF post ID. - * @param array $field Custom field definition. - * @return mixed Modified custom field value. - */ - public static function update( $value, $acf_id, $field ) { - $object = static::get_by_acf_id( $acf_id ); - return empty( $object ) ? $value : $object->update( $value, $field ); - } - - /** - * Copies or synchronizes ACF custom fields when using Polylang's copy post function (and not the post-new.php where ACF filters are applied). - * (e.g. using bulk translate, creating a synchronized post). - * - * @since 3.7 - * - * @param int $post_id ID of the source post. - * @param int $tr_post_id ID of the target post. - * @param string $lang Language of the target post. - * @param string $sync `sync` if doing synchro, `copy` otherwise. - * @return void - * - * @phpstan-param 'sync'|'copy' $sync - */ - public static function on_post_synchronized( $post_id, $tr_post_id, $lang, $sync ) { - ( new Post( $post_id ) )->on_post_synchronized( $tr_post_id, $lang, $sync ); - } - - /** - * Export custom fields to translate. - * - * @since 3.7 - * - * @param PLL_Export_Data $export The export object. - * @param object $from The object to export. - * @param object|null $to The translated object if it exists, `null` otherwise. - * @return void - */ - public static function export( $export, $from, $to ) { - $object = self::get_by_object( $from ); - if ( ! empty( $object ) && $export instanceof PLL_Export_Data ) { - $object->export( $export, $to ); - } - } - - /** - * Collects post IDs from fields. - * - * @since 3.7 - * - * @param int[] $linked_ids Object IDs linked to a post. - * @param WP_Post $post The post we get other post from. - * @return int[] - */ - public static function collect_post_ids( $linked_ids, $post ) { - $object = self::get_by_object( $post ); - - if ( ! empty( $object ) ) { - return array_merge( (array) $linked_ids, ( new Collect_Post_Ids() )->get( $object ) ); - } - - return $linked_ids; - } - - /** - * Collects post IDs from fields in ACF blocks. - * - * @since 3.7 - * - * @param int[] $linked_ids Object IDs linked to a post. - * @param WP_Post $post The post we get other post from. - * @return int[] - */ - public static function collect_post_ids_in_blocks( $linked_ids, $post ) { - if ( has_blocks( $post ) ) { - $linked_ids = array_merge( (array) $linked_ids, ( new Collect_Post_Ids() )->get( new Blocks( $post->ID ) ) ); - } - - return $linked_ids; - } - - /** - * Collects term IDs from fields. - * - * @since 3.7 - * - * @param int[] $linked_ids Object IDs linked to a post. - * @param WP_Post $post The post we get other term from. - * @return int[] - */ - public static function collect_term_ids( $linked_ids, $post ) { - $object = self::get_by_object( $post ); - - if ( ! empty( $object ) ) { - return array_merge( (array) $linked_ids, ( new Collect_Term_Ids() )->get( $object ) ); - } - - return $linked_ids; - } - - - /** - * Collects term IDs from fields in ACF blocks. - * - * @since 3.7 - * - * @param int[] $linked_ids Object IDs linked to a post. - * @param WP_Post $post The post we get other term from. - * @return int[] - */ - public static function collect_term_ids_in_blocks( $linked_ids, $post ) { - if ( has_blocks( $post ) ) { - $linked_ids = array_merge( (array) $linked_ids, ( new Collect_Term_Ids() )->get( new Blocks( $post->ID ) ) ); - } - - return $linked_ids; - } - - /** - * Translates the custom fields from a given object. - * - * @since 3.7 - * - * @param object $from Source object to get the custom fields from. - * @param object $to Translated object to translate the custom fields from. - * @param PLL_Language $target_lang Target language object. - * @param Translations $translations A set of translations to search the custom fields translations in. - * @return void - */ - public static function translate( $from, $to, $target_lang, $translations ) { - /* - * Remove filter for `Dispatcher::render_field` to avoid running `Strategy\Copy` on the same object. - * For instance, when translating fields with DeepL, we don't want to override the translated values with the original ones with `Strategy\Copy`. - */ - remove_filter( 'acf/pre_render_field', array( self::class, 'render_field' ) ); - - $object = self::get_by_object( $from ); - if ( ! empty( $object ) && $target_lang instanceof PLL_Language && $translations instanceof Translations ) { - $object->translate( $to, $target_lang, $translations ); - } - } - - - /** - * Adds the language of the current object to the arguments that will be used for the query in the `relationship` ACF field. - * - * @since 3.7 - * - * @param array $args Arguments to retrieve posts. - * @param array $field The current field. - * @param int|string $acf_id ACF post ID. - * @return array The arguments to retrieve posts with the current object language. - */ - public static function add_language_to_query( $args, $field, $acf_id ) { - if ( isset( $args['lang'] ) ) { - return $args; - } - - $object = self::get_by_acf_id( $acf_id ); - if ( empty( $object ) ) { - return $args; - } - - $language = PLL()->model->{$object->get_type()}->get_language( $object->get_id() ); - if ( empty( $language ) ) { - return $args; - } - - $args['lang'] = $language->slug; - - return $args; - } - - /** - * Translates the media fields. - * - * @since 3.7 - * - * @param int $from_id The source media ID. - * @param int $to_id The target media ID. - * @param PLL_Language $target_language The target language. - * @return void - */ - public static function copy_media_fields( $from_id, $to_id, $target_language ) { - ( new Media( $from_id ) )->copy_fields( $to_id, $target_language ); - } - - /** - * Saves ACF fields for a term that was automatically duplicated when a post has been duplicated. - * - * @since 3.7 - * - * @param int $from Term ID of the source term. - * @param int $to Term ID of the new term translation. - * @param string $lang Language code of the new translation. - * @return void - */ - public static function on_duplicate_term( $from, $to, $lang ) { - $lang = PLL()->model->get_language( $lang ); - if ( $lang instanceof PLL_Language ) { - ( new Term( $from ) )->apply_to_all_fields( - new Copy(), - $to, - array( 'target_language' => $lang ) - ); - } - } - - /** - * Copies the field values in blocks. - * - * @since 3.7 - * - * @param array $blocks The blocks. - * @param PLL_Language $target_lang The target language. - * @param WP_Post|null $source_post The source post, `null` if not available. - * @return array The blocks. - */ - public static function copy_blocks( $blocks, $target_lang, $source_post ) { - if ( ! is_array( $blocks ) || ! $target_lang instanceof PLL_Language || ! $source_post instanceof WP_Post ) { - return $blocks; - } - return ( new Blocks() )->copy( $blocks, $target_lang, $source_post ); - } - - /** - * Translates fields in blocks during import. - * - * @since 3.7 - * - * @param WP_Post $to Translated post where to translate the custom fields included in blocks. - * @param WP_Post $from Source post where to get the custom fields included in blocks. - * @param PLL_Language $target_lang The target language. - * @param Translations $translations A set of translations where to search translations of the custom fields translations included in blocks. - * @return WP_Post The translated post. - */ - public static function translate_blocks( $to, $from, $target_lang, $translations ) { - if ( ! $to instanceof WP_Post ) { - return $to; - } - - return ( new Blocks( $from->ID ) )->translate( $to, $target_lang, $translations ); - } - - /** - * Adds ACF fields in blocks to the exported data. - * - * @since 3.7 - * - * @param PLL_Export_Data $export The export data. - * @param WP_Post $from The source post. - * @return void - */ - public static function export_blocks( $export, $from ) { - ( new Blocks( $from->ID ) )->export( $export ); - } - - /** - * Builds an Abstract_Object based on the object type, typically post or term. - * - * @since 3.7 - * - * @param int|string $acf_id ACF post ID. - * @return Abstract_Object|null. - */ - protected static function get_by_acf_id( $acf_id ): ?Abstract_Object { - $decoded = acf_decode_post_id( $acf_id ); - $id = (int) $decoded['id']; - - switch ( $decoded['type'] ) { - case 'post': - if ( PLL()->options['media_support'] && 'attachment' === get_post_type( $id ) ) { - return new Media( $id ); - } - if ( pll_is_translated_post_type( (string) get_post_type( $id ) ) ) { - return new Post( $id ); - } - break; - case 'term': - $term = get_term( $id ); - if ( $term instanceof WP_Term && pll_is_translated_taxonomy( $term->taxonomy ) ) { - return new Term( $id ); - } - - // No nonce to check. - if ( 0 === $id && ! empty( $_GET['new_lang'] ) && ! empty( $_GET['taxonomy'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended - && pll_is_translated_taxonomy( sanitize_key( $_GET['taxonomy'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - // This is a term creation with `term_0`, see `acf_form_taxonomy::add_term()`. - return new Term( $id ); - } - break; - } - - return null; - } - - /** - * Builds an Abstract_Object based on the WP object, typically `WP_Post` or `WP_Term`. - * - * @since 3.7 - * - * @param object $object The object. - * @return Abstract_Object|null. - */ - protected static function get_by_object( $object ): ?Abstract_Object { - if ( $object instanceof WP_Post ) { - if ( 'attachment' === $object->post_type && PLL()->options['media_support'] ) { - return new Media( $object->ID ); - } - if ( pll_is_translated_post_type( $object->post_type ) ) { - return new Post( (int) $object->ID ); - } - } - if ( $object instanceof WP_Term && pll_is_translated_taxonomy( $object->taxonomy ) ) { - return new Term( $object->term_id ); - } - - return null; - } +class Dispatcher +{ + /** + * Setup filters. + * + * @since 3.7 + * + * @return void + */ + public static function on_acf_init() + { + /* + * Removes ACF fields from synchronized metas by Polylang, this way ACF integration manage itself the synchronization. + * Applied after `PLL_Sync_Post_Model::copy_post_metas/copy_taxonomies` for it to be effective. + */ + add_filter('pll_copy_post_metas', [Post::class, 'remove_acf_metas_from_pll_sync'], 10, 4); + add_filter('pll_copy_term_metas', [Term::class, 'remove_acf_metas_from_pll_sync'], 10, 4); + + add_action('pll_post_synchronized', [static::class, 'on_post_synchronized'], 10, 4); + + add_action('pll_duplicate_term', [static::class, 'on_duplicate_term'], 10, 3); + + add_filter('acf/update_value', [static::class, 'update'], 5, 3); + add_filter('acf/pre_render_field', [static::class, 'render_field'], 10, 2); + + add_action('pll_after_post_translation', [static::class, 'translate'], 10, 4); + add_action('pll_after_term_translation', [static::class, 'translate'], 10, 4); + add_action('pll_after_post_export', [static::class, 'export'], 10, 3); + add_action('pll_after_term_export', [static::class, 'export'], 10, 3); + + add_filter('pll_collect_post_ids', [static::class, 'collect_post_ids'], 10, 2); + add_filter('pll_collect_term_ids', [static::class, 'collect_term_ids'], 10, 2); + + if (PLL()->options['media_support']) { + add_action('pll_translate_media', [static::class, 'copy_media_fields'], 10, 3); + } + } + + /** + * Initializes ACF blocks integration when blocks are registered. + * + * @since 3.7 + * + * @return void + */ + public static function on_blocks_registered(): void + { + if (!function_exists('acf_get_block_types') || empty(acf_get_block_types())) { + return; + } + + add_filter('pll_collect_post_ids', [static::class, 'collect_post_ids_in_blocks'], 10, 2); + add_filter('pll_collect_term_ids', [static::class, 'collect_term_ids_in_blocks'], 10, 2); + add_filter('pll_translate_blocks_with_context', [static::class, 'copy_blocks'], 10, 3); + add_filter('pll_filter_translated_post', [static::class, 'translate_blocks'], 10, 4); + add_action('pll_after_post_export', [static::class, 'export_blocks'], 10, 2); + } + + /** + * Filters the field about to be rendered. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * @param int|string $acf_id ACF post ID. + * + * @return mixed Modified custom field. + */ + public static function render_field($field, $acf_id) + { + $object = static::get_by_acf_id($acf_id); + + return empty($object) ? $field : $object->render_field($field); + } + + /** + * Filters the custom field value when updated. + * + * @since 3.7 + * + * @param mixed $value Custom field value. + * @param int|string $acf_id ACF post ID. + * @param array $field Custom field definition. + * + * @return mixed Modified custom field value. + */ + public static function update($value, $acf_id, $field) + { + $object = static::get_by_acf_id($acf_id); + + return empty($object) ? $value : $object->update($value, $field); + } + + /** + * Copies or synchronizes ACF custom fields when using Polylang's copy post function (and not the post-new.php where ACF filters are applied). + * (e.g. using bulk translate, creating a synchronized post). + * + * @since 3.7 + * + * @param int $post_id ID of the source post. + * @param int $tr_post_id ID of the target post. + * @param string $lang Language of the target post. + * @param string $sync `sync` if doing synchro, `copy` otherwise. + * + * @return void + * + * @phpstan-param 'sync'|'copy' $sync + */ + public static function on_post_synchronized($post_id, $tr_post_id, $lang, $sync) + { + (new Post($post_id))->on_post_synchronized($tr_post_id, $lang, $sync); + } + + /** + * Export custom fields to translate. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export object. + * @param object $from The object to export. + * @param object|null $to The translated object if it exists, `null` otherwise. + * + * @return void + */ + public static function export($export, $from, $to) + { + $object = self::get_by_object($from); + if (!empty($object) && $export instanceof PLL_Export_Data) { + $object->export($export, $to); + } + } + + /** + * Collects post IDs from fields. + * + * @since 3.7 + * + * @param int[] $linked_ids Object IDs linked to a post. + * @param WP_Post $post The post we get other post from. + * + * @return int[] + */ + public static function collect_post_ids($linked_ids, $post) + { + $object = self::get_by_object($post); + + if (!empty($object)) { + return array_merge((array) $linked_ids, (new Collect_Post_Ids())->get($object)); + } + + return $linked_ids; + } + + /** + * Collects post IDs from fields in ACF blocks. + * + * @since 3.7 + * + * @param int[] $linked_ids Object IDs linked to a post. + * @param WP_Post $post The post we get other post from. + * + * @return int[] + */ + public static function collect_post_ids_in_blocks($linked_ids, $post) + { + if (has_blocks($post)) { + $linked_ids = array_merge((array) $linked_ids, (new Collect_Post_Ids())->get(new Blocks($post->ID))); + } + + return $linked_ids; + } + + /** + * Collects term IDs from fields. + * + * @since 3.7 + * + * @param int[] $linked_ids Object IDs linked to a post. + * @param WP_Post $post The post we get other term from. + * + * @return int[] + */ + public static function collect_term_ids($linked_ids, $post) + { + $object = self::get_by_object($post); + + if (!empty($object)) { + return array_merge((array) $linked_ids, (new Collect_Term_Ids())->get($object)); + } + + return $linked_ids; + } + + /** + * Collects term IDs from fields in ACF blocks. + * + * @since 3.7 + * + * @param int[] $linked_ids Object IDs linked to a post. + * @param WP_Post $post The post we get other term from. + * + * @return int[] + */ + public static function collect_term_ids_in_blocks($linked_ids, $post) + { + if (has_blocks($post)) { + $linked_ids = array_merge((array) $linked_ids, (new Collect_Term_Ids())->get(new Blocks($post->ID))); + } + + return $linked_ids; + } + + /** + * Translates the custom fields from a given object. + * + * @since 3.7 + * + * @param object $from Source object to get the custom fields from. + * @param object $to Translated object to translate the custom fields from. + * @param PLL_Language $target_lang Target language object. + * @param Translations $translations A set of translations to search the custom fields translations in. + * + * @return void + */ + public static function translate($from, $to, $target_lang, $translations) + { + /* + * Remove filter for `Dispatcher::render_field` to avoid running `Strategy\Copy` on the same object. + * For instance, when translating fields with DeepL, we don't want to override the translated values with the original ones with `Strategy\Copy`. + */ + remove_filter('acf/pre_render_field', [self::class, 'render_field']); + + $object = self::get_by_object($from); + if (!empty($object) && $target_lang instanceof PLL_Language && $translations instanceof Translations) { + $object->translate($to, $target_lang, $translations); + } + } + + /** + * Adds the language of the current object to the arguments that will be used for the query in the `relationship` ACF field. + * + * @since 3.7 + * + * @param array $args Arguments to retrieve posts. + * @param array $field The current field. + * @param int|string $acf_id ACF post ID. + * + * @return array The arguments to retrieve posts with the current object language. + */ + public static function add_language_to_query($args, $field, $acf_id) + { + if (isset($args['lang'])) { + return $args; + } + + $object = self::get_by_acf_id($acf_id); + if (empty($object)) { + return $args; + } + + $language = PLL()->model->{$object->get_type()}->get_language($object->get_id()); + if (empty($language)) { + return $args; + } + + $args['lang'] = $language->slug; + + return $args; + } + + /** + * Translates the media fields. + * + * @since 3.7 + * + * @param int $from_id The source media ID. + * @param int $to_id The target media ID. + * @param PLL_Language $target_language The target language. + * + * @return void + */ + public static function copy_media_fields($from_id, $to_id, $target_language) + { + (new Media($from_id))->copy_fields($to_id, $target_language); + } + + /** + * Saves ACF fields for a term that was automatically duplicated when a post has been duplicated. + * + * @since 3.7 + * + * @param int $from Term ID of the source term. + * @param int $to Term ID of the new term translation. + * @param string $lang Language code of the new translation. + * + * @return void + */ + public static function on_duplicate_term($from, $to, $lang) + { + $lang = PLL()->model->get_language($lang); + if ($lang instanceof PLL_Language) { + (new Term($from))->apply_to_all_fields( + new Copy(), + $to, + ['target_language' => $lang] + ); + } + } + + /** + * Copies the field values in blocks. + * + * @since 3.7 + * + * @param array $blocks The blocks. + * @param PLL_Language $target_lang The target language. + * @param WP_Post|null $source_post The source post, `null` if not available. + * + * @return array The blocks. + */ + public static function copy_blocks($blocks, $target_lang, $source_post) + { + if (!is_array($blocks) || !$target_lang instanceof PLL_Language || !$source_post instanceof WP_Post) { + return $blocks; + } + + return (new Blocks())->copy($blocks, $target_lang, $source_post); + } + + /** + * Translates fields in blocks during import. + * + * @since 3.7 + * + * @param WP_Post $to Translated post where to translate the custom fields included in blocks. + * @param WP_Post $from Source post where to get the custom fields included in blocks. + * @param PLL_Language $target_lang The target language. + * @param Translations $translations A set of translations where to search translations of the custom fields translations included in blocks. + * + * @return WP_Post The translated post. + */ + public static function translate_blocks($to, $from, $target_lang, $translations) + { + if (!$to instanceof WP_Post) { + return $to; + } + + return (new Blocks($from->ID))->translate($to, $target_lang, $translations); + } + + /** + * Adds ACF fields in blocks to the exported data. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export data. + * @param WP_Post $from The source post. + * + * @return void + */ + public static function export_blocks($export, $from) + { + (new Blocks($from->ID))->export($export); + } + + /** + * Builds an Abstract_Object based on the object type, typically post or term. + * + * @since 3.7 + * + * @param int|string $acf_id ACF post ID. + * + * @return Abstract_Object|null. + */ + protected static function get_by_acf_id($acf_id): ?Abstract_Object + { + $decoded = acf_decode_post_id($acf_id); + $id = (int) $decoded['id']; + + switch ($decoded['type']) { + case 'post': + if (PLL()->options['media_support'] && 'attachment' === get_post_type($id)) { + return new Media($id); + } + if (pll_is_translated_post_type((string) get_post_type($id))) { + return new Post($id); + } + break; + case 'term': + $term = get_term($id); + if ($term instanceof WP_Term && pll_is_translated_taxonomy($term->taxonomy)) { + return new Term($id); + } + + // No nonce to check. + if (0 === $id && !empty($_GET['new_lang']) && !empty($_GET['taxonomy']) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + && pll_is_translated_taxonomy(sanitize_key($_GET['taxonomy']))) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // This is a term creation with `term_0`, see `acf_form_taxonomy::add_term()`. + return new Term($id); + } + break; + } + + return null; + } + + /** + * Builds an Abstract_Object based on the WP object, typically `WP_Post` or `WP_Term`. + * + * @since 3.7 + * + * @param object $object The object. + * + * @return Abstract_Object|null. + */ + protected static function get_by_object($object): ?Abstract_Object + { + if ($object instanceof WP_Post) { + if ('attachment' === $object->post_type && PLL()->options['media_support']) { + return new Media($object->ID); + } + if (pll_is_translated_post_type($object->post_type)) { + return new Post((int) $object->ID); + } + } + if ($object instanceof WP_Term && pll_is_translated_taxonomy($object->taxonomy)) { + return new Term($object->term_id); + } + + return null; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Abstract_Object.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Abstract_Object.php index 40428d32f357f8dc609be3dbc97a405e56553dbf..2cfe1ab22fb801541d43e5e3382516bdc8b37b32 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Abstract_Object.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Abstract_Object.php @@ -1,19 +1,16 @@ id = $id; - } - - /** - * Filters the field about to be rendered. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return array Custom field of the target object with a value. - */ - public function render_field( $field ) { - if ( empty( $_GET['new_lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - return $field; - } - - $lang = PLL()->model->get_language( sanitize_key( $_GET['new_lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended - if ( empty( $lang ) ) { - return $field; - } - - $from_id = $this->get_from_id_in_request(); - if ( empty( $from_id ) || $from_id === $this->get_id() ) { - return $field; - } - - $from_value = acf_get_value( static::acf_id( $from_id ), $field ); - $original_value = $field['value'] ?? ( $field['default_value'] ?? null ); - $field['value'] = ( new Copy() )->execute( - $this, - $from_value, - $field, - array( - 'target_language' => $lang, - 'source_language' => PLL()->model->{$this->get_type()}->get_language( $from_id ), - 'original_value' => $original_value, - ) - ); - return $field; - } - - /** - * Updates the custom field value of the current object. - * - * @since 3.7 - * - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @return mixed Custom field value of the target object. - */ - public function update( $value, $field ) { - // Avoid reverse sync. - if ( in_array( $this->get_storage_key( $this->get_id(), $field['key'] ), self::$updated, true ) ) { - return $value; - } - - $strategy = new Synchronize( new Copy() ); - - - if ( ! $strategy->can_execute( $field ) ) { - return $value; - } - - $translations = PLL()->model->{$this->get_type()}->get_translations( $this->get_id() ); - foreach ( $translations as $lang => $tr_id ) { - if ( $this->get_id() === $tr_id ) { - continue; - } - - /** @var PLL_Language */ - $lang = PLL()->model->get_language( $lang ); - - self::$updated[] = $this->get_storage_key( $tr_id, $field['key'] ); - - $acf_id = static::acf_id( $tr_id ); - $tr_value = acf_get_value( $acf_id, $field ); - $tr_value = $strategy->execute( - $this, - $value, - $field, - array( - 'target_language' => $lang, - 'original_value' => $tr_value, - 'target_id' => $tr_id, - ) - ); - - if ( ! empty( $field['sub_fields'] ) && is_array( $tr_value ) && empty( $tr_value ) ) { - /* - * The fields has subfields but they have been removed - * by `Abstract_Strategy::apply_on_subfield()` - * as they cannot be synchronized, so do not update. - - */ - continue; - } - - acf_update_value( $tr_value, $acf_id, $field ); - } - - return $value; - } - - /** - * Executes a strategy on fields from the current object to a target object. - * - * @since 3.7 - * - * @param Abstract_Strategy $strategy Strategy to execute. - * @param int $to ID of the target object. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value Optional. The translated value of the field, if any. - * @type bool $update Optional. Tells if we can update the target ID fields, default `true`. - * } - * @return void - */ - public function apply_to_all_fields( Abstract_Strategy $strategy, int $to = 0, array $args = array() ) { - // Removes filters on `Dispatcher::update()` to avoid unnecessary operations on `acf_update_value`. - remove_filter( 'acf/update_value', array( Dispatcher::class, 'update' ), 5 ); - - $fields = get_field_objects( static::acf_id( $this->get_id() ), false ); - - if ( empty( $fields ) ) { - $fields = array(); - } - - $args['update'] = ! isset( $args['update'] ) || (bool) $args['update']; - - foreach ( $fields as $field ) { - if ( empty( $field['value'] ) && ! is_string( $field['value'] ) ) { - continue; - } - - $args['original_value'] = acf_get_value( static::acf_id( $to ), $field ); - if ( empty( $args['original_value'] ) ) { - $args['original_value'] = $field['default_value'] ?? null; - } - - $tr_value = $strategy->execute( - $this, - $field['value'], - $field, - $args - ); - - if ( 0 < $to && ! empty( $args['update'] ) ) { - acf_update_value( - $tr_value, - static::acf_id( $to ), - $field - ); - } - } - - // Reset filter for `Dispatcher::update` so our integration works for later operations. - add_filter( 'acf/update_value', array( Dispatcher::class, 'update' ), 5, 3 ); - } - - /** - * Exports custom fields. - * - * @param PLL_Export_Data $export The export object. - * @param object|null $to The translated object if it exists, `null` otherwise. - * @return void - * @since 3.7 - */ - public function export( PLL_Export_Data $export, ?object $to ) { - $this->apply_to_all_fields( - new Export( $export ), - empty( $to ) ? 0 : $this->get_object_id( $to ), - array( - 'target_language' => $export->get_target_language(), - 'update' => false, - ) - ); - } - - /** - * Translates the custom fields from the current object. - * - * @since 3.7 - * - * @param object $to The target object. - * @param PLL_Language $target_lang Target language object. - * @param Translations $translations A set of translations to search the custom fields translations in. - * @return object The translated object. - */ - public function translate( object $to, PLL_Language $target_lang, Translations $translations ): object { - $this->apply_to_all_fields( - new Import( $translations ), - $this->get_object_id( $to ), - array( 'target_language' => $target_lang ) - ); - - return $to; - } - - /** - * Removes ACF metas from metas to be synchronized by Polylang. - * To use only the ACF integration synchronization mechanism. - * - * @since 3.7 - * - * @param string[] $metas List of custom fields names. - * @param bool $sync True if it is synchronization, false if it is a copy. - * @param int|string $from ID of the object from which we copy information. - * @param int|string $to ID of the object to which we copy information. - * @return string[] - */ - public static function remove_acf_metas_from_pll_sync( $metas, $sync, $from, $to ) { - if ( ! is_array( $metas ) ) { - return $metas; - } - - $from = static::acf_id( (int) $from ); - $to = static::acf_id( (int) $to ); - - $acf_metas = array_merge( (array) acf_get_meta( $from ), (array) acf_get_meta( $to ) ); - $acf_metas = array_keys( $acf_metas ); - - return array_diff( $metas, $acf_metas ); - } - - /** - * Returns current object ID. - * - * @since 3.7 - * - * @return int - */ - public function get_id(): int { - return $this->id; - } - - /** - * Gets the ACF field key to store. - * - * @since 3.7 - * - * @param int $id Object ID. - * @param string $key The custom field key. - * @return string - */ - protected function get_storage_key( $id, $key ) { - return static::acf_id( $id ) . '|' . $key; - } - - /** - * Returns the object ID. - * - * @since 3.7 - * - * @param object $object The object. - * @return int - */ - abstract protected function get_object_id( $object ): int; - - /** - * Transforms an object ID to the corresponding ACF post ID. - * - * @since 3.7 - * - * @param int $id Object ID. - * @return int|string ACF post ID. - */ - abstract protected static function acf_id( $id ); - - /** - * Returns source object ID passed in the main request if exists. - * - * @since 3.7 - * - * @return int - */ - abstract protected function get_from_id_in_request(): int; - - /** - * Returns current object type. - * - * The returned value must match: - * - the name of the property storing the corresponding model (`PLL()->model->{type}`). - * - the `object_type` from `PLL_Export_Data::add_translation_entry()`. - * - * @since 3.7 - * - * @return string - * @phpstan-return non-falsy-string - */ - abstract public function get_type(): string; +abstract class Abstract_Object implements Translatable_Entity_Interface +{ + /** + * Stores fields to avoid reverse synchronization. + * + * @var string[] + */ + private static $updated = []; + + /** + * Object ID, could be a source or target. + * + * @var int + */ + private $id; + + /** + * Constructor. + * + * @since 3.7 + * + * @param int $id The object ID, default to 0. + */ + public function __construct(int $id = 0) + { + $this->id = $id; + } + + /** + * Filters the field about to be rendered. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return array Custom field of the target object with a value. + */ + public function render_field($field) + { + if (empty($_GET['new_lang'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return $field; + } + + $lang = PLL()->model->get_language(sanitize_key($_GET['new_lang'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if (empty($lang)) { + return $field; + } + + $from_id = $this->get_from_id_in_request(); + if (empty($from_id) || $from_id === $this->get_id()) { + return $field; + } + + $from_value = acf_get_value(static::acf_id($from_id), $field); + $original_value = $field['value'] ?? ($field['default_value'] ?? null); + $field['value'] = (new Copy())->execute( + $this, + $from_value, + $field, + [ + 'target_language' => $lang, + 'source_language' => PLL()->model->{$this->get_type()}->get_language($from_id), + 'original_value' => $original_value, + ] + ); + + return $field; + } + + /** + * Updates the custom field value of the current object. + * + * @since 3.7 + * + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * + * @return mixed Custom field value of the target object. + */ + public function update($value, $field) + { + // Avoid reverse sync. + if (in_array($this->get_storage_key($this->get_id(), $field['key']), self::$updated, true)) { + return $value; + } + + $strategy = new Synchronize(new Copy()); + + if (!$strategy->can_execute($field)) { + return $value; + } + + $translations = PLL()->model->{$this->get_type()}->get_translations($this->get_id()); + foreach ($translations as $lang => $tr_id) { + if ($this->get_id() === $tr_id) { + continue; + } + + /** @var PLL_Language */ + $lang = PLL()->model->get_language($lang); + + self::$updated[] = $this->get_storage_key($tr_id, $field['key']); + + $acf_id = static::acf_id($tr_id); + $tr_value = acf_get_value($acf_id, $field); + $tr_value = $strategy->execute( + $this, + $value, + $field, + [ + 'target_language' => $lang, + 'original_value' => $tr_value, + 'target_id' => $tr_id, + ] + ); + + if (!empty($field['sub_fields']) && is_array($tr_value) && empty($tr_value)) { + /* + * The fields has subfields but they have been removed + * by `Abstract_Strategy::apply_on_subfield()` + * as they cannot be synchronized, so do not update. + + */ + continue; + } + + acf_update_value($tr_value, $acf_id, $field); + } + + return $value; + } + + /** + * Executes a strategy on fields from the current object to a target object. + * + * @since 3.7 + * + * @param Abstract_Strategy $strategy Strategy to execute. + * @param int $to ID of the target object. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value Optional. The translated value of the field, if any. + * @var bool $update Optional. Tells if we can update the target ID fields, default `true`. + * } + * + * @return void + */ + public function apply_to_all_fields(Abstract_Strategy $strategy, int $to = 0, array $args = []) + { + // Removes filters on `Dispatcher::update()` to avoid unnecessary operations on `acf_update_value`. + remove_filter('acf/update_value', [Dispatcher::class, 'update'], 5); + + $fields = get_field_objects(static::acf_id($this->get_id()), false); + + if (empty($fields)) { + $fields = []; + } + + $args['update'] = !isset($args['update']) || (bool) $args['update']; + + foreach ($fields as $field) { + if (empty($field['value']) && !is_string($field['value'])) { + continue; + } + + $args['original_value'] = acf_get_value(static::acf_id($to), $field); + if (empty($args['original_value'])) { + $args['original_value'] = $field['default_value'] ?? null; + } + + $tr_value = $strategy->execute( + $this, + $field['value'], + $field, + $args + ); + + if (0 < $to && !empty($args['update'])) { + acf_update_value( + $tr_value, + static::acf_id($to), + $field + ); + } + } + + // Reset filter for `Dispatcher::update` so our integration works for later operations. + add_filter('acf/update_value', [Dispatcher::class, 'update'], 5, 3); + } + + /** + * Exports custom fields. + * + * @param PLL_Export_Data $export The export object. + * @param object|null $to The translated object if it exists, `null` otherwise. + * + * @return void + * + * @since 3.7 + */ + public function export(PLL_Export_Data $export, ?object $to) + { + $this->apply_to_all_fields( + new Export($export), + empty($to) ? 0 : $this->get_object_id($to), + [ + 'target_language' => $export->get_target_language(), + 'update' => false, + ] + ); + } + + /** + * Translates the custom fields from the current object. + * + * @since 3.7 + * + * @param object $to The target object. + * @param PLL_Language $target_lang Target language object. + * @param Translations $translations A set of translations to search the custom fields translations in. + * + * @return object The translated object. + */ + public function translate(object $to, PLL_Language $target_lang, Translations $translations): object + { + $this->apply_to_all_fields( + new Import($translations), + $this->get_object_id($to), + ['target_language' => $target_lang] + ); + + return $to; + } + + /** + * Removes ACF metas from metas to be synchronized by Polylang. + * To use only the ACF integration synchronization mechanism. + * + * @since 3.7 + * + * @param string[] $metas List of custom fields names. + * @param bool $sync True if it is synchronization, false if it is a copy. + * @param int|string $from ID of the object from which we copy information. + * @param int|string $to ID of the object to which we copy information. + * + * @return string[] + */ + public static function remove_acf_metas_from_pll_sync($metas, $sync, $from, $to) + { + if (!is_array($metas)) { + return $metas; + } + + $from = static::acf_id((int) $from); + $to = static::acf_id((int) $to); + + $acf_metas = array_merge((array) acf_get_meta($from), (array) acf_get_meta($to)); + $acf_metas = array_keys($acf_metas); + + return array_diff($metas, $acf_metas); + } + + /** + * Returns current object ID. + * + * @since 3.7 + * + * @return int + */ + public function get_id(): int + { + return $this->id; + } + + /** + * Gets the ACF field key to store. + * + * @since 3.7 + * + * @param int $id Object ID. + * @param string $key The custom field key. + * + * @return string + */ + protected function get_storage_key($id, $key) + { + return static::acf_id($id).'|'.$key; + } + + /** + * Returns the object ID. + * + * @since 3.7 + * + * @param object $object The object. + * + * @return int + */ + abstract protected function get_object_id($object): int; + + /** + * Transforms an object ID to the corresponding ACF post ID. + * + * @since 3.7 + * + * @param int $id Object ID. + * + * @return int|string ACF post ID. + */ + abstract protected static function acf_id($id); + + /** + * Returns source object ID passed in the main request if exists. + * + * @since 3.7 + * + * @return int + */ + abstract protected function get_from_id_in_request(): int; + + /** + * Returns current object type. + * + * The returned value must match: + * - the name of the property storing the corresponding model (`PLL()->model->{type}`). + * - the `object_type` from `PLL_Export_Data::add_translation_entry()`. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-falsy-string + */ + abstract public function get_type(): string; } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Blocks.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Blocks.php index 2a1e13e3d046847bef814c74eed47e84175af4fe..a559e52cc920ee2a1df318d9738a075290d2788b 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Blocks.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Blocks.php @@ -1,18 +1,15 @@ id = $id; - } - - /** - * Copies the field values in blocks. - * - * @since 3.7 - * - * @param array $blocks The blocks. - * @param PLL_Language $target_lang The target language. - * @param WP_Post $source_post The source post. - * @return array The blocks. - */ - public function copy( array $blocks, PLL_Language $target_lang, WP_Post $source_post ) { - return $this->apply_on_blocks( - new Copy(), - $blocks, - $source_post->ID, - $target_lang - ); - } - - /** - * Translates fields in blocks during import. - * - * @since 3.7 - * - * @param WP_Post $to The translated post. - * @param PLL_Language $target_lang The target language. - * @param Translations $translations A set of translations to search the custom fields translations in. - * @return WP_Post The translated post. - */ - public function translate( WP_Post $to, PLL_Language $target_lang, Translations $translations ): WP_Post { - $post = get_post( $this->id ); - - if ( empty( $post ) || ! has_blocks( $post ) ) { - return $to; - } - - $new_post_content = serialize_blocks( - $this->apply_on_blocks( - new Import( $translations ), - parse_blocks( $to->post_content ), - $post->ID, - $target_lang - ) - ); - - $to->post_content = $new_post_content; - - return $to; - } - - /** - * Adds ACF fields to the exported data. - * - * @since 3.7 - * - * @param PLL_Export_Data $export The export data. - * @return void - */ - public function export( PLL_Export_Data $export ) { - $post = get_post( $this->id ); - - if ( empty( $post ) || ! has_blocks( $post ) ) { - return; - } - - $this->apply_on_blocks( - new Export( $export ), - parse_blocks( $post->post_content ), - $post->ID, - $export->get_target_language() - ); - } - - /** - * Executes a strategy on blocks from the current post to a target post. - * - * @since 3.7 - * - * @param Abstract_Strategy $strategy Strategy to execute. - * @param int $to ID of the target post. Not used. - * @param array $args { - * Array of arguments. - * - * @type PLL_Language|null $target_language The language used to apply the strategy. - * } - * @return void - */ - public function apply_to_all_fields( Abstract_Strategy $strategy, int $to = 0, array $args = array() ) { - $post = get_post( $this->id ); - - if ( empty( $post ) || ! has_blocks( $post ) ) { - return; - } - - $this->apply_on_blocks( - $strategy, - parse_blocks( $post->post_content ), - $post->ID, - $args['target_language'] ?? null - ); - } - - /** - * Apply given strategy to field values in blocks. - * - * @since 3.7 - * - * @param Abstract_Strategy $strategy The strategy. - * @param array $blocks List of blocks. - * @param int $id The post ID. - * @param PLL_Language|null $language The language used to apply the strategy: - * the target language when applying a translate strategy, - * unused i.e. null when collecting linked content. - * @return array The blocks. - */ - private function apply_on_blocks( Abstract_Strategy $strategy, array $blocks, int $id, ?PLL_Language $language = null ): array { - foreach ( $blocks as &$block ) { - if ( ! isset( $block['blockName'], $block['attrs'] ) ) { - // No can do. - continue; - } - - if ( empty( acf_get_block_type( $block['blockName'] ) ) ) { - if ( ! empty( $block['innerBlocks'] ) ) { - $block['innerBlocks'] = $this->apply_on_blocks( $strategy, $block['innerBlocks'], $id, $language ); - } - continue; - } - - /* - * Ensure the block has the required keys for `acf_prepare_block()`. - */ - $block['name'] = $block['blockName']; - if ( empty( $block['id'] ) ) { - $block['id'] = acf_ensure_block_id_prefix( acf_get_block_id( $block['attrs'] ) ); - } - - $block = acf_prepare_block( $block ); - - if ( ! isset( $block['data'] ) ) { - // No can do. - continue; - } - - // Backward compatibility with ACF < 6.3. - if ( function_exists( 'acf_add_block_meta_values' ) && function_exists( 'acf_block_uses_post_meta' ) && acf_block_uses_post_meta( $block ) ) { - $block = acf_add_block_meta_values( $block, $id ); - $block['attrs']['data'] = $block['data']; - } - - if ( ! isset( $block['attrs']['data'] ) ) { - // No can do. - continue; - } - - /* - * Loads block fields values like ACF does. - * @see {acf_render_block()}. - */ - acf_setup_meta( $block['attrs']['data'], $block['id'], true ); - - $values = array(); - foreach ( acf_get_block_fields( $block['attrs'] ) as $field ) { - $value = acf_get_value( $block['id'], $field ); - $values[ $field['key'] ] = $strategy->execute( - new Post( $id ), - $value, - $field, - array( - 'target_language' => $language, - 'original_value' => $field['default_value'] ?? null, - ) - ); - } - - if ( ! empty( $values ) ) { - /* - * Converts "data" to "meta" and ensures the block attributes are all set. - * `acf_setup_meta()` cannot be used directly because it relies on form data, which is not the case here. - * @see {acf_parse_save_blocks_callback()}. - */ - $block['attrs']['data'] = acf_get_instance( 'ACF_Local_Meta' ) - ->capture( - $values, - $block['id'] - ); - - // Backward compatibility with ACF < 6.3. - if ( function_exists( 'acf_block_uses_post_meta' ) && acf_block_uses_post_meta( $block ) ) { - $block = $this->prepare_block_post_meta( $block ); - } - } - - /* - * Reset block data for safety, so we don't change its data tree by mistake. - * @see {acf_render_block()}. - */ - acf_reset_meta( $block['id'] ); - - if ( ! empty( $block['innerBlocks'] ) ) { - $block['innerBlocks'] = $this->apply_on_blocks( $strategy, $block['innerBlocks'], $id, $language ); - } - } - - return $blocks; - } - - /** - * Converts block data using meta keys to block data using field keys. - * ACF requires a specific format for it to be saved in post meta. - * - * @since 3.7 - * - * @param array $block The block to convert. - * @return array The converted block. - */ - private function prepare_block_post_meta( array $block ): array { - $new_data = array(); - foreach ( $block['attrs']['data'] as $key => $value ) { - if ( ! str_starts_with( $key, '_' ) || empty( $value ) ) { - continue; - } - - // We got a field key in `$value`. - $field = acf_get_field( $value ); - if ( empty( $field ) ) { - continue; - } - - $new_data[ $field['key'] ] = acf_get_value( $block['id'], $field ); - } - - $block['attrs']['data'] = $new_data; - - return $block; - } +class Blocks implements Translatable_Entity_Interface +{ + /** + * ID of the post that contains blocks. + * + * @var int + */ + private $id; + + /** + * Constructor. + * + * @since 3.7 + * + * @param int $id The post ID, defaults to 0. + */ + public function __construct(int $id = 0) + { + $this->id = $id; + } + + /** + * Copies the field values in blocks. + * + * @since 3.7 + * + * @param array $blocks The blocks. + * @param PLL_Language $target_lang The target language. + * @param WP_Post $source_post The source post. + * + * @return array The blocks. + */ + public function copy(array $blocks, PLL_Language $target_lang, WP_Post $source_post) + { + return $this->apply_on_blocks( + new Copy(), + $blocks, + $source_post->ID, + $target_lang + ); + } + + /** + * Translates fields in blocks during import. + * + * @since 3.7 + * + * @param WP_Post $to The translated post. + * @param PLL_Language $target_lang The target language. + * @param Translations $translations A set of translations to search the custom fields translations in. + * + * @return WP_Post The translated post. + */ + public function translate(WP_Post $to, PLL_Language $target_lang, Translations $translations): WP_Post + { + $post = get_post($this->id); + + if (empty($post) || !has_blocks($post)) { + return $to; + } + + $new_post_content = serialize_blocks( + $this->apply_on_blocks( + new Import($translations), + parse_blocks($to->post_content), + $post->ID, + $target_lang + ) + ); + + $to->post_content = $new_post_content; + + return $to; + } + + /** + * Adds ACF fields to the exported data. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export data. + * + * @return void + */ + public function export(PLL_Export_Data $export) + { + $post = get_post($this->id); + + if (empty($post) || !has_blocks($post)) { + return; + } + + $this->apply_on_blocks( + new Export($export), + parse_blocks($post->post_content), + $post->ID, + $export->get_target_language() + ); + } + + /** + * Executes a strategy on blocks from the current post to a target post. + * + * @since 3.7 + * + * @param Abstract_Strategy $strategy Strategy to execute. + * @param int $to ID of the target post. Not used. + * @param array $args { + * Array of arguments. + * + * @var PLL_Language|null $target_language The language used to apply the strategy. + * } + * + * @return void + */ + public function apply_to_all_fields(Abstract_Strategy $strategy, int $to = 0, array $args = []) + { + $post = get_post($this->id); + + if (empty($post) || !has_blocks($post)) { + return; + } + + $this->apply_on_blocks( + $strategy, + parse_blocks($post->post_content), + $post->ID, + $args['target_language'] ?? null + ); + } + + /** + * Apply given strategy to field values in blocks. + * + * @since 3.7 + * + * @param Abstract_Strategy $strategy The strategy. + * @param array $blocks List of blocks. + * @param int $id The post ID. + * @param PLL_Language|null $language The language used to apply the strategy: + * the target language when applying a translate strategy, + * unused i.e. null when collecting linked content. + * + * @return array The blocks. + */ + private function apply_on_blocks(Abstract_Strategy $strategy, array $blocks, int $id, ?PLL_Language $language = null): array + { + foreach ($blocks as &$block) { + if (!isset($block['blockName'], $block['attrs'])) { + // No can do. + continue; + } + + if (empty(acf_get_block_type($block['blockName']))) { + if (!empty($block['innerBlocks'])) { + $block['innerBlocks'] = $this->apply_on_blocks($strategy, $block['innerBlocks'], $id, $language); + } + continue; + } + + /* + * Ensure the block has the required keys for `acf_prepare_block()`. + */ + $block['name'] = $block['blockName']; + if (empty($block['id'])) { + $block['id'] = acf_ensure_block_id_prefix(acf_get_block_id($block['attrs'])); + } + + $block = acf_prepare_block($block); + + if (!isset($block['data'])) { + // No can do. + continue; + } + + // Backward compatibility with ACF < 6.3. + if (function_exists('acf_add_block_meta_values') && function_exists('acf_block_uses_post_meta') && acf_block_uses_post_meta($block)) { + $block = acf_add_block_meta_values($block, $id); + $block['attrs']['data'] = $block['data']; + } + + if (!isset($block['attrs']['data'])) { + // No can do. + continue; + } + + /* + * Loads block fields values like ACF does. + * @see {acf_render_block()}. + */ + acf_setup_meta($block['attrs']['data'], $block['id'], true); + + $values = []; + foreach (acf_get_block_fields($block['attrs']) as $field) { + $value = acf_get_value($block['id'], $field); + $values[$field['key']] = $strategy->execute( + new Post($id), + $value, + $field, + [ + 'target_language' => $language, + 'original_value' => $field['default_value'] ?? null, + ] + ); + } + + if (!empty($values)) { + /* + * Converts "data" to "meta" and ensures the block attributes are all set. + * `acf_setup_meta()` cannot be used directly because it relies on form data, which is not the case here. + * @see {acf_parse_save_blocks_callback()}. + */ + $block['attrs']['data'] = acf_get_instance('ACF_Local_Meta') + ->capture( + $values, + $block['id'] + ); + + // Backward compatibility with ACF < 6.3. + if (function_exists('acf_block_uses_post_meta') && acf_block_uses_post_meta($block)) { + $block = $this->prepare_block_post_meta($block); + } + } + + /* + * Reset block data for safety, so we don't change its data tree by mistake. + * @see {acf_render_block()}. + */ + acf_reset_meta($block['id']); + + if (!empty($block['innerBlocks'])) { + $block['innerBlocks'] = $this->apply_on_blocks($strategy, $block['innerBlocks'], $id, $language); + } + } + + return $blocks; + } + + /** + * Converts block data using meta keys to block data using field keys. + * ACF requires a specific format for it to be saved in post meta. + * + * @since 3.7 + * + * @param array $block The block to convert. + * + * @return array The converted block. + */ + private function prepare_block_post_meta(array $block): array + { + $new_data = []; + foreach ($block['attrs']['data'] as $key => $value) { + if (!str_starts_with($key, '_') || empty($value)) { + continue; + } + + // We got a field key in `$value`. + $field = acf_get_field($value); + if (empty($field)) { + continue; + } + + $new_data[$field['key']] = acf_get_value($block['id'], $field); + } + + $block['attrs']['data'] = $new_data; + + return $block; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Media.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Media.php index 02a5e2a66c3be14b5d5a12abb666f0da6004f62b..0847e947233b06468fa7145bbd4d71ed1fdbc031 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Media.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Media.php @@ -1,7 +1,4 @@ model->get_language( $target_language ); - if ( empty( $target_language ) ) { - return; - } - - $this->maybe_reset_fields_store( $target_language ); +class Media extends Post +{ + /** + * Copies media fields when a new translation is created. + * + * @since 3.7 + * + * @param int $to_id Target media ID. + * @param PLL_Language $target_language The language to translate into. + * + * @return void + */ + public function copy_fields($to_id, $target_language) + { + $target_language = PLL()->model->get_language($target_language); + if (empty($target_language)) { + return; + } - $this->apply_to_all_fields( new Copy(), $to_id, array( 'target_language' => $target_language ) ); - } + $this->maybe_reset_fields_store($target_language); - /** - * Transforms a post ID to the corresponding ACF post ID. - * - * @since 3.7 - * - * @param int $id Post ID. - * @return string ACF post ID. - */ - protected static function acf_id( $id ): string { - return 'attachment_' . $id; - } + $this->apply_to_all_fields(new Copy(), $to_id, ['target_language' => $target_language]); + } - /** - * Does nothing for media. `self::translate_fields()` does the job instead. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return array Custom field of the target object. - */ - public function render_field( $field ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + /** + * Transforms a post ID to the corresponding ACF post ID. + * + * @since 3.7 + * + * @param int $id Post ID. + * + * @return string ACF post ID. + */ + protected static function acf_id($id): string + { + return 'attachment_'.$id; + } - /* - * Does nothing, media translations are created in two times. - * 1. A request is made to create the media translation. - * 2. A redirect is made toward the edit page of the media, where ACF loads the fields. - */ - return $field; - } + /** + * Does nothing for media. `self::translate_fields()` does the job instead. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return array Custom field of the target object. + */ + public function render_field($field) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {/* + * Does nothing, media translations are created in two times. + * 1. A request is made to create the media translation. + * 2. A redirect is made toward the edit page of the media, where ACF loads the fields. + */ + return $field; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Post.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Post.php index 4f6b883e9cfbe7c45d478ed6ee1c57a2d5e334bc..884d66384294e697b7c8743ef300d4d18ffb700e 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Post.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Post.php @@ -1,14 +1,11 @@ ID; - } - - /** - * Transforms a post ID to the corresponding ACF post ID. - * - * @since 3.7 - * - * @param int $id Post ID. - * @return int ACF post ID. - */ - protected static function acf_id( $id ) { - return $id; - } - - /** - * Returns source object ID passed in the main request if exists. - * - * @since 3.7 - * - * @return int - */ - protected function get_from_id_in_request(): int { - if ( ! PLL()->links instanceof PLL_Admin_Links ) { - return 0; - } - - $data = PLL()->links->get_data_from_new_post_translation_request( - (string) get_post_type( $this->get_id() ) - ); - - return ! empty( $data['from_post'] ) ? $data['from_post']->ID : 0; - } - - /** - * Returns current object type. - * - * @since 3.7 - * - * @return string - * @phpstan-return non-falsy-string - */ - public function get_type(): string { - return 'post'; - } - - /** - * Copies or synchronizes ACF custom fields when using Polylang's copy post function (and not the post-new.php where ACF filters are applied). - * (e.g. using bulk translate, creating a synchronized post). - * - * @since 3.7 - * - * @param int $tr_post_id ID of the target post. - * @param string $lang Language of the target post. - * @param string $sync `sync` if doing synchro, `copy` otherwise. - * @return void - * - * @phpstan-param 'sync'|'copy' $sync - */ - public function on_post_synchronized( $tr_post_id, $lang, $sync ) { - $lang = PLL()->model->get_language( $lang ); - if ( empty( $lang ) ) { - return; - } - - $this->maybe_reset_fields_store( $lang ); - - if ( PLL_Sync_Post_Model::COPY === $sync ) { - $this->apply_to_all_fields( new Copy(), $tr_post_id, array( 'target_language' => $lang ) ); - return; - } - - // Sync all custom fields between synchronized posts. - $post_id = $this->get_id(); - foreach ( pll_get_post_translations( $post_id ) as $tr_lang => $tr_id ) { - if ( $tr_id === $post_id || ! PLL()->sync_post_model->are_synchronized( $post_id, $tr_id ) ) { - continue; - } - /** @var PLL_Language */ - $tr_lang = PLL()->model->get_language( $tr_lang ); - - $this->apply_to_all_fields( new Copy_All(), $tr_id, array( 'target_language' => $tr_lang ) ); - } - } - - /** - * Resets the `fields` store to translate the default values in the correct language. - * Only if the current target language has been changed. - * - * @since 3.7 - * - * @param PLL_Language $lang Language of the target post. - * @return void - */ - protected function maybe_reset_fields_store( PLL_Language $lang ) { - if ( self::$previous_lang !== $lang->slug ) { - $store = acf_get_store( 'fields' ); - $store->reset(); - self::$previous_lang = $lang->slug; - } - } +class Post extends Abstract_Object +{ + /** + * The previous language slug of the target post. + * + * @var string + */ + protected static $previous_lang = ''; + + /** + * Returns the object ID. + * + * @since 3.7 + * + * @param WP_Post $object The object. + * + * @return int + */ + protected function get_object_id($object): int + { + return $object->ID; + } + + /** + * Transforms a post ID to the corresponding ACF post ID. + * + * @since 3.7 + * + * @param int $id Post ID. + * + * @return int ACF post ID. + */ + protected static function acf_id($id) + { + return $id; + } + + /** + * Returns source object ID passed in the main request if exists. + * + * @since 3.7 + * + * @return int + */ + protected function get_from_id_in_request(): int + { + if (!PLL()->links instanceof PLL_Admin_Links) { + return 0; + } + + $data = PLL()->links->get_data_from_new_post_translation_request( + (string) get_post_type($this->get_id()) + ); + + return !empty($data['from_post']) ? $data['from_post']->ID : 0; + } + + /** + * Returns current object type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-falsy-string + */ + public function get_type(): string + { + return 'post'; + } + + /** + * Copies or synchronizes ACF custom fields when using Polylang's copy post function (and not the post-new.php where ACF filters are applied). + * (e.g. using bulk translate, creating a synchronized post). + * + * @since 3.7 + * + * @param int $tr_post_id ID of the target post. + * @param string $lang Language of the target post. + * @param string $sync `sync` if doing synchro, `copy` otherwise. + * + * @return void + * + * @phpstan-param 'sync'|'copy' $sync + */ + public function on_post_synchronized($tr_post_id, $lang, $sync) + { + $lang = PLL()->model->get_language($lang); + if (empty($lang)) { + return; + } + + $this->maybe_reset_fields_store($lang); + + if (PLL_Sync_Post_Model::COPY === $sync) { + $this->apply_to_all_fields(new Copy(), $tr_post_id, ['target_language' => $lang]); + + return; + } + + // Sync all custom fields between synchronized posts. + $post_id = $this->get_id(); + foreach (pll_get_post_translations($post_id) as $tr_lang => $tr_id) { + if ($tr_id === $post_id || !PLL()->sync_post_model->are_synchronized($post_id, $tr_id)) { + continue; + } + /** @var PLL_Language */ + $tr_lang = PLL()->model->get_language($tr_lang); + + $this->apply_to_all_fields(new Copy_All(), $tr_id, ['target_language' => $tr_lang]); + } + } + + /** + * Resets the `fields` store to translate the default values in the correct language. + * Only if the current target language has been changed. + * + * @since 3.7 + * + * @param PLL_Language $lang Language of the target post. + * + * @return void + */ + protected function maybe_reset_fields_store(PLL_Language $lang) + { + if (self::$previous_lang !== $lang->slug) { + $store = acf_get_store('fields'); + $store->reset(); + self::$previous_lang = $lang->slug; + } + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Term.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Term.php index 6337dc433c9681057b880135286722189b3518e2..1495544431dc1fd86ff33d36e255e58aa9fc5a8e 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Term.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Term.php @@ -1,7 +1,4 @@ term_id; - } +class Term extends Abstract_Object +{ + /** + * Returns the object ID. + * + * @since 3.7 + * + * @param WP_Term $object The object. + * + * @return int + */ + protected function get_object_id($object): int + { + return $object->term_id; + } - /** - * Transforms a term ID to the corresponding ACF post ID. - * - * @since 3.7 - * - * @param int $id Term ID. - * @return string ACF post ID. - */ - protected static function acf_id( $id ) { - return 'term_' . $id; - } + /** + * Transforms a term ID to the corresponding ACF post ID. + * + * @since 3.7 + * + * @param int $id Term ID. + * + * @return string ACF post ID. + */ + protected static function acf_id($id) + { + return 'term_'.$id; + } - /** - * Returns source object ID passed in the main request if exists. - * - * @since 3.7 - * - * @return int - */ - protected function get_from_id_in_request(): int { - if ( isset( $_GET['taxonomy'], $_GET['from_tag'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - return (int) $_GET['from_tag']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended - } + /** + * Returns source object ID passed in the main request if exists. + * + * @since 3.7 + * + * @return int + */ + protected function get_from_id_in_request(): int + { + if (isset($_GET['taxonomy'], $_GET['from_tag'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return (int) $_GET['from_tag']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } - return 0; - } + return 0; + } - /** - * Returns current object type. - * - * @since 3.7 - * - * @return string - * @phpstan-return non-falsy-string - */ - public function get_type(): string { - return 'term'; - } + /** + * Returns current object type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-falsy-string + */ + public function get_type(): string + { + return 'term'; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Translatable_Entity_Interface.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Translatable_Entity_Interface.php index bbac41b4c94581ea73e6b2809829c82c7b37f66b..a9b1e670d87a3c42c45e857444f8988570f95bf3 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Translatable_Entity_Interface.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Entity/Translatable_Entity_Interface.php @@ -1,7 +1,4 @@ category ) { - add_action( "acf/render_field_settings/type={$type->name}", array( $this, 'render_field_settings' ) ); - } - } - } +class Field_Settings +{ + /** + * Setups actions. + * + * @since 3.7 + * + * @return void + */ + public function on_acf_init() + { + // Adds the field setting, except for fields of type layout. + foreach (acf_get_field_types() as $type) { // Since ACF 5.6.0. + if ('layout' !== $type->category) { + add_action("acf/render_field_settings/type={$type->name}", [$this, 'render_field_settings']); + } + } + } - /** - * Tells if the given fields group was created with Polylang Pro < 3.7 and had been translated. - * - * @since 3.7.3 - * - * @param int $id The fields group ID to check. - * @return bool True if the fields group is from Polylang Pro < 3.7 and had been translated, false otherwise. - */ - public static function is_legacy_translated_field_group( int $id ) { - return ! empty( - get_terms( - array( - 'taxonomy' => 'language', - 'fields' => 'ids', - 'object_ids' => $id, - ) - ) - ); - } + /** + * Tells if the given fields group was created with Polylang Pro < 3.7 and had been translated. + * + * @since 3.7.3 + * + * @param int $id The fields group ID to check. + * + * @return bool True if the fields group is from Polylang Pro < 3.7 and had been translated, false otherwise. + */ + public static function is_legacy_translated_field_group(int $id) + { + return !empty( + get_terms( + [ + 'taxonomy' => 'language', + 'fields' => 'ids', + 'object_ids' => $id, + ] + ) + ); + } - /** - * Renders translations setting and its default value according to the field type. - * - * @since 2.7 - * @since 3.3.1 Renamed and merged two methods. - * @since 3.7 Added `translate_once` option. - * - * @param array $field Custom field definition. - * @return void - */ - public function render_field_settings( $field ) { - $field_group = Location_Language::get_field_group_from_field( $field ); - if ( ! empty( $field_group ) && Location_Language::has_language_location_rule( $field_group ) && ! $this::is_legacy_translated_field_group( (int) $field_group['ID'] ) ) { - return; - } + /** + * Renders translations setting and its default value according to the field type. + * + * @since 2.7 + * @since 3.3.1 Renamed and merged two methods. + * @since 3.7 Added `translate_once` option. + * + * @param array $field Custom field definition. + * + * @return void + */ + public function render_field_settings($field) + { + $field_group = Location_Language::get_field_group_from_field($field); + if (!empty($field_group) && Location_Language::has_language_location_rule($field_group) && !$this::is_legacy_translated_field_group((int) $field_group['ID'])) { + return; + } - $choices = array( - 'ignore' => __( 'Ignore', 'polylang-pro' ), - 'copy_once' => __( 'Copy once', 'polylang-pro' ), - 'sync' => __( 'Synchronize', 'polylang-pro' ), - ); - $default = in_array( 'post_meta', PLL()->options['sync'] ) ? 'sync' : 'copy_once'; + $choices = [ + 'ignore' => __('Ignore', 'polylang-pro'), + 'copy_once' => __('Copy once', 'polylang-pro'), + 'sync' => __('Synchronize', 'polylang-pro'), + ]; + $default = in_array('post_meta', PLL()->options['sync']) ? 'sync' : 'copy_once'; - switch ( $field['type'] ) { - case 'text': - case 'textarea': - case 'wysiwyg': - if ( empty( $field['ID'] ) ) { // Workaround a bug in ACF which doesn't save options added after a field has been created. - $default = 'translate'; - } - // Intentional fall-through to add the translate options below. + switch ($field['type']) { + case 'text': + case 'textarea': + case 'wysiwyg': + if (empty($field['ID'])) { // Workaround a bug in ACF which doesn't save options added after a field has been created. + $default = 'translate'; + } + // Intentional fall-through to add the translate options below. - case 'email': - case 'oembed': - case 'url': - // Add translate and translate_once option from the 3rd position. - $choices = array_merge( - array_slice( $choices, 0, 2 ), - array( - 'translate' => __( 'Translate', 'polylang-pro' ), - 'translate_once' => __( 'Translate once', 'polylang-pro' ), - ), - array_slice( $choices, -1 ) - ); - break; - } - $this->render_field_setting( $field, $choices, $default ); - } + case 'email': + case 'oembed': + case 'url': + // Add translate and translate_once option from the 3rd position. + $choices = array_merge( + array_slice($choices, 0, 2), + [ + 'translate' => __('Translate', 'polylang-pro'), + 'translate_once' => __('Translate once', 'polylang-pro'), + ], + array_slice($choices, -1) + ); + break; + } + $this->render_field_setting($field, $choices, $default); + } - /** - * Renders the translations setting. - * - * @since 2.7 - * - * @param array $field Custom field definition. - * @param array $choices An array of choices for the select (value as key and label as value). - * @param string $default Default value for the select. - * @return void - */ - protected function render_field_setting( $field, $choices, $default ) { - acf_render_field_setting( // Since ACF 5.7.10. - $field, - array( - 'label' => __( 'Translations', 'polylang-pro' ), - 'instructions' => '', - 'name' => 'translations', - 'type' => 'select', - 'choices' => $choices, - 'default_value' => $default, - ), - false // The setting is depending on the type of field. - ); - } + /** + * Renders the translations setting. + * + * @since 2.7 + * + * @param array $field Custom field definition. + * @param array $choices An array of choices for the select (value as key and label as value). + * @param string $default Default value for the select. + * + * @return void + */ + protected function render_field_setting($field, $choices, $default) + { + acf_render_field_setting( // Since ACF 5.7.10. + $field, + [ + 'label' => __('Translations', 'polylang-pro'), + 'instructions' => '', + 'name' => 'translations', + 'type' => 'select', + 'choices' => $choices, + 'default_value' => $default, + ], + false // The setting is depending on the type of field. + ); + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Abstract_Object_Type.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Abstract_Object_Type.php index bc0fa791469f69d6028a437411acfe79f8f7d3e7..4e9ac5ffd19dd77701ec08f8144bcf862f14d7e1 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Abstract_Object_Type.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Abstract_Object_Type.php @@ -1,12 +1,9 @@ register_strings(); - - if ( did_action( 'pll_language_defined' ) ) { - $this->translate_registered_strings(); - } else { - // Special case when the language is set from the content as CPT and taxonomies are registered before the language is defined. - add_action( 'pll_language_defined', array( $this, 'translate_registered_strings' ) ); - } - } - - /** - * Registers strings for custom post types or taxonomies labels. - * - * @since 3.7 - * - * @return void - */ - public function register_strings(): void { - foreach ( $this->get_acf_type_instance()->get_posts( array( 'active' => true ) ) as $acf_object ) { - $label_start = sprintf( 'ACF %s, %s,', $this->get_type_label(), $acf_object[ $this->get_type() ] ); - - pll_register_string( "{$label_start} title", $acf_object['title'], 'ACF' ); - pll_register_string( "{$label_start} description", $acf_object['description'], 'ACF', true ); - - foreach ( $acf_object['labels'] as $key => $label ) { - pll_register_string( "{$label_start} {$key}", $label, 'ACF' ); - } - } - } - - /** - * Translates custom post type and taxonomy labels when the language is ready. - * - * @since 3.7 - * - * @return void - */ - public function translate_registered_strings(): void { - $acf_objects = $this->get_acf_type_instance()->get_posts( array( 'active' => true ) ); - $acf_objects = array_column( $acf_objects, $this->get_type(), $this->get_type() ); - $acf_objects = array_intersect_key( $this->get_type_objects(), $acf_objects ); - - foreach ( $acf_objects as $type ) { - $type->label = pll__( $type->label ); - $type->description = pll__( $type->description ); - - foreach ( array_keys( get_object_vars( $type->labels ) ) as $key ) { - $type->labels->$key = pll__( $type->labels->$key ); - } - } - } - - /** - * Returns the type. - * - * @since 3.7 - * - * @return string - * - * @phpstan-return non-falsy-string - */ - abstract protected function get_type(): string; - - /** - * Returns the instance of the related "ACF type". - * - * @since 3.7 - * - * @return ACF_Internal_Post_Type - */ - abstract protected function get_acf_type_instance(): ACF_Internal_Post_Type; - - /** - * Returns the list of type objects containing labels. - * - * @since 3.7 - * - * @return object[] - * - * @phpstan-return array< - * non-falsy-string, - * object{label: string, description: string, labels: object}&stdClass - * > - */ - abstract protected function get_type_objects(): array; - - /** - * Returns the label of the type. - * - * @since 3.7 - * - * @return string - * - * @phpstan-return non-empty-string - */ - abstract protected function get_type_label(): string; +abstract class Abstract_Object_Type +{ + /** + * Setups actions and filters. + * + * @since 3.7 + * + * @return void + */ + public function on_acf_init(): void + { + if (!defined('ACF_VERSION') || version_compare(ACF_VERSION, '6.1.0', '<')) { + // Backward compatibility with ACF < 6.1. + return; + } + + if (!acf_get_setting('enable_post_types')) { + // The feature is deactivated. + return; + } + + /* + * After `ACF::init()` (prio 5). + * Otherwise the classes `ACF_Post_Type` and `ACF_Taxonomy` won't exist (their file is included there). + */ + $this->register_strings(); + + if (did_action('pll_language_defined')) { + $this->translate_registered_strings(); + } else { + // Special case when the language is set from the content as CPT and taxonomies are registered before the language is defined. + add_action('pll_language_defined', [$this, 'translate_registered_strings']); + } + } + + /** + * Registers strings for custom post types or taxonomies labels. + * + * @since 3.7 + * + * @return void + */ + public function register_strings(): void + { + foreach ($this->get_acf_type_instance()->get_posts(['active' => true]) as $acf_object) { + $label_start = sprintf('ACF %s, %s,', $this->get_type_label(), $acf_object[$this->get_type()]); + + pll_register_string("{$label_start} title", $acf_object['title'], 'ACF'); + pll_register_string("{$label_start} description", $acf_object['description'], 'ACF', true); + + foreach ($acf_object['labels'] as $key => $label) { + pll_register_string("{$label_start} {$key}", $label, 'ACF'); + } + } + } + + /** + * Translates custom post type and taxonomy labels when the language is ready. + * + * @since 3.7 + * + * @return void + */ + public function translate_registered_strings(): void + { + $acf_objects = $this->get_acf_type_instance()->get_posts(['active' => true]); + $acf_objects = array_column($acf_objects, $this->get_type(), $this->get_type()); + $acf_objects = array_intersect_key($this->get_type_objects(), $acf_objects); + + foreach ($acf_objects as $type) { + $type->label = pll__($type->label); + $type->description = pll__($type->description); + + foreach (array_keys(get_object_vars($type->labels)) as $key) { + $type->labels->$key = pll__($type->labels->$key); + } + } + } + + /** + * Returns the type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-falsy-string + */ + abstract protected function get_type(): string; + + /** + * Returns the instance of the related "ACF type". + * + * @since 3.7 + * + * @return ACF_Internal_Post_Type + */ + abstract protected function get_acf_type_instance(): ACF_Internal_Post_Type; + + /** + * Returns the list of type objects containing labels. + * + * @since 3.7 + * + * @return object[] + * + * @phpstan-return array< + * non-falsy-string, + * object{label: string, description: string, labels: object}&stdClass + * > + */ + abstract protected function get_type_objects(): array; + + /** + * Returns the label of the type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-empty-string + */ + abstract protected function get_type_label(): string; } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Field_Groups.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Field_Groups.php index 20a36a3b4ed6ed83af13252152f5089ff873138b..6f10d94fdaf9649cc4e9c33269a7c3172765df99 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Field_Groups.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Field_Groups.php @@ -1,12 +1,9 @@ * @phpstan-type LabelsByType array */ -class Field_Groups { - /** - * @var array|null - * - * @phpstan-var LabelsByType|null - */ - private $labels; - - /** - * Setups actions and filters. - * - * @since 3.7 - * - * @return void - */ - public function on_acf_init(): void { - $this->register_field_groups(); - - if ( ! $this->can_translate_labels() ) { - return; - } - - add_filter( 'acf/load_field', array( $this, 'translate_field_labels' ), 20 ); // After `child_of_acf_field::load_field()` (prio 10). - add_filter( 'acf/load_field_groups', array( $this, 'translate_field_groups_labels' ), 25 ); // After `_acf_apply_get_local_internal_posts()` (prio 20). - } - - /** - * Registers the labels of all field groups. - * - * @since 3.7 - * - * @return void - */ - public function register_field_groups() { - acf_disable_filter( 'clone' ); // To allow seamless clone fields to appear in acf_get_fields(). - - foreach ( acf_get_field_groups() as $field_group ) { - pll_register_string( 'Title', $field_group['title'], 'ACF' ); - $this->register_fields( acf_get_fields( $field_group ) ); - } - - acf_enable_filter( 'clone' ); - } - - /** - * Recursively translates labels that are not originally translated by ACF for the given field. - * - * @since 3.7 - * - * @param array $field The field array. - * @return array - */ - public function translate_field_labels( $field ) { - if ( ! is_array( $field ) || ! isset( $field['type'] ) ) { - return $field; - } - - $matcher = new PLL_Format_Util(); - - foreach ( $this->get_field_labels_to_translate() as $field_type => $field_labels ) { - if ( ! $matcher->matches( $field['type'], $field_type ) ) { - continue; - } - - $field = $this->translate_field_labels_recursive( $field_labels, $field ); - } - - return $field; - } - - /** - * Tells if the labels should be translated in the current context. - * - * @since 3.7 - * - * @return bool - */ - private function can_translate_labels(): bool { - global $pagenow; - - // Polylang's settings pages. - if ( PLL() instanceof PLL_Settings ) { - return false; - } - - // ACF's settings pages. - $acf_post_types = array( 'acf-field-group', 'acf-post-type', 'acf-taxonomy', 'acf-ui-options-page' ); - - if ( 'edit.php' === $pagenow && isset( $_GET['post_type'] ) && in_array( $_GET['post_type'], $acf_post_types, true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - return false; - } - - if ( 'post.php' === $pagenow && isset( $_GET['post'] ) && in_array( get_post_type( (int) $_GET['post'] ), $acf_post_types, true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended - return false; - } - - return true; - } - - /** - * Recursively translates the given labels for the given field or subset of a field (e.g subfields or layouts). - * - * @since 3.7 - * - * @param array $labels An array of labels to translate with label keys as array keys and label names as array values. - * @param array $field A custom field definition. - * @return array The translated labels. - * - * @phpstan-param LabelsMap $labels - */ - private function translate_field_labels_recursive( array $labels, array $field ) { - $matcher = new PLL_Format_Util(); - - if ( isset( $field['default_value'] ) ) { - // Stores the untranslated default value for later (before it - $field['pll_default_value'] = $field['default_value']; - } - - foreach ( $labels as $field_key => $sub_labels ) { - $field_filtered = $matcher->filter_list( $field, $field_key ); - - foreach ( $field_filtered as $key => $field_value ) { - if ( is_array( $sub_labels ) ) { - /** - * `$sub_labels` is like: - * array( - * 'default_value' => _x( 'Default value', 'ACF field setting label', 'polylang-pro' ), - * 'placeholder' => _x( 'Placeholder', 'ACF field setting label', 'polylang-pro' ), - * ) - */ - if ( is_array( $field_value ) ) { - $field[ $key ] = $this->translate_field_labels_recursive( $sub_labels, $field_value ); - } - } elseif ( '' !== $field_value ) { - $field[ $key ] = pll__( $field_value ); - } - } - } - - return $field; - } - - /** - * Translates all the ACF field groups labels. - * - * We hook to 'acf/load_field_groups' instead 'acf/translate_field_group', - * not to be overridden by ACF when its local store is enabled. - * - * @see _acf_apply_get_local_internal_posts() - * - * @since 3.7 - * - * @param array[] $posts An array of ACF posts. - * @return array[] The array of ACF posts with the translated post title. - */ - public function translate_field_groups_labels( $posts ) { - foreach ( $posts as $key => $post ) { - $posts[ $key ]['title'] = pll__( $post['title'] ); - } - return $posts; - } - - /** - * Registers the labels of fields. - * The method is recursive for layout fields. - * - * @since 3.7 - * - * @param array $fields An array of Custom field definitions. - * @return void - */ - private function register_fields( $fields ) { - foreach ( $fields as $field ) { - switch ( $field['type'] ) { - case 'group': - case 'repeater': - $this->register_fields( $field['sub_fields'] ); - break; - case 'flexible_content': - foreach ( $field['layouts'] as $layout ) { - $this->register_fields( $layout['sub_fields'] ); - } - break; - } - - $matcher = new PLL_Format_Util(); - foreach ( $this->get_field_labels_to_translate() as $field_type => $field_labels ) { - if ( ! is_string( $field['type'] ) || ! $matcher->matches( $field['type'], (string) $field_type ) ) { - continue; - } - - $this->register_field( $field_labels, $field ); - } - - // Clears the field cache to let ACF generate field keys properly afterwards (e.g. for seamless clone fields). - acf_flush_field_cache( $field ); - } - } - - /** - * Registers the labels of a field recursively. - * - * @since 3.7 - * - * @param array $labels An array of labels to register with label keys as array keys and label names as array values. - * @param array $field A custom field definition. - * @return void - * - * @phpstan-param LabelsMap $labels - */ - private function register_field( array $labels, array $field ) { - $matcher = new PLL_Format_Util(); - - foreach ( $labels as $field_key => $label ) { - $field_filtered = $matcher->filter_list( $field, $field_key ); - - foreach ( $field_filtered as $field_value ) { - if ( is_array( $label ) ) { - /** - * `$label` is like: - * array( - * 'default_value' => _x( 'Default value', 'ACF field setting label', 'polylang-pro' ), - * 'placeholder' => _x( 'Placeholder', 'ACF field setting label', 'polylang-pro' ), - * ) - */ - if ( is_array( $field_value ) ) { - $this->register_field( $label, $field_value ); - } - } else { - pll_register_string( $label, $field_value, 'ACF', true ); - } - } - } - } - - /** - * Returns the list of field labels to translate, by field type and label key. - * Wildcards are supported for the field types and label keys. - * - * @since 3.7 - * - * @return array { - * An array with field types as array keys, and recursive sub-arrays as array values. - * These sub-arrays have label keys as array keys, and label names as array values. - * - * Ex: - * array( - * 'custom_field_type' => array( - * 'custom_field_key' => 'Custom field key', - * 'another_field_key' => array( - * 'first_choice' => 'First choice', - * 'second_choice' => 'Second choice', - * ), - * ), - * ) - * } - * - * @phpstan-return LabelsByType - */ - private function get_field_labels_to_translate(): array { - if ( is_array( $this->labels ) ) { - return $this->labels; - } - - $labels = array( - 'default_value' => _x( 'Default value', 'ACF field setting label', 'polylang-pro' ), - 'placeholder' => _x( 'Placeholder', 'ACF field setting label', 'polylang-pro' ), - 'prepend' => _x( 'Prefix', 'ACF field setting label', 'polylang-pro' ), - 'append' => _x( 'Suffix', 'ACF field setting label', 'polylang-pro' ), - 'message' => _x( 'Message', 'ACF field setting label', 'polylang-pro' ), - 'ui_on_text' => _x( 'ON text', 'ACF field setting label', 'polylang-pro' ), - 'ui_off_text' => _x( 'OFF text', 'ACF field setting label', 'polylang-pro' ), - 'choice' => _x( 'Choice', 'ACF field setting label', 'polylang-pro' ), - 'label' => _x( 'Label', 'ACF field setting label', 'polylang-pro' ), - ); - - $this->labels = array( - '*' => array( - 'label' => $labels['label'], - 'instructions' => _x( 'Instructions', 'ACF field setting label', 'polylang-pro' ), - ), - 'button_group' => array( - 'choices' => array( - '*' => $labels['choice'], - ), - ), - 'checkbox' => array( - 'choices' => array( - '*' => $labels['choice'], - ), - ), - 'email' => array( - 'default_value' => $labels['default_value'], - 'placeholder' => $labels['placeholder'], - 'prepend' => $labels['prepend'], - 'append' => $labels['append'], - ), - 'flexible_content' => array( - 'layouts' => array( - '*' => array( - 'label' => $labels['label'], - ), - ), - ), - 'number' => array( - 'placeholder' => $labels['placeholder'], - 'prepend' => $labels['prepend'], - 'append' => $labels['append'], - ), - 'message' => array( - 'message' => $labels['message'], - ), - 'password' => array( - 'placeholder' => $labels['placeholder'], - 'prepend' => $labels['prepend'], - 'append' => $labels['append'], - ), - 'radio' => array( - 'choices' => array( - '*' => $labels['choice'], - ), - ), - 'range' => array( - 'prepend' => $labels['prepend'], - 'append' => $labels['append'], - ), - 'select' => array( - 'choices' => array( - '*' => $labels['choice'], - ), - ), - 'text' => array( - 'default_value' => $labels['default_value'], - 'placeholder' => $labels['placeholder'], - 'prepend' => $labels['prepend'], - 'append' => $labels['append'], - ), - 'textarea' => array( - 'default_value' => $labels['default_value'], - 'placeholder' => $labels['placeholder'], - ), - 'true_false' => array( - 'message' => $labels['message'], - 'ui_on_text' => $labels['ui_on_text'], - 'ui_off_text' => $labels['ui_off_text'], - ), - 'url' => array( - 'default_value' => $labels['default_value'], - 'placeholder' => $labels['placeholder'], - ), - 'wysiwyg' => array( - 'default_value' => $labels['default_value'], - ), - ); - - /** - * Filters the list of field keys to translate, by field type. - * Wildcards are supported for the field types. - * - * @var array $labels array { - * An array with field types as array keys, and recursive sub-arrays as array values. - * These sub-arrays have label keys as array keys, and label names as array values. - * - * Ex: - * array( - * 'custom_field_type' => array( - * 'custom_field_key' => 'Custom field key', - * 'another_field_key' => array( - * 'first_choice' => 'First choice', - * 'second_choice' => 'Second choice', - * ), - * ), - * ) - * } - */ - $labels = apply_filters( 'pll_acf_field_labels_to_translate', $this->labels ); - - if ( is_array( $labels ) ) { - $this->labels = $labels; - } - - return $this->labels; - } +class Field_Groups +{ + /** + * @var array|null + * + * @phpstan-var LabelsByType|null + */ + private $labels; + + /** + * Setups actions and filters. + * + * @since 3.7 + * + * @return void + */ + public function on_acf_init(): void + { + $this->register_field_groups(); + + if (!$this->can_translate_labels()) { + return; + } + + add_filter('acf/load_field', [$this, 'translate_field_labels'], 20); // After `child_of_acf_field::load_field()` (prio 10). + add_filter('acf/load_field_groups', [$this, 'translate_field_groups_labels'], 25); // After `_acf_apply_get_local_internal_posts()` (prio 20). + } + + /** + * Registers the labels of all field groups. + * + * @since 3.7 + * + * @return void + */ + public function register_field_groups() + { + acf_disable_filter('clone'); // To allow seamless clone fields to appear in acf_get_fields(). + + foreach (acf_get_field_groups() as $field_group) { + pll_register_string('Title', $field_group['title'], 'ACF'); + $this->register_fields(acf_get_fields($field_group)); + } + + acf_enable_filter('clone'); + } + + /** + * Recursively translates labels that are not originally translated by ACF for the given field. + * + * @since 3.7 + * + * @param array $field The field array. + * + * @return array + */ + public function translate_field_labels($field) + { + if (!is_array($field) || !isset($field['type'])) { + return $field; + } + + $matcher = new PLL_Format_Util(); + + foreach ($this->get_field_labels_to_translate() as $field_type => $field_labels) { + if (!$matcher->matches($field['type'], $field_type)) { + continue; + } + + $field = $this->translate_field_labels_recursive($field_labels, $field); + } + + return $field; + } + + /** + * Tells if the labels should be translated in the current context. + * + * @since 3.7 + * + * @return bool + */ + private function can_translate_labels(): bool + { + global $pagenow; + + // Polylang's settings pages. + if (PLL() instanceof PLL_Settings) { + return false; + } + + // ACF's settings pages. + $acf_post_types = ['acf-field-group', 'acf-post-type', 'acf-taxonomy', 'acf-ui-options-page']; + + if ('edit.php' === $pagenow && isset($_GET['post_type']) && in_array($_GET['post_type'], $acf_post_types, true)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + if ('post.php' === $pagenow && isset($_GET['post']) && in_array(get_post_type((int) $_GET['post']), $acf_post_types, true)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + return true; + } + + /** + * Recursively translates the given labels for the given field or subset of a field (e.g subfields or layouts). + * + * @since 3.7 + * + * @param array $labels An array of labels to translate with label keys as array keys and label names as array values. + * @param array $field A custom field definition. + * + * @return array The translated labels. + * + * @phpstan-param LabelsMap $labels + */ + private function translate_field_labels_recursive(array $labels, array $field) + { + $matcher = new PLL_Format_Util(); + + if (isset($field['default_value'])) { + // Stores the untranslated default value for later (before it + $field['pll_default_value'] = $field['default_value']; + } + + foreach ($labels as $field_key => $sub_labels) { + $field_filtered = $matcher->filter_list($field, $field_key); + + foreach ($field_filtered as $key => $field_value) { + if (is_array($sub_labels)) { + /** + * `$sub_labels` is like: + * array( + * 'default_value' => _x( 'Default value', 'ACF field setting label', 'polylang-pro' ), + * 'placeholder' => _x( 'Placeholder', 'ACF field setting label', 'polylang-pro' ), + * ) + */ + if (is_array($field_value)) { + $field[$key] = $this->translate_field_labels_recursive($sub_labels, $field_value); + } + } elseif ('' !== $field_value) { + $field[$key] = pll__($field_value); + } + } + } + + return $field; + } + + /** + * Translates all the ACF field groups labels. + * + * We hook to 'acf/load_field_groups' instead 'acf/translate_field_group', + * not to be overridden by ACF when its local store is enabled. + * + * @see _acf_apply_get_local_internal_posts() + * @since 3.7 + * + * @param array[] $posts An array of ACF posts. + * + * @return array[] The array of ACF posts with the translated post title. + */ + public function translate_field_groups_labels($posts) + { + foreach ($posts as $key => $post) { + $posts[$key]['title'] = pll__($post['title']); + } + + return $posts; + } + + /** + * Registers the labels of fields. + * The method is recursive for layout fields. + * + * @since 3.7 + * + * @param array $fields An array of Custom field definitions. + * + * @return void + */ + private function register_fields($fields) + { + foreach ($fields as $field) { + switch ($field['type']) { + case 'group': + case 'repeater': + $this->register_fields($field['sub_fields']); + break; + case 'flexible_content': + foreach ($field['layouts'] as $layout) { + $this->register_fields($layout['sub_fields']); + } + break; + } + + $matcher = new PLL_Format_Util(); + foreach ($this->get_field_labels_to_translate() as $field_type => $field_labels) { + if (!is_string($field['type']) || !$matcher->matches($field['type'], (string) $field_type)) { + continue; + } + + $this->register_field($field_labels, $field); + } + + // Clears the field cache to let ACF generate field keys properly afterwards (e.g. for seamless clone fields). + acf_flush_field_cache($field); + } + } + + /** + * Registers the labels of a field recursively. + * + * @since 3.7 + * + * @param array $labels An array of labels to register with label keys as array keys and label names as array values. + * @param array $field A custom field definition. + * + * @return void + * + * @phpstan-param LabelsMap $labels + */ + private function register_field(array $labels, array $field) + { + $matcher = new PLL_Format_Util(); + + foreach ($labels as $field_key => $label) { + $field_filtered = $matcher->filter_list($field, $field_key); + + foreach ($field_filtered as $field_value) { + if (is_array($label)) { + /** + * `$label` is like: + * array( + * 'default_value' => _x( 'Default value', 'ACF field setting label', 'polylang-pro' ), + * 'placeholder' => _x( 'Placeholder', 'ACF field setting label', 'polylang-pro' ), + * ) + */ + if (is_array($field_value)) { + $this->register_field($label, $field_value); + } + } else { + pll_register_string($label, $field_value, 'ACF', true); + } + } + } + } + + /** + * Returns the list of field labels to translate, by field type and label key. + * Wildcards are supported for the field types and label keys. + * + * @since 3.7 + * + * @return array { + * An array with field types as array keys, and recursive sub-arrays as array values. + * These sub-arrays have label keys as array keys, and label names as array values. + * + * Ex: + * array( + * 'custom_field_type' => array( + * 'custom_field_key' => 'Custom field key', + * 'another_field_key' => array( + * 'first_choice' => 'First choice', + * 'second_choice' => 'Second choice', + * ), + * ), + * ) + * } + * + * @phpstan-return LabelsByType + */ + private function get_field_labels_to_translate(): array + { + if (is_array($this->labels)) { + return $this->labels; + } + + $labels = [ + 'default_value' => _x('Default value', 'ACF field setting label', 'polylang-pro'), + 'placeholder' => _x('Placeholder', 'ACF field setting label', 'polylang-pro'), + 'prepend' => _x('Prefix', 'ACF field setting label', 'polylang-pro'), + 'append' => _x('Suffix', 'ACF field setting label', 'polylang-pro'), + 'message' => _x('Message', 'ACF field setting label', 'polylang-pro'), + 'ui_on_text' => _x('ON text', 'ACF field setting label', 'polylang-pro'), + 'ui_off_text' => _x('OFF text', 'ACF field setting label', 'polylang-pro'), + 'choice' => _x('Choice', 'ACF field setting label', 'polylang-pro'), + 'label' => _x('Label', 'ACF field setting label', 'polylang-pro'), + ]; + + $this->labels = [ + '*' => [ + 'label' => $labels['label'], + 'instructions' => _x('Instructions', 'ACF field setting label', 'polylang-pro'), + ], + 'button_group' => [ + 'choices' => [ + '*' => $labels['choice'], + ], + ], + 'checkbox' => [ + 'choices' => [ + '*' => $labels['choice'], + ], + ], + 'email' => [ + 'default_value' => $labels['default_value'], + 'placeholder' => $labels['placeholder'], + 'prepend' => $labels['prepend'], + 'append' => $labels['append'], + ], + 'flexible_content' => [ + 'layouts' => [ + '*' => [ + 'label' => $labels['label'], + ], + ], + ], + 'number' => [ + 'placeholder' => $labels['placeholder'], + 'prepend' => $labels['prepend'], + 'append' => $labels['append'], + ], + 'message' => [ + 'message' => $labels['message'], + ], + 'password' => [ + 'placeholder' => $labels['placeholder'], + 'prepend' => $labels['prepend'], + 'append' => $labels['append'], + ], + 'radio' => [ + 'choices' => [ + '*' => $labels['choice'], + ], + ], + 'range' => [ + 'prepend' => $labels['prepend'], + 'append' => $labels['append'], + ], + 'select' => [ + 'choices' => [ + '*' => $labels['choice'], + ], + ], + 'text' => [ + 'default_value' => $labels['default_value'], + 'placeholder' => $labels['placeholder'], + 'prepend' => $labels['prepend'], + 'append' => $labels['append'], + ], + 'textarea' => [ + 'default_value' => $labels['default_value'], + 'placeholder' => $labels['placeholder'], + ], + 'true_false' => [ + 'message' => $labels['message'], + 'ui_on_text' => $labels['ui_on_text'], + 'ui_off_text' => $labels['ui_off_text'], + ], + 'url' => [ + 'default_value' => $labels['default_value'], + 'placeholder' => $labels['placeholder'], + ], + 'wysiwyg' => [ + 'default_value' => $labels['default_value'], + ], + ]; + + /** + * Filters the list of field keys to translate, by field type. + * Wildcards are supported for the field types. + * + * @var array $labels array { + * An array with field types as array keys, and recursive sub-arrays as array values. + * These sub-arrays have label keys as array keys, and label names as array values. + * + * Ex: + * array( + * 'custom_field_type' => array( + * 'custom_field_key' => 'Custom field key', + * 'another_field_key' => array( + * 'first_choice' => 'First choice', + * 'second_choice' => 'Second choice', + * ), + * ), + * ) + * } + */ + $labels = apply_filters('pll_acf_field_labels_to_translate', $this->labels); + + if (is_array($labels)) { + $this->labels = $labels; + } + + return $this->labels; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Post_Type.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Post_Type.php index 15f7705d30b3f0b84641e5162fb5c3e2bee473a2..0a20b6d003cd2529202757c58c0c947f048309ff 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Post_Type.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Post_Type.php @@ -1,12 +1,9 @@ - */ - protected function get_type_objects(): array { - return $GLOBALS['wp_post_types']; - } + /** + * Returns the list of type objects containing labels. + * + * @since 3.7 + * + * @return object[] + * + * @phpstan-return array< + * non-falsy-string, + * object{label: string, description: string, labels: object}&stdClass + * > + */ + protected function get_type_objects(): array + { + return $GLOBALS['wp_post_types']; + } - /** - * Returns the label of the type. - * - * @since 3.7 - * - * @return string - * - * @phpstan-return non-empty-string - */ - protected function get_type_label(): string { - return 'Post Type'; - } + /** + * Returns the label of the type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-empty-string + */ + protected function get_type_label(): string + { + return 'Post Type'; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Taxonomy.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Taxonomy.php index b1552b37f6a0560923a9c7c46849a710c9c9eb61..9afe76c06fade74a3684e8cb7f26825cc970d233 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Taxonomy.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Labels/Taxonomy.php @@ -1,12 +1,9 @@ - */ - protected function get_type_objects(): array { - return $GLOBALS['wp_taxonomies']; - } + /** + * Returns the list of type objects containing labels. + * + * @since 3.7 + * + * @return object[] + * + * @phpstan-return array< + * non-falsy-string, + * object{label: string, description: string, labels: object}&stdClass + * > + */ + protected function get_type_objects(): array + { + return $GLOBALS['wp_taxonomies']; + } - /** - * Returns the label of the type. - * - * @since 3.7 - * - * @return string - * - * @phpstan-return non-empty-string - */ - protected function get_type_label(): string { - return 'Taxonomy'; - } + /** + * Returns the label of the type. + * + * @since 3.7 + * + * @return string + * + * @phpstan-return non-empty-string + */ + protected function get_type_label(): string + { + return 'Taxonomy'; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Location_Language.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Location_Language.php index 932d5eb6fdb7ce3d28aea38ba106dfa9eebc2624..0ce1cdda7204bd32197353bd6f01ed4165a3d38d 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Location_Language.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Location_Language.php @@ -1,7 +1,4 @@ name = 'language'; - $this->label = __( 'Language', 'polylang-pro' ); - $this->category = $this->label; // Create a new category with the same name. - } - - /** - * Matches the provided rule against the screen args returning a bool result. - * - * @since 3.7 - * - * @param array $rule The location rule. - * @param array $screen The screen args. - * @param array $field_group The field group settings. - * @return bool - */ - public function match( $rule, $screen, $field_group ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - $language = pll_current_language(); - return empty( $language ) || $this->compare_to_rule( $language, $rule ); - } - - /** - * Returns an array of possible values for this rule type. - * - * @since 3.7 - * - * @param array $rule A location rule. - * @return array - */ - public function get_values( $rule ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return array_combine( pll_languages_list(), pll_languages_list( array( 'fields' => 'name' ) ) ); - } - - /** - * Checks if the field group has a language location rule. - * - * @since 3.7.1 - * - * @param array $field_group Field group definition. - * @return bool True if the field group has a language location rule, false otherwise. - */ - public static function has_language_location_rule( array $field_group ): bool { - if ( empty( $field_group ) ) { - return false; - } - - if ( empty( $field_group['location'] ) ) { - return false; - } - - foreach ( $field_group['location'] as $location ) { - foreach ( $location as $rule ) { - if ( 'language' === $rule['param'] ) { - return true; - } - } - } - - return false; - } - - /** - * Gets the ACF field group of a field (including nested fields within repeaters, flexible content, or other complex field types). - * - * @since 3.7.3 - * - * @param array $field Custom field definition. - * - * @return array The field group array on success, an empty array on failure. - */ - public static function get_field_group_from_field( array $field ): array { - if ( 0 === $field['ID'] ) { - // New field. - $field_group = acf_get_field_group( 0 ); - return ! empty( $field_group ) ? $field_group : array(); - } - - if ( empty( $field['parent'] ) ) { - return array(); - } - - $field_group = acf_get_field_group( $field['parent'] ); - if ( ! empty( $field_group ) ) { - return $field_group; - } - - // If not a field group, get parent field and continue. - $parent_field = acf_get_field( $field['parent'] ); - return $parent_field ? self::get_field_group_from_field( $parent_field ) : array(); - } +class Location_Language extends ACF_Location +{ + /** + * Initializes props. + * + * @since 3.7 + * + * @return void + */ + public function initialize() + { + $this->name = 'language'; + $this->label = __('Language', 'polylang-pro'); + $this->category = $this->label; // Create a new category with the same name. + } + + /** + * Matches the provided rule against the screen args returning a bool result. + * + * @since 3.7 + * + * @param array $rule The location rule. + * @param array $screen The screen args. + * @param array $field_group The field group settings. + * + * @return bool + */ + public function match($rule, $screen, $field_group) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {$language = pll_current_language(); + + return empty($language) || $this->compare_to_rule($language, $rule); + } + + /** + * Returns an array of possible values for this rule type. + * + * @since 3.7 + * + * @param array $rule A location rule. + * + * @return array + */ + public function get_values($rule) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {return array_combine(pll_languages_list(), pll_languages_list(['fields' => 'name'])); + } + + /** + * Checks if the field group has a language location rule. + * + * @since 3.7.1 + * + * @param array $field_group Field group definition. + * + * @return bool True if the field group has a language location rule, false otherwise. + */ + public static function has_language_location_rule(array $field_group): bool + { + if (empty($field_group)) { + return false; + } + + if (empty($field_group['location'])) { + return false; + } + + foreach ($field_group['location'] as $location) { + foreach ($location as $rule) { + if ('language' === $rule['param']) { + return true; + } + } + } + + return false; + } + + /** + * Gets the ACF field group of a field (including nested fields within repeaters, flexible content, or other complex field types). + * + * @since 3.7.3 + * + * @param array $field Custom field definition. + * + * @return array The field group array on success, an empty array on failure. + */ + public static function get_field_group_from_field(array $field): array + { + if (0 === $field['ID']) { + // New field. + $field_group = acf_get_field_group(0); + + return !empty($field_group) ? $field_group : []; + } + + if (empty($field['parent'])) { + return []; + } + + $field_group = acf_get_field_group($field['parent']); + if (!empty($field_group)) { + return $field_group; + } + + // If not a field group, get parent field and continue. + $parent_field = acf_get_field($field['parent']); + + return $parent_field ? self::get_field_group_from_field($parent_field) : []; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Main.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Main.php index 99a0758c5da885b31cd2a895376007fbc58bf444..1b2873c22b053714c47bd4a3af5c192a206da024 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Main.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Main.php @@ -1,138 +1,142 @@ ajax = new Ajax_Lang_Choice(); - $this->field_settings = new Field_Settings(); - $this->field_groups_labels = new Field_Groups(); - $this->post_types_labels = new Post_Type(); - $this->taxonomies_labels = new Taxonomy(); - $this->translation_instructions = new Translation_Instructions(); - } - - /** - * Initializes filters for ACF. - * - * @since 2.0 - * - * @return void - */ - public function on_acf_init(): void { - $this->ajax->on_acf_init(); - $this->field_settings->on_acf_init(); - $this->translation_instructions->on_acf_init(); - - /** - * Filters whether ACF labels translation should be enabled. - * This allows users to completely disable ACF labels translation (fields, post types, taxonomies) if they don't need it. - * - * @since 3.7.5 - * - * @param bool $enabled Whether ACF labels translation is enabled. Default true. - */ - $labels_translation_enabled = apply_filters( 'pll_enable_acf_labels_translation', true ); - if ( $labels_translation_enabled ) { - $this->field_groups_labels->on_acf_init(); - $this->post_types_labels->on_acf_init(); - $this->taxonomies_labels->on_acf_init(); - } - - Dispatcher::on_acf_init(); - - acf_register_location_type( Location_Language::class ); - - add_filter( 'acf/get_taxonomies', array( $this, 'get_taxonomies' ) ); - add_filter( 'pll_get_post_types', array( $this, 'get_post_types' ) ); - add_action( 'init', array( Dispatcher::class, 'on_blocks_registered' ), 999 ); // Late so blocks have a chance to register, usually done on `init`. - - PLL()->model->cache->clean( 'post_types' ); // A bit hacky. - } - - /** - * Tells whether or not ACF integration can be used. - * - * @since 3.7 - * - * @return bool True if the integration can be used, false otherwise. - */ - public static function can_use(): bool { - return defined( 'ACF_VERSION' ) && version_compare( ACF_VERSION, '6.0.0', '>=' ); - } - - /** - * Prevents ACF to display our private taxonomies. - * - * @since 2.8 - * - * @param string[] $taxonomies Taxonomy names. - * @return string[] - */ - public function get_taxonomies( $taxonomies ) { - return array_diff( $taxonomies, get_taxonomies( array( '_pll' => true ) ) ); - } - - /** - * Makes sure not to translate the Field Groups post type. - * - * @since 2.0 - * @since 3.7 Removed second param and disallow to translate the field groups. - * - * @param string[] $post_types List of post types. - * @return string[] - */ - public function get_post_types( $post_types ) { - unset( $post_types['acf-field-group'] ); - return $post_types; - } +class Main +{ + /** + * @var Ajax_Lang_Choice + */ + public $ajax; + + /** + * @var Field_Settings + */ + public $field_settings; + + /** + * @var Field_Groups + */ + public $field_groups_labels; + + /** + * @var Post_Type + */ + public $post_types_labels; + + /** + * @var Taxonomy + */ + public $taxonomies_labels; + + /** + * @var Translation_Instructions + */ + public $translation_instructions; + + /** + * Constructor. + * + * @since 3.7 + */ + public function __construct() + { + $this->ajax = new Ajax_Lang_Choice(); + $this->field_settings = new Field_Settings(); + $this->field_groups_labels = new Field_Groups(); + $this->post_types_labels = new Post_Type(); + $this->taxonomies_labels = new Taxonomy(); + $this->translation_instructions = new Translation_Instructions(); + } + + /** + * Initializes filters for ACF. + * + * @since 2.0 + * + * @return void + */ + public function on_acf_init(): void + { + $this->ajax->on_acf_init(); + $this->field_settings->on_acf_init(); + $this->translation_instructions->on_acf_init(); + + /** + * Filters whether ACF labels translation should be enabled. + * This allows users to completely disable ACF labels translation (fields, post types, taxonomies) if they don't need it. + * + * @since 3.7.5 + * + * @param bool $enabled Whether ACF labels translation is enabled. Default true. + */ + $labels_translation_enabled = apply_filters('pll_enable_acf_labels_translation', true); + if ($labels_translation_enabled) { + $this->field_groups_labels->on_acf_init(); + $this->post_types_labels->on_acf_init(); + $this->taxonomies_labels->on_acf_init(); + } + + Dispatcher::on_acf_init(); + + acf_register_location_type(Location_Language::class); + + add_filter('acf/get_taxonomies', [$this, 'get_taxonomies']); + add_filter('pll_get_post_types', [$this, 'get_post_types']); + add_action('init', [Dispatcher::class, 'on_blocks_registered'], 999); // Late so blocks have a chance to register, usually done on `init`. + + PLL()->model->cache->clean('post_types'); // A bit hacky. + } + + /** + * Tells whether or not ACF integration can be used. + * + * @since 3.7 + * + * @return bool True if the integration can be used, false otherwise. + */ + public static function can_use(): bool + { + return defined('ACF_VERSION') && version_compare(ACF_VERSION, '6.0.0', '>='); + } + + /** + * Prevents ACF to display our private taxonomies. + * + * @since 2.8 + * + * @param string[] $taxonomies Taxonomy names. + * + * @return string[] + */ + public function get_taxonomies($taxonomies) + { + return array_diff($taxonomies, get_taxonomies(['_pll' => true])); + } + + /** + * Makes sure not to translate the Field Groups post type. + * + * @since 2.0 + * @since 3.7 Removed second param and disallow to translate the field groups. + * + * @param string[] $post_types List of post types. + * + * @return string[] + */ + public function get_post_types($post_types) + { + unset($post_types['acf-field-group']); + + return $post_types; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Collect_Ids.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Collect_Ids.php index 964c2c86f527143958885242fac07022b3e17322..cd162e2fb617e675dfe6e80022ab88b67cc9324c 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Collect_Ids.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Collect_Ids.php @@ -1,7 +1,4 @@ linked_ids = array_merge( - $this->linked_ids, - $this->sanitize_ids( $this->get_ids_from_field( $value, $field ) ) - ); + /** + * Executes the strategy on a given field. + * Depending on the type of fields, this will add the collected IDs to the relevant property. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args Optional arguments, none here. + * + * @return mixed Untouched custom field value. + */ + protected function apply(Abstract_Object $object, $value, array $field, array $args = []) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {$this->linked_ids = array_merge( + $this->linked_ids, + $this->sanitize_ids($this->get_ids_from_field($value, $field)) + ); - return $value; - } + return $value; + } - /** - * Recursively checks if a field can be collected. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return bool - */ - protected function can_execute_recursive( array $field ): bool { - if ( isset( $field['translations'] ) && 'ignore' !== $field['translations'] ) { - return true; - } + /** + * Recursively checks if a field can be collected. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + protected function can_execute_recursive(array $field): bool + { + if (isset($field['translations']) && 'ignore' !== $field['translations']) { + return true; + } - return parent::can_execute_recursive( $field ); - } + return parent::can_execute_recursive($field); + } - /** - * Sanitizes IDs. - * - * @since 3.7 - * - * @param int|int[]|scalar|scalar[] $value Custom field value of the source object. - * @return int[] - */ - protected function sanitize_ids( $value ): array { - if ( is_numeric( $value ) ) { - return array( (int) $value ); - } + /** + * Sanitizes IDs. + * + * @since 3.7 + * + * @param int|int[]|scalar|scalar[] $value Custom field value of the source object. + * + * @return int[] + */ + protected function sanitize_ids($value): array + { + if (is_numeric($value)) { + return [(int) $value]; + } - if ( is_array( $value ) ) { - return array_map( 'intval', $value ); - } + if (is_array($value)) { + return array_map('intval', $value); + } - return array(); - } + return []; + } - /** - * Returns the collected entities IDs. - * - * @since 3.7 - * - * @param Translatable_Entity_Interface $object Object holding the logic to apply the strategy. - * @return int[] - */ - public function get( Translatable_Entity_Interface $object ): array { - $object->apply_to_all_fields( $this ); + /** + * Returns the collected entities IDs. + * + * @since 3.7 + * + * @param Translatable_Entity_Interface $object Object holding the logic to apply the strategy. + * + * @return int[] + */ + public function get(Translatable_Entity_Interface $object): array + { + $object->apply_to_all_fields($this); - return $this->linked_ids; - } + return $this->linked_ids; + } - /** - * Collects the object IDs of a field. - * - * @since 3.7 - * - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @return int|int[]|scalar|scalar[] Custom field value. - */ - abstract protected function get_ids_from_field( $value, array $field ); + /** + * Collects the object IDs of a field. + * + * @since 3.7 + * + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * + * @return int|int[]|scalar|scalar[] Custom field value. + */ + abstract protected function get_ids_from_field($value, array $field); } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Strategy.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Strategy.php index 924b4f4dbc9a50d2019ce7c44be699b18c523918..18e0192399f1e844b53c54801b073b780e98d8a5 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Strategy.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Abstract_Strategy.php @@ -1,7 +1,4 @@ can_execute[ $key ] ) ) { - $this->can_execute[ $key ] = $this->can_execute_recursive( $field ); - } - - return $this->can_execute[ $key ]; - } - - /** - * Applies the translation strategy. - * - * Depending on the type of fields, this will copy / synchronize a layout or - * auto-translate object ids. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value Optional. The translated value of the field, if any. - * } - * @return mixed Custom field value of the target object. - */ - public function execute( Abstract_Object $object, $value, array $field, array $args = array() ) { - $args = wp_parse_args( $args, array( 'original_value' => null ) ); - - if ( ! $this->can_execute( $field ) ) { - return $args['original_value']; - } - - if ( empty( $value ) ) { - return $value; - } - - switch ( $field['type'] ) { - /* - * For ACF `flexible_content`, `repeater`, `clone` and `group` fields, ACF gets their real value (null) as their original value - * and not an array of their subfields. So, in this case, we force this original value to an empty array to be able to call - * `apply_on_rows()` method to copy subfields and make their translation. - */ - case 'flexible_content': - if ( is_array( $value ) ) { - $args['original_value'] = is_array( $args['original_value'] ) ? $args['original_value'] : array(); - $value = $this->apply_on_layouts( $object, $value, $field, $args ); - } - break; - case 'repeater': - if ( is_array( $value ) ) { - $args['original_value'] = is_array( $args['original_value'] ) ? $args['original_value'] : array(); - $value = $this->apply_on_rows( $object, $value, $field, $args ); - } - break; - case 'clone': - case 'group': - if ( is_array( $value ) ) { - $args['original_value'] = is_array( $args['original_value'] ) ? $args['original_value'] : array(); - $value = $this->apply_on_group( $object, $value, $field, $args ); - } - break; - default: - $value = $this->apply( $object, $value, $field, $args ); - } - - return $value; - } - - /** - * Recursively checks if the translation strategy can be applied. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return bool - */ - protected function can_execute_recursive( array $field ): bool { - switch ( $field['type'] ) { - case 'flexible_content': - foreach ( $field['layouts'] as $layout ) { - foreach ( $layout['sub_fields'] as $sub_field ) { - if ( $this->can_execute( $sub_field ) ) { - return true; - } - } - } - break; - - case 'clone': - case 'group': - case 'repeater': - foreach ( $field['sub_fields'] as $sub_field ) { - if ( $this->can_execute( $sub_field ) ) { - return true; - } - } - break; - } - - return false; - } - - /** - * Executes the strategy on a given field. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args Array of arguments. - * @return mixed Custom field value of the target object. - */ - abstract protected function apply( Abstract_Object $object, $value, array $field, array $args = array() ); - - /** - * Copies or synchronizes subfields in a repeater or flexible content field. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param array $values Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value The translated value of the field, if any. - * } - * @return array Custom field value of the target object. - */ - protected function apply_on_rows( Abstract_Object $object, array $values, array $field, array $args = array() ): array { - if ( empty( $field['sub_fields'] ) ) { - return $values; - } - - $original_value = $args['original_value']; - - foreach ( $field['sub_fields'] as $subfield ) { - foreach ( $values as $row => $subvalues ) { - if ( ! is_array( $subvalues ) ) { - continue; - } - - - $subfield['pll_key'] = $this->get_field_key( $field ) . '_' . $row . '_' . $subfield['key']; // Adds an entry in `subfield` with the full path of the field. - $args['original_value'] = $original_value[ $row ] ?? null; - $values[ $row ] = $this->apply_on_subfield( - $object, - $subvalues, - $subfield, - $args - ); - } - } - - return $values; - } - - /** - * Walks through layouts and apply the strategy to their subfields. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param array $values Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args Array of arguments. - * @return array Custom field value of the target object. - */ - protected function apply_on_layouts( Abstract_Object $object, array $values, array $field, array $args = array() ): array { - if ( empty( $field['layouts'] ) ) { - return $values; - } - - foreach ( $field['layouts'] as $layout ) { - if ( empty( $layout['sub_fields'] ) ) { - continue; - } - - $values = $this->apply_on_rows( $object, $values, $layout, $args ); - } - - return $values; - } - - /** - * Copies or synchronizes sub fields in a group or clone field. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param array $values Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args Array of arguments. - * @return array Custom field value of the target object. - */ - protected function apply_on_group( Abstract_Object $object, array $values, array $field, array $args = array() ): array { - foreach ( $field['sub_fields'] as $subfield ) { - $subfield['pll_key'] = $this->get_field_key( $subfield ) . '_' . $subfield['key']; // Adds an entry in `subfield` with the full path of the field. - $values = $this->apply_on_subfield( - $object, - $values, - $subfield, - $args - ); - } - - return $values; - } - - /** - * Copies or synchronizes subfield values. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param array $subvalues Custom field values of the source object. - * @param array $subfield Custom subfield definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value The translated value of the field, if any. - * } - * @return array Custom field value of the target object. - */ - protected function apply_on_subfield( Abstract_Object $object, array $subvalues, array $subfield, array $args = array() ): array { - if ( empty( $subfield['parent'] ) ) { - // Not a subfield. - return $subvalues; - } - - if ( ! $this->can_execute( $subfield ) ) { - if ( isset( $args['original_value'][ $subfield['key'] ] ) ) { - // If original values are hold with field key. - $subvalues[ $subfield['key'] ] = $args['original_value'][ $subfield['key'] ]; - } elseif ( isset( $args['original_value'][ $subfield['name'] ] ) ) { - // If original values are hold with field name. - $subvalues[ $subfield['name'] ] = $args['original_value'][ $subfield['name'] ]; - } else { - // Strip out the subfield if it can't be executed so the value won't be processed. - unset( $subvalues[ $subfield['name'] ], $subvalues[ $subfield['key'] ] ); - } - return $subvalues; - } - - if ( isset( $subvalues[ $subfield['name'] ] ) ) { - // Is the group value update with field name as key? - $selector = $subfield['name']; - } elseif ( isset( $subvalues[ $subfield['key'] ] ) ) { - // Is the group value update with field key as key? - $selector = $subfield['key']; - } - - if ( empty( $selector ) ) { - return $subvalues; - } - - $args['original_value'] = $args['original_value'][ $selector ] ?? null; - - $subvalues[ $selector ] = $this->execute( - $object, - $subvalues[ $selector ], - $subfield, - $args - ); - - return $subvalues; - } - - /** - * Returns the field key to use. - * Used to have a steady way of finding nested fields. - * - * @since 3.7 - * - * @param array $field Field definition. - * @return string Field key. - */ - protected function get_field_key( array $field ): string { - /* - * Why look for these keys in this order? - * #1: `pll_key` should be defined most of the time, it's used to prepend parent key - * to keep track of fields hierarchy and identify them easily during export/import. - * #2: `_key` ensures to look for the original key in case the field is a seamless clone for instance. - * #3: `key` the standard field key. - */ - return $field['pll_key'] ?? $field['__key'] ?? $field['key']; - } +abstract class Abstract_Strategy +{ + /** + * @var bool[] + */ + private $can_execute = []; + + /** + * Checks if the translation strategy can be applied. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + public function can_execute(array $field): bool + { + $key = $field['key']; + + if (!isset($this->can_execute[$key])) { + $this->can_execute[$key] = $this->can_execute_recursive($field); + } + + return $this->can_execute[$key]; + } + + /** + * Applies the translation strategy. + * + * Depending on the type of fields, this will copy / synchronize a layout or + * auto-translate object ids. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value Optional. The translated value of the field, if any. + * } + * + * @return mixed Custom field value of the target object. + */ + public function execute(Abstract_Object $object, $value, array $field, array $args = []) + { + $args = wp_parse_args($args, ['original_value' => null]); + + if (!$this->can_execute($field)) { + return $args['original_value']; + } + + if (empty($value)) { + return $value; + } + + switch ($field['type']) { + /* + * For ACF `flexible_content`, `repeater`, `clone` and `group` fields, ACF gets their real value (null) as their original value + * and not an array of their subfields. So, in this case, we force this original value to an empty array to be able to call + * `apply_on_rows()` method to copy subfields and make their translation. + */ + case 'flexible_content': + if (is_array($value)) { + $args['original_value'] = is_array($args['original_value']) ? $args['original_value'] : []; + $value = $this->apply_on_layouts($object, $value, $field, $args); + } + break; + case 'repeater': + if (is_array($value)) { + $args['original_value'] = is_array($args['original_value']) ? $args['original_value'] : []; + $value = $this->apply_on_rows($object, $value, $field, $args); + } + break; + case 'clone': + case 'group': + if (is_array($value)) { + $args['original_value'] = is_array($args['original_value']) ? $args['original_value'] : []; + $value = $this->apply_on_group($object, $value, $field, $args); + } + break; + default: + $value = $this->apply($object, $value, $field, $args); + } + + return $value; + } + + /** + * Recursively checks if the translation strategy can be applied. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + protected function can_execute_recursive(array $field): bool + { + switch ($field['type']) { + case 'flexible_content': + foreach ($field['layouts'] as $layout) { + foreach ($layout['sub_fields'] as $sub_field) { + if ($this->can_execute($sub_field)) { + return true; + } + } + } + break; + + case 'clone': + case 'group': + case 'repeater': + foreach ($field['sub_fields'] as $sub_field) { + if ($this->can_execute($sub_field)) { + return true; + } + } + break; + } + + return false; + } + + /** + * Executes the strategy on a given field. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args Array of arguments. + * + * @return mixed Custom field value of the target object. + */ + abstract protected function apply(Abstract_Object $object, $value, array $field, array $args = []); + + /** + * Copies or synchronizes subfields in a repeater or flexible content field. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param array $values Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value The translated value of the field, if any. + * } + * + * @return array Custom field value of the target object. + */ + protected function apply_on_rows(Abstract_Object $object, array $values, array $field, array $args = []): array + { + if (empty($field['sub_fields'])) { + return $values; + } + + $original_value = $args['original_value']; + + foreach ($field['sub_fields'] as $subfield) { + foreach ($values as $row => $subvalues) { + if (!is_array($subvalues)) { + continue; + } + + $subfield['pll_key'] = $this->get_field_key($field).'_'.$row.'_'.$subfield['key']; // Adds an entry in `subfield` with the full path of the field. + $args['original_value'] = $original_value[$row] ?? null; + $values[$row] = $this->apply_on_subfield( + $object, + $subvalues, + $subfield, + $args + ); + } + } + + return $values; + } + + /** + * Walks through layouts and apply the strategy to their subfields. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param array $values Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args Array of arguments. + * + * @return array Custom field value of the target object. + */ + protected function apply_on_layouts(Abstract_Object $object, array $values, array $field, array $args = []): array + { + if (empty($field['layouts'])) { + return $values; + } + + foreach ($field['layouts'] as $layout) { + if (empty($layout['sub_fields'])) { + continue; + } + + $values = $this->apply_on_rows($object, $values, $layout, $args); + } + + return $values; + } + + /** + * Copies or synchronizes sub fields in a group or clone field. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param array $values Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args Array of arguments. + * + * @return array Custom field value of the target object. + */ + protected function apply_on_group(Abstract_Object $object, array $values, array $field, array $args = []): array + { + foreach ($field['sub_fields'] as $subfield) { + $subfield['pll_key'] = $this->get_field_key($subfield).'_'.$subfield['key']; // Adds an entry in `subfield` with the full path of the field. + $values = $this->apply_on_subfield( + $object, + $values, + $subfield, + $args + ); + } + + return $values; + } + + /** + * Copies or synchronizes subfield values. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param array $subvalues Custom field values of the source object. + * @param array $subfield Custom subfield definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value The translated value of the field, if any. + * } + * + * @return array Custom field value of the target object. + */ + protected function apply_on_subfield(Abstract_Object $object, array $subvalues, array $subfield, array $args = []): array + { + if (empty($subfield['parent'])) { + // Not a subfield. + return $subvalues; + } + + if (!$this->can_execute($subfield)) { + if (isset($args['original_value'][$subfield['key']])) { + // If original values are hold with field key. + $subvalues[$subfield['key']] = $args['original_value'][$subfield['key']]; + } elseif (isset($args['original_value'][$subfield['name']])) { + // If original values are hold with field name. + $subvalues[$subfield['name']] = $args['original_value'][$subfield['name']]; + } else { + // Strip out the subfield if it can't be executed so the value won't be processed. + unset($subvalues[$subfield['name']], $subvalues[$subfield['key']]); + } + + return $subvalues; + } + + if (isset($subvalues[$subfield['name']])) { + // Is the group value update with field name as key? + $selector = $subfield['name']; + } elseif (isset($subvalues[$subfield['key']])) { + // Is the group value update with field key as key? + $selector = $subfield['key']; + } + + if (empty($selector)) { + return $subvalues; + } + + $args['original_value'] = $args['original_value'][$selector] ?? null; + + $subvalues[$selector] = $this->execute( + $object, + $subvalues[$selector], + $subfield, + $args + ); + + return $subvalues; + } + + /** + * Returns the field key to use. + * Used to have a steady way of finding nested fields. + * + * @since 3.7 + * + * @param array $field Field definition. + * + * @return string Field key. + */ + protected function get_field_key(array $field): string + { + /* + * Why look for these keys in this order? + * #1: `pll_key` should be defined most of the time, it's used to prepend parent key + * to keep track of fields hierarchy and identify them easily during export/import. + * #2: `_key` ensures to look for the original key in case the field is a seamless clone for instance. + * #3: `key` the standard field key. + */ + return $field['pll_key'] ?? $field['__key'] ?? $field['key']; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Post_Ids.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Post_Ids.php index a33f75d4f8bea174d0fb3fe35e40dc3f1848a7df..c8438951fe8ebe8935cb7b749ced9e61ba5859c9 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Post_Ids.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Post_Ids.php @@ -1,7 +1,4 @@ options['media_support'] && is_numeric( $value ) ) { - return $value; - } - break; - case 'gallery': - if ( PLL()->options['media_support'] && is_array( $value ) ) { - return $value; - } - break; - } +class Collect_Post_Ids extends Abstract_Collect_Ids +{ + /** + * Collects the post IDs of a field. + * + * @since 3.7 + * + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * + * @return int|int[]|scalar|scalar[] Custom field value. + */ + protected function get_ids_from_field($value, array $field) + { + switch ($field['type']) { + case 'image': + case 'file': + if (PLL()->options['media_support'] && is_numeric($value)) { + return $value; + } + break; + case 'gallery': + if (PLL()->options['media_support'] && is_array($value)) { + return $value; + } + break; + } - return array(); - } + return []; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Term_Ids.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Term_Ids.php index 1720a7ec846bd5d9bf0d15c0b6cd30e43f257d03..85a436790c884988cdadca0edfd00998d2d06729 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Term_Ids.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Collect_Term_Ids.php @@ -1,7 +1,4 @@ options['media_support'] && is_numeric( $value ) ) { - $value = $this->translate_media( (int) $value, $args['target_language'] ); - } - break; - case 'gallery': - if ( PLL()->options['media_support'] && is_array( $value ) ) { - $value = $this->translate_gallery( $value, $args['target_language'] ); - } - break; - case 'post_object': - case 'relationship': - if ( is_array( $value ) || ( is_numeric( $value ) && ! is_float( $value ) ) ) { - $value = $this->translate_post( $value, $args['target_language'] ); - } - break; - case 'taxonomy': - if ( ! is_array( $value ) && ! ( is_numeric( $value ) && ! is_float( $value ) ) ) { - break; - } - if ( pll_is_translated_taxonomy( $field['taxonomy'] ) ) { - $value = $this->translate_term( $value, $args['target_language'] ); - } - break; - case 'page_link': - if ( is_array( $value ) || is_int( $value ) || is_string( $value ) ) { - $value = $this->translate_page_link( $value, $args['target_language'] ); - } - break; - case 'wysiwyg': - if ( is_string( $value ) ) { - $value = PLL()->sync_content->translate_content( - $value, - null, - $args['target_language'] - ); - - } - break; - } - - return $this->maybe_translate_field_default_value( $value, $field, $args ); - } - - /** - * Determines if a field's value is the default value. - * - * @since 3.7.2 - * - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type PLL_Language $source_language Optional. The language object of the source object. - * @type mixed $original_value Optional. The translated value of the field, if any. - * } - * - * @return mixed Custom field value of the target object. - */ - protected function maybe_translate_field_default_value( $value, array $field, array $args = array() ) { - if ( ! isset( $field['pll_default_value'], $args['source_language'] ) || ! $args['source_language'] instanceof PLL_Language ) { - return $value; - } - - $default_value_in_source_language = pll_translate_string( $field['pll_default_value'], $args['source_language']->slug ); - - return $default_value_in_source_language === $value ? $args['original_value'] : $value; - } - - /** - * Recursively checks if a field can be copied. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return bool - */ - protected function can_execute_recursive( array $field ): bool { - if ( isset( $field['translations'] ) && 'ignore' !== $field['translations'] ) { - return true; - } - - return parent::can_execute_recursive( $field ); - } - - /** - * Translates a media field. - * - * @since 3.7 - * - * @param int $value Custom field value of the source object. - * @param PLL_Language $lang Language object of the target object. - * @return int Custom field value of the target object. - */ - protected function translate_media( int $value, PLL_Language $lang ): int { - $tr_id = pll_get_post( $value, $lang ); - - if ( $tr_id ) { - return $tr_id; - } - - return PLL()->model->post->create_media_translation( $value, $lang ); - } - - /** - * Translates media ids in a gallery field. - * - * @since 3.7 - * - * @param int[] $values Custom field value of the source object. - * @param PLL_Language $lang Language object of the target object. - * @return string[] Custom field value of the target object. - * - * @phpstan-param array $values - * @phpstan-return list - */ - protected function translate_gallery( array $values, PLL_Language $lang ): array { - $return = array(); - - foreach ( $values as $value ) { - $return[] = $this->translate_media( (int) $value, $lang ); - } - - /** @phpstan-var list */ - return array_map( 'strval', $return ); // See `acf_field_gallery::update_value()`. - } - - /** - * Translates post ids relationship and post object fields. - * - * @since 3.7 - * - * @param int|int[] $value Custom field value of the source object. - * @param PLL_Language $lang Language object of the target object. - * @return int|string[] Custom field value of the target object. - * - * @phpstan-param int|numeric-string|array $value - * @phpstan-return ( - * $value is array ? list : int - * ) - */ - protected function translate_post( $value, PLL_Language $lang ) { - if ( is_numeric( $value ) ) { - $value = (int) $value; - $post_type = get_post_type( $value ); - - if ( ! $post_type || ! pll_is_translated_post_type( $post_type ) ) { - // Same ID for not-translated languages. - return $value; - } - - return (int) pll_get_post( $value, $lang ); - } - - if ( is_array( $value ) ) { - $return = array(); - foreach ( $value as $id ) { - $return[] = $this->translate_post( $id, $lang ); - } - /** @phpstan-var list */ - return array_map( 'strval', $return ); // See the method update_value() for these fields. - } - - // Something went wrong. - return 0; - } - - /** - * Translates term ids in a taxonomy field. - * - * @since 3.7 - * - * @param int|int[] $value Custom field value of the source object. - * @param PLL_Language $lang Language object of the target object. - * @return int|int[] Custom field value of the target object. - * - * @phpstan-param int|numeric-string|array $value - * @phpstan-return ( - * $value is array ? list : int - * ) - */ - protected function translate_term( $value, PLL_Language $lang ) { - if ( is_numeric( $value ) ) { - return (int) pll_get_term( (int) $value, $lang ); - } - - if ( is_array( $value ) ) { - $return = array(); - foreach ( $value as $id ) { - $return[] = $this->translate_term( $id, $lang ); - } - return $return; - } - - // Something went wrong. - return 0; - } - - /** - * Translates a page link field. - * - * @since 3.7 - * - * @param int|string|(int|string)[] $value Custom field value of the source object. - * @param PLL_Language $lang Language slug of the target object. - * @return int|string|string[] Custom field value of the target object. - */ - protected function translate_page_link( $value, PLL_Language $lang ) { - if ( is_numeric( $value ) ) { - return (int) pll_get_post( (int) $value, $lang ); - } - - if ( is_array( $value ) ) { - // Multiple choices. - $return = array(); - foreach ( $value as $p ) { - if ( is_numeric( $p ) ) { - $return[] = (int) pll_get_post( (int) $p, $lang ); - } elseif ( is_string( $p ) ) { - $return[] = $this->translate_cpt_archive_link( $p, $lang ); // Archive. - } - } - return array_map( 'strval', $return ); // See `acf_field_page_link::update_value()`. - } - - return $this->translate_cpt_archive_link( $value, $lang ); // Archive. - } - - /** - * Translates a CPT archive link in a page link field. - * - * @since 2.3.6 - * @since 3.7 `$lang` is a `PLL_Language` instead of a string. - * - * @param string $link CPT archive link. - * @param PLL_Language $lang Language object of the target object. - * @return string Modified link. - */ - protected function translate_cpt_archive_link( string $link, PLL_Language $lang ): string { - /* - * ACF doesn't use correctly `home_url()` function. It makes this URL not end with a trailing slash. - * It makes our `PLL_Links_Model::switch_language_in_link()` not work correctly in this case. - * @see https://github.com/AdvancedCustomFields/acf/blob/6.3.8/includes/fields/class-acf-field-page_link.php#L174 - */ - if ( home_url() === $link ) { - $link = home_url( '/' ); - } - - $show_on_front = get_option( 'show_on_front' ); - $page_for_posts = get_option( 'page_for_posts' ); - - if ( 'page' === $show_on_front && is_numeric( $page_for_posts ) ) { - // Gets `page_for_posts` URL of the target language. - $post_archive_link = get_permalink( $lang->page_for_posts ); - return ! empty( $post_archive_link ) ? $post_archive_link : $link; - } - - $link = PLL()->links_model->switch_language_in_link( $link, $lang ); - - if ( ! isset( PLL()->translate_slugs ) ) { - return $link; - } - - foreach ( PLL()->translate_slugs->slugs_model->get_translatable_slugs() as $type => $data ) { - // Unfortunately ACF does not pass the post type, so let's try with all post type archives. - if ( 0 === strpos( $type, 'archive_' ) ) { - $link = PLL()->translate_slugs->slugs_model->switch_translated_slug( $link, $lang, $type ); - } - } - - return $link; - } +class Copy extends Abstract_Strategy +{ + /** + * Executes the strategy on a given field. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var PLL_Language $target_language Optional. The language object of the target object. + * @var PLL_Language $source_language Optional. The language object of the source object. + * } + * + * @return mixed Custom field value of the target object. + */ + protected function apply(Abstract_Object $object, $value, array $field, array $args = []) + { + if (!isset($args['target_language']) || !$args['target_language'] instanceof PLL_Language) { + return $value; + } + + switch ($field['type']) { + case 'image': + case 'file': + if (PLL()->options['media_support'] && is_numeric($value)) { + $value = $this->translate_media((int) $value, $args['target_language']); + } + break; + case 'gallery': + if (PLL()->options['media_support'] && is_array($value)) { + $value = $this->translate_gallery($value, $args['target_language']); + } + break; + case 'post_object': + case 'relationship': + if (is_array($value) || (is_numeric($value) && !is_float($value))) { + $value = $this->translate_post($value, $args['target_language']); + } + break; + case 'taxonomy': + if (!is_array($value) && !(is_numeric($value) && !is_float($value))) { + break; + } + if (pll_is_translated_taxonomy($field['taxonomy'])) { + $value = $this->translate_term($value, $args['target_language']); + } + break; + case 'page_link': + if (is_array($value) || is_int($value) || is_string($value)) { + $value = $this->translate_page_link($value, $args['target_language']); + } + break; + case 'wysiwyg': + if (is_string($value)) { + $value = PLL()->sync_content->translate_content( + $value, + null, + $args['target_language'] + ); + } + break; + } + + return $this->maybe_translate_field_default_value($value, $field, $args); + } + + /** + * Determines if a field's value is the default value. + * + * @since 3.7.2 + * + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var PLL_Language $source_language Optional. The language object of the source object. + * @var mixed $original_value Optional. The translated value of the field, if any. + * } + * + * @return mixed Custom field value of the target object. + */ + protected function maybe_translate_field_default_value($value, array $field, array $args = []) + { + if (!isset($field['pll_default_value'], $args['source_language']) || !$args['source_language'] instanceof PLL_Language) { + return $value; + } + + $default_value_in_source_language = pll_translate_string($field['pll_default_value'], $args['source_language']->slug); + + return $default_value_in_source_language === $value ? $args['original_value'] : $value; + } + + /** + * Recursively checks if a field can be copied. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + protected function can_execute_recursive(array $field): bool + { + if (isset($field['translations']) && 'ignore' !== $field['translations']) { + return true; + } + + return parent::can_execute_recursive($field); + } + + /** + * Translates a media field. + * + * @since 3.7 + * + * @param int $value Custom field value of the source object. + * @param PLL_Language $lang Language object of the target object. + * + * @return int Custom field value of the target object. + */ + protected function translate_media(int $value, PLL_Language $lang): int + { + $tr_id = pll_get_post($value, $lang); + + if ($tr_id) { + return $tr_id; + } + + return PLL()->model->post->create_media_translation($value, $lang); + } + + /** + * Translates media ids in a gallery field. + * + * @since 3.7 + * + * @param int[] $values Custom field value of the source object. + * @param PLL_Language $lang Language object of the target object. + * + * @return string[] Custom field value of the target object. + * + * @phpstan-param array $values + * + * @phpstan-return list + */ + protected function translate_gallery(array $values, PLL_Language $lang): array + { + $return = []; + + foreach ($values as $value) { + $return[] = $this->translate_media((int) $value, $lang); + } + + /** @phpstan-var list */ + return array_map('strval', $return); // See `acf_field_gallery::update_value()`. + } + + /** + * Translates post ids relationship and post object fields. + * + * @since 3.7 + * + * @param int|int[] $value Custom field value of the source object. + * @param PLL_Language $lang Language object of the target object. + * + * @return int|string[] Custom field value of the target object. + * + * @phpstan-param int|numeric-string|array $value + * + * @phpstan-return ( + * $value is array ? list : int + * ) + */ + protected function translate_post($value, PLL_Language $lang) + { + if (is_numeric($value)) { + $value = (int) $value; + $post_type = get_post_type($value); + + if (!$post_type || !pll_is_translated_post_type($post_type)) { + // Same ID for not-translated languages. + return $value; + } + + return (int) pll_get_post($value, $lang); + } + + if (is_array($value)) { + $return = []; + foreach ($value as $id) { + $return[] = $this->translate_post($id, $lang); + } + + /** @phpstan-var list */ + return array_map('strval', $return); // See the method update_value() for these fields. + } + + // Something went wrong. + return 0; + } + + /** + * Translates term ids in a taxonomy field. + * + * @since 3.7 + * + * @param int|int[] $value Custom field value of the source object. + * @param PLL_Language $lang Language object of the target object. + * + * @return int|int[] Custom field value of the target object. + * + * @phpstan-param int|numeric-string|array $value + * + * @phpstan-return ( + * $value is array ? list : int + * ) + */ + protected function translate_term($value, PLL_Language $lang) + { + if (is_numeric($value)) { + return (int) pll_get_term((int) $value, $lang); + } + + if (is_array($value)) { + $return = []; + foreach ($value as $id) { + $return[] = $this->translate_term($id, $lang); + } + + return $return; + } + + // Something went wrong. + return 0; + } + + /** + * Translates a page link field. + * + * @since 3.7 + * + * @param int|string|(int|string)[] $value Custom field value of the source object. + * @param PLL_Language $lang Language slug of the target object. + * + * @return int|string|string[] Custom field value of the target object. + */ + protected function translate_page_link($value, PLL_Language $lang) + { + if (is_numeric($value)) { + return (int) pll_get_post((int) $value, $lang); + } + + if (is_array($value)) { + // Multiple choices. + $return = []; + foreach ($value as $p) { + if (is_numeric($p)) { + $return[] = (int) pll_get_post((int) $p, $lang); + } elseif (is_string($p)) { + $return[] = $this->translate_cpt_archive_link($p, $lang); // Archive. + } + } + + return array_map('strval', $return); // See `acf_field_page_link::update_value()`. + } + + return $this->translate_cpt_archive_link($value, $lang); // Archive. + } + + /** + * Translates a CPT archive link in a page link field. + * + * @since 2.3.6 + * @since 3.7 `$lang` is a `PLL_Language` instead of a string. + * + * @param string $link CPT archive link. + * @param PLL_Language $lang Language object of the target object. + * + * @return string Modified link. + */ + protected function translate_cpt_archive_link(string $link, PLL_Language $lang): string + { + /* + * ACF doesn't use correctly `home_url()` function. It makes this URL not end with a trailing slash. + * It makes our `PLL_Links_Model::switch_language_in_link()` not work correctly in this case. + * @see https://github.com/AdvancedCustomFields/acf/blob/6.3.8/includes/fields/class-acf-field-page_link.php#L174 + */ + if (home_url() === $link) { + $link = home_url('/'); + } + + $show_on_front = get_option('show_on_front'); + $page_for_posts = get_option('page_for_posts'); + + if ('page' === $show_on_front && is_numeric($page_for_posts)) { + // Gets `page_for_posts` URL of the target language. + $post_archive_link = get_permalink($lang->page_for_posts); + + return !empty($post_archive_link) ? $post_archive_link : $link; + } + + $link = PLL()->links_model->switch_language_in_link($link, $lang); + + if (!isset(PLL()->translate_slugs)) { + return $link; + } + + foreach (PLL()->translate_slugs->slugs_model->get_translatable_slugs() as $type => $data) { + // Unfortunately ACF does not pass the post type, so let's try with all post type archives. + if (0 === strpos($type, 'archive_')) { + $link = PLL()->translate_slugs->slugs_model->switch_translated_slug($link, $lang, $type); + } + } + + return $link; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Copy_All.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Copy_All.php index fb80ff090c83d33c7fe86c17d922aa974ac7113e..3661965b2c00ea79a20203f9ffd8b354d3c9b9b3 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Copy_All.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Copy_All.php @@ -1,7 +1,4 @@ export = $export; - } + /** + * Constructor. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export object. + * + * @return void + */ + public function __construct(PLL_Export_Data $export) + { + $this->export = $export; + } - /** - * Executes the strategy on a given field. - * Depending on the type of fields, this will add the fields with the translate option to the fields to export. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value The translated or default value of the field, if any. - * } - * @return mixed The original value, so the strategy behaves like others. - */ - protected function apply( Abstract_Object $object, $value, array $field, array $args = array() ) { - if ( 'translate_once' === $field['translations'] - && 0 < PLL()->model->{$object->get_type()}->get( $object->get_id(), $args['target_language'] ) ) { - // A translation exists and we're on a `translate_once` field, so return. - return $value; - } + /** + * Executes the strategy on a given field. + * Depending on the type of fields, this will add the fields with the translate option to the fields to export. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value The translated or default value of the field, if any. + * } + * + * @return mixed The original value, so the strategy behaves like others. + */ + protected function apply(Abstract_Object $object, $value, array $field, array $args = []) + { + if ('translate_once' === $field['translations'] + && 0 < PLL()->model->{$object->get_type()}->get($object->get_id(), $args['target_language'])) { + // A translation exists and we're on a `translate_once` field, so return. + return $value; + } - if ( ! is_string( $value ) || empty( $value ) ) { - return $value; - } + if (!is_string($value) || empty($value)) { + return $value; + } - $original_value = is_string( $args['original_value'] ) ? $args['original_value'] : ''; + $original_value = is_string($args['original_value']) ? $args['original_value'] : ''; - $this->export->add_translation_entry( - array( - 'object_type' => $object->get_type(), - 'field_type' => 'acf', - 'field_id' => $this->get_field_key( $field ), - 'object_id' => $object->get_id(), - ), - $value, - $original_value === $field['default_value'] ? '' : $original_value // Do not export translated default values. - ); + $this->export->add_translation_entry( + [ + 'object_type' => $object->get_type(), + 'field_type' => 'acf', + 'field_id' => $this->get_field_key($field), + 'object_id' => $object->get_id(), + ], + $value, + $original_value === $field['default_value'] ? '' : $original_value // Do not export translated default values. + ); - return $value; - } + return $value; + } - /** - * Recursively checks if a field can be copied. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return bool - */ - protected function can_execute_recursive( array $field ): bool { - if ( isset( $field['translations'] ) && in_array( $field['translations'], array( 'translate', 'translate_once' ), true ) ) { - return true; - } + /** + * Recursively checks if a field can be copied. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + protected function can_execute_recursive(array $field): bool + { + if (isset($field['translations']) && in_array($field['translations'], ['translate', 'translate_once'], true)) { + return true; + } - return parent::can_execute_recursive( $field ); - } + return parent::can_execute_recursive($field); + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Import.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Import.php index 821b510d30987e56ef4fa4589a930dfbb8a9a8d6..ad204d1371a0201a27f04d1ea93194a525c13676 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Import.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Import.php @@ -1,113 +1,115 @@ translations = $translations; - } + /** + * Constructor. + * + * @since 3.7 + * + * @param Translations $translations A set of translations to search the custom fields translations in. + * + * @return void + */ + public function __construct(Translations $translations) + { + $this->translations = $translations; + } - /** - * Applies the translation strategy. - * - * Depending on the type of fields, this will copy a layout and - * auto-translate object ids and translated custom fields. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value Optional. The translated or default value of the field, if any. - * } - * @return mixed Custom field value of the target object. - */ - public function execute( Abstract_Object $object, $value, array $field, array $args = array() ) { - $args = wp_parse_args( $args, array( 'original_value' => null ) ); + /** + * Applies the translation strategy. + * + * Depending on the type of fields, this will copy a layout and + * auto-translate object ids and translated custom fields. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value Optional. The translated or default value of the field, if any. + * } + * + * @return mixed Custom field value of the target object. + */ + public function execute(Abstract_Object $object, $value, array $field, array $args = []) + { + $args = wp_parse_args($args, ['original_value' => null]); - if ( ! $this->can_execute( $field ) ) { - return $args['original_value']; - } + if (!$this->can_execute($field)) { + return $args['original_value']; + } - if ( empty( $value ) ) { - return $value; - } + if (empty($value)) { + return $value; + } - if ( ! is_string( $value ) ) { - return parent::execute( $object, $value, $field, $args ); - } + if (!is_string($value)) { + return parent::execute($object, $value, $field, $args); + } - $args['original_value'] = is_string( $args['original_value'] ) ? $args['original_value'] : ''; + $args['original_value'] = is_string($args['original_value']) ? $args['original_value'] : ''; - $entry = new Translation_Entry( - array( - 'singular' => $value, - 'context' => Context::to_string( - array( - Context::FIELD => 'acf', - Context::ID => $this->get_field_key( $field ), - ) - ), - ) - ); + $entry = new Translation_Entry( + [ + 'singular' => $value, + 'context' => Context::to_string( + [ + Context::FIELD => 'acf', + Context::ID => $this->get_field_key($field), + ] + ), + ] + ); - /* - * Use `translate_entry()` to know whether the entry is in the translation set or not. - * Because `translate()` doesn't return false but the source string if the entry doesn't exist. - */ - if ( ! $this->translations->translate_entry( $entry ) ) { - // The entry is not in the translation set. - if ( 'translate_once' === $field['translations'] && ! empty( $args['original_value'] ) ) { - /* - * If there is no entry in the translation set and the field is set to `translate_once`, - * it means that it has already been translated. - */ - return $args['original_value']; - } + /* + * Use `translate_entry()` to know whether the entry is in the translation set or not. + * Because `translate()` doesn't return false but the source string if the entry doesn't exist. + */ + if (!$this->translations->translate_entry($entry)) { + // The entry is not in the translation set. + if ('translate_once' === $field['translations'] && !empty($args['original_value'])) { + /* + * If there is no entry in the translation set and the field is set to `translate_once`, + * it means that it has already been translated. + */ + return $args['original_value']; + } - return parent::execute( $object, $value, $field, $args ); - } + return parent::execute($object, $value, $field, $args); + } - $value = $this->translations->translate( $value, $entry->context ); + $value = $this->translations->translate($value, $entry->context); - return parent::execute( - $object, - $value, - $field, - $args - ); - } + return parent::execute( + $object, + $value, + $field, + $args + ); + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Synchronize.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Synchronize.php index d1883d0c6d8b23ecfd804cad536a0cd56d0e023a..cf603b238814f63f7b9f42d74f56315e76ff091d 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Synchronize.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Strategy/Synchronize.php @@ -1,7 +1,4 @@ copy = $copy; - } - - /** - * Applies the translation strategy. - * - * Depending on the type of fields, this will copy / synchronize a layout or - * auto-translate object ids. - * - * @since 3.7.1 - * - * @param Abstract_Object $object ACF object. - * @param mixed $value Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type mixed $original_value Optional. The translated value of the field, if any. - * } - * @return mixed Custom field value of the target object. - */ - public function execute( Abstract_Object $object, $value, array $field, array $args = array() ) { - $args = wp_parse_args( $args, array( 'original_value' => null ) ); - - if ( ! $this->can_execute( $field ) ) { - return $args['original_value']; - } - - if ( 'taxonomy' !== $field['type'] ) { - return parent::execute( $object, $value, $field, $args ); - } - - if ( ! isset( $args['target_language'] ) || ! $args['target_language'] instanceof PLL_Language ) { - return $value; - } - - if ( ! pll_is_translated_taxonomy( $field['taxonomy'] ) - || ! in_array( $field['taxonomy'], get_object_taxonomies( $object->get_type() ), true ) ) { - // Do not go any further if a taxonomy is not registered for the current object type. - return $value; - } - - $value = $this->translate_term( $value, $args['target_language'] ); - - if ( ! empty( $field['save_terms'] ) && isset( $args['target_id'] ) ) { - /* - * Save terms for the target object. - * - * The `acf_field_taxonomy::save_post()` method which assigns terms to the post fired - * by the `'acf/save_post'`action, is only fired for the source post. - * So we have to assign them to the target post ourselves. - */ - wp_set_object_terms( $args['target_id'], $value, $field['taxonomy'] ); - } - - return $value; - } - - /** - * Recursively checks if a field can be synchronized. - * - * @since 3.7 - * - * @param array $field Custom field definition. - * @return bool - */ - protected function can_execute_recursive( array $field ): bool { - if ( isset( $field['translations'] ) && 'sync' === $field['translations'] ) { - return true; - } - - switch ( $field['type'] ) { - case 'clone': - case 'group': - case 'repeater': - foreach ( $field['sub_fields'] as $sub_field ) { - // A child field is synchronized or translatable. Let's synchronize the parent field. - if ( isset( $sub_field['translations'] ) && 'translate' === $sub_field['translations'] ) { - return true; - } - - if ( $this->can_execute( $sub_field ) ) { - return true; - } - } - break; - - case 'flexible_content': - foreach ( $field['layouts'] as $layout ) { - foreach ( $layout['sub_fields'] as $sub_field ) { - // A child field is synchronized or translatable. Let's synchronize the parent field. - if ( isset( $sub_field['translations'] ) && 'translate' === $sub_field['translations'] ) { - return true; - } - - if ( $this->can_execute( $sub_field ) ) { - return true; - } - } - } - break; - } - - return false; - } - - /** - * Copies subfields in a repeater or flexible content field. - * - * @since 3.7 - * - * @param Abstract_Object $object ACF object. - * @param array $values Custom field value of the source object. - * @param array $field Custom field definition. - * @param array $args { - * Array of arguments. - * - * @type PLL_Language $target_language Language object of the target object. - * @type array $original_value The value to return if the field must not be synced/copied. Basically it's - * the field's original value. - * } - * @return array Custom field value of the target object. - */ - protected function apply_on_rows( Abstract_Object $object, array $values, array $field, array $args = array() ): array { - if ( empty( $field['sub_fields'] ) ) { - return $values; - } - - foreach ( $field['sub_fields'] as $subfield ) { - foreach ( $values as $row => $subvalues ) { - if ( preg_match( '/^row-(?.+)$/', (string) $row, $matches ) ) { - // Row already exists, let's update it. - if ( ! is_array( $subvalues ) ) { - continue; - } - - $i = $matches['incr']; - $parent = $this->get_field_key( $field ); - $subfield['pll_key'] = $parent . '_' . $i . '_' . $subfield['key']; // Adds an entry in `subfield` with the full path of the field. - $values[ $row ] = $this->apply_on_subfield( - $object, - $subvalues, - $subfield, - array( - 'target_language' => $args['target_language'], - 'original_value' => $args['original_value'][ $i ] ?? null, - ) - ); - - continue; - } - - // New row added, let's copy it. - $parent = $this->get_field_key( $field ); - $subfield['pll_key'] = $parent . '_' . $row . '_' . $subfield['key']; // Adds an entry in `subfield` with the full path of the field. - $values[ $row ] = $this->copy->apply_on_subfield( - $object, - $subvalues, - $subfield, - array( - 'target_language' => $args['target_language'], - 'original_value' => null, - ) - ); - } - } - - return $values; - } +class Synchronize extends Copy +{ + /** + * @var Copy + */ + protected $copy; + + /** + * Constructor. + * + * @since 3.7 + * + * @param Copy $copy Copy Strategy. + */ + public function __construct(Copy $copy) + { + $this->copy = $copy; + } + + /** + * Applies the translation strategy. + * + * Depending on the type of fields, this will copy / synchronize a layout or + * auto-translate object ids. + * + * @since 3.7.1 + * + * @param Abstract_Object $object ACF object. + * @param mixed $value Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var mixed $original_value Optional. The translated value of the field, if any. + * } + * + * @return mixed Custom field value of the target object. + */ + public function execute(Abstract_Object $object, $value, array $field, array $args = []) + { + $args = wp_parse_args($args, ['original_value' => null]); + + if (!$this->can_execute($field)) { + return $args['original_value']; + } + + if ('taxonomy' !== $field['type']) { + return parent::execute($object, $value, $field, $args); + } + + if (!isset($args['target_language']) || !$args['target_language'] instanceof PLL_Language) { + return $value; + } + + if (!pll_is_translated_taxonomy($field['taxonomy']) + || !in_array($field['taxonomy'], get_object_taxonomies($object->get_type()), true)) { + // Do not go any further if a taxonomy is not registered for the current object type. + return $value; + } + + $value = $this->translate_term($value, $args['target_language']); + + if (!empty($field['save_terms']) && isset($args['target_id'])) { + /* + * Save terms for the target object. + * + * The `acf_field_taxonomy::save_post()` method which assigns terms to the post fired + * by the `'acf/save_post'`action, is only fired for the source post. + * So we have to assign them to the target post ourselves. + */ + wp_set_object_terms($args['target_id'], $value, $field['taxonomy']); + } + + return $value; + } + + /** + * Recursively checks if a field can be synchronized. + * + * @since 3.7 + * + * @param array $field Custom field definition. + * + * @return bool + */ + protected function can_execute_recursive(array $field): bool + { + if (isset($field['translations']) && 'sync' === $field['translations']) { + return true; + } + + switch ($field['type']) { + case 'clone': + case 'group': + case 'repeater': + foreach ($field['sub_fields'] as $sub_field) { + // A child field is synchronized or translatable. Let's synchronize the parent field. + if (isset($sub_field['translations']) && 'translate' === $sub_field['translations']) { + return true; + } + + if ($this->can_execute($sub_field)) { + return true; + } + } + break; + + case 'flexible_content': + foreach ($field['layouts'] as $layout) { + foreach ($layout['sub_fields'] as $sub_field) { + // A child field is synchronized or translatable. Let's synchronize the parent field. + if (isset($sub_field['translations']) && 'translate' === $sub_field['translations']) { + return true; + } + + if ($this->can_execute($sub_field)) { + return true; + } + } + } + break; + } + + return false; + } + + /** + * Copies subfields in a repeater or flexible content field. + * + * @since 3.7 + * + * @param Abstract_Object $object ACF object. + * @param array $values Custom field value of the source object. + * @param array $field Custom field definition. + * @param array $args { + * Array of arguments. + * + * @var PLL_Language $target_language Language object of the target object. + * @var array $original_value The value to return if the field must not be synced/copied. Basically it's + * the field's original value. + * } + * + * @return array Custom field value of the target object. + */ + protected function apply_on_rows(Abstract_Object $object, array $values, array $field, array $args = []): array + { + if (empty($field['sub_fields'])) { + return $values; + } + + foreach ($field['sub_fields'] as $subfield) { + foreach ($values as $row => $subvalues) { + if (preg_match('/^row-(?.+)$/', (string) $row, $matches)) { + // Row already exists, let's update it. + if (!is_array($subvalues)) { + continue; + } + + $i = $matches['incr']; + $parent = $this->get_field_key($field); + $subfield['pll_key'] = $parent.'_'.$i.'_'.$subfield['key']; // Adds an entry in `subfield` with the full path of the field. + $values[$row] = $this->apply_on_subfield( + $object, + $subvalues, + $subfield, + [ + 'target_language' => $args['target_language'], + 'original_value' => $args['original_value'][$i] ?? null, + ] + ); + + continue; + } + + // New row added, let's copy it. + $parent = $this->get_field_key($field); + $subfield['pll_key'] = $parent.'_'.$row.'_'.$subfield['key']; // Adds an entry in `subfield` with the full path of the field. + $values[$row] = $this->copy->apply_on_subfield( + $object, + $subvalues, + $subfield, + [ + 'target_language' => $args['target_language'], + 'original_value' => null, + ] + ); + } + } + + return $values; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/Translation_Instructions.php b/__plugins/polylang-pro-3.7.6/integrations/acf/Translation_Instructions.php index d8203b90f5c7973cc940027c6d14ccb9661fb08f..c9adc08d89f3ef5f57742e816f1a07a4655e2efc 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/Translation_Instructions.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/Translation_Instructions.php @@ -1,14 +1,9 @@ 'no_translations_settings', - 'required' => 0, - 'label' => esc_html__( 'No translations settings', 'polylang-pro' ), - 'instructions' => esc_html__( 'No translations settings are available for field group with language location rules.', 'polylang-pro' ), - ); - - acf_render_field_instructions( $no_translations_settings ); - return; - } - - if ( $is_legacy_translated_field_group && ! isset( $field_group[ self::SETTING_KEY ] ) ) { - $field_group[ self::SETTING_KEY ] = 1; - } - - acf_render_field_wrap( - array( - 'label' => esc_html__( 'Display translation field instructions', 'polylang-pro' ), - 'instructions' => esc_html__( 'When enabled, the translation field instructions will be displayed below the field label.', 'polylang-pro' ), - 'type' => 'true_false', - 'name' => self::SETTING_KEY, - 'prefix' => 'acf_field_group', - 'value' => $field_group[ self::SETTING_KEY ] ?? 1, - 'ui' => 1, - ) - ); - } - - /** - * Appends the translation instructions to the field label using `acf/prepare_field` hook. - * Hooked to `acf/pre_render_fields` only to ensure instructions are displayed in the editor fields metabox. - * - * @since 3.7 - * @since 3.7.2 Moved from Dispatcher. - * - * @param array $fields The fields being rendered. - * @return array The fields. - */ - public static function append_translation_instructions( $fields ) { - add_filter( 'acf/prepare_field', array( static::class, 'get_field_instructions' ) ); - - return $fields; - } - - /** - * Returns the instructions for the given field. - * - * @since 3.7 - * @since 3.7.2 Moved from Dispatcher. - * - * @param array|false $field The field array or false. - * @return array|false The field array or false. - */ - public static function get_field_instructions( $field ) { - if ( ! is_array( $field ) ) { - return $field; - } - - $field_group = acf_get_field_group( $field['parent'] ); - if ( ! $field_group || ! isset( $field_group[ self::SETTING_KEY ] ) || ! $field_group[ self::SETTING_KEY ] ) { - return $field; - } - - $instructions = ' ' - . self::get_field_instruction( $field ); - - $field['instructions'] = ! empty( $field['instructions'] ) ? $field['instructions'] . '
' . $instructions : $instructions; - - return $field; - } - - /** - * Returns the instruction for the given field. - * - * @since 3.7 - * @since 3.7.2 Moved from Dispatcher. - * @since 3.7.5 Changed visibility from private to public. - * - * @param array $field The field. - * @return string The instruction. - */ - public static function get_field_instruction( array $field ): string { - if ( empty( $field ) ) { - return ''; - } - - if ( empty( $field['translations'] ) ) { - if ( in_array( $field['type'], array( 'group', 'repeater', 'clone', 'flexible_content' ), true ) ) { - $copy_strategy = new Copy(); - if ( $copy_strategy->can_execute( $field ) ) { - return __( 'This field is copied.', 'polylang-pro' ); - } - - $sync_strategy = new Synchronize( $copy_strategy ); - if ( $sync_strategy->can_execute( $field ) ) { - return __( 'This field is synchronized.', 'polylang-pro' ); - } - } - } else { - switch ( $field['translations'] ) { - case 'copy_once': - return __( 'This field is copied once.', 'polylang-pro' ); - case 'sync': - return __( 'This field is synchronized.', 'polylang-pro' ); - case 'translate': - return __( 'This field is translated.', 'polylang-pro' ); - case 'translate_once': - return __( 'This field is translated once.', 'polylang-pro' ); - } - } - - return __( 'This field is ignored.', 'polylang-pro' ); - } +class Translation_Instructions +{ + const TAB_NAME = 'pll-instructions'; + const SETTING_KEY = 'pll_display_field_instructions'; + + /** + * Initializes the instructions and their settings. + * + * @since 3.7.2 + * + * @return void + */ + public function on_acf_init() + { + add_filter('acf/field_group/additional_group_settings_tabs', [$this, 'add_field_instructions_setting_tab']); + add_action('acf/field_group/render_group_settings_tab/'.self::TAB_NAME, [$this, 'add_field_instructions_setting']); + add_filter('acf/pre_render_fields', [static::class, 'append_translation_instructions']); + } + + /** + * Adds the field instructions setting tab in fields group edit page. + * + * @since 3.7.2 + * + * @param array $tabs The tabs. + * + * @return array The tabs. + */ + public function add_field_instructions_setting_tab($tabs) + { + $tabs[self::TAB_NAME] = __('Translations Settings', 'polylang-pro'); + + return $tabs; + } + + /** + * Adds the field instructions setting in fields group edit page. + * + * @since 3.7.2 + * + * @param array $field_group The field group. + * + * @return void + */ + public function add_field_instructions_setting($field_group) + { + $is_legacy_translated_field_group = Field_Settings::is_legacy_translated_field_group((int) $field_group['ID']); + + if (Location_Language::has_language_location_rule($field_group) && !$is_legacy_translated_field_group) { + $field_group[self::SETTING_KEY] = 0; + + $no_translations_settings = [ + 'id' => 'no_translations_settings', + 'required' => 0, + 'label' => esc_html__('No translations settings', 'polylang-pro'), + 'instructions' => esc_html__('No translations settings are available for field group with language location rules.', 'polylang-pro'), + ]; + + acf_render_field_instructions($no_translations_settings); + + return; + } + + if ($is_legacy_translated_field_group && !isset($field_group[self::SETTING_KEY])) { + $field_group[self::SETTING_KEY] = 1; + } + + acf_render_field_wrap( + [ + 'label' => esc_html__('Display translation field instructions', 'polylang-pro'), + 'instructions' => esc_html__('When enabled, the translation field instructions will be displayed below the field label.', 'polylang-pro'), + 'type' => 'true_false', + 'name' => self::SETTING_KEY, + 'prefix' => 'acf_field_group', + 'value' => $field_group[self::SETTING_KEY] ?? 1, + 'ui' => 1, + ] + ); + } + + /** + * Appends the translation instructions to the field label using `acf/prepare_field` hook. + * Hooked to `acf/pre_render_fields` only to ensure instructions are displayed in the editor fields metabox. + * + * @since 3.7 + * @since 3.7.2 Moved from Dispatcher. + * + * @param array $fields The fields being rendered. + * + * @return array The fields. + */ + public static function append_translation_instructions($fields) + { + add_filter('acf/prepare_field', [static::class, 'get_field_instructions']); + + return $fields; + } + + /** + * Returns the instructions for the given field. + * + * @since 3.7 + * @since 3.7.2 Moved from Dispatcher. + * + * @param array|false $field The field array or false. + * + * @return array|false The field array or false. + */ + public static function get_field_instructions($field) + { + if (!is_array($field)) { + return $field; + } + + $field_group = acf_get_field_group($field['parent']); + if (!$field_group || !isset($field_group[self::SETTING_KEY]) || !$field_group[self::SETTING_KEY]) { + return $field; + } + + $instructions = ' ' + .self::get_field_instruction($field); + + $field['instructions'] = !empty($field['instructions']) ? $field['instructions'].'
'.$instructions : $instructions; + + return $field; + } + + /** + * Returns the instruction for the given field. + * + * @since 3.7 + * @since 3.7.2 Moved from Dispatcher. + * @since 3.7.5 Changed visibility from private to public. + * + * @param array $field The field. + * + * @return string The instruction. + */ + public static function get_field_instruction(array $field): string + { + if (empty($field)) { + return ''; + } + + if (empty($field['translations'])) { + if (in_array($field['type'], ['group', 'repeater', 'clone', 'flexible_content'], true)) { + $copy_strategy = new Copy(); + if ($copy_strategy->can_execute($field)) { + return __('This field is copied.', 'polylang-pro'); + } + + $sync_strategy = new Synchronize($copy_strategy); + if ($sync_strategy->can_execute($field)) { + return __('This field is synchronized.', 'polylang-pro'); + } + } + } else { + switch ($field['translations']) { + case 'copy_once': + return __('This field is copied once.', 'polylang-pro'); + case 'sync': + return __('This field is synchronized.', 'polylang-pro'); + case 'translate': + return __('This field is translated.', 'polylang-pro'); + case 'translate_once': + return __('This field is translated once.', 'polylang-pro'); + } + } + + return __('This field is ignored.', 'polylang-pro'); + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/acf/load.php b/__plugins/polylang-pro-3.7.6/integrations/acf/load.php index 4bfcda6b45f5c2de4db1a49ab303617aa1c1f584..19c6fcf6c80cf042a9bcd13c0d9fbe09c89cf38b 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/acf/load.php +++ b/__plugins/polylang-pro-3.7.6/integrations/acf/load.php @@ -1,34 +1,31 @@ model->has_languages() ) { - // Run only if Polylang (and its API) is loaded, and if there is at least one language. - return; - } + if (!did_action('pll_init') || !PLL()->model->has_languages()) { + // Run only if Polylang (and its API) is loaded, and if there is at least one language. + return; + } - PLL_Integrations::instance()->acf = new Main(); + PLL_Integrations::instance()->acf = new Main(); - add_action( 'acf/init', array( PLL_Integrations::instance()->acf, 'on_acf_init' ) ); - } + add_action('acf/init', [PLL_Integrations::instance()->acf, 'on_acf_init']); + } ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/admin-columns/cpac.php b/__plugins/polylang-pro-3.7.6/integrations/admin-columns/cpac.php index a8038987f7987e629cade9a4bc8693f179a5b08b..5ff8818c95ed1556c1bc1865376a6765b2bcb30b 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/admin-columns/cpac.php +++ b/__plugins/polylang-pro-3.7.6/integrations/admin-columns/cpac.php @@ -1,7 +1,4 @@ model->get_translated_post_types() as $type) { + if (isset($_REQUEST['list_screen'])) { // phpcs:ignore WordPress.Security.NonceVerification + $filter = 'manage_'.('attachment' === $type ? 'upload' : 'edit-'.$type).'_columns'; + add_filter($filter, [$this, 'remove_filter_lang'], 90); // Before Polylang. + } - /** - * Add filters. - * - * @since 2.4 - * - * @return void - */ - public function init() { - foreach ( PLL()->model->get_translated_post_types() as $type ) { - if ( isset( $_REQUEST['list_screen'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - $filter = 'manage_' . ( 'attachment' === $type ? 'upload' : 'edit-' . $type ) . '_columns'; - add_filter( $filter, array( $this, 'remove_filter_lang' ), 90 ); // Before Polylang. - } + $filter = 'option_cpac_options_'.('attachment' === $type ? 'wp-media' : $type).'__default'; + add_filter($filter, [$this, 'filter_default_columns']); + } + } - $filter = 'option_cpac_options_' . ( 'attachment' === $type ? 'wp-media' : $type ) . '__default'; - add_filter( $filter, array( $this, 'filter_default_columns' ) ); - } - } + /** + * Deactivates the admin language filter on Admin Columns settings page. + * + * @since 2.4 + * + * @param array $columns List of table columns. + * + * @return array + */ + public function remove_filter_lang($columns) + { + PLL()->filters_columns->filter_lang = ''; - /** - * Deactivates the admin language filter on Admin Columns settings page. - * - * @since 2.4 - * - * @param array $columns List of table columns. - * @return array - */ - public function remove_filter_lang( $columns ) { - PLL()->filters_columns->filter_lang = ''; - return $columns; - } + return $columns; + } - /** - * Fixes the Polylang columns in default columns. - * - * @since 2.4 - * - * @param array $columns List of table columns. - * @return array - */ - public function filter_default_columns( $columns ) { - $screen = get_current_screen(); + /** + * Fixes the Polylang columns in default columns. + * + * @since 2.4 + * + * @param array $columns List of table columns. + * + * @return array + */ + public function filter_default_columns($columns) + { + $screen = get_current_screen(); - if ( isset( $screen->base ) ) { - $is_post_type = 'edit' === $screen->base && has_filter( 'manage_edit-' . $screen->post_type . '_columns', array( PLL()->filters_columns, 'add_post_column' ) ); - $is_media = 'upload' === $screen->base && has_filter( 'manage_upload_columns', array( PLL()->filters_columns, 'add_post_column' ) ); + if (isset($screen->base)) { + $is_post_type = 'edit' === $screen->base && has_filter('manage_edit-'.$screen->post_type.'_columns', [PLL()->filters_columns, 'add_post_column']); + $is_media = 'upload' === $screen->base && has_filter('manage_upload_columns', [PLL()->filters_columns, 'add_post_column']); - if ( $is_post_type || $is_media ) { - foreach ( pll_languages_list() as $lang ) { - unset( $columns[ 'language_' . $lang ] ); - } + if ($is_post_type || $is_media) { + foreach (pll_languages_list() as $lang) { + unset($columns['language_'.$lang]); + } - $columns = PLL()->filters_columns->add_post_column( $columns ); - } - } + $columns = PLL()->filters_columns->add_post_column($columns); + } + } - return $columns; - } + return $columns; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/admin-columns/load.php b/__plugins/polylang-pro-3.7.6/integrations/admin-columns/load.php index 89b7586b6584f364cb95e1819f5e3f43832b835f..993d46da751f94271d25ba91d0313ec70faa43bc 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/admin-columns/load.php +++ b/__plugins/polylang-pro-3.7.6/integrations/admin-columns/load.php @@ -1,17 +1,14 @@ cpac = new PLL_CPAC(), 'init' ) ); - } - } + 'after_setup_theme', + function () { + if (did_action('pll_init') && (defined('AC_FILE') || defined('ACP_FILE'))) { + add_action('admin_init', [PLL_Integrations::instance()->cpac = new PLL_CPAC(), 'init']); + } + } ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/beaver-builder/flbuilder.php b/__plugins/polylang-pro-3.7.6/integrations/beaver-builder/flbuilder.php index 212ec6b0443207e4621b50c8b208590fc192f857..bcf1503421f1c961e2843c3fcf72b8e32879fde5 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/beaver-builder/flbuilder.php +++ b/__plugins/polylang-pro-3.7.6/integrations/beaver-builder/flbuilder.php @@ -1,41 +1,42 @@ flbuilder = new PLL_FLBuilder(); - } - } + 'plugins_loaded', + function () { + if (class_exists('FLBuilderLoader')) { + PLL_Integrations::instance()->flbuilder = new PLL_FLBuilder(); + } + } ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/content-blocks/content-blocks.php b/__plugins/polylang-pro-3.7.6/integrations/content-blocks/content-blocks.php index b34c113ce4745abeed4e2ed862a1be32fee2a5a1..30b1b1964231d7e5d7bcd12897168540fcc6d0d7 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/content-blocks/content-blocks.php +++ b/__plugins/polylang-pro-3.7.6/integrations/content-blocks/content-blocks.php @@ -1,7 +1,4 @@ content_blocks = new PLL_Content_Blocks(), 'init' ) ); - } - }, - 0 + 'plugins_loaded', + function () { + if (function_exists('custom_post_widget_plugin_init')) { + add_action('pll_init', [PLL_Integrations::instance()->content_blocks = new PLL_Content_Blocks(), 'init']); + } + }, + 0 ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/cptui/cptui.php b/__plugins/polylang-pro-3.7.6/integrations/cptui/cptui.php index 5d020d281c6e53ff80163725eb8e24b7390042c0..33cd51357e0c10d6eac010945deb8c004f6339c3 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/cptui/cptui.php +++ b/__plugins/polylang-pro-3.7.6/integrations/cptui/cptui.php @@ -1,7 +1,4 @@ array( - 'label' => 1, - 'singular_label' => 1, - 'description' => 1, - 'labels' => array( - '*' => 1, - ), - ), - ); +class PLL_CPTUI +{ + /** + * Initializes filters and actions. + * + * @since 2.1 + * + * @return void + */ + public function init() + { + $keys = [ + '*' => [ + 'label' => 1, + 'singular_label' => 1, + 'description' => 1, + 'labels' => [ + '*' => 1, + ], + ], + ]; - new PLL_Translate_Option( 'cptui_post_types', $keys, array( 'context' => 'CPT UI' ) ); - new PLL_Translate_Option( 'cptui_taxonomies', $keys, array( 'context' => 'CPT UI' ) ); + new PLL_Translate_Option('cptui_post_types', $keys, ['context' => 'CPT UI']); + new PLL_Translate_Option('cptui_taxonomies', $keys, ['context' => 'CPT UI']); - if ( PLL() instanceof PLL_Frontend && ! PLL()->options['force_lang'] ) { - // Special case when the language is set from the content as CPT and taxonomies are registered before the language is defined. - add_action( 'pll_language_defined', array( $this, 'pll_language_defined' ) ); - } + if (PLL() instanceof PLL_Frontend && !PLL()->options['force_lang']) { + // Special case when the language is set from the content as CPT and taxonomies are registered before the language is defined. + add_action('pll_language_defined', [$this, 'pll_language_defined']); + } + // Add CPT UI post types and taxonomies to Polylang settings. + add_filter('pll_get_post_types', [$this, 'pll_get_types'], 10, 2); + add_filter('pll_get_taxonomies', [$this, 'pll_get_types'], 10, 2); + } - // Add CPT UI post types and taxonomies to Polylang settings. - add_filter( 'pll_get_post_types', array( $this, 'pll_get_types' ), 10, 2 ); - add_filter( 'pll_get_taxonomies', array( $this, 'pll_get_types' ), 10, 2 ); - } + /** + * Translates custom post types and taxonomies labels when the language is set from the content. + * + * @since 2.1 + * + * @param array $types Array of registered post types or taxonomies. + * @param array $cptui_types Array of CPT UI post types or taxonomies. + */ + public function translate_registered_types($types, $cptui_types) + { + foreach ($types as $name => $type) { + if (in_array($name, $cptui_types)) { + $type->label = pll__($type->label); + $type->description = pll__($type->description); - /** - * Translates custom post types and taxonomies labels when the language is set from the content. - * - * @since 2.1 - * - * @param array $types Array of registered post types or taxonomies. - * @param array $cptui_types Array of CPT UI post types or taxonomies. - */ - public function translate_registered_types( $types, $cptui_types ) { - foreach ( $types as $name => $type ) { - if ( in_array( $name, $cptui_types ) ) { - $type->label = pll__( $type->label ); - $type->description = pll__( $type->description ); + foreach (array_keys(get_object_vars($type->labels)) as $key) { + $type->labels->$key = pll__($type->labels->$key); + } + } + } + } - foreach ( array_keys( get_object_vars( $type->labels ) ) as $key ) { - $type->labels->$key = pll__( $type->labels->$key ); - } - } - } - } + /** + * Translates custom post types and taxonomies labels when the language is set from the content. + * + * @since 2.1 + */ + public function pll_language_defined() + { + $this->translate_registered_types($GLOBALS['wp_post_types'], array_keys(get_option('cptui_post_types', []))); + $this->translate_registered_types($GLOBALS['wp_taxonomies'], array_keys(get_option('cptui_taxonomies', []))); + } - /** - * Translates custom post types and taxonomies labels when the language is set from the content. - * - * @since 2.1 - */ - public function pll_language_defined() { - $this->translate_registered_types( $GLOBALS['wp_post_types'], array_keys( get_option( 'cptui_post_types', array() ) ) ); - $this->translate_registered_types( $GLOBALS['wp_taxonomies'], array_keys( get_option( 'cptui_taxonomies', array() ) ) ); - } + /** + * Add CPT UI post types and taxonomies to Polylang settings. + * + * @since 2.1 + * + * @param string[] $types List of post type or taxonomy names. + * @param bool $is_settings True when displaying the list in Polylang settings. + * + * @return string[] + */ + public function pll_get_types($types, $is_settings) + { + if ($is_settings) { + $type = substr(current_filter(), 8); + $cptui_types = get_option("cptui_{$type}"); - /** - * Add CPT UI post types and taxonomies to Polylang settings. - * - * @since 2.1 - * - * @param string[] $types List of post type or taxonomy names. - * @param bool $is_settings True when displaying the list in Polylang settings. - * @return string[] - */ - public function pll_get_types( $types, $is_settings ) { - if ( $is_settings ) { - $type = substr( current_filter(), 8 ); - $cptui_types = get_option( "cptui_{$type}" ); + if (is_array($cptui_types)) { + $types = array_merge($types, array_keys($cptui_types)); + } + } - if ( is_array( $cptui_types ) ) { - $types = array_merge( $types, array_keys( $cptui_types ) ); - } - } - return $types; - } + return $types; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/cptui/load.php b/__plugins/polylang-pro-3.7.6/integrations/cptui/load.php index a7eb0a80bd3c3926aa997c7587e86781504f7c32..ad685d4887124090c4e77b45683d7423f5b01752 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/cptui/load.php +++ b/__plugins/polylang-pro-3.7.6/integrations/cptui/load.php @@ -1,18 +1,15 @@ cptui = new PLL_CPTUI(), 'init' ) ); - } - }, - 0 + 'plugins_loaded', + function () { + if (defined('CPTUI_VERSION')) { + add_action('pll_init', [PLL_Integrations::instance()->cptui = new PLL_CPTUI(), 'init']); + } + }, + 0 ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/divi/divi-builder.php b/__plugins/polylang-pro-3.7.6/integrations/divi/divi-builder.php index 8a689847f032bb74930888ecdd7916f9e818c3ff..d68316d8f28fb6d78c04c2528f728f1969c82c93 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/divi/divi-builder.php +++ b/__plugins/polylang-pro-3.7.6/integrations/divi/divi-builder.php @@ -1,74 +1,77 @@ is_active() ) { - wp_update_post( $post ); - } + if ((new PLL_Toggle_User_Meta(PLL_Duplicate_Action::META_NAME))->is_active()) { + wp_update_post($post); + } - return $is_block_editor; - } + return $is_block_editor; + } } diff --git a/__plugins/polylang-pro-3.7.6/integrations/divi/load.php b/__plugins/polylang-pro-3.7.6/integrations/divi/load.php index 84b5b07cb5fc5e3fec217c062b1ab7235870bc4d..194adaa4a9a7f5568a93b35f5a59c06078a3e447 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/divi/load.php +++ b/__plugins/polylang-pro-3.7.6/integrations/divi/load.php @@ -1,17 +1,14 @@ divi_builder = new PLL_Divi_Builder(); - } - } + 'plugins_loaded', + function () { + if ('Divi' === get_template() || defined('ET_BUILDER_PLUGIN_VERSION')) { + PLL_Integrations::instance()->divi_builder = new PLL_Divi_Builder(); + } + } ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/events-calendar/load.php b/__plugins/polylang-pro-3.7.6/integrations/events-calendar/load.php index 30a18bdc186ac455c0a2473e20198aadfc428a7d..9d0f669c039dfffd0b6b444ebb12b43f2b3b0ead 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/events-calendar/load.php +++ b/__plugins/polylang-pro-3.7.6/integrations/events-calendar/load.php @@ -1,18 +1,15 @@ tec = new PLL_TEC(), 'init' ) ); - } - }, - 0 + 'plugins_loaded', + function () { + if (defined('TRIBE_EVENTS_FILE')) { + add_action('pll_init', [PLL_Integrations::instance()->tec = new PLL_TEC(), 'init']); + } + }, + 0 ); diff --git a/__plugins/polylang-pro-3.7.6/integrations/events-calendar/tec.php b/__plugins/polylang-pro-3.7.6/integrations/events-calendar/tec.php index 7b0bc170d0d0bcbd21ff17a1304b90eef8538b21..73f4dfa151e943d6bece85ca4463d6fc363e3e14 100644 --- a/__plugins/polylang-pro-3.7.6/integrations/events-calendar/tec.php +++ b/__plugins/polylang-pro-3.7.6/integrations/events-calendar/tec.php @@ -1,1281 +1,1363 @@ model->has_languages() ) { - return; - } - - $this->polylang = $polylang; - $this->curlang = null; - $this->slugs_model = ! empty( $polylang->translate_slugs ) && ! empty( $polylang->translate_slugs->slugs_model ) ? $polylang->translate_slugs->slugs_model : null; - $this->is_tec_rest_request = array(); - $this->translatable_slug_ids = array(); - - if ( $polylang->links instanceof PLL_Admin_Links ) { - $this->links = $polylang->links; - } - - add_filter( 'pll_get_taxonomies', array( $this, 'translate_taxonomies' ), 10, 2 ); - add_filter( 'pll_get_post_types', array( $this, 'translate_types' ), 10, 2 ); - - add_action( 'save_post_' . Venue::POSTTYPE, array( $this, 'set_language' ), 10, 3 ); - add_action( 'save_post_' . Organizer::POSTTYPE, array( $this, 'set_language' ), 10, 3 ); - - $tec = TEC::instance(); - - if ( empty( $polylang->options['force_lang'] ) ) { - add_action( 'pll_language_defined', array( $this, 'fix_date_translations' ) ); - } - - self::$metas = array_merge( $tec->metaTags, $tec->venueTags, $tec->organizerTags, array( '_VenueShowMap', '_VenueShowMapLink' ) ); - - if ( ! empty( $this->links ) && ! empty( $GLOBALS['post'] ) ) { - $data = $this->links->get_data_from_new_post_translation_request( $GLOBALS['post']->post_type ); - - if ( ! empty( $data ) ) { - // Default values for events. - foreach ( self::$metas as $meta ) { - $filter = str_replace( array( '_Event', '_Organizer', '_Venue' ), array( '', 'Organizer', 'Venue' ), $meta ); - add_filter( 'tribe_get_meta_default_value_' . $filter, array( $this, 'copy_event_meta' ), 10, 4 ); // Since TEC 4.0.7. - } - - add_filter( 'tribe_display_event_linked_post_dropdown_id', array( $this, 'translate_linked_post' ) ); - } - } - - add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 60 ); // After `Tribe__Events__Query->pre_get_posts()`. - - add_filter( 'pll_copy_post_metas', array( $this, 'copy_post_metas' ) ); - add_filter( 'pll_translate_post_meta', array( $this, 'translate_meta' ), 10, 3 ); - - // Translate links with translated slugs - add_action( 'init', array( $this, 'reset_slugs' ), 11 ); // Just after `Tribe__Events__Main->init()`. - add_filter( 'register_taxonomy_args', array( $this, 'register_taxonomy_args' ), 10, 2 ); - add_filter( 'tribe_events_get_link', array( $this, 'get_link' ) ); - add_filter( 'pll_get_archive_url', array( $this, 'pll_get_archive_url' ), 10, 2 ); - add_filter( 'pll_term_link', array( $this, 'filter_tec_term_link' ), 5, 3 ); // Before `PLL_Translate_Slugs->pll_term_link()`. - add_filter( 'pll_translated_slugs', array( $this, 'pll_translated_slugs' ), 10, 3 ); - add_filter( 'pll_sanitize_string_translation', array( $this, 'sanitize_string_translation' ), 10, 2 ); - add_filter( 'tribe_events_rewrite_i18n_slugs_raw', array( $this, 'rewrite_slugs' ) ); - - // Options to translate. - $keys = array( - 'dateWithYearFormat' => 1, - 'dateWithoutYearFormat' => 1, - 'monthAndYearFormat' => 1, - 'dateTimeSeparator' => 1, - 'timeRangeSeparator' => 1, - 'tribeEventsBeforeHTML' => 1, - 'tribeEventsAfterHTML' => 1, - ); - - $args = array( - 'context' => 'The Events Calendar', - 'sanitize_callback' => array( $this, 'sanitize_strings' ), - ); - - new PLL_Translate_Option( 'tribe_events_calendar_options', $keys, $args ); - - // TEC views V2. - add_filter( 'tribe_events_rewrite_i18n_domains', '__return_empty_array', 10000 ); // No i18n domains, no translations to deal with. - add_filter( 'tribe_events_rewrite_i18n_slugs', array( $this, 'fix_escaped_dashes_in_slugs' ), 10, 2 ); - - add_filter( 'tribe_events_category_slug', array( $this, 'get_category_slug' ) ); - add_filter( 'tribe_events_tag_slug', array( $this, 'get_tag_slug' ) ); - - add_filter( 'tribe_events_views_v2_endpoint_url', array( $this, 'add_missing_lang_to_rest_url' ) ); - add_filter( 'locale', array( $this, 'filter_locale_for_rest' ), 5 ); - - add_filter( 'tribe_events_views_v2_publicly_visible_views_query_args', array( $this, 'add_language_to_publicly_visible_views_query_args' ), 5 ); - add_filter( 'tribe_events_views_v2_view_template_vars', array( $this, 'translate_widget_view_more_link' ), 5 ); - add_filter( 'tribe_rewrite_parse_query_vars', array( $this, 'force_language_on_tec_parse_query_vars' ), 10, 3 ); - add_filter( 'tribe_events_views_v2_url_query_args', array( $this, 'add_missing_lang_to_query_arg' ) ); - add_filter( 'tribe_rewrite_pre_canonical_url', array( $this, 'add_missing_lang_to_non_canonical_url' ), 10, 2 ); - add_filter( 'tribe_rewrite_pre_canonical_url', array( $this, 'remove_name_arg_from_non_canonical_url' ), 10, 2 ); - - add_filter( 'tribe_rewrite_canonical_url', array( $this, 'fix_language_in_canonical_url' ), 5, 2 ); - add_filter( 'tribe_rewrite_canonical_url', array( $this, 'translate_canonical_url' ), 5, 2 ); - } - - /** - * Language and translation management for taxonomies. - * - * @since 2.2 - * - * @param string[] $taxonomies List of taxonomy names for which Polylang manages language and translations. - * @param bool $hide True when displaying the list in Polylang settings. - * @return string[] List of taxonomy names for which Polylang manages language and translations. - */ - public function translate_taxonomies( $taxonomies, $hide ) { - // Hide from Polylang settings - return $hide ? array_diff( $taxonomies, array( TEC::TAXONOMY ) ) : array_merge( $taxonomies, array( TEC::TAXONOMY ) ); - } - - /** - * Language and translation management for custom post types. - * - * @since 2.2 - * - * @param string[] $types 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[] List of post type names for which Polylang manages language and translations. - */ - public function translate_types( $types, $hide ) { - $tec_types = array( TEC::POSTTYPE, TEC::VENUE_POST_TYPE, TEC::ORGANIZER_POST_TYPE ); - return $hide ? array_diff( $types, $tec_types ) : array_merge( $types, $tec_types ); - } - - /** - * Save the language of Venues and Organizers. - * Needed when they are created from the Event form. - * - * @since 2.2 - * - * @param int $post_id Post id. - * @param WP_Post $post Post object. - * @param bool $update Whether it is an update or not. - * @return void - */ - public function set_language( $post_id, $post, $update ) { - if ( $update || ! isset( $_POST['post_lang_choice'] ) ) { - return; - } - - $post_type_object = get_post_type_object( $post->post_type ); - - if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) { - return; - } - - check_admin_referer( 'pll_language', '_pll_nonce' ); - $lang = $this->polylang->model->get_language( sanitize_key( $_POST['post_lang_choice'] ) ); - - if ( ! $lang ) { - return; - } - - $this->polylang->model->post->set_language( $post_id, $lang ); - } - - /** - * Once the language is set from content, this resets all the date-related translations in TEC to the current lang. - * In `Tribe__Date_Utils`, TEC stores these translations in static private properties before the language is set in - * PLL. Then these translations are use later, AFTER the language is set in PLL, leading to views exploding due to - * array keys not being set. - * The filter 'tribe_events_get_days_of_week' can't be used because it doesn't include the function's arg `$format`. - * - * @since 3.1 - * @see tribe_events_get_days_of_week() - * - * @return void - */ - public function fix_date_translations() { - $properties = array( - 'localized_months_full', - 'localized_months_short', - 'localized_weekdays', - 'localized_months', - ); - - foreach ( $properties as $property ) { - $property = new ReflectionProperty( Tribe__Date_Utils::class, $property ); - $property->setAccessible( true ); - $property->setValue( array() ); - } - - TEC::instance()->setup_l10n_strings(); - } - - /** - * Populates default event metas for a newly created event translation - * - * @since 2.2 - * - * @param mixed $value Meta value. - * @param int $id Post id. - * @param string $meta Meta key. - * @param bool $single Whether to return a single value. - * @return mixed - */ - public function copy_event_meta( $value, $id, $meta, $single ) { - if ( ! empty( $_GET['from_post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - $value = get_post_meta( (int) $_GET['from_post'], $meta, $single ); // phpcs:ignore WordPress.Security.NonceVerification - } - return $value; - } - - /** - * Populates default values for venues and organizers for a newly created event translation. - * - * @since 2.2 - * - * @param array $posts Array of linked posts. - * @return array - */ - public function translate_linked_post( $posts ) { - if ( empty( $posts ) || empty( $GLOBALS['post'] ) || empty( $this->links ) ) { - return $posts; - } - - $data = $this->links->get_data_from_new_post_translation_request( $GLOBALS['post']->post_type ); - - if ( empty( $data ) ) { - return $posts; - } - - $lang = $data['new_lang']->slug; - $post_metas = ! empty( $this->polylang->sync ) && ! empty( $this->polylang->sync->post_metas ) ? $this->polylang->sync->post_metas : false; - - foreach ( $posts as $key => $post_id ) { - $tr_id = pll_get_post( $post_id, $lang ); - - if ( ! empty( $tr_id ) ) { - $posts[ $key ] = $tr_id; - continue; - } - - // If the translated venue or organizer doesn't exist, create it. - $post = get_post( $post_id, ARRAY_A ); // Output as an array for `wp_insert_post()`. - - if ( empty( $post ) ) { - // `null` value. - continue; - } - - unset( $post['ID'] ); - - $tr_id = wp_insert_post( wp_slash( $post ) ); - - if ( ! is_int( $tr_id ) ) { - // `WP_Error` value. - continue; - } - - $translations = pll_get_post_translations( $post_id ); - $translations[ $lang ] = $tr_id; - - pll_set_post_language( $tr_id, $lang ); - pll_save_post_translations( $translations ); - - if ( ! empty( $post_metas ) ) { - $post_metas->copy( $post_id, $tr_id, $lang ); - } - - $posts[ $key ] = $tr_id; - } - - return $posts; - } - - /** - * Removes date filters when searching for untranslated events in the metabox autocomplete field - * - * @since 2.2.8 - * - * @return void - */ - public function pre_get_posts() { - if ( wp_doing_ajax() && isset( $_GET['action'] ) && 'pll_posts_not_translated' === $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification - // See Tribe__Events__Query::pre_get_posts() when should_remove_date_filters() returns true - remove_filter( 'posts_where', array( 'Tribe__Events__Query', 'posts_where' ) ); - remove_filter( 'posts_fields', array( 'Tribe__Events__Query', 'posts_fields' ) ); - remove_filter( 'posts_orderby', array( 'Tribe__Events__Query', 'posts_orderby' ) ); - } - } - - /** - * Synchronize event metas. - * - * @since 2.2 - * - * @param array $metas Custom fields to copy or synchronize. - * @return array - */ - public function copy_post_metas( $metas ) { - return array_merge( $metas, self::$metas ); - } - - /** - * Translate venues and organizers before they are copied or synchronized. - * - * @since 2.3 - * - * @param mixed $value Meta value. - * @param string $key Meta key. - * @param string $lang Language of target. - * @return mixed - */ - public function translate_meta( $value, $key, $lang ) { - if ( ( '_EventVenueID' === $key || '_EventOrganizerID' === $key ) && $tr_value = pll_get_post( $value, $lang ) ) { - $value = $tr_value; - } - return $value; - } - - /** - * Resets all TEC translated slugs to an English value as the TEC slug translation system - * does not work in a multilingual context (TEC 4.4.5 + WP 4.7.3). - * - * @since 2.2 - * - * @return void - */ - public function reset_slugs() { - $tec = TEC::instance(); - - foreach ( $this->get_slugs_to_reset() as $key => $slug ) { - $tec->$key = $slug; - } - - // Those are deprecated since TEC 4.0 and should not appear in the list of translatable strings anymore. - $tec->taxRewriteSlug = $tec->rewriteSlug . '/category'; - $tec->tagRewriteSlug = $tec->rewriteSlug . '/tag'; - } - - /** - * Resets the category base rewrite slug in taxonomy. - * - * @since 2.2 - * - * @param array $args Array of arguments for registering a taxonomy. - * @param string $taxonomy Taxonomy key. - * @return array - */ - public function register_taxonomy_args( $args, $taxonomy ) { - if ( TEC::TAXONOMY === $taxonomy && is_array( $args['rewrite'] ) ) { - $args['rewrite']['slug'] = TEC::instance()->rewriteSlug . '/category'; - } - - return $args; - } - - /** - * Filters the links to add the language code. - * - * @since 2.2 - * - * @param string $link Link generated by The Events Calendar. - * @return string - */ - public function get_link( $link ) { - $curlang = $this->get_curlang(); - - if ( empty( $curlang ) || empty( $this->slugs_model ) ) { - return $link; - } - - $link = $this->polylang->links_model->add_language_to_link( $link, $curlang ); - - foreach ( $this->get_translatable_slug_ids() as $slug_id ) { - $link = $this->slugs_model->translate_slug( $link, $curlang, $slug_id ); - } - - return $link; - } - - /** - * Translates slugs in the language switcher. - * - * @since 2.2 - * - * @param string $url Url in the language switcher. - * @param PLL_Language $language Language object. - * @return string Modified url. - */ - public function pll_get_archive_url( $url, $language ) { - if ( empty( $this->slugs_model ) || ! is_post_type_archive( TEC::POSTTYPE ) ) { - return $url; - } - - foreach ( $this->get_translatable_slug_ids() as $slug_id ) { - $url = $this->slugs_model->switch_translated_slug( $url, $language, $slug_id ); - } - - return $url; - } - - /** - * Modifies term links for the taxonomies used by TEC. - * In `PLL_TEC->reset_slugs()` we don't include the full taxonomy slug (`Tribe__Events__Main->taxRewriteSlug`) in - * our translated strings, as it is deprecated in TEC 4.0 and also a composite of - * `{post type archive slug}/{tax base slug}`. This explains why this method is needed. - * - * @since 3.1 - * @see PLL_TEC->reset_slugs() - * @see PLL_TEC->pll_translated_slugs() - * - * @param string $url The term link. - * @param PLL_Language $lang The term language. - * @param WP_Term $term The term object. - * @return string - */ - public function filter_tec_term_link( $url, $lang, $term ) { - if ( empty( $this->slugs_model ) || TEC::TAXONOMY !== $term->taxonomy ) { - return $url; - } - - $url = $this->slugs_model->translate_slug( $url, $lang, 'archive_' . TEC::POSTTYPE ); - return $this->slugs_model->translate_slug( $url, $lang, 'tribe_category' ); - } - - /** - * Fixes the events slug in translatable slugs. - * Translates other TEC slugs. - * - * @since 2.2 - * - * @param array $slugs Translated slugs. - * @param PLL_Language $language Language object. - * @param PLL_MO $mo Strings translations object. - * @return array - */ - public function pll_translated_slugs( $slugs, $language, &$mo ) { - /** - * In `PLL_TEC->reset_slugs()` we don't include the full taxonomy slug (`Tribe__Events__Main->taxRewriteSlug`) in - * our translated strings, as it is deprecated in TEC 4.0 and also a composite of - * `{post type archive slug}/{tax base slug}`. This is why we unset `$slugs[ TEC::TAXONOMY ]` here. - */ - unset( $slugs[ 'archive_' . TEC::POSTTYPE ]['hide'], $slugs[ TEC::TAXONOMY ] ); - - $slugs[ 'archive_' . TEC::POSTTYPE ]['slug'] = $slug = TEC::instance()->getRewriteSlug(); - $tr_slug = $mo->translate( $slug ); - $slugs[ 'archive_' . TEC::POSTTYPE ]['translations'][ $language->slug ] = empty( $tr_slug ) ? $slug : $tr_slug; - - foreach ( $this->get_slugs_to_reset() as $slug ) { - $slugs[ 'tribe_' . $slug ]['slug'] = $slug; - $tr_slug = $mo->translate( $slug ); - $slugs[ 'tribe_' . $slug ]['translations'][ $language->slug ] = empty( $tr_slug ) ? $slug : $tr_slug; - } - - return $slugs; - } - - /** - * Performs the sanitization ( before saving in DB ) of slugs translations - * The Events Calendar does not accept accents, but let's accept slashes for the event category slug - * - * @since 1.9 - * - * @param string $translation Translation to sanitize. - * @param string $name Unique name for the string. - * @return string - */ - public function sanitize_string_translation( $translation, $name ) { - if ( 'slug_archive_tribe_events' === $name || 0 === strpos( $name, 'slug_tribe_' ) ) { - $slugs = explode( '/', $translation ); - $slugs = array_map( 'sanitize_title', $slugs ); - return implode( '/', $slugs ); - } - return $translation; - } - - /** - * Add translated slugs to specific TEC rewrite rules. - * - * @since 2.2 - * - * @param array $bases Array of arrays of rewrite base slugs. - * @return array - */ - public function rewrite_slugs( $bases ) { - $translatable_slugs = $this->get_translatable_slugs(); - - if ( empty( $translatable_slugs ) ) { - return $bases; - } - - foreach ( $bases as $type => $base ) { - $default_slug = reset( $base ); - - foreach ( $translatable_slugs as $slugs ) { - if ( $slugs['slug'] === $default_slug ) { - $bases[ $type ] = array_unique( array_merge( array( $default_slug ), $slugs['translations'] ) ); - break; - } - } - } - - return $bases; - } - - /** - * Translated strings must be sanitized the same way The Events Calendar does before they are saved. - * All are of validation_type 'html'. - * - * @since 2.2 - * - * @param string $translation Translated string. - * @param string $name String name. - * @param string $context String context. - * @return string Sanitized translation. - */ - public function sanitize_strings( $translation, $name, $context ) { - if ( 'The Events Calendar' === $context ) { - $translation = balanceTags( $translation ); - } - - return $translation; - } - - /** - * Filters TEC's base slugs to unescape dashes. - * - * If `$method` is 'regex', `Tribe__Events__Rewrite->get_bases()` will use `preg_quote()` to get - * its slugs ready as regex patterns. However, `-` characters are valid in this context and - * should not be escaped (reminder: they come from PLL's string translations). - * - * @since 3.1 - * @see $this->rewrite_slugs() - * - * @param string[] $bases An array of rewrite bases that have been generated. - * @param string $method The method that's being used to generate the bases; defaults to `regex`. - * @return string[] - */ - public function fix_escaped_dashes_in_slugs( $bases, $method ) { - if ( 'regex' !== $method ) { - return $bases; - } - - return array_map( - function ( $base ) { - return str_replace( '\\-', '-', $base ); - }, - $bases - ); - } - - /** - * Filters the string to be used as the taxonomy slug. - * This replaces TEC's translated category slug by the untranslated one, as it is returned by the public method - * `Tribe__Events__Main->get_category_slug()`. - * - * @since 3.1 - * - * @return string - */ - public function get_category_slug() { - return 'category'; - } - - /** - * Filters the string to be used as the tag slug. - * This replaces TEC's translated tag slug by the untranslated one, as it is returned by a public method - * `Tribe__Events__Main->get_tag_slug()`. - * - * @since 3.1 - * - * @return string - */ - public function get_tag_slug() { - return 'tag'; - } - - /** - * Adds the lang to TEC's REST URL. - * This provides a way to identify in which language PLL should work in the REST request. - * - * @since 3.1 - * - * @param string $url The View endpoint URL, either a REST API URL or a admin-ajax.php fallback URL if REST API - * is not available. - * @return string - */ - public function add_missing_lang_to_rest_url( $url ) { - $curlang = $this->get_curlang(); - - if ( empty( $curlang ) ) { - return $url; - } - - $lang = $this->get_lang_from_url_query_arg( $url ); - - if ( ! empty( $lang ) ) { - return $url; - } - - return add_query_arg( array( 'lang' => $curlang->slug ), $url ); - } - - /** - * Filters the locale when TEC is performing a REST request. - * - * @since 3.1 - * - * @param string $locale The locale ID. - * @return string - */ - public function filter_locale_for_rest( $locale ) { - if ( ! $this->is_tec_rest_request() ) { - return $locale; - } - - $curlang = $this->get_curlang(); - - if ( empty( $curlang->locale ) ) { - return $locale; - } - - return $curlang->locale; - } - - /** - * Filters the query arguments that should be applied to the View links to add the missing language. - * The added language is the global current language. - * - * @since 3.1 - * - * @param mixed[] $url_args The current URL query arguments, created from a filtered version of the current - * request context. - * @return mixed[] - */ - public function add_language_to_publicly_visible_views_query_args( $url_args ) { - $curlang = $this->get_curlang(); - - if ( ! empty( $curlang ) ) { - $url_args['lang'] = $curlang->slug; - } - return $url_args; - } - - /** - * Fixes the "upcoming events" widget link. - * - * @since 3.1 - * - * @param mixed[] $template_vars An associative array of template variables. Variables will be extracted in the - * template hence the key will be the name of the variable available in the template. - * @return mixed[] - */ - public function translate_widget_view_more_link( $template_vars ) { - if ( ! isset( $template_vars['view_more_link'] ) ) { - return $template_vars; - } - - $curlang = $this->get_curlang(); - - if ( empty( $curlang ) || empty( $this->slugs_model ) ) { - return $template_vars; - } - - $template_vars['view_more_link'] = home_url( '/' . tribe_get_option( 'eventsSlug', 'events' ) ); - $template_vars['view_more_link'] = $this->polylang->links_model->add_language_to_link( $template_vars['view_more_link'], $curlang ); - $template_vars['view_more_link'] = $this->slugs_model->translate_slug( $template_vars['view_more_link'], $curlang, 'archive_' . TEC::POSTTYPE ); - $template_vars['view_more_link'] = user_trailingslashit( $template_vars['view_more_link'] ); - - return $template_vars; - } - - /** - * Filters the array of query variables parsed by TEC to force the use of the right language. - * For example, `example.com/events?lang=de` would return the default language instead of using the provided query - * arg because TEC tries to use the WP rewrite rules to match the query path, and `events` => default language. - * However, this is not needed for `example.com/de/events-de/` and `example.com/?lang=de` because the right - * language will be set in these cases. - * - * @since 3.1 - * @see Tribe__Rewrite->parse_request() - * @see PLL_TEC->add_language_to_publicly_visible_views_query_args() - * - * @param string[] $query_vars The parsed query vars array. - * @param string[] $extra_query_vars An associative array of extra query vars that will be processed before - * the WordPress defined ones. - * @param string $url The URL to parse. - * @return string[] - */ - public function force_language_on_tec_parse_query_vars( $query_vars, $extra_query_vars, $url ) { - // Find the lang in the URL... - $lang = $this->get_lang_from_url_query_arg( $url ); - - if ( ! empty( $lang ) ) { - // ... and add it to the query vars. - $query_vars['lang'] = $lang->slug; - } - - return $query_vars; - } - - /** - * Adds the lang to TEC's query arguments that will be used to build a View URL. - * This insures that a lang arg is available when building the view's URL. - * - * @since 3.1 - * - * @param mixed[] $query_args An array of query args that will be used to build the URL for the View. - * @return mixed[] - */ - public function add_missing_lang_to_query_arg( $query_args ) { - if ( isset( $query_args['lang'] ) ) { - return $query_args; - } - - $curlang = $this->get_curlang(); - - $query_args['lang'] = ! empty( $curlang ) ? $curlang->slug : $this->polylang->options['default_lang']; - - return $query_args; - } - - /** - * Adds the lang to the URL passed to `Tribe__Rewrite->get_canonical_url()`. - * This insures that a lang arg is available when building a URL. - * - * @since 3.1 - * - * @param string|null $canonical_url The canonical URL, defaults to `null`; returning a non `null` value will - * make the logic bail and return the value. - * @param string $url The input URL to resolve to a canonical one. - * @return string|null - */ - public function add_missing_lang_to_non_canonical_url( $canonical_url, $url ) { - $lang = $this->get_lang_from_url_query_arg( $url ); - - if ( ! empty( $lang ) ) { - // All good. - return $canonical_url; - } - - $curlang = $this->get_curlang(); - - if ( empty( $curlang ) ) { - // We're screwed. - return $canonical_url; - } - - // Re-inject the URL with the current lang. `$this->get_lang_from_url_query_arg()` will prevent an infinite loop. - return Tribe__Rewrite::instance()->get_canonical_url( add_query_arg( 'lang', $curlang->slug, $url ) ); - } - - /** - * Removes the `name` arg from the URL passed to `Tribe__Rewrite->get_canonical_url()` when there is already a - * `post_type` arg: this seems to mess up the process. - * - * @since 3.1 - * - * @param string|null $canonical_url The canonical URL, defaults to `null`; returning a non `null` value will - * make the logic bail and return the value. - * @param string $url The input URL to resolve to a canonical one. - * @return string|null - */ - public function remove_name_arg_from_non_canonical_url( $canonical_url, $url ) { - $url_query = wp_parse_url( $url, PHP_URL_QUERY ); - - if ( empty( $url_query ) ) { - return $canonical_url; - } - - $parsed_query = array(); - wp_parse_str( $url_query, $parsed_query ); - - if ( empty( $parsed_query ) ) { - return $canonical_url; - } - - if ( empty( $parsed_query['post_type'] ) || empty( $parsed_query['name'] ) || ! empty( $parsed_query['ical'] ) ) { - return $canonical_url; - } - - if ( TEC::POSTTYPE !== $parsed_query['post_type'] ) { - return $canonical_url; - } - - if ( ! empty( $parsed_query['lang'] ) && $parsed_query['lang'] === $parsed_query['name'] ) { - // ¯\(°_o)/¯. - $remove = true; - } elseif ( tribe_get_option( 'eventsSlug' ) === $parsed_query['name'] ) { - $remove = true; - } - - if ( empty( $remove ) ) { - return $canonical_url; - } - - // Re-inject the URL. Tests against `$parsed_query['name']` will prevent an infinite loop. - return Tribe__Rewrite::instance()->get_canonical_url( remove_query_arg( 'name', $url ) ); - } - - /** - * Filters TEC's canonical URL to fix the language slug in it. - * Because of TEC's method to build URLs, using the rewrite rules array, the language slug is not replaced and is - * outputted like the rewrite rule pattern: `/(en|fr|de)/`. This filter replaces the pattern by the language - * contained in the original URL. If not found in the original URL, falls back to the current language or the default - * one. - * - * @since 3.1 - * - * @param string $resolved The resolved, canonical URL. - * @param string $url The original URL to resolve. - * @return string - */ - public function fix_language_in_canonical_url( $resolved, $url ) { - $options = $this->polylang->options; - - // Remove the default language if it must be hidden in the URLs. - $languages = $this->polylang->model->get_languages_list( - array( - 'hide_default' => $options['hide_default'], - 'fields' => 'slug', - ) - ); - - if ( empty( $languages ) ) { - return $resolved; - } - - /** - * What we want to modify in the URL. - * Ex: `/(en|fr|de)`, `/language/(fr|de)`. - */ - $language_path = $options['rewrite'] ? '' : '/language'; - $to_replace = $language_path . '/(' . implode( '|', $languages ) . ')/'; - - if ( strpos( $resolved, $to_replace ) === false ) { - return $resolved; - } - - // Find the lang in the URL (or fallback). - $lang = $this->get_lang_from_url_query_arg( $url ); - - if ( empty( $lang ) ) { - // We need a lang, whichever it is. - $curlang = $this->get_curlang(); - $lang = ! empty( $curlang ) ? $curlang->slug : $options['default_lang']; - } else { - $lang = $lang->slug; - } - - // Make the final replacement. - if ( $options['hide_default'] && $options['default_lang'] === $lang ) { - // The default language is hidden in the URL. - $replacement = '/'; - } else { - $replacement = "{$language_path}/{$lang}/"; - } - - return str_replace( $to_replace, $replacement, $resolved ); - } - - /** - * Filters TEC's canonical URL to translate all slugs in it. - * This is possible because a `lang` arg is available in the "ugly" URL. - * - * @since 3.1 - * @see Tribe__Events__Rewrite->get_dynamic_matchers() - * - * @param string $resolved The resolved, canonical URL. - * @param string $url The original URL to resolve. - * @return string - */ - public function translate_canonical_url( $resolved, $url ) { - if ( empty( $this->slugs_model ) ) { - return $resolved; - } - - // Find the lang in the URL (or fallback). - $lang = $this->get_lang_from_url_query_arg_or_fallback( $url ); - - if ( empty( $lang ) ) { - // What? - return $resolved; - } - - // Make sure the language is well formatted. - $resolved = remove_query_arg( 'lang', $resolved ); - $resolved = $this->polylang->links_model->add_language_to_link( $resolved, $lang ); - - foreach ( $this->get_translatable_slugs() as $slug_id => $translations ) { - $resolved = $this->slugs_model->switch_translated_slug( $resolved, $lang, $slug_id ); - } - - return $resolved; - } - - /** - * Tells if a request is a TEC REST API request. - * TEC does a good job for their REST URL by providing a `admin-ajax.php` fallback in case the REST API is not - * available. Unfortunately, this choice is late in the process so we have to test the given URL against the 2 - * possibilities. - * - * @since 3.1 - * @see Tribe\Events\Views\V2\Rest_Endpoint->get_url() - * - * @param string $requested_url The requested URL. Falls back to the current URL. - * @return bool|null Whether the request is a TEC REST API request. Null if not ready to answer yet. - */ - protected function is_tec_rest_request( $requested_url = null ) { - if ( ! isset( $this->is_tec_rest_request['views_v2_is_enabled'] ) ) { - if ( ! function_exists( 'tribe_events_views_v2_is_enabled' ) ) { - return null; - } - - $this->is_tec_rest_request['views_v2_is_enabled'] = tribe_events_views_v2_is_enabled(); - } - - if ( ! $this->is_tec_rest_request['views_v2_is_enabled'] ) { - // If the views V2 are not enabled, no REST requests. - return false; - } - - if ( empty( $requested_url ) || ! is_string( $requested_url ) ) { - // Fall back to the current URL. - if ( ! isset( $this->is_tec_rest_request['pll_requested_url'] ) ) { - $this->is_tec_rest_request['pll_requested_url'] = (string) set_url_scheme( pll_get_requested_url() ); - } - - $requested_url = $this->is_tec_rest_request['pll_requested_url']; - } else { - $requested_url = (string) set_url_scheme( $requested_url ); - } - - if ( isset( $this->is_tec_rest_request[ 'is:' . $requested_url ] ) ) { - return $this->is_tec_rest_request[ 'is:' . $requested_url ]; - } - - if ( false === strpos( $requested_url, '/admin-ajax.php' ) ) { - // Test against the REST URL. - if ( ! isset( $this->is_tec_rest_request['tec_rest_url_pattern'] ) ) { - $url = $this->get_tec_rest_url( true ); - - if ( empty( $url ) ) { - // `$wp_rewrite` is probably not set yet. - return null; - } - - $this->is_tec_rest_request['tec_rest_url_pattern'] = preg_replace( '@[#?].*$@', '', $url ); - $this->is_tec_rest_request['tec_rest_url_pattern'] = sprintf( '@^%s[/?#]@i', preg_quote( $this->is_tec_rest_request['tec_rest_url_pattern'], '@' ) ); - } - - $this->is_tec_rest_request[ 'is:' . $requested_url ] = (bool) preg_match( $this->is_tec_rest_request['tec_rest_url_pattern'], $requested_url ); - - return $this->is_tec_rest_request[ 'is:' . $requested_url ]; - } - - // Test against the admin ajax URL. - if ( ! isset( $this->is_tec_rest_request['tec_ajax_url_action'] ) ) { - $this->is_tec_rest_request['tec_ajax_url_action'] = $this->get_tec_rest_url( false ); - $this->is_tec_rest_request['tec_ajax_url_action'] = $this->get_query_arg_from_url( $this->is_tec_rest_request['tec_ajax_url_action'], 'action' ); - } - - if ( empty( $this->is_tec_rest_request['tec_ajax_url_action'] ) ) { - // Uh? - $this->is_tec_rest_request[ 'is:' . $requested_url ] = false; - return false; - } - - $requested_action = $this->get_query_arg_from_url( $requested_url, 'action' ); - - $this->is_tec_rest_request[ 'is:' . $requested_url ] = $requested_action === $this->is_tec_rest_request['tec_ajax_url_action']; - - return $this->is_tec_rest_request[ 'is:' . $requested_url ]; - } - - /** - * Returns the current language object. - * Can return `null` if not defined yet. - * - * @since 3.1 - * - * @return PLL_Language|null - */ - protected function get_curlang() { - if ( ! empty( $this->curlang ) ) { - return $this->curlang; - } - - if ( ! empty( $_REQUEST['lang'] ) && is_string( $_REQUEST['lang'] ) && Polylang::is_rest_request() ) { // phpcs:ignore WordPress.Security.NonceVerification - // REST request. - $curlang = $this->polylang->model->get_language( sanitize_key( $_REQUEST['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - - if ( ! empty( $curlang ) ) { - $this->curlang = $curlang; - $this->polylang->curlang = $this->curlang; - return $this->curlang; - } - } - - if ( ! empty( $this->polylang->curlang ) ) { - // Global context. - $this->curlang = &$this->polylang->curlang; - return $this->curlang; - } - - if ( empty( $this->polylang->options['force_lang'] ) && $this->polylang instanceof PLL_Frontend && ! did_action( 'pll_language_defined' ) ) { - // Lang defined by content: too soon. - return null; - } - - $curlang = $this->polylang->model->get_default_language(); - - if ( empty( $curlang ) ) { - // We're screwed. - return null; - } - - // Default lang. - $this->curlang = $curlang; - return $this->curlang; - } - - /** - * Returns the list of slugs that need to be reset in TEC, except the deprecated ones. - * - * @since 3.1 - * @see PLL_TEC->reset_slugs() - * - * @return string[] Array keys match `Tribe__Events__Main`'s properties name. - */ - protected function get_slugs_to_reset() { - return array( - 'category_slug' => 'category', - 'tag_slug' => 'tag', - 'monthSlug' => 'month', - 'listSlug' => 'list', - 'upcomingSlug' => 'upcoming', - 'pastSlug' => 'past', - 'daySlug' => 'day', - 'todaySlug' => 'today', - 'featured_slug' => 'featured', - 'all_slug' => 'all', - ); - } - - /** - * Returns the list of IDs of translatable slugs dedicated to TEC. - * Ex: `tribe_venue`, `archive_tribe_events`, `paged`, `tribe_today`. - * - * @since 3.1 - * - * @return string[] - */ - protected function get_translatable_slug_ids() { - if ( ! empty( $this->translatable_slug_ids ) ) { - return $this->translatable_slug_ids; - } - - $slug_ids = array( - Venue::POSTTYPE, - Organizer::POSTTYPE, - TEC::POSTTYPE, - 'archive_' . TEC::POSTTYPE, - 'post_tag', - 'paged', - ); - - foreach ( $this->get_slugs_to_reset() as $slug ) { - $slug_ids[] = 'tribe_' . $slug; - } - - $this->translatable_slug_ids = array_combine( $slug_ids, $slug_ids ); - - return $this->translatable_slug_ids; - } - - /** - * Returns the list of translatable slugs dedicated to TEC. - * - * @since 3.1 - * - * @return mixed[] - */ - protected function get_translatable_slugs() { - if ( empty( $this->slugs_model ) ) { - return array(); - } - - return array_intersect_key( $this->slugs_model->get_translatable_slugs(), $this->get_translatable_slug_ids() ); - } - - /** - * Returns the value of the `lang` query arg from the given URL. - * - * @since 3.1 - * - * @param string $url The URL to retrieve the arg from. - * @return PLL_Language|null The lang object. Null if not found or invalid. - */ - protected function get_lang_from_url_query_arg( $url ) { - $lang = $this->get_query_arg_from_url( $url, 'lang' ); - - if ( empty( $lang ) || ! is_string( $lang ) ) { - return null; - } - - $lang = $this->polylang->model->get_language( sanitize_key( $lang ) ); - - if ( empty( $lang ) ) { - return null; - } - - return $lang; - } - - /** - * Returns the value of the `lang` query arg from the given URL. - * - * @since 3.1 - * - * @param string $url The URL to retrieve the arg from. - * @return PLL_Language|null The lang object. Null if not found or invalid. - */ - protected function get_lang_from_url_query_arg_or_fallback( $url ) { - $lang = $this->get_lang_from_url_query_arg( $url ); - - if ( ! empty( $lang ) ) { - return $lang; - } - - $lang = $this->get_curlang(); - - if ( ! empty( $lang ) ) { - return $lang; - } - - // Uh? - $lang = $this->polylang->model->get_default_language(); - - if ( ! empty( $lang ) ) { - return $lang; - } - - // What? - return null; - } - - /** - * Returns the value of a query arg from the given URL. - * - * @since 3.1 - * - * @param string $url The URL to retrieve the arg from. - * @param string $query_arg_name The name of the query arg to retrieve. - * @return string|null The raw value of the query arg. Null if not found. - */ - protected function get_query_arg_from_url( $url, $query_arg_name ) { - if ( empty( $url ) || ! is_string( $url ) ) { - return null; - } - - if ( empty( $query_arg_name ) || ! is_string( $query_arg_name ) ) { - return null; - } - - $url_query = wp_parse_url( $url, PHP_URL_QUERY ); - - if ( empty( $url_query ) ) { - return null; - } - - $parsed = array(); - wp_parse_str( $url_query, $parsed ); - - if ( empty( $parsed[ $query_arg_name ] ) || ! is_string( $parsed[ $query_arg_name ] ) ) { - return null; - } - - return sanitize_key( $parsed[ $query_arg_name ] ); - } - - /** - * Returns TEC's REST URL. - * - * @since 3.1 - * - * @param bool $enable_rest True to get the REST URL. False to get the admin ajax URL. - * @return string|null The REST URL. Null if too soon to be determined: this may happen when requesting the - * the real REST URL (`$enable_rest` is true) but `$wp_rewrite` is not ready. - */ - protected function get_tec_rest_url( $enable_rest ) { - global $wp_rewrite; - - if ( $enable_rest ) { - // In this case, `Rest_Endpoint->get_url()` will use `get_rest_url()`. - if ( ( is_multisite() && get_blog_option( 0, 'permalink_structure' ) ) || get_option( 'permalink_structure' ) ) { // See the same test done in `get_rest_url()`. - // We test `$wp_rewrite` to prevent `get_rest_url()` to explode. - if ( ! $wp_rewrite instanceof WP_Rewrite ) { - return null; - } - } - } - - // Force `Rest_Endpoint->is_available()`'s behavior with this filter callback. - $priority = 100000; - $callback = function () use ( $enable_rest ) { - return (bool) $enable_rest; - }; - - add_filter( 'tribe_events_views_v2_rest_endpoint_available', $callback, $priority ); - $url = ( new Rest_Endpoint() )->get_url(); - remove_filter( 'tribe_events_views_v2_rest_endpoint_available', $callback, $priority ); - - return $url; - } +class PLL_TEC +{ + /** + * Current language (used to filter the content). + * + * @var PLL_Language|null + */ + protected $curlang; + + /** + * The main Polylang object. + * + * @var PLL_Settings|PLL_Admin|PLL_REST_Request|PLL_Frontend + */ + protected $polylang; + + /** + * Links Model for translating slugs. + * + * @var PLL_Translate_Slugs_Model|null + */ + protected $slugs_model; + + /** + * @var PLL_Admin_Links|null + */ + protected $links; + + /** + * Cache for the method `is_tec_rest_request()`. + * + * @var mixed[] + */ + protected $is_tec_rest_request = []; + + /** + * The list of IDs of translatable slugs dedicated to TEC. + * + * @var string[] + */ + protected $translatable_slug_ids = []; + + /** + * The list of post metas to synchronize. + * + * @var string[] + */ + protected static $metas; + + /** + * Initializes filters and actions. + * + * @since 2.2 + * + * @param PLL_Settings|PLL_Admin|PLL_REST_Request|PLL_Frontend $polylang The main Polylang object. + * + * @return void + */ + public function init($polylang) + { + if (!$polylang->model->has_languages()) { + return; + } + + $this->polylang = $polylang; + $this->curlang = null; + $this->slugs_model = !empty($polylang->translate_slugs) && !empty($polylang->translate_slugs->slugs_model) ? $polylang->translate_slugs->slugs_model : null; + $this->is_tec_rest_request = []; + $this->translatable_slug_ids = []; + + if ($polylang->links instanceof PLL_Admin_Links) { + $this->links = $polylang->links; + } + + add_filter('pll_get_taxonomies', [$this, 'translate_taxonomies'], 10, 2); + add_filter('pll_get_post_types', [$this, 'translate_types'], 10, 2); + + add_action('save_post_'.Venue::POSTTYPE, [$this, 'set_language'], 10, 3); + add_action('save_post_'.Organizer::POSTTYPE, [$this, 'set_language'], 10, 3); + + $tec = TEC::instance(); + + if (empty($polylang->options['force_lang'])) { + add_action('pll_language_defined', [$this, 'fix_date_translations']); + } + + self::$metas = array_merge($tec->metaTags, $tec->venueTags, $tec->organizerTags, ['_VenueShowMap', '_VenueShowMapLink']); + + if (!empty($this->links) && !empty($GLOBALS['post'])) { + $data = $this->links->get_data_from_new_post_translation_request($GLOBALS['post']->post_type); + + if (!empty($data)) { + // Default values for events. + foreach (self::$metas as $meta) { + $filter = str_replace(['_Event', '_Organizer', '_Venue'], ['', 'Organizer', 'Venue'], $meta); + add_filter('tribe_get_meta_default_value_'.$filter, [$this, 'copy_event_meta'], 10, 4); // Since TEC 4.0.7. + } + + add_filter('tribe_display_event_linked_post_dropdown_id', [$this, 'translate_linked_post']); + } + } + + add_action('pre_get_posts', [$this, 'pre_get_posts'], 60); // After `Tribe__Events__Query->pre_get_posts()`. + + add_filter('pll_copy_post_metas', [$this, 'copy_post_metas']); + add_filter('pll_translate_post_meta', [$this, 'translate_meta'], 10, 3); + + // Translate links with translated slugs + add_action('init', [$this, 'reset_slugs'], 11); // Just after `Tribe__Events__Main->init()`. + add_filter('register_taxonomy_args', [$this, 'register_taxonomy_args'], 10, 2); + add_filter('tribe_events_get_link', [$this, 'get_link']); + add_filter('pll_get_archive_url', [$this, 'pll_get_archive_url'], 10, 2); + add_filter('pll_term_link', [$this, 'filter_tec_term_link'], 5, 3); // Before `PLL_Translate_Slugs->pll_term_link()`. + add_filter('pll_translated_slugs', [$this, 'pll_translated_slugs'], 10, 3); + add_filter('pll_sanitize_string_translation', [$this, 'sanitize_string_translation'], 10, 2); + add_filter('tribe_events_rewrite_i18n_slugs_raw', [$this, 'rewrite_slugs']); + + // Options to translate. + $keys = [ + 'dateWithYearFormat' => 1, + 'dateWithoutYearFormat' => 1, + 'monthAndYearFormat' => 1, + 'dateTimeSeparator' => 1, + 'timeRangeSeparator' => 1, + 'tribeEventsBeforeHTML' => 1, + 'tribeEventsAfterHTML' => 1, + ]; + + $args = [ + 'context' => 'The Events Calendar', + 'sanitize_callback' => [$this, 'sanitize_strings'], + ]; + + new PLL_Translate_Option('tribe_events_calendar_options', $keys, $args); + + // TEC views V2. + add_filter('tribe_events_rewrite_i18n_domains', '__return_empty_array', 10000); // No i18n domains, no translations to deal with. + add_filter('tribe_events_rewrite_i18n_slugs', [$this, 'fix_escaped_dashes_in_slugs'], 10, 2); + + add_filter('tribe_events_category_slug', [$this, 'get_category_slug']); + add_filter('tribe_events_tag_slug', [$this, 'get_tag_slug']); + + add_filter('tribe_events_views_v2_endpoint_url', [$this, 'add_missing_lang_to_rest_url']); + add_filter('locale', [$this, 'filter_locale_for_rest'], 5); + + add_filter('tribe_events_views_v2_publicly_visible_views_query_args', [$this, 'add_language_to_publicly_visible_views_query_args'], 5); + add_filter('tribe_events_views_v2_view_template_vars', [$this, 'translate_widget_view_more_link'], 5); + add_filter('tribe_rewrite_parse_query_vars', [$this, 'force_language_on_tec_parse_query_vars'], 10, 3); + add_filter('tribe_events_views_v2_url_query_args', [$this, 'add_missing_lang_to_query_arg']); + add_filter('tribe_rewrite_pre_canonical_url', [$this, 'add_missing_lang_to_non_canonical_url'], 10, 2); + add_filter('tribe_rewrite_pre_canonical_url', [$this, 'remove_name_arg_from_non_canonical_url'], 10, 2); + + add_filter('tribe_rewrite_canonical_url', [$this, 'fix_language_in_canonical_url'], 5, 2); + add_filter('tribe_rewrite_canonical_url', [$this, 'translate_canonical_url'], 5, 2); + } + + /** + * Language and translation management for taxonomies. + * + * @since 2.2 + * + * @param string[] $taxonomies List of taxonomy names for which Polylang manages language and translations. + * @param bool $hide True when displaying the list in Polylang settings. + * + * @return string[] List of taxonomy names for which Polylang manages language and translations. + */ + public function translate_taxonomies($taxonomies, $hide) + { + // Hide from Polylang settings + return $hide ? array_diff($taxonomies, [TEC::TAXONOMY]) : array_merge($taxonomies, [TEC::TAXONOMY]); + } + + /** + * Language and translation management for custom post types. + * + * @since 2.2 + * + * @param string[] $types 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[] List of post type names for which Polylang manages language and translations. + */ + public function translate_types($types, $hide) + { + $tec_types = [TEC::POSTTYPE, TEC::VENUE_POST_TYPE, TEC::ORGANIZER_POST_TYPE]; + + return $hide ? array_diff($types, $tec_types) : array_merge($types, $tec_types); + } + + /** + * Save the language of Venues and Organizers. + * Needed when they are created from the Event form. + * + * @since 2.2 + * + * @param int $post_id Post id. + * @param WP_Post $post Post object. + * @param bool $update Whether it is an update or not. + * + * @return void + */ + public function set_language($post_id, $post, $update) + { + if ($update || !isset($_POST['post_lang_choice'])) { + return; + } + + $post_type_object = get_post_type_object($post->post_type); + + if (!$post_type_object || !current_user_can($post_type_object->cap->create_posts)) { + return; + } + + check_admin_referer('pll_language', '_pll_nonce'); + $lang = $this->polylang->model->get_language(sanitize_key($_POST['post_lang_choice'])); + + if (!$lang) { + return; + } + + $this->polylang->model->post->set_language($post_id, $lang); + } + + /** + * Once the language is set from content, this resets all the date-related translations in TEC to the current lang. + * In `Tribe__Date_Utils`, TEC stores these translations in static private properties before the language is set in + * PLL. Then these translations are use later, AFTER the language is set in PLL, leading to views exploding due to + * array keys not being set. + * The filter 'tribe_events_get_days_of_week' can't be used because it doesn't include the function's arg `$format`. + * + * @since 3.1 + * @see tribe_events_get_days_of_week() + * + * @return void + */ + public function fix_date_translations() + { + $properties = [ + 'localized_months_full', + 'localized_months_short', + 'localized_weekdays', + 'localized_months', + ]; + + foreach ($properties as $property) { + $property = new ReflectionProperty(Tribe__Date_Utils::class, $property); + $property->setAccessible(true); + $property->setValue([]); + } + + TEC::instance()->setup_l10n_strings(); + } + + /** + * Populates default event metas for a newly created event translation. + * + * @since 2.2 + * + * @param mixed $value Meta value. + * @param int $id Post id. + * @param string $meta Meta key. + * @param bool $single Whether to return a single value. + * + * @return mixed + */ + public function copy_event_meta($value, $id, $meta, $single) + { + if (!empty($_GET['from_post'])) { // phpcs:ignore WordPress.Security.NonceVerification + $value = get_post_meta((int) $_GET['from_post'], $meta, $single); // phpcs:ignore WordPress.Security.NonceVerification + } + + return $value; + } + + /** + * Populates default values for venues and organizers for a newly created event translation. + * + * @since 2.2 + * + * @param array $posts Array of linked posts. + * + * @return array + */ + public function translate_linked_post($posts) + { + if (empty($posts) || empty($GLOBALS['post']) || empty($this->links)) { + return $posts; + } + + $data = $this->links->get_data_from_new_post_translation_request($GLOBALS['post']->post_type); + + if (empty($data)) { + return $posts; + } + + $lang = $data['new_lang']->slug; + $post_metas = !empty($this->polylang->sync) && !empty($this->polylang->sync->post_metas) ? $this->polylang->sync->post_metas : false; + + foreach ($posts as $key => $post_id) { + $tr_id = pll_get_post($post_id, $lang); + + if (!empty($tr_id)) { + $posts[$key] = $tr_id; + continue; + } + + // If the translated venue or organizer doesn't exist, create it. + $post = get_post($post_id, ARRAY_A); // Output as an array for `wp_insert_post()`. + + if (empty($post)) { + // `null` value. + continue; + } + + unset($post['ID']); + + $tr_id = wp_insert_post(wp_slash($post)); + + if (!is_int($tr_id)) { + // `WP_Error` value. + continue; + } + + $translations = pll_get_post_translations($post_id); + $translations[$lang] = $tr_id; + + pll_set_post_language($tr_id, $lang); + pll_save_post_translations($translations); + + if (!empty($post_metas)) { + $post_metas->copy($post_id, $tr_id, $lang); + } + + $posts[$key] = $tr_id; + } + + return $posts; + } + + /** + * Removes date filters when searching for untranslated events in the metabox autocomplete field. + * + * @since 2.2.8 + * + * @return void + */ + public function pre_get_posts() + { + if (wp_doing_ajax() && isset($_GET['action']) && 'pll_posts_not_translated' === $_GET['action']) { // phpcs:ignore WordPress.Security.NonceVerification + // See Tribe__Events__Query::pre_get_posts() when should_remove_date_filters() returns true + remove_filter('posts_where', ['Tribe__Events__Query', 'posts_where']); + remove_filter('posts_fields', ['Tribe__Events__Query', 'posts_fields']); + remove_filter('posts_orderby', ['Tribe__Events__Query', 'posts_orderby']); + } + } + + /** + * Synchronize event metas. + * + * @since 2.2 + * + * @param array $metas Custom fields to copy or synchronize. + * + * @return array + */ + public function copy_post_metas($metas) + { + return array_merge($metas, self::$metas); + } + + /** + * Translate venues and organizers before they are copied or synchronized. + * + * @since 2.3 + * + * @param mixed $value Meta value. + * @param string $key Meta key. + * @param string $lang Language of target. + * + * @return mixed + */ + public function translate_meta($value, $key, $lang) + { + if (('_EventVenueID' === $key || '_EventOrganizerID' === $key) && $tr_value = pll_get_post($value, $lang)) { + $value = $tr_value; + } + + return $value; + } + + /** + * Resets all TEC translated slugs to an English value as the TEC slug translation system + * does not work in a multilingual context (TEC 4.4.5 + WP 4.7.3). + * + * @since 2.2 + * + * @return void + */ + public function reset_slugs() + { + $tec = TEC::instance(); + + foreach ($this->get_slugs_to_reset() as $key => $slug) { + $tec->$key = $slug; + } + + // Those are deprecated since TEC 4.0 and should not appear in the list of translatable strings anymore. + $tec->taxRewriteSlug = $tec->rewriteSlug.'/category'; + $tec->tagRewriteSlug = $tec->rewriteSlug.'/tag'; + } + + /** + * Resets the category base rewrite slug in taxonomy. + * + * @since 2.2 + * + * @param array $args Array of arguments for registering a taxonomy. + * @param string $taxonomy Taxonomy key. + * + * @return array + */ + public function register_taxonomy_args($args, $taxonomy) + { + if (TEC::TAXONOMY === $taxonomy && is_array($args['rewrite'])) { + $args['rewrite']['slug'] = TEC::instance()->rewriteSlug.'/category'; + } + + return $args; + } + + /** + * Filters the links to add the language code. + * + * @since 2.2 + * + * @param string $link Link generated by The Events Calendar. + * + * @return string + */ + public function get_link($link) + { + $curlang = $this->get_curlang(); + + if (empty($curlang) || empty($this->slugs_model)) { + return $link; + } + + $link = $this->polylang->links_model->add_language_to_link($link, $curlang); + + foreach ($this->get_translatable_slug_ids() as $slug_id) { + $link = $this->slugs_model->translate_slug($link, $curlang, $slug_id); + } + + return $link; + } + + /** + * Translates slugs in the language switcher. + * + * @since 2.2 + * + * @param string $url Url in the language switcher. + * @param PLL_Language $language Language object. + * + * @return string Modified url. + */ + public function pll_get_archive_url($url, $language) + { + if (empty($this->slugs_model) || !is_post_type_archive(TEC::POSTTYPE)) { + return $url; + } + + foreach ($this->get_translatable_slug_ids() as $slug_id) { + $url = $this->slugs_model->switch_translated_slug($url, $language, $slug_id); + } + + return $url; + } + + /** + * Modifies term links for the taxonomies used by TEC. + * In `PLL_TEC->reset_slugs()` we don't include the full taxonomy slug (`Tribe__Events__Main->taxRewriteSlug`) in + * our translated strings, as it is deprecated in TEC 4.0 and also a composite of + * `{post type archive slug}/{tax base slug}`. This explains why this method is needed. + * + * @since 3.1 + * @see PLL_TEC->reset_slugs() + * @see PLL_TEC->pll_translated_slugs() + * + * @param string $url The term link. + * @param PLL_Language $lang The term language. + * @param WP_Term $term The term object. + * + * @return string + */ + public function filter_tec_term_link($url, $lang, $term) + { + if (empty($this->slugs_model) || TEC::TAXONOMY !== $term->taxonomy) { + return $url; + } + + $url = $this->slugs_model->translate_slug($url, $lang, 'archive_'.TEC::POSTTYPE); + + return $this->slugs_model->translate_slug($url, $lang, 'tribe_category'); + } + + /** + * Fixes the events slug in translatable slugs. + * Translates other TEC slugs. + * + * @since 2.2 + * + * @param array $slugs Translated slugs. + * @param PLL_Language $language Language object. + * @param PLL_MO $mo Strings translations object. + * + * @return array + */ + public function pll_translated_slugs($slugs, $language, &$mo) + { + /** + * In `PLL_TEC->reset_slugs()` we don't include the full taxonomy slug (`Tribe__Events__Main->taxRewriteSlug`) in + * our translated strings, as it is deprecated in TEC 4.0 and also a composite of + * `{post type archive slug}/{tax base slug}`. This is why we unset `$slugs[ TEC::TAXONOMY ]` here. + */ + unset($slugs['archive_'.TEC::POSTTYPE]['hide'], $slugs[TEC::TAXONOMY]); + + $slugs['archive_'.TEC::POSTTYPE]['slug'] = $slug = TEC::instance()->getRewriteSlug(); + $tr_slug = $mo->translate($slug); + $slugs['archive_'.TEC::POSTTYPE]['translations'][$language->slug] = empty($tr_slug) ? $slug : $tr_slug; + + foreach ($this->get_slugs_to_reset() as $slug) { + $slugs['tribe_'.$slug]['slug'] = $slug; + $tr_slug = $mo->translate($slug); + $slugs['tribe_'.$slug]['translations'][$language->slug] = empty($tr_slug) ? $slug : $tr_slug; + } + + return $slugs; + } + + /** + * Performs the sanitization ( before saving in DB ) of slugs translations + * The Events Calendar does not accept accents, but let's accept slashes for the event category slug. + * + * @since 1.9 + * + * @param string $translation Translation to sanitize. + * @param string $name Unique name for the string. + * + * @return string + */ + public function sanitize_string_translation($translation, $name) + { + if ('slug_archive_tribe_events' === $name || 0 === strpos($name, 'slug_tribe_')) { + $slugs = explode('/', $translation); + $slugs = array_map('sanitize_title', $slugs); + + return implode('/', $slugs); + } + + return $translation; + } + + /** + * Add translated slugs to specific TEC rewrite rules. + * + * @since 2.2 + * + * @param array $bases Array of arrays of rewrite base slugs. + * + * @return array + */ + public function rewrite_slugs($bases) + { + $translatable_slugs = $this->get_translatable_slugs(); + + if (empty($translatable_slugs)) { + return $bases; + } + + foreach ($bases as $type => $base) { + $default_slug = reset($base); + + foreach ($translatable_slugs as $slugs) { + if ($slugs['slug'] === $default_slug) { + $bases[$type] = array_unique(array_merge([$default_slug], $slugs['translations'])); + break; + } + } + } + + return $bases; + } + + /** + * Translated strings must be sanitized the same way The Events Calendar does before they are saved. + * All are of validation_type 'html'. + * + * @since 2.2 + * + * @param string $translation Translated string. + * @param string $name String name. + * @param string $context String context. + * + * @return string Sanitized translation. + */ + public function sanitize_strings($translation, $name, $context) + { + if ('The Events Calendar' === $context) { + $translation = balanceTags($translation); + } + + return $translation; + } + + /** + * Filters TEC's base slugs to unescape dashes. + * + * If `$method` is 'regex', `Tribe__Events__Rewrite->get_bases()` will use `preg_quote()` to get + * its slugs ready as regex patterns. However, `-` characters are valid in this context and + * should not be escaped (reminder: they come from PLL's string translations). + * + * @since 3.1 + * @see $this->rewrite_slugs() + * + * @param string[] $bases An array of rewrite bases that have been generated. + * @param string $method The method that's being used to generate the bases; defaults to `regex`. + * + * @return string[] + */ + public function fix_escaped_dashes_in_slugs($bases, $method) + { + if ('regex' !== $method) { + return $bases; + } + + return array_map( + function ($base) { + return str_replace('\\-', '-', $base); + }, + $bases + ); + } + + /** + * Filters the string to be used as the taxonomy slug. + * This replaces TEC's translated category slug by the untranslated one, as it is returned by the public method + * `Tribe__Events__Main->get_category_slug()`. + * + * @since 3.1 + * + * @return string + */ + public function get_category_slug() + { + return 'category'; + } + + /** + * Filters the string to be used as the tag slug. + * This replaces TEC's translated tag slug by the untranslated one, as it is returned by a public method + * `Tribe__Events__Main->get_tag_slug()`. + * + * @since 3.1 + * + * @return string + */ + public function get_tag_slug() + { + return 'tag'; + } + + /** + * Adds the lang to TEC's REST URL. + * This provides a way to identify in which language PLL should work in the REST request. + * + * @since 3.1 + * + * @param string $url The View endpoint URL, either a REST API URL or a admin-ajax.php fallback URL if REST API + * is not available. + * + * @return string + */ + public function add_missing_lang_to_rest_url($url) + { + $curlang = $this->get_curlang(); + + if (empty($curlang)) { + return $url; + } + + $lang = $this->get_lang_from_url_query_arg($url); + + if (!empty($lang)) { + return $url; + } + + return add_query_arg(['lang' => $curlang->slug], $url); + } + + /** + * Filters the locale when TEC is performing a REST request. + * + * @since 3.1 + * + * @param string $locale The locale ID. + * + * @return string + */ + public function filter_locale_for_rest($locale) + { + if (!$this->is_tec_rest_request()) { + return $locale; + } + + $curlang = $this->get_curlang(); + + if (empty($curlang->locale)) { + return $locale; + } + + return $curlang->locale; + } + + /** + * Filters the query arguments that should be applied to the View links to add the missing language. + * The added language is the global current language. + * + * @since 3.1 + * + * @param mixed[] $url_args The current URL query arguments, created from a filtered version of the current + * request context. + * + * @return mixed[] + */ + public function add_language_to_publicly_visible_views_query_args($url_args) + { + $curlang = $this->get_curlang(); + + if (!empty($curlang)) { + $url_args['lang'] = $curlang->slug; + } + + return $url_args; + } + + /** + * Fixes the "upcoming events" widget link. + * + * @since 3.1 + * + * @param mixed[] $template_vars An associative array of template variables. Variables will be extracted in the + * template hence the key will be the name of the variable available in the template. + * + * @return mixed[] + */ + public function translate_widget_view_more_link($template_vars) + { + if (!isset($template_vars['view_more_link'])) { + return $template_vars; + } + + $curlang = $this->get_curlang(); + + if (empty($curlang) || empty($this->slugs_model)) { + return $template_vars; + } + + $template_vars['view_more_link'] = home_url('/'.tribe_get_option('eventsSlug', 'events')); + $template_vars['view_more_link'] = $this->polylang->links_model->add_language_to_link($template_vars['view_more_link'], $curlang); + $template_vars['view_more_link'] = $this->slugs_model->translate_slug($template_vars['view_more_link'], $curlang, 'archive_'.TEC::POSTTYPE); + $template_vars['view_more_link'] = user_trailingslashit($template_vars['view_more_link']); + + return $template_vars; + } + + /** + * Filters the array of query variables parsed by TEC to force the use of the right language. + * For example, `example.com/events?lang=de` would return the default language instead of using the provided query + * arg because TEC tries to use the WP rewrite rules to match the query path, and `events` => default language. + * However, this is not needed for `example.com/de/events-de/` and `example.com/?lang=de` because the right + * language will be set in these cases. + * + * @since 3.1 + * @see Tribe__Rewrite->parse_request() + * @see PLL_TEC->add_language_to_publicly_visible_views_query_args() + * + * @param string[] $query_vars The parsed query vars array. + * @param string[] $extra_query_vars An associative array of extra query vars that will be processed before + * the WordPress defined ones. + * @param string $url The URL to parse. + * + * @return string[] + */ + public function force_language_on_tec_parse_query_vars($query_vars, $extra_query_vars, $url) + { + // Find the lang in the URL... + $lang = $this->get_lang_from_url_query_arg($url); + + if (!empty($lang)) { + // ... and add it to the query vars. + $query_vars['lang'] = $lang->slug; + } + + return $query_vars; + } + + /** + * Adds the lang to TEC's query arguments that will be used to build a View URL. + * This insures that a lang arg is available when building the view's URL. + * + * @since 3.1 + * + * @param mixed[] $query_args An array of query args that will be used to build the URL for the View. + * + * @return mixed[] + */ + public function add_missing_lang_to_query_arg($query_args) + { + if (isset($query_args['lang'])) { + return $query_args; + } + + $curlang = $this->get_curlang(); + + $query_args['lang'] = !empty($curlang) ? $curlang->slug : $this->polylang->options['default_lang']; + + return $query_args; + } + + /** + * Adds the lang to the URL passed to `Tribe__Rewrite->get_canonical_url()`. + * This insures that a lang arg is available when building a URL. + * + * @since 3.1 + * + * @param string|null $canonical_url The canonical URL, defaults to `null`; returning a non `null` value will + * make the logic bail and return the value. + * @param string $url The input URL to resolve to a canonical one. + * + * @return string|null + */ + public function add_missing_lang_to_non_canonical_url($canonical_url, $url) + { + $lang = $this->get_lang_from_url_query_arg($url); + + if (!empty($lang)) { + // All good. + return $canonical_url; + } + + $curlang = $this->get_curlang(); + + if (empty($curlang)) { + // We're screwed. + return $canonical_url; + } + + // Re-inject the URL with the current lang. `$this->get_lang_from_url_query_arg()` will prevent an infinite loop. + return Tribe__Rewrite::instance()->get_canonical_url(add_query_arg('lang', $curlang->slug, $url)); + } + + /** + * Removes the `name` arg from the URL passed to `Tribe__Rewrite->get_canonical_url()` when there is already a + * `post_type` arg: this seems to mess up the process. + * + * @since 3.1 + * + * @param string|null $canonical_url The canonical URL, defaults to `null`; returning a non `null` value will + * make the logic bail and return the value. + * @param string $url The input URL to resolve to a canonical one. + * + * @return string|null + */ + public function remove_name_arg_from_non_canonical_url($canonical_url, $url) + { + $url_query = wp_parse_url($url, PHP_URL_QUERY); + + if (empty($url_query)) { + return $canonical_url; + } + + $parsed_query = []; + wp_parse_str($url_query, $parsed_query); + + if (empty($parsed_query)) { + return $canonical_url; + } + + if (empty($parsed_query['post_type']) || empty($parsed_query['name']) || !empty($parsed_query['ical'])) { + return $canonical_url; + } + + if (TEC::POSTTYPE !== $parsed_query['post_type']) { + return $canonical_url; + } + + if (!empty($parsed_query['lang']) && $parsed_query['lang'] === $parsed_query['name']) { + // ¯\(°_o)/¯. + $remove = true; + } elseif (tribe_get_option('eventsSlug') === $parsed_query['name']) { + $remove = true; + } + + if (empty($remove)) { + return $canonical_url; + } + + // Re-inject the URL. Tests against `$parsed_query['name']` will prevent an infinite loop. + return Tribe__Rewrite::instance()->get_canonical_url(remove_query_arg('name', $url)); + } + + /** + * Filters TEC's canonical URL to fix the language slug in it. + * Because of TEC's method to build URLs, using the rewrite rules array, the language slug is not replaced and is + * outputted like the rewrite rule pattern: `/(en|fr|de)/`. This filter replaces the pattern by the language + * contained in the original URL. If not found in the original URL, falls back to the current language or the default + * one. + * + * @since 3.1 + * + * @param string $resolved The resolved, canonical URL. + * @param string $url The original URL to resolve. + * + * @return string + */ + public function fix_language_in_canonical_url($resolved, $url) + { + $options = $this->polylang->options; + + // Remove the default language if it must be hidden in the URLs. + $languages = $this->polylang->model->get_languages_list( + [ + 'hide_default' => $options['hide_default'], + 'fields' => 'slug', + ] + ); + + if (empty($languages)) { + return $resolved; + } + + /** + * What we want to modify in the URL. + * Ex: `/(en|fr|de)`, `/language/(fr|de)`. + */ + $language_path = $options['rewrite'] ? '' : '/language'; + $to_replace = $language_path.'/('.implode('|', $languages).')/'; + + if (strpos($resolved, $to_replace) === false) { + return $resolved; + } + + // Find the lang in the URL (or fallback). + $lang = $this->get_lang_from_url_query_arg($url); + + if (empty($lang)) { + // We need a lang, whichever it is. + $curlang = $this->get_curlang(); + $lang = !empty($curlang) ? $curlang->slug : $options['default_lang']; + } else { + $lang = $lang->slug; + } + + // Make the final replacement. + if ($options['hide_default'] && $options['default_lang'] === $lang) { + // The default language is hidden in the URL. + $replacement = '/'; + } else { + $replacement = "{$language_path}/{$lang}/"; + } + + return str_replace($to_replace, $replacement, $resolved); + } + + /** + * Filters TEC's canonical URL to translate all slugs in it. + * This is possible because a `lang` arg is available in the "ugly" URL. + * + * @since 3.1 + * @see Tribe__Events__Rewrite->get_dynamic_matchers() + * + * @param string $resolved The resolved, canonical URL. + * @param string $url The original URL to resolve. + * + * @return string + */ + public function translate_canonical_url($resolved, $url) + { + if (empty($this->slugs_model)) { + return $resolved; + } + + // Find the lang in the URL (or fallback). + $lang = $this->get_lang_from_url_query_arg_or_fallback($url); + + if (empty($lang)) { + // What? + return $resolved; + } + + // Make sure the language is well formatted. + $resolved = remove_query_arg('lang', $resolved); + $resolved = $this->polylang->links_model->add_language_to_link($resolved, $lang); + + foreach ($this->get_translatable_slugs() as $slug_id => $translations) { + $resolved = $this->slugs_model->switch_translated_slug($resolved, $lang, $slug_id); + } + + return $resolved; + } + + /** + * Tells if a request is a TEC REST API request. + * TEC does a good job for their REST URL by providing a `admin-ajax.php` fallback in case the REST API is not + * available. Unfortunately, this choice is late in the process so we have to test the given URL against the 2 + * possibilities. + * + * @since 3.1 + * @see Tribe\Events\Views\V2\Rest_Endpoint->get_url() + * + * @param string $requested_url The requested URL. Falls back to the current URL. + * + * @return bool|null Whether the request is a TEC REST API request. Null if not ready to answer yet. + */ + protected function is_tec_rest_request($requested_url = null) + { + if (!isset($this->is_tec_rest_request['views_v2_is_enabled'])) { + if (!function_exists('tribe_events_views_v2_is_enabled')) { + return null; + } + + $this->is_tec_rest_request['views_v2_is_enabled'] = tribe_events_views_v2_is_enabled(); + } + + if (!$this->is_tec_rest_request['views_v2_is_enabled']) { + // If the views V2 are not enabled, no REST requests. + return false; + } + + if (empty($requested_url) || !is_string($requested_url)) { + // Fall back to the current URL. + if (!isset($this->is_tec_rest_request['pll_requested_url'])) { + $this->is_tec_rest_request['pll_requested_url'] = (string) set_url_scheme(pll_get_requested_url()); + } + + $requested_url = $this->is_tec_rest_request['pll_requested_url']; + } else { + $requested_url = (string) set_url_scheme($requested_url); + } + + if (isset($this->is_tec_rest_request['is:'.$requested_url])) { + return $this->is_tec_rest_request['is:'.$requested_url]; + } + + if (false === strpos($requested_url, '/admin-ajax.php')) { + // Test against the REST URL. + if (!isset($this->is_tec_rest_request['tec_rest_url_pattern'])) { + $url = $this->get_tec_rest_url(true); + + if (empty($url)) { + // `$wp_rewrite` is probably not set yet. + return null; + } + + $this->is_tec_rest_request['tec_rest_url_pattern'] = preg_replace('@[#?].*$@', '', $url); + $this->is_tec_rest_request['tec_rest_url_pattern'] = sprintf('@^%s[/?#]@i', preg_quote($this->is_tec_rest_request['tec_rest_url_pattern'], '@')); + } + + $this->is_tec_rest_request['is:'.$requested_url] = (bool) preg_match($this->is_tec_rest_request['tec_rest_url_pattern'], $requested_url); + + return $this->is_tec_rest_request['is:'.$requested_url]; + } + + // Test against the admin ajax URL. + if (!isset($this->is_tec_rest_request['tec_ajax_url_action'])) { + $this->is_tec_rest_request['tec_ajax_url_action'] = $this->get_tec_rest_url(false); + $this->is_tec_rest_request['tec_ajax_url_action'] = $this->get_query_arg_from_url($this->is_tec_rest_request['tec_ajax_url_action'], 'action'); + } + + if (empty($this->is_tec_rest_request['tec_ajax_url_action'])) { + // Uh? + $this->is_tec_rest_request['is:'.$requested_url] = false; + + return false; + } + + $requested_action = $this->get_query_arg_from_url($requested_url, 'action'); + + $this->is_tec_rest_request['is:'.$requested_url] = $requested_action === $this->is_tec_rest_request['tec_ajax_url_action']; + + return $this->is_tec_rest_request['is:'.$requested_url]; + } + + /** + * Returns the current language object. + * Can return `null` if not defined yet. + * + * @since 3.1 + * + * @return PLL_Language|null + */ + protected function get_curlang() + { + if (!empty($this->curlang)) { + return $this->curlang; + } + + if (!empty($_REQUEST['lang']) && is_string($_REQUEST['lang']) && Polylang::is_rest_request()) { // phpcs:ignore WordPress.Security.NonceVerification + // REST request. + $curlang = $this->polylang->model->get_language(sanitize_key($_REQUEST['lang'])); // phpcs:ignore WordPress.Security.NonceVerification + + if (!empty($curlang)) { + $this->curlang = $curlang; + $this->polylang->curlang = $this->curlang; + + return $this->curlang; + } + } + + if (!empty($this->polylang->curlang)) { + // Global context. + $this->curlang = &$this->polylang->curlang; + + return $this->curlang; + } + + if (empty($this->polylang->options['force_lang']) && $this->polylang instanceof PLL_Frontend && !did_action('pll_language_defined')) { + // Lang defined by content: too soon. + return null; + } + + $curlang = $this->polylang->model->get_default_language(); + + if (empty($curlang)) { + // We're screwed. + return null; + } + + // Default lang. + $this->curlang = $curlang; + + return $this->curlang; + } + + /** + * Returns the list of slugs that need to be reset in TEC, except the deprecated ones. + * + * @since 3.1 + * @see PLL_TEC->reset_slugs() + * + * @return string[] Array keys match `Tribe__Events__Main`'s properties name. + */ + protected function get_slugs_to_reset() + { + return [ + 'category_slug' => 'category', + 'tag_slug' => 'tag', + 'monthSlug' => 'month', + 'listSlug' => 'list', + 'upcomingSlug' => 'upcoming', + 'pastSlug' => 'past', + 'daySlug' => 'day', + 'todaySlug' => 'today', + 'featured_slug' => 'featured', + 'all_slug' => 'all', + ]; + } + + /** + * Returns the list of IDs of translatable slugs dedicated to TEC. + * Ex: `tribe_venue`, `archive_tribe_events`, `paged`, `tribe_today`. + * + * @since 3.1 + * + * @return string[] + */ + protected function get_translatable_slug_ids() + { + if (!empty($this->translatable_slug_ids)) { + return $this->translatable_slug_ids; + } + + $slug_ids = [ + Venue::POSTTYPE, + Organizer::POSTTYPE, + TEC::POSTTYPE, + 'archive_'.TEC::POSTTYPE, + 'post_tag', + 'paged', + ]; + + foreach ($this->get_slugs_to_reset() as $slug) { + $slug_ids[] = 'tribe_'.$slug; + } + + $this->translatable_slug_ids = array_combine($slug_ids, $slug_ids); + + return $this->translatable_slug_ids; + } + + /** + * Returns the list of translatable slugs dedicated to TEC. + * + * @since 3.1 + * + * @return mixed[] + */ + protected function get_translatable_slugs() + { + if (empty($this->slugs_model)) { + return []; + } + + return array_intersect_key($this->slugs_model->get_translatable_slugs(), $this->get_translatable_slug_ids()); + } + + /** + * Returns the value of the `lang` query arg from the given URL. + * + * @since 3.1 + * + * @param string $url The URL to retrieve the arg from. + * + * @return PLL_Language|null The lang object. Null if not found or invalid. + */ + protected function get_lang_from_url_query_arg($url) + { + $lang = $this->get_query_arg_from_url($url, 'lang'); + + if (empty($lang) || !is_string($lang)) { + return null; + } + + $lang = $this->polylang->model->get_language(sanitize_key($lang)); + + if (empty($lang)) { + return null; + } + + return $lang; + } + + /** + * Returns the value of the `lang` query arg from the given URL. + * + * @since 3.1 + * + * @param string $url The URL to retrieve the arg from. + * + * @return PLL_Language|null The lang object. Null if not found or invalid. + */ + protected function get_lang_from_url_query_arg_or_fallback($url) + { + $lang = $this->get_lang_from_url_query_arg($url); + + if (!empty($lang)) { + return $lang; + } + + $lang = $this->get_curlang(); + + if (!empty($lang)) { + return $lang; + } + + // Uh? + $lang = $this->polylang->model->get_default_language(); + + if (!empty($lang)) { + return $lang; + } + + // What? + return null; + } + + /** + * Returns the value of a query arg from the given URL. + * + * @since 3.1 + * + * @param string $url The URL to retrieve the arg from. + * @param string $query_arg_name The name of the query arg to retrieve. + * + * @return string|null The raw value of the query arg. Null if not found. + */ + protected function get_query_arg_from_url($url, $query_arg_name) + { + if (empty($url) || !is_string($url)) { + return null; + } + + if (empty($query_arg_name) || !is_string($query_arg_name)) { + return null; + } + + $url_query = wp_parse_url($url, PHP_URL_QUERY); + + if (empty($url_query)) { + return null; + } + + $parsed = []; + wp_parse_str($url_query, $parsed); + + if (empty($parsed[$query_arg_name]) || !is_string($parsed[$query_arg_name])) { + return null; + } + + return sanitize_key($parsed[$query_arg_name]); + } + + /** + * Returns TEC's REST URL. + * + * @since 3.1 + * + * @param bool $enable_rest True to get the REST URL. False to get the admin ajax URL. + * + * @return string|null The REST URL. Null if too soon to be determined: this may happen when requesting the + * the real REST URL (`$enable_rest` is true) but `$wp_rewrite` is not ready. + */ + protected function get_tec_rest_url($enable_rest) + { + global $wp_rewrite; + + if ($enable_rest) { + // In this case, `Rest_Endpoint->get_url()` will use `get_rest_url()`. + if ((is_multisite() && get_blog_option(0, 'permalink_structure')) || get_option('permalink_structure')) { // See the same test done in `get_rest_url()`. + // We test `$wp_rewrite` to prevent `get_rest_url()` to explode. + if (!$wp_rewrite instanceof WP_Rewrite) { + return null; + } + } + } + + // Force `Rest_Endpoint->is_available()`'s behavior with this filter callback. + $priority = 100000; + $callback = function () use ($enable_rest) { + return (bool) $enable_rest; + }; + + add_filter('tribe_events_views_v2_rest_endpoint_available', $callback, $priority); + $url = (new Rest_Endpoint())->get_url(); + remove_filter('tribe_events_views_v2_rest_endpoint_available', $callback, $priority); + + return $url; + } } diff --git a/__plugins/polylang-pro-3.7.6/polylang.php b/__plugins/polylang-pro-3.7.6/polylang.php index 9c648f1dbd38028a62b61d9c495756a61ba5936f..931b95e9cc8f862f72f2e7231c308d638bed62c5 100644 --- a/__plugins/polylang-pro-3.7.6/polylang.php +++ b/__plugins/polylang-pro-3.7.6/polylang.php @@ -1,8 +1,8 @@ time() ) + (array) get_option( 'recently_activated' ) ); - } else { - update_site_option( 'recently_activated', array( POLYLANG_BASENAME => time() ) + (array) get_site_option( 'recently_activated' ) ); - } + // Add the deactivated plugin to the list of recent activated plugins. + if (!is_network_admin()) { + update_option('recently_activated', [POLYLANG_BASENAME => time()] + (array) get_option('recently_activated')); + } else { + update_site_option('recently_activated', [POLYLANG_BASENAME => time()] + (array) get_site_option('recently_activated')); + } } else { - define( 'POLYLANG_BASENAME', plugin_basename( __FILE__ ) ); // Plugin name as known by WP. + define('POLYLANG_BASENAME', plugin_basename(__FILE__)); // Plugin name as known by WP. } -require __DIR__ . '/vendor/autoload.php'; -require __DIR__ . '/vendor/wpsyntex/polylang/polylang.php'; +require __DIR__.'/vendor/autoload.php'; +require __DIR__.'/vendor/wpsyntex/polylang/polylang.php'; -if ( empty( $_GET['deactivate-polylang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - add_action( 'pll_init_options_for_blog', array( Options_Registry::class, 'register' ), 15 ); // After Polylang. - add_action( 'pll_pre_init', array( new PLL_Pro(), 'init' ), 0 ); +if (empty($_GET['deactivate-polylang'])) { // phpcs:ignore WordPress.Security.NonceVerification + add_action('pll_init_options_for_blog', [Options_Registry::class, 'register'], 15); // After Polylang. + add_action('pll_pre_init', [new PLL_Pro(), 'init'], 0); } diff --git a/__plugins/polylang-pro-3.7.6/services/admin-loader/admin-loader.php b/__plugins/polylang-pro-3.7.6/services/admin-loader/admin-loader.php index f9df289c2b3fbb14adc9098839dd3d23b158a381..268f3ee3f0871226847ea95331dbdab118e928b0 100644 --- a/__plugins/polylang-pro-3.7.6/services/admin-loader/admin-loader.php +++ b/__plugins/polylang-pro-3.7.6/services/admin-loader/admin-loader.php @@ -1,112 +1,114 @@ polylang = &$polylang; - $this->property = $property; - $this->args = $args; + /** + * Constructor. + * + * @since 2.8 + * @since 3.6 New parameter `$args`. + * + * @param PLL_Base $polylang Polylang object. + * @param string $property Name of the property to add to $polylang. + * @param array $args Optional. List of arguments to use when instantiating the class. + * + * @phpstan-param non-falsy-string $property + */ + public function __construct(&$polylang, $property, array $args = []) + { + $this->polylang = &$polylang; + $this->property = $property; + $this->args = $args; - add_action( 'admin_init', array( $this, 'load' ), 20 ); // After fusion Builder. - } + add_action('admin_init', [$this, 'load'], 20); // After fusion Builder. + } - /** - * Finds out if the block editor is in use and loads the correct class accordingly. - * - * @since 2.8 - * - * @return void - */ - public function load() { - if ( 'post-new.php' === $GLOBALS['pagenow'] ) { - // We need to wait until we know which editor is in use - add_filter( 'use_block_editor_for_post', array( $this, '_load' ), 999 ); // After the plugin Classic Editor - } elseif ( 'post.php' === $GLOBALS['pagenow'] && isset( $_GET['action'], $_GET['post'] ) && 'edit' === $_GET['action'] && empty( $_GET['meta-box-loader'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - $this->_load( use_block_editor_for_post( (int) $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - } else { - $this->_load( false ); - } - } + /** + * Finds out if the block editor is in use and loads the correct class accordingly. + * + * @since 2.8 + * + * @return void + */ + public function load() + { + if ('post-new.php' === $GLOBALS['pagenow']) { + // We need to wait until we know which editor is in use + add_filter('use_block_editor_for_post', [$this, '_load'], 999); // After the plugin Classic Editor + } elseif ('post.php' === $GLOBALS['pagenow'] && isset($_GET['action'], $_GET['post']) && 'edit' === $_GET['action'] && empty($_GET['meta-box-loader'])) { // phpcs:ignore WordPress.Security.NonceVerification + $this->_load(use_block_editor_for_post((int) $_GET['post'])); // phpcs:ignore WordPress.Security.NonceVerification + } else { + $this->_load(false); + } + } - /** - * Loads the correct class, depending on the editor in use. - * - * We must make sure to instantiate the class only once, as the function may be called from a filter, - * - * @since 2.8 - * - * @param bool $is_block_editor Whether to use the block editor or not. - * @return bool - */ - public function _load( $is_block_editor ) { - $prop = $this->property; + /** + * Loads the correct class, depending on the editor in use. + * + * We must make sure to instantiate the class only once, as the function may be called from a filter, + * + * @since 2.8 + * + * @param bool $is_block_editor Whether to use the block editor or not. + * + * @return bool + */ + public function _load($is_block_editor) + { + $prop = $this->property; - if ( isset( $this->polylang->$prop ) ) { - return $is_block_editor; - } + if (isset($this->polylang->$prop)) { + return $is_block_editor; + } - switch ( $prop ) { - case 'machine_translation': - $classname = WP_Syntex\Polylang_Pro\Modules\Machine_Translation\Posts\Button::class; - break; + switch ($prop) { + case 'machine_translation': + $classname = WP_Syntex\Polylang_Pro\Modules\Machine_Translation\Posts\Button::class; + break; - default: - $classname = 'PLL_' . ucwords( $prop, '_' ); - break; - } + default: + $classname = 'PLL_'.ucwords($prop, '_'); + break; + } - if ( $is_block_editor && pll_use_block_editor_plugin() ) { - $classname = "{$classname}_REST"; - } + if ($is_block_editor && pll_use_block_editor_plugin()) { + $classname = "{$classname}_REST"; + } - $args = array( $this->polylang ); + $args = [$this->polylang]; - if ( ! empty( $this->args ) ) { - $args = array_merge( $args, array_values( $this->args ) ); - } + if (!empty($this->args)) { + $args = array_merge($args, array_values($this->args)); + } - $this->polylang->$prop = new $classname( ...$args ); + $this->polylang->$prop = new $classname(...$args); - return $is_block_editor; - } + return $is_block_editor; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-posts.php b/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-posts.php index 35963bfcec0c4d4f8ad0fbc23c1be5f627a8b44e..f84ea9186ad339664601b07527c9abdf9260bf58 100644 --- a/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-posts.php +++ b/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-posts.php @@ -1,7 +1,4 @@ options = $options; - } - - /** - * Gets all the post ids linked to a set of posts. - * - * @since 3.3 - * @since 3.4 Parameter changed from int[] to WP_Post[]. - * @since 3.5 Renamed from get_linked_post_ids and now returns a WP_Post array. - * Also added a second parameter for the post types to retrieve. - * - * @param WP_Post[] $posts The posts for which searching linked posts. - * @param string[] $post_types Limits the linked posts search to these post types. - * @return WP_Post[] An array of linked post objects. - */ - public function get_linked_posts( array $posts, array $post_types ) { - $linked_ids = array(); - - foreach ( $posts as $post ) { - $linked_ids = array_merge( $linked_ids, $this->get_post_ids_from_post( $post ) ); - } - - $linked_ids = array_unique( $linked_ids ); - - if ( empty( $linked_ids ) ) { - return array(); - } - - // Query all the linked posts outside the PLL_Export_Bulk_Option::translate() loop to avoid multiple SQL queries with get_post() call. - return get_posts( - array( - 'include' => $linked_ids, - 'post_type' => $post_types, - 'post_status' => 'any', - 'orderby' => 'ID', - 'order' => 'ASC', - ) - ); - } - - /** - * Gets the post ids from a post, whether it's classic or block edited. - * - * @since 3.3 - * - * @param WP_Post $post A given WP_Post object. - * @return int[] An array of post ids. - */ - protected function get_post_ids_from_post( $post ) { - $linked_ids = array(); - - if ( function_exists( 'has_blocks' ) && has_blocks( $post->post_content ) ) { - $linked_ids = $this->get_post_ids_from_block_content( $post->post_content ); - } elseif ( $this->options['media_support'] ) { - $linked_ids = $this->get_medias_from_html_content( $post->post_content ); - } - - if ( $this->options['media_support'] && has_post_thumbnail( $post->ID ) ) { - $linked_ids[] = get_post_thumbnail_id( $post->ID ); - } - - $linked_ids = array_filter( $linked_ids ); // Clean up the array. - - /** - * Filters the medias linked to a post. - * - * @since 3.3 - * @since 3.7 `post_id` has been replaced by `post`, we now send the `WP_Post` object instead of the ID. - * - * @param int[] $linked_ids Post ids attached to a post (could be in content or in post metas). - * @param WP_Post $post The post we get other post from. - */ - $linked_ids = apply_filters( 'pll_collect_post_ids', $linked_ids, $post ); - - return array_unique( $linked_ids ); - } - - /** - * Gets the post ids from block type content. - * - * @since 3.3 - * - * @param string $post_content The content of the post. - * @return int[] An array of post ids. - */ - protected function get_post_ids_from_block_content( $post_content ) { - $blocks = parse_blocks( $post_content ); - - return $this->get_post_ids_from_blocks_deep( $blocks ); - } - - /** - * Gets the post ids from blocks. - * - * @since 3.3 - * - * @param array $blocks An array of blocks. - * @return int[] An array of post ids. - */ - protected function get_post_ids_from_blocks_deep( $blocks ) { - $post_ids = array(); - - foreach ( $blocks as $block ) { - if ( $this->options['media_support'] ) { - $post_ids = array_merge( $post_ids, $this->get_media_ids_from_block( $block ) ); - } - $post_ids = array_merge( $post_ids, $this->get_navigation_block_ids( $block ) ); - $post_ids = array_merge( $post_ids, $this->get_reusable_block_ids( $block ) ); - } - return array_unique( $post_ids ); - } - - /** - * Gets the media ids from a block. - * - * @since 3.3 - * - * @param array $block A representative array of a block. - * @return int[] An array of media ids, empty if none found. - */ - protected function get_media_ids_from_block( $block ) { - $post_ids = array(); - - switch ( $block['blockName'] ) { - case 'core/audio': - case 'core/cover': - case 'core/file': - case 'core/image': - case 'core/video': - if ( ! empty( $block['attrs']['id'] ) ) { - $post_ids[] = $block['attrs']['id']; - } - break; - case 'core/gallery': - // Backward compatibility with WP < 5.9. - if ( ! empty( $block['attrs']['ids'] ) && is_array( $block['attrs']['ids'] ) ) { - $post_ids = array_merge( $post_ids, $block['attrs']['ids'] ); - } - break; - case 'core/media-text': - if ( ! empty( $block['attrs']['mediaId'] ) ) { - $post_ids[] = $block['attrs']['mediaId']; - } - break; - default: - if ( ! empty( $block['innerHTML'] ) ) { - $post_ids = array_merge( $post_ids, $this->get_medias_from_html_content( $block['innerHTML'] ) ); - } - break; - } - if ( ! empty( $block['innerBlocks'] ) ) { - $post_ids = array_merge( $post_ids, $this->get_post_ids_from_blocks_deep( $block['innerBlocks'] ) ); - } - - return $post_ids; - } - - /** - * Gets the media ids from classic type content. - * - * @since 3.3 - * - * @param string $post_content The content of the post. - * @return int[] - */ - protected function get_medias_from_html_content( $post_content ) { - - return array_merge( $this->get_medias_from_img_tags( $post_content ), $this->get_medias_from_shortcodes( $post_content ) ); - } - - /** - * Gets media ids from shortcodes. - * - * @since 3.3 - * - * @param string $post_content The content of the required post. - * @return int[] The media ids. - */ - protected function get_medias_from_shortcodes( $post_content ) { - $media_ids = array(); - - if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post_content, $matches, PREG_SET_ORDER ) ) { - foreach ( $matches as $shortcode ) { - $attributes = shortcode_parse_atts( $shortcode[3] ); // $shortcode[3] returns the shortcode attributes as string. - - switch ( $shortcode[2] ) { // $shortcode[2] returns the shortcode name. - case 'caption': - if ( isset( $attributes['id'] ) && preg_match( '/attachment_([0-9]+)/', $attributes['id'], $attr ) ) { - $media_ids[] = (int) $attr[1]; - } - break; - case 'gallery': - case 'playlist': - if ( isset( $attributes['ids'] ) ) { - $media_ids = array_merge( $media_ids, array_map( 'intval', explode( ',', $attributes['ids'] ) ) ); - } - break; - } - } - } - - return $media_ids; - } - - /** - * Gets media ids from img tags - * - * @since 3.3 - * - * @param string $post_content The content of the post to search from. - * @return int[] An array of media ids (empty if no media is found). - */ - protected function get_medias_from_img_tags( $post_content ) { - $media_ids = array(); - $textarr = wp_html_split( $post_content ); - if ( ! is_array( $textarr ) ) { - return $media_ids; - } - foreach ( $textarr as $text ) { - if ( 0 !== strpos( $text, 'get_post_ids_from_block_content( $linked_block_post->post_content ) ); - } - - return $ids; - } - - /** - * Returns the ID from a navigation block. - * - * @since 3.3 - * - * @param array $block An array containing block data. - * @return int[] An array of navigation post IDs, empty if none found. - */ - protected function get_navigation_block_ids( $block ) { - if ( 'core/navigation' !== $block['blockName'] ) { - return array(); - } - return array( (int) $block['attrs']['ref'] ); - } +class PLL_Collect_Linked_Posts +{ + /** + * Stores the plugin options. + * + * @var Options + */ + protected $options; + + /** + * PLL_Collect_Linked_Posts constructor. + * + * @since 3.3 + * + * @param Options $options The plugin options. + */ + public function __construct($options) + { + $this->options = $options; + } + + /** + * Gets all the post ids linked to a set of posts. + * + * @since 3.3 + * @since 3.4 Parameter changed from int[] to WP_Post[]. + * @since 3.5 Renamed from get_linked_post_ids and now returns a WP_Post array. + * Also added a second parameter for the post types to retrieve. + * + * @param WP_Post[] $posts The posts for which searching linked posts. + * @param string[] $post_types Limits the linked posts search to these post types. + * + * @return WP_Post[] An array of linked post objects. + */ + public function get_linked_posts(array $posts, array $post_types) + { + $linked_ids = []; + + foreach ($posts as $post) { + $linked_ids = array_merge($linked_ids, $this->get_post_ids_from_post($post)); + } + + $linked_ids = array_unique($linked_ids); + + if (empty($linked_ids)) { + return []; + } + + // Query all the linked posts outside the PLL_Export_Bulk_Option::translate() loop to avoid multiple SQL queries with get_post() call. + return get_posts( + [ + 'include' => $linked_ids, + 'post_type' => $post_types, + 'post_status' => 'any', + 'orderby' => 'ID', + 'order' => 'ASC', + ] + ); + } + + /** + * Gets the post ids from a post, whether it's classic or block edited. + * + * @since 3.3 + * + * @param WP_Post $post A given WP_Post object. + * + * @return int[] An array of post ids. + */ + protected function get_post_ids_from_post($post) + { + $linked_ids = []; + + if (function_exists('has_blocks') && has_blocks($post->post_content)) { + $linked_ids = $this->get_post_ids_from_block_content($post->post_content); + } elseif ($this->options['media_support']) { + $linked_ids = $this->get_medias_from_html_content($post->post_content); + } + + if ($this->options['media_support'] && has_post_thumbnail($post->ID)) { + $linked_ids[] = get_post_thumbnail_id($post->ID); + } + + $linked_ids = array_filter($linked_ids); // Clean up the array. + + /** + * Filters the medias linked to a post. + * + * @since 3.3 + * @since 3.7 `post_id` has been replaced by `post`, we now send the `WP_Post` object instead of the ID. + * + * @param int[] $linked_ids Post ids attached to a post (could be in content or in post metas). + * @param WP_Post $post The post we get other post from. + */ + $linked_ids = apply_filters('pll_collect_post_ids', $linked_ids, $post); + + return array_unique($linked_ids); + } + + /** + * Gets the post ids from block type content. + * + * @since 3.3 + * + * @param string $post_content The content of the post. + * + * @return int[] An array of post ids. + */ + protected function get_post_ids_from_block_content($post_content) + { + $blocks = parse_blocks($post_content); + + return $this->get_post_ids_from_blocks_deep($blocks); + } + + /** + * Gets the post ids from blocks. + * + * @since 3.3 + * + * @param array $blocks An array of blocks. + * + * @return int[] An array of post ids. + */ + protected function get_post_ids_from_blocks_deep($blocks) + { + $post_ids = []; + + foreach ($blocks as $block) { + if ($this->options['media_support']) { + $post_ids = array_merge($post_ids, $this->get_media_ids_from_block($block)); + } + $post_ids = array_merge($post_ids, $this->get_navigation_block_ids($block)); + $post_ids = array_merge($post_ids, $this->get_reusable_block_ids($block)); + } + + return array_unique($post_ids); + } + + /** + * Gets the media ids from a block. + * + * @since 3.3 + * + * @param array $block A representative array of a block. + * + * @return int[] An array of media ids, empty if none found. + */ + protected function get_media_ids_from_block($block) + { + $post_ids = []; + + switch ($block['blockName']) { + case 'core/audio': + case 'core/cover': + case 'core/file': + case 'core/image': + case 'core/video': + if (!empty($block['attrs']['id'])) { + $post_ids[] = $block['attrs']['id']; + } + break; + case 'core/gallery': + // Backward compatibility with WP < 5.9. + if (!empty($block['attrs']['ids']) && is_array($block['attrs']['ids'])) { + $post_ids = array_merge($post_ids, $block['attrs']['ids']); + } + break; + case 'core/media-text': + if (!empty($block['attrs']['mediaId'])) { + $post_ids[] = $block['attrs']['mediaId']; + } + break; + default: + if (!empty($block['innerHTML'])) { + $post_ids = array_merge($post_ids, $this->get_medias_from_html_content($block['innerHTML'])); + } + break; + } + if (!empty($block['innerBlocks'])) { + $post_ids = array_merge($post_ids, $this->get_post_ids_from_blocks_deep($block['innerBlocks'])); + } + + return $post_ids; + } + + /** + * Gets the media ids from classic type content. + * + * @since 3.3 + * + * @param string $post_content The content of the post. + * + * @return int[] + */ + protected function get_medias_from_html_content($post_content) + { + return array_merge($this->get_medias_from_img_tags($post_content), $this->get_medias_from_shortcodes($post_content)); + } + + /** + * Gets media ids from shortcodes. + * + * @since 3.3 + * + * @param string $post_content The content of the required post. + * + * @return int[] The media ids. + */ + protected function get_medias_from_shortcodes($post_content) + { + $media_ids = []; + + if (preg_match_all('/'.get_shortcode_regex().'/s', $post_content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $shortcode) { + $attributes = shortcode_parse_atts($shortcode[3]); // $shortcode[3] returns the shortcode attributes as string. + + switch ($shortcode[2]) { // $shortcode[2] returns the shortcode name. + case 'caption': + if (isset($attributes['id']) && preg_match('/attachment_([0-9]+)/', $attributes['id'], $attr)) { + $media_ids[] = (int) $attr[1]; + } + break; + case 'gallery': + case 'playlist': + if (isset($attributes['ids'])) { + $media_ids = array_merge($media_ids, array_map('intval', explode(',', $attributes['ids']))); + } + break; + } + } + } + + return $media_ids; + } + + /** + * Gets media ids from img tags. + * + * @since 3.3 + * + * @param string $post_content The content of the post to search from. + * + * @return int[] An array of media ids (empty if no media is found). + */ + protected function get_medias_from_img_tags($post_content) + { + $media_ids = []; + $textarr = wp_html_split($post_content); + if (!is_array($textarr)) { + return $media_ids; + } + foreach ($textarr as $text) { + if (0 !== strpos($text, 'get_post_ids_from_block_content($linked_block_post->post_content)); + } + + return $ids; + } + + /** + * Returns the ID from a navigation block. + * + * @since 3.3 + * + * @param array $block An array containing block data. + * + * @return int[] An array of navigation post IDs, empty if none found. + */ + protected function get_navigation_block_ids($block) + { + if ('core/navigation' !== $block['blockName']) { + return []; + } + + return [(int) $block['attrs']['ref']]; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-terms.php b/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-terms.php index e99fee16ea31c99e3645b9620a75003e614c7373..dbf28c404dfc808ab8a424c9b2e26751d9a56072 100644 --- a/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-terms.php +++ b/__plugins/polylang-pro-3.7.6/services/collect-linked-objects/collect-linked-terms.php @@ -1,333 +1,354 @@ - */ - protected $processed_posts = array(); - - /** - * Gets all the term objects linked to a set of posts. - * - * @since 3.3 - * - * @param WP_Post[] $posts An array of post objects. - * @param string[] $taxonomies Optional. Terms will be limited to the given taxonomies. - * @return WP_Term[] An array of linked term objects. - */ - public function get_linked_terms( array $posts, array $taxonomies = array() ) { - $terms = $this->get_terms_assigned_to_posts( $posts, $taxonomies ); - - $terms = array_merge( $terms, $this->get_terms_from_posts( $posts, $taxonomies ) ); - - return array_merge( $terms, $this->get_terms_parents( $terms ) ); - } - - /** - * Gets terms assigned to linked posts. - * - * @since 3.7 - * - * @param WP_Post[] $posts An array of post objects. - * @param string[] $taxonomies Terms will be limited to the given taxonomies. - * @return WP_Term[] An array of assigned term objects. - */ - protected function get_terms_assigned_to_posts( array $posts, array $taxonomies ): array { - $post_ids = wp_list_pluck( $posts, 'ID' ); - $post_terms = wp_get_object_terms( $post_ids, $taxonomies ); - - return is_array( $post_terms ) ? $post_terms : array(); - } - - /** - * Gets all the term objects from a set of posts content. - * - * @since 3.7 - * - * @param WP_Post[] $posts An array of post objects. - * @param string[] $taxonomies Terms will be limited to the given taxonomies. - * @return WP_Term[] An array of linked term objects. - */ - protected function get_terms_from_posts( array $posts, array $taxonomies ): array { - $this->processed_posts = array(); - $linked_ids = array(); - - foreach ( $posts as $post ) { - $linked_ids = array_merge( $linked_ids, $this->get_term_ids_from_post( $post ) ); - } - - $this->processed_posts = array(); - - if ( empty( $linked_ids ) ) { - return array(); - } - - $linked_ids = array_unique( $linked_ids ); - - $terms = get_terms( - array( - 'include' => $linked_ids, - 'taxonomy' => $taxonomies, - 'hide_empty' => false, - ) - ); - - return is_array( $terms ) ? $terms : array(); - } - - /** - * Returns all the term IDs linked in a post. - * - * @since 3.3 - * - * @param WP_Post $post A given WP_Post object. - * @return int[] An array of term IDs. - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_post( WP_Post $post ) { - if ( isset( $this->processed_posts[ $post->ID ] ) ) { - return array(); - } - - $this->processed_posts[ $post->ID ] = $post->ID; - - $linked_ids = array(); - - if ( has_blocks( $post->post_content ) ) { - $linked_ids = $this->get_term_ids_from_block_content( $post->post_content ); - } - - /** - * Filters the term IDs linked in a post. - * - * @since 3.3 - * - * @param int[] $linked_ids Term IDs linked in a post. - * @param WP_Post $post The post we get term IDs from. - */ - $linked_ids = apply_filters( 'pll_collect_term_ids', $linked_ids, $post ); - - /** @phpstan-var array, positive-int> */ - return array_unique( $linked_ids ); - } - - /** - * Returns the term IDs from block type content. - * - * @since 3.3 - * - * @param string $post_content The content of the post. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_block_content( $post_content ) { - return $this->get_term_ids_from_blocks( parse_blocks( $post_content ) ); - } - - /** - * Returns the term IDs from blocks. - * - * @since 3.3 - * - * @param array[] $blocks An array of blocks. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_blocks( array $blocks ) { - $term_ids = array(); - - foreach ( $blocks as $block ) { - $term_ids = array_merge( $term_ids, $this->get_term_ids_from_block( $block ) ); - } - - return array_unique( $term_ids ); - } - - /** - * Returns the term IDs from a block. - * - * @since 3.3 - * - * @param array $block A representative array of a block. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_block( array $block ) { - $term_ids = array(); - - switch ( $block['blockName'] ) { - case 'core/block': - $term_ids = array_merge( $term_ids, $this->get_term_ids_from_reusable_block( $block ) ); - break; - - case 'core/latest-posts': - $term_ids = array_merge( $term_ids, $this->get_term_ids_from_latest_posts_block( $block ) ); - break; - - case 'core/query': - $term_ids = array_merge( $term_ids, $this->get_term_ids_from_query_block( $block ) ); - break; - } - - if ( ! empty( $block['innerBlocks'] ) ) { - $term_ids = array_merge( $term_ids, $this->get_term_ids_from_blocks( $block['innerBlocks'] ) ); - } - - return array_unique( $term_ids ); - } - - /** - * Returns the term IDs from a reusable block. - * - * @since 3.3 - * - * @param array $block A representative array of a block. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_reusable_block( array $block ) { - if ( empty( $block['attrs']['ref'] ) || ! is_int( $block['attrs']['ref'] ) ) { - return array(); - } - - $post_id = $block['attrs']['ref']; - - if ( isset( $this->processed_posts[ $post_id ] ) ) { - return array(); - } - - $this->processed_posts[ $post_id ] = $post_id; - - $linked_post = get_post( $post_id ); - - if ( ! $linked_post instanceof WP_Post ) { - return array(); - } - - return $this->get_term_ids_from_block_content( $linked_post->post_content ); - } - - /** - * Returns the term IDs from a latest posts block. - * - * @since 3.3 - * - * @param array $block A representative array of a block. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_latest_posts_block( array $block ) { - if ( empty( $block['attrs']['categories'] ) || ! is_array( $block['attrs']['categories'] ) ) { - return array(); - } - - /** - * The terms are available like this: - * array( - * 'categories' => array( - * 0 => array( - * 'id' => 12, - * // ... - * ), - * 1 => array( - * 'id' => 16, - * // ... - * ), - * ), - * ) - */ - /** @phpstan-var array, positive-int> */ - return array_column( $block['attrs']['categories'], 'id' ); - } - - /** - * Returns the term IDs from a query block. - * - * @since 3.3 - * - * @param array $block A representative array of a block. - * @return int[] An array of term IDs. - * - * @phpstan-return array, positive-int> - */ - protected function get_term_ids_from_query_block( array $block ) { - if ( empty( $block['attrs']['query']['taxQuery'] ) || ! is_array( $block['attrs']['query']['taxQuery'] ) ) { - return array(); - } - - /** - * The terms are available like this: - * array( - * // ... - * 'query' => array( - * // ... - * 'taxQuery' => array( - * 'post_tag' => array( - * 0 => 261, - * ), - * 'category' => array( - * 0 => 12, - * ), - * ), - * ), - * ) - */ - /** @phpstan-var array, positive-int> */ - return array_merge( ...array_values( $block['attrs']['query']['taxQuery'] ) ); - } - - /** - * Gets the parent terms of all terms linked to posts. - * - * @since 3.7 - * - * @param WP_Term[] $terms An array of terms. - * @return array The array of terms parents, if any. - */ - protected function get_terms_parents( array $terms ): array { - $term_parents_ids = array(); - foreach ( $terms as $term ) { - if ( empty( $term->parent ) ) { - continue; - } - - $term_parents = get_ancestors( $term->term_id, $term->taxonomy ); - if ( empty( $term_parents ) ) { - continue; - } - - $term_parents_ids = array_merge( $term_parents_ids, $term_parents ); - } - - if ( empty( $term_parents_ids ) ) { - return array(); - } - - $terms_parents = get_terms( - array( - 'include' => $term_parents_ids, - 'hide_empty' => false, - ) - ); - - return is_array( $terms_parents ) ? $terms_parents : array(); - } +class PLL_Collect_Linked_Terms +{ + /** + * Stores the ID's of the posts that have been parsed already. + * The idea is to prevent processing the same posts several times via the reusable block, or worse, infinite loops. + * + * @var int[] + * + * @phpstan-var array + */ + protected $processed_posts = []; + + /** + * Gets all the term objects linked to a set of posts. + * + * @since 3.3 + * + * @param WP_Post[] $posts An array of post objects. + * @param string[] $taxonomies Optional. Terms will be limited to the given taxonomies. + * + * @return WP_Term[] An array of linked term objects. + */ + public function get_linked_terms(array $posts, array $taxonomies = []) + { + $terms = $this->get_terms_assigned_to_posts($posts, $taxonomies); + + $terms = array_merge($terms, $this->get_terms_from_posts($posts, $taxonomies)); + + return array_merge($terms, $this->get_terms_parents($terms)); + } + + /** + * Gets terms assigned to linked posts. + * + * @since 3.7 + * + * @param WP_Post[] $posts An array of post objects. + * @param string[] $taxonomies Terms will be limited to the given taxonomies. + * + * @return WP_Term[] An array of assigned term objects. + */ + protected function get_terms_assigned_to_posts(array $posts, array $taxonomies): array + { + $post_ids = wp_list_pluck($posts, 'ID'); + $post_terms = wp_get_object_terms($post_ids, $taxonomies); + + return is_array($post_terms) ? $post_terms : []; + } + + /** + * Gets all the term objects from a set of posts content. + * + * @since 3.7 + * + * @param WP_Post[] $posts An array of post objects. + * @param string[] $taxonomies Terms will be limited to the given taxonomies. + * + * @return WP_Term[] An array of linked term objects. + */ + protected function get_terms_from_posts(array $posts, array $taxonomies): array + { + $this->processed_posts = []; + $linked_ids = []; + + foreach ($posts as $post) { + $linked_ids = array_merge($linked_ids, $this->get_term_ids_from_post($post)); + } + + $this->processed_posts = []; + + if (empty($linked_ids)) { + return []; + } + + $linked_ids = array_unique($linked_ids); + + $terms = get_terms( + [ + 'include' => $linked_ids, + 'taxonomy' => $taxonomies, + 'hide_empty' => false, + ] + ); + + return is_array($terms) ? $terms : []; + } + + /** + * Returns all the term IDs linked in a post. + * + * @since 3.3 + * + * @param WP_Post $post A given WP_Post object. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_post(WP_Post $post) + { + if (isset($this->processed_posts[$post->ID])) { + return []; + } + + $this->processed_posts[$post->ID] = $post->ID; + + $linked_ids = []; + + if (has_blocks($post->post_content)) { + $linked_ids = $this->get_term_ids_from_block_content($post->post_content); + } + + /** + * Filters the term IDs linked in a post. + * + * @since 3.3 + * + * @param int[] $linked_ids Term IDs linked in a post. + * @param WP_Post $post The post we get term IDs from. + */ + $linked_ids = apply_filters('pll_collect_term_ids', $linked_ids, $post); + + /** @phpstan-var array, positive-int> */ + return array_unique($linked_ids); + } + + /** + * Returns the term IDs from block type content. + * + * @since 3.3 + * + * @param string $post_content The content of the post. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_block_content($post_content) + { + return $this->get_term_ids_from_blocks(parse_blocks($post_content)); + } + + /** + * Returns the term IDs from blocks. + * + * @since 3.3 + * + * @param array[] $blocks An array of blocks. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_blocks(array $blocks) + { + $term_ids = []; + + foreach ($blocks as $block) { + $term_ids = array_merge($term_ids, $this->get_term_ids_from_block($block)); + } + + return array_unique($term_ids); + } + + /** + * Returns the term IDs from a block. + * + * @since 3.3 + * + * @param array $block A representative array of a block. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_block(array $block) + { + $term_ids = []; + + switch ($block['blockName']) { + case 'core/block': + $term_ids = array_merge($term_ids, $this->get_term_ids_from_reusable_block($block)); + break; + + case 'core/latest-posts': + $term_ids = array_merge($term_ids, $this->get_term_ids_from_latest_posts_block($block)); + break; + + case 'core/query': + $term_ids = array_merge($term_ids, $this->get_term_ids_from_query_block($block)); + break; + } + + if (!empty($block['innerBlocks'])) { + $term_ids = array_merge($term_ids, $this->get_term_ids_from_blocks($block['innerBlocks'])); + } + + return array_unique($term_ids); + } + + /** + * Returns the term IDs from a reusable block. + * + * @since 3.3 + * + * @param array $block A representative array of a block. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_reusable_block(array $block) + { + if (empty($block['attrs']['ref']) || !is_int($block['attrs']['ref'])) { + return []; + } + + $post_id = $block['attrs']['ref']; + + if (isset($this->processed_posts[$post_id])) { + return []; + } + + $this->processed_posts[$post_id] = $post_id; + + $linked_post = get_post($post_id); + + if (!$linked_post instanceof WP_Post) { + return []; + } + + return $this->get_term_ids_from_block_content($linked_post->post_content); + } + + /** + * Returns the term IDs from a latest posts block. + * + * @since 3.3 + * + * @param array $block A representative array of a block. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_latest_posts_block(array $block) + { + if (empty($block['attrs']['categories']) || !is_array($block['attrs']['categories'])) { + return []; + } + + /** + * The terms are available like this: + * array( + * 'categories' => array( + * 0 => array( + * 'id' => 12, + * // ... + * ), + * 1 => array( + * 'id' => 16, + * // ... + * ), + * ), + * ) + */ + /** @phpstan-var array, positive-int> */ + return array_column($block['attrs']['categories'], 'id'); + } + + /** + * Returns the term IDs from a query block. + * + * @since 3.3 + * + * @param array $block A representative array of a block. + * + * @return int[] An array of term IDs. + * + * @phpstan-return array, positive-int> + */ + protected function get_term_ids_from_query_block(array $block) + { + if (empty($block['attrs']['query']['taxQuery']) || !is_array($block['attrs']['query']['taxQuery'])) { + return []; + } + + /** + * The terms are available like this: + * array( + * // ... + * 'query' => array( + * // ... + * 'taxQuery' => array( + * 'post_tag' => array( + * 0 => 261, + * ), + * 'category' => array( + * 0 => 12, + * ), + * ), + * ), + * ) + */ + /** @phpstan-var array, positive-int> */ + return array_merge(...array_values($block['attrs']['query']['taxQuery'])); + } + + /** + * Gets the parent terms of all terms linked to posts. + * + * @since 3.7 + * + * @param WP_Term[] $terms An array of terms. + * + * @return array The array of terms parents, if any. + */ + protected function get_terms_parents(array $terms): array + { + $term_parents_ids = []; + foreach ($terms as $term) { + if (empty($term->parent)) { + continue; + } + + $term_parents = get_ancestors($term->term_id, $term->taxonomy); + if (empty($term_parents)) { + continue; + } + + $term_parents_ids = array_merge($term_parents_ids, $term_parents); + } + + if (empty($term_parents_ids)) { + return []; + } + + $terms_parents = get_terms( + [ + 'include' => $term_parents_ids, + 'hide_empty' => false, + ] + ); + + return is_array($terms_parents) ? $terms_parents : []; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/context.php b/__plugins/polylang-pro-3.7.6/services/context.php index b542092b0021b0cf015442f2f6e3edc6e3034193..459966b19262b27c499e176ccb298a8194e995a7 100644 --- a/__plugins/polylang-pro-3.7.6/services/context.php +++ b/__plugins/polylang-pro-3.7.6/services/context.php @@ -1,7 +1,4 @@ - */ - public static function to_string( array $context ): string { - $context = array_merge( static::get_default(), $context ); - $context = wp_json_encode( $context ); + /** + * Returns stringified context suitable for `Translation_entry`. + * + * @since 3.6 + * + * @param array $context Array of data to convert to string for `Translation_entry` context. + * + * @return string String usable with `Translation_entry`. + * + * @phpstan-param $context array<'encoding'|'field'|'id', string> + */ + public static function to_string(array $context): string + { + $context = array_merge(static::get_default(), $context); + $context = wp_json_encode($context); - return false === $context ? '' : $context; - } + return false === $context ? '' : $context; + } - /** - * Returns array of data from a `Translation_entry` context. - * - * @since 3.6 - * - * @param string $context Raw context from a `Translation_Entry`. - * @return array Extracted array of data. - * - * @phpstan-return array<'encoding'|'field'|'id', string> - */ - public static function to_array( string $context ): array { - $context = json_decode( $context, true ); - $context = is_array( $context ) ? $context : array(); - $context = array_filter( $context, 'is_string' ); - $default = static::get_default(); + /** + * Returns array of data from a `Translation_entry` context. + * + * @since 3.6 + * + * @param string $context Raw context from a `Translation_Entry`. + * + * @return array Extracted array of data. + * + * @phpstan-return array<'encoding'|'field'|'id', string> + */ + public static function to_array(string $context): array + { + $context = json_decode($context, true); + $context = is_array($context) ? $context : []; + $context = array_filter($context, 'is_string'); + $default = static::get_default(); - return array_merge( $default, array_intersect_key( $context, $default ) ); - } + return array_merge($default, array_intersect_key($context, $default)); + } - /** - * Returns allowed context keys with empty values. - * - * @since 3.6 - * - * @return array Context array keys with empty values. - * - * @phpstan-return array<'encoding'|'field'|'id', string> - */ - public static function get_default(): array { - return array( - self::FIELD => '', - self::ID => '', - self::ENCODING => '', - ); - } + /** + * Returns allowed context keys with empty values. + * + * @since 3.6 + * + * @return array Context array keys with empty values. + * + * @phpstan-return array<'encoding'|'field'|'id', string> + */ + public static function get_default(): array + { + return [ + self::FIELD => '', + self::ID => '', + self::ENCODING => '', + ]; + } - /** - * Returns field context. - * - * @since 3.6 - * - * @param Translation_Entry $entry Entry to get field from. - * @return string - */ - public static function get_field( Translation_Entry $entry ): string { - $context = self::to_array( $entry->context ); + /** + * Returns field context. + * + * @since 3.6 + * + * @param Translation_Entry $entry Entry to get field from. + * + * @return string + */ + public static function get_field(Translation_Entry $entry): string + { + $context = self::to_array($entry->context); - return $context[ static::FIELD ]; - } + return $context[static::FIELD]; + } - /** - * Returns id context. - * - * @since 3.6 - * - * @param Translation_Entry $entry Entry to get id from. - * @return string - */ - public static function get_id( Translation_Entry $entry ): string { - $context = self::to_array( $entry->context ); + /** + * Returns id context. + * + * @since 3.6 + * + * @param Translation_Entry $entry Entry to get id from. + * + * @return string + */ + public static function get_id(Translation_Entry $entry): string + { + $context = self::to_array($entry->context); - return $context[ static::ID ]; - } + return $context[static::ID]; + } - /** - * Returns encoding context. - * - * @since 3.6 - * - * @param Translation_Entry $entry Entry to get encoding from. - * @return string - */ - public static function get_encoding( Translation_Entry $entry ): string { - $context = self::to_array( $entry->context ); + /** + * Returns encoding context. + * + * @since 3.6 + * + * @param Translation_Entry $entry Entry to get encoding from. + * + * @return string + */ + public static function get_encoding(Translation_Entry $entry): string + { + $context = self::to_array($entry->context); - return $context[ static::ENCODING ]; - } + return $context[static::ENCODING]; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/data-encoding.php b/__plugins/polylang-pro-3.7.6/services/data-encoding.php index 5027e275c3bcf1916751c8dd93d706c9ebbb45f9..57feffba007e61393e06686fbd57780e68fdc331 100644 --- a/__plugins/polylang-pro-3.7.6/services/data-encoding.php +++ b/__plugins/polylang-pro-3.7.6/services/data-encoding.php @@ -1,7 +1,4 @@ format = ! empty( $format ) ? $format : 'serialize'; - } - - /** - * Tells if the current format is `serialize`, which is what WP uses by default for the metas. - * - * @since 3.6 - * - * @return bool - */ - public function use_serialize(): bool { - return 'serialize' === $this->format; - } - - /** - * Decodes the given data. - * Returns a `WP_Error` object upon decoding failure. - * - * @since 3.6 - * - * @param mixed $data Data. - * @return mixed|WP_Error Decoded data. A `WP_Error` object upon decoding failure. - */ - public function decode( $data ) { - switch ( $this->format ) { - case 'json': - return $this->decode_from_json( $data ); - - case 'serialize': - return maybe_unserialize( $data ); - - default: - return new WP_Error( 'pll-decode-unknown-format', 'Unknown format.' ); - } - } - - /** - * Decodes the given data. - * The data is passed by reference and the method returns a `WP_Error` object upon decoding failure. - * - * @since 3.6 - * - * @param mixed $data Data, passed by reference. - * @return WP_Error - */ - public function decode_reference( &$data ): WP_Error { - $value = $this->decode( $data ); - - if ( is_wp_error( $value ) ) { - return $value; - } - - $data = $value; - return new WP_Error(); - } - - /** - * Encodes the given data. - * Returns a `WP_Error` object upon encoding failure. - * - * @since 3.6 - * - * @param mixed $data Data. - * @return mixed Decoded data. A `WP_Error` object upon encoding failure. - */ - public function encode( $data ) { - switch ( $this->format ) { - case 'json': - return $this->encode_to_json( $data ); - - case 'serialize': - return maybe_serialize( $data ); - - default: - return new WP_Error( 'pll-encode-unknown-format', 'Unknown format.' ); - } - } - - /** - * Encodes the given data. - * The data is passed by reference and the method returns a `WP_Error` object upon encoding failure. - * - * @since 3.6 - * - * @param mixed $data Data, passed by reference. - * @return WP_Error - */ - public function encode_reference( &$data ): WP_Error { - $value = $this->encode( $data ); - - if ( is_wp_error( $value ) ) { - return $value; - } - - $data = $value; - return new WP_Error(); - } - - /** - * Decodes the given JSON data. - * - * @since 3.6 - * - * @param mixed $data Data. - * @return mixed|WP_Error Decoded data. A `WP_Error` object upon decoding failure. - */ - private function decode_from_json( $data ) { - if ( ! is_string( $data ) ) { - return new WP_Error( 'pll-json-not-a-string', 'Value is not a string.' ); - } - - $decoded = json_decode( $data, true ); - - if ( json_last_error() !== JSON_ERROR_NONE ) { - return new WP_Error( 'pll-json-decoding-error', json_last_error_msg() ); - } - - return $decoded; - } - - /** - * Encodes the given data to JSON. - * - * @since 3.6 - * - * @param mixed $data Data. - * @return string|WP_Error Encoded data. A `WP_Error` object upon decoding failure. - */ - private function encode_to_json( $data ) { - $encoded = wp_json_encode( $data, JSON_PRESERVE_ZERO_FRACTION ); // Cannot trigger an Exception since we use the default value for `$depth`. - - if ( ! is_string( $encoded ) ) { - return new WP_Error( 'pll-json-encoding-error', json_last_error_msg() ); - } - - return $encoded; - } +class PLL_Data_Encoding +{ + /** + * Encoding format. + * + * @var string + */ + private $format; + + /** + * Constructor. + * + * @since 3.6 + * + * @param string $format Encoding format. Possible values are: + * - An empty string: to (un)serialize. + * - `serialize`. + * - `json`. + * Default is an empty string. + */ + public function __construct(string $format = '') + { + $this->format = !empty($format) ? $format : 'serialize'; + } + + /** + * Tells if the current format is `serialize`, which is what WP uses by default for the metas. + * + * @since 3.6 + * + * @return bool + */ + public function use_serialize(): bool + { + return 'serialize' === $this->format; + } + + /** + * Decodes the given data. + * Returns a `WP_Error` object upon decoding failure. + * + * @since 3.6 + * + * @param mixed $data Data. + * + * @return mixed|WP_Error Decoded data. A `WP_Error` object upon decoding failure. + */ + public function decode($data) + { + switch ($this->format) { + case 'json': + return $this->decode_from_json($data); + + case 'serialize': + return maybe_unserialize($data); + + default: + return new WP_Error('pll-decode-unknown-format', 'Unknown format.'); + } + } + + /** + * Decodes the given data. + * The data is passed by reference and the method returns a `WP_Error` object upon decoding failure. + * + * @since 3.6 + * + * @param mixed $data Data, passed by reference. + * + * @return WP_Error + */ + public function decode_reference(&$data): WP_Error + { + $value = $this->decode($data); + + if (is_wp_error($value)) { + return $value; + } + + $data = $value; + + return new WP_Error(); + } + + /** + * Encodes the given data. + * Returns a `WP_Error` object upon encoding failure. + * + * @since 3.6 + * + * @param mixed $data Data. + * + * @return mixed Decoded data. A `WP_Error` object upon encoding failure. + */ + public function encode($data) + { + switch ($this->format) { + case 'json': + return $this->encode_to_json($data); + + case 'serialize': + return maybe_serialize($data); + + default: + return new WP_Error('pll-encode-unknown-format', 'Unknown format.'); + } + } + + /** + * Encodes the given data. + * The data is passed by reference and the method returns a `WP_Error` object upon encoding failure. + * + * @since 3.6 + * + * @param mixed $data Data, passed by reference. + * + * @return WP_Error + */ + public function encode_reference(&$data): WP_Error + { + $value = $this->encode($data); + + if (is_wp_error($value)) { + return $value; + } + + $data = $value; + + return new WP_Error(); + } + + /** + * Decodes the given JSON data. + * + * @since 3.6 + * + * @param mixed $data Data. + * + * @return mixed|WP_Error Decoded data. A `WP_Error` object upon decoding failure. + */ + private function decode_from_json($data) + { + if (!is_string($data)) { + return new WP_Error('pll-json-not-a-string', 'Value is not a string.'); + } + + $decoded = json_decode($data, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return new WP_Error('pll-json-decoding-error', json_last_error_msg()); + } + + return $decoded; + } + + /** + * Encodes the given data to JSON. + * + * @since 3.6 + * + * @param mixed $data Data. + * + * @return string|WP_Error Encoded data. A `WP_Error` object upon decoding failure. + */ + private function encode_to_json($data) + { + $encoded = wp_json_encode($data, JSON_PRESERVE_ZERO_FRACTION); // Cannot trigger an Exception since we use the default value for `$depth`. + + if (!is_string($encoded)) { + return new WP_Error('pll-json-encoding-error', json_last_error_msg()); + } + + return $encoded; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/dom/dom-content.php b/__plugins/polylang-pro-3.7.6/services/dom/dom-content.php index dea16283c41014e1041b39936a3ac533d89432c5..2f1b9c8d2d831b86dbeeeee377349a2f9d0801ec 100644 --- a/__plugins/polylang-pro-3.7.6/services/dom/dom-content.php +++ b/__plugins/polylang-pro-3.7.6/services/dom/dom-content.php @@ -1,249 +1,256 @@ content = $content; - $this->charset = get_bloginfo( 'charset' ); - $this->charset = is_string( $this->charset ) && ! empty( $this->charset ) ? $this->charset : 'UTF-8'; - $this->placeholder = substr( uniqid( 'pll_' ), 0, 10 ); - $this->placeholder = "@@{$this->placeholder}-%d@@"; - } - - /** - * Extracts strings from content, given a list of parsing rules. - * - * @since 3.3 - * - * @uses DOMXPath - * - * @param string[] $rules Parsing rules. Ex: `[ '//figure/img/@alt' ]`. - * @return string[] Path to matching nodes as array keys, extracted strings as array values. - * Ex: `[ '/figure/img[1]/@alt' => 'Image alt text.' ]`. - * - * @phpstan-return array - */ - public function get_strings( array $rules ) { - if ( empty( $rules ) ) { - return array(); - } - - $matched_parts = array(); - $document = PLL_DOM_Document::from_html( $this->content ); - $xpath = new DOMXPath( $document ); - - foreach ( $rules as $parsing_rule ) { - $node_list = $xpath->query( $parsing_rule ); - - if ( empty( $node_list ) ) { - // Error. - continue; - } - - foreach ( $node_list as $node ) { - if ( ! $node instanceof DOMNode ) { - continue; - } - - $node_path = $node->getNodePath(); - - if ( ! is_string( $node_path ) ) { - // Trouble. - continue; - } - - $node_content = $this->get_node_content( $node ); - - if ( '' === $node_content ) { - continue; - } - - $node_path = preg_replace( '@/text\(\)$@', '', $node_path ); - - $matched_parts[ $node_path ] = $node_content; - } - } - - return $matched_parts; - } - - /** - * Replaces strings in the content, given a list of parsing rules. - * - * @since 3.3 - * - * @uses DOMXPath - * - * @param string[] $new_strings Path to matching nodes as array keys, new strings as array values. - * Ex: `[ '/figure/img[1]/@alt' => 'New image alt text.' ]`. - * @return string Content with replaced strings. - * - * @phpstan-param array $new_strings - */ - public function replace_content( array $new_strings ) { - $document = PLL_DOM_Document::from_html( $this->content ); - $xpath = new DOMXPath( $document ); - $node_values = array(); - $incr = 0; - - foreach ( $new_strings as $node_path => $new_string ) { - $node_list = $xpath->query( $node_path ); - - if ( ! $node_list instanceof DOMNodeList ) { - // Error. - continue; - } - - // Each node path corresponds to only one node: get the first node then. - $node = $this->get_first_dom_node( $node_list ); - - if ( ! $node instanceof DOMNode ) { - continue; - } - - $node_placeholder = sprintf( $this->placeholder, ++$incr ); - $node_values[ $node_placeholder ] = html_entity_decode( $new_string, ENT_QUOTES, $this->charset ); - - if ( $node instanceof DOMAttr ) { - /** - * Tag attribute. - * Escape the value and insert the placeholder. - */ - $node_values[ $node_placeholder ] = esc_attr( $node_values[ $node_placeholder ] ); - $node->nodeValue = $node_placeholder; - continue; - } - - /** - * Tag content. - */ - if ( ! $node->ownerDocument instanceof DOMDocument ) { - // Trouble. - continue; - } - - // 1- Remove all child nodes. - while ( $node->hasChildNodes() ) { - if ( ! empty( $node->firstChild ) ) { - $node->removeChild( $node->firstChild ); - } - } - - // 2- Insert a placeholder node. - $node_values[ $node_placeholder ] = wp_kses_post( $node_values[ $node_placeholder ] ); - $node->appendChild( $node->ownerDocument->createTextNode( $node_placeholder ) ); - } - - $content = $document->saveHTML(); - - if ( is_string( $content ) && '' !== trim( $content ) ) { - // Decode entities, then put back the translated texts. - $this->content = html_entity_decode( $content, ENT_QUOTES, $this->charset ); - $this->content = str_replace( array_keys( $node_values ), $node_values, $this->content ); - } - - return $this->content; - } - - /** - * Returns the first `DOMNode` element from a `DOMNodeList`. - * - * @since 3.3 - * - * @param DOMNodeList $node_list A `DOMNodeList` element. - * @return DOMNode|null A `DOMNode` element, or null if no elements have been found. - */ - private function get_first_dom_node( DOMNodeList $node_list ) { - foreach ( $node_list as $node ) { - if ( $node instanceof DOMNode ) { - return $node; - } - } - - return null; - } - - /** - * Returns a node's content. - * If the node is an attribute, the attribute's value is returned. - * If the node is a tag, the tag's HTML is returned. - * - * @since 3.3 - * - * @param DOMNode $node A node. - * @return string - */ - private function get_node_content( DOMNode $node ) { - if ( $node instanceof DOMAttr || ! $node->hasChildNodes() ) { - // Tag attribute. - if ( ! is_string( $node->nodeValue ) ) { - return ''; - } - - return trim( html_entity_decode( $node->nodeValue, ENT_QUOTES, $this->charset ) ); - } - - // Tag content. - if ( ! $node->ownerDocument instanceof DOMDocument ) { - // Trouble. - return ''; - } - - $content = ''; - - foreach ( iterator_to_array( $node->childNodes ) as $node ) { - if ( ! $node instanceof DOMNode || ! $node->ownerDocument instanceof DOMDocument ) { - // Don't return partial content: if there is at least 1 error, return an empty content. - return ''; - } - - $node_content = $node->ownerDocument->saveHTML( $node ); - - if ( ! is_string( $node_content ) ) { - // Don't return partial content: if there is at least 1 error, return an empty content. - return ''; - } - - $content .= $node_content; - } - - return trim( html_entity_decode( $content, ENT_QUOTES, $this->charset ) ); - } +class PLL_DOM_Content +{ + /** + * The content to work with. + * + * @var string + */ + private $content; + + /** + * The site's charset. + * + * @var string + */ + private $charset; + + /** + * Placeholder used in {@see PLL_DOM_Content::replace_content()}. + * + * @var string + * + * @phpstan-var non-empty-string + */ + private $placeholder; + + /** + * Constructor. + * + * @since 3.3 + * + * @param string $content The content to work with. + */ + public function __construct($content) + { + $this->content = $content; + $this->charset = get_bloginfo('charset'); + $this->charset = is_string($this->charset) && !empty($this->charset) ? $this->charset : 'UTF-8'; + $this->placeholder = substr(uniqid('pll_'), 0, 10); + $this->placeholder = "@@{$this->placeholder}-%d@@"; + } + + /** + * Extracts strings from content, given a list of parsing rules. + * + * @since 3.3 + * + * @uses DOMXPath + * + * @param string[] $rules Parsing rules. Ex: `[ '//figure/img/@alt' ]`. + * + * @return string[] Path to matching nodes as array keys, extracted strings as array values. + * Ex: `[ '/figure/img[1]/@alt' => 'Image alt text.' ]`. + * + * @phpstan-return array + */ + public function get_strings(array $rules) + { + if (empty($rules)) { + return []; + } + + $matched_parts = []; + $document = PLL_DOM_Document::from_html($this->content); + $xpath = new DOMXPath($document); + + foreach ($rules as $parsing_rule) { + $node_list = $xpath->query($parsing_rule); + + if (empty($node_list)) { + // Error. + continue; + } + + foreach ($node_list as $node) { + if (!$node instanceof DOMNode) { + continue; + } + + $node_path = $node->getNodePath(); + + if (!is_string($node_path)) { + // Trouble. + continue; + } + + $node_content = $this->get_node_content($node); + + if ('' === $node_content) { + continue; + } + + $node_path = preg_replace('@/text\(\)$@', '', $node_path); + + $matched_parts[$node_path] = $node_content; + } + } + + return $matched_parts; + } + + /** + * Replaces strings in the content, given a list of parsing rules. + * + * @since 3.3 + * + * @uses DOMXPath + * + * @param string[] $new_strings Path to matching nodes as array keys, new strings as array values. + * Ex: `[ '/figure/img[1]/@alt' => 'New image alt text.' ]`. + * + * @return string Content with replaced strings. + * + * @phpstan-param array $new_strings + */ + public function replace_content(array $new_strings) + { + $document = PLL_DOM_Document::from_html($this->content); + $xpath = new DOMXPath($document); + $node_values = []; + $incr = 0; + + foreach ($new_strings as $node_path => $new_string) { + $node_list = $xpath->query($node_path); + + if (!$node_list instanceof DOMNodeList) { + // Error. + continue; + } + + // Each node path corresponds to only one node: get the first node then. + $node = $this->get_first_dom_node($node_list); + + if (!$node instanceof DOMNode) { + continue; + } + + $node_placeholder = sprintf($this->placeholder, ++$incr); + $node_values[$node_placeholder] = html_entity_decode($new_string, ENT_QUOTES, $this->charset); + + if ($node instanceof DOMAttr) { + /** + * Tag attribute. + * Escape the value and insert the placeholder. + */ + $node_values[$node_placeholder] = esc_attr($node_values[$node_placeholder]); + $node->nodeValue = $node_placeholder; + continue; + } + + /** + * Tag content. + */ + if (!$node->ownerDocument instanceof DOMDocument) { + // Trouble. + continue; + } + + // 1- Remove all child nodes. + while ($node->hasChildNodes()) { + if (!empty($node->firstChild)) { + $node->removeChild($node->firstChild); + } + } + + // 2- Insert a placeholder node. + $node_values[$node_placeholder] = wp_kses_post($node_values[$node_placeholder]); + $node->appendChild($node->ownerDocument->createTextNode($node_placeholder)); + } + + $content = $document->saveHTML(); + + if (is_string($content) && '' !== trim($content)) { + // Decode entities, then put back the translated texts. + $this->content = html_entity_decode($content, ENT_QUOTES, $this->charset); + $this->content = str_replace(array_keys($node_values), $node_values, $this->content); + } + + return $this->content; + } + + /** + * Returns the first `DOMNode` element from a `DOMNodeList`. + * + * @since 3.3 + * + * @param DOMNodeList $node_list A `DOMNodeList` element. + * + * @return DOMNode|null A `DOMNode` element, or null if no elements have been found. + */ + private function get_first_dom_node(DOMNodeList $node_list) + { + foreach ($node_list as $node) { + if ($node instanceof DOMNode) { + return $node; + } + } + + return null; + } + + /** + * Returns a node's content. + * If the node is an attribute, the attribute's value is returned. + * If the node is a tag, the tag's HTML is returned. + * + * @since 3.3 + * + * @param DOMNode $node A node. + * + * @return string + */ + private function get_node_content(DOMNode $node) + { + if ($node instanceof DOMAttr || !$node->hasChildNodes()) { + // Tag attribute. + if (!is_string($node->nodeValue)) { + return ''; + } + + return trim(html_entity_decode($node->nodeValue, ENT_QUOTES, $this->charset)); + } + + // Tag content. + if (!$node->ownerDocument instanceof DOMDocument) { + // Trouble. + return ''; + } + + $content = ''; + + foreach (iterator_to_array($node->childNodes) as $node) { + if (!$node instanceof DOMNode || !$node->ownerDocument instanceof DOMDocument) { + // Don't return partial content: if there is at least 1 error, return an empty content. + return ''; + } + + $node_content = $node->ownerDocument->saveHTML($node); + + if (!is_string($node_content)) { + // Don't return partial content: if there is at least 1 error, return an empty content. + return ''; + } + + $content .= $node_content; + } + + return trim(html_entity_decode($content, ENT_QUOTES, $this->charset)); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/dom/dom-document.php b/__plugins/polylang-pro-3.7.6/services/dom/dom-document.php index a61a0d100444948754563cf0c64e81b5e36011bd..b110a53c2fe596aa4dd7a5276706ceced7304224 100644 --- a/__plugins/polylang-pro-3.7.6/services/dom/dom-document.php +++ b/__plugins/polylang-pro-3.7.6/services/dom/dom-document.php @@ -1,203 +1,213 @@ preserveWhiteSpace = false; - $document->formatOutput = true; - - return self::from_string( $xml, $document, array( $document, 'loadXML' ), $flags ); - } - - /** - * Creates a PLL_DOM_Document instance from a HTML string. - * - * @since 3.1 - * @since 3.3 Added parameters $version, $encoding, and $flags. - * @since 3.3 Doesn't format the output anymore. - * @since 3.3 Doesn't add the `` and `` tags by default anymore. - * - * Doctype declaration is disallowed for security reasons (XEE vulnerability). - * - * @param string $html A HTML valid string. - * @param string $version Optional. XML version to use. Default is '1.0'. - * @param string $encoding Optional. Encoding to use. Default is 'UTF-8'. - * @param int $flags Optional. A series of libxml flags to parameterize the loading. - * Default is `LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD`. - * {@link https://www.php.net/manual/en/libxml.constants.php}. - * @return PLL_DOM_Document - */ - public static function from_html( $html, $version = '1.0', $encoding = 'UTF-8', $flags = 0 ) { - $document = new self( $version, $encoding ); - $document->strictErrorChecking = false; - - /* - * Hack to enforce that the string will be processed with the right encoding by DOMDocument. - * The added processing instruction is then removed by contains_not_allowed_node(). - */ - $html = '' . $html; - - $flags = ! empty( $flags ) ? $flags : LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD; - - $document = self::from_string( $html, $document, array( $document, 'loadHTML' ), $flags ); - $document->encoding = $encoding; // Enforce encoding, as it is not set by DOMDocument. - - return $document; - } - - /** - * Factory function to safely generate DOMDocument from strings. - * - * @since 3.1 - * - * Entity loading is disabled to prevent External Entity Injections {@link https://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection}. - * - * @param string $string A XML content to load. - * @param PLL_DOM_Document $document A document parameterized to load the content into. - * @param callable $function Method name which will handle the loading. - * @param int $flags A series of libxml flags to parameterize the loading. {@link https://www.php.net/manual/en/libxml.constants.php}. - * @return PLL_DOM_Document - */ - private static function from_string( $string, $document, $function, $flags = 0 ) { - if ( ! empty( $string ) ) { - // libxml2 version 2.9.0 and superior doesn't load external entities by default. libxml_disable_entity_loader() is deprecated since PHP 8.0.0 . - $internal_errors = libxml_use_internal_errors( true ); - libxml_clear_errors(); - if ( ! defined( 'LIBXML_DOTTED_VERSION' ) || version_compare( LIBXML_DOTTED_VERSION, '2.9.0', '<' ) ) { - $entity_loader = libxml_disable_entity_loader( true ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated - $document = self::safe_load_string( $string, $document, $function, $flags ); - libxml_disable_entity_loader( $entity_loader ); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated - } else { - $document = self::safe_load_string( $string, $document, $function, $flags ); - } - libxml_clear_errors(); - libxml_use_internal_errors( $internal_errors ); - } - - return $document; - } - - /** - * Loads the string into the given document, returns the document if it's safe, or return an empty document with errors. - * - * @since 3.1 - * - * @param string $string A string to be loaded and parsed as the document. - * @param PLL_DOM_Document $document A configured instance of PLL_DOM_Document to load the string into. - * @param callable $function Name of the loading method to use. - * @param int $flags A series of libxml flags to parameterize the loading. {@link https://www.php.net/manual/en/libxml.constants.php}. - * @return PLL_DOM_Document - */ - private static function safe_load_string( $string, $document, $function, $flags = 0 ) { - call_user_func( $function, $string, LIBXML_NONET | $flags ); - if ( $document->contains_not_allowed_node() ) { - $document = new self(); - } - $document->errors = array_merge( $document->errors, libxml_get_errors() ); - - return $document; - } - - /** - * Verifies that the document contains only nodes of allowed types. - * - * @since 3.1 - * - * @see https://www.php.net/manual/en/dom.constants.php. - * - * @return bool - */ - public function contains_not_allowed_node() { - foreach ( $this->childNodes as $node ) { - if ( ! $node instanceof DOMNode || ! in_array( - $node->nodeType, - array( - XML_DOCUMENT_NODE, - XML_ELEMENT_NODE, - XML_ATTRIBUTE_NODE, - XML_TEXT_NODE, - XML_COMMENT_NODE, - XML_CDATA_SECTION_NODE, - XML_PI_NODE, - ) - ) ) { - return true; - } - - if ( XML_PI_NODE === $node->nodeType ) { - $this->removeChild( $node ); // Remove our hacky element. - * - * @return DOMNodeList - */ - public function get_first_level_html_nodes() { - $body = $this->getElementsByTagName( 'body' )->item( 0 ); - - return null !== $body ? $body->childNodes : new DOMNodeList(); - } - - /** - * Whether the document contains errors or not - * - * @since 3.1 - * - * @return bool - */ - public function has_errors() { - return ! empty( $this->errors ); - } - - /** - * Returns the document's errors. - * - * @since 3.3 - * - * @return LibXMLError[] - */ - public function get_errors() { - return $this->errors; - } +class PLL_DOM_Document extends DOMDocument +{ + /** + * Store the errors that happened during the loading process. + * + * @since 3.1 + * + * @var array + */ + private $errors = []; + + /** + * Creates a PLL_DOM_Document instance from a XML string. + * + * @since 3.1 + * @since 3.3 Added parameter $flags. + * + * @param string $xml A XML valid string. + * @param string $version Optional. XML version to use. Default is '1.0'. + * @param string $encoding Optional. Encoding to use. Default is 'UTF-8'. + * @param int $flags Optional. A series of libxml flags to parameterize the loading. Default is 0. + * {@link https://www.php.net/manual/en/libxml.constants.php}. + * + * @return PLL_DOM_Document + */ + public static function from_xml($xml, $version = '1.0', $encoding = 'UTF-8', $flags = 0) + { + $document = new self($version, $encoding); + $document->preserveWhiteSpace = false; + $document->formatOutput = true; + + return self::from_string($xml, $document, [$document, 'loadXML'], $flags); + } + + /** + * Creates a PLL_DOM_Document instance from a HTML string. + * + * @since 3.1 + * @since 3.3 Added parameters $version, $encoding, and $flags. + * @since 3.3 Doesn't format the output anymore. + * @since 3.3 Doesn't add the `` and `` tags by default anymore. + * + * Doctype declaration is disallowed for security reasons (XEE vulnerability). + * + * @param string $html A HTML valid string. + * @param string $version Optional. XML version to use. Default is '1.0'. + * @param string $encoding Optional. Encoding to use. Default is 'UTF-8'. + * @param int $flags Optional. A series of libxml flags to parameterize the loading. + * Default is `LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD`. + * {@link https://www.php.net/manual/en/libxml.constants.php}. + * + * @return PLL_DOM_Document + */ + public static function from_html($html, $version = '1.0', $encoding = 'UTF-8', $flags = 0) + { + $document = new self($version, $encoding); + $document->strictErrorChecking = false; + + /* + * Hack to enforce that the string will be processed with the right encoding by DOMDocument. + * The added processing instruction is then removed by contains_not_allowed_node(). + */ + $html = ''.$html; + + $flags = !empty($flags) ? $flags : LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD; + + $document = self::from_string($html, $document, [$document, 'loadHTML'], $flags); + $document->encoding = $encoding; // Enforce encoding, as it is not set by DOMDocument. + + return $document; + } + + /** + * Factory function to safely generate DOMDocument from strings. + * + * @since 3.1 + * + * Entity loading is disabled to prevent External Entity Injections {@link https://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection}. + * + * @param string $string A XML content to load. + * @param PLL_DOM_Document $document A document parameterized to load the content into. + * @param callable $function Method name which will handle the loading. + * @param int $flags A series of libxml flags to parameterize the loading. {@link https://www.php.net/manual/en/libxml.constants.php}. + * + * @return PLL_DOM_Document + */ + private static function from_string($string, $document, $function, $flags = 0) + { + if (!empty($string)) { + // libxml2 version 2.9.0 and superior doesn't load external entities by default. libxml_disable_entity_loader() is deprecated since PHP 8.0.0 . + $internal_errors = libxml_use_internal_errors(true); + libxml_clear_errors(); + if (!defined('LIBXML_DOTTED_VERSION') || version_compare(LIBXML_DOTTED_VERSION, '2.9.0', '<')) { + $entity_loader = libxml_disable_entity_loader(true); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated + $document = self::safe_load_string($string, $document, $function, $flags); + libxml_disable_entity_loader($entity_loader); // phpcs:ignore Generic.PHP.DeprecatedFunctions.Deprecated + } else { + $document = self::safe_load_string($string, $document, $function, $flags); + } + libxml_clear_errors(); + libxml_use_internal_errors($internal_errors); + } + + return $document; + } + + /** + * Loads the string into the given document, returns the document if it's safe, or return an empty document with errors. + * + * @since 3.1 + * + * @param string $string A string to be loaded and parsed as the document. + * @param PLL_DOM_Document $document A configured instance of PLL_DOM_Document to load the string into. + * @param callable $function Name of the loading method to use. + * @param int $flags A series of libxml flags to parameterize the loading. {@link https://www.php.net/manual/en/libxml.constants.php}. + * + * @return PLL_DOM_Document + */ + private static function safe_load_string($string, $document, $function, $flags = 0) + { + call_user_func($function, $string, LIBXML_NONET | $flags); + if ($document->contains_not_allowed_node()) { + $document = new self(); + } + $document->errors = array_merge($document->errors, libxml_get_errors()); + + return $document; + } + + /** + * Verifies that the document contains only nodes of allowed types. + * + * @since 3.1 + * @see https://www.php.net/manual/en/dom.constants.php. + * + * @return bool + */ + public function contains_not_allowed_node() + { + foreach ($this->childNodes as $node) { + if (!$node instanceof DOMNode || !in_array( + $node->nodeType, + [ + XML_DOCUMENT_NODE, + XML_ELEMENT_NODE, + XML_ATTRIBUTE_NODE, + XML_TEXT_NODE, + XML_COMMENT_NODE, + XML_CDATA_SECTION_NODE, + XML_PI_NODE, + ] + )) { + return true; + } + + if (XML_PI_NODE === $node->nodeType) { + $this->removeChild($node); // Remove our hacky element. + * + * @return DOMNodeList + */ + public function get_first_level_html_nodes() + { + $body = $this->getElementsByTagName('body')->item(0); + + return null !== $body ? $body->childNodes : new DOMNodeList(); + } + + /** + * Whether the document contains errors or not. + * + * @since 3.1 + * + * @return bool + */ + public function has_errors() + { + return !empty($this->errors); + } + + /** + * Returns the document's errors. + * + * @since 3.3 + * + * @return LibXMLError[] + */ + public function get_errors() + { + return $this->errors; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/dom/dom-nodes-iterator.php b/__plugins/polylang-pro-3.7.6/services/dom/dom-nodes-iterator.php index cd72f6c6c3cb8a805e41281628ba9b38ae927342..9c0705495aac6c67cf21eb48748268485febf92b 100644 --- a/__plugins/polylang-pro-3.7.6/services/dom/dom-nodes-iterator.php +++ b/__plugins/polylang-pro-3.7.6/services/dom/dom-nodes-iterator.php @@ -1,7 +1,6 @@ nodes = $nodes; + } - /** - * The offset. - * - * @var int. - */ - private $offset = 0; + /** + * Rewind the Iterator to the first element. + * + * @since 3.1 + * + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->offset = 0; + } - /** - * Constructor. - * - * @since 3.1 - * - * @param DOMNodeList $nodes Nodes. - */ - public function __construct( DOMNodeList $nodes ) { - $this->nodes = $nodes; - } + /** + * Returns the current element. + * + * @since 3.1 + * + * @return DOMNode|null + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->nodes->item($this->offset); + } - /** - * Rewind the Iterator to the first element. - * - * @since 3.1 - * - * @return void - */ - #[\ReturnTypeWillChange] - public function rewind() { - $this->offset = 0; - } + /** + * Returns the key of the current element. + * Issues `E_NOTICE` on failure. + * + * @since 3.1 + * + * @return string|null Returns anything on success, or null on failure. + */ + #[\ReturnTypeWillChange] + public function key() + { + if ($this->current()) { + return $this->current()->nodeName; + } - /** - * Returns the current element. - * - * @since 3.1 - * - * @return DOMNode|null - */ - #[\ReturnTypeWillChange] - public function current() { - return $this->nodes->item( $this->offset ); - } + return null; + } - /** - * Returns the key of the current element. - * Issues `E_NOTICE` on failure. - * - * @since 3.1 - * - * @return string|null Returns anything on success, or null on failure. - */ - #[\ReturnTypeWillChange] - public function key() { - if ( $this->current() ) { - return $this->current()->nodeName; - } - return null; - } + /** + * Moves forward to next element. + * + * @since 3.1 + * + * @return void + */ + #[\ReturnTypeWillChange] + public function next() + { + $this->offset++; + } - /** - * Moves forward to next element. - * - * @since 3.1 - * - * @return void - */ - #[\ReturnTypeWillChange] - public function next() { - ++$this->offset; - } + /** + * Checks if current position is valid. + * + * @since 3.1 + * + * @return bool + */ + public function valid(): bool + { + return $this->offset < $this->nodes->length; + } - /** - * Checks if current position is valid. - * - * @since 3.1 - * - * @return bool - */ - public function valid(): bool { - return $this->offset < $this->nodes->length; - } + /** + * Returns if an iterator can be created for the current entry. + * + * @since 3.1 + * + * @return bool Returns `true` if the current entry can be iterated over, otherwise returns `false`. + */ + public function hasChildren(): bool + { + return isset($this->current()->childNodes->length) && $this->current()->childNodes->length > 0; + } - /** - * Returns if an iterator can be created for the current entry. - * - * @since 3.1 - * - * @return bool Returns `true` if the current entry can be iterated over, otherwise returns `false`. - */ - public function hasChildren(): bool { - return isset( $this->current()->childNodes->length ) && $this->current()->childNodes->length > 0; - } + /** + * Returns an iterator for the current entry. + * + * @since 3.1 + * + * @return RecursiveIterator|null Returns an iterator for the current entry if it exists, or null otherwise. + */ + #[\ReturnTypeWillChange] + public function getChildren() + { + if ($this->current()) { + return new self($this->current()->childNodes); + } - /** - * Returns an iterator for the current entry. - * - * @since 3.1 - * - * @return RecursiveIterator|null Returns an iterator for the current entry if it exists, or null otherwise. - */ - #[\ReturnTypeWillChange] - public function getChildren() { - if ( $this->current() ) { - return new self( $this->current()->childNodes ); - } - return null; - } + return null; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-container.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-container.php index 5d468061cee9e5b28ff7f272b96423687add2512..7eeae9e02b4a828fcda80a3201462aaad73001d4 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-container.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-container.php @@ -1,7 +1,4 @@ */ -class PLL_Export_Container implements IteratorAggregate, Countable { - - /** - * Name of the class defining an individual export. - * - * @var string - * - * @phpstan-var class-string - */ - private $class_name; +class PLL_Export_Container implements IteratorAggregate, Countable +{ + /** + * Name of the class defining an individual export. + * + * @var string + * + * @phpstan-var class-string + */ + private $class_name; - /** - * Contains all the exports. - * Each export is referenced with a key composed of its source and target languages. - * - * @var PLL_Export_Data[] - * - * @phpstan-var array - */ - private $exports = array(); + /** + * Contains all the exports. + * Each export is referenced with a key composed of its source and target languages. + * + * @var PLL_Export_Data[] + * + * @phpstan-var array + */ + private $exports = []; - /** - * Constructor. - * - * @since 3.6 - * - * @param string $class_name Name of the class that defines an individual export. The class must implement - * the interface `PLL_Export_Data`. - * - * @phpstan-param class-string $class_name - */ - public function __construct( string $class_name ) { - $this->class_name = $class_name; - } + /** + * Constructor. + * + * @since 3.6 + * + * @param string $class_name Name of the class that defines an individual export. The class must implement + * the interface `PLL_Export_Data`. + * + * @phpstan-param class-string $class_name + */ + public function __construct(string $class_name) + { + $this->class_name = $class_name; + } - /** - * Returns an export object for the given source/target languages pair. - * - * @since 3.6 - * - * @param PLL_Language $source_language The export's source language. - * @param PLL_Language $target_language The export's target language. - * @return PLL_Export_Data - */ - public function get( PLL_Language $source_language, PLL_Language $target_language ): PLL_Export_Data { - $export_key = "{$source_language->slug}/{$target_language->slug}"; - $class_name = $this->class_name; + /** + * Returns an export object for the given source/target languages pair. + * + * @since 3.6 + * + * @param PLL_Language $source_language The export's source language. + * @param PLL_Language $target_language The export's target language. + * + * @return PLL_Export_Data + */ + public function get(PLL_Language $source_language, PLL_Language $target_language): PLL_Export_Data + { + $export_key = "{$source_language->slug}/{$target_language->slug}"; + $class_name = $this->class_name; - if ( ! array_key_exists( $export_key, $this->exports ) ) { - $this->exports[ $export_key ] = new $class_name( $source_language, $target_language ); - } + if (!array_key_exists($export_key, $this->exports)) { + $this->exports[$export_key] = new $class_name($source_language, $target_language); + } - return $this->exports[ $export_key ]; - } + return $this->exports[$export_key]; + } - /** - * Returns an exports iterator. - * Needed for the interface `IteratorAggregate`. - * - * @since 3.6 - * - * @return ArrayIterator - * - * @phpstan-return ArrayIterator - */ - public function getIterator(): ArrayIterator { - return new ArrayIterator( $this->exports ); - } + /** + * Returns an exports iterator. + * Needed for the interface `IteratorAggregate`. + * + * @since 3.6 + * + * @return ArrayIterator + * + * @phpstan-return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->exports); + } - /** - * Returns the number of exports. - * Needed for the interface `Countable`. - * - * @since 3.6 - * - * @return int - */ - public function count(): int { - return count( $this->exports ); - } + /** + * Returns the number of exports. + * Needed for the interface `Countable`. + * + * @since 3.6 + * + * @return int + */ + public function count(): int + { + return count($this->exports); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-posts.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-posts.php index 382ae4dfaa9a6c88792ce55eb6ab37061ed2e7f4..84278d89b8af1abc17fdf3ee64397022630edaee 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-posts.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-posts.php @@ -1,150 +1,154 @@ model = $model; - - $this->export_posts = new PLL_Export_Posts( $this->model->post ); - $this->export_terms = new PLL_Export_Terms( $this->model->term ); - $this->collect_posts = new PLL_Collect_Linked_Posts( $this->model->options ); - $this->collect_terms = new PLL_Collect_Linked_Terms(); - } - - /** - * Exports the items with their related ones. - * - * @since 3.6 - * - * @param PLL_Export_Container $container Data to export. - * @param WP_Post[] $posts An array containing `WP_Post` objects. - * @param PLL_Language $target_language The target language of the posts. - * @param array $args { - * Optional. A list of optional arguments. - * - * @type bool $include_translated_items Tells if items that are already translated in the target languages must - * also be exported. This applies only to linked items (like assigned - * terms, items from reusable blocks, etc). Default is false. - * } - * @return void - */ - public function send_to_export( PLL_Export_Container $container, array $posts, PLL_Language $target_language, array $args = array() ) { - $posts_to_export = $this->get_posts_to_export( $posts, $target_language, $args ); - $this->export_posts->add_items( $container, $posts_to_export, $target_language ); - - $terms_to_export = $this->get_terms_to_export( $posts_to_export, $target_language, $args ); - $this->export_terms->add_items( $container, $terms_to_export, $target_language ); - } - - /** - * Gets all posts to be exported. - * - * @since 3.6 - * - * @param WP_Post[] $posts An array of posts. - * @param PLL_Language $lang The target language of the posts. - * @param array $args A list of optional arguments. - * @return WP_Post[] - */ - protected function get_posts_to_export( array $posts, PLL_Language $lang, array $args = array() ): array { - $include_translated_items = ! empty( $args['include_translated_items'] ); - $post_types = $this->model->get_translated_post_types(); - $collected_posts = array_merge( - $posts, - $this->collect_posts->get_linked_posts( $posts, $post_types ) - ); - - if ( ! $include_translated_items ) { - // Remove items that are already translated in this language. - foreach ( $collected_posts as $i => $linked_post ) { - if ( $this->model->post->get_translation( $linked_post->ID, $lang ) ) { - // A translation already exists. - unset( $collected_posts[ $i ] ); - } - } - } - - // Merge posts and remove duplicates. - return $this->export_posts->remove_duplicate_items( $collected_posts ); - } - - /** - * Gets all terms to be exported. - * - * @since 3.6 - * - * @param WP_Post[] $posts An array of posts. - * @param PLL_Language $lang The target language of the posts. - * @param array $args A list of optional arguments. - * @return WP_Term[] - */ - protected function get_terms_to_export( array $posts, PLL_Language $lang, array $args = array() ): array { - $include_translated_items = ! empty( $args['include_translated_items'] ); - $taxonomies = $this->model->get_translated_taxonomies(); - - // Collect terms in posts. - $collected_terms = $this->collect_terms->get_linked_terms( $posts, $taxonomies ); - - if ( ! $include_translated_items ) { - // Remove items that are already translated in this language. - foreach ( $collected_terms as $i => $term ) { - if ( $this->model->term->get_translation( $term->term_id, $lang ) ) { - // A translation already exists. - unset( $collected_terms[ $i ] ); - } - } - } - - return $this->export_terms->remove_duplicate_items( $collected_terms ); - } +class PLL_Export_Data_From_Posts +{ + /** + * A reference to the current `PLL_Model`. + * + * @var PLL_Model + */ + protected $model; + + /** + * Handles posts export. + * + * @var PLL_Export_Posts + */ + protected $export_posts; + + /** + * Handles terms export. + * + * @var PLL_Export_Terms + */ + protected $export_terms; + + /** + * The service to collect the linked posts. + * + * @var PLL_Collect_Linked_Posts + */ + protected $collect_posts; + + /** + * The service to collect the linked terms. + * + * @var PLL_Collect_Linked_Terms + */ + protected $collect_terms; + + /** + * PLL_Export_Items constructor. + * + * @since 3.6 + * + * @param PLL_Model $model Used to query languages and post translations. + */ + public function __construct(PLL_Model $model) + { + $this->model = $model; + + $this->export_posts = new PLL_Export_Posts($this->model->post); + $this->export_terms = new PLL_Export_Terms($this->model->term); + $this->collect_posts = new PLL_Collect_Linked_Posts($this->model->options); + $this->collect_terms = new PLL_Collect_Linked_Terms(); + } + + /** + * Exports the items with their related ones. + * + * @since 3.6 + * + * @param PLL_Export_Container $container Data to export. + * @param WP_Post[] $posts An array containing `WP_Post` objects. + * @param PLL_Language $target_language The target language of the posts. + * @param array $args { + * Optional. A list of optional arguments. + * + * @var bool $include_translated_items Tells if items that are already translated in the target languages must + * also be exported. This applies only to linked items (like assigned + * terms, items from reusable blocks, etc). Default is false. + * } + * + * @return void + */ + public function send_to_export(PLL_Export_Container $container, array $posts, PLL_Language $target_language, array $args = []) + { + $posts_to_export = $this->get_posts_to_export($posts, $target_language, $args); + $this->export_posts->add_items($container, $posts_to_export, $target_language); + + $terms_to_export = $this->get_terms_to_export($posts_to_export, $target_language, $args); + $this->export_terms->add_items($container, $terms_to_export, $target_language); + } + + /** + * Gets all posts to be exported. + * + * @since 3.6 + * + * @param WP_Post[] $posts An array of posts. + * @param PLL_Language $lang The target language of the posts. + * @param array $args A list of optional arguments. + * + * @return WP_Post[] + */ + protected function get_posts_to_export(array $posts, PLL_Language $lang, array $args = []): array + { + $include_translated_items = !empty($args['include_translated_items']); + $post_types = $this->model->get_translated_post_types(); + $collected_posts = array_merge( + $posts, + $this->collect_posts->get_linked_posts($posts, $post_types) + ); + + if (!$include_translated_items) { + // Remove items that are already translated in this language. + foreach ($collected_posts as $i => $linked_post) { + if ($this->model->post->get_translation($linked_post->ID, $lang)) { + // A translation already exists. + unset($collected_posts[$i]); + } + } + } + + // Merge posts and remove duplicates. + return $this->export_posts->remove_duplicate_items($collected_posts); + } + + /** + * Gets all terms to be exported. + * + * @since 3.6 + * + * @param WP_Post[] $posts An array of posts. + * @param PLL_Language $lang The target language of the posts. + * @param array $args A list of optional arguments. + * + * @return WP_Term[] + */ + protected function get_terms_to_export(array $posts, PLL_Language $lang, array $args = []): array + { + $include_translated_items = !empty($args['include_translated_items']); + $taxonomies = $this->model->get_translated_taxonomies(); + + // Collect terms in posts. + $collected_terms = $this->collect_terms->get_linked_terms($posts, $taxonomies); + + if (!$include_translated_items) { + // Remove items that are already translated in this language. + foreach ($collected_terms as $i => $term) { + if ($this->model->term->get_translation($term->term_id, $lang)) { + // A translation already exists. + unset($collected_terms[$i]); + } + } + } + + return $this->export_terms->remove_duplicate_items($collected_terms); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-strings.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-strings.php index 90cbd05e29202b3d978d995a614cfa7db7ab384a..ad651294d533109946fd8a4bcf09a07c844dc711 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-strings.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-data-from-strings.php @@ -1,67 +1,68 @@ model = $model; - } + /** + * Constructor. + * + * @since 3.6 + * + * @param PLL_Model $model Polylang model. + */ + public function __construct(PLL_Model $model) + { + $this->model = $model; + } - /** - * Prepares and exports the selected strings translations. - * - * @since 3.6 - * - * @param PLL_Export_Container $container Export container. - * @param array $sources Currated list of strings to export. - * @param PLL_Language $target_language The target language. - * @param bool $no_update Whether to remove already translated strings. Default to false. - * @return WP_Error A `WP_Error` object. Note: an "empty" `WP_Error` object is returned on success. - */ - public function send_to_export( PLL_Export_Container $container, array $sources, PLL_Language $target_language, bool $no_update = false ): WP_Error { - $source_language = $this->model->get_default_language(); + /** + * Prepares and exports the selected strings translations. + * + * @since 3.6 + * + * @param PLL_Export_Container $container Export container. + * @param array $sources Currated list of strings to export. + * @param PLL_Language $target_language The target language. + * @param bool $no_update Whether to remove already translated strings. Default to false. + * + * @return WP_Error A `WP_Error` object. Note: an "empty" `WP_Error` object is returned on success. + */ + public function send_to_export(PLL_Export_Container $container, array $sources, PLL_Language $target_language, bool $no_update = false): WP_Error + { + $source_language = $this->model->get_default_language(); - if ( empty( $source_language ) ) { - return new WP_Error( 'pll_export_no_source_language', __( 'Error: Default language not defined.', 'polylang-pro' ) ); - } + if (empty($source_language)) { + return new WP_Error('pll_export_no_source_language', __('Error: Default language not defined.', 'polylang-pro')); + } - if ( $no_update ) { - $mo = new PLL_MO(); - $mo->import_from_db( $target_language ); - $sources = array_filter( - $sources, - function ( $source ) use ( $mo ) { - return empty( $mo->translate_if_any( $source['string'] ) ); - } - ); - } + if ($no_update) { + $mo = new PLL_MO(); + $mo->import_from_db($target_language); + $sources = array_filter( + $sources, + function ($source) use ($mo) { + return empty($mo->translate_if_any($source['string'])); + } + ); + } - if ( empty( $sources ) ) { - return new WP_Error( 'pll_export_no_strings', __( 'Error: No strings found.', 'polylang-pro' ) ); - } + if (empty($sources)) { + return new WP_Error('pll_export_no_strings', __('Error: No strings found.', 'polylang-pro')); + } - ( new PLL_Export_Strings( $this->model ) )->add_items( $container, $sources, $target_language ); + (new PLL_Export_Strings($this->model))->add_items($container, $sources, $target_language); - return new WP_Error(); - } + return new WP_Error(); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-data.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-data.php index 08707b30d9e1cc63e283f3665d134315f78ee848..716f104e8be5a7a816f3746a032b105665d5e10f 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-data.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-data.php @@ -1,7 +1,4 @@ source_language = $source_language; - $this->target_language = $target_language; - } + /** + * Constructor. + * + * @since 3.6 + * + * @param PLL_Language $source_language The export's source language. + * @param PLL_Language $target_language The export's target language. + */ + public function __construct(PLL_Language $source_language, PLL_Language $target_language) + { + $this->source_language = $source_language; + $this->target_language = $target_language; + } - /** - * Returns the source language. - * - * @since 3.1 - * @since 3.6 Returns a `PLL_Language` object. - * Is public and concrete. - * - * @return PLL_Language Source language object. - */ - public function get_source_language(): PLL_Language { - return $this->source_language; - } + /** + * Returns the source language. + * + * @since 3.1 + * @since 3.6 Returns a `PLL_Language` object. + * Is public and concrete. + * + * @return PLL_Language Source language object. + */ + public function get_source_language(): PLL_Language + { + return $this->source_language; + } - /** - * Returns the target language. - * - * @since 3.1 - * @since 3.6 Returns a `PLL_Language` object. - * Is public and concrete. - * - * @return PLL_Language Target language object. - */ - public function get_target_language(): PLL_Language { - return $this->target_language; - } + /** + * Returns the target language. + * + * @since 3.1 + * @since 3.6 Returns a `PLL_Language` object. + * Is public and concrete. + * + * @return PLL_Language Target language object. + */ + public function get_target_language(): PLL_Language + { + return $this->target_language; + } - /** - * Adds a source string to exported data and optionally a pre-existing translated one. - * - * @since 3.6 - * - * @param array $ref { - * Array containing the content type and optionally the corresponding object ID. - * - * @type string $object_type Object type to be exported (e.g. `post` or `term`). - * @type string $field_type Field type to be exported (e.g. `post_content`, `post_title`...). - * @type int $object_id A unique identifier to retrieve the corresponding object from the database. - * @type string $field_id Optional, a unique identifier to retrieve the corresponding field from the database. - * @type string $field_comment Optional, a comment meant for the translators. - * @type string $encoding Optional, encoding format for the field group. - * } - * @param string $source The source to be translated. - * @param string $target Optional, a preexisting translation, if any. - * @return void - * - * @phpstan-param translationEntryRef $ref - * @phpstan-param non-empty-string $source - */ - abstract public function add_translation_entry( array $ref, string $source, string $target = '' ); + /** + * Adds a source string to exported data and optionally a pre-existing translated one. + * + * @since 3.6 + * + * @param array $ref { + * Array containing the content type and optionally the corresponding object ID. + * + * @var string $object_type Object type to be exported (e.g. `post` or `term`). + * @var string $field_type Field type to be exported (e.g. `post_content`, `post_title`...). + * @var int $object_id A unique identifier to retrieve the corresponding object from the database. + * @var string $field_id Optional, a unique identifier to retrieve the corresponding field from the database. + * @var string $field_comment Optional, a comment meant for the translators. + * @var string $encoding Optional, encoding format for the field group. + * } + * + * @param string $source The source to be translated. + * @param string $target Optional, a preexisting translation, if any. + * + * @return void + * + * @phpstan-param translationEntryRef $ref + * @phpstan-param non-empty-string $source + */ + abstract public function add_translation_entry(array $ref, string $source, string $target = ''); - /** - * Checks the parameters given are valid. - * - * @since 3.6 - * - * @param array $ref { - * Array containing the content type and optionally the corresponding object ID. - * - * @type string $object_type Object type to be exported (e.g. `post` or `term`). - * @type string $field_type Field type to be exported (e.g. `post_content`, `post_title`...). - * @type int $object_id A unique identifier to retrieve the corresponding object from the database. - * @type string $field_id Optional, a unique identifier to retrieve the corresponding field from the database. - * @type string $field_comment Optional, a comment meant for the translators. - * @type string $encoding Optional, encoding format for the field group. - * } - * @param string $source The source to be translated. - * @return bool True if valid, false otherwise. - * - * @phpstan-param translationEntryRef $ref - * @phpstan-param non-empty-string $source - */ - public function are_entry_parameters_valid( array $ref, string $source ): bool { - return '' !== $source - && isset( $ref['object_id'] ) && is_numeric( $ref['object_id'] ) && (int) $ref['object_id'] >= 0 - && ! empty( $ref['object_type'] ) && is_string( $ref['object_type'] ) - && ! empty( $ref['field_type'] ) && is_string( $ref['field_type'] ); - } + /** + * Checks the parameters given are valid. + * + * @since 3.6 + * + * @param array $ref { + * Array containing the content type and optionally the corresponding object ID. + * + * @var string $object_type Object type to be exported (e.g. `post` or `term`). + * @var string $field_type Field type to be exported (e.g. `post_content`, `post_title`...). + * @var int $object_id A unique identifier to retrieve the corresponding object from the database. + * @var string $field_id Optional, a unique identifier to retrieve the corresponding field from the database. + * @var string $field_comment Optional, a comment meant for the translators. + * @var string $encoding Optional, encoding format for the field group. + * } + * + * @param string $source The source to be translated. + * + * @return bool True if valid, false otherwise. + * + * @phpstan-param translationEntryRef $ref + * @phpstan-param non-empty-string $source + */ + public function are_entry_parameters_valid(array $ref, string $source): bool + { + return '' !== $source + && isset($ref['object_id']) && is_numeric($ref['object_id']) && (int) $ref['object_id'] >= 0 + && !empty($ref['object_type']) && is_string($ref['object_type']) + && !empty($ref['field_type']) && is_string($ref['field_type']); + } - /** - * Returns exported data. - * - * @since 3.6 - * - * @return mixed - */ - abstract public function get(); + /** + * Returns exported data. + * + * @since 3.6 + * + * @return mixed + */ + abstract public function get(); } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-metas.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-metas.php index 0af8e6d052a992e09aff0572d979bf4f6b86dc00..35836c99cd6d20eea40e73ae6699d285f7d95380 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-metas.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-metas.php @@ -1,291 +1,304 @@ 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, - * ), - * 'sub_key_is_an_array_with_all_values_to_translate' => 1, - * ), - * 'meta_name_*' => array( - * '*' => array( - * 'sub_key_*_to_translate' => 1, - * ), - * ), - * ) - * @param int $from ID of the source object. - * @param int $to ID of the target object. - */ - return (array) apply_filters( "pll_{$this->meta_type}_metas_to_export", array(), $from, $to ); - } - - /** - * Returns the encodings to use for metas. - * - * @since 3.6 - * - * @param int $from ID of the source object. - * @param int $to ID of the target object. - * @return array List of custom fields encodings. - */ - protected function get_meta_encodings( int $from, int $to ): array { - /** - * Filters the encodings to use for metas. - * Metas that are serialized do not need to be listed here since WordPress automatically decodes this format. - * - * @since 3.6 - * - * @param array $keys A recursive array containing nested meta sub keys to translate. Wildcards (`*`) can be - * used to match any characters. If `*` are already present in the meta name or sub-key, - * escape them with a baclslash: `\*`. - * @example array( - * 'meta_to_translate_1' => 'json', - * 'meta_name_*_foobar' => 'json', - * ) - * @param int $from ID of the source object. - * @param int $to ID of the target object. - */ - return (array) apply_filters( "pll_{$this->meta_type}_meta_encodings", array(), $from, $to ); - } - - /** - * Export metas to translate, along their translated values if possible. - * - * @since 3.3 - * - * @param PLL_Export_Data $export Export object. - * @param int $from ID of the source object. - * @param int $to ID of the target object. - * @return void - */ - public function export( PLL_Export_Data $export, int $from, int $to = 0 ) { - $meta_names_to_export = $this->get_meta_names_to_export( $from, $to ); - - if ( empty( $meta_names_to_export ) ) { - return; - } - - $source_metas = get_metadata( $this->meta_type, $from ); - - if ( empty( $source_metas ) || ! is_array( $source_metas ) ) { - return; - } - - $tr_metas = get_metadata( $this->meta_type, $to ); - $tr_metas = is_array( $tr_metas ) ? $tr_metas : array(); - $encodings = $this->get_meta_encodings( $from, $to ); - $matcher = new PLL_Format_Util(); - - foreach ( $meta_names_to_export as $meta_name => $meta_subfield ) { - $entries = $matcher->filter_list( $source_metas, (string) $meta_name ); - - foreach ( $entries as $meta_name => $meta_values ) { - $tr_meta_values = $tr_metas[ $meta_name ] ?? array(); - $encodings[ $meta_name ] = $encodings[ $meta_name ] ?? ''; - $decoder = new PLL_Data_Encoding( $encodings[ $meta_name ] ); - $index = 0; - - foreach ( $meta_values as $value_index => $meta_value ) { - if ( $decoder->decode_reference( $meta_value )->has_errors() ) { - // Error while decoding. - continue; - } - - $meta_subfield = is_array( $meta_subfield ) ? $meta_subfield : array(); - $tr_value = isset( $tr_meta_values[ $value_index ] ) ? $tr_meta_values[ $value_index ] : array(); - - if ( $decoder->decode_reference( $tr_value )->has_errors() ) { - // Error while decoding. - $tr_value = array(); - } - - $index += (int) $this->maybe_export_metas_sub_fields( - $meta_subfield, - addcslashes( $meta_name, '\\|' ), - $index, - $meta_value, - $tr_value, - $from, - $export, - $encodings[ $meta_name ] - ); - } - } - } - } - - /** - * Maybe exports metas sub fields recursively if the given meta values is contained in the fields to export. - * - * @since 3.3 - * @since 3.6 New parameter `$object_id`. - * - * @param array $fields_to_export A recursive array containing nested meta sub keys to translate. - * @example array( - * 'sub_key_to_translate_1' => 1, - * 'sub_key_to_translate_2' => array( - * 'sub_sub_key_to_translate_1' => 1, - * ), - * ), - * ) - * @param string $parent_key_string A string containing parent keys separated with pipes. Each pipe in key - * should be escaped to avoid conflicts. - * @param int $index Index of the current meta value. Useful when a meta has several values. - * @param array|string $source_metas The source post metas. - * @param array|string $tr_metas The translated post metas. - * @param int $object_id ID of the object the meta belongs to. - * @param PLL_Export_Data $export Export object. - * @param string $encoding Encoding format for the field group. - * @return bool True if the meta value has been exported, false otherwise. - */ - protected function maybe_export_metas_sub_fields( array $fields_to_export, string $parent_key_string, int $index, $source_metas, $tr_metas, int $object_id, PLL_Export_Data $export, string $encoding = '' ): bool { - $is_exported = false; - - if ( ! empty( $fields_to_export ) ) { - if ( is_object( $source_metas ) ) { - $source_metas = get_object_vars( $source_metas ); - } elseif ( ! is_array( $source_metas ) ) { - return false; - } - - $matcher = new PLL_Format_Util(); - - foreach ( $fields_to_export as $key => $field_value ) { - $entries = $matcher->filter_list( $source_metas, (string) $key ); - - foreach ( $entries as $key => $meta_values ) { - $escaped_key = addcslashes( (string) $key, '\\|' ); - $key_string = "$parent_key_string|$escaped_key"; - $sub_field = is_array( $field_value ) ? $field_value : array(); - - if ( is_array( $tr_metas ) && isset( $tr_metas[ $key ] ) ) { - $tr_sub_meta = $tr_metas[ $key ]; - } elseif ( is_object( $tr_metas ) && isset( $tr_metas->$key ) ) { - $tr_sub_meta = $tr_metas->$key; - } else { - $tr_sub_meta = array(); - } - - $is_exported = $this->maybe_export_metas_sub_fields( - $sub_field, - $key_string, - $index, - $meta_values, - $tr_sub_meta, - $object_id, - $export, - $encoding - ) || $is_exported; - } - } - - return $is_exported; - } - - $id_suffix = 0 < $index ? ":{$index}" : ''; - - if ( is_scalar( $source_metas ) ) { - // Single value to export doesn't require any index. - $source_metas = (string) $source_metas; - - if ( '' === $source_metas ) { - return false; - } - - $export->add_translation_entry( - array( - 'object_type' => $this->meta_type, - 'field_type' => $this->import_export_meta_type, - 'object_id' => $object_id, - 'field_id' => $parent_key_string . $id_suffix, - 'encoding' => $encoding, - ), - $source_metas, - is_scalar( $tr_metas ) ? (string) $tr_metas : '' - ); - return true; - } - - if ( ! is_array( $source_metas ) ) { - return false; - } - - $tr_metas = (array) $tr_metas; - foreach ( $source_metas as $sub_field_index => $source_value ) { - if ( ! is_scalar( $source_value ) ) { - continue; - } - - $source_value = (string) $source_value; - - if ( '' === $source_value ) { - continue; - } - - $escaped_key = addcslashes( (string) $sub_field_index, '\\|' ); - $tr_values = isset( $tr_metas[ $sub_field_index ] ) && is_scalar( $tr_metas[ $sub_field_index ] ) ? (string) $tr_metas[ $sub_field_index ] : ''; - $export->add_translation_entry( - array( - 'object_type' => $this->meta_type, - 'field_type' => $this->import_export_meta_type, - 'object_id' => $object_id, - 'field_id' => "{$parent_key_string}|{$escaped_key}{$id_suffix}", - 'encoding' => $encoding, - ), - $source_value, - $tr_values - ); - $is_exported = true; - } - - return $is_exported; - } +abstract class PLL_Export_Metas +{ + /** + * Meta type. Typically 'post' or 'term' and must be filled by the child class. + * + * @var string + * + * @phpstan-var non-falsy-string + */ + protected $meta_type; + + /** + * Import/Export meta type. {@see PLL_Import_Export::POST_META} or {@see PLL_Import_Export::POST_META} and must be filled by the child class. + * + * @var string + * + * @phpstan-var non-falsy-string + */ + protected $import_export_meta_type; + + /** + * Returns the meta names to export. + * + * @since 3.3 + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + * + * @return array List of custom fields names. + */ + protected function get_meta_names_to_export(int $from, int $to): array + { + /** + * Filters the meta names to export. + * + * @since 3.3 + * + * @param array $keys A recursive array containing nested meta sub keys to translate. Wildcards (`*`) can be + * used to match any characters. If `*` are already present in the meta name or sub-key, + * escape them with a backslash: `\*`. + * + * @example 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, + * ), + * 'sub_key_is_an_array_with_all_values_to_translate' => 1, + * ), + * 'meta_name_*' => array( + * '*' => array( + * 'sub_key_*_to_translate' => 1, + * ), + * ), + * ) + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + */ + return (array) apply_filters("pll_{$this->meta_type}_metas_to_export", [], $from, $to); + } + + /** + * Returns the encodings to use for metas. + * + * @since 3.6 + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + * + * @return array List of custom fields encodings. + */ + protected function get_meta_encodings(int $from, int $to): array + { + /** + * Filters the encodings to use for metas. + * Metas that are serialized do not need to be listed here since WordPress automatically decodes this format. + * + * @since 3.6 + * + * @param array $keys A recursive array containing nested meta sub keys to translate. Wildcards (`*`) can be + * used to match any characters. If `*` are already present in the meta name or sub-key, + * escape them with a baclslash: `\*`. + * + * @example array( + * 'meta_to_translate_1' => 'json', + * 'meta_name_*_foobar' => 'json', + * ) + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + */ + return (array) apply_filters("pll_{$this->meta_type}_meta_encodings", [], $from, $to); + } + + /** + * Export metas to translate, along their translated values if possible. + * + * @since 3.3 + * + * @param PLL_Export_Data $export Export object. + * @param int $from ID of the source object. + * @param int $to ID of the target object. + * + * @return void + */ + public function export(PLL_Export_Data $export, int $from, int $to = 0) + { + $meta_names_to_export = $this->get_meta_names_to_export($from, $to); + + if (empty($meta_names_to_export)) { + return; + } + + $source_metas = get_metadata($this->meta_type, $from); + + if (empty($source_metas) || !is_array($source_metas)) { + return; + } + + $tr_metas = get_metadata($this->meta_type, $to); + $tr_metas = is_array($tr_metas) ? $tr_metas : []; + $encodings = $this->get_meta_encodings($from, $to); + $matcher = new PLL_Format_Util(); + + foreach ($meta_names_to_export as $meta_name => $meta_subfield) { + $entries = $matcher->filter_list($source_metas, (string) $meta_name); + + foreach ($entries as $meta_name => $meta_values) { + $tr_meta_values = $tr_metas[$meta_name] ?? []; + $encodings[$meta_name] = $encodings[$meta_name] ?? ''; + $decoder = new PLL_Data_Encoding($encodings[$meta_name]); + $index = 0; + + foreach ($meta_values as $value_index => $meta_value) { + if ($decoder->decode_reference($meta_value)->has_errors()) { + // Error while decoding. + continue; + } + + $meta_subfield = is_array($meta_subfield) ? $meta_subfield : []; + $tr_value = isset($tr_meta_values[$value_index]) ? $tr_meta_values[$value_index] : []; + + if ($decoder->decode_reference($tr_value)->has_errors()) { + // Error while decoding. + $tr_value = []; + } + + $index += (int) $this->maybe_export_metas_sub_fields( + $meta_subfield, + addcslashes($meta_name, '\\|'), + $index, + $meta_value, + $tr_value, + $from, + $export, + $encodings[$meta_name] + ); + } + } + } + } + + /** + * Maybe exports metas sub fields recursively if the given meta values is contained in the fields to export. + * + * @since 3.3 + * @since 3.6 New parameter `$object_id`. + * + * @param array $fields_to_export A recursive array containing nested meta sub keys to translate. + * + * @example array( + * 'sub_key_to_translate_1' => 1, + * 'sub_key_to_translate_2' => array( + * 'sub_sub_key_to_translate_1' => 1, + * ), + * ), + * ) + * + * @param string $parent_key_string A string containing parent keys separated with pipes. Each pipe in key + * should be escaped to avoid conflicts. + * @param int $index Index of the current meta value. Useful when a meta has several values. + * @param array|string $source_metas The source post metas. + * @param array|string $tr_metas The translated post metas. + * @param int $object_id ID of the object the meta belongs to. + * @param PLL_Export_Data $export Export object. + * @param string $encoding Encoding format for the field group. + * + * @return bool True if the meta value has been exported, false otherwise. + */ + protected function maybe_export_metas_sub_fields(array $fields_to_export, string $parent_key_string, int $index, $source_metas, $tr_metas, int $object_id, PLL_Export_Data $export, string $encoding = ''): bool + { + $is_exported = false; + + if (!empty($fields_to_export)) { + if (is_object($source_metas)) { + $source_metas = get_object_vars($source_metas); + } elseif (!is_array($source_metas)) { + return false; + } + + $matcher = new PLL_Format_Util(); + + foreach ($fields_to_export as $key => $field_value) { + $entries = $matcher->filter_list($source_metas, (string) $key); + + foreach ($entries as $key => $meta_values) { + $escaped_key = addcslashes((string) $key, '\\|'); + $key_string = "$parent_key_string|$escaped_key"; + $sub_field = is_array($field_value) ? $field_value : []; + + if (is_array($tr_metas) && isset($tr_metas[$key])) { + $tr_sub_meta = $tr_metas[$key]; + } elseif (is_object($tr_metas) && isset($tr_metas->$key)) { + $tr_sub_meta = $tr_metas->$key; + } else { + $tr_sub_meta = []; + } + + $is_exported = $this->maybe_export_metas_sub_fields( + $sub_field, + $key_string, + $index, + $meta_values, + $tr_sub_meta, + $object_id, + $export, + $encoding + ) || $is_exported; + } + } + + return $is_exported; + } + + $id_suffix = 0 < $index ? ":{$index}" : ''; + + if (is_scalar($source_metas)) { + // Single value to export doesn't require any index. + $source_metas = (string) $source_metas; + + if ('' === $source_metas) { + return false; + } + + $export->add_translation_entry( + [ + 'object_type' => $this->meta_type, + 'field_type' => $this->import_export_meta_type, + 'object_id' => $object_id, + 'field_id' => $parent_key_string.$id_suffix, + 'encoding' => $encoding, + ], + $source_metas, + is_scalar($tr_metas) ? (string) $tr_metas : '' + ); + + return true; + } + + if (!is_array($source_metas)) { + return false; + } + + $tr_metas = (array) $tr_metas; + foreach ($source_metas as $sub_field_index => $source_value) { + if (!is_scalar($source_value)) { + continue; + } + + $source_value = (string) $source_value; + + if ('' === $source_value) { + continue; + } + + $escaped_key = addcslashes((string) $sub_field_index, '\\|'); + $tr_values = isset($tr_metas[$sub_field_index]) && is_scalar($tr_metas[$sub_field_index]) ? (string) $tr_metas[$sub_field_index] : ''; + $export->add_translation_entry( + [ + 'object_type' => $this->meta_type, + 'field_type' => $this->import_export_meta_type, + 'object_id' => $object_id, + 'field_id' => "{$parent_key_string}|{$escaped_key}{$id_suffix}", + 'encoding' => $encoding, + ], + $source_value, + $tr_values + ); + $is_exported = true; + } + + return $is_exported; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-post-metas.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-post-metas.php index 0f1ae8372215b129eeda87439dad018685203370..6ec924b0dd61374c4080e8b2dd4d64b4d1c79e1a 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-post-metas.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-post-metas.php @@ -1,62 +1,65 @@ meta_type = 'post'; - $this->import_export_meta_type = PLL_Import_Export::POST_META; - } +class PLL_Export_Post_Metas extends PLL_Export_Metas +{ + /** + * Constructor. + * + * @since 3.3 + */ + public function __construct() + { + $this->meta_type = 'post'; + $this->import_export_meta_type = PLL_Import_Export::POST_META; + } - /** - * Get the meta names to export. - * - * @since 3.3 - * - * @param int $from ID of the source object. - * @param int $to ID of the target object. - * @return string[] List of custom fields names. - */ - protected function get_meta_names_to_export( int $from, int $to ): array { - $default_metas_to_export = array( - '_wp_attachment_image_alt' => 1, - 'footnotes' => array( - '*' => array( - 'content' => 1, - ), - ), - ); + /** + * Get the meta names to export. + * + * @since 3.3 + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + * + * @return string[] List of custom fields names. + */ + protected function get_meta_names_to_export(int $from, int $to): array + { + $default_metas_to_export = [ + '_wp_attachment_image_alt' => 1, + 'footnotes' => [ + '*' => [ + 'content' => 1, + ], + ], + ]; - /** This filter is documented in modules/import-export/export/export-metas.php */ - return (array) apply_filters( "pll_{$this->meta_type}_metas_to_export", $default_metas_to_export, $from, $to ); - } + /** This filter is documented in modules/import-export/export/export-metas.php */ + return (array) apply_filters("pll_{$this->meta_type}_metas_to_export", $default_metas_to_export, $from, $to); + } - /** - * Returns the meta formats. - * - * @since 3.6 - * - * @param int $from ID of the source object. - * @param int $to ID of the target object. - * @return array List of custom fields formats. - */ - protected function get_meta_encodings( int $from, int $to ): array { - $formats = array( - 'footnotes' => 'json', - ); + /** + * Returns the meta formats. + * + * @since 3.6 + * + * @param int $from ID of the source object. + * @param int $to ID of the target object. + * + * @return array List of custom fields formats. + */ + protected function get_meta_encodings(int $from, int $to): array + { + $formats = [ + 'footnotes' => 'json', + ]; - /** This filter is documented in modules/import-export/export/export-metas.php */ - return (array) apply_filters( "pll_{$this->meta_type}_meta_encodings", $formats, $from, $to ); - } + /** This filter is documented in modules/import-export/export/export-metas.php */ + return (array) apply_filters("pll_{$this->meta_type}_meta_encodings", $formats, $from, $to); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-posts.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-posts.php index 1a45107c048d2ae08f6ce5fea7ceac7ce8070396..ce4a4e756a9809b505219879332b8f8286e3dcc8 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-posts.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-posts.php @@ -1,159 +1,166 @@ post_metas = new PLL_Export_Post_Metas(); - } + $this->post_metas = new PLL_Export_Post_Metas(); + } - /** - * Adds one post to export. - * - * @since 3.6 - * - * @param PLL_Export_Data $export Export object. - * @param WP_Post $item Post to export. - * @return void - */ - public function add_item( PLL_Export_Data $export, $item ) { - $tr_id = $this->translated_object->get( $item->ID, $export->get_target_language() ); - $tr_item = get_post( $tr_id ); + /** + * Adds one post to export. + * + * @since 3.6 + * + * @param PLL_Export_Data $export Export object. + * @param WP_Post $item Post to export. + * + * @return void + */ + public function add_item(PLL_Export_Data $export, $item) + { + $tr_id = $this->translated_object->get($item->ID, $export->get_target_language()); + $tr_item = get_post($tr_id); - $default_fields = array( - PLL_Import_Export::POST_TITLE, - PLL_Import_Export::POST_CONTENT, - PLL_Import_Export::POST_EXCERPT, - ); + $default_fields = [ + PLL_Import_Export::POST_TITLE, + PLL_Import_Export::POST_CONTENT, + PLL_Import_Export::POST_EXCERPT, + ]; - /** - * Filters which post fields we want to export. - * - * @since 3.5 - * - * @param string[] $allowed_fields List of post fields names. - * @param WP_Post $item Post object. - */ - $allowed_fields = apply_filters( 'pll_export_post_fields', $default_fields, $item ); - $allowed_fields = array_intersect( $default_fields, $allowed_fields ); + /** + * Filters which post fields we want to export. + * + * @since 3.5 + * + * @param string[] $allowed_fields List of post fields names. + * @param WP_Post $item Post object. + */ + $allowed_fields = apply_filters('pll_export_post_fields', $default_fields, $item); + $allowed_fields = array_intersect($default_fields, $allowed_fields); - foreach ( $allowed_fields as $field ) { - if ( '' === $item->$field ) { - continue; - } + foreach ($allowed_fields as $field) { + if ('' === $item->$field) { + continue; + } - if ( PLL_Import_Export::POST_CONTENT === $field ) { - $this->add_post_content( - $export, - $item, - $tr_item instanceof WP_Post ? $tr_item->post_content : '' - ); - } else { - $export->add_translation_entry( - array( - 'object_type' => PLL_Import_Export::TYPE_POST, - 'field_type' => $field, - 'object_id' => $item->ID, - ), - $item->$field, - $tr_item instanceof WP_Post ? $tr_item->$field : '' - ); - } - } + if (PLL_Import_Export::POST_CONTENT === $field) { + $this->add_post_content( + $export, + $item, + $tr_item instanceof WP_Post ? $tr_item->post_content : '' + ); + } else { + $export->add_translation_entry( + [ + 'object_type' => PLL_Import_Export::TYPE_POST, + 'field_type' => $field, + 'object_id' => $item->ID, + ], + $item->$field, + $tr_item instanceof WP_Post ? $tr_item->$field : '' + ); + } + } - $this->post_metas->export( $export, $item->ID, $tr_item instanceof WP_Post ? $tr_item->ID : 0 ); + $this->post_metas->export($export, $item->ID, $tr_item instanceof WP_Post ? $tr_item->ID : 0); - /** - * Fires after exporting a post. - * - * @since 3.7 - * - * @param PLL_Export_Data $export The export object. - * @param WP_Post $item The post to export. - * @param WP_Post|null $tr_item The translated post if it exists, `null` otherwise. - */ - do_action( 'pll_after_post_export', $export, $item, $tr_item instanceof WP_Post ? $tr_item : null ); - } + /** + * Fires after exporting a post. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export object. + * @param WP_Post $item The post to export. + * @param WP_Post|null $tr_item The translated post if it exists, `null` otherwise. + */ + do_action('pll_after_post_export', $export, $item, $tr_item instanceof WP_Post ? $tr_item : null); + } - /** - * Caches posts to avoid too many SQL queries during export. - * - * @since 3.6 - * - * @param int[] $ids Post IDs. - * @return void - * - * @phpstan-param non-empty-array $ids - */ - protected function add_to_cache( array $ids ) { - _prime_post_caches( $ids ); - } + /** + * Caches posts to avoid too many SQL queries during export. + * + * @since 3.6 + * + * @param int[] $ids Post IDs. + * + * @return void + * + * @phpstan-param non-empty-array $ids + */ + protected function add_to_cache(array $ids) + { + _prime_post_caches($ids); + } - /** - * Returns ID corresponding to the given post. - * - * @since 3.6 - * - * @param WP_Post $item Post to get ID from. - * @return int Post ID. - */ - protected function get_item_id( $item ): int { - return $item->ID; - } + /** + * Returns ID corresponding to the given post. + * + * @since 3.6 + * + * @param WP_Post $item Post to get ID from. + * + * @return int Post ID. + */ + protected function get_item_id($item): int + { + return $item->ID; + } - /** - * Adds post content to exported data. - * - * @since 3.6 - * - * @param PLL_Export_Data $export Export object. - * @param WP_Post $post Source post. - * @param string $translation Translated post content. - * @return void - */ - private function add_post_content( PLL_Export_Data $export, WP_Post $post, string $translation ) { - $content = PLL_Translation_Walker_Factory::create_from( $post->post_content ); - $translations = new Translations(); - $content->walk( array( $translations, 'add_entry' ) ); + /** + * Adds post content to exported data. + * + * @since 3.6 + * + * @param PLL_Export_Data $export Export object. + * @param WP_Post $post Source post. + * @param string $translation Translated post content. + * + * @return void + */ + private function add_post_content(PLL_Export_Data $export, WP_Post $post, string $translation) + { + $content = PLL_Translation_Walker_Factory::create_from($post->post_content); + $translations = new Translations(); + $content->walk([$translations, 'add_entry']); - foreach ( $translations->entries as $entry ) { - if ( '' === $entry->singular ) { - continue; - } + foreach ($translations->entries as $entry) { + if ('' === $entry->singular) { + continue; + } - // The translated post content isn't exported when source or translated posts have blocks. - $translation = has_blocks( $translation ) || has_blocks( $post->post_content ) ? '' : $translation; - $export->add_translation_entry( - array( - 'object_type' => PLL_Import_Export::TYPE_POST, - 'field_type' => PLL_Import_Export::POST_CONTENT, - 'object_id' => $post->ID, - ), - $entry->singular, - $translation - ); - } - } + // The translated post content isn't exported when source or translated posts have blocks. + $translation = has_blocks($translation) || has_blocks($post->post_content) ? '' : $translation; + $export->add_translation_entry( + [ + 'object_type' => PLL_Import_Export::TYPE_POST, + 'field_type' => PLL_Import_Export::POST_CONTENT, + 'object_id' => $post->ID, + ], + $entry->singular, + $translation + ); + } + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-strings.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-strings.php index a27c54181a93de13d3e4c8fd5b19b2aeaa0e8ee9..4d8314d30522ef89cbe3d540b466001d56db6bce 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-strings.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-strings.php @@ -1,7 +1,4 @@ - */ - private $mo = array(); + /** + * Used to query translations. + * The array keys are target language slugs (the source language is always the default one, so there is no need to + * differentiate translations by the source language). + * + * @var PLL_MO[] + * + * @phpstan-var array + */ + private $mo = []; - /** - * Constructor. - * - * @since 3.6 - * - * @param PLL_Model $model Polylang model. - */ - public function __construct( PLL_Model $model ) { - $this->model = $model; - } + /** + * Constructor. + * + * @since 3.6 + * + * @param PLL_Model $model Polylang model. + */ + public function __construct(PLL_Model $model) + { + $this->model = $model; + } - /** - * Adds multiple string translations to an export. - * - * @since 3.6 - * - * @param PLL_Export_Container $export_container Export container. - * @param string[][] $items Items to export. - * @param PLL_Language $target_language Language to translate into. - * @return void - * - * @phpstan-param non-empty-array $items - */ - public function add_items( PLL_Export_Container $export_container, array $items, PLL_Language $target_language ) { - $source_language = $this->model->get_default_language(); + /** + * Adds multiple string translations to an export. + * + * @since 3.6 + * + * @param PLL_Export_Container $export_container Export container. + * @param string[][] $items Items to export. + * @param PLL_Language $target_language Language to translate into. + * + * @return void + * + * @phpstan-param non-empty-array $items + */ + public function add_items(PLL_Export_Container $export_container, array $items, PLL_Language $target_language) + { + $source_language = $this->model->get_default_language(); - if ( empty( $source_language ) ) { - return; - } + if (empty($source_language)) { + return; + } - $export = $export_container->get( $source_language, $target_language ); + $export = $export_container->get($source_language, $target_language); - // Caching is done in `add_item()`. - foreach ( $items as $item ) { - $this->add_item( $export, $item ); - } - } + // Caching is done in `add_item()`. + foreach ($items as $item) { + $this->add_item($export, $item); + } + } - /** - * Adds one string translation to an export. - * - * @since 3.6 - * - * @param PLL_Export_Data $export Export object. - * @param string[] $item Item to export. - * @return void - * - * @phpstan-param exportSource $item - */ - public function add_item( PLL_Export_Data $export, array $item ) { - $translation = $this->get_translation( $item['string'], $export->get_target_language() ); - $ref = array( - 'object_type' => PLL_Import_Export::STRINGS_TRANSLATIONS, - 'field_type' => 'string_translation', - 'object_id' => 0, // Set 0 for strings so that this parameter is always filled. - 'field_comment' => sprintf( '%s, %s', $item['context'], $item['name'] ), - ); + /** + * Adds one string translation to an export. + * + * @since 3.6 + * + * @param PLL_Export_Data $export Export object. + * @param string[] $item Item to export. + * + * @return void + * + * @phpstan-param exportSource $item + */ + public function add_item(PLL_Export_Data $export, array $item) + { + $translation = $this->get_translation($item['string'], $export->get_target_language()); + $ref = [ + 'object_type' => PLL_Import_Export::STRINGS_TRANSLATIONS, + 'field_type' => 'string_translation', + 'object_id' => 0, // Set 0 for strings so that this parameter is always filled. + 'field_comment' => sprintf('%s, %s', $item['context'], $item['name']), + ]; - // Arrays use Windows line ending syntax. This is also performed in {@see Translation_Entry::key()}. - $source_string = str_replace( array( "\r\n", "\r" ), "\n", $item['string'] ); - $translation = str_replace( array( "\r\n", "\r" ), "\n", $translation ); + // Arrays use Windows line ending syntax. This is also performed in {@see Translation_Entry::key()}. + $source_string = str_replace(["\r\n", "\r"], "\n", $item['string']); + $translation = str_replace(["\r\n", "\r"], "\n", $translation); - if ( '' === $source_string ) { - return; - } + if ('' === $source_string) { + return; + } - $export->add_translation_entry( $ref, $source_string, $translation ); - } + $export->add_translation_entry($ref, $source_string, $translation); + } - /** - * Returns a translation for the given string, if it exists. - * - * @since 3.6 - * - * @param string $item Source string. - * @param PLL_Language $target_language Language to translate into. - * @return string - */ - private function get_translation( string $item, PLL_Language $target_language ): string { - if ( ! isset( $this->mo[ $target_language->slug ] ) ) { - // Cache translations. - $this->mo[ $target_language->slug ] = new PLL_MO(); - $this->mo[ $target_language->slug ]->import_from_db( $target_language ); - } + /** + * Returns a translation for the given string, if it exists. + * + * @since 3.6 + * + * @param string $item Source string. + * @param PLL_Language $target_language Language to translate into. + * + * @return string + */ + private function get_translation(string $item, PLL_Language $target_language): string + { + if (!isset($this->mo[$target_language->slug])) { + // Cache translations. + $this->mo[$target_language->slug] = new PLL_MO(); + $this->mo[$target_language->slug]->import_from_db($target_language); + } - return $this->mo[ $target_language->slug ]->translate( $item ); - } + return $this->mo[$target_language->slug]->translate($item); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-term-metas.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-term-metas.php index 2438a922ac75f19f119ce30892a18cae31189e13..762db32c10a8aa6a37f38a00b3c6f838b45049d1 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-term-metas.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-term-metas.php @@ -1,22 +1,20 @@ meta_type = 'term'; - $this->import_export_meta_type = PLL_Import_Export::TERM_META; - } +class PLL_Export_Term_Metas extends PLL_Export_Metas +{ + /** + * Constructor. + * + * @since 3.3 + */ + public function __construct() + { + $this->meta_type = 'term'; + $this->import_export_meta_type = PLL_Import_Export::TERM_META; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-terms.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-terms.php index 6ed6e88d78148960502b3dcdc30823b6b587b7bc..5bcbba5d8cb611b52b3bc911e5ee690468bd5977 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-terms.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-terms.php @@ -1,110 +1,114 @@ term_metas = new PLL_Export_Term_Metas(); - } + $this->term_metas = new PLL_Export_Term_Metas(); + } - /** - * Adds one term to export. - * - * @since 3.6 - * - * @param PLL_Export_Data $export Export object. - * @param WP_Term $item Term to export. - * - * @return void - */ - public function add_item( PLL_Export_Data $export, $item ) { - $tr_id = $this->translated_object->get( $item->term_id, $export->get_target_language() ); - $tr_item = get_term( $tr_id ); + /** + * Adds one term to export. + * + * @since 3.6 + * + * @param PLL_Export_Data $export Export object. + * @param WP_Term $item Term to export. + * + * @return void + */ + public function add_item(PLL_Export_Data $export, $item) + { + $tr_id = $this->translated_object->get($item->term_id, $export->get_target_language()); + $tr_item = get_term($tr_id); - if ( '' !== $item->name ) { - $export->add_translation_entry( - array( - 'object_type' => PLL_Import_Export::TYPE_TERM, - 'field_type' => PLL_Import_Export::TERM_NAME, - 'object_id' => $item->term_id, - ), - $item->name, - $tr_item instanceof WP_Term ? $tr_item->name : '' - ); - } + if ('' !== $item->name) { + $export->add_translation_entry( + [ + 'object_type' => PLL_Import_Export::TYPE_TERM, + 'field_type' => PLL_Import_Export::TERM_NAME, + 'object_id' => $item->term_id, + ], + $item->name, + $tr_item instanceof WP_Term ? $tr_item->name : '' + ); + } - if ( '' !== $item->description ) { - $export->add_translation_entry( - array( - 'object_type' => PLL_Import_Export::TYPE_TERM, - 'field_type' => PLL_Import_Export::TERM_DESCRIPTION, - 'object_id' => $item->term_id, - ), - $item->description, - $tr_item instanceof WP_Term ? $tr_item->description : '' - ); - } + if ('' !== $item->description) { + $export->add_translation_entry( + [ + 'object_type' => PLL_Import_Export::TYPE_TERM, + 'field_type' => PLL_Import_Export::TERM_DESCRIPTION, + 'object_id' => $item->term_id, + ], + $item->description, + $tr_item instanceof WP_Term ? $tr_item->description : '' + ); + } - $this->term_metas->export( $export, $item->term_id, $tr_item instanceof WP_Term ? $tr_item->term_id : 0 ); + $this->term_metas->export($export, $item->term_id, $tr_item instanceof WP_Term ? $tr_item->term_id : 0); - /** - * Fires after exporting a term. - * - * @since 3.7 - * - * @param PLL_Export_Data $export The export object. - * @param WP_Term $item The term to export. - * @param WP_Term|null $tr_item The translated term if it exists, `null` otherwise. - */ - do_action( 'pll_after_term_export', $export, $item, $tr_item instanceof WP_Term ? $tr_item : null ); - } + /** + * Fires after exporting a term. + * + * @since 3.7 + * + * @param PLL_Export_Data $export The export object. + * @param WP_Term $item The term to export. + * @param WP_Term|null $tr_item The translated term if it exists, `null` otherwise. + */ + do_action('pll_after_term_export', $export, $item, $tr_item instanceof WP_Term ? $tr_item : null); + } - /** - * Caches terms to avoid too many SQL queries during export. - * - * @since 3.6 - * - * @param int[] $ids Term IDs. - * @return void - * - * @phpstan-param non-empty-array $ids - */ - protected function add_to_cache( array $ids ) { - _prime_term_caches( $ids ); - } + /** + * Caches terms to avoid too many SQL queries during export. + * + * @since 3.6 + * + * @param int[] $ids Term IDs. + * + * @return void + * + * @phpstan-param non-empty-array $ids + */ + protected function add_to_cache(array $ids) + { + _prime_term_caches($ids); + } - /** - * Returns ID corresponding to the given term. - * - * @since 3.6 - * - * @param WP_Term $item Term to get ID from. - * @return int Term ID. - */ - protected function get_item_id( $item ): int { - return $item->term_id; - } + /** + * Returns ID corresponding to the given term. + * + * @since 3.6 + * + * @param WP_Term $item Term to get ID from. + * + * @return int Term ID. + */ + protected function get_item_id($item): int + { + return $item->term_id; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/exporter/export-translated-objects.php b/__plugins/polylang-pro-3.7.6/services/exporter/export-translated-objects.php index 1d98caf237e6ef839afa3c1a27b1486a1c15eaaf..ffdb9ca3d7c54bd13b1b73171e9439abd221cffe 100644 --- a/__plugins/polylang-pro-3.7.6/services/exporter/export-translated-objects.php +++ b/__plugins/polylang-pro-3.7.6/services/exporter/export-translated-objects.php @@ -1,119 +1,125 @@ translated_object = $translated_object; - } + /** + * Constructor. + * + * @since 3.6 + * + * @param PLL_Translated_Object $translated_object Translated object. + */ + public function __construct(PLL_Translated_Object $translated_object) + { + $this->translated_object = $translated_object; + } - /** - * Adds multiple items to export. - * Expects all items to belong to the same language, corresponding to export source. - * - * @since 3.6 - * - * @param PLL_Export_Container $export_container Export container. - * @param object[] $items Items to export. - * @param PLL_Language $target_language Language to translate into. - * @return void - */ - public function add_items( PLL_Export_Container $export_container, array $items, PLL_Language $target_language ) { - $tr_ids = array(); + /** + * Adds multiple items to export. + * Expects all items to belong to the same language, corresponding to export source. + * + * @since 3.6 + * + * @param PLL_Export_Container $export_container Export container. + * @param object[] $items Items to export. + * @param PLL_Language $target_language Language to translate into. + * + * @return void + */ + public function add_items(PLL_Export_Container $export_container, array $items, PLL_Language $target_language) + { + $tr_ids = []; - foreach ( $items as $item ) { - $tr_ids[] = $this->translated_object->get( $this->get_item_id( $item ), $target_language ); - } + foreach ($items as $item) { + $tr_ids[] = $this->translated_object->get($this->get_item_id($item), $target_language); + } - $tr_ids = array_filter( $tr_ids ); + $tr_ids = array_filter($tr_ids); - if ( ! empty( $tr_ids ) ) { - $this->add_to_cache( $tr_ids ); - } + if (!empty($tr_ids)) { + $this->add_to_cache($tr_ids); + } - foreach ( $items as $item ) { - $source_language = $this->translated_object->get_language( $this->get_item_id( $item ) ); + foreach ($items as $item) { + $source_language = $this->translated_object->get_language($this->get_item_id($item)); - if ( empty( $source_language ) ) { - continue; - } + if (empty($source_language)) { + continue; + } - $this->add_item( $export_container->get( $source_language, $target_language ), $item ); - } - } + $this->add_item($export_container->get($source_language, $target_language), $item); + } + } - /** - * Adds one item to export. - * - * @since 3.6 - * - * @param PLL_Export_Data $export Export object. - * @param object $item Item to export. - * @return void - */ - abstract public function add_item( PLL_Export_Data $export, $item ); + /** + * Adds one item to export. + * + * @since 3.6 + * + * @param PLL_Export_Data $export Export object. + * @param object $item Item to export. + * + * @return void + */ + abstract public function add_item(PLL_Export_Data $export, $item); - /** - * Removes duplicate items from array. - * - * @since 3.6 - * - * @param array $items An array of items of the same instance. - * @return array Array without duplicate items. - */ - public function remove_duplicate_items( array $items ): array { - $all_items = array(); + /** + * Removes duplicate items from array. + * + * @since 3.6 + * + * @param array $items An array of items of the same instance. + * + * @return array Array without duplicate items. + */ + public function remove_duplicate_items(array $items): array + { + $all_items = []; - foreach ( $items as $item ) { - if ( ! isset( $all_items[ $this->get_item_id( $item ) ] ) ) { - $all_items[ $this->get_item_id( $item ) ] = $item; - } - } + foreach ($items as $item) { + if (!isset($all_items[$this->get_item_id($item)])) { + $all_items[$this->get_item_id($item)] = $item; + } + } - return $all_items; - } + return $all_items; + } - /** - * Caches objects to avoid too many SQL queries during export. - * - * @since 3.6 - * - * @param int[] $ids Object IDs. - * @return void - * - * @phpstan-param non-empty-array $ids - */ - abstract protected function add_to_cache( array $ids ); + /** + * Caches objects to avoid too many SQL queries during export. + * + * @since 3.6 + * + * @param int[] $ids Object IDs. + * + * @return void + * + * @phpstan-param non-empty-array $ids + */ + abstract protected function add_to_cache(array $ids); - /** - * Returns ID corresponding to the given item. - * - * @since 3.6 - * - * @param object $item Item to get ID from. - * @return int Object ID. - * - * @phpstan-return int<0, max> - */ - abstract protected function get_item_id( $item ): int; + /** + * Returns ID corresponding to the given item. + * + * @since 3.6 + * + * @param object $item Item to get ID from. + * + * @return int Object ID. + * + * @phpstan-return int<0, max> + */ + abstract protected function get_item_id($item): int; } diff --git a/__plugins/polylang-pro-3.7.6/services/manage-user-capabilities.php b/__plugins/polylang-pro-3.7.6/services/manage-user-capabilities.php index e89de382382fdb17736bcd6d06bc8461bafbffae..f6ed51a971daadc682193118aa64c5cb3a7d1470 100644 --- a/__plugins/polylang-pro-3.7.6/services/manage-user-capabilities.php +++ b/__plugins/polylang-pro-3.7.6/services/manage-user-capabilities.php @@ -1,66 +1,69 @@ id = $id; - $this->args = array_merge( - array( - 'class' => '', - 'icon' => '', - 'before' => '', - 'after' => '', - 'priority' => 10, - ), - $args - ); - - if ( 'before_post_translations' === $args['position'] ) { - $this->args['class'] .= ' pll-before-post-translations-button'; - } - - add_action( 'pll_' . $args['position'], array( $this, 'add_icon' ), $this->args['priority'] ); - add_action( 'wp_ajax_toggle_' . $id, array( $this, 'toggle' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) ); - } - - /** - * Tells whether the button is active or not. - * - * @since 2.1 - * - * @return bool - */ - abstract public function is_active(); - - /** - * Saves the button state. - * - * @since 2.1 - * - * @param string $post_type Current post type. - * @param bool $active New requested button state. - * @return bool Whether the new button state is accepted or not. - */ - protected function toggle_option( $post_type, $active ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return true; - } - - /** - * Displays the button. - * - * @since 2.1 - * - * @param string $post_type The current post type. - * @return void - */ - public function add_icon( $post_type ) { - if ( 'attachment' !== $post_type ) { - echo $this->get_html( $this->is_active() ); // phpcs:ignore WordPress.Security.EscapeOutput - } - } - - /** - * Ajax response to a clic on the button. - * - * @since 2.1 - * - * @return void - */ - public function toggle() { - check_ajax_referer( 'pll_language', '_pll_nonce' ); - - if ( isset( $_POST['value'], $_POST['post_type'] ) ) { - $is_active = 'false' === $_POST['value']; - $post_type = sanitize_key( $_POST['post_type'] ); - - if ( post_type_exists( $post_type ) && $this->toggle_option( $post_type, $is_active ) ) { - $x = new WP_Ajax_Response( array( 'what' => 'icon', 'data' => $this->get_text( $is_active ) ) ); - $x->send(); - } - } - - wp_die( 0 ); - } - - /** - * Get the text for the button title depending on its state. - * - * @since 2.1 - * - * @param bool $is_active Whether the button is already active or not. - * @return string - */ - protected function get_text( $is_active ) { - return $is_active ? $this->args['deactivate'] : $this->args['activate']; - } - - /** - * Returns the html to display the button. - * - * @since 2.1 - * - * @param bool $is_active Whether the button is already active or not. - * @return string - */ - protected function get_html( $is_active ) { - $text = $this->get_text( $is_active ); - - return sprintf( - '%6$s%7$s', - $this->id, - esc_attr( $this->args['class'] ) . ( $is_active ? ' wp-ui-text-highlight' : '' ), - esc_attr( $text ), - esc_html( $text ), - $is_active ? 'true' : 'false', - $this->args['before'], - $this->args['after'], - $this->args['icon'] - ); - } - - /** - * Enqueues script and style. - * - * @since 2.8 - * - * @return void - */ - public function admin_enqueue_scripts() { - $screen = get_current_screen(); - - if ( $screen && in_array( $screen->base, array( 'post', 'media' ) ) && ! wp_script_is( 'pll_metabox_button', 'enqueued' ) ) { - $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; - - wp_enqueue_script( - 'pll_metabox_button', - plugins_url( '/js/build/metabox-button' . $suffix . '.js', POLYLANG_ROOT_FILE ), - array( 'jquery', 'wp-ajax-response', 'post' ), - POLYLANG_VERSION, - true - ); - - wp_localize_script( - 'pll_metabox_button', - 'pll_sync_post', - array( 'confirm_text' => __( 'You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang-pro' ) ) - ); - - wp_enqueue_style( - 'pll_metabox_button', - plugins_url( '/css/build/metabox-button' . $suffix . '.css', POLYLANG_ROOT_FILE ), - array(), - POLYLANG_VERSION - ); - } - } +abstract class PLL_Metabox_Button +{ + /** + * Id used for the css class. + * + * @var string + */ + public $id; + + /** + * Arguments used to create the button. + * + * @var array + */ + public $args; + + /** + * Constructor. + * + * @since 2.1 + * + * Parameters must be provided by the child class. + * + * @param string $id Id used for the css class. + * @param array $args { + * Arguments used to create the button. + * + * @var string $position Defines the position of the button. Accepted values are + * 'before_post_translations' and 'before_post_translation_{$language_code}'. + * @var string $activate Text displayed to activate the button. + * @var string $deactivate Text displayed to deactivate the button. + * @var string $class Optional. Classes defining the icon to display. + * @var string $icon Optional. A svg icon, required only if not using Dashicons. + * @var string $before Optional. HTML markup placed before the button. + * @var string $after Optional. HTML markup placed after the button. + * } + * + * @phpstan-param non-empty-string $id + * @phpstan-param array{ + * position: non-falsy-string, + * activate: string, + * deactivate: string, + * class?: string, + * icon?: string, + * before?: string, + * after?: string + * } $args + */ + public function __construct($id, $args) + { + $this->id = $id; + $this->args = array_merge( + [ + 'class' => '', + 'icon' => '', + 'before' => '', + 'after' => '', + 'priority' => 10, + ], + $args + ); + + if ('before_post_translations' === $args['position']) { + $this->args['class'] .= ' pll-before-post-translations-button'; + } + + add_action('pll_'.$args['position'], [$this, 'add_icon'], $this->args['priority']); + add_action('wp_ajax_toggle_'.$id, [$this, 'toggle']); + add_action('admin_enqueue_scripts', [$this, 'admin_enqueue_scripts']); + } + + /** + * Tells whether the button is active or not. + * + * @since 2.1 + * + * @return bool + */ + abstract public function is_active(); + + /** + * Saves the button state. + * + * @since 2.1 + * + * @param string $post_type Current post type. + * @param bool $active New requested button state. + * + * @return bool Whether the new button state is accepted or not. + */ + protected function toggle_option($post_type, $active) // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {return true; + } + + /** + * Displays the button. + * + * @since 2.1 + * + * @param string $post_type The current post type. + * + * @return void + */ + public function add_icon($post_type) + { + if ('attachment' !== $post_type) { + echo $this->get_html($this->is_active()); // phpcs:ignore WordPress.Security.EscapeOutput + } + } + + /** + * Ajax response to a clic on the button. + * + * @since 2.1 + * + * @return void + */ + public function toggle() + { + check_ajax_referer('pll_language', '_pll_nonce'); + + if (isset($_POST['value'], $_POST['post_type'])) { + $is_active = 'false' === $_POST['value']; + $post_type = sanitize_key($_POST['post_type']); + + if (post_type_exists($post_type) && $this->toggle_option($post_type, $is_active)) { + $x = new WP_Ajax_Response(['what' => 'icon', 'data' => $this->get_text($is_active)]); + $x->send(); + } + } + + wp_die(0); + } + + /** + * Get the text for the button title depending on its state. + * + * @since 2.1 + * + * @param bool $is_active Whether the button is already active or not. + * + * @return string + */ + protected function get_text($is_active) + { + return $is_active ? $this->args['deactivate'] : $this->args['activate']; + } + + /** + * Returns the html to display the button. + * + * @since 2.1 + * + * @param bool $is_active Whether the button is already active or not. + * + * @return string + */ + protected function get_html($is_active) + { + $text = $this->get_text($is_active); + + return sprintf( + '%6$s%7$s', + $this->id, + esc_attr($this->args['class']).($is_active ? ' wp-ui-text-highlight' : ''), + esc_attr($text), + esc_html($text), + $is_active ? 'true' : 'false', + $this->args['before'], + $this->args['after'], + $this->args['icon'] + ); + } + + /** + * Enqueues script and style. + * + * @since 2.8 + * + * @return void + */ + public function admin_enqueue_scripts() + { + $screen = get_current_screen(); + + if ($screen && in_array($screen->base, ['post', 'media']) && !wp_script_is('pll_metabox_button', 'enqueued')) { + $suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min'; + + wp_enqueue_script( + 'pll_metabox_button', + plugins_url('/js/build/metabox-button'.$suffix.'.js', POLYLANG_ROOT_FILE), + ['jquery', 'wp-ajax-response', 'post'], + POLYLANG_VERSION, + true + ); + + wp_localize_script( + 'pll_metabox_button', + 'pll_sync_post', + ['confirm_text' => __('You are about to overwrite an existing translation. Are you sure you want to proceed?', 'polylang-pro')] + ); + + wp_enqueue_style( + 'pll_metabox_button', + plugins_url('/css/build/metabox-button'.$suffix.'.css', POLYLANG_ROOT_FILE), + [], + POLYLANG_VERSION + ); + } + } } diff --git a/__plugins/polylang-pro-3.7.6/services/metabox-button/metabox-user-button.php b/__plugins/polylang-pro-3.7.6/services/metabox-button/metabox-user-button.php index 3f6daa46bf1e37bf0f0d092628d572d7e9cfc5fe..c82b5837a72b1b44250218a7eb42daea734d0431 100644 --- a/__plugins/polylang-pro-3.7.6/services/metabox-button/metabox-user-button.php +++ b/__plugins/polylang-pro-3.7.6/services/metabox-button/metabox-user-button.php @@ -1,40 +1,41 @@ user_meta->is_active(); - } + /** + * Tells whether the button is active or not. + * + * @since 3.6 + * + * @return bool + */ + public function is_active() + { + return $this->user_meta->is_active(); + } - /** - * Saves the button state. - * - * @since 3.6 - * - * @param string $post_type Current post type. - * @param bool $active New requested button state. - * @return bool Whether the new button state is accepted or not. - */ - protected function toggle_option( $post_type, $active ) { - return $this->user_meta->toggle_option( $post_type, $active ); - } + /** + * Saves the button state. + * + * @since 3.6 + * + * @param string $post_type Current post type. + * @param bool $active New requested button state. + * + * @return bool Whether the new button state is accepted or not. + */ + protected function toggle_option($post_type, $active) + { + return $this->user_meta->toggle_option($post_type, $active); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/metabox-button/toggle-user-meta.php b/__plugins/polylang-pro-3.7.6/services/metabox-button/toggle-user-meta.php index fd215353c158651ebc23b095c8fc00e4c5eb2862..b315b1774c7746137784c86192fe3b57e3f7759e 100644 --- a/__plugins/polylang-pro-3.7.6/services/metabox-button/toggle-user-meta.php +++ b/__plugins/polylang-pro-3.7.6/services/metabox-button/toggle-user-meta.php @@ -1,102 +1,110 @@ meta_name = $meta_name; + } + + /** + * Returns the user meta name storing the enabled/disabled statuses of the action per post type. + * + * @since 3.6 + * + * @return string The user meta name. + */ + public function get_meta_name(): string + { + return $this->meta_name; + } - /** - * Constructor. - * - * @since 3.6 - * - * @param string $meta_name Meta name the object will manage. - */ - public function __construct( string $meta_name ) { - $this->meta_name = $meta_name; - } + /** + * Tells whether the button is active or not. + * + * @since 2.1 + * + * @global $post + * + * @return bool + */ + public function is_active() + { + global $post; + $user_meta = $this->get(); - /** - * Returns the user meta name storing the enabled/disabled statuses of the action per post type. - * - * @since 3.6 - * - * @return string The user meta name. - */ - public function get_meta_name(): string { - return $this->meta_name; - } + return !empty($user_meta[$post->post_type]); + } - /** - * Tells whether the button is active or not. - * - * @since 2.1 - * - * @global $post - * - * @return bool - */ - public function is_active() { - global $post; - $user_meta = $this->get(); - return ! empty( $user_meta[ $post->post_type ] ); - } + /** + * Returns the user meta value. + * + * @since 3.6 + * + * @return bool[] + */ + public function get() + { + $user_meta = get_user_meta((int) get_current_user_id(), $this->get_meta_name(), true); - /** - * Returns the user meta value. - * - * @since 3.6 - * - * @return bool[] - */ - public function get() { - $user_meta = get_user_meta( (int) get_current_user_id(), $this->get_meta_name(), true ); - return is_array( $user_meta ) ? $user_meta : array(); - } + return is_array($user_meta) ? $user_meta : []; + } - /** - * Updates the user meta. - * - * @since 3.6 - * - * @param bool[] $user_meta An array with post type as key and boolean as value. - * @param WP_User|null $user An instance of `WP_User`. - * @return bool - */ - public function update( $user_meta, $user = null ) { - if ( ! $user instanceof WP_User ) { - $user = wp_get_current_user(); - } + /** + * Updates the user meta. + * + * @since 3.6 + * + * @param bool[] $user_meta An array with post type as key and boolean as value. + * @param WP_User|null $user An instance of `WP_User`. + * + * @return bool + */ + public function update($user_meta, $user = null) + { + if (!$user instanceof WP_User) { + $user = wp_get_current_user(); + } - return (bool) update_user_meta( (int) $user->ID, $this->get_meta_name(), $user_meta ); - } + return (bool) update_user_meta((int) $user->ID, $this->get_meta_name(), $user_meta); + } - /** - * Saves the button state. - * - * @since 2.1 - * - * @param string $post_type Current post type. - * @param bool $active New requested button state. - * @return bool Whether the new button state is accepted or not. - */ - public function toggle_option( $post_type, $active ) { - $user_meta = $this->get(); - $user_meta[ $post_type ] = (bool) $active; + /** + * Saves the button state. + * + * @since 2.1 + * + * @param string $post_type Current post type. + * @param bool $active New requested button state. + * + * @return bool Whether the new button state is accepted or not. + */ + public function toggle_option($post_type, $active) + { + $user_meta = $this->get(); + $user_meta[$post_type] = (bool) $active; - return $this->update( $user_meta ); - } + return $this->update($user_meta); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-block-parsing-rules.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-block-parsing-rules.php index 700c45d72fd96eb92e1c01a8c26d9743dbfba074..7d15702d5b80f375d3a71cd927e6a9b262d32b6b 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-block-parsing-rules.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-block-parsing-rules.php @@ -1,7 +1,4 @@ > * @phpstan-type AttributesParsingRules array> */ -class PLL_Translation_Block_Parsing_Rules { - - /** - * Caches values about parsable and non-parsable blocks. - * - * @var array - * - * @phpstan-var array{ - * parsing_rules?: XpathParsingRules, - * parsing_rules_for_attributes?: AttributesParsingRules, - * blocks_not_to_parse?: string[] - * } - */ - private $cache = array(); - - /** - * Holds some rules for block attributes to translate. - * Keep this list alphabetically sorted when adding entries. - * - * @var array - * @phpstan-var AttributesParsingRules - */ - private $parsing_rules_attributes = array( - 'core/comments-pagination-next' => array( - 'label' => true, - ), - 'core/comments-pagination-previous' => array( - 'label' => true, - ), - 'core/home-link' => array( - 'label' => true, - ), - 'core/more' => array( - 'customText' => true, - ), - 'core/navigation-link' => array( - 'label' => true, - 'title' => true, - 'description' => true, - ), - 'core/navigation-submenu' => array( - 'label' => true, - 'title' => true, - 'description' => true, - ), - 'core/page-list-item' => array( - 'label' => true, - 'title' => true, - ), - 'core/post-excerpt' => array( - 'moreText' => true, - ), - 'core/post-navigation-link' => array( - 'label' => true, - ), - 'core/post-terms' => array( - 'prefix' => true, - 'suffix' => true, - ), - 'core/query' => array( - 'query' => array( - 'search' => true, - ), - ), - 'core/query-pagination-next' => array( - 'label' => true, - ), - 'core/query-pagination-previous' => array( - 'label' => true, - ), - 'core/read-more' => array( - 'content' => true, - ), - 'core/search' => array( - 'label' => true, - 'placeholder' => true, - 'buttonText' => true, - ), - 'core/social-link' => array( - 'label' => true, - ), - 'core/widget-group' => array( - 'title' => true, - ), - ); - - /** - * Holds some rules as Xpath expressions to evaluate in the blocks content. - * Keep this list alphabetically sorted when adding entries. - * - * @var string[][] - * @phpstan-var XpathParsingRules - */ - private $parsing_rules = array( - 'core/accordion-heading' => array( - '//span[@class="wp-block-accordion-heading__toggle-title"]', - ), - 'core/audio' => array( - '//figure/figcaption', - ), - 'core/button' => array( - '//a', - '//a/@href', - ), - 'core/cover' => array( - '//div/p', - ), - 'core/cover-image' => array( - '//div/p', - ), - 'core/details' => array( - '//details/summary', - ), - 'core/embed' => array( - '//figure/figcaption', - ), - 'core/file' => array( - '//div/a', - ), - 'core/gallery' => array( - '//figure/figcaption', - '//figure/img/@alt', // Backward compatibility. - ), - 'core/heading' => array( - '//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]', - ), - 'core/image' => array( - '//figure/figcaption', - '//figure/img/@alt|//figure/a/img/@alt', - '//figure/img/@title|//figure/a/img/@title', - '//figure/a/@href', - ), - 'core/list' => array( - '//ul/li|//ol/li', - ), - 'core/media-text' => array( - '//figure/img/@alt', - ), - 'core/paragraph' => array( - '//p', - ), - 'core/preformatted' => array( - '//pre', - ), - 'core/pullquote' => array( - '//blockquote/p', - '//blockquote/cite', - ), - 'core/quote' => array( - '//blockquote/p', - '//blockquote/cite', - ), - 'core/subhead' => array( - '//p', - ), - 'core/table' => array( - '//th', - '//td', - '//figure/figcaption', - ), - 'core/text-columns' => array( - '//div[@class="wp-block-column"]', - ), - 'core/verse' => array( - '//pre', - ), - 'core/video' => array( - '//figure/figcaption', - ), - ); - - /** - * List of known blocks that don't need to be parsed, because they don't contain contents to be translated. - * Though, they may contain blocks that need to be parsed. - * Keep this list alphabetically sorted when adding entries. - * - * @var string[] - */ - private $blocks_not_to_parse = array( - 'core/buttons', - 'core/code', - 'core/column', - 'core/columns', - 'core/group', - 'core/math', - 'core/nextpage', - 'core/separator', - 'core/shortcode', - 'core/spacer', - ); - - /** - * Holds the name of the block type being currently parsed. - * - * @var string $block_type Similar to {@see WP_Block_Parser_Block::$blockName}. - */ - private $block_type; - - /** - * Only keeps the rules matching a certain block type. - * - * @since 3.3 - * - * @param string $block_type {@see WP_Block_Parser_Block::$blockName}. - * @return PLL_Translation_Block_Parsing_Rules $this This object with its $rules property updated. - */ - public function set_block_name( $block_type ) { - $this->block_type = $block_type; - return $this; - } - - /** - * Extracts translatable parts from the block content. - * Returns an empty array if the parsing rules are not defined. - * - * @since 3.3 - * - * @param string $content {@see WP_Block_Parser_Block::$innerContent}. - * @return string[] Parsing rules as array keys, strings to translate as array values. - * - * @phpstan-return array - */ - public function parse( $content ) { - $rules = $this->get_parsing_rules( $this->block_type ); - - if ( ! isset( $rules[ $this->block_type ] ) ) { - return array(); - } - - // Check if there's HTML encoded non-breaking-space, if so decode it for consistency. - $content = str_replace( ' ', html_entity_decode( ' ' ), $content ); - - $rules = $rules[ $this->block_type ]; - $sanitized_content = wp_kses_post( $content ); - - if ( empty( $sanitized_content ) ) { - return array(); - } - - return ( new PLL_DOM_Content( $content ) )->get_strings( $rules ); - } - - /** - * Tells if a block should be parsed using Xpath rules. - * - * @since 3.3 - * - * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. - * @return bool - */ - public function has_parsing_rules( $block ) { - $rules = $this->get_parsing_rules( $block['blockName'] ); - return isset( $rules[ $block['blockName'] ] ); - } - - /** - * Tells if a block needs to be parsed, because it contains contents to be translated. - * Though, even if not, it may contain blocks that need to be parsed. - * - * @since 3.3 - * - * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. - * @return bool - */ - public function should_be_parsed( $block ) { - if ( '' === trim( $block['innerHTML'] ) && empty( $block['attrs'] ) ) { - // A block that doesn't have contents and attributes. - return false; - } - - if ( in_array( $block['blockName'], $this->get_blocks_not_to_parse(), true ) ) { - /** - * A known block that doesn't contain contents to be translated. - * Let's avoid useless operations. - */ - return false; - } - - return true; - } - - /** - * Returns the rules as Xpath expressions to evaluate in the blocks content. - * - * @since 3.3 - * - * @param string|null $block_name Optional. The block name we want to get the parsing rules for. - * Only necessary for back-compatibility with the old `core-embed/` blocks. - * @return string[][] - * - * @phpstan-return XpathParsingRules - */ - private function get_parsing_rules( $block_name = null ) { - if ( ! isset( $this->cache['parsing_rules'] ) || ! is_array( $this->cache['parsing_rules'] ) ) { - /** - * Filters the rules as Xpath expressions to evaluate in the blocks content. - * - * @since 3.3 - * - * @param string[][] $parsing_rules Rules as Xpath expressions to evaluate in the blocks content. - * - * @phpstan-param XpathParsingRules $parsing_rules - */ - $this->cache['parsing_rules'] = (array) apply_filters( 'pll_blocks_xpath_rules', $this->parsing_rules ); - } - - $parsing_rules = $this->cache['parsing_rules']; - - if ( is_string( $block_name ) && ! isset( $parsing_rules[ $block_name ] ) && strpos( $block_name, 'core-embed/' ) === 0 ) { - $parsing_rules[ $block_name ] = array( - '//figure/figcaption', - ); - } - - return $parsing_rules; - } - - /** - * Returns the rules for the attributes to translate. - * - * @since 3.3 - * @since 3.6 Format changed from `array` to `array>`. - * - * @return array Rules for block attributes to translate. - * Array keys are block names for the 1st level, then attribute names for the next levels. - * Arrays values are `true` or an array containing sub attributes. - * Wildcards are allowed. Ex: - * array( - * 'block/name' => array( - * 'sub_key_1' => true, - * 'sub_key_2' => array( - * 'sub_sub_key_*' => true, - * ), - * ), - * ) - * - * @phpstan-return AttributesParsingRules - */ - private function get_parsing_rules_for_attributes() { - if ( ! isset( $this->cache['parsing_rules_for_attributes'] ) || ! is_array( $this->cache['parsing_rules_for_attributes'] ) ) { - /** - * Filters the list of blocks attributes to translate. - * - * @since 3.3 - * @since 3.6 Format changed from `array` to `array>`. - * - * @param array $parsing_rules_attributes Rules for block attributes to translate. - * Array keys are block names for the 1st level, then attribute names for the next levels. - * Arrays values are `true` or an array containing sub attributes. - * Wildcards are allowed. Ex: - * array( - * 'block/name' => array( - * 'sub_key_1' => true, - * 'sub_key_2' => array( - * 'sub_sub_key_*' => true, - * ), - * ), - * ) - */ - $this->cache['parsing_rules_for_attributes'] = (array) apply_filters( 'pll_blocks_rules_for_attributes', $this->parsing_rules_attributes ); - - // Backward compatibility with Polylang < 3.6. - $this->cache['parsing_rules_for_attributes'] = $this->handle_blocks_rules_for_attributes_old_format( $this->cache['parsing_rules_for_attributes'] ); - } - - return $this->cache['parsing_rules_for_attributes']; - } - - /** - * Converts the old format for the rules for attributes (used from v3.3) - * by changing it to the new format (used since v3.6). - * - * @since 3.6 - * - * @param array $rules Rules for attributes (old and new formats). - * @return array Rules for attributes (new format only). - * - * @phpstan-param array|array)> $rules - * @phpstan-return AttributesParsingRules - */ - private function handle_blocks_rules_for_attributes_old_format( array $rules ): array { - $deprecated = array(); - - // Change old format into new format. - foreach ( $rules as $block_name => $attributes ) { - foreach ( $attributes as $index => $value ) { - if ( is_string( $value ) ) { - $deprecated[] = $value; - $rules[ $block_name ][ $value ] = true; - unset( $rules[ $block_name ][ $index ] ); - } - } - } - - /** - * Filters whether to trigger an error for deprecated argument format. - * - * @since 3.6 - * - * @param bool $trigger Whether to trigger the error for deprecated argument format. Default true. - */ - if ( ! empty( $deprecated ) && WP_DEBUG && apply_filters( 'deprecated_argument_trigger_error', true ) ) { - $message = "Filter 'pll_blocks_rules_for_attributes' was used with an argument format that is deprecated since version 3.6!"; - $message .= "\n
\n" . wp_strip_all_tags( var_export( $deprecated, true ) ) . "\n
"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export - - trigger_error( $message, E_USER_DEPRECATED ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.Security.EscapeOutput.OutputNotEscaped - } - - /** @var AttributesParsingRules */ - return $rules; - } - - /** - * Returns the list of blocks not to parse. - * - * @since 3.3 - * - * @return string[] - */ - private function get_blocks_not_to_parse() { - $blocks_not_to_parse = isset( $this->cache['blocks_not_to_parse'] ) ? $this->cache['blocks_not_to_parse'] : null; - - if ( is_array( $blocks_not_to_parse ) ) { - return $blocks_not_to_parse; - } - - /** - * Filters the list of blocks not to parse. - * - * @since 3.3 - * - * @param string[] $blocks_not_to_parse List of blocks not to parse. - */ - $this->cache['blocks_not_to_parse'] = (array) apply_filters( 'pll_blocks_not_to_parse', $this->blocks_not_to_parse ); - - return $this->cache['blocks_not_to_parse']; - } - - /** - * Checks if a block has translatable attributes (or not) and returns them. - * - * @since 3.3 - * - * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. - * @return array An array with attributes to translate or an empty array. - * - * @phpstan-return array - */ - public function get_attributes_to_translate( $block ) { - if ( empty( $block['attrs'] ) ) { - return array(); - } - - $rules = $this->get_parsing_rules_for_attributes(); - if ( ! isset( $rules[ $block['blockName'] ] ) ) { - return array(); - } - - return $rules[ $block['blockName'] ]; - } +class PLL_Translation_Block_Parsing_Rules +{ + /** + * Caches values about parsable and non-parsable blocks. + * + * @var array + * + * @phpstan-var array{ + * parsing_rules?: XpathParsingRules, + * parsing_rules_for_attributes?: AttributesParsingRules, + * blocks_not_to_parse?: string[] + * } + */ + private $cache = []; + + /** + * Holds some rules for block attributes to translate. + * Keep this list alphabetically sorted when adding entries. + * + * @var array + * + * @phpstan-var AttributesParsingRules + */ + private $parsing_rules_attributes = [ + 'core/comments-pagination-next' => [ + 'label' => true, + ], + 'core/comments-pagination-previous' => [ + 'label' => true, + ], + 'core/home-link' => [ + 'label' => true, + ], + 'core/more' => [ + 'customText' => true, + ], + 'core/navigation-link' => [ + 'label' => true, + 'title' => true, + 'description' => true, + ], + 'core/navigation-submenu' => [ + 'label' => true, + 'title' => true, + 'description' => true, + ], + 'core/page-list-item' => [ + 'label' => true, + 'title' => true, + ], + 'core/post-excerpt' => [ + 'moreText' => true, + ], + 'core/post-navigation-link' => [ + 'label' => true, + ], + 'core/post-terms' => [ + 'prefix' => true, + 'suffix' => true, + ], + 'core/query' => [ + 'query' => [ + 'search' => true, + ], + ], + 'core/query-pagination-next' => [ + 'label' => true, + ], + 'core/query-pagination-previous' => [ + 'label' => true, + ], + 'core/read-more' => [ + 'content' => true, + ], + 'core/search' => [ + 'label' => true, + 'placeholder' => true, + 'buttonText' => true, + ], + 'core/social-link' => [ + 'label' => true, + ], + 'core/widget-group' => [ + 'title' => true, + ], + ]; + + /** + * Holds some rules as Xpath expressions to evaluate in the blocks content. + * Keep this list alphabetically sorted when adding entries. + * + * @var string[][] + * + * @phpstan-var XpathParsingRules + */ + private $parsing_rules = [ + 'core/accordion-heading' => [ + '//span[@class="wp-block-accordion-heading__toggle-title"]', + ], + 'core/audio' => [ + '//figure/figcaption', + ], + 'core/button' => [ + '//a', + '//a/@href', + ], + 'core/cover' => [ + '//div/p', + ], + 'core/cover-image' => [ + '//div/p', + ], + 'core/details' => [ + '//details/summary', + ], + 'core/embed' => [ + '//figure/figcaption', + ], + 'core/file' => [ + '//div/a', + ], + 'core/gallery' => [ + '//figure/figcaption', + '//figure/img/@alt', // Backward compatibility. + ], + 'core/heading' => [ + '//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]', + ], + 'core/image' => [ + '//figure/figcaption', + '//figure/img/@alt|//figure/a/img/@alt', + '//figure/img/@title|//figure/a/img/@title', + '//figure/a/@href', + ], + 'core/list' => [ + '//ul/li|//ol/li', + ], + 'core/media-text' => [ + '//figure/img/@alt', + ], + 'core/paragraph' => [ + '//p', + ], + 'core/preformatted' => [ + '//pre', + ], + 'core/pullquote' => [ + '//blockquote/p', + '//blockquote/cite', + ], + 'core/quote' => [ + '//blockquote/p', + '//blockquote/cite', + ], + 'core/subhead' => [ + '//p', + ], + 'core/table' => [ + '//th', + '//td', + '//figure/figcaption', + ], + 'core/text-columns' => [ + '//div[@class="wp-block-column"]', + ], + 'core/verse' => [ + '//pre', + ], + 'core/video' => [ + '//figure/figcaption', + ], + ]; + + /** + * List of known blocks that don't need to be parsed, because they don't contain contents to be translated. + * Though, they may contain blocks that need to be parsed. + * Keep this list alphabetically sorted when adding entries. + * + * @var string[] + */ + private $blocks_not_to_parse = [ + 'core/buttons', + 'core/code', + 'core/column', + 'core/columns', + 'core/group', + 'core/math', + 'core/nextpage', + 'core/separator', + 'core/shortcode', + 'core/spacer', + ]; + + /** + * Holds the name of the block type being currently parsed. + * + * @var string Similar to {@see WP_Block_Parser_Block::$blockName}. + */ + private $block_type; + + /** + * Only keeps the rules matching a certain block type. + * + * @since 3.3 + * + * @param string $block_type {@see WP_Block_Parser_Block::$blockName}. + * + * @return PLL_Translation_Block_Parsing_Rules $this This object with its $rules property updated. + */ + public function set_block_name($block_type) + { + $this->block_type = $block_type; + + return $this; + } + + /** + * Extracts translatable parts from the block content. + * Returns an empty array if the parsing rules are not defined. + * + * @since 3.3 + * + * @param string $content {@see WP_Block_Parser_Block::$innerContent}. + * + * @return string[] Parsing rules as array keys, strings to translate as array values. + * + * @phpstan-return array + */ + public function parse($content) + { + $rules = $this->get_parsing_rules($this->block_type); + + if (!isset($rules[$this->block_type])) { + return []; + } + + // Check if there's HTML encoded non-breaking-space, if so decode it for consistency. + $content = str_replace(' ', html_entity_decode(' '), $content); + + $rules = $rules[$this->block_type]; + $sanitized_content = wp_kses_post($content); + + if (empty($sanitized_content)) { + return []; + } + + return (new PLL_DOM_Content($content))->get_strings($rules); + } + + /** + * Tells if a block should be parsed using Xpath rules. + * + * @since 3.3 + * + * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. + * + * @return bool + */ + public function has_parsing_rules($block) + { + $rules = $this->get_parsing_rules($block['blockName']); + + return isset($rules[$block['blockName']]); + } + + /** + * Tells if a block needs to be parsed, because it contains contents to be translated. + * Though, even if not, it may contain blocks that need to be parsed. + * + * @since 3.3 + * + * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. + * + * @return bool + */ + public function should_be_parsed($block) + { + if ('' === trim($block['innerHTML']) && empty($block['attrs'])) { + // A block that doesn't have contents and attributes. + return false; + } + + if (in_array($block['blockName'], $this->get_blocks_not_to_parse(), true)) { + /** + * A known block that doesn't contain contents to be translated. + * Let's avoid useless operations. + */ + return false; + } + + return true; + } + + /** + * Returns the rules as Xpath expressions to evaluate in the blocks content. + * + * @since 3.3 + * + * @param string|null $block_name Optional. The block name we want to get the parsing rules for. + * Only necessary for back-compatibility with the old `core-embed/` blocks. + * + * @return string[][] + * + * @phpstan-return XpathParsingRules + */ + private function get_parsing_rules($block_name = null) + { + if (!isset($this->cache['parsing_rules']) || !is_array($this->cache['parsing_rules'])) { + /** + * Filters the rules as Xpath expressions to evaluate in the blocks content. + * + * @since 3.3 + * + * @param string[][] $parsing_rules Rules as Xpath expressions to evaluate in the blocks content. + * + * @phpstan-param XpathParsingRules $parsing_rules + */ + $this->cache['parsing_rules'] = (array) apply_filters('pll_blocks_xpath_rules', $this->parsing_rules); + } + + $parsing_rules = $this->cache['parsing_rules']; + + if (is_string($block_name) && !isset($parsing_rules[$block_name]) && strpos($block_name, 'core-embed/') === 0) { + $parsing_rules[$block_name] = [ + '//figure/figcaption', + ]; + } + + return $parsing_rules; + } + + /** + * Returns the rules for the attributes to translate. + * + * @since 3.3 + * @since 3.6 Format changed from `array` to `array>`. + * + * @return array Rules for block attributes to translate. + * Array keys are block names for the 1st level, then attribute names for the next levels. + * Arrays values are `true` or an array containing sub attributes. + * Wildcards are allowed. Ex: + * array( + * 'block/name' => array( + * 'sub_key_1' => true, + * 'sub_key_2' => array( + * 'sub_sub_key_*' => true, + * ), + * ), + * ) + * + * @phpstan-return AttributesParsingRules + */ + private function get_parsing_rules_for_attributes() + { + if (!isset($this->cache['parsing_rules_for_attributes']) || !is_array($this->cache['parsing_rules_for_attributes'])) { + /** + * Filters the list of blocks attributes to translate. + * + * @since 3.3 + * @since 3.6 Format changed from `array` to `array>`. + * + * @param array $parsing_rules_attributes Rules for block attributes to translate. + * Array keys are block names for the 1st level, then attribute names for the next levels. + * Arrays values are `true` or an array containing sub attributes. + * Wildcards are allowed. Ex: + * array( + * 'block/name' => array( + * 'sub_key_1' => true, + * 'sub_key_2' => array( + * 'sub_sub_key_*' => true, + * ), + * ), + * ) + */ + $this->cache['parsing_rules_for_attributes'] = (array) apply_filters('pll_blocks_rules_for_attributes', $this->parsing_rules_attributes); + + // Backward compatibility with Polylang < 3.6. + $this->cache['parsing_rules_for_attributes'] = $this->handle_blocks_rules_for_attributes_old_format($this->cache['parsing_rules_for_attributes']); + } + + return $this->cache['parsing_rules_for_attributes']; + } + + /** + * Converts the old format for the rules for attributes (used from v3.3) + * by changing it to the new format (used since v3.6). + * + * @since 3.6 + * + * @param array $rules Rules for attributes (old and new formats). + * + * @return array Rules for attributes (new format only). + * + * @phpstan-param array|array)> $rules + * + * @phpstan-return AttributesParsingRules + */ + private function handle_blocks_rules_for_attributes_old_format(array $rules): array + { + $deprecated = []; + + // Change old format into new format. + foreach ($rules as $block_name => $attributes) { + foreach ($attributes as $index => $value) { + if (is_string($value)) { + $deprecated[] = $value; + $rules[$block_name][$value] = true; + unset($rules[$block_name][$index]); + } + } + } + + /** + * Filters whether to trigger an error for deprecated argument format. + * + * @since 3.6 + * + * @param bool $trigger Whether to trigger the error for deprecated argument format. Default true. + */ + if (!empty($deprecated) && WP_DEBUG && apply_filters('deprecated_argument_trigger_error', true)) { + $message = "Filter 'pll_blocks_rules_for_attributes' was used with an argument format that is deprecated since version 3.6!"; + $message .= "\n
\n".wp_strip_all_tags(var_export($deprecated, true))."\n
"; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + + trigger_error($message, E_USER_DEPRECATED); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error, WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** @var AttributesParsingRules */ + return $rules; + } + + /** + * Returns the list of blocks not to parse. + * + * @since 3.3 + * + * @return string[] + */ + private function get_blocks_not_to_parse() + { + $blocks_not_to_parse = isset($this->cache['blocks_not_to_parse']) ? $this->cache['blocks_not_to_parse'] : null; + + if (is_array($blocks_not_to_parse)) { + return $blocks_not_to_parse; + } + + /** + * Filters the list of blocks not to parse. + * + * @since 3.3 + * + * @param string[] $blocks_not_to_parse List of blocks not to parse. + */ + $this->cache['blocks_not_to_parse'] = (array) apply_filters('pll_blocks_not_to_parse', $this->blocks_not_to_parse); + + return $this->cache['blocks_not_to_parse']; + } + + /** + * Checks if a block has translatable attributes (or not) and returns them. + * + * @since 3.3 + * + * @param array $block An array mimicking a {@see WP_Block_Parser_Block}. + * + * @return array An array with attributes to translate or an empty array. + * + * @phpstan-return array + */ + public function get_attributes_to_translate($block) + { + if (empty($block['attrs'])) { + return []; + } + + $rules = $this->get_parsing_rules_for_attributes(); + if (!isset($rules[$block['blockName']])) { + return []; + } + + return $rules[$block['blockName']]; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-content.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-content.php index 95b000be764e64a4d18340f12c835e4e1a0d92a4..6d37413b6b14d4b699aee349b9b6df22b472a93d 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-content.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-content.php @@ -1,8 +1,7 @@ translations = $translations; - } + /** + * Constructor. + * + * @since 3.7 + * + * @param Translations $translations Used to translate the content. + * + * @return void + */ + public function __construct(Translations $translations) + { + $this->translations = $translations; + } - /** - * Translates the original's post title. - * - * @since 3.3 - * - * @param string $from_post The post_content field of the original WP_Post. - * @return string - */ - public function translate_title( $from_post ) { - return $this->translations->translate( - $from_post, - Context::to_string( - array( - Context::FIELD => PLL_Import_Export::POST_TITLE, - ) - ) - ); - } + /** + * Translates the original's post title. + * + * @since 3.3 + * + * @param string $from_post The post_content field of the original WP_Post. + * + * @return string + */ + public function translate_title($from_post) + { + return $this->translations->translate( + $from_post, + Context::to_string( + [ + Context::FIELD => PLL_Import_Export::POST_TITLE, + ] + ) + ); + } - /** - * Uses a {@see PLL_Translation_Walker_Interface} subclass to iterate over each translatable part of the passed content, and applies a transformation callback to it. Then returns the transformed content. - * - * @since 3.3 - * - * @param string $content The post_content field of the original WP_Post. - * @return string - */ - public function translate_content( $content ) { - $walker = PLL_Translation_Walker_Factory::create_from( $content ); + /** + * Uses a {@see PLL_Translation_Walker_Interface} subclass to iterate over each translatable part of the passed content, and applies a transformation callback to it. Then returns the transformed content. + * + * @since 3.3 + * + * @param string $content The post_content field of the original WP_Post. + * + * @return string + */ + public function translate_content($content) + { + $walker = PLL_Translation_Walker_Factory::create_from($content); - return $walker->walk( array( $this->translations, 'translate_entry' ) ); - } + return $walker->walk([$this->translations, 'translate_entry']); + } - /** - * Translates the original post's excerpt. - * - * @since 3.3 - * - * @param string $post_excerpt The post_excerpt field of the original WP_Post. - * @return string - */ - public function translate_excerpt( $post_excerpt ) { - return $this->translations->translate( - $post_excerpt, - Context::to_string( - array( - Context::FIELD => PLL_Import_Export::POST_EXCERPT, - ) - ) - ); - } + /** + * Translates the original post's excerpt. + * + * @since 3.3 + * + * @param string $post_excerpt The post_excerpt field of the original WP_Post. + * + * @return string + */ + public function translate_excerpt($post_excerpt) + { + return $this->translations->translate( + $post_excerpt, + Context::to_string( + [ + Context::FIELD => PLL_Import_Export::POST_EXCERPT, + ] + ) + ); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-data-model-interface.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-data-model-interface.php index 98513cf9eda21742b53b3c024b668515648edb54..74b4ee6a2b401b11b90ca89894acebe8f68a441d 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-data-model-interface.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-data-model-interface.php @@ -1,32 +1,32 @@ , value_position: int, encoding: string}> - */ - protected $metas_to_translate; - - /** - * Constructor. - * - * @since 3.3 - * @since 3.7 $translations parameter added. - * - * @param PLL_Sync_Metas $sync_metas Object to manage copied metas during import. - * @param Translations $translations Translations set where to look for the metas translations. - */ - public function __construct( PLL_Sync_Metas $sync_metas, Translations $translations ) { - $this->sync_metas = $sync_metas; - $this->translations = $this->sanitize_translations( $translations ); - } - - /** - * Returns the meta type. - * - * @since 3.7 - * - * @return string Meta type. Typically 'post' or 'term'. - */ - abstract protected function get_type(): string; - - /** - * Returns the context to translate entry. - * - * @since 3.7 - * - * @return string The context. - */ - abstract protected function get_context(): string; - - /** - * Translates the metas from a given object, whether it's a copy or a real translation. - * - * @since 3.3 - * - * @param int $src_object_id Source object to get the metas from. - * @param int $tr_object_id Translated object to translate the metas from. - * @param PLL_Language $target_language Target language object. - * @param bool $copy Whether to copy source metas. For instance, if the translation is updated, there is no need to copy source metas. - * @return void - */ - public function translate( $src_object_id, $tr_object_id, PLL_Language $target_language, $copy ) { - $this->metas_to_translate = $this->get_metas_to_translate(); - - /** - * If source metas must be copied, let's filter them to remove further translated metas. - * This avoids to copy source meta value and add another translated value to it... - */ - if ( $copy ) { - add_filter( "pll_copy_{$this->get_type()}_metas", array( $this, 'remove_metas_to_translate' ) ); - $this->sync_metas->copy( $src_object_id, $tr_object_id, $target_language->slug, false ); - remove_filter( "pll_copy_{$this->get_type()}_metas", array( $this, 'remove_metas_to_translate' ) ); - } - - $this->translate_metas_values( $src_object_id, $tr_object_id ); - } - - /** - * Removes meta keys to translate from an array of meta to copy. - * - * @since 3.3 - * - * @param string[] $meta_keys Meta keys to copy. - * @return string[] Filtered array of meta to copy. - */ - public function remove_metas_to_translate( $meta_keys ) { - return array_diff( $meta_keys, wp_list_pluck( $this->metas_to_translate, 'meta_key' ) ); - } - - /** - * Translates metas values. - * - * @since 3.3 - * - * @param int $src_object_id Source object id. - * @param int $tr_object_id Translated object id. - * @return void - */ - private function translate_metas_values( $src_object_id, $tr_object_id ) { - $src_metas = get_metadata( $this->get_type(), $src_object_id ); - - if ( ! is_array( $src_metas ) || empty( $src_metas ) ) { - return; - } - - $tr_metas = array(); - $formats = array(); - - foreach ( $this->metas_to_translate as $meta ) { - $meta_key = $meta['meta_key']; - - if ( empty( $src_metas[ $meta_key ] ) ) { - // Exported meta key doesn't exist anymore ?! - continue; - } - - if ( ! isset( $formats[ $meta_key ] ) ) { - $formats[ $meta_key ] = $meta['encoding']; - } - - $src_meta_values = $src_metas[ $meta_key ]; - $decoder = new PLL_Data_Encoding( $formats[ $meta_key ] ); - - if ( ! empty( $meta['meta_sub_keys'] ) && is_array( $src_meta_values ) && count( $src_meta_values ) > 1 && ! $this->has_only_scalar_values( $src_meta_values, $decoder ) ) { - // Do not import meta with multiple non scalar values. - continue; - } - - if ( ! isset( $src_meta_values[ $meta['value_position'] ] ) ) { - // Meta value doesn't match. - continue; - } - - if ( isset( $tr_metas[ $meta_key ][ $meta['value_position'] ] ) ) { - // Meta has already been decoded and translated, but other subfields remain to be processed. - $value_to_translate = $tr_metas[ $meta_key ][ $meta['value_position'] ]; - } else { - $value_to_translate = $src_meta_values[ $meta['value_position'] ]; - - if ( ! empty( $meta['meta_sub_keys'] ) ) { - // Sub field has to be translated, let's decode its value. - if ( $decoder->decode_reference( $value_to_translate )->has_errors() ) { - // Error while decoding. - continue; - } - } - } - - $tr_metas[ $meta_key ][ $meta['value_position'] ] = $this->maybe_translate_metas_sub_fields( $value_to_translate, $meta ); - } - - // Re-encode. - $formats = array_intersect_key( $formats, $tr_metas ); - - foreach ( $formats as $meta_key => $format ) { - $decoder = new PLL_Data_Encoding( $format ); - - if ( $decoder->use_serialize() ) { - // `update_metadata()` will serialize it. - continue; - } - - foreach ( $tr_metas[ $meta_key ] as &$tr_meta ) { - $decoder->encode_reference( $tr_meta ); - } - } - - $this->insert_translated_metas( $tr_object_id, $tr_metas ); - } - - /** - * Inserts the translated metas into the database and - * takes care to add multiples meta values if needed. - * Note that if a meta has several values that aren't scalar, - * they won't be inserted in the database to avoid to - * delete potential useful data. - * - * @since 3.3 - * - * @param int $tr_object_id Translated object id. - * @param array $tr_metas Translated metas value(s). - * @return void - */ - private function insert_translated_metas( $tr_object_id, array $tr_metas ) { - $this->sync_metas->remove_all_meta_actions(); - foreach ( $tr_metas as $meta_key => $values ) { - $slashed_key = wp_slash( $meta_key ); - - // $values is an indexed array, so it contains one or more values? - if ( 1 < count( $values ) ) { - // To update multiple meta values, it's easier to delete and add rather than attempting to update them individually. - delete_metadata( $this->get_type(), $tr_object_id, $slashed_key ); - foreach ( $values as $value ) { - add_metadata( $this->get_type(), $tr_object_id, $slashed_key, wp_slash( $value ) ); // Multiple meta values must be added one by one. - } - } else { - // $values contains a single meta value, let's take it. - update_metadata( $this->get_type(), $tr_object_id, $slashed_key, wp_slash( reset( $values ) ) ); - } - } - $this->sync_metas->add_all_meta_actions(); - } - - /** - * Returns the metas to translate from the translations entries. - * Each meta translation entry is identified by a concatenation of - * meta key, subfields and position (or index) of the meta. - * For instance: 'meta_key|with|sub|fields:2'. - * - * @since 3.3 - * - * @return array[] { - * A list of arrays described as follows: - * - * @type string $meta_key The name of the meta. - * @type string[] $meta_sub_keys The meta subfields to translate. - * @type int $value_position The position of the value in case of multiple values. - * @type string $encoding Encoding format of the meta value. - * @type string $context_id ID key from entry context, useful to translate back. - * } - * - * @phpstan-return array, value_position: int, encoding: string}> - */ - private function get_metas_to_translate() { - $metas = array(); - - foreach ( $this->translations->entries as $entry ) { - if ( Context::get_field( $entry ) !== $this->get_context() ) { - continue; - } - - $meta_identifier = Context::get_id( $entry ); - if ( empty( $meta_identifier ) ) { - continue; - } - - $position = 0; - $sub_keys = array(); - - if ( preg_match( '/^(?.+)(?::(?\d+))?$/U', $meta_identifier, $matches ) ) { // Extract position (i.e. index) of the meta string. - $position = isset( $matches['position'] ) ? absint( $matches['position'] ) : 0; - $sub_keys = preg_split( '/(? ! empty( $meta_key ) ? $meta_key : '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key - 'meta_sub_keys' => $sub_keys, - 'value_position' => $position, - 'encoding' => Context::get_encoding( $entry ), - 'context_id' => $meta_identifier, - ); - } - - return $metas; - } - - /** - * Translates meta subfields recursively. - * - * @since 3.3 - * - * @param mixed $meta_value Meta value(s) to translate. - * @param array $meta { - * An array with the meta_key, subfields to translate (ordered by dimension) and value position. - * - * @type array $meta_sub_keys The meta sub keys. - * @type int $value_position The value position. - * @type string $encoding Meta encoding. - * @type string $context_id ID key from entry context, useful to translate back. - * } - * @return mixed Translated meta value(s). - */ - private function maybe_translate_metas_sub_fields( $meta_value, array $meta ) { - if ( ! is_array( $meta_value ) && ! is_object( $meta_value ) && ! is_scalar( $meta_value ) ) { - // We're not able to translate something else for now. - return $meta_value; - } - - if ( empty( $meta['meta_sub_keys'] ) ) { - // No sub key to translate, let's process the current value. - return $this->maybe_translate_field( $meta_value, $meta ); - } - - $first_key = array_shift( $meta['meta_sub_keys'] ); // Let's get the first subfield key to process. - - if ( is_array( $meta_value ) && isset( $meta_value[ $first_key ] ) ) { - $meta_value[ $first_key ] = $this->maybe_translate_sub_fields( $meta_value[ $first_key ], $meta ); - } elseif ( is_object( $meta_value ) && isset( $meta_value->$first_key ) ) { - $meta_value->$first_key = $this->maybe_translate_sub_fields( $meta_value->$first_key, $meta ); - } - - return $meta_value; - } - - /** - * Translates meta subfields recursively. - * - * @since 3.7 - * - * @param array|object $meta_value Meta value(s) to translate. - * @param array $meta { - * An array with the meta_key, subfields to translate (ordered by dimension) and value position. - * - * @type array $meta_sub_keys The meta sub keys. - * @type int $value_position The value position. - * @type string $encoding Meta encoding. - * @type string $context_id ID key from entry context, useful to translate back. - * } - * @return mixed Translated meta value(s). - */ - private function maybe_translate_sub_fields( $meta_value, array $meta ) { - if ( empty( $meta['meta_sub_keys'] ) ) { - // No more sub keys to translate. - return $this->maybe_translate_field( $meta_value, $meta ); - } - - return $this->maybe_translate_metas_sub_fields( $meta_value, $meta ); - } - - /** - * Translates meta field. - * - * @since 3.7 - * - * @param mixed $meta_value Meta value to translate. - * @param array $meta { - * An array with the meta_key, subfields to translate (ordered by dimension) and value position. - * - * @type array $meta_sub_keys The meta sub keys. - * @type int $value_position The value position. - * @type string $encoding Meta encoding. - * @type string $context_id ID key from entry context, useful to translate back. - * } - * @return mixed Translated meta value. - */ - private function maybe_translate_field( $meta_value, array $meta ) { - if ( ! is_scalar( $meta_value ) ) { - return $meta_value; - } - - return $this->translations->translate( - (string) $meta_value, - Context::to_string( - array( - Context::FIELD => $this->get_context(), - Context::ID => $meta['context_id'], - Context::ENCODING => $meta['encoding'], - ) - ) - ); - } - - /** - * Asserts an array contains only scalar values. - * - * @since 3.3 - * @since 3.6 Added parameter `$decoder`. - * - * @param array $array Array to check. - * @param PLL_Data_Encoding $decoder Data decoder. - * @return bool True if the array contains only scalar values, false otherwise. - */ - private function has_only_scalar_values( array $array, PLL_Data_Encoding $decoder ) { - foreach ( $array as $value ) { - $value = $decoder->decode( $value ); - if ( ! is_scalar( $value ) ) { - return false; - } - } - - return true; - } - - /** - * Sanitizes translation entries. - * - * @since 3.7 - * - * @param Translations $translations Translations to sanitize. - * @return Translations Sanitized translations. - */ - private function sanitize_translations( Translations $translations ): Translations { - foreach ( $translations->entries as $key => $entry ) { - if ( Context::get_field( $entry ) !== $this->get_context() ) { - continue; - } - - foreach ( $entry->translations as $i => $translation ) { - if ( $entry->singular === $translation || '' === $translation ) { - continue; - } - - $translations->entries[ $key ]->translations[ $i ] = wp_kses_post( $translation ); - } - } - - return $translations; - } +abstract class PLL_Translation_Metas +{ + /** + * Translations set where to look for the post metas translations. + * + * @var Translations + */ + protected $translations; + + /** + * Object to manage copied metas during import. + * + * @var PLL_Sync_Metas + */ + protected $sync_metas; + + /** + * Array containing meta keys to translate. + * + * @var array[] { + * A list of arrays described as follow: + * @var string $meta_key The name of the meta. + * @var string[] $meta_sub_keys The meta sub-fields to translate. + * @var int $value_position The position of the value in case of multiple values. + * @var string $encoding Encoding format of the meta value. + * } + * + * @phpstan-var array, value_position: int, encoding: string}> + */ + protected $metas_to_translate; + + /** + * Constructor. + * + * @since 3.3 + * @since 3.7 $translations parameter added. + * + * @param PLL_Sync_Metas $sync_metas Object to manage copied metas during import. + * @param Translations $translations Translations set where to look for the metas translations. + */ + public function __construct(PLL_Sync_Metas $sync_metas, Translations $translations) + { + $this->sync_metas = $sync_metas; + $this->translations = $this->sanitize_translations($translations); + } + + /** + * Returns the meta type. + * + * @since 3.7 + * + * @return string Meta type. Typically 'post' or 'term'. + */ + abstract protected function get_type(): string; + + /** + * Returns the context to translate entry. + * + * @since 3.7 + * + * @return string The context. + */ + abstract protected function get_context(): string; + + /** + * Translates the metas from a given object, whether it's a copy or a real translation. + * + * @since 3.3 + * + * @param int $src_object_id Source object to get the metas from. + * @param int $tr_object_id Translated object to translate the metas from. + * @param PLL_Language $target_language Target language object. + * @param bool $copy Whether to copy source metas. For instance, if the translation is updated, there is no need to copy source metas. + * + * @return void + */ + public function translate($src_object_id, $tr_object_id, PLL_Language $target_language, $copy) + { + $this->metas_to_translate = $this->get_metas_to_translate(); + + /** + * If source metas must be copied, let's filter them to remove further translated metas. + * This avoids to copy source meta value and add another translated value to it... + */ + if ($copy) { + add_filter("pll_copy_{$this->get_type()}_metas", [$this, 'remove_metas_to_translate']); + $this->sync_metas->copy($src_object_id, $tr_object_id, $target_language->slug, false); + remove_filter("pll_copy_{$this->get_type()}_metas", [$this, 'remove_metas_to_translate']); + } + + $this->translate_metas_values($src_object_id, $tr_object_id); + } + + /** + * Removes meta keys to translate from an array of meta to copy. + * + * @since 3.3 + * + * @param string[] $meta_keys Meta keys to copy. + * + * @return string[] Filtered array of meta to copy. + */ + public function remove_metas_to_translate($meta_keys) + { + return array_diff($meta_keys, wp_list_pluck($this->metas_to_translate, 'meta_key')); + } + + /** + * Translates metas values. + * + * @since 3.3 + * + * @param int $src_object_id Source object id. + * @param int $tr_object_id Translated object id. + * + * @return void + */ + private function translate_metas_values($src_object_id, $tr_object_id) + { + $src_metas = get_metadata($this->get_type(), $src_object_id); + + if (!is_array($src_metas) || empty($src_metas)) { + return; + } + + $tr_metas = []; + $formats = []; + + foreach ($this->metas_to_translate as $meta) { + $meta_key = $meta['meta_key']; + + if (empty($src_metas[$meta_key])) { + // Exported meta key doesn't exist anymore ?! + continue; + } + + if (!isset($formats[$meta_key])) { + $formats[$meta_key] = $meta['encoding']; + } + + $src_meta_values = $src_metas[$meta_key]; + $decoder = new PLL_Data_Encoding($formats[$meta_key]); + + if (!empty($meta['meta_sub_keys']) && is_array($src_meta_values) && count($src_meta_values) > 1 && !$this->has_only_scalar_values($src_meta_values, $decoder)) { + // Do not import meta with multiple non scalar values. + continue; + } + + if (!isset($src_meta_values[$meta['value_position']])) { + // Meta value doesn't match. + continue; + } + + if (isset($tr_metas[$meta_key][$meta['value_position']])) { + // Meta has already been decoded and translated, but other subfields remain to be processed. + $value_to_translate = $tr_metas[$meta_key][$meta['value_position']]; + } else { + $value_to_translate = $src_meta_values[$meta['value_position']]; + + if (!empty($meta['meta_sub_keys'])) { + // Sub field has to be translated, let's decode its value. + if ($decoder->decode_reference($value_to_translate)->has_errors()) { + // Error while decoding. + continue; + } + } + } + + $tr_metas[$meta_key][$meta['value_position']] = $this->maybe_translate_metas_sub_fields($value_to_translate, $meta); + } + + // Re-encode. + $formats = array_intersect_key($formats, $tr_metas); + + foreach ($formats as $meta_key => $format) { + $decoder = new PLL_Data_Encoding($format); + + if ($decoder->use_serialize()) { + // `update_metadata()` will serialize it. + continue; + } + + foreach ($tr_metas[$meta_key] as &$tr_meta) { + $decoder->encode_reference($tr_meta); + } + } + + $this->insert_translated_metas($tr_object_id, $tr_metas); + } + + /** + * Inserts the translated metas into the database and + * takes care to add multiples meta values if needed. + * Note that if a meta has several values that aren't scalar, + * they won't be inserted in the database to avoid to + * delete potential useful data. + * + * @since 3.3 + * + * @param int $tr_object_id Translated object id. + * @param array $tr_metas Translated metas value(s). + * + * @return void + */ + private function insert_translated_metas($tr_object_id, array $tr_metas) + { + $this->sync_metas->remove_all_meta_actions(); + foreach ($tr_metas as $meta_key => $values) { + $slashed_key = wp_slash($meta_key); + + // $values is an indexed array, so it contains one or more values? + if (1 < count($values)) { + // To update multiple meta values, it's easier to delete and add rather than attempting to update them individually. + delete_metadata($this->get_type(), $tr_object_id, $slashed_key); + foreach ($values as $value) { + add_metadata($this->get_type(), $tr_object_id, $slashed_key, wp_slash($value)); // Multiple meta values must be added one by one. + } + } else { + // $values contains a single meta value, let's take it. + update_metadata($this->get_type(), $tr_object_id, $slashed_key, wp_slash(reset($values))); + } + } + $this->sync_metas->add_all_meta_actions(); + } + + /** + * Returns the metas to translate from the translations entries. + * Each meta translation entry is identified by a concatenation of + * meta key, subfields and position (or index) of the meta. + * For instance: 'meta_key|with|sub|fields:2'. + * + * @since 3.3 + * + * @return array[] { + * A list of arrays described as follows: + * + * @var string $meta_key The name of the meta. + * @var string[] $meta_sub_keys The meta subfields to translate. + * @var int $value_position The position of the value in case of multiple values. + * @var string $encoding Encoding format of the meta value. + * @var string $context_id ID key from entry context, useful to translate back. + * } + * + * @phpstan-return array, value_position: int, encoding: string}> + */ + private function get_metas_to_translate() + { + $metas = []; + + foreach ($this->translations->entries as $entry) { + if (Context::get_field($entry) !== $this->get_context()) { + continue; + } + + $meta_identifier = Context::get_id($entry); + if (empty($meta_identifier)) { + continue; + } + + $position = 0; + $sub_keys = []; + + if (preg_match('/^(?.+)(?::(?\d+))?$/U', $meta_identifier, $matches)) { // Extract position (i.e. index) of the meta string. + $position = isset($matches['position']) ? absint($matches['position']) : 0; + $sub_keys = preg_split('/(? !empty($meta_key) ? $meta_key : '', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key + 'meta_sub_keys' => $sub_keys, + 'value_position' => $position, + 'encoding' => Context::get_encoding($entry), + 'context_id' => $meta_identifier, + ]; + } + + return $metas; + } + + /** + * Translates meta subfields recursively. + * + * @since 3.3 + * + * @param mixed $meta_value Meta value(s) to translate. + * @param array $meta { + * An array with the meta_key, subfields to translate (ordered by dimension) and value position. + * + * @var array $meta_sub_keys The meta sub keys. + * @var int $value_position The value position. + * @var string $encoding Meta encoding. + * @var string $context_id ID key from entry context, useful to translate back. + * } + * + * @return mixed Translated meta value(s). + */ + private function maybe_translate_metas_sub_fields($meta_value, array $meta) + { + if (!is_array($meta_value) && !is_object($meta_value) && !is_scalar($meta_value)) { + // We're not able to translate something else for now. + return $meta_value; + } + + if (empty($meta['meta_sub_keys'])) { + // No sub key to translate, let's process the current value. + return $this->maybe_translate_field($meta_value, $meta); + } + + $first_key = array_shift($meta['meta_sub_keys']); // Let's get the first subfield key to process. + + if (is_array($meta_value) && isset($meta_value[$first_key])) { + $meta_value[$first_key] = $this->maybe_translate_sub_fields($meta_value[$first_key], $meta); + } elseif (is_object($meta_value) && isset($meta_value->$first_key)) { + $meta_value->$first_key = $this->maybe_translate_sub_fields($meta_value->$first_key, $meta); + } + + return $meta_value; + } + + /** + * Translates meta subfields recursively. + * + * @since 3.7 + * + * @param array|object $meta_value Meta value(s) to translate. + * @param array $meta { + * An array with the meta_key, subfields to translate (ordered by dimension) and value position. + * + * @var array $meta_sub_keys The meta sub keys. + * @var int $value_position The value position. + * @var string $encoding Meta encoding. + * @var string $context_id ID key from entry context, useful to translate back. + * } + * + * @return mixed Translated meta value(s). + */ + private function maybe_translate_sub_fields($meta_value, array $meta) + { + if (empty($meta['meta_sub_keys'])) { + // No more sub keys to translate. + return $this->maybe_translate_field($meta_value, $meta); + } + + return $this->maybe_translate_metas_sub_fields($meta_value, $meta); + } + + /** + * Translates meta field. + * + * @since 3.7 + * + * @param mixed $meta_value Meta value to translate. + * @param array $meta { + * An array with the meta_key, subfields to translate (ordered by dimension) and value position. + * + * @var array $meta_sub_keys The meta sub keys. + * @var int $value_position The value position. + * @var string $encoding Meta encoding. + * @var string $context_id ID key from entry context, useful to translate back. + * } + * + * @return mixed Translated meta value. + */ + private function maybe_translate_field($meta_value, array $meta) + { + if (!is_scalar($meta_value)) { + return $meta_value; + } + + return $this->translations->translate( + (string) $meta_value, + Context::to_string( + [ + Context::FIELD => $this->get_context(), + Context::ID => $meta['context_id'], + Context::ENCODING => $meta['encoding'], + ] + ) + ); + } + + /** + * Asserts an array contains only scalar values. + * + * @since 3.3 + * @since 3.6 Added parameter `$decoder`. + * + * @param array $array Array to check. + * @param PLL_Data_Encoding $decoder Data decoder. + * + * @return bool True if the array contains only scalar values, false otherwise. + */ + private function has_only_scalar_values(array $array, PLL_Data_Encoding $decoder) + { + foreach ($array as $value) { + $value = $decoder->decode($value); + if (!is_scalar($value)) { + return false; + } + } + + return true; + } + + /** + * Sanitizes translation entries. + * + * @since 3.7 + * + * @param Translations $translations Translations to sanitize. + * + * @return Translations Sanitized translations. + */ + private function sanitize_translations(Translations $translations): Translations + { + foreach ($translations->entries as $key => $entry) { + if (Context::get_field($entry) !== $this->get_context()) { + continue; + } + + foreach ($entry->translations as $i => $translation) { + if ($entry->singular === $translation || '' === $translation) { + continue; + } + + $translations->entries[$key]->translations[$i] = wp_kses_post($translation); + } + } + + return $translations; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-object-model-trait.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-object-model-trait.php index 3634901fcb8877e6b4a9f955446804737465337a..c2dfe3f5c270c36304cf8852bfdfd063e5761f91 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-object-model-trait.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-object-model-trait.php @@ -1,31 +1,31 @@ assign_parents( $ids, $target_language ); - } + $this->assign_parents($ids, $target_language); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-post-metas.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-post-metas.php index 83da04f4448160ce29b67e698a38680f7162d22f..34c1279c472ed053f4eac2630d223321677da849 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-post-metas.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-post-metas.php @@ -1,35 +1,35 @@ model = &$polylang->model; - $this->sync = &$polylang->sync; - $this->sync_post_model = &$polylang->sync_post_model; - $this->sync_content = &$polylang->sync_content; - $this->user_capabilities_manager = new PLL_Manage_User_Capabilities(); - } - - /** - * Translates a post in a given language. - * - * @since 3.3 - * - * @param array $entry { - * Import data. - * int $id Source object ID. - * Translations $data Object containing translated data. - * array $fields { - * Fields to create the new translation. - * string $post_status Post status to use during translation creation. - * } - * }. - * @param PLL_Language $target_language A language to translate into. - * @return int|WP_Error The translated post ID, `WP_Error` on failure. - * - * @phpstan-param EntryData $entry - */ - public function translate( array $entry, PLL_Language $target_language ) { - if ( ! $entry['data'] instanceof Translations ) { - /* translators: %d is a post ID. */ - return new WP_Error( 'pll_translate_post_no_translations', sprintf( __( 'The post with ID %d could not be translated.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - - $source_post = get_post( $entry['id'] ); - if ( ! $source_post instanceof WP_Post || ! $source_post->ID ) { - /* translators: %d is a post ID. */ - return new WP_Error( 'pll_translate_post_no_source_post', sprintf( __( 'The post with ID %d could not be translated as it doesn\'t exist.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - - $tr_post_id = $this->model->post->get( $entry['id'], $target_language ); - $tr_post = $tr_post_id ? get_post( $tr_post_id ) : null; - - $this->user_capabilities_manager->forbid_unfiltered_html( $source_post ); - - $translation_exists = $tr_post instanceof WP_Post; - - if ( $translation_exists ) { - $tr_post = $this->update_post_translation( - $entry, - $source_post, - $target_language, - $tr_post - ); - } else { - $tr_post = $this->create_post_translation( - $entry, - $source_post, - $target_language - ); - } - - if ( ! $tr_post instanceof WP_Post ) { - /* translators: %d is a post ID. */ - return new WP_Error( 'pll_translate_post_failed', sprintf( __( 'The post with ID %d could not be translated.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - - // Fix for `term_exists()`. - add_filter( 'term_exists_default_query_args', array( $this, 'term_exists_default_query_args' ), 10, 3 ); - - $this->sync->taxonomies->copy( $source_post->ID, $tr_post->ID, $target_language->slug ); - ( new PLL_Translation_Post_Metas( $this->sync->post_metas, $entry['data'] ) ) - ->translate( $source_post->ID, $tr_post->ID, $target_language, ! $translation_exists ); - - /** - * Fires once a post has been translated. - * - * @since 3.7 - * - * @param WP_Post $source_post The source post. - * @param WP_Post $tr_post The target post. - * @param PLL_Language $target_language The language to translate into. - * @param Translations $translations The set of translations for the entry. - */ - do_action( 'pll_after_post_translation', $source_post, $tr_post, $target_language, $entry['data'] ); - - $this->user_capabilities_manager->allow_unfiltered_html(); - - /** This action is documented in include/crud-posts.php. */ - do_action( 'pll_save_post', $tr_post->ID, $tr_post, $this->model->post->get_translations( $tr_post->ID ) ); // Triggers the the post metas synchronization. - - return $tr_post->ID; - } - - /** - * Creates a new post translation. - * - * @since 3.3 - * - * @param array $data_import Import data, @see {self::translate()}. - * @param WP_Post $source_post The source post object. - * @param PLL_Language $target_language The language to translate into. - * @return WP_Post|null The translated post object, `null` on failure. - * - * @phpstan-param EntryData $data_import - */ - protected function create_post_translation( array $data_import, WP_Post $source_post, PLL_Language $target_language ): ?WP_Post { - $tr_post = get_default_post_to_edit( $source_post->post_type, true ); - $this->model->post->set_language( $tr_post->ID, $target_language ); // Do it now to share slug. - - $tr_post = $this->copy_source_post( $source_post, $tr_post ); - - $tr_post = $this->translate_content( $data_import, $source_post, $target_language, $tr_post ); - - if ( ! $tr_post instanceof WP_Post ) { - return null; - } - - // Set post status in post data. - $data_import['fields'] = wp_parse_args( $data_import['fields'], array( 'post_status' => 'draft' ) ); - $tr_post->post_status = $data_import['fields']['post_status']; - - $tr_post_args = $tr_post->to_array(); - - $tr_id = wp_update_post( wp_slash( $tr_post_args ) ); - if ( ! $tr_id ) { - // Failure during post update. - return null; - } - - $this->save_translations_group( $source_post->ID, $tr_post->ID, $target_language->slug ); - - return get_post( $tr_id ); - } - - /** - * Saves the translations group. - * - * @since 3.3 - * - * @param int $from_id The post source id. - * @param int $tr_id The translated post id. - * @param string $lang The language slug of the translated post. - * @return void - */ - protected function save_translations_group( $from_id, $tr_id, $lang ) { - $translations = $this->model->post->get_translations( $from_id ); - $translations[ $lang ] = $tr_id; - $this->model->post->save_translations( $from_id, $translations ); - } - - /** - * Updates an existing post translation. - * - * @since 3.3 - * @since 3.7 $data_import parameter added. - * - * @param array $data_import Import data, @see {self::translate()}. - * @param WP_Post $source_post The source post object. - * @param PLL_Language $target_language The language to translate into. - * @param WP_Post $tr_post The translated post object. - * @return WP_Post|null The translated post object, `null` on failure. - * - * @phpstan-param EntryData $data_import - */ - protected function update_post_translation( array $data_import, WP_Post $source_post, PLL_Language $target_language, WP_Post $tr_post ): ?WP_Post { - $this->maybe_unsync_posts( $source_post->ID, $tr_post->ID, $target_language ); - $tr_post = $this->translate_content( $data_import, $source_post, $target_language, $tr_post ); - - if ( ! $tr_post instanceof WP_Post ) { - return null; - } - - return get_post( - wp_update_post( $tr_post ) - ); - } - - /** - * Translates all content type of a post (i.e. title, excerpt and content). - * - * @since 3.3 - * @since 3.7 $translations parameter added. - * - * @param array $data_import Import data, @see {self::translate()}. - * @param WP_Post $source_post The source post object. - * @param PLL_Language $target_language The language to translate into. - * @param WP_Post $tr_post The translated post object. - * @return WP_Post|null The translated post object populated with new data. Null otherwise. - */ - protected function translate_content( array $data_import, WP_Post $source_post, PLL_Language $target_language, WP_Post $tr_post ): ?WP_Post { - $translate_content = new PLL_Translation_Content( $data_import['data'] ); - $tr_post->post_title = $translate_content->translate_title( $source_post->post_title ); - $tr_post->post_excerpt = $translate_content->translate_excerpt( $source_post->post_excerpt ); - $tr_post->post_content = $this->sync_content->translate_content( - $translate_content->translate_content( $source_post->post_content ), - $tr_post, - $target_language - ); - - /** - * Filters a translated post before it is saved. - * - * @since 3.7 - * - * @param WP_Post $tr_post The target post. - * @param WP_Post $source_post The source post. - * @param PLL_Language $target_language The language to translate into. - * @param Translations $translations The set of translations for the entry.. - */ - $tr_post = apply_filters( 'pll_filter_translated_post', $tr_post, $source_post, $target_language, $data_import['data'] ); - - return $tr_post instanceof WP_Post ? $tr_post : null; - } - - /** - * Copy the source post data in the translated post. - * - * @since 3.3 - * @since 3.4 Renamed from `clone_source_post` and added second parameter `$tr_post`. - * - * @param WP_Post $source_post The Source Post. - * @param WP_Post $tr_post The translated Post. - * @return WP_Post The translated post. - */ - protected function copy_source_post( $source_post, $tr_post ) { - // The columns to copy. - $columns = array( - 'post_author', - 'post_content', - 'post_title', - 'post_excerpt', - 'comment_status', - 'ping_status', - 'post_parent', - 'menu_order', - 'post_mime_type', - 'post_password', - ); - - foreach ( $columns as $column ) { - $tr_post->{$column} = $source_post->{$column}; - } - - return $tr_post; - } - - /** - * Assigns the parents to posts creating during the import. - * - * @since 3.3 - * @since 3.7 Renamed from `translate_parents` and made private. - * - * @param int[] $ids Array of source post ids. - * @param PLL_Language $target_language The target language. - * @return void - */ - private function assign_parents( array $ids, PLL_Language $target_language ) { - // Keep only the posts that have a parent. - $posts = get_posts( - array( - 'include' => $ids, - 'post_type' => 'any', - 'post_status' => 'any', - 'post_parent__not_in' => array( 0 ), - 'update_post_term_cache' => false, - 'update_post_meta_cache' => false, - 'fields' => 'id=>parent', - ) - ); - - if ( empty( $posts ) ) { - // No posts with parents. - return; - } - - $tr_ids = array(); - foreach ( $posts as $child => $post ) { - $tr_ids[ $child ] = $this->model->post->get( $child, $target_language->slug ); - } - $tr_ids = array_filter( $tr_ids ); - - if ( empty( $tr_ids ) ) { - // No translations. - return; - } - - foreach ( $posts as $child => $post ) { - if ( empty( $tr_ids[ $child ] ) ) { - // Not translated. - continue; - } - - $tr_parent_post = $this->model->post->get( $post, $target_language->slug ); - - if ( empty( $tr_parent_post ) ) { - // The parent post is not translated. - continue; - } - - wp_update_post( - array( - 'ID' => $tr_ids[ $child ], - 'post_parent' => $tr_parent_post, - ) - ); - } - } - - /** - * Filters default query arguments when 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. - * Copied from PLL_Filters::term_exists_default_query_args - * - * @since 3.3 - * - * @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; - } - - /** - * Unsynchronizes translated post from the source. - * - * @since 3.3 - * - * @param int $source_post_id Source post ID. - * @param int $target_post_id Translated post ID. - * @param PLL_Language $target_language Translated post language object. - * @return void - */ - protected function maybe_unsync_posts( $source_post_id, $target_post_id, $target_language ) { - if ( ! $this->sync_post_model->are_synchronized( $source_post_id, $target_post_id ) ) { - return; - } - - $sync_posts = $this->sync_post_model->get( $source_post_id ); - - if ( ! isset( $sync_posts[ $target_language->slug ] ) || $sync_posts[ $target_language->slug ] !== $target_post_id ) { - return; - } - - unset( $sync_posts[ $target_language->slug ] ); - - $this->sync_post_model->save_group( $source_post_id, array_keys( $sync_posts ) ); - } +class PLL_Translation_Post_Model implements PLL_Translation_Data_Model_Interface +{ + use PLL_Translation_Object_Model_Trait; + + /** + * Used to query languages and translations. + * + * @var PLL_Model + */ + protected $model; + + /** + * @var PLL_Sync + */ + protected $sync; + + /** + * @var PLL_Sync_Post_Model + */ + protected $sync_post_model; + + /** + * Service to manage user capabilities, espcecially 'unfiltered_html'. + * + * @var PLL_Manage_User_Capabilities + */ + protected $user_capabilities_manager; + + /** + * Used to sync the post content. + * + * @var PLL_Sync_Content + */ + protected $sync_content; + + /** + * Constructor. + * + * @since 3.3 + * + * @param PLL_Settings|PLL_Admin $polylang Polylang object. + */ + public function __construct(&$polylang) + { + $this->model = &$polylang->model; + $this->sync = &$polylang->sync; + $this->sync_post_model = &$polylang->sync_post_model; + $this->sync_content = &$polylang->sync_content; + $this->user_capabilities_manager = new PLL_Manage_User_Capabilities(); + } + + /** + * Translates a post in a given language. + * + * @since 3.3 + * + * @param array $entry { + * Import data. + * int $id Source object ID. + * Translations $data Object containing translated data. + * array $fields { + * Fields to create the new translation. + * string $post_status Post status to use during translation creation. + * } + * }. + * @param PLL_Language $target_language A language to translate into. + * + * @return int|WP_Error The translated post ID, `WP_Error` on failure. + * + * @phpstan-param EntryData $entry + */ + public function translate(array $entry, PLL_Language $target_language) + { + if (!$entry['data'] instanceof Translations) { + /* translators: %d is a post ID. */ + return new WP_Error('pll_translate_post_no_translations', sprintf(__('The post with ID %d could not be translated.', 'polylang-pro'), (int) $entry['id'])); + } + + $source_post = get_post($entry['id']); + if (!$source_post instanceof WP_Post || !$source_post->ID) { + /* translators: %d is a post ID. */ + return new WP_Error('pll_translate_post_no_source_post', sprintf(__('The post with ID %d could not be translated as it doesn\'t exist.', 'polylang-pro'), (int) $entry['id'])); + } + + $tr_post_id = $this->model->post->get($entry['id'], $target_language); + $tr_post = $tr_post_id ? get_post($tr_post_id) : null; + + $this->user_capabilities_manager->forbid_unfiltered_html($source_post); + + $translation_exists = $tr_post instanceof WP_Post; + + if ($translation_exists) { + $tr_post = $this->update_post_translation( + $entry, + $source_post, + $target_language, + $tr_post + ); + } else { + $tr_post = $this->create_post_translation( + $entry, + $source_post, + $target_language + ); + } + + if (!$tr_post instanceof WP_Post) { + /* translators: %d is a post ID. */ + return new WP_Error('pll_translate_post_failed', sprintf(__('The post with ID %d could not be translated.', 'polylang-pro'), (int) $entry['id'])); + } + + // Fix for `term_exists()`. + add_filter('term_exists_default_query_args', [$this, 'term_exists_default_query_args'], 10, 3); + + $this->sync->taxonomies->copy($source_post->ID, $tr_post->ID, $target_language->slug); + (new PLL_Translation_Post_Metas($this->sync->post_metas, $entry['data'])) + ->translate($source_post->ID, $tr_post->ID, $target_language, !$translation_exists); + + /** + * Fires once a post has been translated. + * + * @since 3.7 + * + * @param WP_Post $source_post The source post. + * @param WP_Post $tr_post The target post. + * @param PLL_Language $target_language The language to translate into. + * @param Translations $translations The set of translations for the entry. + */ + do_action('pll_after_post_translation', $source_post, $tr_post, $target_language, $entry['data']); + + $this->user_capabilities_manager->allow_unfiltered_html(); + + /** This action is documented in include/crud-posts.php. */ + do_action('pll_save_post', $tr_post->ID, $tr_post, $this->model->post->get_translations($tr_post->ID)); // Triggers the the post metas synchronization. + + return $tr_post->ID; + } + + /** + * Creates a new post translation. + * + * @since 3.3 + * + * @param array $data_import Import data, @see {self::translate()}. + * @param WP_Post $source_post The source post object. + * @param PLL_Language $target_language The language to translate into. + * + * @return WP_Post|null The translated post object, `null` on failure. + * + * @phpstan-param EntryData $data_import + */ + protected function create_post_translation(array $data_import, WP_Post $source_post, PLL_Language $target_language): ?WP_Post + { + $tr_post = get_default_post_to_edit($source_post->post_type, true); + $this->model->post->set_language($tr_post->ID, $target_language); // Do it now to share slug. + + $tr_post = $this->copy_source_post($source_post, $tr_post); + + $tr_post = $this->translate_content($data_import, $source_post, $target_language, $tr_post); + + if (!$tr_post instanceof WP_Post) { + return null; + } + + // Set post status in post data. + $data_import['fields'] = wp_parse_args($data_import['fields'], ['post_status' => 'draft']); + $tr_post->post_status = $data_import['fields']['post_status']; + + $tr_post_args = $tr_post->to_array(); + + $tr_id = wp_update_post(wp_slash($tr_post_args)); + if (!$tr_id) { + // Failure during post update. + return null; + } + + $this->save_translations_group($source_post->ID, $tr_post->ID, $target_language->slug); + + return get_post($tr_id); + } + + /** + * Saves the translations group. + * + * @since 3.3 + * + * @param int $from_id The post source id. + * @param int $tr_id The translated post id. + * @param string $lang The language slug of the translated post. + * + * @return void + */ + protected function save_translations_group($from_id, $tr_id, $lang) + { + $translations = $this->model->post->get_translations($from_id); + $translations[$lang] = $tr_id; + $this->model->post->save_translations($from_id, $translations); + } + + /** + * Updates an existing post translation. + * + * @since 3.3 + * @since 3.7 $data_import parameter added. + * + * @param array $data_import Import data, @see {self::translate()}. + * @param WP_Post $source_post The source post object. + * @param PLL_Language $target_language The language to translate into. + * @param WP_Post $tr_post The translated post object. + * + * @return WP_Post|null The translated post object, `null` on failure. + * + * @phpstan-param EntryData $data_import + */ + protected function update_post_translation(array $data_import, WP_Post $source_post, PLL_Language $target_language, WP_Post $tr_post): ?WP_Post + { + $this->maybe_unsync_posts($source_post->ID, $tr_post->ID, $target_language); + $tr_post = $this->translate_content($data_import, $source_post, $target_language, $tr_post); + + if (!$tr_post instanceof WP_Post) { + return null; + } + + return get_post( + wp_update_post($tr_post) + ); + } + + /** + * Translates all content type of a post (i.e. title, excerpt and content). + * + * @since 3.3 + * @since 3.7 $translations parameter added. + * + * @param array $data_import Import data, @see {self::translate()}. + * @param WP_Post $source_post The source post object. + * @param PLL_Language $target_language The language to translate into. + * @param WP_Post $tr_post The translated post object. + * + * @return WP_Post|null The translated post object populated with new data. Null otherwise. + */ + protected function translate_content(array $data_import, WP_Post $source_post, PLL_Language $target_language, WP_Post $tr_post): ?WP_Post + { + $translate_content = new PLL_Translation_Content($data_import['data']); + $tr_post->post_title = $translate_content->translate_title($source_post->post_title); + $tr_post->post_excerpt = $translate_content->translate_excerpt($source_post->post_excerpt); + $tr_post->post_content = $this->sync_content->translate_content( + $translate_content->translate_content($source_post->post_content), + $tr_post, + $target_language + ); + + /** + * Filters a translated post before it is saved. + * + * @since 3.7 + * + * @param WP_Post $tr_post The target post. + * @param WP_Post $source_post The source post. + * @param PLL_Language $target_language The language to translate into. + * @param Translations $translations The set of translations for the entry.. + */ + $tr_post = apply_filters('pll_filter_translated_post', $tr_post, $source_post, $target_language, $data_import['data']); + + return $tr_post instanceof WP_Post ? $tr_post : null; + } + + /** + * Copy the source post data in the translated post. + * + * @since 3.3 + * @since 3.4 Renamed from `clone_source_post` and added second parameter `$tr_post`. + * + * @param WP_Post $source_post The Source Post. + * @param WP_Post $tr_post The translated Post. + * + * @return WP_Post The translated post. + */ + protected function copy_source_post($source_post, $tr_post) + { + // The columns to copy. + $columns = [ + 'post_author', + 'post_content', + 'post_title', + 'post_excerpt', + 'comment_status', + 'ping_status', + 'post_parent', + 'menu_order', + 'post_mime_type', + 'post_password', + ]; + + foreach ($columns as $column) { + $tr_post->{$column} = $source_post->{$column}; + } + + return $tr_post; + } + + /** + * Assigns the parents to posts creating during the import. + * + * @since 3.3 + * @since 3.7 Renamed from `translate_parents` and made private. + * + * @param int[] $ids Array of source post ids. + * @param PLL_Language $target_language The target language. + * + * @return void + */ + private function assign_parents(array $ids, PLL_Language $target_language) + { + // Keep only the posts that have a parent. + $posts = get_posts( + [ + 'include' => $ids, + 'post_type' => 'any', + 'post_status' => 'any', + 'post_parent__not_in' => [0], + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'fields' => 'id=>parent', + ] + ); + + if (empty($posts)) { + // No posts with parents. + return; + } + + $tr_ids = []; + foreach ($posts as $child => $post) { + $tr_ids[$child] = $this->model->post->get($child, $target_language->slug); + } + $tr_ids = array_filter($tr_ids); + + if (empty($tr_ids)) { + // No translations. + return; + } + + foreach ($posts as $child => $post) { + if (empty($tr_ids[$child])) { + // Not translated. + continue; + } + + $tr_parent_post = $this->model->post->get($post, $target_language->slug); + + if (empty($tr_parent_post)) { + // The parent post is not translated. + continue; + } + + wp_update_post( + [ + 'ID' => $tr_ids[$child], + 'post_parent' => $tr_parent_post, + ] + ); + } + } + + /** + * Filters default query arguments when 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. + * Copied from PLL_Filters::term_exists_default_query_args. + * + * @since 3.3 + * + * @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 = []; + } + + if (!isset($defaults['lang'])) { + $defaults['lang'] = ''; + } + + return $defaults; + } + + /** + * Unsynchronizes translated post from the source. + * + * @since 3.3 + * + * @param int $source_post_id Source post ID. + * @param int $target_post_id Translated post ID. + * @param PLL_Language $target_language Translated post language object. + * + * @return void + */ + protected function maybe_unsync_posts($source_post_id, $target_post_id, $target_language) + { + if (!$this->sync_post_model->are_synchronized($source_post_id, $target_post_id)) { + return; + } + + $sync_posts = $this->sync_post_model->get($source_post_id); + + if (!isset($sync_posts[$target_language->slug]) || $sync_posts[$target_language->slug] !== $target_post_id) { + return; + } + + unset($sync_posts[$target_language->slug]); + + $this->sync_post_model->save_group($source_post_id, array_keys($sync_posts)); + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-strings-model.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-strings-model.php index 03428a81ac9d768b5fa14a72d4e6325f5676976d..8f910696156b30a4206c49a8f675964784d529ee 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-strings-model.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-strings-model.php @@ -1,119 +1,125 @@ imported_strings = array(); // Reset the imported strings array. - - $pll_mo = new PLL_MO(); - $pll_mo->import_from_db( $target_language ); - $registered_strings = PLL_Admin_Strings::get_strings(); - - $translations = $entry['data']; - - // Clone the $pll_mo element to avoid modifying the original one since we will then update it. - $pll_mo_clone = clone $pll_mo; - - // Remove the context for the translation entries to generate the same key between the translation strings - // and the database strings. - $translations->entries = $this->remove_context_from_translations( $translations->entries ); - - foreach ( $translations->entries as $entry ) { - if ( empty( $entry->translations ) ) { - $entry->translations = array( '' ); - } - - /** This filter is documented in /polylang/settings/table-string.php */ - $sanitized_translation = apply_filters( 'pll_sanitize_string_translation', $entry->translations[0], $entry->extracted_comments, $entry->context, $entry->singular ); - $sanitized_translation = wp_kses_post( $sanitized_translation ); - - if ( '' === $sanitized_translation ) { - // Don't overwrite a translation with an empty string. - continue; - } - - // Set a unique key for each entry to compare the original and translated strings. - $key = $entry->key(); - - if ( empty( $key ) ) { - continue; - } - if ( isset( $pll_mo_clone->entries[ $key ]->translations[0] ) || isset( $registered_strings[ md5( $key ) ] ) ) { - // Checks that the string did not exist or has been edited before updating. - if ( ! isset( $pll_mo_clone->entries[ $key ]->translations[0] ) || $pll_mo_clone->entries[ $key ]->translations[0] !== $sanitized_translation ) { - $pll_mo->add_entry( $pll_mo->make_entry( $entry->singular, $sanitized_translation ) ); - // Store the source strings as ids during the import process. - $this->imported_strings[] = $entry->singular; - } - } - } - - if ( 0 < count( $this->imported_strings ) ) { - $pll_mo->export_to_db( $target_language ); - - return $this->imported_strings; - } - - return new WP_Error( 'pll_translate_strings_no_imported_strings', __( 'No strings have been translated.', 'polylang-pro' ) ); - } - - /** - * Performs actions after a translation process. - * Does nothing. - * - * @since 3.7 - * - * @param string[] $ids The entity ids to process after translation. - * @param PLL_Language $target_language The target language. - * @return void - */ - public function do_after_process( array $ids, PLL_Language $target_language ) { //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - // Nothing to do. - } - - /** - * Removes the context for the translation entries. - * - * @since 3.2 - * @since 3.3 Moved from PLL_Import_Action to PLL_Import_Strings. - * @since 3.7 Moved from PLL_Import_Strings to PLL_Translation_Strings_Model. - * - * @param Translation_Entry[] $translations An array with all the entries. - * @return Translation_Entry[] An array with the same entries with an empty context. - */ - private function remove_context_from_translations( $translations ) { - foreach ( $translations as $translation_entry ) { - $translation_entry->context = ''; - } - return $translations; - } +class PLL_Translation_Strings_Model implements PLL_Translation_Data_Model_Interface +{ + /** + * Imported strings. + * + * @var string[] + */ + private $imported_strings = []; + + /** + * Handles the import of strings translations. + * + * @since 3.3 + * @since 3.7 Moved from PLL_Import_Strings to PLL_Translation_Strings_Model. + * + * @param array $entry { + * An array containing the translations data. + * + * @var string $type Either 'post', 'term' or 'string_translations'. + * @var int $id Id of the object in the database (if applicable). + * @var Translations $data Objects holding all the retrieved Translation_Entry objects. + * } + * + * @param PLL_Language $target_language The targeted language for import. + * + * @return string[]|WP_Error The imported strings, `WP_Error` on failure. + */ + public function translate(array $entry, PLL_Language $target_language) + { + $this->imported_strings = []; // Reset the imported strings array. + + $pll_mo = new PLL_MO(); + $pll_mo->import_from_db($target_language); + $registered_strings = PLL_Admin_Strings::get_strings(); + + $translations = $entry['data']; + + // Clone the $pll_mo element to avoid modifying the original one since we will then update it. + $pll_mo_clone = clone $pll_mo; + + // Remove the context for the translation entries to generate the same key between the translation strings + // and the database strings. + $translations->entries = $this->remove_context_from_translations($translations->entries); + + foreach ($translations->entries as $entry) { + if (empty($entry->translations)) { + $entry->translations = ['']; + } + + /** This filter is documented in /polylang/settings/table-string.php */ + $sanitized_translation = apply_filters('pll_sanitize_string_translation', $entry->translations[0], $entry->extracted_comments, $entry->context, $entry->singular); + $sanitized_translation = wp_kses_post($sanitized_translation); + + if ('' === $sanitized_translation) { + // Don't overwrite a translation with an empty string. + continue; + } + + // Set a unique key for each entry to compare the original and translated strings. + $key = $entry->key(); + + if (empty($key)) { + continue; + } + if (isset($pll_mo_clone->entries[$key]->translations[0]) || isset($registered_strings[md5($key)])) { + // Checks that the string did not exist or has been edited before updating. + if (!isset($pll_mo_clone->entries[$key]->translations[0]) || $pll_mo_clone->entries[$key]->translations[0] !== $sanitized_translation) { + $pll_mo->add_entry($pll_mo->make_entry($entry->singular, $sanitized_translation)); + // Store the source strings as ids during the import process. + $this->imported_strings[] = $entry->singular; + } + } + } + + if (0 < count($this->imported_strings)) { + $pll_mo->export_to_db($target_language); + + return $this->imported_strings; + } + + return new WP_Error('pll_translate_strings_no_imported_strings', __('No strings have been translated.', 'polylang-pro')); + } + + /** + * Performs actions after a translation process. + * Does nothing. + * + * @since 3.7 + * + * @param string[] $ids The entity ids to process after translation. + * @param PLL_Language $target_language The target language. + * + * @return void + */ + public function do_after_process(array $ids, PLL_Language $target_language) //phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + {// Nothing to do. + } + + /** + * Removes the context for the translation entries. + * + * @since 3.2 + * @since 3.3 Moved from PLL_Import_Action to PLL_Import_Strings. + * @since 3.7 Moved from PLL_Import_Strings to PLL_Translation_Strings_Model. + * + * @param Translation_Entry[] $translations An array with all the entries. + * + * @return Translation_Entry[] An array with the same entries with an empty context. + */ + private function remove_context_from_translations($translations) + { + foreach ($translations as $translation_entry) { + $translation_entry->context = ''; + } + + return $translations; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-term-metas.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-term-metas.php index e39603887301c33e5b3b51abb6f79b9b4f4ee3de..67e1c9c89a5ad07760fb3ddcd18e87321f49ab25 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-term-metas.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-term-metas.php @@ -1,35 +1,35 @@ model = &$polylang->model; - $this->sync_term_metas = &$polylang->sync->term_metas; - } - - /** - * Translates a term. - * - * @since 3.3 - * - * @param array $entry Properties array of an entry. - * @param PLL_Language $target_language The target language. - * @return int|WP_Error The translated term id, `WP_Error` on failure. - */ - public function translate( array $entry, PLL_Language $target_language ) { - if ( ! $entry['data'] instanceof Translations ) { - /* translators: %d is a term ID. */ - return new WP_Error( 'pll_translate_term_no_translations', sprintf( __( 'The term with ID %d could not be translated.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - - $source_term = get_term( $entry['id'] ); - - if ( ! $source_term instanceof WP_Term ) { - /* translators: %d is a term ID. */ - return new WP_Error( 'pll_translate_term_no_source_term', sprintf( __( 'The term with ID %d could not be translated as it doesn\'t exist.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - - $tr_term_name = $this->get_translated_term_name( $source_term, $entry['data'] ); - $tr_term_description = $this->get_translated_term_description( $source_term, $entry['data'] ); - $tr_term_id = $this->model->term->get( $entry['id'], $target_language ); - $translation_exists = $tr_term_id > 0; - - if ( $tr_term_id ) { - // The translation already exists. - $args = array(); - // Don't update name or description if not provided in translations. - if ( $source_term->name !== $tr_term_name ) { - $args['name'] = $tr_term_name; - } - if ( $source_term->description !== $tr_term_description ) { - $args['description'] = $tr_term_description; - } - - $tr_term = $this->model->term->update( $tr_term_id, $args ); - if ( is_wp_error( $tr_term ) ) { - /* translators: %d is a term ID. */ - return new WP_Error( 'pll_translate_update_term_failed', sprintf( __( 'The term with ID %d could not be updated.', 'polylang-pro' ), (int) $tr_term_id ) ); - } - } else { - $args = array( - 'translations' => $this->model->term->get_translations( $source_term->term_id ), - 'description' => $tr_term_description, - ); - - $tr_term = $this->model->term->insert( $tr_term_name, $source_term->taxonomy, $target_language, $args ); - if ( is_wp_error( $tr_term ) ) { - /* translators: %d is a term ID. */ - return new WP_Error( 'pll_translate_term_failed', sprintf( __( 'The term with ID %d could not be translated.', 'polylang-pro' ), (int) $entry['id'] ) ); - } - $tr_term_id = (int) $tr_term['term_id']; - } - - ( new PLL_Translation_Term_Metas( $this->sync_term_metas, $entry['data'] ) ) - ->translate( - $source_term->term_id, - $tr_term_id, - $target_language, - ! $translation_exists - ); - - /** @var WP_Term $tr_term */ - $tr_term = get_term( $tr_term_id ); - - /** - * Fires once a term has been translated. - * - * @since 3.7 - * - * @param WP_Term $source_term The source term. - * @param WP_Term $tr_term The target term. - * @param PLL_Language $target_language The language to translate into. - * @param Translations $translations The set of translations for the entry. - */ - do_action( 'pll_after_term_translation', $source_term, $tr_term, $target_language, $entry['data'] ); - - /** This action is documented in include/crud-terms.php. */ - do_action( 'pll_save_term', $tr_term_id, $source_term->taxonomy, $this->model->term->get_translations( $tr_term_id ) ); // Triggers the term metas synchronization. - - return $tr_term_id; - } - - /** - * Returns the translated term name if exists, the source name otherwise. - * - * @since 3.3 - * @since 3.7 $translations parameter added. - * - * @param WP_Term $source_term The source term object. - * @param Translations $translations Translated data object. - * @return string The translated name. - */ - private function get_translated_term_name( WP_Term $source_term, Translations $translations ) { - $translated = $translations->translate( - $source_term->name, - Context::to_string( array( Context::FIELD => PLL_Import_Export::TERM_NAME ) ) - ); - - return ! empty( $translated ) ? $translated : $source_term->name; - } - - /** - * Returns the translated term description if exists, the source description otherwise. - * - * @since 3.3 - * @since 3.7 $translations parameter added. - * - * @param WP_Term $source_term The source term object. - * @param Translations $translations Translated data object. - * @return string The translated description. - */ - private function get_translated_term_description( WP_Term $source_term, Translations $translations ) { - $translated = $translations->translate( - $source_term->description, - Context::to_string( array( Context::FIELD => PLL_Import_Export::TERM_DESCRIPTION ) ) - ); - - return ! empty( $translated ) ? $translated : $source_term->description; - } - - /** - * Assigns the parents to terms creating during the import. - * - * @since 3.3 - * @since 3.7 Renamed from `translate_parents`. - * - * @param int[] $ids Array of source term ids. - * @param PLL_Language $target_language The target language. - * @return void - */ - public function assign_parents( array $ids, PLL_Language $target_language ) { - // Get the terms with their parents (or 0). - $terms = get_terms( - array( - 'include' => $ids, - 'hide_empty' => false, - 'fields' => 'id=>parent', - ) - ); - - if ( ! is_array( $terms ) ) { - // No terms with parents. - return; - } - - // ‘id=>parent’ returns an array of numeric strings, so let's cast it into int. - $terms = array_map( 'intval', array_filter( $terms, 'is_numeric' ) ); - - // Keep only the terms that have a parent. - $terms = array_filter( $terms ); - - if ( empty( $terms ) ) { - // No terms with parents. - return; - } - - $tr_ids = array(); - foreach ( $terms as $child => $term_id ) { - $tr_ids[ $child ] = $this->model->term->get( $child, $target_language->slug ); - } - $tr_ids = array_filter( $tr_ids ); - - if ( empty( $tr_ids ) ) { - // No translations. - return; - } - - foreach ( $terms as $child => $term_id ) { - if ( empty( $tr_ids[ $child ] ) ) { - // Not translated. - continue; - } - - $tr_parent_term = $this->model->term->get( $term_id, $target_language->slug ); - if ( empty( $tr_parent_term ) ) { - // The parent term is not translated. - continue; - } - - $tr_term_id = $this->model->term->get( $tr_ids[ $child ], $target_language->slug ); - if ( empty( $tr_term_id ) ) { - continue; - } - - $tr_term = get_term( $tr_term_id ); - if ( ! $tr_term instanceof WP_Term ) { - continue; - } - - // Set term parent for shared slugs. - $this->model->term->update( $tr_term->term_id, array( 'parent' => $tr_parent_term ) ); - } - } +class PLL_Translation_Term_Model implements PLL_Translation_Data_Model_Interface +{ + use PLL_Translation_Object_Model_Trait; + + /** + * Used to manage languages and translations. + * + * @var PLL_Model + */ + private $model; + + /** + * Dependency to translate term metas. + * + * @var PLL_Sync_Term_Metas + */ + private $sync_term_metas; + + /** + * PLL_Translation_Term_Model constructor. + * + * @since 3.3 + * + * @param PLL_Settings|PLL_Admin $polylang Polylang object. + */ + public function __construct(&$polylang) + { + $this->model = &$polylang->model; + $this->sync_term_metas = &$polylang->sync->term_metas; + } + + /** + * Translates a term. + * + * @since 3.3 + * + * @param array $entry Properties array of an entry. + * @param PLL_Language $target_language The target language. + * + * @return int|WP_Error The translated term id, `WP_Error` on failure. + */ + public function translate(array $entry, PLL_Language $target_language) + { + if (!$entry['data'] instanceof Translations) { + /* translators: %d is a term ID. */ + return new WP_Error('pll_translate_term_no_translations', sprintf(__('The term with ID %d could not be translated.', 'polylang-pro'), (int) $entry['id'])); + } + + $source_term = get_term($entry['id']); + + if (!$source_term instanceof WP_Term) { + /* translators: %d is a term ID. */ + return new WP_Error('pll_translate_term_no_source_term', sprintf(__('The term with ID %d could not be translated as it doesn\'t exist.', 'polylang-pro'), (int) $entry['id'])); + } + + $tr_term_name = $this->get_translated_term_name($source_term, $entry['data']); + $tr_term_description = $this->get_translated_term_description($source_term, $entry['data']); + $tr_term_id = $this->model->term->get($entry['id'], $target_language); + $translation_exists = $tr_term_id > 0; + + if ($tr_term_id) { + // The translation already exists. + $args = []; + // Don't update name or description if not provided in translations. + if ($source_term->name !== $tr_term_name) { + $args['name'] = $tr_term_name; + } + if ($source_term->description !== $tr_term_description) { + $args['description'] = $tr_term_description; + } + + $tr_term = $this->model->term->update($tr_term_id, $args); + if (is_wp_error($tr_term)) { + /* translators: %d is a term ID. */ + return new WP_Error('pll_translate_update_term_failed', sprintf(__('The term with ID %d could not be updated.', 'polylang-pro'), (int) $tr_term_id)); + } + } else { + $args = [ + 'translations' => $this->model->term->get_translations($source_term->term_id), + 'description' => $tr_term_description, + ]; + + $tr_term = $this->model->term->insert($tr_term_name, $source_term->taxonomy, $target_language, $args); + if (is_wp_error($tr_term)) { + /* translators: %d is a term ID. */ + return new WP_Error('pll_translate_term_failed', sprintf(__('The term with ID %d could not be translated.', 'polylang-pro'), (int) $entry['id'])); + } + $tr_term_id = (int) $tr_term['term_id']; + } + + (new PLL_Translation_Term_Metas($this->sync_term_metas, $entry['data'])) + ->translate( + $source_term->term_id, + $tr_term_id, + $target_language, + !$translation_exists + ); + + /** @var WP_Term $tr_term */ + $tr_term = get_term($tr_term_id); + + /** + * Fires once a term has been translated. + * + * @since 3.7 + * + * @param WP_Term $source_term The source term. + * @param WP_Term $tr_term The target term. + * @param PLL_Language $target_language The language to translate into. + * @param Translations $translations The set of translations for the entry. + */ + do_action('pll_after_term_translation', $source_term, $tr_term, $target_language, $entry['data']); + + /** This action is documented in include/crud-terms.php. */ + do_action('pll_save_term', $tr_term_id, $source_term->taxonomy, $this->model->term->get_translations($tr_term_id)); // Triggers the term metas synchronization. + + return $tr_term_id; + } + + /** + * Returns the translated term name if exists, the source name otherwise. + * + * @since 3.3 + * @since 3.7 $translations parameter added. + * + * @param WP_Term $source_term The source term object. + * @param Translations $translations Translated data object. + * + * @return string The translated name. + */ + private function get_translated_term_name(WP_Term $source_term, Translations $translations) + { + $translated = $translations->translate( + $source_term->name, + Context::to_string([Context::FIELD => PLL_Import_Export::TERM_NAME]) + ); + + return !empty($translated) ? $translated : $source_term->name; + } + + /** + * Returns the translated term description if exists, the source description otherwise. + * + * @since 3.3 + * @since 3.7 $translations parameter added. + * + * @param WP_Term $source_term The source term object. + * @param Translations $translations Translated data object. + * + * @return string The translated description. + */ + private function get_translated_term_description(WP_Term $source_term, Translations $translations) + { + $translated = $translations->translate( + $source_term->description, + Context::to_string([Context::FIELD => PLL_Import_Export::TERM_DESCRIPTION]) + ); + + return !empty($translated) ? $translated : $source_term->description; + } + + /** + * Assigns the parents to terms creating during the import. + * + * @since 3.3 + * @since 3.7 Renamed from `translate_parents`. + * + * @param int[] $ids Array of source term ids. + * @param PLL_Language $target_language The target language. + * + * @return void + */ + public function assign_parents(array $ids, PLL_Language $target_language) + { + // Get the terms with their parents (or 0). + $terms = get_terms( + [ + 'include' => $ids, + 'hide_empty' => false, + 'fields' => 'id=>parent', + ] + ); + + if (!is_array($terms)) { + // No terms with parents. + return; + } + + // ‘id=>parent’ returns an array of numeric strings, so let's cast it into int. + $terms = array_map('intval', array_filter($terms, 'is_numeric')); + + // Keep only the terms that have a parent. + $terms = array_filter($terms); + + if (empty($terms)) { + // No terms with parents. + return; + } + + $tr_ids = []; + foreach ($terms as $child => $term_id) { + $tr_ids[$child] = $this->model->term->get($child, $target_language->slug); + } + $tr_ids = array_filter($tr_ids); + + if (empty($tr_ids)) { + // No translations. + return; + } + + foreach ($terms as $child => $term_id) { + if (empty($tr_ids[$child])) { + // Not translated. + continue; + } + + $tr_parent_term = $this->model->term->get($term_id, $target_language->slug); + if (empty($tr_parent_term)) { + // The parent term is not translated. + continue; + } + + $tr_term_id = $this->model->term->get($tr_ids[$child], $target_language->slug); + if (empty($tr_term_id)) { + continue; + } + + $tr_term = get_term($tr_term_id); + if (!$tr_term instanceof WP_Term) { + continue; + } + + // Set term parent for shared slugs. + $this->model->term->update($tr_term->term_id, ['parent' => $tr_parent_term]); + } + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-blocks.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-blocks.php index 1c6b288b24594e9e39b9d15f7ac2b67f7d3117a0..86f8cb434fdba0751cad8b52dc6145e8f74a691f 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-blocks.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-blocks.php @@ -1,7 +1,4 @@ Polylang placeholder do not modify'; - - /** - * Holds the blocks parsed by the WP_Block_Parser. - * - * @var array[] - * - * @phpstan-var array - */ - private $blocks; - - /** - * A reference to the block parsing rules. - * - * @var PLL_Translation_Block_Parsing_Rules - */ - private $parsing_rules; - - /** - * Holds the callback to be applied on each block, including the nested blocks. - * - * @var callable - */ - private $callback; - - /** - * HTML delimiter used in {@see PLL_Translation_Walker_Blocks::parse_as_html()}. - * - * @var string - * @phpstan-var non-empty-string - */ - private $placeholder_delimiter; - - /** - * PLL_Content_Walker_Blocks constructor. - * - * @since 3.3 - * - * @param string $content An original (post?) content. - */ - public function __construct( $content ) { - $this->parsing_rules = new PLL_Translation_Block_Parsing_Rules(); - $this->blocks = parse_blocks( $content ); - $this->placeholder_delimiter = sprintf( '', wp_rand( 1, 100000 ) ); - } - - /** - * Walks through the blocks and nested blocks to apply a callback on every one of them. - * - * @since 3.3 - * - * @param callable $callback A callable to be applied on each block. - * @return string The walked content, eventually transformed by the callback. - */ - public function walk( $callback ) { - $this->callback = $callback; - $this->blocks = array_map( array( $this, 'apply' ), $this->blocks ); - - return serialize_blocks( $this->blocks ); - } - - /** - * Recursively applies the callback provided to the {@see PLL_Translation_Walker_Blocks::walk()} method on a block. - * Searches for translatable strings matching rules defined by {@see PLL_Translation_Rules_Block} and passes those to the callback function. - * Delegates to {@see PLL_Translation_Walker_Classic} when no parsing rules match the current block being parsed. - * - * @since 3.3 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @return array An array mimicking a WP_Block_Parser_Block object. - * - * @phpstan-param Block $block - * @phpstan-return Block - */ - private function apply( $block ) { - if ( ! empty( $block['innerBlocks'] ) ) { - $block['innerBlocks'] = array_map( array( $this, 'apply' ), $block['innerBlocks'] ); - } - - if ( ! $this->parsing_rules->should_be_parsed( $block ) ) { - // No contents to translate. - return $block; - } - - $attributes_to_translate = $this->parsing_rules->get_attributes_to_translate( $block ); - if ( ! empty( $attributes_to_translate ) ) { - $block['attrs'] = $this->parse_attributes( $block['attrs'], $attributes_to_translate ); - } - - // Handles specific blocks. - $block = $this->parse_specific_blocks( $block ); - - if ( $this->parsing_rules->has_parsing_rules( $block ) ) { - // A known block that will be parsed with Xpath rules. - return $this->parse_with_rules( $block ); - } - - // A block that will be parsed as HTML. - return $this->parse_as_html( $block ); - } - - /** - * Recursively applies the callback provided to the {@see PLL_Translation_Walker_Blocks::walk()} method on a block. - * Searches for translatable strings matching rules defined by {@see PLL_Translation_Rules_Block} and passes those to the callback function. - * - * @since 3.3 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @return array An array mimicking a WP_Block_Parser_Block object. - * - * @phpstan-param Block $block - * @phpstan-return Block - */ - private function parse_with_rules( $block ) { - // Get the whole block's content to parse with placeholders. - $source_string = $this->get_block_content_to_parse( $block ); - - // Parse by using our pre-defined rules. - $parsed_strings = $this->parsing_rules->set_block_name( $block['blockName'] )->parse( $source_string ); - - if ( empty( $parsed_strings ) ) { - // Nothing to translate. - return $block; - } - - $to_replace = array(); - - foreach ( $parsed_strings as $node_path => $parsed_string ) { - $entry = new Translation_Entry( - array( - 'singular' => $parsed_string, - 'context' => Context::to_string( - array( - Context::FIELD => PLL_Import_Export::POST_CONTENT, - ) - ), - ) - ); - - $result = call_user_func_array( $this->callback, array( &$entry ) ); - - if ( ! $result instanceof Translation_Entry || empty( $result->translations ) ) { - continue; - } - - $translation = reset( $result->translations ); - - if ( '' === trim( $translation ) ) { - continue; - } - - $to_replace[ $node_path ] = $translation; - } - - if ( empty( $to_replace ) ) { - // No need to modify things if there are no translations. - return $block; - } - - $result_string = ( new PLL_DOM_Content( $source_string ) )->replace_content( $to_replace ); - - // Put the content back into the block. - return $this->update_block_with_content( $block, $result_string ); - } - - /** - * Parses a block's contents as HTML and applies the callback provided to the - * {@see PLL_Translation_Walker_Blocks::walk()} method on these contents. Uses {@see PLL_Translation_Walker_Classic}. - * - * @since 3.3 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @return array An array mimicking a WP_Block_Parser_Block object. - * - * @phpstan-param Block $block - * @phpstan-return Block - */ - private function parse_as_html( $block ) { - // Get the whole block's content to parse with placeholders. - $source_string = $this->get_block_content_to_parse( $block ); - - $walker = new PLL_Translation_Walker_Classic( $source_string, array( self::BLOCK_PLACEHOLDER ) ); - $result_string = $walker->walk( $this->callback ); - - // Put the content back into the block. - return $this->update_block_with_content( $block, $result_string ); - } - - /** - * Returns a block's content as a string and with placeholders in place of sub-blocks, ready to be parsed. - * - * @since 3.3 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @return string The block's content as a string and with placeholders in place of sub-blocks. - * - * @phpstan-param Block $block - */ - private function get_block_content_to_parse( array $block ) { - $content = array_map( - function ( $content_part ) { - return is_string( $content_part ) ? $content_part : self::BLOCK_PLACEHOLDER; - }, - $block['innerContent'] - ); - - return implode( '', $content ); - } - - /** - * Puts a translated content back into a block. - * - * @since 3.3 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @param string $content The content to put back into the block. - * @return array An array mimicking a WP_Block_Parser_Block object. - * - * @phpstan-param Block $block - * @phpstan-return Block - */ - private function update_block_with_content( array $block, $content ) { - // Explode by using a delimiter. - $content = str_replace( - self::BLOCK_PLACEHOLDER, - $this->placeholder_delimiter . self::BLOCK_PLACEHOLDER . $this->placeholder_delimiter, - $content - ); - $content = explode( $this->placeholder_delimiter, $content ); - // Replace placeholders by `null` values. - $block['innerContent'] = array_map( - function ( $content_part ) { - return self::BLOCK_PLACEHOLDER === $content_part ? null : $content_part; - }, - $content - ); - // Make innerHTML consistent. - $block['innerHTML'] = implode( '', $block['innerContent'] ); - - return $block; - } - - /** - * Returns the translatable block attributes and passes them to the callback function. - * - * @since 3.3 - * @since 3.6 Now pass an array of attributes as first param instead of a block. - * - * @param mixed $attrs An array of attributes to parse, or an attribute value. - * @param string[] $attributes_to_translate Optional. An array of attributes to translate. - * @return mixed An array of parsed attributes, or a translated attribute value. - * - * @phpstan-param array $attributes_to_translate - * @phpstan-return ( - * $attrs is array ? array : ( - * $attrs is scalar ? string : mixed - * ) - * ) - */ - private function parse_attributes( $attrs, array $attributes_to_translate = array() ) { - if ( ! empty( $attributes_to_translate ) ) { - // We have sub-keys to match. - if ( ! is_array( $attrs ) ) { - // No more attributes. - return $attrs; - } - - $matcher = new PLL_Format_Util(); - - foreach ( $attributes_to_translate as $attribute_name => $attribute_sub_fields ) { - // Find the attributes matching `$attribute_name` (may contain wildcards). - $entries = $matcher->filter_list( $attrs, (string) $attribute_name ); - $sub_field = is_array( $attribute_sub_fields ) ? $attribute_sub_fields : array(); - - foreach ( $entries as $key => $values ) { - // Parse sub-attributes. - $attrs[ $key ] = $this->parse_attributes( $attrs[ $key ], $sub_field ); - } - } - - return $attrs; - } - - if ( is_array( $attrs ) ) { - // No more sub-keys to match but we still have sub-attributes: return everything. - return array_map( array( $this, 'parse_attributes' ), $attrs ); - } - - if ( is_scalar( $attrs ) ) { - // Maybe translate the value. - if ( '' === (string) $attrs ) { - // No need to translate. - return $attrs; - } - - $result = $this->add_entry_and_translate( (string) $attrs ); - - if ( '' === $result ) { - // No translations: return the original value. - return $attrs; - } - - return $result; - } - - // Not a value that can be translated. - return $attrs; - } - - /** - * Parses specific blocks. - * - * @since 3.6 - * - * @param array $block An associative array mimicking a WP_Block_Parser_Block object. - * @return array An array mimicking a WP_Block_Parser_Block object. - * - * @phpstan-param Block $block - * @phpstan-return Block - */ - private function parse_specific_blocks( array $block ): array { - if ( 'core/more' === $block['blockName'] && isset( $block['attrs']['customText'] ) ) { - // Special case for 'core/more' block content that need to be updated according to its translated attribute. - $core_more_content = ""; - $block = $this->update_block_with_content( $block, $core_more_content ); - } - - return $block; - } - - /** - * Adds the translation entry and return the translation if there is one. - * - * @since 3.6 - * - * @param string $attribute_value The attribute value. - * @return string Translated entry, empty if none. - */ - private function add_entry_and_translate( string $attribute_value ): string { - $entry = new Translation_Entry( - array( - 'singular' => trim( $attribute_value ), - 'context' => Context::to_string( - array( - Context::FIELD => PLL_Import_Export::POST_CONTENT, - ) - ), - ) - ); - - $result = call_user_func_array( $this->callback, array( &$entry ) ); - if ( ! $result instanceof Translation_Entry || empty( $result->translations[0] ) ) { - return ''; - } - - return $result->translations[0]; - } +class PLL_Translation_Walker_Blocks implements PLL_Translation_Walker_Interface +{ + /** + * Placeholder for inner blocks, used in exported contents. + * + * @var string + */ + const BLOCK_PLACEHOLDER = '
Polylang placeholder do not modify
'; + + /** + * Holds the blocks parsed by the WP_Block_Parser. + * + * @var array[] + * + * @phpstan-var array + */ + private $blocks; + + /** + * A reference to the block parsing rules. + * + * @var PLL_Translation_Block_Parsing_Rules + */ + private $parsing_rules; + + /** + * Holds the callback to be applied on each block, including the nested blocks. + * + * @var callable + */ + private $callback; + + /** + * HTML delimiter used in {@see PLL_Translation_Walker_Blocks::parse_as_html()}. + * + * @var string + * + * @phpstan-var non-empty-string + */ + private $placeholder_delimiter; + + /** + * PLL_Content_Walker_Blocks constructor. + * + * @since 3.3 + * + * @param string $content An original (post?) content. + */ + public function __construct($content) + { + $this->parsing_rules = new PLL_Translation_Block_Parsing_Rules(); + $this->blocks = parse_blocks($content); + $this->placeholder_delimiter = sprintf('', wp_rand(1, 100000)); + } + + /** + * Walks through the blocks and nested blocks to apply a callback on every one of them. + * + * @since 3.3 + * + * @param callable $callback A callable to be applied on each block. + * + * @return string The walked content, eventually transformed by the callback. + */ + public function walk($callback) + { + $this->callback = $callback; + $this->blocks = array_map([$this, 'apply'], $this->blocks); + + return serialize_blocks($this->blocks); + } + + /** + * Recursively applies the callback provided to the {@see PLL_Translation_Walker_Blocks::walk()} method on a block. + * Searches for translatable strings matching rules defined by {@see PLL_Translation_Rules_Block} and passes those to the callback function. + * Delegates to {@see PLL_Translation_Walker_Classic} when no parsing rules match the current block being parsed. + * + * @since 3.3 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * + * @return array An array mimicking a WP_Block_Parser_Block object. + * + * @phpstan-param Block $block + * + * @phpstan-return Block + */ + private function apply($block) + { + if (!empty($block['innerBlocks'])) { + $block['innerBlocks'] = array_map([$this, 'apply'], $block['innerBlocks']); + } + + if (!$this->parsing_rules->should_be_parsed($block)) { + // No contents to translate. + return $block; + } + + $attributes_to_translate = $this->parsing_rules->get_attributes_to_translate($block); + if (!empty($attributes_to_translate)) { + $block['attrs'] = $this->parse_attributes($block['attrs'], $attributes_to_translate); + } + + // Handles specific blocks. + $block = $this->parse_specific_blocks($block); + + if ($this->parsing_rules->has_parsing_rules($block)) { + // A known block that will be parsed with Xpath rules. + return $this->parse_with_rules($block); + } + + // A block that will be parsed as HTML. + return $this->parse_as_html($block); + } + + /** + * Recursively applies the callback provided to the {@see PLL_Translation_Walker_Blocks::walk()} method on a block. + * Searches for translatable strings matching rules defined by {@see PLL_Translation_Rules_Block} and passes those to the callback function. + * + * @since 3.3 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * + * @return array An array mimicking a WP_Block_Parser_Block object. + * + * @phpstan-param Block $block + * + * @phpstan-return Block + */ + private function parse_with_rules($block) + { + // Get the whole block's content to parse with placeholders. + $source_string = $this->get_block_content_to_parse($block); + + // Parse by using our pre-defined rules. + $parsed_strings = $this->parsing_rules->set_block_name($block['blockName'])->parse($source_string); + + if (empty($parsed_strings)) { + // Nothing to translate. + return $block; + } + + $to_replace = []; + + foreach ($parsed_strings as $node_path => $parsed_string) { + $entry = new Translation_Entry( + [ + 'singular' => $parsed_string, + 'context' => Context::to_string( + [ + Context::FIELD => PLL_Import_Export::POST_CONTENT, + ] + ), + ] + ); + + $result = call_user_func_array($this->callback, [&$entry]); + + if (!$result instanceof Translation_Entry || empty($result->translations)) { + continue; + } + + $translation = reset($result->translations); + + if ('' === trim($translation)) { + continue; + } + + $to_replace[$node_path] = $translation; + } + + if (empty($to_replace)) { + // No need to modify things if there are no translations. + return $block; + } + + $result_string = (new PLL_DOM_Content($source_string))->replace_content($to_replace); + + // Put the content back into the block. + return $this->update_block_with_content($block, $result_string); + } + + /** + * Parses a block's contents as HTML and applies the callback provided to the + * {@see PLL_Translation_Walker_Blocks::walk()} method on these contents. Uses {@see PLL_Translation_Walker_Classic}. + * + * @since 3.3 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * + * @return array An array mimicking a WP_Block_Parser_Block object. + * + * @phpstan-param Block $block + * + * @phpstan-return Block + */ + private function parse_as_html($block) + { + // Get the whole block's content to parse with placeholders. + $source_string = $this->get_block_content_to_parse($block); + + $walker = new PLL_Translation_Walker_Classic($source_string, [self::BLOCK_PLACEHOLDER]); + $result_string = $walker->walk($this->callback); + + // Put the content back into the block. + return $this->update_block_with_content($block, $result_string); + } + + /** + * Returns a block's content as a string and with placeholders in place of sub-blocks, ready to be parsed. + * + * @since 3.3 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * + * @return string The block's content as a string and with placeholders in place of sub-blocks. + * + * @phpstan-param Block $block + */ + private function get_block_content_to_parse(array $block) + { + $content = array_map( + function ($content_part) { + return is_string($content_part) ? $content_part : self::BLOCK_PLACEHOLDER; + }, + $block['innerContent'] + ); + + return implode('', $content); + } + + /** + * Puts a translated content back into a block. + * + * @since 3.3 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * @param string $content The content to put back into the block. + * + * @return array An array mimicking a WP_Block_Parser_Block object. + * + * @phpstan-param Block $block + * + * @phpstan-return Block + */ + private function update_block_with_content(array $block, $content) + { + // Explode by using a delimiter. + $content = str_replace( + self::BLOCK_PLACEHOLDER, + $this->placeholder_delimiter.self::BLOCK_PLACEHOLDER.$this->placeholder_delimiter, + $content + ); + $content = explode($this->placeholder_delimiter, $content); + // Replace placeholders by `null` values. + $block['innerContent'] = array_map( + function ($content_part) { + return self::BLOCK_PLACEHOLDER === $content_part ? null : $content_part; + }, + $content + ); + // Make innerHTML consistent. + $block['innerHTML'] = implode('', $block['innerContent']); + + return $block; + } + + /** + * Returns the translatable block attributes and passes them to the callback function. + * + * @since 3.3 + * @since 3.6 Now pass an array of attributes as first param instead of a block. + * + * @param mixed $attrs An array of attributes to parse, or an attribute value. + * @param string[] $attributes_to_translate Optional. An array of attributes to translate. + * + * @return mixed An array of parsed attributes, or a translated attribute value. + * + * @phpstan-param array $attributes_to_translate + * + * @phpstan-return ( + * $attrs is array ? array : ( + * $attrs is scalar ? string : mixed + * ) + * ) + */ + private function parse_attributes($attrs, array $attributes_to_translate = []) + { + if (!empty($attributes_to_translate)) { + // We have sub-keys to match. + if (!is_array($attrs)) { + // No more attributes. + return $attrs; + } + + $matcher = new PLL_Format_Util(); + + foreach ($attributes_to_translate as $attribute_name => $attribute_sub_fields) { + // Find the attributes matching `$attribute_name` (may contain wildcards). + $entries = $matcher->filter_list($attrs, (string) $attribute_name); + $sub_field = is_array($attribute_sub_fields) ? $attribute_sub_fields : []; + + foreach ($entries as $key => $values) { + // Parse sub-attributes. + $attrs[$key] = $this->parse_attributes($attrs[$key], $sub_field); + } + } + + return $attrs; + } + + if (is_array($attrs)) { + // No more sub-keys to match but we still have sub-attributes: return everything. + return array_map([$this, 'parse_attributes'], $attrs); + } + + if (is_scalar($attrs)) { + // Maybe translate the value. + if ('' === (string) $attrs) { + // No need to translate. + return $attrs; + } + + $result = $this->add_entry_and_translate((string) $attrs); + + if ('' === $result) { + // No translations: return the original value. + return $attrs; + } + + return $result; + } + + // Not a value that can be translated. + return $attrs; + } + + /** + * Parses specific blocks. + * + * @since 3.6 + * + * @param array $block An associative array mimicking a WP_Block_Parser_Block object. + * + * @return array An array mimicking a WP_Block_Parser_Block object. + * + * @phpstan-param Block $block + * + * @phpstan-return Block + */ + private function parse_specific_blocks(array $block): array + { + if ('core/more' === $block['blockName'] && isset($block['attrs']['customText'])) { + // Special case for 'core/more' block content that need to be updated according to its translated attribute. + $core_more_content = ""; + $block = $this->update_block_with_content($block, $core_more_content); + } + + return $block; + } + + /** + * Adds the translation entry and return the translation if there is one. + * + * @since 3.6 + * + * @param string $attribute_value The attribute value. + * + * @return string Translated entry, empty if none. + */ + private function add_entry_and_translate(string $attribute_value): string + { + $entry = new Translation_Entry( + [ + 'singular' => trim($attribute_value), + 'context' => Context::to_string( + [ + Context::FIELD => PLL_Import_Export::POST_CONTENT, + ] + ), + ] + ); + + $result = call_user_func_array($this->callback, [&$entry]); + if (!$result instanceof Translation_Entry || empty($result->translations[0])) { + return ''; + } + + return $result->translations[0]; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-classic.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-classic.php index 1ede2356d5e98b38351a10fe9b1b83540821bccd..ca918c22aa4ded5ea0d93cc712c681d124d769e4 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-classic.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-classic.php @@ -1,109 +1,113 @@ non_translatable_contents = $non_translatable_contents; - $this->content = $content; - } + /** + * PLL_Content_Walker_Classic constructor. + * + * @since 3.3 + * + * @param string $content Classic editor HTML content. + * @param string[] $non_translatable_contents List of contents that are not translatable, like placeholders. + */ + public function __construct($content, array $non_translatable_contents = []) + { + $this->non_translatable_contents = $non_translatable_contents; + $this->content = $content; + } - /** - * Applies the defined callback on the content, and then returns a transformed content. - * - * @since 3.3 - * - * @param callable $callback A transformation to apply to the content, whether it is for export or import. - * @return string - */ - public function walk( $callback ) { - $this->callback = $callback; + /** + * Applies the defined callback on the content, and then returns a transformed content. + * + * @since 3.3 + * + * @param callable $callback A transformation to apply to the content, whether it is for export or import. + * + * @return string + */ + public function walk($callback) + { + $this->callback = $callback; - return $this->apply( $this->content ); - } + return $this->apply($this->content); + } - /** - * Applies a callback on a given post content, whether it is to add a translation entry or translate it. - * - * @since 3.3 - * - * @param string $content A post content to apply a callback onto. - * @return string The processed content. - */ - private function apply( $content ) { - $translatable_content = str_replace( $this->non_translatable_contents, '', $content ); - if ( '' === str_replace( "\n", '', wp_strip_all_tags( $translatable_content ) ) ) { - return $content; - } + /** + * Applies a callback on a given post content, whether it is to add a translation entry or translate it. + * + * @since 3.3 + * + * @param string $content A post content to apply a callback onto. + * + * @return string The processed content. + */ + private function apply($content) + { + $translatable_content = str_replace($this->non_translatable_contents, '', $content); + if ('' === str_replace("\n", '', wp_strip_all_tags($translatable_content))) { + return $content; + } - $args = array( - 'singular' => $content, - 'context' => Context::to_string( - array( - Context::FIELD => PLL_Import_Export::POST_CONTENT, - ) - ), - ); - $entry = new Translation_Entry( $args ); + $args = [ + 'singular' => $content, + 'context' => Context::to_string( + [ + Context::FIELD => PLL_Import_Export::POST_CONTENT, + ] + ), + ]; + $entry = new Translation_Entry($args); - $return = call_user_func_array( $this->callback, array( &$entry ) ); + $return = call_user_func_array($this->callback, [&$entry]); - if ( $return instanceof Translation_Entry ) { - return $this->maybe_translate_entry( $return ); - } + if ($return instanceof Translation_Entry) { + return $this->maybe_translate_entry($return); + } - return $content; - } + return $content; + } - /** - * Checks if the translation entry exists and return it, otherwise return the source text. - * - * @since 3.3 - * - * @param Translation_Entry $entry A translation entry parsed from a translation document. - * @return string The translated string. - */ - private function maybe_translate_entry( $entry ) { - return isset( $entry->translations[0] ) ? $entry->translations[0] : $entry->singular; - } + /** + * Checks if the translation entry exists and return it, otherwise return the source text. + * + * @since 3.3 + * + * @param Translation_Entry $entry A translation entry parsed from a translation document. + * + * @return string The translated string. + */ + private function maybe_translate_entry($entry) + { + return isset($entry->translations[0]) ? $entry->translations[0] : $entry->singular; + } } diff --git a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-factory.php b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-factory.php index aa52a834423ac30849b0026046562e9930e4f221..82317fcf7c655d95785c1716fb5eb01a16b619e9 100644 --- a/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-factory.php +++ b/__plugins/polylang-pro-3.7.6/services/translation/translation-walker-factory.php @@ -1,30 +1,31 @@ admin_url('admin-ajax.php'), - 'nonce' => wp_create_nonce('ajax-nonce'), + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('ajax-nonce'), 'price_slider' => [ - 'currency_symbol' => html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8'), - 'currency_format' => html_entity_decode(get_woocommerce_price_format(), ENT_QUOTES, 'UTF-8'), + 'currency_symbol' => html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8'), + 'currency_format' => html_entity_decode(get_woocommerce_price_format(), ENT_QUOTES, 'UTF-8'), 'currency_format_num_decimals' => wc_get_price_decimals(), - 'currency_format_decimal_sep' => wc_get_price_decimal_separator(), + 'currency_format_decimal_sep' => wc_get_price_decimal_separator(), 'currency_format_thousand_sep' => wc_get_price_thousand_separator(), ], ]); @@ -137,20 +137,20 @@ class AdminOptions public function registerContext($context): array { $context['main_menu'] = Timber::get_menu('main_menu'); - $context['site_url'] = home_url('/'); + $context['site_url'] = home_url('/'); $context['site_name'] = get_bloginfo('name'); - $context['logo'] = get_custom_logo(); + $context['logo'] = get_custom_logo(); if (function_exists('pll_the_languages')) { $context['languages'] = pll_the_languages(['raw' => 1, 'echo' => 0]); } if (function_exists('WC') && WC()->cart) { - $context['cart'] = WC()->cart; + $context['cart'] = WC()->cart; $context['currency_symbol'] = get_woocommerce_currency_symbol(); - $context['cart_link'] = get_localized_wc_page_url('cart'); - $context['checkout_link'] = get_localized_wc_page_url('checkout'); - $context['account_link'] = get_localized_wc_page_url('myaccount'); + $context['cart_link'] = get_localized_wc_page_url('cart'); + $context['checkout_link'] = get_localized_wc_page_url('checkout'); + $context['account_link'] = get_localized_wc_page_url('myaccount'); } return $context; diff --git a/app/Controllers/AccountController.php b/app/Controllers/AccountController.php index 2a42d2e6651f9a55d75b189275314dfa19980742..0746a059f42a0ab0e79ab139d84edff829e8397e 100644 --- a/app/Controllers/AccountController.php +++ b/app/Controllers/AccountController.php @@ -20,12 +20,14 @@ class AccountController extends Controller { $this->data['post'] = Timber::get_post(); $this->data['cart'] = WC()->cart; + return $this->data; } public function account(): array { $this->data['post'] = Timber::get_post(); + return $this->data; } } diff --git a/app/Controllers/Controller.php b/app/Controllers/Controller.php index 2585fbac8e61be7b2c9b63af6685dc565e084fb2..8912624c1d41f9c4b89703c18621fa1b57e48d5b 100644 --- a/app/Controllers/Controller.php +++ b/app/Controllers/Controller.php @@ -1,6 +1,7 @@ attribute_name; - $attributeTaxonomy = 'pa_' . $attributeName; + $attributeTaxonomy = 'pa_'.$attributeName; $productIds = $this->getArchiveProductIds([$attributeTaxonomy], true); $terms = $this->getAttributeTermsForProducts($attributeTaxonomy, $productIds); @@ -78,8 +78,8 @@ class ProductController extends Controller $queryArgs['paged'], $queryArgs['page'], $queryArgs['product-page'], - $queryArgs['filter_' . $attributeName], - $queryArgs['query_type_' . $attributeName] + $queryArgs['filter_'.$attributeName], + $queryArgs['query_type_'.$attributeName] ); $attributes[] = [ @@ -88,7 +88,7 @@ class ProductController extends Controller $term->filter_url = add_query_arg( array_merge( $queryArgs, - ['filter_' . $attributeName => $term->slug] + ['filter_'.$attributeName => $term->slug] ), $this->getCurrentArchiveUrl() ); @@ -134,7 +134,7 @@ class ProductController extends Controller foreach ($excludedTaxonomies as $taxonomy) { if (str_starts_with($taxonomy, 'pa_')) { - unset($queryArgs['filter_' . substr($taxonomy, 3)], $queryArgs['query_type_' . substr($taxonomy, 3)]); + unset($queryArgs['filter_'.substr($taxonomy, 3)], $queryArgs['query_type_'.substr($taxonomy, 3)]); } } } @@ -325,5 +325,4 @@ class ProductController extends Controller return $this->data; } - } diff --git a/app/Extensions/SyncWCPricesPolylang.php b/app/Extensions/SyncWCPricesPolylang.php index 13fd0ab3ca6421d2fd18e680fce6eb51e0a37ada..c7032d0c7f9553081422bb529584bb30f90c1e98 100644 --- a/app/Extensions/SyncWCPricesPolylang.php +++ b/app/Extensions/SyncWCPricesPolylang.php @@ -1,8 +1,8 @@ is_type('variable')) { self::syncVariableProductPrices($productId); + return; } @@ -77,13 +78,13 @@ final class SyncWCPricesPolylang public static function syncVariationPrices(int $variationId, int $loop): void { - if (self::$syncing || ! self::canSync()) { + if (self::$syncing || !self::canSync()) { return; } $variation = wc_get_product($variationId); - if (! $variation || ! $variation->is_type('variation')) { + if (!$variation || !$variation->is_type('variation')) { return; } @@ -114,7 +115,7 @@ final class SyncWCPricesPolylang $translatedProduct = wc_get_product($translatedProductId); - if (! $translatedProduct || $translatedProduct->is_type('variable')) { + if (!$translatedProduct || $translatedProduct->is_type('variable')) { continue; } @@ -133,7 +134,7 @@ final class SyncWCPricesPolylang { $product = wc_get_product($productId); - if (! $product || ! $product->is_type('variable')) { + if (!$product || !$product->is_type('variable')) { return; } @@ -142,7 +143,7 @@ final class SyncWCPricesPolylang foreach ($sourceVariationIds as $sourceVariationId) { $sourceVariation = wc_get_product($sourceVariationId); - if (! $sourceVariation) { + if (!$sourceVariation) { continue; } @@ -174,7 +175,7 @@ final class SyncWCPricesPolylang $translatedVariation = wc_get_product($translatedVariationId); - if (! $translatedVariation || ! $translatedVariation->is_type('variation')) { + if (!$translatedVariation || !$translatedVariation->is_type('variation')) { continue; } @@ -195,7 +196,7 @@ final class SyncWCPricesPolylang { $sourceParentId = $variation->get_parent_id(); - if (! $sourceParentId) { + if (!$sourceParentId) { return; } @@ -228,7 +229,7 @@ final class SyncWCPricesPolylang $translatedVariation = wc_get_product((int) $translatedSiblingIds[$variationIndex]); - if (! $translatedVariation || ! $translatedVariation->is_type('variation')) { + if (!$translatedVariation || !$translatedVariation->is_type('variation')) { continue; } @@ -258,10 +259,10 @@ final class SyncWCPricesPolylang $targetVariation = wc_get_product((int) $targetVariationIds[$index]); if ( - ! $sourceVariation || - ! $targetVariation || - ! $sourceVariation->is_type('variation') || - ! $targetVariation->is_type('variation') + !$sourceVariation || + !$targetVariation || + !$sourceVariation->is_type('variation') || + !$targetVariation->is_type('variation') ) { continue; } @@ -285,7 +286,7 @@ final class SyncWCPricesPolylang foreach ($translations as $translatedProductId) { $translatedProductId = (int) $translatedProductId; - if (! $translatedProductId) { + if (!$translatedProductId) { continue; } @@ -324,4 +325,4 @@ final class SyncWCPricesPolylang } } -SyncWCPricesPolylang::init(); \ No newline at end of file +SyncWCPricesPolylang::init(); diff --git a/app/helpers.php b/app/helpers.php index b7a25ac4d4dd778f54b98d308d5b9847b369f21b..7192e7f07d9b29c4d16604fda50627024a93f438 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -143,14 +143,14 @@ if (!function_exists('get_cart_data')) { } $item_data = [ - 'id' => $cart_item['product_id'], - 'name' => $_product->get_name(), - 'link' => get_permalink($cart_item['product_id']), - 'thumbnail' => $_product->get_image(), - 'quantity' => $cart_item['quantity'], + 'id' => $cart_item['product_id'], + 'name' => $_product->get_name(), + 'link' => get_permalink($cart_item['product_id']), + 'thumbnail' => $_product->get_image(), + 'quantity' => $cart_item['quantity'], 'cart_item_key' => $cart_item_key, 'regular_price' => wc_price($_product->get_regular_price()), - 'sale_price' => $sale_price, + 'sale_price' => $sale_price, ]; $cart_data[] = $item_data; @@ -216,9 +216,9 @@ if (!function_exists('get_localized_wc_page_url')) { } return match ($page) { - 'cart' => function_exists('wc_get_cart_url') ? wc_get_cart_url() : home_url('/'), + 'cart' => function_exists('wc_get_cart_url') ? wc_get_cart_url() : home_url('/'), 'checkout' => function_exists('wc_get_checkout_url') ? wc_get_checkout_url() : home_url('/'), - default => home_url('/'), + default => home_url('/'), }; } } diff --git a/resources/functions.php b/resources/functions.php index 2a9f312f070de785ec586e3d107b5f8087835e50..fc2628fff941818e3e9d9d04f92e162c58855b79 100644 --- a/resources/functions.php +++ b/resources/functions.php @@ -16,4 +16,4 @@ require $composer; */ add_action('after_setup_theme', function () { Carbon_Fields::boot(); -}); \ No newline at end of file +}); diff --git a/woocommerce/archive-product.php b/woocommerce/archive-product.php index 1cc104e2ab04d4d9b12ce51e09b2a74cb7a50129..022f00134b5de2902dad6d7103504fd2408e49ce 100644 --- a/woocommerce/archive-product.php +++ b/woocommerce/archive-product.php @@ -2,7 +2,7 @@ /** * @see https://woocommerce.com/document/template-structure/ - * @package WooCommerce\Templates + * * @version 8.6.0 */ diff --git a/woocommerce/brands/taxonomy-product_brand.php b/woocommerce/brands/taxonomy-product_brand.php index 56898bf0cb3885306580143e2dc191f4724fe425..82843d8f62bd3478989ac9e3fdcf17a0936c1d4d 100644 --- a/woocommerce/brands/taxonomy-product_brand.php +++ b/woocommerce/brands/taxonomy-product_brand.php @@ -1,12 +1,13 @@ -get_order_number() ), esc_html( $order->get_formatted_billing_full_name() ) ) . "\n\n"; +echo sprintf(esc_html($text), esc_html($order->get_order_number()), esc_html($order->get_formatted_billing_full_name()))."\n\n"; /* * @hooked WC_Emails::order_details() Shows the order details table. @@ -39,29 +40,29 @@ echo sprintf( esc_html( $text ), esc_html( $order->get_order_number() ), esc_htm * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/admin-failed-order.php b/woocommerce/emails/plain/admin-failed-order.php index f40c8c8a9a5e0a947b81048d5e44c67554c89b96..3d8ea59a157ac82fed1d10c7bdd0bd8a0a6cc95a 100644 --- a/woocommerce/emails/plain/admin-failed-order.php +++ b/woocommerce/emails/plain/admin-failed-order.php @@ -1,6 +1,7 @@ get_order_number() ), esc_html( $order->get_formatted_billing_full_name() ) ) . "\n\n"; +echo sprintf(esc_html($text), esc_html($order->get_order_number()), esc_html($order->get_formatted_billing_full_name()))."\n\n"; /* * @hooked WC_Emails::order_details() Shows the order details table. @@ -39,29 +40,29 @@ echo sprintf( esc_html( $text ), esc_html( $order->get_order_number() ), esc_htm * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/admin-new-order.php b/woocommerce/emails/plain/admin-new-order.php index 60479633089aff373e50a5a59f83b56db2ee686a..40901f10f9f7d7a031695ac514bbdeec4368e7c4 100644 --- a/woocommerce/emails/plain/admin-new-order.php +++ b/woocommerce/emails/plain/admin-new-order.php @@ -1,6 +1,7 @@ get_formatted_billing_full_name() ) ) . "\n\n"; +echo sprintf(esc_html($text), esc_html($order->get_formatted_billing_full_name()))."\n\n"; /* * @hooked WC_Emails::order_details() Shows the order details table. @@ -39,29 +40,29 @@ echo sprintf( esc_html( $text ), esc_html( $order->get_formatted_billing_full_na * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/admin-payment-gateway-enabled.php b/woocommerce/emails/plain/admin-payment-gateway-enabled.php index ec10bcfa6d12b3edd41fd838afb338fc17632840..55a452049b4213adf8e574e44b64e5f1fd828024 100644 --- a/woocommerce/emails/plain/admin-payment-gateway-enabled.php +++ b/woocommerce/emails/plain/admin-payment-gateway-enabled.php @@ -1,6 +1,7 @@ get_order_number() ) ) . "\n\n"; +echo sprintf(esc_html($text), esc_html($order->get_order_number()))."\n\n"; /** * Hook: woocommerce_email_order_details. @@ -39,9 +40,10 @@ echo sprintf( esc_html( $text ), esc_html( $order->get_order_number() ) ) . "\n\ * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; @@ -49,33 +51,36 @@ echo "\n----------------------------------------\n\n"; * Hook: woocommerce_email_order_meta. * * @hooked WC_Emails::order_meta() Shows order meta data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /** * Hook: woocommerce_email_customer_details. * * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address + * * @since 2.5.0 */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } /** * Hook: woocommerce_email_footer. * * @hooked WC_Emails::email_footer() Output the email footer + * * @since 2.5.0 */ -do_action( 'woocommerce_email_footer', $email ); +do_action('woocommerce_email_footer', $email); diff --git a/woocommerce/emails/plain/customer-completed-order.php b/woocommerce/emails/plain/customer-completed-order.php index b00abe9b066a4790f3af171323519144c6bcfb6e..07d4e12360e310dbfefb59b93c06e4fd3e860a3f 100644 --- a/woocommerce/emails/plain/customer-completed-order.php +++ b/woocommerce/emails/plain/customer-completed-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -echo esc_html__( 'We have finished processing your order.', 'woocommerce' ) . "\n\n"; -if ( $email_improvements_enabled ) { - echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +echo esc_html__('We have finished processing your order.', 'woocommerce')."\n\n"; +if ($email_improvements_enabled) { + echo esc_html__('Here’s a reminder of what you’ve ordered:', 'woocommerce')."\n\n"; } /* @@ -38,29 +39,29 @@ if ( $email_improvements_enabled ) { * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-failed-order.php b/woocommerce/emails/plain/customer-failed-order.php index 859fd6342f2e7db5c4d9dfbd55a5a531f19cbb95..a4c790531e86799a72c0c3bec6302b1a48a3d4d3 100644 --- a/woocommerce/emails/plain/customer-failed-order.php +++ b/woocommerce/emails/plain/customer-failed-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -echo esc_html__( "Unfortunately, we couldn't complete your order due to an issue with your payment method.", 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +echo esc_html__("Unfortunately, we couldn't complete your order due to an issue with your payment method.", 'woocommerce')."\n\n"; /* translators: %s: Site title */ -echo sprintf( esc_html__( "If you'd like to continue with your purchase, please return to %s and try a different method of payment.", 'woocommerce' ), esc_html( $blogname ) ) . "\n\n"; -echo esc_html__( 'Your order details are as follows:', 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__("If you'd like to continue with your purchase, please return to %s and try a different method of payment.", 'woocommerce'), esc_html($blogname))."\n\n"; +echo esc_html__('Your order details are as follows:', 'woocommerce')."\n\n"; /** * Hook for the woocommerce_email_order_details. @@ -34,9 +34,10 @@ echo esc_html__( 'Your order details are as follows:', 'woocommerce' ) . "\n\n"; * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; @@ -44,27 +45,29 @@ echo "\n----------------------------------------\n\n"; * Hook for the woocommerce_email_order_meta. * * @hooked WC_Emails::order_meta() Shows order meta data. + * * @since 1.0.0 */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /** * Hook for woocommerce_email_customer_details. * * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address + * * @since 1.0.0 */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } /** @@ -72,4 +75,4 @@ if ( $additional_content ) { * * @since 3.7.0 */ -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-fulfillment-created.php b/woocommerce/emails/plain/customer-fulfillment-created.php index c2d85ba02d058642280b616fd8668b7fbf325066..d5e712d65f3adce043fb7696b72bad0b1ff0cf31 100644 --- a/woocommerce/emails/plain/customer-fulfillment-created.php +++ b/woocommerce/emails/plain/customer-fulfillment-created.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; -if ( $order->needs_payment() ) { - if ( $order->has_status( OrderStatus::FAILED ) ) { - echo wp_kses_post( - sprintf( - /* translators: %1$s: Site title, %2$s: Order pay link */ - __( 'Sorry, your order on %1$s was unsuccessful. Your order details are below, with a link to try your payment again: %2$s', 'woocommerce' ), - esc_html( get_bloginfo( 'name', 'display' ) ), - esc_url( $order->get_checkout_payment_url() ) - ) - ) . "\n\n"; - } else { - echo wp_kses_post( - sprintf( - /* translators: %1$s: Site title, %2$s: Order pay link */ - __( 'An order has been created for you on %1$s. Your order details are below, with a link to make payment when you’re ready: %2$s', 'woocommerce' ), - esc_html( get_bloginfo( 'name', 'display' ) ), - esc_url( $order->get_checkout_payment_url() ) - ) - ) . "\n\n"; - } +if ($order->needs_payment()) { + if ($order->has_status(OrderStatus::FAILED)) { + echo wp_kses_post( + sprintf( + /* translators: %1$s: Site title, %2$s: Order pay link */ + __('Sorry, your order on %1$s was unsuccessful. Your order details are below, with a link to try your payment again: %2$s', 'woocommerce'), + esc_html(get_bloginfo('name', 'display')), + esc_url($order->get_checkout_payment_url()) + ) + )."\n\n"; + } else { + echo wp_kses_post( + sprintf( + /* translators: %1$s: Site title, %2$s: Order pay link */ + __('An order has been created for you on %1$s. Your order details are below, with a link to make payment when you’re ready: %2$s', 'woocommerce'), + esc_html(get_bloginfo('name', 'display')), + esc_url($order->get_checkout_payment_url()) + ) + )."\n\n"; + } } else { - /* translators: %s: Order date */ - echo sprintf( esc_html__( 'Here are the details of your order placed on %s:', 'woocommerce' ), esc_html( wc_format_datetime( $order->get_date_created() ) ) ) . "\n\n"; + /* translators: %s: Order date */ + echo sprintf(esc_html__('Here are the details of your order placed on %s:', 'woocommerce'), esc_html(wc_format_datetime($order->get_date_created())))."\n\n"; } /** @@ -59,9 +60,10 @@ if ( $order->needs_payment() ) { * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; @@ -70,26 +72,26 @@ echo "\n----------------------------------------\n\n"; * * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /** - * Hook for woocommerce_email_customer_details + * Hook for woocommerce_email_customer_details. * * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); // phpcs:enable Universal.WhiteSpace.PrecisionAlignment.Found, Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed diff --git a/woocommerce/emails/plain/customer-new-account.php b/woocommerce/emails/plain/customer-new-account.php index 28fc818133bf9eb724028c98309c707e334de6b6..b14b97f8e5e13b7c36949d095c22c928caa400bb 100644 --- a/woocommerce/emails/plain/customer-new-account.php +++ b/woocommerce/emails/plain/customer-new-account.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -echo esc_html__( 'The following note has been added to your order:', 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +echo esc_html__('The following note has been added to your order:', 'woocommerce')."\n\n"; echo "----------\n\n"; -echo wc_wptexturize_order_note( $customer_note ) . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +echo wc_wptexturize_order_note($customer_note)."\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo "----------\n\n"; -echo esc_html__( 'As a reminder, here are your order details:', 'woocommerce' ) . "\n\n"; +echo esc_html__('As a reminder, here are your order details:', 'woocommerce')."\n\n"; /* * @hooked WC_Emails::order_details() Shows the order details table. @@ -39,29 +39,29 @@ echo esc_html__( 'As a reminder, here are your order details:', 'woocommerce' ) * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-on-hold-order.php b/woocommerce/emails/plain/customer-on-hold-order.php index 417ae6c945a9786868ae56c6e8b887602a5bd5d2..3f9bea1160ff34d6cdacc34f62d233bddea82fba 100644 --- a/woocommerce/emails/plain/customer-on-hold-order.php +++ b/woocommerce/emails/plain/customer-on-hold-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -if ( $email_improvements_enabled ) { - echo esc_html__( 'We’ve received your order and it’s currently on hold until we can confirm your payment has been processed.', 'woocommerce' ) . "\n\n"; - echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +if ($email_improvements_enabled) { + echo esc_html__('We’ve received your order and it’s currently on hold until we can confirm your payment has been processed.', 'woocommerce')."\n\n"; + echo esc_html__('Here’s a reminder of what you’ve ordered:', 'woocommerce')."\n\n"; } else { - echo esc_html__( 'Thanks for your order. It’s on-hold until we confirm that payment has been received.', 'woocommerce' ) . "\n\n"; + echo esc_html__('Thanks for your order. It’s on-hold until we confirm that payment has been received.', 'woocommerce')."\n\n"; } /* @@ -40,29 +41,29 @@ if ( $email_improvements_enabled ) { * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-pos-completed-order.php b/woocommerce/emails/plain/customer-pos-completed-order.php index 811ea13155f366d83c3f4247803ebc676246f4a3..14592338387cb4b3f0051a117bcccbfae66adae5 100644 --- a/woocommerce/emails/plain/customer-pos-completed-order.php +++ b/woocommerce/emails/plain/customer-pos-completed-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) { - /* translators: %s: Customer first name */ - echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; +if (!empty($order->get_billing_first_name())) { + /* translators: %s: Customer first name */ + echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; } else { - echo esc_html__( 'Hi there,', 'woocommerce' ) . "\n\n"; + echo esc_html__('Hi there,', 'woocommerce')."\n\n"; } -echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ) . "\n\n"; +echo esc_html__('Here’s a reminder of what you’ve ordered:', 'woocommerce')."\n\n"; /** * Show the order details table, generate and output structured data. @@ -40,9 +41,10 @@ echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; @@ -50,55 +52,57 @@ echo "\n----------------------------------------\n\n"; * Show order meta data. * * @hooked WC_Emails::order_meta() Shows order meta data. + * * @since 1.0.0 */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /** * Show customer details and email address. * * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address + * * @since 1.0.0 */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } /** * Show store information - store details are set in the Point of Sale settings. */ -if ( ! empty( $pos_store_email ) || ! empty( $pos_store_phone_number ) || ! empty( $pos_store_address ) ) { - if ( ! empty( $pos_store_name ) ) { - echo "\n" . esc_html( $pos_store_name ) . "\n\n"; - } - if ( ! empty( $pos_store_email ) ) { - echo esc_html( $pos_store_email ) . "\n"; - } - if ( ! empty( $pos_store_phone_number ) ) { - echo esc_html( $pos_store_phone_number ) . "\n"; - } - if ( ! empty( $pos_store_address ) ) { - echo esc_html( wp_strip_all_tags( wptexturize( $pos_store_address ) ) ) . "\n"; - } - echo "\n----------------------------------------\n\n"; +if (!empty($pos_store_email) || !empty($pos_store_phone_number) || !empty($pos_store_address)) { + if (!empty($pos_store_name)) { + echo "\n".esc_html($pos_store_name)."\n\n"; + } + if (!empty($pos_store_email)) { + echo esc_html($pos_store_email)."\n"; + } + if (!empty($pos_store_phone_number)) { + echo esc_html($pos_store_phone_number)."\n"; + } + if (!empty($pos_store_address)) { + echo esc_html(wp_strip_all_tags(wptexturize($pos_store_address)))."\n"; + } + echo "\n----------------------------------------\n\n"; } /** * Show refund & returns policy - this is set in the Point of Sale settings. */ -if ( ! empty( $pos_refund_returns_policy ) ) { - echo "\n" . esc_html__( 'Refund & Returns Policy', 'woocommerce' ) . "\n\n"; - echo esc_html( wp_strip_all_tags( wptexturize( $pos_refund_returns_policy ) ) ) . "\n"; - echo "\n----------------------------------------\n\n"; +if (!empty($pos_refund_returns_policy)) { + echo "\n".esc_html__('Refund & Returns Policy', 'woocommerce')."\n\n"; + echo esc_html(wp_strip_all_tags(wptexturize($pos_refund_returns_policy)))."\n"; + echo "\n----------------------------------------\n\n"; } /** @@ -106,4 +110,4 @@ if ( ! empty( $pos_refund_returns_policy ) ) { * * @since 4.0.0 */ -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ), $email ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'), $email)); diff --git a/woocommerce/emails/plain/customer-pos-refunded-order.php b/woocommerce/emails/plain/customer-pos-refunded-order.php index 50048841790f6c3ff59327a62f02342d7edbd481..41c570fd153a4d4ee3b08519cc05b88f27a60ad7 100644 --- a/woocommerce/emails/plain/customer-pos-refunded-order.php +++ b/woocommerce/emails/plain/customer-pos-refunded-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) { - /* translators: %s: Customer first name */ - echo sprintf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $order->get_billing_first_name() ) ) . "\n\n"; +if (!empty($order->get_billing_first_name())) { + /* translators: %s: Customer first name */ + echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; } else { - echo esc_html__( 'Hi there,', 'woocommerce' ) . "\n\n"; + echo esc_html__('Hi there,', 'woocommerce')."\n\n"; } -if ( $partial_refund ) { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order from %s has been partially refunded.', 'woocommerce' ), esc_html( $pos_store_name ) ) . "\n\n"; +if ($partial_refund) { + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order from %s has been partially refunded.', 'woocommerce'), esc_html($pos_store_name))."\n\n"; } else { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order from %s has been refunded.', 'woocommerce' ), esc_html( $pos_store_name ) ) . "\n\n"; + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order from %s has been refunded.', 'woocommerce'), esc_html($pos_store_name))."\n\n"; } -echo esc_html__( 'Here’s a reminder of what you’ve bought:', 'woocommerce' ) . "\n\n"; +echo esc_html__('Here’s a reminder of what you’ve bought:', 'woocommerce')."\n\n"; /** * Show order details. @@ -47,9 +48,10 @@ echo esc_html__( 'Here’s a reminder of what you’ve bought:', 'woocommerce' ) * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; @@ -57,55 +59,57 @@ echo "\n----------------------------------------\n\n"; * Show order meta data. * * @hooked WC_Emails::order_meta() Shows order meta data. + * * @since 1.0.0 */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /** * Show customer details and email address. * * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address + * * @since 1.0.0 */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } /** * Show store information - store details are set in the Point of Sale settings. */ -if ( ! empty( $pos_store_email ) || ! empty( $pos_store_phone_number ) || ! empty( $pos_store_address ) ) { - if ( ! empty( $pos_store_name ) ) { - echo "\n" . esc_html( $pos_store_name ) . "\n\n"; - } - if ( ! empty( $pos_store_email ) ) { - echo esc_html( $pos_store_email ) . "\n"; - } - if ( ! empty( $pos_store_phone_number ) ) { - echo esc_html( $pos_store_phone_number ) . "\n"; - } - if ( ! empty( $pos_store_address ) ) { - echo esc_html( wp_strip_all_tags( wptexturize( $pos_store_address ) ) ) . "\n"; - } - echo "\n----------------------------------------\n\n"; +if (!empty($pos_store_email) || !empty($pos_store_phone_number) || !empty($pos_store_address)) { + if (!empty($pos_store_name)) { + echo "\n".esc_html($pos_store_name)."\n\n"; + } + if (!empty($pos_store_email)) { + echo esc_html($pos_store_email)."\n"; + } + if (!empty($pos_store_phone_number)) { + echo esc_html($pos_store_phone_number)."\n"; + } + if (!empty($pos_store_address)) { + echo esc_html(wp_strip_all_tags(wptexturize($pos_store_address)))."\n"; + } + echo "\n----------------------------------------\n\n"; } /** * Show refund & returns policy - this is set in the Point of Sale settings. */ -if ( ! empty( $pos_refund_returns_policy ) ) { - echo "\n" . esc_html__( 'Refund & Returns Policy', 'woocommerce' ) . "\n\n"; - echo esc_html( wp_strip_all_tags( wptexturize( $pos_refund_returns_policy ) ) ) . "\n"; - echo "\n----------------------------------------\n\n"; +if (!empty($pos_refund_returns_policy)) { + echo "\n".esc_html__('Refund & Returns Policy', 'woocommerce')."\n\n"; + echo esc_html(wp_strip_all_tags(wptexturize($pos_refund_returns_policy)))."\n"; + echo "\n----------------------------------------\n\n"; } /** @@ -113,4 +117,4 @@ if ( ! empty( $pos_refund_returns_policy ) ) { * * @since 4.0.0 */ -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ), $email ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'), $email)); diff --git a/woocommerce/emails/plain/customer-processing-order.php b/woocommerce/emails/plain/customer-processing-order.php index 85e6a6588dab1428b4f5358cda1f5e8d43da54e2..540809f31a37577ea5be5c3b36ba14000f64605c 100644 --- a/woocommerce/emails/plain/customer-processing-order.php +++ b/woocommerce/emails/plain/customer-processing-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -if ( $email_improvements_enabled ) { - echo esc_html__( 'Just to let you know — we’ve received your order, and it is now being processed.', 'woocommerce' ) . "\n\n"; - echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +if ($email_improvements_enabled) { + echo esc_html__('Just to let you know — we’ve received your order, and it is now being processed.', 'woocommerce')."\n\n"; + echo esc_html__('Here’s a reminder of what you’ve ordered:', 'woocommerce')."\n\n"; } else { - /* translators: %s: Order number */ - echo sprintf( esc_html__( 'Just to let you know — we\'ve received your order #%s, and it is now being processed:', 'woocommerce' ), esc_html( $order->get_order_number() ) ) . "\n\n"; + /* translators: %s: Order number */ + echo sprintf(esc_html__('Just to let you know — we\'ve received your order #%s, and it is now being processed:', 'woocommerce'), esc_html($order->get_order_number()))."\n\n"; } /* @@ -41,29 +42,29 @@ if ( $email_improvements_enabled ) { * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-refunded-order.php b/woocommerce/emails/plain/customer-refunded-order.php index dc27ed6f768ba05457f20f2ad4baf65fecc3e268..abc501e92dda2f9bd4ed299e627cae636c7f1ebd 100644 --- a/woocommerce/emails/plain/customer-refunded-order.php +++ b/woocommerce/emails/plain/customer-refunded-order.php @@ -1,6 +1,7 @@ get_billing_first_name() ) ) . "\n\n"; -if ( $email_improvements_enabled ) { - if ( $partial_refund ) { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order from %s has been partially refunded.', 'woocommerce' ), esc_html( $blogname ) ) . "\n\n"; - } else { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order from %s has been refunded.', 'woocommerce' ), esc_html( $blogname ) ) . "\n\n"; - } - echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ) . "\n\n"; -} elseif ( $partial_refund ) { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order on %s has been partially refunded. There are more details below for your reference:', 'woocommerce' ), esc_html( $blogname ) ) . "\n\n"; +echo sprintf(esc_html__('Hi %s,', 'woocommerce'), esc_html($order->get_billing_first_name()))."\n\n"; +if ($email_improvements_enabled) { + if ($partial_refund) { + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order from %s has been partially refunded.', 'woocommerce'), esc_html($blogname))."\n\n"; + } else { + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order from %s has been refunded.', 'woocommerce'), esc_html($blogname))."\n\n"; + } + echo esc_html__('Here’s a reminder of what you’ve ordered:', 'woocommerce')."\n\n"; +} elseif ($partial_refund) { + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order on %s has been partially refunded. There are more details below for your reference:', 'woocommerce'), esc_html($blogname))."\n\n"; } else { - /* translators: %s: Site title */ - echo sprintf( esc_html__( 'Your order on %s has been refunded. There are more details below for your reference:', 'woocommerce' ), esc_html( $blogname ) ) . "\n\n"; + /* translators: %s: Site title */ + echo sprintf(esc_html__('Your order on %s has been refunded. There are more details below for your reference:', 'woocommerce'), esc_html($blogname))."\n\n"; } /* @@ -50,29 +51,29 @@ if ( $email_improvements_enabled ) { * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ -do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); echo "\n----------------------------------------\n\n"; /* * @hooked WC_Emails::order_meta() Shows order meta data. */ -do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ -do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-reset-password.php b/woocommerce/emails/plain/customer-reset-password.php index c28627c3d770847c33932cf8096db77986eb7fc2..613739d1fa657925156f5e311c7ce34db753f72b 100644 --- a/woocommerce/emails/plain/customer-reset-password.php +++ b/woocommerce/emails/plain/customer-reset-password.php @@ -1,6 +1,7 @@ $reset_key, 'id' => $user_id, 'login' => rawurlencode( $user_login ) ), wc_get_endpoint_url( 'lost-password', '', wc_get_page_permalink( 'myaccount' ) ) ) ) . "\n\n"; // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound +echo esc_url(add_query_arg(['key' => $reset_key, 'id' => $user_id, 'login' => rawurlencode($user_login)], wc_get_endpoint_url('lost-password', '', wc_get_page_permalink('myaccount'))))."\n\n"; // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound echo "\n\n----------------------------------------\n\n"; /** * Show user-defined additional content - this is set in each email's settings. */ -if ( $additional_content ) { - echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); - echo "\n\n----------------------------------------\n\n"; +if ($additional_content) { + echo esc_html(wp_strip_all_tags(wptexturize($additional_content))); + echo "\n\n----------------------------------------\n\n"; } -echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); +echo wp_kses_post(apply_filters('woocommerce_email_footer_text', get_option('woocommerce_email_footer_text'))); diff --git a/woocommerce/emails/plain/customer-stock-notification-verified.php b/woocommerce/emails/plain/customer-stock-notification-verified.php index 6132a99803dd710c168bd62fc772d63ef8fd97d7..7e05a0f0a13552daf9962ec0bebd44cf66a1c948 100644 --- a/woocommerce/emails/plain/customer-stock-notification-verified.php +++ b/woocommerce/emails/plain/customer-stock-notification-verified.php @@ -1,4 +1,5 @@ #i', "\n", $order->get_formatted_billing_address() ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +echo "\n".esc_html(wc_strtoupper(esc_html__('Billing address', 'woocommerce')))."\n\n"; +echo preg_replace('##i', "\n", $order->get_formatted_billing_address())."\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -if ( $order->get_billing_phone() ) { - echo $order->get_billing_phone() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +if ($order->get_billing_phone()) { + echo $order->get_billing_phone()."\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } -if ( $order->get_billing_email() ) { - echo $order->get_billing_email() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +if ($order->get_billing_email()) { + echo $order->get_billing_email()."\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** @@ -33,34 +33,34 @@ if ( $order->get_billing_email() ) { * * @since 8.6.0 * - * @param string $type Address type. Either 'billing' or 'shipping'. - * @param WC_Order $order Order instance. - * @param bool $sent_to_admin If this email is being sent to the admin or not. - * @param bool $plain_text If this email is plain text or not. + * @param string $type Address type. Either 'billing' or 'shipping'. + * @param WC_Order $order Order instance. + * @param bool $sent_to_admin If this email is being sent to the admin or not. + * @param bool $plain_text If this email is plain text or not. */ -do_action( 'woocommerce_email_customer_address_section', 'billing', $order, $sent_to_admin, true ); +do_action('woocommerce_email_customer_address_section', 'billing', $order, $sent_to_admin, true); -if ( ! wc_ship_to_billing_address_only() && $order->needs_shipping_address() ) { - $shipping = $order->get_formatted_shipping_address(); +if (!wc_ship_to_billing_address_only() && $order->needs_shipping_address()) { + $shipping = $order->get_formatted_shipping_address(); - if ( $shipping ) { - echo "\n" . esc_html( wc_strtoupper( esc_html__( 'Shipping address', 'woocommerce' ) ) ) . "\n\n"; - echo preg_replace( '##i', "\n", $shipping ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + if ($shipping) { + echo "\n".esc_html(wc_strtoupper(esc_html__('Shipping address', 'woocommerce')))."\n\n"; + echo preg_replace('##i', "\n", $shipping)."\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - if ( $order->get_shipping_phone() ) { - echo $order->get_shipping_phone() . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + if ($order->get_shipping_phone()) { + echo $order->get_shipping_phone()."\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - /** - * Fires after the core address fields in emails. - * - * @since 8.6.0 - * - * @param string $type Address type. Either 'billing' or 'shipping'. - * @param WC_Order $order Order instance. - * @param bool $sent_to_admin If this email is being sent to the admin or not. - * @param bool $plain_text If this email is plain text or not. - */ - do_action( 'woocommerce_email_customer_address_section', 'shipping', $order, $sent_to_admin, true ); - } + /** + * Fires after the core address fields in emails. + * + * @since 8.6.0 + * + * @param string $type Address type. Either 'billing' or 'shipping'. + * @param WC_Order $order Order instance. + * @param bool $sent_to_admin If this email is being sent to the admin or not. + * @param bool $plain_text If this email is plain text or not. + */ + do_action('woocommerce_email_customer_address_section', 'shipping', $order, $sent_to_admin, true); + } } diff --git a/woocommerce/emails/plain/email-customer-details.php b/woocommerce/emails/plain/email-customer-details.php index c0dbc457451c52701fce243e64c7384170f72e47..e2618c7dfc83a1286f37d326c8914fb94c533be4 100644 --- a/woocommerce/emails/plain/email-customer-details.php +++ b/woocommerce/emails/plain/email-customer-details.php @@ -1,6 +1,7 @@ $column_name ) { - echo wp_kses_post( $column_name ) . ': '; +foreach ($downloads as $download) { + foreach ($columns as $column_id => $column_name) { + echo wp_kses_post($column_name).': '; - if ( has_action( 'woocommerce_email_downloads_column_' . $column_id ) ) { - do_action( 'woocommerce_email_downloads_column_' . $column_id, $download, $plain_text ); - } else { - switch ( $column_id ) { - case 'download-product': - echo esc_html( $download['product_name'] ); - break; - case 'download-file': - echo esc_html( $download['download_name'] ) . ' - ' . esc_url( $download['download_url'] ); - break; - case 'download-expires': - if ( ! empty( $download['access_expires'] ) ) { - echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $download['access_expires'] ) ) ); - } else { - esc_html_e( 'Never', 'woocommerce' ); - } - break; - } - } - echo "\n"; - } - echo "\n"; + if (has_action('woocommerce_email_downloads_column_'.$column_id)) { + do_action('woocommerce_email_downloads_column_'.$column_id, $download, $plain_text); + } else { + switch ($column_id) { + case 'download-product': + echo esc_html($download['product_name']); + break; + case 'download-file': + echo esc_html($download['download_name']).' - '.esc_url($download['download_url']); + break; + case 'download-expires': + if (!empty($download['access_expires'])) { + echo esc_html(date_i18n(get_option('date_format'), strtotime($download['access_expires']))); + } else { + esc_html_e('Never', 'woocommerce'); + } + break; + } + } + echo "\n"; + } + echo "\n"; } echo '=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-='; echo "\n\n"; diff --git a/woocommerce/emails/plain/email-fulfillment-details.php b/woocommerce/emails/plain/email-fulfillment-details.php index 9503073fd0960dda75482300e91feb9cb8ac2417..c57ab767bd51829fc733ca5fe971d3df1bc38931 100644 --- a/woocommerce/emails/plain/email-fulfillment-details.php +++ b/woocommerce/emails/plain/email-fulfillment-details.php @@ -1,4 +1,5 @@ get_date_deleted()) { + $tracking_number = $fulfillment->get_meta('_tracking_number', true); + $tracking_url = $fulfillment->get_meta('_tracking_url'); + $shipment_provider = $fulfillment->get_meta('_shipment_provider'); + if (!$tracking_number && !$tracking_url && !$shipment_provider) { + echo esc_html__('No tracking information available for this fulfillment at the moment.', 'woocommerce'); -if ( null === $fulfillment->get_date_deleted() ) { - $tracking_number = $fulfillment->get_meta( '_tracking_number', true ); - $tracking_url = $fulfillment->get_meta( '_tracking_url' ); - $shipment_provider = $fulfillment->get_meta( '_shipment_provider' ); - if ( ! $tracking_number && ! $tracking_url && ! $shipment_provider ) { - echo esc_html__( 'No tracking information available for this fulfillment at the moment.', 'woocommerce' ); - return; - } else { - echo esc_html__( 'Tracking Number', 'woocommerce' ) . ': ' . esc_attr( $tracking_number ) . "\n"; - echo esc_html__( 'Shipment Provider', 'woocommerce' ) . ': ' . esc_html( $shipment_provider ) . "\n"; - echo esc_html__( 'Tracking URL', 'woocommerce' ) . ': ' . esc_html( $tracking_url ) . "\n\n"; - } + return; + } else { + echo esc_html__('Tracking Number', 'woocommerce').': '.esc_attr($tracking_number)."\n"; + echo esc_html__('Shipment Provider', 'woocommerce').': '.esc_html($shipment_provider)."\n"; + echo esc_html__('Tracking URL', 'woocommerce').': '.esc_html($tracking_url)."\n\n"; + } - echo esc_html__( 'You can access to more details of your order by visiting My Account > Orders and select the order you wish to see the latest status of the delivery.', 'woocommerce' ); - echo "\n\n\n"; + echo esc_html__('You can access to more details of your order by visiting My Account > Orders and select the order you wish to see the latest status of the delivery.', 'woocommerce'); + echo "\n\n\n"; } /** * Action hook to add custom content before fulfillment details in email. * - * @param WC_Order $order Order object. - * @param Fulfillment $fulfillment Fulfillment object. - * @param bool $sent_to_admin Whether it's sent to admin or customer. - * @param bool $plain_text Whether it's a plain text email. - * @param WC_Email $email Email object. + * @param WC_Order $order Order object. + * @param Fulfillment $fulfillment Fulfillment object. + * @param bool $sent_to_admin Whether it's sent to admin or customer. + * @param bool $plain_text Whether it's a plain text email. + * @param WC_Email $email Email object. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_before_fulfillment_table', $order, $fulfillment, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_before_fulfillment_table', $order, $fulfillment, $sent_to_admin, $plain_text, $email); -echo wp_kses_post( __( 'Fulfillment summary', 'woocommerce' ) ); +echo wp_kses_post(__('Fulfillment summary', 'woocommerce')); echo "\n\n==========\n\n"; -if ( $sent_to_admin ) { - $before = ''; - $after = '(' . esc_url( $order->get_edit_order_url() ) . ')'; +if ($sent_to_admin) { + $before = ''; + $after = '('.esc_url($order->get_edit_order_url()).')'; } else { - $before = ''; - $after = ''; + $before = ''; + $after = ''; } /* translators: %s: Order ID. */ -$order_number_string = __( 'Order #%s', 'woocommerce' ); +$order_number_string = __('Order #%s', 'woocommerce'); echo wp_kses_post( - $before . sprintf( - $order_number_string . $after . ' (%s)', - $order->get_order_number(), - wc_format_datetime( $order->get_date_created() ) - ) + $before.sprintf( + $order_number_string.$after.' (%s)', + $order->get_order_number(), + wc_format_datetime($order->get_date_created()) + ) ); echo "\n\n\n"; echo wc_get_email_fulfillment_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - $order, - $fulfillment, - array( - 'show_sku' => $sent_to_admin, - 'show_image' => true, - 'image_size' => array( 48, 48 ), - 'plain_text' => $plain_text, - 'sent_to_admin' => $sent_to_admin, - ) + $order, + $fulfillment, + [ + 'show_sku' => $sent_to_admin, + 'show_image' => true, + 'image_size' => [48, 48], + 'plain_text' => $plain_text, + 'sent_to_admin' => $sent_to_admin, + ] ); /** * Action hook to add custom content after fulfillment details in email. * - * @param WC_Order $order Order object. + * @param WC_Order $order Order object. * @param bool $sent_to_admin Whether it's sent to admin or customer. - * @param bool $plain_text Whether it's a plain text email. - * @param WC_Email $email Email object. + * @param bool $plain_text Whether it's a plain text email. + * @param WC_Email $email Email object. + * * @since 2.5.0 */ -do_action( 'woocommerce_email_after_fulfillment_table', $order, $fulfillment, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_after_fulfillment_table', $order, $fulfillment, $sent_to_admin, $plain_text, $email); diff --git a/woocommerce/emails/plain/email-fulfillment-items.php b/woocommerce/emails/plain/email-fulfillment-items.php index 49ddb6eef340a384205fe97441aa16990edd94f4..46902c4a08b4fa2a513d81a356838f133e5357f6 100644 --- a/woocommerce/emails/plain/email-fulfillment-items.php +++ b/woocommerce/emails/plain/email-fulfillment-items.php @@ -1,6 +1,7 @@ $item ) : - /** - * Email Order Item Visibility hook. - * - * @since 2.5.0 - * @param $visible Whether the item is visible in the email. - * @param WC_Order_Item_Product $item The order item object. - * - * @return bool - */ - if ( apply_filters( - 'woocommerce_order_item_visible', - true, - $item->item - ) ) { - $product = $item->item->get_product(); - $sku = ''; - $purchase_note = ''; +foreach ($items as $item_id => $item) { + /** + * Email Order Item Visibility hook. + * + * @since 2.5.0 + * + * @param $visible Whether the item is visible in the email. + * @param WC_Order_Item_Product $item The order item object. + * + * @return bool + */ + if (apply_filters( + 'woocommerce_order_item_visible', + true, + $item->item + )) { + $product = $item->item->get_product(); + $sku = ''; + $purchase_note = ''; - if ( is_object( $product ) ) { - $sku = $product->get_sku(); - $purchase_note = $product->get_purchase_note(); - } + if (is_object($product)) { + $sku = $product->get_sku(); + $purchase_note = $product->get_purchase_note(); + } - /** - * Email Order Item Name hook. - * - * @since 2.1.0 - * @since 2.4.0 Added $is_visible parameter. - * @param string $product_name Product name. - * @param WC_Order_Item $item Order item object. - * @param bool $is_visible Is item visible. - */ - $product_name = apply_filters( 'woocommerce_order_item_name', $item->item->get_name(), $item->item, false ); - /** - * Email Order Item Quantity hook. - * - * @since 2.4.0 - * @param int $quantity Item quantity. - * @param WC_Order_Item $item Item object. - */ - $product_name .= ' × ' . apply_filters( 'woocommerce_email_order_item_quantity', $item->qty, $item->item ); - echo wp_kses_post( str_pad( wp_kses_post( $product_name ), 40 ) ); - echo ' '; - echo esc_html( str_pad( wp_kses( $order->get_formatted_line_subtotal( $item->item ), array() ), 20, ' ', STR_PAD_LEFT ) ) . "\n"; + /** + * Email Order Item Name hook. + * + * @since 2.1.0 + * @since 2.4.0 Added $is_visible parameter. + * + * @param string $product_name Product name. + * @param WC_Order_Item $item Order item object. + * @param bool $is_visible Is item visible. + */ + $product_name = apply_filters('woocommerce_order_item_name', $item->item->get_name(), $item->item, false); + /** + * Email Order Item Quantity hook. + * + * @since 2.4.0 + * + * @param int $quantity Item quantity. + * @param WC_Order_Item $item Item object. + */ + $product_name .= ' × '.apply_filters('woocommerce_email_order_item_quantity', $item->qty, $item->item); + echo wp_kses_post(str_pad(wp_kses_post($product_name), 40)); + echo ' '; + echo esc_html(str_pad(wp_kses($order->get_formatted_line_subtotal($item->item), []), 20, ' ', STR_PAD_LEFT))."\n"; - // SKU. - if ( $show_sku && $sku ) { - echo esc_html( '(#' . $sku . ")\n" ); - } - } - // Note. - if ( $show_purchase_note && $purchase_note ) { - echo "\n" . do_shortcode( wp_kses_post( $purchase_note ) ); - } -endforeach; + // SKU. + if ($show_sku && $sku) { + echo esc_html('(#'.$sku.")\n"); + } + } + // Note. + if ($show_purchase_note && $purchase_note) { + echo "\n".do_shortcode(wp_kses_post($purchase_note)); + } +} diff --git a/woocommerce/emails/plain/email-order-details.php b/woocommerce/emails/plain/email-order-details.php index e3bf5500dcaea8bc839bcc2f45ed9aedf90c818b..a6a6735870f34ac928bfa9003e6fbd1e8aa2ec9a 100644 --- a/woocommerce/emails/plain/email-order-details.php +++ b/woocommerce/emails/plain/email-order-details.php @@ -1,4 +1,5 @@ get_order_number(), wc_format_datetime( $order->get_date_created() ) ) ) . "\n"; - echo "\n==========\n"; +if ($email_improvements_enabled) { + /* translators: %1$s: Order ID. %2$s: Order date */ + echo wp_kses_post(sprintf(esc_html__('Order #%1$s (%2$s)', 'woocommerce'), $order->get_order_number(), wc_format_datetime($order->get_date_created())))."\n"; + echo "\n==========\n"; } else { - /* translators: %1$s: Order ID. %2$s: Order date */ - echo wp_kses_post( wc_strtoupper( sprintf( esc_html__( '[Order #%1$s] (%2$s)', 'woocommerce' ), $order->get_order_number(), wc_format_datetime( $order->get_date_created() ) ) ) ) . "\n"; + /* translators: %1$s: Order ID. %2$s: Order date */ + echo wp_kses_post(wc_strtoupper(sprintf(esc_html__('[Order #%1$s] (%2$s)', 'woocommerce'), $order->get_order_number(), wc_format_datetime($order->get_date_created()))))."\n"; } -echo "\n" . wc_get_email_order_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - $order, - array( - 'show_sku' => $sent_to_admin, - 'show_image' => false, - 'image_size' => array( 32, 32 ), - 'plain_text' => true, - 'sent_to_admin' => $sent_to_admin, - ) +echo "\n".wc_get_email_order_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $order, + [ + 'show_sku' => $sent_to_admin, + 'show_image' => false, + 'image_size' => [32, 32], + 'plain_text' => true, + 'sent_to_admin' => $sent_to_admin, + ] ); echo "==========\n\n"; $item_totals = $order->get_order_item_totals(); -if ( $item_totals ) { - foreach ( $item_totals as $total ) { - if ( $email_improvements_enabled ) { - $label = $total['label']; - if ( isset( $total['meta'] ) ) { - $label .= ' ' . $total['meta']; - } - echo wp_kses_post( str_pad( wp_kses_post( $label ), 40 ) ); - echo ' '; - echo esc_html( str_pad( wp_kses( $total['value'], array() ), 20, ' ', STR_PAD_LEFT ) ) . "\n"; - } else { - echo wp_kses_post( $total['label'] . "\t " . $total['value'] ) . "\n"; - } - } +if ($item_totals) { + foreach ($item_totals as $total) { + if ($email_improvements_enabled) { + $label = $total['label']; + if (isset($total['meta'])) { + $label .= ' '.$total['meta']; + } + echo wp_kses_post(str_pad(wp_kses_post($label), 40)); + echo ' '; + echo esc_html(str_pad(wp_kses($total['value'], []), 20, ' ', STR_PAD_LEFT))."\n"; + } else { + echo wp_kses_post($total['label']."\t ".$total['value'])."\n"; + } + } } -if ( $order->get_customer_note() ) { - if ( $email_improvements_enabled ) { - echo "\n" . esc_html__( 'Note:', 'woocommerce' ) . "\n" . wp_kses( wc_wptexturize_order_note( $order->get_customer_note() ), array() ) . "\n"; - } else { - echo esc_html__( 'Note:', 'woocommerce' ) . "\t " . wp_kses( wc_wptexturize_order_note( $order->get_customer_note() ), array() ) . "\n"; - } +if ($order->get_customer_note()) { + if ($email_improvements_enabled) { + echo "\n".esc_html__('Note:', 'woocommerce')."\n".wp_kses(wc_wptexturize_order_note($order->get_customer_note()), [])."\n"; + } else { + echo esc_html__('Note:', 'woocommerce')."\t ".wp_kses(wc_wptexturize_order_note($order->get_customer_note()), [])."\n"; + } } -if ( $sent_to_admin ) { - /* translators: %s: Order link. */ - echo "\n" . sprintf( esc_html__( 'View order: %s', 'woocommerce' ), esc_url( $order->get_edit_order_url() ) ) . "\n"; +if ($sent_to_admin) { + /* translators: %s: Order link. */ + echo "\n".sprintf(esc_html__('View order: %s', 'woocommerce'), esc_url($order->get_edit_order_url()))."\n"; } -do_action( 'woocommerce_email_after_order_table', $order, $sent_to_admin, $plain_text, $email ); +do_action('woocommerce_email_after_order_table', $order, $sent_to_admin, $plain_text, $email); diff --git a/woocommerce/emails/plain/email-order-items.php b/woocommerce/emails/plain/email-order-items.php index e7235437c9ae41da4e5b44990d635dcf3773e26f..0d3841b90b2e4c66e7aed3b778c43dfd7e7eb43d 100644 --- a/woocommerce/emails/plain/email-order-items.php +++ b/woocommerce/emails/plain/email-order-items.php @@ -1,6 +1,7 @@ $item ) : - if ( apply_filters( 'woocommerce_order_item_visible', true, $item ) ) { - $product = $item->get_product(); - $sku = ''; - $purchase_note = ''; +foreach ($items as $item_id => $item) { + if (apply_filters('woocommerce_order_item_visible', true, $item)) { + $product = $item->get_product(); + $sku = ''; + $purchase_note = ''; - if ( is_object( $product ) ) { - $sku = $product->get_sku(); - $purchase_note = $product->get_purchase_note(); - } + if (is_object($product)) { + $sku = $product->get_sku(); + $purchase_note = $product->get_purchase_note(); + } - if ( $email_improvements_enabled ) { - /** - * Email Order Item Name hook. - * - * @since 2.1.0 - * @since 2.4.0 Added $is_visible parameter. - * @param string $product_name Product name. - * @param WC_Order_Item $item Order item object. - * @param bool $is_visible Is item visible. - */ - $product_name = apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ); - /** - * Email Order Item Quantity hook. - * - * @since 2.4.0 - * @param int $quantity Item quantity. - * @param WC_Order_Item $item Item object. - */ - $product_name .= ' × ' . apply_filters( 'woocommerce_email_order_item_quantity', $item->get_quantity(), $item ); - echo wp_kses_post( str_pad( wp_kses_post( $product_name ), 40 ) ); - echo ' '; - echo esc_html( str_pad( wp_kses( $order->get_formatted_line_subtotal( $item ), array() ), 20, ' ', STR_PAD_LEFT ) ) . "\n"; - if ( $show_sku && $sku ) { - echo esc_html( '(#' . $sku . ")\n" ); - } - } else { - // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped - /** - * Email Order Item Name hook. - * - * @since 2.1.0 - * @since 2.4.0 Added $is_visible parameter. - * @param string $product_name Product name. - * @param WC_Order_Item $item Order item object. - * @param bool $is_visible Is item visible. - */ - echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ) ); - if ( $show_sku && $sku ) { - echo ' (#' . $sku . ')'; - } - /** - * Email Order Item Quantity hook. - * - * @since 2.4.0 - * @param int $quantity Item quantity. - * @param WC_Order_Item $item Item object. - */ - echo ' X ' . apply_filters( 'woocommerce_email_order_item_quantity', $item->get_quantity(), $item ); - echo ' = ' . $order->get_formatted_line_subtotal( $item ) . "\n"; - // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped - } + if ($email_improvements_enabled) { + /** + * Email Order Item Name hook. + * + * @since 2.1.0 + * @since 2.4.0 Added $is_visible parameter. + * + * @param string $product_name Product name. + * @param WC_Order_Item $item Order item object. + * @param bool $is_visible Is item visible. + */ + $product_name = apply_filters('woocommerce_order_item_name', $item->get_name(), $item, false); + /** + * Email Order Item Quantity hook. + * + * @since 2.4.0 + * + * @param int $quantity Item quantity. + * @param WC_Order_Item $item Item object. + */ + $product_name .= ' × '.apply_filters('woocommerce_email_order_item_quantity', $item->get_quantity(), $item); + echo wp_kses_post(str_pad(wp_kses_post($product_name), 40)); + echo ' '; + echo esc_html(str_pad(wp_kses($order->get_formatted_line_subtotal($item), []), 20, ' ', STR_PAD_LEFT))."\n"; + if ($show_sku && $sku) { + echo esc_html('(#'.$sku.")\n"); + } + } else { + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped + /** + * Email Order Item Name hook. + * + * @since 2.1.0 + * @since 2.4.0 Added $is_visible parameter. + * + * @param string $product_name Product name. + * @param WC_Order_Item $item Order item object. + * @param bool $is_visible Is item visible. + */ + echo wp_kses_post(apply_filters('woocommerce_order_item_name', $item->get_name(), $item, false)); + if ($show_sku && $sku) { + echo ' (#'.$sku.')'; + } + /** + * Email Order Item Quantity hook. + * + * @since 2.4.0 + * + * @param int $quantity Item quantity. + * @param WC_Order_Item $item Item object. + */ + echo ' X '.apply_filters('woocommerce_email_order_item_quantity', $item->get_quantity(), $item); + echo ' = '.$order->get_formatted_line_subtotal($item)."\n"; + // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped + } - // allow other plugins to add additional product information here. - do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $order, $plain_text ); - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo strip_tags( - wc_display_item_meta( - $item, - array( - 'before' => "\n- ", - 'separator' => "\n- ", - 'after' => '', - 'echo' => false, - 'autop' => false, - ) - ) - ); + // allow other plugins to add additional product information here. + do_action('woocommerce_order_item_meta_start', $item_id, $item, $order, $plain_text); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo strip_tags( + wc_display_item_meta( + $item, + [ + 'before' => "\n- ", + 'separator' => "\n- ", + 'after' => '', + 'echo' => false, + 'autop' => false, + ] + ) + ); - // allow other plugins to add additional product information here. - do_action( 'woocommerce_order_item_meta_end', $item_id, $item, $order, $plain_text ); - } - // Note. - if ( $show_purchase_note && $purchase_note ) { - echo "\n" . do_shortcode( wp_kses_post( $purchase_note ) ); - } - echo "\n\n"; -endforeach; + // allow other plugins to add additional product information here. + do_action('woocommerce_order_item_meta_end', $item_id, $item, $order, $plain_text); + } + // Note. + if ($show_purchase_note && $purchase_note) { + echo "\n".do_shortcode(wp_kses_post($purchase_note)); + } + echo "\n\n"; +} diff --git a/woocommerce/global/breadcrumb.php b/woocommerce/global/breadcrumb.php index dd6eb68e959acbaec0b2f1062ad3a927159889da..815b94ac4d0f705d4c79e61abdb3cb427fd52a92 100644 --- a/woocommerce/global/breadcrumb.php +++ b/woocommerce/global/breadcrumb.php @@ -1,6 +1,7 @@ $crumb ) { - - echo $before; +if (!empty($breadcrumb)) { + echo $wrap_before; - if ( ! empty( $crumb[1] ) && sizeof( $breadcrumb ) !== $key + 1 ) { - echo '' . esc_html( $crumb[0] ) . ''; - } else { - echo esc_html( $crumb[0] ); - } + foreach ($breadcrumb as $key => $crumb) { + echo $before; - echo $after; + if (!empty($crumb[1]) && sizeof($breadcrumb) !== $key + 1) { + echo ''.esc_html($crumb[0]).''; + } else { + echo esc_html($crumb[0]); + } - if ( sizeof( $breadcrumb ) !== $key + 1 ) { - echo $delimiter; - } - } + echo $after; - echo $wrap_after; + if (sizeof($breadcrumb) !== $key + 1) { + echo $delimiter; + } + } + echo $wrap_after; } diff --git a/woocommerce/global/sidebar.php b/woocommerce/global/sidebar.php index f92bf0f4f8d3bc523f7d6aba2fbfc248dc88b693..1e8262e3b4c826e5583d5dd81bc199dfe5f2893a 100644 --- a/woocommerce/global/sidebar.php +++ b/woocommerce/global/sidebar.php @@ -1,6 +1,7 @@ '; - break; - case 'twentyeleven': - echo ''; - get_sidebar( 'shop' ); - echo ''; - break; - case 'twentytwelve': - echo ''; - break; - case 'twentythirteen': - echo ''; - break; - case 'twentyfourteen': - echo ''; - get_sidebar( 'content' ); - break; - case 'twentyfifteen': - echo ''; - break; - case 'twentysixteen': - echo ''; - break; - default: - echo ''; - break; +switch ($template) { + case 'twentyten': + echo ''; + break; + case 'twentyeleven': + echo ''; + get_sidebar('shop'); + echo ''; + break; + case 'twentytwelve': + echo ''; + break; + case 'twentythirteen': + echo ''; + break; + case 'twentyfourteen': + echo ''; + get_sidebar('content'); + break; + case 'twentyfifteen': + echo ''; + break; + case 'twentysixteen': + echo ''; + break; + default: + echo ''; + break; } diff --git a/woocommerce/global/wrapper-start.php b/woocommerce/global/wrapper-start.php index a2a6da9202b29a42f110f2e2407dcc63e38d458d..28c35f176119868b059ad82c8d98087b486e7080 100644 --- a/woocommerce/global/wrapper-start.php +++ b/woocommerce/global/wrapper-start.php @@ -1,6 +1,7 @@
'; - break; - case 'twentyeleven': - echo '
'; - break; - case 'twentytwelve': - echo '
'; - break; - case 'twentythirteen': - echo '
'; - break; - case 'twentyfourteen': - echo '
'; - break; - case 'twentyfifteen': - echo '
'; - break; - case 'twentysixteen': - echo '
'; - break; - default: - echo '
'; - break; +switch ($template) { + case 'twentyten': + echo '
'; + break; + case 'twentyeleven': + echo '
'; + break; + case 'twentytwelve': + echo '
'; + break; + case 'twentythirteen': + echo '
'; + break; + case 'twentyfourteen': + echo '
'; + break; + case 'twentyfifteen': + echo '
'; + break; + case 'twentysixteen': + echo '
'; + break; + default: + echo '
'; + break; } diff --git a/woocommerce/loop/rating.php b/woocommerce/loop/rating.php index 8d9aeb6e48c37f2c27a57401e15e856abe12f27f..6b43543684140ca170e4d5edf4e745d749fe24d6 100644 --- a/woocommerce/loop/rating.php +++ b/woocommerce/loop/rating.php @@ -1,6 +1,7 @@ get_average_rating() ); // WordPress.XSS.EscapeOutput.OutputNotEscaped. +echo wc_get_rating_html($product->get_average_rating()); // WordPress.XSS.EscapeOutput.OutputNotEscaped. diff --git a/woocommerce/single-product.php b/woocommerce/single-product.php index 6b622ab1c5a1067d465216020e4fcc58edf3c2ed..e72813cabd9d0240516d29e3d98debe11d7323ef 100644 --- a/woocommerce/single-product.php +++ b/woocommerce/single-product.php @@ -2,7 +2,7 @@ /** * @see https://woocommerce.com/document/template-structure/ - * @package WooCommerce\Templates + * * @version 1.6.4 */ diff --git a/woocommerce/single-product/product-thumbnails.php b/woocommerce/single-product/product-thumbnails.php index ad0890274ad203982ac50a60f42614fc611786b2..a2a50c6d387a25f394cede46b7408ae7be947ae6 100644 --- a/woocommerce/single-product/product-thumbnails.php +++ b/woocommerce/single-product/product-thumbnails.php @@ -1,6 +1,7 @@ get_gallery_image_ids(); -if ( $attachment_ids && $product->get_image_id() ) { - foreach ( $attachment_ids as $key => $attachment_id ) { - /** - * Filter product image thumbnail HTML string. - * - * @since 1.6.4 - * - * @param string $html Product image thumbnail HTML string. - * @param int $attachment_id Attachment ID. - */ - echo apply_filters( 'woocommerce_single_product_image_thumbnail_html', wc_get_gallery_image_html( $attachment_id, false, $key ), $attachment_id ); // PHPCS:Ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } +if ($attachment_ids && $product->get_image_id()) { + foreach ($attachment_ids as $key => $attachment_id) { + /** + * Filter product image thumbnail HTML string. + * + * @since 1.6.4 + * + * @param string $html Product image thumbnail HTML string. + * @param int $attachment_id Attachment ID. + */ + echo apply_filters('woocommerce_single_product_image_thumbnail_html', wc_get_gallery_image_html($attachment_id, false, $key), $attachment_id); // PHPCS:Ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } } diff --git a/woocommerce/single-product/review-rating.php b/woocommerce/single-product/review-rating.php index 244f2867f397297375f60faa67087a70ecef73d6..2339700a3b199bc166f58a0950b4d3a6e89f2323 100644 --- a/woocommerce/single-product/review-rating.php +++ b/woocommerce/single-product/review-rating.php @@ -1,6 +1,7 @@ comment_ID, 'rating', true ) ); +$rating = intval(get_comment_meta($comment->comment_ID, 'rating', true)); -if ( $rating && wc_review_ratings_enabled() ) { - echo wc_get_rating_html( $rating ); // WPCS: XSS ok. +if ($rating && wc_review_ratings_enabled()) { + echo wc_get_rating_html($rating); // WPCS: XSS ok. } diff --git a/woocommerce/single-product/share.php b/woocommerce/single-product/share.php index 43b3134a35808300f3cff55d6094332258351ee8..9b19ea68c81598b11e3247665c80c5f26916a30a 100644 --- a/woocommerce/single-product/share.php +++ b/woocommerce/single-product/share.php @@ -1,6 +1,7 @@ ', '' ); +the_title('

', '

'); diff --git a/woocommerce/taxonomy-product-attribute.php b/woocommerce/taxonomy-product-attribute.php index 6972e8bd56b6b520a33de7107850b6379fe8155a..df0ef15beb36ebe4d551a36dd90d32741287e63e 100644 --- a/woocommerce/taxonomy-product-attribute.php +++ b/woocommerce/taxonomy-product-attribute.php @@ -1,6 +1,7 @@