'atfp_add_blocks', 'posts_per_page' => 1, 'orderby' => 'date', 'order' => 'ASC', ) ); $existing_post = $query->posts ? $query->posts[0] : null; if (! $existing_post) { $post_title = esc_html__('Add More Gutenberg Blocks', 'automatic-translation-for-polylang-pro'); $first_post_id = wp_insert_post( array( 'post_title' => $post_title, 'post_content' => '', 'post_status' => 'publish', 'post_type' => 'atfp_add_blocks', ) ); } elseif ($query->have_posts()) { $query->the_post(); $first_post_id = get_the_ID(); } return $first_post_id; } public function get_block_parse_rules() { $response = wp_remote_get( esc_url_raw( ATFPP_URL . 'includes/block-translation-rules/block-rules.json' ), array( 'timeout' => 15, ) ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { global $wp_filesystem; // Initialize the WordPress filesystem if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); $local_path = ATFPP_DIR_PATH . 'includes/block-translation-rules/block-rules.json'; if($wp_filesystem->exists($local_path) && $wp_filesystem->is_readable( $local_path )){ $block_rules = $wp_filesystem->get_contents( $local_path ); }else{ $block_rules = array(); } } else { $block_rules = wp_remote_retrieve_body( $response ); } if(empty($block_rules)){ return array(); } $block_translation_rules = json_decode($block_rules, true); $this->custom_block_data_array = isset($block_translation_rules['AtfpBlockParseRules']) ? $block_translation_rules['AtfpBlockParseRules'] : null; $custom_block_translation = get_option('atfp_custom_block_translation', false); if (! empty($custom_block_translation) && is_array($custom_block_translation)) { foreach ($custom_block_translation as $key => $block_data) { $block_rules = isset($block_translation_rules['AtfpBlockParseRules'][$key]) ? $block_translation_rules['AtfpBlockParseRules'][$key] : null; $this->filter_custom_block_rules(array($key), $block_data, $block_rules); } } $block_translation_rules['AtfpBlockParseRules'] = $this->custom_block_data_array ? $this->custom_block_data_array : array(); return $block_translation_rules; } private function filter_custom_block_rules(array $id_keys, $value, $block_rules, $attr_key = false) { $block_rules = is_object($block_rules) ? json_decode(json_encode($block_rules)) : $block_rules; if (! isset($block_rules)) { return $this->merge_nested_attribute($id_keys, $value); } if (is_object($value) && isset($block_rules)) { foreach ($value as $key => $item) { if (isset($block_rules[$key]) && is_object($item)) { $this->filter_custom_block_rules(array_merge($id_keys, array($key)), $item, $block_rules[$key], false); continue; } elseif (! isset($block_rules[$key]) && true === $item) { $this->merge_nested_attribute(array_merge($id_keys, array($key)), true); continue; } elseif (! isset($block_rules[$key]) && is_object($item)) { $this->merge_nested_attribute(array_merge($id_keys, array($key)), $item); continue; } } } } private function merge_nested_attribute(array $id_keys, $value) { $value = is_object($value) ? json_decode(json_encode($value), true) : $value; $current_array = &$this->custom_block_data_array; foreach ($id_keys as $index => $id) { if (! isset($current_array[$id])) { $current_array[$id] = array(); } $current_array = &$current_array[$id]; } $current_array = $value; } public static function replace_links_with_translations($content, $locale, $current_locale) { // Get all URLs in the content that start with the current home page URL (current domain), regardless of attribute or tag $home_url = preg_quote(get_home_url(), '/'); $pattern = '/(' . $home_url . '[^\s"\'<>]*)/i'; $terms_data=self::get_terms_data(); if (preg_match_all($pattern, $content, $matches)) { $image_urls=self::get_image_ids_by_urls($matches[1], $locale); $content = preg_replace_callback($pattern, function ($match) use ($image_urls, $locale, $current_locale, $terms_data) { $href = $match[1]; if (preg_match('/]+(src|srcset)=[\'"][^\'"]*' . preg_quote($href, '/') . '[\'"][^>]*>/i', $match[0])) { return $href; } if (isset($image_urls[$href]) && !empty($image_urls[$href])) { return $image_urls[$href]; } $postID = url_to_postid($href); if ($postID > 0) { $translatedPost = pll_get_post($postID, $locale); if ($translatedPost) { $link = get_permalink($translatedPost); if ($link) { return esc_url(urldecode_deep($link)); } } } $path = trim(str_replace(pll_home_url($current_locale), '', $href), '/'); $category_slug = end(array_filter(explode('/', $path))); $taxonomy_name = self::extract_taxonomy_name($path, $terms_data); $taxonomy_name = $taxonomy_name ? $taxonomy_name : 'category'; $category = get_term_by('slug', $category_slug, $taxonomy_name); if (!$category) { // Remove the language prefix if using Polylang $languages = pll_languages_list(); // e.g., ['en', 'fr'] $segments = explode('/', $path); if (in_array($segments[0], $languages)) { $lang_code = $segments[0]; $category_id = Pll()->model->term_exists_by_slug($category_slug, $lang_code, $taxonomy_name); if ($category_id) { $category = get_term($category_id, $taxonomy_name); } } } if ($category) { $term_id = pll_get_term($category->term_id, $locale); if ($term_id > 0) { $link = get_category_link($term_id); return esc_url($link); } } return $href; }, $content); } return $content; } private static function extract_taxonomy_name($path, $terms_data){ // Remove the language prefix if using Polylang $languages = pll_languages_list(); // e.g., ['en', 'fr'] $segments = explode('/', $path); if (in_array($segments[0], $languages)) { array_shift($segments); // remove 'en', 'fr', etc. } if (empty($segments)) { return null; } // First segment after language is usually the taxonomy slug $possible_tax = $segments[0]; if (taxonomy_exists($possible_tax) || (isset($terms_data[$possible_tax]) && taxonomy_exists($terms_data[$possible_tax]))) { return isset($terms_data[$possible_tax]) ? $terms_data[$possible_tax] : $possible_tax; } return false; } private static function get_terms_data(){ $taxonomies=get_taxonomies([],'objects'); $taxonomies_data=array(); foreach($taxonomies as $key=>$taxonomy){ if(isset($taxonomy->rewrite['slug'])){ $taxonomies_data[$taxonomy->rewrite['slug']]=$key; }else{ $taxonomies_data[$key]=$key; } } return $taxonomies_data; } private static function get_image_ids_by_urls( $image_urls = [] , $locale='en') { global $wpdb; // Convert single URL string to array if ( is_string( $image_urls ) ) { $image_urls = [ $image_urls ]; } if ( empty( $image_urls ) || ! is_array( $image_urls ) ) { return []; } $upload_dir = wp_upload_dir(); $base_url = $upload_dir['baseurl']; $results = []; $cleaned_paths_map = []; // [cleaned_path] => [original_urls] foreach ( $image_urls as $url ) { if ( strpos( $url, $base_url ) === false ) { $results[ $url ] = false; continue; } // Relative path $relative_path = str_replace( $base_url . '/', '', $url ); // Strip size suffix if present $cleaned_path = preg_replace( '/-\d+x\d+(?=\.(jpg|jpeg|png|gif|webp)$)/i', '', $relative_path ); // Map cleaned path to original URL(s) if ( ! isset( $cleaned_paths_map[ $cleaned_path ] ) ) { $cleaned_paths_map[ $cleaned_path ] = []; } $cleaned_paths_map[ $cleaned_path ][] = $url; } $like_parts = []; $args = []; // Build the OR'ed LIKEs and gather args foreach ( array_keys( $cleaned_paths_map ) as $path ) { $like_parts[] = 'guid LIKE %s'; // Always escape for LIKE, then add wildcards yourself $args[] = '%' . $wpdb->esc_like( $path ) . '%'; } // Nothing to search? bail early. if ( empty( $like_parts ) ) { return $results; } // Compose the SQL with placeholders only $sql = " SELECT ID, guid FROM {$wpdb->posts} WHERE post_type = %s AND (" . implode( ' OR ', $like_parts ) . ") "; // First arg is for post_type, then all the LIKE args array_unshift( $args, 'attachment' ); // Prepare once with all arguments (wpdb::prepare accepts an array) $prepared = $wpdb->prepare( $sql, $args ); // Run the query $found = $wpdb->get_results( $prepared ); // Map found GUIDs to IDs $guid_to_id = []; foreach ( $found as $row ) { $guid_to_id[ $row->guid ] = esc_url($row->guid); $translated_post=pll_get_post(intval($row->ID), $locale); if($translated_post){ $image_url=wp_get_attachment_url(intval($translated_post)); if($image_url && !empty($image_url)){ $guid_to_id[$row->guid]=esc_url($image_url); } } } // Match original URLs to found IDs foreach ( $image_urls as $original_url ) { $found_id = false; if(isset($guid_to_id[$original_url])){ $results[$original_url]=$guid_to_id[$original_url]; continue; } // Exact match first if ( isset( $guid_to_id[ $original_url ] ) ) { $found_id = $guid_to_id[ $original_url ]; } else { // Fallback to cleaned path match $relative_path = str_replace( $base_url . '/', '', $original_url ); $cleaned_path = preg_replace('/-\d+x\d+(?=\.(jpg|jpeg|png|gif|webp)$)/i', '', $relative_path ); preg_match('/-\d+x\d+(?=\.(jpg|jpeg|png|gif|webp)$)/i', $original_url, $matches); $suffix = isset($matches[0]) ? $matches[0] : ''; foreach ( $guid_to_id as $guid => $url ) { if ( strpos( $guid, $cleaned_path ) !== false ) { if (!empty($suffix)) { // Insert $suffix before the file extension in $url $found_id = preg_replace('/(\.[a-zA-Z0-9]+)$/', $suffix . '$1', $url); } else { $found_id = $url; } break; } } } $results[ $original_url ] = esc_url($found_id); } return $results; } public static function translation_data_migration(){ $already_migrated = get_option('atfp_translation_string_migration', false); if(!$already_migrated){ $old_data=get_option('cpt_dashboard_data', array()); $updated=array(); if(isset($old_data['atfp']) && count($old_data['atfp']) > 0){ foreach($old_data['atfp'] as $data){ $updated_data=$data; if(isset($data['string_count'])){ $updated_data['word_count']=$data['string_count']; $updated_data['string_count']=$data['string_count'] / 30; } if(isset($data['source_string_count'])){ $updated_data['source_word_count']=$data['source_string_count']; $updated_data['source_string_count']=$data['source_string_count'] / 30; } $updated[]=$updated_data; } if(count($updated) > 0){ $old_data['atfp']=$updated; update_option('cpt_dashboard_data', $old_data); } } update_option('atfp_translation_string_migration', true); } } public static function get_custom_fields_data(){ $result=self::get_custom_fields_query(); $data=array(); if($result && is_array($result)){ $excluded_fields=self::get_excluded_custom_fields_keys(); $allowed_fields=self::get_allowed_custom_fields(); foreach($result as $result){ if(in_array($result['meta_key'], $excluded_fields)){ continue; } $serialized_value=maybe_unserialize($result['meta_value']); $value_type=json_decode($result['meta_value'], true) ? 'array' : (is_array($serialized_value) ? 'array' : 'string'); $type=isset($allowed_fields[$result['meta_key']]) && true === $allowed_fields[$result['meta_key']]['status'] ? $allowed_fields[$result['meta_key']]['type'] : $value_type; $status=isset($allowed_fields[$result['meta_key']]) && true === $allowed_fields[$result['meta_key']]['status'] ? 'Supported' : 'Unsupported'; $data[sanitize_text_field($result['meta_key'])]=['type'=>$type, 'status'=>$status]; } } $default_allowed_fields=self::get_default_allowed_fields(); $default_key_diff=array_diff(array_keys($default_allowed_fields), array_keys($data)); foreach($default_key_diff as $key){ $status='Supported'; $saved_allowed_fields=get_option('atfp_allowed_custom_fields', false); $status=isset($saved_allowed_fields[$key]) && true === $saved_allowed_fields[$key]['status'] ? 'Supported' : 'Unsupported'; $data[$key]=['type'=>$default_allowed_fields[$key]['type'], 'status'=>$status]; } $data=apply_filters('atfp/custom_fields/all_fields', $data); return $data; } private static function get_custom_fields_query(){ global $wpdb; // Escape LIKE pattern for system meta (_%) $like_pattern = $wpdb->esc_like('_') . '%'; // SQL with DISTINCT + filtering $sql = $wpdb->prepare( " SELECT DISTINCT pm.meta_key, pm.meta_value FROM {$wpdb->postmeta} pm WHERE pm.meta_key NOT LIKE %s AND pm.meta_value <> '' -- skip empty AND pm.meta_value NOT IN ('0','1') -- skip boolean AND pm.meta_value NOT REGEXP '^[0-9]+$' -- skip integer AND pm.meta_value NOT REGEXP '^[0-9]+\\.[0-9]+$' -- skip decimal AND pm.meta_value NOT REGEXP '^(https?:\/\/|www\.)[A-Za-z0-9\.\-]+.*$' -- skip URLs ORDER BY pm.meta_key ASC ", $like_pattern ); // Get results $results = $wpdb->get_results($sql, ARRAY_A); return $results; } private static function get_excluded_custom_fields_keys(){ $excluded_fields= array( '_edit_last', '_edit_lock', '_wp_page_template', '_wp_attachment_metadata', '_icl_translator_note', '_alp_processed', '_pingme', '_encloseme', '_icl_lang_duplicate_of', 'atfpp_parent_post_language', 'atfp_parent_post_language_slug', 'atfpp_parent_post_language_slug', 'twae_exists', 'twae_post_migration', 'twae_style_migration', '_thumbnail_id', ); return apply_filters('atfp/custom_fields/excluded_keys', $excluded_fields); } public static function get_allowed_custom_fields(){ $allowed_custom_fields=self::get_allowed_custom_fields_data(); $allowed_custom_fields=apply_filters('atfp/custom_fields/allowed_fields', $allowed_custom_fields); return $allowed_custom_fields; } private static function get_allowed_custom_fields_data(){ $allowed_fields=get_option('atfp_allowed_custom_fields', false); if($allowed_fields && is_array($allowed_fields) && count($allowed_fields) > 0){ return $allowed_fields; } if(!$allowed_fields || !is_array($allowed_fields)){ $default_allowed_fields=self::get_default_allowed_fields(); foreach($default_allowed_fields as $key => $value){ $allowed_fields[$key]=['status'=>true, 'type'=>'string']; } update_option('atfp_allowed_custom_fields', $allowed_fields); } ksort($allowed_fields); return $allowed_fields; } private static function get_default_allowed_fields(){ $found=false; $response = wp_remote_get( esc_url_raw( ATFPP_URL . 'includes/block-translation-rules/default-allow-metafields.json' ), array( 'timeout' => 15, ) ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { global $wp_filesystem; // Initialize the WordPress filesystem if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); $local_path = ATFPP_DIR_PATH . 'includes/block-translation-rules/default-allow-metafields.json'; if($wp_filesystem->exists($local_path) && $wp_filesystem->is_readable( $local_path )){ $found=true; $default_allowed_fields = $wp_filesystem->get_contents( $local_path ); } }else{ $found=true; $default_allowed_fields = wp_remote_retrieve_body( $response ); } if(!$found){ return array(); } return json_decode($default_allowed_fields, true) ; } public static function bulk_translation_render($current_screen){ global $polylang; if(!$polylang || !property_exists($polylang, 'model')){ return false; } $translated_post_types = $polylang->model->get_translated_post_types(); $translated_taxonomies = $polylang->model->get_translated_taxonomies(); $translated_post_types = array_values($translated_post_types); $translated_taxonomies = array_values($translated_taxonomies); $translated_post_types=array_filter($translated_post_types, function($post_type){ return is_string($post_type); }); $translated_taxonomies=array_filter($translated_taxonomies, function($taxonomy){ return is_string($taxonomy); }); $valid_post_type=(isset($current_screen->post_type) && !empty($current_screen->post_type)) && in_array($current_screen->post_type, $translated_post_types) && $current_screen->post_type !== 'attachment' ? $current_screen->post_type : false; $valid_taxonomy=(isset($current_screen->taxonomy) && !empty($current_screen->taxonomy)) && in_array($current_screen->taxonomy, $translated_taxonomies) ? $current_screen->taxonomy : false; if((!$valid_post_type && !$valid_taxonomy) || ((!$valid_post_type || empty($valid_post_type)) && !isset($valid_taxonomy)) || (isset($current_screen->taxonomy) && !empty($current_screen->taxonomy) && !$valid_taxonomy)){ return false; } return true; } } }