Plugin Directory

Changeset 3170433


Ignore:
Timestamp:
10/17/2024 03:46:28 AM (5 months ago)
Author:
mvpis
Message:

Major update to improve html processing. Thank you for all of the great feedback, Jean, Jessica and many more

Location:
fluentc-translation
Files:
379 added
14 edited

Legend:

Unmodified
Added
Removed
  • fluentc-translation/trunk/bootstrap.php

    r3148988 r3170433  
    4646            '\FluentC\Services\Widget',
    4747            '\FluentC\Services\Connect',
    48             '\FluentC\Services\Html',
    4948            '\FluentC\Services\Scan',
    5049            '\FluentC\Services\TranslationProcessor',
     50            '\FluentC\Services\HtmlProcessor',
     51            '\FluentC\Services\TranslationManager',
    5152
    5253        );
  • fluentc-translation/trunk/fluentc_autoload.php

    r3122693 r3170433  
    1414 * @return void
    1515 */
    16 function fluentc_autoload( $class_name ) {
    17     $dir_class = __DIR__ . '/src/';
    18     $prefix     = 'class-';
    19     $file_parts = explode( '\\', $class_name );
     16function fluentc_autoload($class_name) {
     17    $dir_class = __DIR__ . '/src/';
     18    $prefixes = ['class-', 'interface-', 'trait-']; // Add more prefixes if needed
     19    $file_parts = explode('\\', $class_name);
    2020
    21     $total_parts = count( $file_parts ) - 1;
    22     $dir_file    = $dir_class;
    23     for ( $i = 1; $i <= $total_parts; $i++ ) {
    24         if ( $total_parts !== $i ) {
    25             $dir_file .= strtolower( $file_parts[ $i ] ) . '/';
    26         } else {
    27             $string    = str_replace( '_', '-', strtolower( $file_parts[ $i ] ) );
    28             $file_load = $dir_file . $prefix . $string . '.php';
     21    $total_parts = count($file_parts) - 1;
     22    $dir_file = $dir_class;
     23    for ($i = 1; $i <= $total_parts; $i++) {
     24        if ($total_parts !== $i) {
     25            $dir_file .= strtolower($file_parts[$i]) . '/';
     26        } else {
     27            $string = str_replace('_', '-', strtolower($file_parts[$i]));
     28           
     29            // Try each prefix
     30            foreach ($prefixes as $prefix) {
     31                $file_load = $dir_file . $prefix . $string . '.php';
     32                if (file_exists($file_load)) {
     33                    include_once $file_load;
     34                    return;
     35                }
     36            }
    2937
    30             if ( file_exists( $file_load ) ) {
    31                 include_once $file_load;
    32             }
    33         }
    34     }
     38            // If no prefixed file found, try without prefix
     39            $file_load = $dir_file . $string . '.php';
     40            if (file_exists($file_load)) {
     41                include_once $file_load;
     42                return;
     43            }
     44        }
     45    }
    3546}
  • fluentc-translation/trunk/fluentc_wordpress_plugin.php

    r3162981 r3170433  
    55/**
    66 * Plugin Name: FluentC Translation
    7  * Plugin URI: https://github.com/fluentc/wordpress-plugin
     7 * Plugin URI: https://www.fluentc.ai
    88 * Description: A plugin that enables website owners to easily install the FluentC Translation on their WordPress site.
    9  * Version: 1.9.6
     9 * Version: 2.0
    1010 * Author: FluentC
    1111 * Author URI: https://www.fluentc.ai
     
    1717define( 'FLUENTC_DIR', __DIR__ );
    1818define( 'FLUENTC_SLUG', 'fluentc_translation' );
    19 define( 'FLUENTC_TRANSLATION_VERSION', "1.9.6" );
     19define( 'FLUENTC_TRANSLATION_VERSION', "2.0" );
    2020define( 'FLUENTC_TRANSLATION_PLUGIN_DIR', plugin_dir_path(__FILE__) );
    2121define( 'FLUENTC_TRANSLATION_PLUGIN_URL', plugin_dir_url(__FILE__) );
  • fluentc-translation/trunk/readme.txt

    r3162981 r3170433  
    55Requires at least: 4.6
    66Tested up to: 6.6.2
    7 Stable tag: 1.9.6
     7Stable tag: 2.0
    88Requires PHP: 7.3
    99License: GPLv2 or later
  • fluentc-translation/trunk/src/actions/class-wordpress.php

    r3162981 r3170433  
    1717use FluentC\Models\Htmltags;
    1818use FluentC\Services\Connect;
    19 use FluentC\Services\Html;
    2019use FluentC\Utils\Language;
    2120use FluentC\Services\Cache;
    22 use PHPHtmlParser\Dom;
    23 use PHPHtmlParser\Options;
     21use FluentC\Services\Html_Processor;
     22use FluentC\Services\Translation_Manager;
     23use FluentC\Utils\Regex_Helper;
     24use FluentC\Utils\Placeholder_Manager;
     25use FluentC\Utils\Performance_Monitor;
    2426
    2527/**
     
    9092     */
    9193    protected $widgetapikey;
    92 
    93 
     94 
     95    /**
     96     * HtmlProcessor instance
     97     *
     98     * @var Html_Processor
     99     */
     100    protected $htmlProcessor;
     101
     102    private $translation_manager;
     103private  $regexHelper;
     104private $placeholderManager;
    94105    /**
    95106     * Constructor.
     
    97108     * @since 1.2
    98109     */
    99     public function __construct() {
    100         $this->fluentc_connect = new Connect();
    101         $this->fluentc_language = new Language();
    102         $this->fluentc_htmltags = new Htmltags();
    103         $this->fluentc_cache = new Cache();
    104         $this->translated_text  = array();
    105         $this->language_code = $this->fluentc_language->get_fluentc_language();
    106         $this->widgetapikey  = get_option( 'fluentc_api_key' );
    107         $this->site_language = $this->fluentc_language->fluentc_site_language();
    108         $this->fluentc_html     = new Html($this->site_language);
    109 
    110     }
     110    public function __construct() {
     111        $this->fluentc_connect = new Connect();
     112        $this->fluentc_language = new Language();
     113        $this->fluentc_htmltags = new Htmltags();
     114        $this->fluentc_cache = new Cache();
     115        $this->translated_text  = array();
     116        $this->language_code = $this->fluentc_language->get_fluentc_language();
     117        $this->widgetapikey  = get_option( 'fluentc_api_key' );
     118        $this->site_language = $this->fluentc_language->fluentc_site_language();
     119        // Initialize new components
     120     
     121       
     122    }
    111123
    112124    /**
     
    116128     */
    117129    public function hooks() {
    118         add_filter( 'the_title', array( $this, 'fluentcFilterTitle' ), 9999, 2 );
     130        /* add_filter( 'the_title', array( $this, 'fluentcFilterTitle' ), 9999, 2 );
    119131        add_filter( 'the_content', array( $this, 'filter_content' ), 9999, 1 );
    120132        add_filter( 'widget_text', array( $this, 'fluentcFilterWidgetText' ), 9999, 2 );
     
    128140        add_filter( 'woocommerce_product_shortcode_content', array( $this, 'filter_content' ), 100, 1 );
    129141        add_filter( 'woocommerce_short_description', array( $this, 'filter_content' ), 100, 1 );
    130         add_filter( 'woocommerce_cart_shortcode_content', array( $this, 'filter_content' ), 100, 1 );
     142        add_filter( 'woocommerce_get_script_data', array( $this, 'ffluentc_woo_script_data' ), 100, 1 );
    131143        add_filter( 'shutdown', array( $this, 'fluentc_shutdown' ), 100, 1 );
    132         add_filter( 'init', array( $this, 'fluentc_language_redirect' ), 10 );
    133         add_filter( 'woocommerce_get_script_data', array( $this, 'fluentc_woo_script_data' ), 10, 2 );
     144       */
     145        add_filter( 'woocommerce_ajax_get_endpoint', array( $this, 'correct_multilingual_ajax_endpoint' ), 100, 2 );
     146        add_action('init', [$this, 'init_output_buffering'], 0);
     147        add_action('shutdown', [$this, 'fluentc_flush_output_buffer'], 0);
    134148    }
    135149
    136150
    137 /**
    138  * Modify WooCommerce script data.
    139  *
    140  * This function intercepts the data being localized for WooCommerce scripts
    141  * and modifies the AJAX URL for the cart fragments script. This is useful
    142  * for multilingual setups where the default AJAX URL might not work correctly.
    143  *
    144  * @param array  $params The original script parameters.
    145  * @param string $handle The script handle.
    146  * @return array The modified script parameters.
    147  */
    148 
    149 public function fluentc_woo_script_data( $params, $handle ) {
    150     if ( 'wc-cart-fragments' === $handle ) {
    151         $params['ajax_url'] = get_bloginfo('url') . '/?wc-ajax=%%endpoint%%';
    152     }
    153     return $params;
    154 }
    155     /**
    156      * Filters Blocks
    157      *
    158      * @param  string $block_content Content of a Block.
    159      * @param  string $block Block Object.
    160      */
    161     public function filter_block($block_content, $block)
     151    public function correct_multilingual_ajax_endpoint($endpoint, $request) {
     152        // Get the site's home URL
     153        $home_url = home_url('/');
     154       
     155        // Parse the current URL
     156        $parsed_url = parse_url($endpoint);
     157       
     158        // If the host is missing or incorrect, rebuild the URL
     159        if (!isset($parsed_url['host']) || $parsed_url['host'] !== parse_url($home_url, PHP_URL_HOST)) {
     160            $corrected_endpoint = $home_url;
     161           
     162            // Remove the leading slash if it exists
     163            $path = ltrim($parsed_url['path'], '/');
     164           
     165            // Reconstruct the URL
     166            $corrected_endpoint .= $path;
     167            if (isset($parsed_url['query'])) {
     168                $corrected_endpoint .= '?' . $parsed_url['query'];
     169            }
     170           
     171            return $corrected_endpoint;
     172        }
     173       
     174        return $endpoint;
     175    }
     176
     177public function init_output_buffering()
    162178{
    163  
    164     if (!$this->should_process_content($block_content)) {
    165         return $block_content;
    166     }
    167 
    168     $dom = $this->load_dom($block_content);
    169    
    170     $texts_to_translate = $entry_map = $entry_skip_map = [];
    171 
    172     $this->process_dom_elements($dom, $texts_to_translate, $entry_map, $entry_skip_map);
    173  
    174     if (!empty($texts_to_translate)) {
    175         $this->process_translations($texts_to_translate, $entry_map, $entry_skip_map);
    176     }
    177    
    178     $html = $this->get_processed_html($dom);
    179    
    180     return $html ?: $block_content;
    181 }
    182    
    183  /**
    184      * Apply translations to nodes
    185      */
    186     private function applyTranslations($entry_map, $translated_texts, $language_code)
    187     {
    188         foreach ($translated_texts as $translatedText) {
    189             $original_text = $translatedText->originalText;
    190             $translated_text = $translatedText->translatedText;
    191            
    192             if (isset($entry_map[$original_text]) && $translated_text !== null && $translated_text !== '') {
    193                 foreach ($entry_map[$original_text] as $node) {
    194                     $this->applyTranslationToNode($node, $translated_text, $node->getDataAttributes());
    195                 }
    196                
    197                 $this->update_translation_cache($original_text, $translated_text, $language_code);
    198             } else {
    199                 do_action('qm/info', 'No Translated Text or No Matching Nodes');
    200             }
    201         }
    202     }
    203 
     179    ob_start([$this, 'process_final_output']);
     180}
     181
     182public function process_final_output($buffer)
     183{
     184    // Don't process admin pages.
     185    if (is_admin()) {
     186        return $buffer;
     187    }
     188    // Don't process RSS feeds.
     189    if (is_feed()) {
     190        return $buffer;
     191    }
     192
     193     // Don't process JSON responses.
     194     if ($this->is_json($buffer)) {
     195        return $buffer;
     196    }
     197
     198    // Process the entire page content.
     199    return $this->filter_content($buffer);
     200}
     201
     202private function is_json($string) {
     203    json_decode($string);
     204    return (json_last_error() == JSON_ERROR_NONE);
     205}
     206public function fluentc_flush_output_buffer()
     207{
     208    if (ob_get_length()) {
     209        ob_end_flush();
     210    }
     211}
    204212    /**
    205      * Apply skipped translations
    206      */
    207     private function applySkippedTranslations($entry_skip_map)
    208     {
    209         foreach ($entry_skip_map as $node) {
    210             $original_text = $node->text();
    211             $key = hash('md5', $original_text);
    212            
    213             if (isset($this->translated_text[$key]) && null !== $this->translated_text[$key]) {
    214                 $this->applyTranslationToNode($node, $this->translated_text[$key], $node->getDataAttributes());
    215             }
    216         }
    217     }
    218 
    219     /**
    220      * Process individual node
    221      */
    222     private function processNode($node, &$texts_to_translate, &$entry_map, &$entry_skip_map)
    223 {
    224     if ($node instanceof \PHPHtmlParser\Dom\Node\HtmlNode) {
    225         // Process child nodes of HTML elements
    226         foreach ($node->getChildren() as $child) {
    227             $this->processNode($child, $texts_to_translate, $entry_map, $entry_skip_map);
    228         }
    229         return;
    230     }
    231 
    232     if (!($node instanceof \PHPHtmlParser\Dom\Node\TextNode)) {
    233         return;
    234     }
    235 
    236     if ($this->should_skip_node($node)) {
    237         return;
    238     }
    239 
    240     $text = $node->text();
    241 
    242     if ($this->is_shortcode($text)) {
    243         return;
    244     }
    245 
    246     if ($this->is_empty_or_numeric($text)) {
    247         return;
    248     }
    249 
    250     if (in_array($text, $this->fluentc_htmltags->forbidden_selectors)) {
    251         $node->setText('');
    252         return;
    253     }
    254 
    255     $this->process_node_text($node, $text, $texts_to_translate, $entry_map, $entry_skip_map);
    256 }
    257 
    258     /**
    259      * Apply translation to node
    260      */
    261     private function applyTranslationToNode($node, $translatedText, $dataAttributes)
    262     {
    263         $node->setText($translatedText);
    264         foreach ($dataAttributes as $dataKey => $dataValue) {
    265             $node->tag->setAttribute($dataKey, $dataValue);
    266         }
    267     }
    268     /**
    269      * Filters Content
    270      *
    271      * @param  string $content Content Object.
    272      */
    273     public function filter_content( $content ) {
    274        
     213     * Filters Content
     214     *
     215     * @param  string $content Content Object.
     216     */
     217    public function filter_content($content)
     218    {
    275219        if (!$this->should_process_content($content)) {
    276220            return $content;
    277221        }
    278 
    279         $dom = $this->load_dom($content);
    280         $texts_to_translate = $entry_map = $entry_skip_map = [];
    281 
    282         $this->process_dom_elements($dom, $texts_to_translate, $entry_map, $entry_skip_map);
    283 
    284         if (!empty($texts_to_translate)) {
    285             $this->process_translations($texts_to_translate, $entry_map, $entry_skip_map);
    286         }
    287 
    288         $html = $this->get_processed_html($dom);
    289        
    290         return $html ?: $content;
    291     }
    292 
    293     /**
    294      * Updates Home page link
    295      *
    296      * @param  string $title Title of the posts.
    297      * @param  string $id id of the post.
    298      */
     222        $translation_manager = $this->get_translation_manager();
     223        if ($translation_manager === null) {
     224            return $content; // No translation needed
     225        }
     226       
     227    $html = $this->htmlProcessor->processHtml(
     228        $content,
     229        $this->site_language,
     230        $this->language_code
     231    );
     232   
     233    return $html ?: $content;
     234    }
     235
     236
     237    /**
     238     * Helper function to check if content should be processed
     239     */
     240    private function should_process_content($content)
     241    {
     242        return $this->language_code && !is_null($content) && $content !== '';
     243    }
     244
     245
     246
    299247     /**
     248     * Filters Blocks
     249     *
     250     * @param  string $block_content Content of a Block.
     251     * @param  string $block Block Object.
     252     */
     253    public function filter_block($block_content, $block)
     254    {
     255        if (!$this->should_process_content($block_content)) {
     256            return $block_content;
     257        }
     258
     259        $translation_manager = $this->get_translation_manager();
     260        if ($translation_manager === null) {
     261            return $block_content; // No translation needed
     262        }
     263       
     264        $html = $this->htmlProcessor->processHtml(
     265            $block_content,
     266            $this->site_language,
     267            $this->language_code,
     268        );
     269       
     270        // Remove the translated flag before returning
     271        return $html ?: $block_content;
     272    }
     273    /**
    300274     * Filter title
    301275     */
    302276    public function fluentcFilterTitle($title, $id = null)
    303277    {
    304        
    305278        if (!$this->language_code) {
    306279            return $title;
    307280        }
    308 
    309         $key = hash('md5', $title . ($id ? "_$id" : ''));
    310 
    311         if (isset($this->translated_text[$key])) {
    312             return $this->translated_text[$key];
    313         }
    314 
    315         $translated_text = $this->get_cached_translation($key);
    316         if ($translated_text) {
    317             return $translated_text;
    318         }
    319 
    320         $translated_data = $this->fluentc_connect->get_translation_text(
    321             $this->widgetapikey,
    322             $this->site_language,
    323             $this->language_code,
    324             $title,
    325             $id
     281        $translation_manager = $this->get_translation_manager();
     282        if ($translation_manager === null) {
     283            return $title; // No translation needed
     284        }
     285       
     286    $html = $this->htmlProcessor->processHtml(
     287        $title,
     288        $this->site_language,
     289        $this->language_code
     290     
     291    );
     292   
     293    return $html ?: $title;
     294    }
     295
     296     
     297     /**
     298     * Updates checkout link
     299     *
     300     * @param  mixed $output HTML Content.
     301     * @param  mixed $tag HTML Content.
     302     * @param  mixed $attr HTML Content.
     303     */
     304    public function fluentc_woocommerce_checkout_shortcode_filter($output, $tag, $attr)
     305    {
     306        if (!$this->should_process_content($output)) {
     307            return $output;
     308        }
     309        $translation_manager = $this->get_translation_manager();
     310        if ($translation_manager === null) {
     311            return $output; // No translation needed
     312        }
     313        $html = $this->htmlProcessor->processHtml(
     314            $output,
     315            $this->site_language,
     316            $this->language_code
     317           
    326318        );
    327 
    328         if (isset($translated_data->data->translateSite->body)) {
    329             $translated_text = $translated_data->data->translateSite->body[0]->translatedText;
    330             $this->update_translation_cache($title, $translated_text, $this->language_code);
    331             return $translated_text;
    332         } else {
    333             $this->translated_text[$key] = $title;
    334             do_action('qm/error', 'Translation failed for title: ' . $title);
    335             return $title;
    336         }
    337     }
    338 
     319       
     320        return $html ?: $output;
     321    }
     322
     323
     324    /**
     325     * Updates Home page link
     326     *
     327     * @param  string $text Text Content.
     328     * @param  string $instance Instance details of the widget.
     329     */
     330    public function fluentcFilterWidgetText($text, $instance)
     331    {
     332        if (!$this->should_process_content($text)) {
     333            return $text;
     334        }
     335
     336        $html = $this->htmlProcessor->processHtml(
     337            $text,
     338            $this->site_language,
     339            $this->language_code
     340        );
     341       
     342        return $html ?: $text;
     343    }
    339344   
    340 
    341     /**
    342      * Updates Home page link
    343      *
    344      * @param  string $text Text Content.
    345      * @param  string $instance Instance details of the widget.
    346      */
    347     public function fluentcFilterWidgetText( $text, $instance ) {
    348         {
    349            
    350             if (!$this->should_process_content($text)) {
    351                 return $text;
    352             }
    353    
    354             $dom = $this->load_dom($text);
    355             $texts_to_translate = $entry_map = $entry_skip_map = [];
    356    
    357             $this->process_dom_elements($dom, $texts_to_translate, $entry_map, $entry_skip_map);
    358    
    359             if (!empty($texts_to_translate)) {
    360                 $this->process_translations($texts_to_translate, $entry_map, $entry_skip_map);
    361             }
    362    
    363             $html = $this->get_processed_html($dom);
    364            
    365             return $html ?: $text;
    366         }
    367     }
    368 
    369     /**
    370      * Updates checkout link
    371      *
    372      * @param  mixed $output HTML Content.
    373      * @param  mixed $tag HTML Content.
    374      * @param  mixed $attr HTML Content.
    375      */
    376     public function fluentc_woocommerce_checkout_shortcode_filter( $output, $tag, $attr ){
    377         {
    378            
    379             if (!$this->should_process_content($output)) {
    380                 return $output;
    381             }
    382    
    383             $dom = $this->load_dom($output);
    384             $texts_to_translate = $entry_map = $entry_skip_map = [];
    385    
    386             $this->process_dom_elements($dom, $texts_to_translate, $entry_map, $entry_skip_map);
    387    
    388             if (!empty($texts_to_translate)) {
    389                 $this->process_translations($texts_to_translate, $entry_map, $entry_skip_map);
    390             }
    391    
    392             $html = $this->get_processed_html($dom);
    393            
    394             return $html ?: $output;
    395         }
    396     }
    397345
    398346    /**
     
    406354    }
    407355
    408 /**
    409  * Process text labels by removing duplicates and formatting for use in a query.
    410  *
    411  * @param array $texts Array of text labels to process.
    412  * @return string Formatted string of unique text labels.
    413  */
    414     public function process_text_labels($texts)
    415     {
    416         $unique_texts = array_unique(array_filter($texts));
    417         return implode(', ', array_map(function($text) {
    418             return '"' . str_replace('"', '&quot;', $text) . '"';
    419         }, $unique_texts));
    420     }
    421 
    422    
    423 
    424     /**
    425      * Helper function to check if content should be processed
    426      */
    427     private function should_process_content($content)
    428     {
    429         return $this->language_code && !is_null($content) && $content !== '';
    430     }
    431 
    432     /**
    433      * Helper function to load DOM
    434      */
    435     private function load_dom($content)
    436     {
    437         $html = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    438         $options = new Options();
    439         $options->setCleanupInput(false);
    440         $options->setWhitespaceTextNode(false);
    441         $options->setRemoveStyles(false);
    442         $options->setPreserveLineBreaks(true);
    443         $dom = new Dom;
    444         $dom->loadStr($html, $options);
    445         return $dom;
    446     }
    447 
    448     /**
    449      * Helper function to process DOM elements
    450      */
    451     private function process_dom_elements($dom, &$texts_to_translate, &$entry_map, &$entry_skip_map)
    452     {
    453         $elements = $dom->find('*:not(script):not(style):not(doctype):not(code):not(figure):not(pre):not(noscript):not(iframe):not(object):not(embed):not(svg):not(math):not(canvas)');
    454         foreach ($elements as $element) {
    455             $nodes = ($element instanceof \PHPHtmlParser\Dom\Node\HtmlNode) ? $element->getChildren() : [$element];
    456             foreach ($nodes as $node) {
    457                 $this->processNode($node, $texts_to_translate, $entry_map, $entry_skip_map);
    458             }
    459         }
    460     }
    461 
    462     /**
    463      * Helper function to process translations
    464      */
    465     private function process_translations(&$texts_to_translate, &$entry_map, &$entry_skip_map)
    466     {
    467         $uncached_texts = [];
    468         $cached_translations = [];
    469 
    470         foreach ($texts_to_translate as $text) {
    471             $cached_translation = $this->get_cached_translation(hash('md5', $text));
    472             if ($cached_translation) {
    473                 $cached_translations[] = $cached_translation;
    474             } else {
    475                 $uncached_texts[] = $text;
    476             }
    477         }
    478 
    479         if (!empty($uncached_texts)) {
    480             $text_labels = $this->process_text_labels($uncached_texts);
    481             $translated_texts = $this->fluentc_connect->get_translation_content(
    482                 $this->widgetapikey,
    483                 $this->site_language,
    484                 $this->language_code,
    485                 $text_labels
    486             );
    487 
    488             if (isset($translated_texts->data->translateSite->body) && !empty($translated_texts->data->translateSite->body)) {
    489                 $cached_translations = array_merge($cached_translations, $translated_texts->data->translateSite->body);
    490             } else {
    491                 do_action('qm/debug', 'Translation error: Data not found in response.');
    492             }
    493         }
    494 
    495         $this->applyTranslations($entry_map, $cached_translations, $this->language_code);
    496     }
    497 
    498     /**
    499      * Helper function to get processed HTML
    500      */
    501     private function get_processed_html($dom)
    502 {
    503     if ($dom->root && method_exists($dom->root, 'outerHtml')) {
    504         try {
    505             $html = $dom->root->outerHtml();
     356
     357    private function get_translation_manager()
     358    {
     359        if ($this->translation_manager === null) {
    506360           
    507             // Remove the root tag if it exists
    508             $html = preg_replace('/<root>|<\/root>/', '', $html);
     361                $this->translation_manager = new Translation_Manager(
     362                    $this->fluentc_connect,
     363                    $this->fluentc_cache,
     364                    $this->site_language,
     365                    $this->language_code
     366                );
     367
     368                $this->regexHelper = new Regex_Helper();
     369                $this->placeholderManager = new Placeholder_Manager();
     370                $performanceMonitor = new Performance_Monitor() ;
     371                $this->htmlProcessor = new Html_Processor($this->regexHelper, $this->placeholderManager ,$this->translation_manager, $this->fluentc_htmltags);
    509372           
    510             if ($html !== null && $html !== '' && is_string($html) && strlen(trim($html)) > 0) {
    511                 // Temporarily replace complex structures.
    512                 $html = $this->replace_complex_structures($html);
    513 
    514                 // Process only text nodes for translation.
    515                 $html = preg_replace_callback('/>((?:[^<]|<(?!\/?\w+(?:\s|>)))+)</s', function($matches) {
    516                     $text = $matches[1];
    517                     if (trim($text) !== '') {
    518                         $translated_text = $this->translate_text($text);
    519                         return '>' . $translated_text . '<';
    520                     }
    521                     return $matches[0];
    522                 }, $html);
    523 
    524                 // Restore complex structures
    525                 $html = $this->restore_complex_structures($html);
    526 
    527                 return $html;
    528             }
    529         } catch (\Exception $e) {
    530             do_action('qm/error', 'Error in get_processed_html: ' . $e->getMessage());
    531         }
    532     } else {
    533         do_action('qm/info', 'Root or outerHtml method not found');
    534     }
    535     return null;
    536 }
    537 
    538 /**
    539  * Summary of replace_complex_structures
    540  * @param mixed $html
    541  * @return array|string|null
    542  */
    543 private function replace_complex_structures($html)
    544 {
    545     // Replace iframes
    546     $html = preg_replace_callback('/<iframe\b[^>]*>(.*?)<\/iframe>/is', function($matches) {
    547         return '###IFRAME' . base64_encode($matches[0]) . '###';
    548     }, $html);
    549 
    550     // Replace data attributes
    551     $html = preg_replace_callback('/\s(data-[^\s"\']+(?:=(?:"[^"]*"|\'[^\']*\'|[^\s"\']+))?)/i', function($matches) {
    552         return ' ###DATA' . base64_encode($matches[0]) . '###';
    553     }, $html);
    554 
    555     // Replace complex class attributes
    556     $html = preg_replace_callback('/\sclass="[^"]+"/i', function($matches) {
    557         return ' ###CLASS' . base64_encode($matches[0]) . '###';
    558     }, $html);
    559 
    560     // Preserve entire Elementor video widget
    561     $html = preg_replace_callback('/<div class="elementor-element[^>]*elementor-widget-video[^>]*>.*?<\/div>/s', function($matches) {
    562         return '###ELEMENTOR_VIDEO_WIDGET' . base64_encode($matches[0]) . '###';
    563     }, $html);
    564     // Preserve Elementor video widget data-settings
    565     $html = preg_replace_callback('/(data-settings=)(["\'])(.+?)\2/s', function($matches) {
    566         return $matches[1] . $matches[2] . '###ELEMENTOR_VIDEO_SETTINGS' . base64_encode($matches[3]) . '###' . $matches[2];
    567     }, $html);
    568 
    569     return $html;
    570 }
    571 
    572 /**
    573  * Summary of restore_complex_structures
    574  * @param mixed $html
    575  * @return array|string|null
    576  */
    577 private function restore_complex_structures($html)
    578 {
    579     // Restore iframes
    580     $html = preg_replace_callback('/###IFRAME(.*?)###/', function($matches) {
    581         return base64_decode($matches[1]);
    582     }, $html);
    583 
    584     // Restore data attributes
    585     $html = preg_replace_callback('/###DATA(.*?)###/', function($matches) {
    586         return ' ' . base64_decode($matches[1]);
    587     }, $html);
    588 
    589     // Restore complex class attributes
    590     $html = preg_replace_callback('/###CLASS(.*?)###/', function($matches) {
    591         return base64_decode($matches[1]);
    592     }, $html);
    593 
    594     // Restore entire Elementor video widget
    595     $html = preg_replace_callback('/###ELEMENTOR_VIDEO_WIDGET(.+?)###/', function($matches) {
    596         return base64_decode($matches[1]);
    597     }, $html);
    598    
    599     // Restore Elementor video widget data-settings
    600     $html = preg_replace_callback('/###ELEMENTOR_VIDEO_SETTINGS(.+?)###/', function($matches) {
    601         return base64_decode($matches[1]);
    602     }, $html);
    603 
    604     return $html;
    605 }
    606 
    607 /**
    608  * Summary of translate_text
    609  * @param mixed $text
    610  * @return mixed
    611  */
    612 private function translate_text($text)
    613 {
    614     $key = hash('md5', $text);
    615     if (isset($this->translated_text[$key])) {
    616         return $this->translated_text[$key];
    617     }
    618    
    619     $cached_translation = $this->get_cached_translation($key);
    620     if ($cached_translation) {
    621         return $cached_translation;
    622     }
    623    
    624     // Return the original text.
    625     return $text;
    626 }
    627 
    628 /**
    629  * Summary of is_shortcode
    630  * @param mixed $text
    631  * @return bool
    632  */
    633 private function is_shortcode($text) {
    634     return preg_match('/^\[[\w\-]+(?:\s+[\w\-]+="[^"]*")*\s*\]$/', trim($text)) === 1;
    635 }
    636     /**
    637      * Helper function to check if node should be skipped
    638      */
    639     private function should_skip_node($node)
    640     {
    641         $parent = $node->getParent();
    642         while ($parent !== null) {
    643             if ($parent instanceof \PHPHtmlParser\Dom\Node\HtmlNode) {
    644                 $tag_name = strtolower($parent->tag->name());
    645                 if (in_array($tag_name, ['style', 'svg'])) {
    646                     return true;
    647                 }
    648             }
    649             $parent = $parent->getParent();
    650         }
    651         return false;
    652     }
    653 
    654     /**
    655      * Helper function to check if text is empty or numeric
    656      */
    657     private function is_empty_or_numeric($text)
    658     {
    659         return preg_match($this->fluentc_htmltags->regex_only_whitespace, $text) ||
    660                preg_match($this->fluentc_htmltags->regex_only_digits_whitespace_punctuation, $text);
    661     }
    662 
    663     /**
    664      * Helper function to process node text
    665      */
    666     private function process_node_text($node, $text, &$texts_to_translate, &$entry_map, &$entry_skip_map)
    667     {
    668         $key = hash('md5', $text);
    669         $dataAttributes = $node->getDataAttributes();
    670 
    671         if (array_key_exists($key, $this->translated_text)) {
    672             $this->applyTranslationToNode($node, $this->translated_text[$key], $dataAttributes);
    673             $entry_skip_map[] = $node;
    674             return;
    675         }
    676 
    677         $cached_translation = $this->get_cached_translation($key);
    678         if ($cached_translation) {
    679             $this->apply_cached_translation($node, $cached_translation, $dataAttributes, $key, $entry_skip_map);
    680             return;
    681         }
    682 
    683         if (!in_array($text, $texts_to_translate)) {
    684             $texts_to_translate[] = $text;
    685         }
    686 
    687         if (!isset($entry_map[$text])) {
    688             $entry_map[$text] = [];
    689         }
    690         $entry_map[$text][] = $node;
    691     }
    692 
    693     /**
    694      * Helper function to get cached translation
    695      */
    696     private function get_cached_translation($key)
    697     {
    698         $cache_key = $this->site_language . $this->language_code . $key;
    699         $cached_translation = $this->fluentc_cache->get($cache_key);
    700         if ($cached_translation) {
    701             $json_cache = json_decode($cached_translation);
    702             if (isset($json_cache->data->translateSite->body->translatedText)) {
    703                 return $json_cache->data->translateSite->body->translatedText;
    704             }
    705         }
    706         return null;
    707     }
    708 
    709     /**
    710      * Helper function to apply cached translation
    711      */
    712     private function apply_cached_translation($node, $translated_text, $dataAttributes, $key, &$entry_skip_map)
    713     {
    714         $node->setText($translated_text);
    715         foreach ($dataAttributes as $dataKey => $dataValue) {
    716             $node->tag->setAttribute($dataKey, $dataValue);
    717         }
    718         $translated_key = hash('md5', $translated_text);
    719         $this->translated_text[$key] = $translated_text;
    720         $this->translated_text[$translated_key] = $translated_text;
    721         $entry_skip_map[] = $node;
    722     }
    723 
    724     /**
    725      * Helper function to update translation cache
    726      */
    727     private function update_translation_cache($original_text, $translated_text, $language_code)
    728     {
    729         $key = hash('md5', $original_text);
    730         $translated_key = hash('md5', $translated_text);
    731         $this->translated_text[$key] = $translated_text;
    732         $this->translated_text[$translated_key] = $translated_text;
    733         $cache_key = $this->site_language . $language_code . $key;
    734         $this->fluentc_cache->set($cache_key, json_encode([
    735             'data' => ['translateSite' => ['body' => [
    736                 'sourceLanguage' => $this->site_language,
    737                 'targetLanguage' => $language_code,
    738                 'translatedText' => $translated_text,
    739                 'originalText' => $original_text
    740             ]]]
    741         ]));
    742     }
    743 
     373        }
     374        return $this->translation_manager;
     375    }
    744376
    745377    /**
  • fluentc-translation/trunk/src/fluentc_pll_api.php

    r3159410 r3170433  
    104104 */
    105105function pll_languages_list( $args = array() ) {
    106     $fluentc_connect = new Connect();
    107     $args = wp_parse_args( $args, array( 'fields' => 'slug' ) );
    108    
    109     $result = $fluentc_connect->get_fluentc_languages_list( $args );
    110     return is_array($result) ? $result : array();
    111 }
    112 
     106    $fluentc_connect = new Connect();
     107   
     108    // Always set 'fields' to 'slug' to ensure we get an array of slugs
     109    $args['fields'] = 'slug';
     110   
     111    $result = $fluentc_connect->get_pll_fluentc_languages_list( $args );
     112   
     113    // Ensure we always return an array of strings (slugs)
     114    if ( !is_array($result) || empty($result) ) {
     115        return array();
     116    }
     117   
     118    // If any elements are still objects, extract the slug
     119    return array_map(function($item) {
     120        return is_object($item) ? $item->slug : $item;
     121    }, $result);
     122}
    113123
    114124/**
  • fluentc-translation/trunk/src/models/class-htmltags.php

    r3159410 r3170433  
    3636        '._fluentc_widget-language-list',
    3737        '._fluentc_widget_float_dropdown_wrapper',
     38        'wpadminbar'
    3839    );
    3940
     
    176177    }
    177178
     179    public function processLink(string $url, string $language_code): string {
     180        $parsed_url = parse_url($url);
     181        $is_relative_path = empty($parsed_url['host']);
     182       
     183        // Check if the URL should be processed
     184        if ($this->shouldSkipProcessing($url, $parsed_url, $is_relative_path)) {
     185            return $url;
     186        }
     187
     188        // Check if the language code is already present
     189        if ($this->hasLanguageCode($parsed_url['path'], $language_code)) {
     190            return $url;
     191        }
     192
     193        return $this->addLanguageCodeToLink($language_code, $url);
     194    }
     195
     196    private function shouldSkipProcessing($url, $parsed_url, $is_relative_path): bool {
     197        return $is_relative_path ||
     198               (isset($parsed_url['host']) && parse_url(home_url(), PHP_URL_HOST) !== $parsed_url['host']) ||
     199               strpos($url, '/wp-admin/') !== false ||
     200               $this->check_for_file($url);
     201    }
     202
     203    private function hasLanguageCode($path, $language_code): bool {
     204        $path_segments = explode('/', trim($path, '/'));
     205        return !empty($path_segments) && $path_segments[0] === $language_code;
     206    }
     207
     208    private function addLanguageCodeToLink($language_code, $url) {
     209        $url_parts = wp_parse_url($url);
     210   
     211        $original_path = isset($url_parts['path']) ? $url_parts['path'] : '';
     212        $path = '/' . $language_code . $original_path;
     213   
     214        $new_permalink = '';
     215   
     216        if (isset($url_parts['scheme']) && isset($url_parts['host'])) {
     217            $new_permalink = $url_parts['scheme'] . '://' . $url_parts['host'];
     218        }
     219   
     220        $new_permalink .= $path;
     221   
     222        if (!empty($url_parts['query'])) {
     223            $new_permalink .= '?' . $url_parts['query'];
     224        }
     225   
     226        if (!empty($url_parts['fragment'])) {
     227            $new_permalink .= '#' . $url_parts['fragment'];
     228        }
     229   
     230        return $new_permalink;
     231    }
    178232    /**
    179233     * Updates Links
     
    215269        return $new_permalink;
    216270    }
     271
     272    /*public function processLink(string $url, string $language_code): string
     273    {
     274        //$current_language_code = $this->getLanguageFromUrl($url, $regex_lang);
     275        $parsed_url = parse_url($url);
     276        $is_relative_path = empty($parsed_url['host']);
     277        $root_url = home_url();
     278       
     279        if (
     280            $is_relative_path ||
     281            (isset($parsed_url['host']) && parse_url($root_url, PHP_URL_HOST) !== $parsed_url['host']) ||
     282            strpos($url, '/wp-admin/') !== false ||
     283            $this->check_for_file($url)) {
     284            return $url;
     285        }
     286
     287        return $this->add_language_code_to_link($language_code, $url);
     288    } */
     289
     290    private function getLanguageFromUrl($url, $regex_lang) {
     291        $pattern = '/\/(' . $regex_lang . ')(\/|$|\?|#)/';
     292        $language_code = null;
     293       
     294        if (preg_match($pattern, wp_unslash($url), $matches)) {
     295            $language_code = $matches[1] ?? null;
     296        }
     297       
     298        return $language_code;
     299    }
    217300}
  • fluentc-translation/trunk/src/services/class-cache.php

    r3152094 r3170433  
    9999    }
    100100
     101
     102    /**
     103     * Get multiple values from cache.
     104     *
     105     * @since 2.0
     106     *
     107     * @param  array $keys Array of cache keys.
     108     * @return array Associative array of cache key => cache value pairs.
     109     */
     110    public function get_multiple(array $keys): array
     111    {
     112        $results = [];
     113        foreach ($keys as $key) {
     114            $results[$key] = $this->get($key);
     115        }
     116        return $results;
     117    }
     118
     119    /**
     120     * Set multiple values in cache.
     121     *
     122     * @since 2.0
     123     *
     124     * @param  array $data Associative array of key => value pairs to cache.
     125     * @param  int   $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
     126     * @return void
     127     */
     128    public function set_multiple(array $data, int $expiration = 0): void
     129    {
     130        foreach ($data as $key => $value) {
     131            if ($expiration > 0) {
     132                $this->set_exp($key, $value, $expiration);
     133            } else {
     134                $this->set($key, $value);
     135            }
     136        }
     137    }
     138
     139
    101140    /**
    102141     * Clean the cache
     
    138177     */
    139178    public function delete_fluentc_transients() {
    140         global $wpdb; // We need this to perform database operations.
    141 
     179        global $wpdb;
     180   
    142181        // Define the SQL query with placeholders for dynamic parts.
    143182        $sql = "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s";
     183       
    144184        // Prepare the SQL statement with actual values to ensure safe queries.
    145         $prepared_sql = $wpdb->prepare( $sql, '_transient_fluentc%', '_transient_timeout_fluentc%' );
    146 
    147         // Attempt to get the result from the cache first.
    148         $cache_key  = 'fluentc_transients';
    149         $transients = wp_cache_get( $cache_key );
    150 
    151         if ( false === $transients ) {
    152             // Execute the query if not found in cache.
    153             $transients = $wpdb->get_col( $prepared_sql );
    154 
    155             // Cache the result to optimize future loads.
    156             wp_cache_set( $cache_key, $transients );
    157         }
    158 
     185        $prepared_sql = $wpdb->prepare($sql, $wpdb->esc_like('_transient_fluentc') . '%', $wpdb->esc_like('_transient_timeout_fluentc') . '%');
     186   
     187        // Execute the query
     188        $transients = $wpdb->get_col($prepared_sql);
     189   
     190        if ($wpdb->last_error) {
     191            error_log('FluentC: Error fetching transients: ' . $wpdb->last_error);
     192            return;
     193        }
     194   
    159195        // Loop through the found transients and delete them.
    160         foreach ( $transients as $transient ) {
     196        foreach ($transients as $transient) {
    161197            // Extract the transient name by removing the prefix.
    162             $transient_name = str_replace( array( '_transient_timeout_', '_transient_' ), '', $transient );
    163 
     198            $transient_name = str_replace(array('_transient_timeout_', '_transient_'), '', $transient);
     199   
    164200            // Use WordPress function to delete the transient.
    165             delete_transient( $transient_name );
    166         }
     201            $deleted = delete_transient($transient_name);
     202   
     203            if (!$deleted) {
     204                error_log('FluentC: Failed to delete transient: ' . $transient_name);
     205            }
     206        }
     207   
     208        // Clear the object cache for good measure
     209        wp_cache_flush();
    167210    }
    168211
  • fluentc-translation/trunk/src/services/class-connect.php

    r3161147 r3170433  
    1515
    1616use FluentC\Models\Body;
    17 use stdClass;
     17
    1818
    1919if ( ! defined( 'ABSPATH' ) ) {
     
    2424 * FluentC Connection Class
    2525 */
    26 class Connect {
    27 
    28 
    29     /**
    30      * Hold the class instance.
    31      *
    32      * @var object
    33      */
    34     private static $instance = null;
    35     /**
    36      * FluentC Cache class
    37      *
    38      * @var object
    39      */
    40     protected $fluentc_cache;
    41    
    42     /**
    43      * FluentC remote url
    44      *
    45      * @var string
    46      */
    47     protected $fluentc_remote_url;
    48 
    49     /**
    50      * Constructor.
    51      *
    52      * @since 1.2
    53      */
    54     public function __construct() {
    55         $this->fluentc_cache      = new Cache();
    56         $this->fluentc_remote_url = 'https://dashboard.fluentc.ai/graphql';
    57     }
    58 
    59 
    60     /**
    61      * Retrieve Language List as ISO Codes
    62      *
    63      * @return array
    64      * @since  1.2
    65      * @param  string $widget_id Site widget id.
    66      */
    67     public function get_language_list( $widget_id ) {
    68 
    69         $languages = array();
    70         if ( $this->fluentc_cache->get( 'fluentc_language_list' ) ) {
    71             $languages = $this->fluentc_cache->get( 'fluentc_language_list' );
    72         } else {
    73 
    74             $widgetapikey = get_option( 'fluentc_api_key' );
    75             if ( $widgetapikey ) {
    76 
    77                 $widget = $this->fetch_widget_options( $widget_id );
    78 
    79                 $get_all_languages = $this->get_available_languages( $widget_id );
    80 
    81                 foreach ( $get_all_languages->data->getAvailableLanguages->body as $key => $value ) {
    82                     $languages[] = $value->code;
    83                 }
    84                 // fetch available languages Environment ID.
    85                 $this->fluentc_cache->set_exp( 'fluentc_language_list', $languages, 43200 );
    86                 do_action( 'fluentc_activation_setup' );
    87             }
    88         }
    89         return $languages;
    90     }
    91 
    92 
    93     /**
    94      * Retrieve Language is with ISO Code and Name
    95      *
    96      * @return array
    97      * @since  1.2
    98      * @param  string $widget_id Site ID.
    99      */
    100     public function get_display_language_list( $widget_id ) {
    101 
    102         $languages = array();
    103         if ( $this->fluentc_cache->get( 'fluentc_display_language_list' ) ) {
    104             $languages = $this->fluentc_cache->get( 'fluentc_display_language_list' );
    105         } else {
    106 
    107             $get_all_languages = $this->get_available_languages( $widget_id );
    108 
    109             foreach ( $get_all_languages->data->getAvailableLanguages->body as $key => $value ) {
    110                 $languages[] = array( $value->name, $value->code );
    111             }
    112 
    113             $this->fluentc_cache->set_exp( 'fluentc_display_language_list', $languages , 43200 );
    114             do_action( 'fluentc_activation_setup' );
    115         }
    116         return $languages;
    117     }
     26class Connect
     27{
     28    protected $fluentc_cache;
     29    protected $fluentc_remote_url;
     30    protected $api_key = 'da2-wtkl5bpofjbu5ex5iugu4o2mbm';
     31
     32    public function __construct()
     33    {
     34        $this->fluentc_cache = new Cache();
     35        $this->fluentc_remote_url = 'https://dashboard.fluentc.ai/graphql';
     36    }
     37
     38    public function get_language_list($widget_id)
     39    {
     40        return $this->getCachedOrFetch('fluentc_language_list', function () use ($widget_id) {
     41            $languages = [];
     42            $get_all_languages = $this->get_available_languages($widget_id);
     43            foreach ($get_all_languages->data->getAvailableLanguages->body as $value) {
     44                $languages[] = $value->code;
     45            }
     46            do_action('fluentc_activation_setup');
     47            return $languages;
     48        }, 43200);
     49    }
     50
     51    public function get_display_language_list($widget_id)
     52    {
     53        return $this->getCachedOrFetch('fluentc_display_language_list', function () use ($widget_id) {
     54            $languages = [];
     55            $get_all_languages = $this->get_available_languages($widget_id);
     56            foreach ($get_all_languages->data->getAvailableLanguages->body as $value) {
     57                $languages[] = [$value->name, $value->code];
     58            }
     59            do_action('fluentc_activation_setup');
     60            return $languages;
     61        }, 43200);
     62    }
     63
     64  /**
     65 * Retrieves FluentC Languages as an array.
     66 *
     67 * This function fetches the available languages from FluentC, caches them,
     68 * and applies any filtering based on the provided arguments.
     69 *
     70 * @param array $args Optional. Arguments to filter the languages.
     71 *                    Supported args:
     72 *                    - 'hide_empty' (bool) Whether to hide languages with no posts.
     73 *                    - 'hide_default' (bool) Whether to hide the default language.
     74 *                    - 'fields' (string|array) Specific fields to return.
     75 * @return array|null An array of language objects, or null if no API key is set.
     76 */
     77public function get_fluentc_languages_list($args = [])
     78{
     79    $widgetapikey = get_option('fluentc_api_key');
     80    if (!$widgetapikey) {
     81        return null;
     82    }
     83
     84    return $this->getCachedOrFetch('fluentc_array_language_list', function () use ($widgetapikey, $args) {
     85        $get_all_languages = $this->get_available_languages($widgetapikey);
     86        $languages = array_map(function ($language) {
     87            return (object) [
     88                'name' => $language->name,
     89                'slug' => $language->code,
     90                'is_default' => false,
     91                'count' => 0,
     92            ];
     93        }, $get_all_languages->data->getAvailableLanguages->body);
     94
     95        return $this->filterLanguages($languages, $args);
     96    }, 43200);
     97}
     98
    11899/**
    119      * Retrieve FluentC Languages as an array
    120      *
    121      * @return array
    122      * @since  1.2
    123      * @param  string $widget_id Widget ID.
    124      */
    125     public function get_fluentc_languages_list( $args = array() ) {
    126         // Check if the widget API key is set
    127         $widgetapikey = get_option( 'fluentc_api_key' );
    128         if ( !$widgetapikey ) {
    129             return; // Return nothing if the API key is not set
    130         }
    131    
    132         // Attempt to retrieve languages from cache.
    133         $languages = $this->fluentc_cache->get( 'fluentc_array_language_list' );
    134    
    135         // If not cached, fetch and cache the languages.
    136         if ( ! is_array( $languages ) ) {
    137             // Fetch available languages from FluentC server.
    138             $get_all_languages = $this->get_available_languages( $widgetapikey );
    139    
    140             $languages = array();
    141             foreach ( $get_all_languages->data->getAvailableLanguages->body as $language ) {
    142                 $languages[] = (object) array(
    143                     'name' => $language->name,
    144                     'slug' => $language->code,
    145                     'is_default' => false, // Adjust this if you have default language logic.
    146                     'count' => 0, // Placeholder for count if needed.
    147                 );
    148             }
    149    
    150             // Cache the fetched languages.
    151             $this->fluentc_cache->set_exp( 'fluentc_array_language_list', $languages , 43200 );
    152         }
    153    
    154         // Filter the languages based on provided arguments.
    155         $languages = array_filter(
    156             $languages,
    157             function ( $language ) use ( $args ) {
    158                 $keep_empty   = empty( $args['hide_empty'] ) || $language->count;
    159                 $keep_default = empty( $args['hide_default'] ) || ! $language->is_default;
    160                 return $keep_empty && $keep_default;
    161             }
    162         );
    163    
    164         // Re-index the array.
    165         $languages = array_values( $languages );
    166        
    167         // Return the requested fields or the full language objects.
    168         return empty( $args['fields'] ) ? $languages : wp_list_pluck( $languages, $args['fields'] );
    169     }
    170    
    171     /**
    172      * Retrieve Site Options and connect to FluentC
    173      *
    174      * @return object
    175      * @since  1.2
    176      * @param  string $widget_id Widget ID.
    177      */
    178     public function fetch_widget_options( $widget_id ) {
    179 
    180         $cache = $this->fluentc_cache->get( $widget_id );
    181         if ( $cache ) {
    182             return json_decode( $cache );
    183         } else {
    184             // Setup the endpoint URL.
    185             $url = $this->fluentc_remote_url;
    186 
    187             // Prepare the GraphQL query with the dynamic widgetID.
    188             $data      = array(
    189                 'query' => "{\n  fetchSiteOptions(\n   siteId: \"$widget_id\"\n   ) {\n    disabled\n    name\n    appearance\n   sourceLanguage\n  languages\n      }\n}",
    190             );
    191             $json_data = wp_json_encode( $data );
    192 
    193             // Set up the arguments for the request.
    194             $args = array(
    195                 'body'        => $json_data,
    196                 'headers'     => array(
    197                     'Content-Type' => 'application/json',
    198                     'x-api-key'    => 'da2-wtkl5bpofjbu5ex5iugu4o2mbm',
    199                 ),
    200                 'referer'     => get_site_url(),
    201                 'method'      => 'POST',
    202                 'data_format' => 'body',
    203                 'timeout'     => 20,
    204             );
    205 
    206             // Use WordPress function to execute the POST request.
    207             $response = wp_remote_post( $url, $args );
    208 
    209             // Check for errors.
    210             if ( is_wp_error( $response ) ) {
    211                 do_action( 'qm/error', 'Error in request: ' . $response->get_error_message() );
    212 
    213                 return null;
    214             }
    215 
    216             // Retrieve the body of the response.
    217             $body   = wp_remote_retrieve_body( $response );
    218             $status = wp_remote_retrieve_response_code( $response );
    219 
    220             $response_data = json_decode( $body, true ); // decode to associative array for easier handling.
    221             if ( ! $response_data ) {
    222                 do_action( 'qm/error', 'Invalid JSON response' );
    223                 return null;
    224             }
    225             if ( 200 === $status ) {
    226                 // Assuming a successful response structure. Adjust according to your API.
    227                 if ( isset( $response_data['data'] ) && ! empty( $response_data['data'] ) ) {
    228                     // Process your data here.
    229                     $this->fluentc_cache->set_exp( $widget_id, $body, 43200 );
    230                 } else {
    231                     do_action( 'qm/error', 'Data not found in response' );
    232                     return null;
    233                 }
    234             } else {
    235                 // Handle errors based on the response body.
    236                 if ( isset( $response_data['errors'] ) && is_array( $response_data['errors'] ) ) {
    237                     foreach ( $response_data['errors'] as $error ) {
    238                         if ( isset( $error['extensions']['code'] ) ) {
    239                             switch ( $error['extensions']['code'] ) {
    240                                 case 'SITE_NOT_FOUND':
    241                                     do_action( 'qm/error', 'Error: Site not found.' );
    242                                     // Handle site not found error.
    243                                     break;
    244                                 case 'LANGUAGE_NOT_ENABLED':
    245                                     do_action( 'qm/error', 'Error: Language not enabled for site.' );
    246                                     // Handle language not enabled error.
    247                                     break;
    248                                 default:
    249                                     do_action( 'qm/error', 'An unknown error occurred.' );
    250                                     // Handle other errors.
    251                                     break;
    252                             }
    253                         }
    254                     }
    255                 } else {
    256                     do_action( 'qm/error', 'An unknown error occurred, but no error details were found' );
    257                 }
    258                 return null;
    259             }
    260             // Return the response.
    261             return json_decode( $body );
    262         }
    263     }
    264     /**
    265      * Retrieve Available Language by Site Key
    266      *
    267      * @return object
    268      * @since  1.2
    269      * @param  string $environment_id FluentC Environment ID.
    270      */
    271     public function get_available_languages( $environment_id ) {
    272         // Setup the endpoint URL.
    273         $url = $this->fluentc_remote_url;
    274 
    275         // Prepare the GraphQL query with the dynamic widgetID.
    276         $data      = array(
    277             'query' => "{\n  getAvailableLanguages(\n    siteId: \"$environment_id\"\n  targetLanguage:\"en\"\n ) {\n  body\n {\n name\n code\n }}\n}",
    278         );
    279         $json_data = wp_json_encode( $data );
    280 
    281         // Set up the arguments for the request.
    282         $args = array(
    283             'body'        => $json_data,
    284             'headers'     => array(
    285                 'Content-Type' => 'application/json',
    286                 'x-api-key'    => 'da2-wtkl5bpofjbu5ex5iugu4o2mbm',
    287             ),
    288             'referer'     => get_site_url(),
    289             'method'      => 'POST',
    290             'data_format' => 'body',
    291         );
    292 
    293         // Use WordPress function to execute the POST request.
    294         $response = wp_remote_post( $url, $args );
    295 
    296         // Check for errors.
    297         if ( is_wp_error( $response ) ) {
    298             do_action( 'qm/error', 'Error in request: ' . $response->get_error_message() );
    299             return null;
    300         }
    301 
    302         // Retrieve the body of the response.
    303         $body   = wp_remote_retrieve_body( $response );
    304         $status = wp_remote_retrieve_response_code( $response );
    305 
    306         $response_data = json_decode( $body, true ); // decode to associative array for easier handling.
    307         if ( ! $response_data ) {
    308             do_action( 'qm/error', 'Invalid JSON response' );
    309             return null;
    310         }
    311         if ( 200 === $status ) {
    312             // Assuming a successful response structure. Adjust according to your API.
    313             if ( isset( $response_data['data'] ) && ! empty( $response_data['data'] ) ) {
    314                 // Process your data here.
    315                 $this->fluentc_cache->set_exp( 'fluentc_' . $environment_id, $body, 43200 );
    316             } else {
    317                 do_action( 'qm/error', 'Data not found in response' );
    318                 return null;
    319             }
    320         } else {
    321             // Handle errors based on the response body.
    322             if ( isset( $response_data['errors'] ) && is_array( $response_data['errors'] ) ) {
    323                 foreach ( $response_data['errors'] as $error ) {
    324                     if ( isset( $error['extensions']['code'] ) ) {
    325                         switch ( $error['extensions']['code'] ) {
    326                             case 'SITE_NOT_FOUND':
    327                                 do_action( 'qm/error', 'Error: Site not found.' );
    328                                 // Handle site not found error.
    329                                 break;
    330                             case 'LANGUAGE_NOT_ENABLED':
    331                                 do_action( 'qm/error', 'Error: Language not enabled for site.' );
    332                                 // Handle language not enabled error.
    333                                 break;
    334                             default:
    335                                 do_action( 'qm/error', 'An unknown error occurred.' );
    336                                 // Handle other errors.
    337                                 break;
    338                         }
    339                     }
    340                 }
    341             } else {
    342                 do_action( 'qm/error', 'An unknown error occurred, but no error details were found' );
    343             }
    344             return null;
    345         }
    346         // Return the response.
    347         return json_decode( $body );
    348     }
    349     /**
    350      * Translates Text that is not expected to have HTML
    351      *
    352      * @return object
    353      * @since  1.2
    354      * @param  string $widget_id FluentC Site ID.
    355      * @param  string $source_language Source Language.
    356      * @param  string $target_language Target Language.
    357      * @param  string $text Text to be translated.
    358      * @param  int    $id id of post.
    359      */
    360     public function get_translation_text( $widget_id, $source_language, $target_language, $text, $id = null ) {
    361         if ( $id ) {
    362             $key = $id;
    363         } else {
    364             $key = hash( 'crc32', $text );
    365         }
    366         $cache = $this->fluentc_cache->get( $source_language . $target_language . $key );
    367         if ( $cache ) {
    368             return json_decode( $cache );
    369         } else {
    370 
    371             // Setup the endpoint URL.
    372             $url  = $this->fluentc_remote_url;
    373             $text = addcslashes( $text, '"' );
    374             // Prepare the GraphQL query with the dynamic widgetID.
    375             $data      = array(
    376                 'query' => "{\n translateSite(\n  siteId: \"$widget_id\" \n sourceLanguage: \"$source_language\" \n targetLanguage: \"$target_language\" \n  labels: [\"$text\"] \n ) {\n body { \n sourceLanguage \n targetLanguage \n  originalText \n translatedText \n }\n }\n }",
    377             );
    378             $json_data = wp_json_encode( $data );
    379 
    380             // Set up the arguments for the request.
    381             $args = array(
    382                 'body'        => $json_data,
    383                 'headers'     => array(
    384                     'Content-Type' => 'application/json',
    385                     'x-api-key'    => 'da2-wtkl5bpofjbu5ex5iugu4o2mbm',
    386                 ),
    387                 'referer'     => get_site_url(),
    388                 'method'      => 'POST',
    389                 'data_format' => 'body',
    390             );
    391 
    392             // Use WordPress function to execute the POST request.
    393             $response = wp_remote_post( $url, $args );
    394             $apicalls = $this->fluentc_cache->get( 'apicalls' );
    395             $this->fluentc_cache->set( 'apicalls', $apicalls + 1 );
    396             // Check for errors.
    397             if ( is_wp_error( $response ) ) {
    398                 do_action( 'qm/error', 'Error in request: ' . $response->get_error_message() );
    399                 return null;
    400             }
    401 
    402             // Retrieve the body of the response.
    403             $body   = wp_remote_retrieve_body( $response );
    404             $status = wp_remote_retrieve_response_code( $response );
    405 
    406             $response_data = json_decode( $body, true ); // decode to associative array for easier handling.
    407             if ( ! $response_data ) {
    408                 do_action( 'qm/error', 'Invalid JSON response' );
    409                 return null;
    410             }
    411 
    412             if ( 200 === $status ) {
    413                 // Assuming a successful response structure. Adjust according to your API.
    414                 if ( isset( $response_data['data'] ) && ! empty( $response_data['data'] ) ) {
    415                     // Process your data here.
    416                     $this->fluentc_cache->set( $source_language . $target_language . $key, $body );
    417                 } else {
    418                     do_action( 'qm/error', 'get_translation_text: Data not found in response' );
    419                     return null;
    420                 }
    421             } else {
    422                 // Handle errors based on the response body.
    423                 if ( isset( $response_data['errors'] ) && is_array( $response_data['errors'] ) ) {
    424                     foreach ( $response_data['errors'] as $error ) {
    425                         if ( isset( $error['extensions']['code'] ) ) {
    426                             switch ( $error['extensions']['code'] ) {
    427                                 case 'SITE_NOT_FOUND':
    428                                     do_action( 'qm/error', 'Error: Site not found.' );
    429                                     // Handle site not found error.
    430                                     break;
    431                                 case 'LANGUAGE_NOT_ENABLED':
    432                                     do_action( 'qm/error', 'Error: Language not enabled for site.' );
    433                                     // Handle language not enabled error.
    434                                     break;
    435                                 default:
    436                                     do_action( 'qm/error', 'An unknown error occurred.' );
    437                                     // Handle other errors.
    438                                     break;
    439                             }
    440                         }
    441                     }
    442                 } else {
    443                     do_action( 'qm/error', 'An unknown error occurred, but no error details were found' );
    444                 }
    445                 return null;
    446             }
    447 
    448             // Return the response.
    449             return json_decode( $body );
    450         }
    451     }
    452     /**
    453      * Translates Text that is expected to have HTML
    454      *
    455      * Return json_decode()
    456      *
    457      * @var   mixed
    458      * @since 1.2
    459      * @param  string $widget_id FluentC Site ID.
    460      * @param  string $source_language Source Language.
    461      * @param  string $target_language Target Language.
    462      * @param  string $text Text to be translated.
    463      * @param  array $text_to_translate Text to be translated.
    464      * @param  int    $id id of post.
    465      */
    466     public function get_translation_content($widget_id, $source_language, $target_language, $text_labels) {
    467     // Ensure text_labels is properly formatted for GraphQL
    468     if (is_string($text_labels)) {
    469         // If it's a string, assume it's already formatted for GraphQL
    470         $labels_string = $text_labels;
    471     } elseif (is_array($text_labels)) {
    472         // If it's an array, format it for GraphQL
    473         $formatted_labels = array_map(function($label) {
    474             return '"' . addslashes($label) . '"';
    475         }, $text_labels);
    476         $labels_string = '[' . implode(', ', $formatted_labels) . ']';
    477     } else {
     100 * Filters the languages based on provided arguments.
     101 *
     102 * @param array $languages Array of language objects to filter.
     103 * @param array $args Arguments for filtering. See get_fluentc_languages_list() for details.
     104 * @return array Filtered array of language objects.
     105 */
     106    private function filterLanguages($languages, $args)
     107    {
     108        $languages = array_filter($languages, function ($language) use ($args) {
     109            $keep_empty = empty($args['hide_empty']) || $language->count;
     110            $keep_default = empty($args['hide_default']) || !$language->is_default;
     111            return $keep_empty && $keep_default;
     112        });
     113
     114        $languages = array_values($languages);
     115
     116        return empty($args['fields']) ? $languages : wp_list_pluck($languages, $args['fields']);
     117    }
     118
     119    public function fetch_widget_options($widget_id)
     120    {
     121        return $this->getCachedOrFetch($widget_id, function () use ($widget_id) {
     122            return $this->makeGraphQLRequest(
     123                "{\n  fetchSiteOptions(\n   siteId: \"$widget_id\"\n   ) {\n    disabled\n    name\n    appearance\n   sourceLanguage\n  languages\n      }\n}"
     124            );
     125        }, 43200);
     126    }
     127
     128    public function get_available_languages($environment_id)
     129    {
     130        return $this->makeGraphQLRequest(
     131            "{\n  getAvailableLanguages(\n    siteId: \"$environment_id\"\n  targetLanguage:\"en\"\n ) {\n  body\n {\n name\n code\n }}\n}"
     132        );
     133    }
     134
     135    public function get_translation_text($widget_id, $source_language, $target_language, $text, $id = null)
     136    {
     137        $key = $id ?: hash('crc32', $text);
     138        return $this->getCachedOrFetch($source_language . $target_language . $key, function () use ($widget_id, $source_language, $target_language, $text) {
     139            $escapedText = addcslashes($text, '"');
     140            return $this->makeGraphQLRequest(
     141                "{\n translateSite(\n  siteId: \"$widget_id\" \n sourceLanguage: \"$source_language\" \n targetLanguage: \"$target_language\" \n  labels: [\"$escapedText\"] \n ) {\n body { \n sourceLanguage \n targetLanguage \n  originalText \n translatedText \n }\n }\n }"
     142            );
     143        });
     144    }
     145
     146 /**
     147     * Get translation content from the API.
     148     *
     149     * @param string $widget_id The widget ID.
     150     * @param string $source_language The source language code.
     151     * @param string $target_language The target language code.
     152     * @param array $text_labels The text labels to translate.
     153     * @return array The translated content.
     154     */
     155    public function get_translation_content($widget_id, $source_language, $target_language, $text_labels)
     156    {
     157        $labels_string = $this->formatLabels($text_labels);
     158        $query = $this->buildTranslationQuery($widget_id, $source_language, $target_language, $labels_string);
     159       
     160        do_action('qm/debug', 'GraphQL Query: ' . $query);
     161
     162        $response = $this->makeGraphQLRequest($query);
     163        $this->fluentc_cache->set('apicalls', $this->fluentc_cache->get('apicalls') + 1);
     164
     165        if (!$response) {
     166            do_action('qm/error', 'Translation error: No response from API');
     167            return [];
     168        }
     169
     170        if (!isset($response->data->translateSite->body)) {
     171            do_action('qm/error', 'Translation error: Unexpected response structure');
     172            do_action('qm/debug', 'API Response: ' . json_encode($response));
     173            return [];
     174        }
     175
     176        return $this->processTranslationResponse($response);
     177    }
     178
     179    /**
     180     * Get translation content from the API.
     181     *
     182     * @param string $widget_id The widget ID.
     183     * @param string $source_language The source language code.
     184     * @param string $target_language The target language code.
     185     * @param array $text_labels The text labels to translate.
     186     * @return array The translated content.
     187     */
     188    public function get_translation_content_with_placeholders($widget_id, $source_language, $target_language, $text_array)
     189    {
     190        $labels_array = $this->convertTranslationArrayFormat($text_array);
     191        $query = $this->buildTranslationQueryWithPlaceholders($widget_id, $source_language, $target_language, $labels_array);
     192       
     193        do_action('qm/debug', 'GraphQL Query: ' . $query);
     194
     195        $response = $this->makeGraphQLRequest($query);
     196        $this->fluentc_cache->set('apicalls', $this->fluentc_cache->get('apicalls') + 1);
     197
     198        if (!$response) {
     199            do_action('qm/error', 'Translation error: No response from API');
     200            return [];
     201        }
     202
     203        if (!isset($response->data->translateWithPlaceholder->body)) {
     204            do_action('qm/error', 'Translation error: Unexpected response structure');
     205            do_action('qm/debug', 'API Response: ' . json_encode($response));
     206            return [];
     207        }
     208
     209        return $this->processTranslationWithPlaceholderResponse($response);
     210    }
     211
     212    private function formatLabels($text_labels)
     213    {
     214        if (is_string($text_labels)) {
     215            return str_replace('"', '&quot;', trim($text_labels));
     216        }
     217        if (is_array($text_labels)) {
     218            return '[' . implode(', ', array_map(function($label) {
     219                return '"' . str_replace('"', '&quot;', trim($label)) . '"';
     220            }, $text_labels)) . ']';
     221        }
    478222        do_action('qm/error', 'Invalid text_labels format');
    479223        return null;
    480224    }
    481225
    482     // Prepare GraphQL query
    483     $query = <<<GRAPHQL
    484     {
    485       translateSite(
    486         siteId: "$widget_id"
    487         sourceLanguage: "$source_language"
    488         targetLanguage: "$target_language"
    489         labels: [$labels_string]
    490       ) {
    491         body {
    492           sourceLanguage
    493           targetLanguage
    494           originalText
    495           translatedText
    496         }
    497       }
    498     }
    499     GRAPHQL;
    500 
    501     // Log the query for debugging
    502     do_action('qm/debug', 'GraphQL Query: ' . $query);
    503 
    504     // Set up the request
    505     $args = [
    506         'body' => wp_json_encode(['query' => $query]),
    507         'headers' => [
    508             'Content-Type' => 'application/json',
    509             'x-api-key' => 'da2-wtkl5bpofjbu5ex5iugu4o2mbm',
    510         ],
    511         'referer' => get_site_url(),
    512         'method' => 'POST',
    513         'timeout' => 20,
    514     ];
    515 
    516     // Execute the request
    517     $response = wp_remote_post($this->fluentc_remote_url, $args);
    518     $this->fluentc_cache->set('apicalls', $this->fluentc_cache->get('apicalls') + 1);
    519 
    520     if (is_wp_error($response)) {
    521         do_action('qm/error', 'Error in request: ' . $response->get_error_message());
     226 
     227
     228    private function buildTranslationQuery($widget_id, $source_language, $target_language, $labels_string)
     229    {
     230        return <<<GRAPHQL
     231        {
     232          translateSite(
     233            siteId: "$widget_id"
     234            sourceLanguage: "$source_language"
     235            targetLanguage: "$target_language"
     236            labels: $labels_string
     237          ) {
     238            body {
     239              sourceLanguage
     240              targetLanguage
     241              originalText
     242              translatedText
     243            }
     244          }
     245        }
     246        GRAPHQL;
     247    }
     248
     249    private function convertTranslationArrayFormat($translationArray)
     250    {
     251        $convertedArray = [];
     252       
     253        foreach ($translationArray as $placeholder => $text) {
     254            $convertedArray[] = [
     255                "label" => str_replace('"', '&quot;', trim($text)),
     256                "placeholder" => "$placeholder"
     257            ];
     258        }
     259       
     260        return $convertedArray;
     261    }
     262    private function buildTranslationQueryWithPlaceholders($widget_id, $source_language, $target_language, $labels_array)
     263    {
     264        // Convert the labels array to a JSON string, ensuring correct key names.
     265    $labels_json = json_encode($labels_array, JSON_UNESCAPED_UNICODE);
     266   
     267    // Remove the quotes around the key names.
     268    $labels_json = preg_replace('/"([^"]+)"\s*:/', '$1:', $labels_json);
     269   
     270        return <<<GRAPHQL
     271        {
     272          translateWithPlaceholder(
     273            siteId: "$widget_id"
     274            sourceLanguage: "$source_language"
     275            targetLanguage: "$target_language"
     276            labels: $labels_json
     277          ) {
     278            body {
     279              targetLanguage
     280              sourceLanguage
     281              labels {
     282                originalLabel
     283                translatedLabel
     284                originalPlaceholder
     285              }
     286            }
     287          }
     288        }
     289        GRAPHQL;
     290    }
     291
     292    private function processTranslationResponse($response)
     293    {
     294        $translations = [];
     295        foreach ($response->data->translateSite->body as $translation) {
     296            $originalText = html_entity_decode($translation->originalText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
     297            $translatedText = html_entity_decode($translation->translatedText, ENT_QUOTES | ENT_HTML5, 'UTF-8');
     298            $translations[$originalText] = $translatedText;
     299        }
     300        return $translations;
     301    }
     302
     303    private function processTranslationWithPlaceholderResponse($response)
     304{
     305    $translations = [];
     306    foreach ($response->data->translateWithPlaceholder->body->labels as $translation) {
     307        $placeholder = $translation->originalPlaceholder;
     308        $originalLabel = $translation->originalLabel;
     309        $translatedLabel = html_entity_decode($translation->translatedLabel, ENT_QUOTES | ENT_HTML5, 'UTF-8');
     310       
     311        $translations[$placeholder] = [
     312            'originalLabel' => $originalLabel,
     313            'translatedLabel' => $translatedLabel,
     314            'originalPlaceholder' => $placeholder
     315        ];
     316    }
     317    return $translations;
     318}
     319
     320    public function get_language_list_string($widget_id)
     321    {
     322        $languages = $this->get_language_list($widget_id);
     323        return empty($languages) ? '' : implode('|', $languages);
     324    }
     325
     326    public function heartbeat($widget_id = null)
     327    {
     328        $site_url = $this->getSiteUrl();
     329        $site_detail = $widget_id ? 'siteId: "' . $widget_id . '" ' : '';
     330       
     331        $query = <<<GRAPHQL
     332        {
     333          heartBeat(
     334            host: "$site_url",
     335            $site_detail
     336          ) {
     337            body {
     338              status
     339            }
     340          }
     341        }
     342        GRAPHQL;
     343
     344        $response = $this->makeGraphQLRequest($query);
     345
     346        if ($response && isset($response->data)) {
     347            do_action('qm/info', 'FluentC Site setup post completed');
     348            return $response;
     349        }
     350
    522351        return null;
    523352    }
    524353
    525     $body = wp_remote_retrieve_body($response);
    526     $status = wp_remote_retrieve_response_code($response);
    527 
    528     // Log the raw response for debugging
    529     do_action('qm/debug', 'Raw API Response: ' . $body);
    530 
    531     $response_data = json_decode($body, true);
    532 
    533     if (!$response_data) {
    534         do_action('qm/error', 'Invalid JSON response');
    535         return null;
    536     }
    537 
    538     if ($status === 200) {
    539         if (isset($response_data['data']) && !empty($response_data['data']['translateSite']['body'])) {
    540             $body_array = [];
    541             foreach ($response_data['data']['translateSite']['body'] as $translatedText) {
    542                 $original_text = $translatedText['originalText'];
    543                 $translated_text = $translatedText['translatedText'];
    544                 $sourceLanguage = $translatedText['sourceLanguage'];
    545                 $targetLanguage = $translatedText['targetLanguage'];
    546 
    547                 $text_to_cache = new Body($original_text, $translated_text, $sourceLanguage, $targetLanguage);
    548                 /*$key = hash('md5', $original_text);
    549                 $cache_key = $source_language . $target_language . $key;
    550                 $this->fluentc_cache->set($cache_key, json_encode([
    551                     'data' => ['translateSite' => ['body' => $text_to_cache]]
    552                 ]));*/
    553 
    554                 $body_array[] = $text_to_cache;
    555             }
    556            
    557             $response_object = new \stdClass();
    558             $response_object->data = new \stdClass();
    559             $response_object->data->translateSite = new \stdClass();
    560             $response_object->data->translateSite->body = $body_array;
    561 
    562             return $response_object;
    563         } else {
    564             do_action('qm/error', 'Translation error: Data not found in response. Response structure: ' . print_r($response_data, true));
    565         }
    566     } else {
    567         $this->handleTranslationError($response_data);
    568     }
    569 
    570     return null;
    571 }
    572 private function handleTranslationError($response_data) {
    573     if (isset($response_data['errors']) && is_array($response_data['errors'])) {
    574         foreach ($response_data['errors'] as $error) {
    575             do_action('qm/error', 'GraphQL Error: ' . json_encode($error));
    576             if (isset($error['message'])) {
    577                 do_action('qm/error', 'Error message: ' . $error['message']);
    578             }
    579             if (isset($error['extensions']['code'])) {
    580                 switch ($error['extensions']['code']) {
    581                     case 'SITE_NOT_FOUND':
    582                         do_action('qm/error', 'Error: Site not found.');
    583                         break;
    584                     case 'LANGUAGE_NOT_ENABLED':
    585                         do_action('qm/error', 'Error: Language not enabled for site.');
    586                         break;
    587                     default:
    588                         do_action('qm/error', 'An unknown error occurred. Code: ' . $error['extensions']['code']);
    589                         break;
    590                 }
    591             }
    592         }
    593     } else {
    594         do_action('qm/error', 'An unknown error occurred, but no error details were found. Response: ' . print_r($response_data, true));
    595     }
    596 }
     354    private function getSiteUrl()
     355    {
     356        $site_language = get_bloginfo('url');
     357        $url_parts = wp_parse_url($site_language);
     358        return $url_parts['scheme'] . '://' . $url_parts['host'];
     359    }
     360
    597361    /**
    598      * Retrieves Language list in regex format
    599      *
    600      * @return string
    601      * @since  1.2
    602      * @param  string $widget_id Widget ID.
     362     * Summary of getCachedOrFetch
     363     * @param mixed $key
     364     * @param mixed $fetchCallback
     365     * @param mixed $expiration
     366     * @return mixed
    603367     */
    604     public function get_language_list_string( $widget_id ) {
    605         $languages = $this->get_language_list( $widget_id ); // Assuming you have access to this method.
    606 
    607         // If the language list is empty, return an empty string.
    608         if ( empty( $languages ) ) {
    609             return '';
    610         }
    611 
    612         // If the language list is not empty, format it as desired.
    613         return implode( '|', $languages );
    614     }
    615 
    616     /**
    617      * Sets up connection to FluentC
    618      *
    619      * @return object
    620      * @since  1.3
    621      * @param  string $widget_id Widget ID.
    622      */
    623     public function heartbeat( $widget_id = null ) {
    624 
    625             // Setup the endpoint URL.
    626             $url      = $this->fluentc_remote_url;
    627             $site_language = get_bloginfo( 'url' );
    628         $url_parts = wp_parse_url( $site_language );
    629 
    630         // Rebuild the URL with the language code.
    631         $site_url = $url_parts['scheme'] . '://' . $url_parts['host'];
    632         do_action( 'qm/debug', '$widget_id: ' . $widget_id  );
    633         if ( $widget_id ) {
    634             $site_detail = 'siteId: "'.$widget_id.'" ';
    635         } else {
    636             $site_detail = '';
    637         }
    638         //'query' => "{\n heartBeat(  \n host: \"$site_url\" ) {\n body { \n status\n } \n}",
    639             // Prepare the GraphQL query with the dynamic widgetID.
    640             $data      = array(
    641                 "query" => "{\n  heartBeat(\n    host: \"$site_url\",\n   $site_detail  ) {\n    body {\n      status\n    }\n  }\n}"
    642             );
    643             $json_data = wp_json_encode( $data );
    644 
    645             // Set up the arguments for the request.
    646             $args = array(
    647                 'body'        => $json_data,
    648                 'headers'     => array(
    649                     'Content-Type' => 'application/json',
    650                     'x-api-key'    => 'da2-wtkl5bpofjbu5ex5iugu4o2mbm',
    651                 ),
    652                 'referer'     => get_site_url(),
    653                 'method'      => 'POST',
    654                 'data_format' => 'body',
    655                 'timeout'     => 20,
    656             );
    657 
    658             // Use WordPress function to execute the POST request.
    659             $response = wp_remote_post( $url, $args );
    660 
    661             // Check for errors.
    662             if ( is_wp_error( $response ) ) {
    663                 do_action( 'qm/error', 'Error in request: ' . $response->get_error_message() );
    664 
    665                 return null;
    666             }
    667 
    668             // Retrieve the body of the response.
    669             $body   = wp_remote_retrieve_body( $response );
    670             $status = wp_remote_retrieve_response_code( $response );
    671 
    672             $response_data = json_decode( $body, true ); // decode to associative array for easier handling.
    673             if ( ! $response_data ) {
    674                 do_action( 'qm/error', 'Invalid JSON response' );
    675                 return null;
    676             }
    677             if ( 200 === $status ) {
    678                 // Assuming a successful response structure. Adjust according to your API.
    679                 if ( isset( $response_data['data'] ) && ! empty( $response_data['data'] ) ) {
    680                     // Process your data here.
    681                     do_action( 'qm/info', 'Data found in response' );
    682                 } else {
    683                     do_action( 'qm/error', 'Data not found in response' );
    684                     return null;
    685                 }
    686             } else {
    687                 // Handle errors based on the response body.
    688                 if ( isset( $response_data['errors'] ) && is_array( $response_data['errors'] ) ) {
    689                     foreach ( $response_data['errors'] as $error ) {
    690                         if ( isset( $error['extensions']['code'] ) ) {
    691                             switch ( $error['extensions']['code'] ) {
    692                                 case 'SITE_NOT_FOUND':
    693                                     do_action( 'qm/debu', 'Error: Site not found.' );
    694                                     // Handle site not found error.
    695                                     break;
    696                                 case 'LANGUAGE_NOT_ENABLED':
    697                                     do_action( 'qm/error', 'Error: Language not enabled for site.' );
    698                                     // Handle language not enabled error.
    699                                     break;
    700                                 default:
    701                                     do_action( 'qm/error', 'An unknown error occurred.' );
    702                                     // Handle other errors.
    703                                     break;
    704                             }
    705                         }
    706                     }
    707                 } else {
    708                     do_action( 'qm/error', 'An unknown error occurred, but no error details were found' );
    709                 }
    710                 return null;
    711             }
    712             do_action( 'qm/info', 'FluentC Site setup post completed' );
    713             // Return the response.
    714             return json_decode( $body );
    715     }
    716 }
     368    private function getCachedOrFetch($key, $fetchCallback, $expiration = 0)
     369{
     370    $cached = $this->fluentc_cache->get($key);
     371    if ($cached !== false) {
     372        return is_string($cached) ? json_decode($cached) : $cached;
     373    }
     374
     375    $data = $fetchCallback();
     376    if ($data !== null) {
     377        $this->fluentc_cache->set_exp($key, is_string($data) ? $data : json_encode($data), $expiration);
     378    }
     379
     380    return $data;
     381}
     382
     383public function get_pll_fluentc_languages_list($args = [])
     384{
     385    $widgetapikey = get_option('fluentc_api_key');
     386    if (!$widgetapikey) {
     387        return []; // Return an empty array instead of null
     388    }
     389
     390    return $this->getPLLCachedOrFetch('fluentc_array_language_list', function () use ($widgetapikey, $args) {
     391        $get_all_languages = $this->get_available_languages($widgetapikey);
     392       
     393        // Check if the API call was successful
     394        if (!$get_all_languages || !isset($get_all_languages->data->getAvailableLanguages->body)) {
     395            return []; // Return an empty array if there's an error
     396        }
     397       
     398        $languages = array_map(function ($language) {
     399            return (object) [
     400                'name' => $language->name,
     401                'slug' => $language->code,
     402                'is_default' => false,
     403                'count' => 0,
     404            ];
     405        }, $get_all_languages->data->getAvailableLanguages->body);
     406
     407        return $this->filterLanguages($languages, $args);
     408    }, 43200);
     409}
     410
     411private function getPLLCachedOrFetch($key, $fetchCallback, $expiration = 0)
     412{
     413    $cached = $this->fluentc_cache->get($key);
     414    if ($cached !== false) {
     415        // Ensure we always return an array
     416        $decoded = is_string($cached) ? json_decode($cached, true) : $cached;
     417        return is_array($decoded) ? $decoded : [];
     418    }
     419
     420    $data = $fetchCallback();
     421    if ($data !== null) {
     422        $this->fluentc_cache->set_exp($key, json_encode($data), $expiration);
     423    }
     424
     425    // Ensure we always return an array
     426    return is_array($data) ? $data : [];
     427}
     428
     429    private function makeGraphQLRequest($query)
     430    {
     431        $response = wp_remote_post($this->fluentc_remote_url, [
     432            'body' => wp_json_encode(['query' => $query]),
     433            'headers' => [
     434                'Content-Type' => 'application/json',
     435                'x-api-key' => $this->api_key,
     436            ],
     437            'timeout' => 20,
     438        ]);
     439
     440        if (is_wp_error($response)) {
     441            do_action('qm/error', 'Error in request: ' . $response->get_error_message());
     442            return null;
     443        }
     444
     445        $body = wp_remote_retrieve_body($response);
     446        $status = wp_remote_retrieve_response_code($response);
     447
     448        $response_data = json_decode($body);
     449
     450        if ($status !== 200 || !$response_data) {
     451            $this->handleRequestError($status, $response_data);
     452            return null;
     453        }
     454
     455        return $response_data;
     456    }
     457
     458    private function handleRequestError($status, $response_data)
     459    {
     460        if ($status !== 200) {
     461            do_action('qm/error', "HTTP Error: $status");
     462        }
     463
     464        if (!$response_data) {
     465            do_action('qm/error', 'Invalid JSON response');
     466            return;
     467        }
     468
     469        if (isset($response_data->errors)) {
     470            foreach ($response_data->errors as $error) {
     471                $code = $error->extensions->code ?? 'UNKNOWN_ERROR';
     472                $message = $error->message ?? 'An unknown error occurred';
     473                do_action('qm/error', "GraphQL Error ($code): $message");
     474            }
     475        }
     476    }
     477}
  • fluentc-translation/trunk/src/services/class-widget.php

    r3152094 r3170433  
    101101                $header_code .= '        f = new fluentcWidgetWordpress({
    102102                    defaultLanguage: \'' . $site_language . '\',
    103                     display: "' . $apperance . '", // dropdown, float, list
     103                    display: "' . $apperance . '",
    104104                    languages: [' . $language_string . ']
    105105                    ' . $init_lang . ',
  • fluentc-translation/trunk/vendor/fluentc/php-html-parser/src/PHPHtmlParser/Content.php

    r3155849 r3170433  
    1414class Content
    1515{
     16   
    1617    /**
    1718     * The content string.
     
    111112    {
    112113        return \strlen($this->content) >= $this->pos + $count;
     114    }
     115
     116    /**
     117     * Copies the remainder of the content from the current position to the end.
     118     *
     119     * @return string The remaining content.
     120     */
     121    public function copyRemainder(): string
     122    {
     123        if ($this->isEof()) {
     124            return '';
     125        }
     126
     127        $remainder = substr($this->content, $this->pos);
     128        $this->pos = strlen($this->content);
     129        return $remainder;
     130    }
     131
     132    /**
     133     * Checks if the current position is at or past the end of the content.
     134     *
     135     * @return bool True if at the end of file, false otherwise.
     136     */
     137    public function isEof(): bool
     138    {
     139        return $this->pos >= $this->size;
    113140    }
    114141
  • fluentc-translation/trunk/vendor/fluentc/php-html-parser/src/PHPHtmlParser/Dom/Node/AbstractNode.php

    r3155849 r3170433  
    106106    {
    107107        // check attribute first
    108         if ($this->getAttribute($key) !== null) {
     108        if ($this->tag !== null && $this->getAttribute($key) !== null) {
    109109            return $this->getAttribute($key);
    110110        }
     
    123123                return $this->getParent();
    124124        }
    125     }
    126 
     125        return null;
     126    }
    127127    /**
    128128     * Simply calls the outer text method.
     
    299299     * Gets the tag object of this node.
    300300     */
    301     public function getTag(): Tag
     301    public function getTag(): ?Tag
    302302    {
    303303        return $this->tag;
    304304    }
     305
     306   
    305307
    306308    /**
     
    343345    public function getAttribute(string $key): ?string
    344346    {
     347        if ($this->tag === null) {
     348            return null;
     349        }
    345350        try {
    346351            $attributeDTO = $this->tag->getAttribute($key);
    347352        } catch (AttributeNotFoundException $e) {
    348353            // no attribute with this key exists, returning null.
    349             unset($e);
    350 
    351354            return null;
    352355        }
  • fluentc-translation/trunk/vendor/fluentc/php-html-parser/src/PHPHtmlParser/Dom/Node/Collection.php

    r3162981 r3170433  
    121121     * @param mixed $offset
    122122     */
     123    #[\ReturnTypeWillChange]
    123124    public function offsetUnset($offset)
    124125    {
  • fluentc-translation/trunk/vendor/fluentc/php-html-parser/src/PHPHtmlParser/Dom/Node/HtmlNode.php

    r3162981 r3170433  
    146146
    147147        // Skip generating tags for the dummy root.
    148     if ($this->tag->name() === 'fluentc-root') {
     148    if ($this->tag->name() === 'root') {
    149149        return $this->innerHtml();
    150150    }
Note: See TracChangeset for help on using the changeset viewer.