settings = $settings; } /** * Returns translations based on original strings and ids. * * Editor-only: this can look up dictionary rows by id and trigger machine translation, so it * requires the translating capability. Logged-out / front-end dynamic translation uses the * separate, restricted get_translations_domchanges() instead. * * Hooked to wp_ajax_translangated_get_translations_regular. */ public function get_translations() { if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { check_ajax_referer( 'get_translations', 'security' ); if ( ! current_user_can( apply_filters( 'translangated_translating_capability', 'manage_options' ) ) ) { wp_die(); } if ( isset( $_POST['action'] ) && $_POST['action'] === 'translangated_get_translations_regular' && !empty( $_POST['language'] ) && in_array( $_POST['language'], $this->settings['translation-languages'] ) ) { $originals = translangated_get_json_post( 'originals' ); $skip_machine_translation = translangated_get_json_post( 'skip_machine_translation' ); $ids = translangated_get_json_post_ids( 'string_ids' ); if ( is_array( $skip_machine_translation ) ) { if ( is_array( $ids ) || is_array( $originals ) ) { $trp = Translangated_Plugin::get_translangated_instance(); if ( !$this->translangated_query ) { $this->translangated_query = $trp->get_component( 'query' ); } if ( !$this->translation_manager ) { $this->translation_manager = $trp->get_component( 'translation_manager' ); } $block_type = $this->translangated_query->get_constant_block_type_regular_string(); $dictionaries = $this->get_translation_for_strings( $ids, $originals, $block_type, $skip_machine_translation ); $localized_text = $this->translation_manager->string_groups(); $string_group = __( 'Others', 'translangated' ); // this type is not registered in the string types because it will be overwritten by the content in data-trp-node-type if ( isset( $_POST['dynamic_strings'] ) && $_POST['dynamic_strings'] === 'true' ) { $string_group = $localized_text['dynamicstrings']; } $dictionary_by_original = translangated_sort_dictionary_by_original( $dictionaries, 'regular', $string_group, sanitize_text_field( $_POST['language'] ) ); echo wp_json_encode( $dictionary_by_original ); } } } } wp_die(); } /** * Dynamic-translation endpoint for the front-end DOM-changes script. * * Hooked to wp_ajax_translangated_get_translations_domchanges * and wp_ajax_nopriv_translangated_get_translations_domchanges. * * Never looks up dictionary rows by id * Also passes languageForId = null to translangated_sort_dictionary_by_original(), so the response does not expose dictionary row ids. * */ public function get_translations_domchanges() { if ( ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { wp_die(); } check_ajax_referer( 'get_translations', 'security' ); if ( empty( $_POST['language'] ) || ! in_array( $_POST['language'], $this->settings['translation-languages'] ) ) { wp_die(); } $current_language = sanitize_text_field( $_POST['language'] ); // The default language has no dictionary of its own. if ( $this->settings['default-language'] == $current_language ) { echo wp_json_encode( array() ); wp_die(); } $originals = translangated_get_json_post( 'originals' ); if ( ! is_array( $originals ) ) { wp_die(); } $skip_machine_translation = translangated_get_json_post( 'skip_machine_translation' ); if ( ! is_array( $skip_machine_translation ) ) { $skip_machine_translation = array(); } $trp = Translangated_Plugin::get_translangated_instance(); if ( ! $this->translangated_query ) { $this->translangated_query = $trp->get_component( 'query' ); } if ( ! $this->translation_render ) { $this->translation_render = $trp->get_component( 'translation_render' ); } if ( ! $this->url_converter ) { $this->url_converter = $trp->get_component( 'url_converter' ); } // Same original-string sanitizing as the editor path: allow plain strings, and only // external or file URLs (never internal URLs, which is how the reset link would arrive). $home_url = home_url(); $original_array = array(); foreach ( $originals as $original ) { if ( ! isset( $original ) ) { continue; } $trimmed_string = translangated_full_trim( translangated_sanitize_string( $original, false ) ); if ( filter_var( $trimmed_string, FILTER_VALIDATE_URL ) === false ) { $original_array[] = $trimmed_string; } elseif ( $this->translation_render->is_external_link( $trimmed_string, $home_url ) || $this->url_converter->url_is_file( $trimmed_string ) ) { $original_array[] = remove_query_arg( 'trp-edit-translation', $trimmed_string ); } } if ( empty( $original_array ) ) { echo wp_json_encode( array() ); wp_die(); } // Look up existing translations ONLY by the supplied originals - never by id. $dictionaries = array( $current_language => $this->translangated_query->get_string_rows( array(), $original_array, $current_language ) ); // Do not return machine translations for href/src attributes flagged as skip. if ( count( $skip_machine_translation ) > 0 ) { foreach ( $dictionaries[ $current_language ] as $key => $string ) { if ( $string->status == 1 && in_array( $string->original, $skip_machine_translation ) ) { $dictionaries[ $current_language ][ $key ]->translated = ''; $dictionaries[ $current_language ][ $key ]->status = 0; } } } // languageForId = null: the response is keyed by original and carries no dictionary row ids. $dictionary_by_original = translangated_sort_dictionary_by_original( $dictionaries, 'regular', 'dynamicstrings', null ); echo wp_json_encode( $dictionary_by_original ); wp_die(); } /** * Return dictionary with translated strings. * * @param $strings * @param null $block_type * * @return array */ protected function get_translation_for_strings( $ids, $originals, $block_type = null, $skip_machine_translation = array() ){ /* Editor-only. Front-end dynamic translation goes through get_translations_domchanges() instead.*/ if ( ! current_user_can( apply_filters( 'translangated_translating_capability', 'manage_options' ) ) ) { return array(); } $trp = Translangated_Plugin::get_translangated_instance(); if ( ! $this->translangated_query ) { $this->translangated_query = $trp->get_component( 'query' ); } if ( ! $this->translation_render ) { $this->translation_render = $trp->get_component('translation_render'); } if ( ! $this->url_converter ) { $this->url_converter = $trp->get_component('url_converter'); } $home_url = home_url(); $id_array = array(); $original_array = array(); $dictionaries = array(); foreach ( $ids as $id ) { if ( isset( $id ) && is_numeric( $id ) ) { $id_array[] = (int) $id; } } foreach( $originals as $original ){ if ( isset( $original ) ) { $trimmed_string = translangated_full_trim( translangated_sanitize_string( $original, false ) ); if ( ( filter_var($trimmed_string, FILTER_VALIDATE_URL) === false) ){ // not url $original_array[] = $trimmed_string; }else{ // is url if ( $this->translation_render->is_external_link( $trimmed_string, $home_url ) || $this->url_converter->url_is_file( $trimmed_string ) ) { // allow only external url or file urls $original_array[] = remove_query_arg( 'trp-edit-translation', $trimmed_string ); } } } } $current_language = isset( $_POST['language'] ) && in_array( $_POST['language'], $this->settings['translation-languages'] ) ? $_POST['language'] : ''; /* phpcs:ignore */ /* sanitized by checking against existing languages */ // necessary in order to obtain all the original strings if ( $this->settings['default-language'] != $current_language ) { if ( !empty ( $original_array ) ) { $this->translation_render->process_strings($original_array, $current_language, $block_type, $skip_machine_translation); } $dictionaries[$current_language] = $this->translangated_query->get_string_rows( $id_array, $original_array, $current_language ); }else{ $dictionaries[$current_language] = array(); } if ( isset( $_POST['all_languages'] ) && $_POST['all_languages'] === 'true' ) { foreach ($this->settings['translation-languages'] as $language) { if ($language == $this->settings['default-language']) { $dictionaries[$language]['default-language'] = true; continue; } if ($language == $current_language) { continue; } if (empty($original_strings)) { $original_strings = $this->extract_original_strings($dictionaries[$current_language], $original_array, $id_array); } $this->translation_render->process_strings($original_strings, $language, $block_type, $skip_machine_translation); $dictionaries[$language] = $this->translangated_query->get_string_rows(array(), $original_strings, $language); } } if ( count( $skip_machine_translation ) > 0 ) { foreach ( $dictionaries as $language => $dictionary ) { if ( $language === $this->settings['default-language'] ) { continue; } foreach ( $dictionary as $key => $string ) { if ( $string->status == 1 && in_array( $string->original, $skip_machine_translation ) ) { // do not return translation for href and src $dictionaries[ $language ][ $key ]->translated = ''; $dictionaries[ $language ][ $key ]->status = 0; } } } } return $dictionaries; } /** * Return array of original strings given their db ids. * * @param array $strings Strings object to extract original * @param array $original_array Original strings array to append to. * @param array $id_array Id array to extract. * @return array Original strings array + Extracted strings from ids. */ protected function extract_original_strings( $strings, $original_array, $id_array ){ if ( count( $strings ) > 0 ) { foreach ($id_array as $id) { if ( isset($strings[$id]) && is_object( $strings[$id] ) ){ $original_array[] = $strings[ $id ]->original; } } } return array_values( $original_array ); } /** * Save translations from ajax post. * * Hooked to wp_ajax_translangated_save_translations_regular. */ public function save_translations(){ if ( defined( 'DOING_AJAX' ) && DOING_AJAX && current_user_can( apply_filters( 'translangated_translating_capability', 'manage_options' ) ) ) { check_ajax_referer( 'save_translations', 'security' ); if ( isset( $_POST['action'] ) && $_POST['action'] === 'translangated_save_translations_regular' && !empty( $_POST['strings'] ) ) { $strings = translangated_get_json_post( 'strings' ); $update_strings = $this->save_translations_of_strings( $strings ); } } echo wp_json_encode( $update_strings ); die(); } /** * Save translations in DB for the strings * * @param $strings * @param null $block_type */ protected function save_translations_of_strings( $strings, $block_type = null ){ if ( !$block_type ){ if (!$this->translangated_query) { $trp = Translangated_Plugin::get_translangated_instance(); $this->translangated_query = $trp->get_component('query'); } $block_type = $this->translangated_query->get_constant_block_type_regular_string(); } $update_strings = array(); foreach ( $strings as $language => $language_strings ) { if ( in_array( $language, $this->settings['translation-languages'] ) && $language != $this->settings['default-language'] ) { $update_strings[ $language ] = array(); foreach( $language_strings as $string ) { if ( isset( $string->id ) && is_numeric( $string->id ) ) { if ( ! isset( $string->block_type ) ){ $string->block_type = $block_type; } // Use URL-safe sanitization for href translations to preserve percent-encoding $translated = $string->translated; if ( filter_var($translated, FILTER_VALIDATE_URL) ) { $translated = esc_url_raw( $translated ); } else { $translated = translangated_sanitize_string( $translated ); } array_push($update_strings[ $language ], array( 'id' => (int)$string->id, 'original' => translangated_sanitize_string( $string->original, false ), 'translated' => $translated, 'status' => (int)$string->status, 'block_type' => (int)$string->block_type )); } } } } if ( ! $this->translangated_query ) { $trp = Translangated_Plugin::get_translangated_instance(); $this->translangated_query = $trp->get_component( 'query' ); } foreach( $update_strings as $language => $update_string_array ) { $this->translangated_query->update_strings( $update_string_array, $language, array('id','translated', 'status', 'block_type')); $this->translangated_query->remove_possible_duplicates($update_string_array, $language, 'regular'); } do_action('translangated_save_editor_translations_regular_strings', $update_strings, $this->settings); return $update_strings; } /** * Set translation block to active. * * Creates TB is not exists. Adds auto translation if one is not provided. * Supports handling multiple translation blocks */ public function create_translation_block(){ if ( defined( 'DOING_AJAX' ) && DOING_AJAX && current_user_can( apply_filters( 'translangated_translating_capability', 'manage_options' ) ) ) { check_ajax_referer( 'merge_translation_block', 'security' ); if ( isset( $_POST['action'] ) && $_POST['action'] === 'translangated_create_translation_block' && !empty( $_POST['strings'] ) && !empty( $_POST['language'] ) && in_array( $_POST['language'], $this->settings['translation-languages'] ) && !empty( $_POST['original'] ) ) { $strings = translangated_get_json_post( 'strings' ); if ( isset ( $this->settings['translation-languages']) ){ $trp = Translangated_Plugin::get_translangated_instance(); if ( ! $this->translangated_query ) { $this->translangated_query = $trp->get_component( 'query' ); } if ( ! $this->translation_render ) { $this->translation_render = $trp->get_component( 'translation_render' ); } $active_block_type = $this->translangated_query->get_constant_block_type_active(); foreach( $this->settings['translation-languages'] as $language ){ if ( $language != $this->settings['default-language'] ){ $dictionaries = $this->get_translation_for_strings( array(), array( translangated_sanitize_string( wp_unslash( $_POST['original'] ), false ) ), $active_block_type, array() ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized by translangated_sanitize_string(). break; } } /* * Merging the dictionary received from get_translation_for_strings (which contains ID and possibly automatic translations) with * ajax translated (which can contain manual translations) */ $originals_array_constructed = false; $originals = array(); if ( isset( $dictionaries ) ){ foreach ( $dictionaries as $language => $dictionary ){ if ( $language == $this->settings['default-language'] ) continue; foreach( $dictionary as $dictionary_string_key => $dictionary_string ){ if ( !isset ($strings->$language) ){ continue; } $ajax_translated_string_list = $strings->$language; foreach( $ajax_translated_string_list as $ajax_key => $ajax_string ) { if ( translangated_full_trim( translangated_sanitize_string( $ajax_string->original, false ) ) == $dictionary_string->original ) { if ( $ajax_string->translated != '' ) { $dictionaries[ $language ][ $dictionary_string_key ]->translated = translangated_sanitize_string( $ajax_string->translated ); $dictionaries[ $language ][ $dictionary_string_key ]->status = (int) $ajax_string->status; } $dictionaries[ $language ][ $dictionary_string_key ]->block_type = (int) $ajax_string->block_type; } $dictionaries[ $language ][ $dictionary_string_key ]->new_translation_block = true; } if( !$originals_array_constructed ){ $originals[] = $dictionary_string->original; } } $originals_array_constructed = true; } $this->save_translations_of_strings( $dictionaries, $active_block_type ); // update deactivated languages $copy_of_originals = $originals; if ( $originals_array_constructed ){ $table_names = $this->translangated_query->get_all_table_names( $this->settings['default-language'], $this->settings['translation-languages'] ); if ( count( $table_names ) > 0 ){ foreach( $table_names as $table_name ) { $originals = $copy_of_originals; $language = $this->translangated_query->get_language_code_from_table_name( $table_name ); $existing_dictionary = $this->translangated_query->get_string_rows( array(), $originals, $language, ARRAY_A ); foreach ( $existing_dictionary as $string_key => $string ){ foreach ( $originals as $original_key => $original ){ if ( $string['original'] == $original ){ unset( $originals[$original_key] ); } } $existing_dictionary[$string_key]['block_type'] = $active_block_type; $originals = array_values( $originals ); } $this->translangated_query->insert_strings( $originals, $language, $active_block_type ); $this->translangated_query->update_strings( $existing_dictionary, $language ); } } } echo wp_json_encode( $dictionaries ); } } } } die(); } /** * Set translation block to deprecated * * Can handle splitting multiple blocks. * * @return mixed|string|void */ public function split_translation_block() { if ( defined( 'DOING_AJAX' ) && DOING_AJAX && current_user_can( apply_filters( 'translangated_translating_capability', 'manage_options' ) ) ) { check_ajax_referer( 'split_translation_block', 'security' ); if ( isset( $_POST['action'] ) && $_POST['action'] === 'translangated_split_translation_block' && ! empty( $_POST['strings'] ) ) { $raw_original_array = translangated_get_json_post( 'strings' ); $trp = Translangated_Plugin::get_translangated_instance(); if ( ! $this->translangated_query ) { $this->translangated_query = $trp->get_component( 'query' ); } $deprecated_block_type = $this->translangated_query->get_constant_block_type_deprecated(); $originals = array(); foreach( $raw_original_array as $original ){ $originals[] = translangated_sanitize_string( $original, false ); } // even inactive languages ( not in $this->settings['translation-languages'] array ) will be updated $all_languages_table_names = $this->translangated_query->get_all_table_names( $this->settings['default-language'], array() ); $rows_affected = $this->translangated_query->update_translation_blocks_by_original( $all_languages_table_names, $originals, $deprecated_block_type ); if ( $rows_affected == 0 ){ // do updates individually if it fails foreach ( $all_languages_table_names as $table_name ){ $this->translangated_query->update_translation_blocks_by_original( array( $table_name ), $originals, $deprecated_block_type ); } } } } die(); } }