Files
Jacek Pyziak cd264483f8 Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards.
- Added getallheaders function to retrieve HTTP headers in a compatible manner.
- Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts.
- Introduced function for triggering deprecation notices in Symfony.
2025-12-28 12:44:00 +01:00

632 lines
19 KiB
PHP

<?php
/**
* ATFPP Ajax Handler
*
* @package ATFPP
*/
/**
* Do not access the page directly
*/
if (! defined('ABSPATH')) {
exit;
}
/**
* ATFPP Helper
*/
if (! class_exists('ATFPP_Helper')) {
class ATFPP_Helper
{
/**
* Member Variable
*
* @var instance
*/
private static $instance;
/**
* Stores custom block data for processing and retrieval.
*
* This static array holds the data related to custom blocks that are
* used within the plugin. It can be utilized to manage and manipulate
* the custom block information as needed during AJAX requests.
*
* @var array
*/
private $custom_block_data_array = array();
/**
* Gets an instance of our plugin.
*
* @param object $settings_obj timeline settings.
*/
public static function get_instance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public static function get_custom_block_post_id()
{
$first_post_id = null;
$query = new WP_Query(
array(
'post_type' => '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('/<img[^>]+(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;
}
}
}