Plugin Directory

Changeset 3454136


Ignore:
Timestamp:
02/04/2026 09:24:11 PM (7 weeks ago)
Author:
conveythis
Message:

269.2 – Added per-language flag customization and fixed a Patchstack-reported security issue.

Location:
conveythis-translate/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • conveythis-translate/trunk/app/class/ConveyThis.php

    r3410103 r3454136  
    330330        $incoming = $_POST['settings'] ?? [];
    331331
     332        // These fields come as JSON strings and need to be decoded
    332333        $try_json = ['exclusions', 'glossary', 'exclusion_blocks', 'target_languages_translations'];
    333334
    334335        foreach ($try_json as $json_field) {
    335             if (isset($incoming[$json_field]) && is_string($incoming[$json_field])) {
    336                 $incoming[$json_field] = json_decode(stripslashes($incoming[$json_field]), true);
    337             }
    338         }
     336            if (isset($incoming[$json_field])) {
     337                // Only decode if it's a string (JSON), not already an array
     338                if (is_string($incoming[$json_field])) {
     339                    $decoded = json_decode(stripslashes($incoming[$json_field]), true);
     340                    if (json_last_error() === JSON_ERROR_NONE) {
     341                        $incoming[$json_field] = $decoded;
     342                    }
     343                }
     344            }
     345        }
     346
    339347
    340348        $exclusions = $incoming['exclusions'] ?? null;
     
    356364        }
    357365
     366        if (!check_ajax_referer('conveythis_ajax_save', 'nonce', false)) {
     367            wp_send_json_error('Invalid nonce');
     368            return;
     369        }
     370
    358371        foreach ($fields as $field) {
    359372            if (isset($incoming[$field])) {
    360                 $value = maybe_unserialize(wp_unslash($incoming[$field]));
     373                $unslashed = wp_unslash($incoming[$field]);
     374
     375                if (is_serialized($unslashed)) {
     376                    if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
     377                        $value = @unserialize($unslashed, ['allowed_classes' => false]);
     378                        if ($value === false && $unslashed !== serialize(false)) {
     379                            wp_send_json_error('Invalid data format');
     380                            return;
     381                        }
     382                    } else {
     383                        if (preg_match('/O:\d+:"/', $unslashed)) {
     384                            wp_send_json_error('Invalid data format');
     385                            return;
     386                        }
     387                        $value = maybe_unserialize($unslashed);
     388                    }
     389                } else {
     390                    $value = $unslashed;
     391                }
     392
     393                if ($field === 'style_change_language' || $field === 'style_change_flag') {
     394                    if (is_array($value)) {
     395                        $value = array_values($value);
     396                    }
     397                }
     398
    361399                update_option($field, $value);
    362400            }
     401        }
     402
     403        if (!array_key_exists('style_change_language', $incoming)) {
     404            update_option('style_change_language', []);
     405        }
     406
     407        if (!array_key_exists('style_change_flag', $incoming)) {
     408            update_option('style_change_flag', []);
    363409        }
    364410
     
    487533        $this->print_log("* genIcon()");
    488534        $i = 0;
    489         while ($i < 5) {
     535        while ($i < 5) { // Limit to 5 language/flag pairs
    490536            if (!empty($this->variables->style_change_language[$i]) && $this->variables->style_change_language[$i] == $language_id) {
    491537                $flag = $this->variables->style_change_flag[$i];
     
    683729
    684730    public function updateDataPlugin() {
     731        // Security: Check user capabilities before performing privileged operations
     732        if (!current_user_can('manage_options')) {
     733            return;
     734        }
     735       
    685736        $this->print_log("* updateDataPlugin()");
    686737        if (($key = array_search($this->variables->source_language, $this->variables->target_languages)) !== false) { //remove source_language from target_languages
     
    732783
    733784    function clearCacheButton() {
     785        // Security: Check user capabilities before performing privileged operations
     786        if (!current_user_can('manage_options')) {
     787            return;
     788        }
     789       
    734790        $this->print_log("* clearCacheButton()");
    735791        $this->send('DELETE', '/plugin/clean-button-cache/', array(
     
    15241580        }
    15251581
    1526         $i = 0;
    15271582        $temp = array();
    15281583
    1529         while ($i < 5) {
    1530             if (!empty($this->variables->style_change_language[$i])) {
    1531                 $temp[] = '"' . $this->variables->style_change_language[$i] . '":"' . $this->variables->style_change_flag[$i] . '"';
    1532             }
    1533             $i++;
     1584        // Ensure arrays are properly indexed and iterate through all values
     1585        // Re-index arrays to ensure sequential indices (0, 1, 2, ...)
     1586        $style_change_language = is_array($this->variables->style_change_language) ? array_values($this->variables->style_change_language) : array();
     1587        $style_change_flag = is_array($this->variables->style_change_flag) ? array_values($this->variables->style_change_flag) : array();
     1588
     1589        // Iterate through all available pairs (up to 5)
     1590        $maxPairs = min(5, max(count($style_change_language), count($style_change_flag)));
     1591       
     1592        for ($i = 0; $i < $maxPairs; $i++) {
     1593            if (!empty($style_change_language[$i])) {
     1594                $langId = $style_change_language[$i];
     1595                $flagCode = !empty($style_change_flag[$i]) ? $style_change_flag[$i] : '';
     1596                $temp[] = '"' . $langId . '":"' . $flagCode . '"';
     1597            }
    15341598        }
    15351599
  • conveythis-translate/trunk/app/class/Variables.php

    r3441289 r3454136  
    320320    public $siblingsAvoidArray = ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "LABEL", "LI", "SVG", "PRE"];
    321321
     322    // Flag groups for languages with multiple regional variants
     323    public $chinese_traditional = [
     324        'Taiwan' => 'tw',
     325        'Hong Kong' => 'hk',
     326        'Macau' => 'mo'
     327    ];
     328
     329    public $chinese_simplified = [
     330        'Mainland China' => 'cn',
     331        'Singapore' => 'sg',
     332        'Malaysia' => 'my'
     333    ];
     334
     335    public $arabic_middle_east = [
     336        'Bahrain' => 'bh',
     337        'Iraq' => 'iq',
     338        'Jordan' => 'jo',
     339        'Kuwait' => 'kw',
     340        'Lebanon' => 'lb',
     341        'Oman' => 'om',
     342        'Palestine' => 'ps',
     343        'Qatar' => 'qa',
     344        'Saudi Arabia' => 'sa',
     345        'Syria' => 'sy',
     346        'United Arab Emirates' => 'ae',
     347        'Yemen' => 'ye'
     348    ];
     349
     350    public $arabic_north_africa = [
     351        'Algeria' => 'dz',
     352        'Chad' => 'td',
     353        'Comoros' => 'km',
     354        'Djibouti' => 'dj',
     355        'Egypt' => 'eg',
     356        'Eritrea' => 'er',
     357        'Libya' => 'ly',
     358        'Mauritania' => 'mr',
     359        'Morocco' => 'ma',
     360        'Somalia' => 'so',
     361        'Sudan' => 'sd',
     362        'Tunisia' => 'tn'
     363    ];
     364
    322365    public $matchingLanguages = array(
    323         703 => array('language_id' => 703, 'title_en' => 'English', 'title' => 'English', 'code2' => 'en', 'code3' => 'eng', 'flag' => 'us', 'flag_ids' => [498, 497, 342]),
    324         768 => array('language_id' => 768, 'title_en' => 'Portuguese', 'title' => 'Português', 'code2' => 'pt', 'code3' => 'por', 'flag' => 'br', 'flag_ids' => [422, 318, 348]),
    325         777 => array('language_id' => 777, 'title_en' => 'Spanish', 'title' => 'Español', 'code2' => 'es', 'code3' => 'spa', 'flag' => 'es', 'flag_ids' => [336]),
     366        703 => array('language_id' => 703, 'title_en' => 'English', 'title' => 'English', 'code2' => 'en', 'code3' => 'eng', 'flag' => 'us', 'flag_ids' => [498, 497, 342, 320]), // US, UK, Canada, Australia
     367        704 => array('language_id' => 704, 'title_en' => 'Afrikaans', 'title' => 'Afrikaans', 'code2' => 'af', 'code3' => 'afr', 'flag' => 'za', 'flag_ids' => [471, 314, 345, 349, 359, 363, 366, 408, 420, 428, 470, 476, 490]), // South Africa + North Africa
     368        707 => array('language_id' => 707, 'title_en' => 'Arabic', 'title' => 'العربية', 'code2' => 'ar', 'code3' => 'ara', 'flag' => 'sa', 'flag_ids' => [324, 390, 396, 401, 405, 441, 451, 461, 481, 496, 505, 314, 345, 349, 359, 363, 366, 408, 420, 428, 470, 476, 490]), // Middle East + North Africa
     369        719 => array('language_id' => 719, 'title_en' => 'Chinese (Simplified)', 'title' => '简体', 'code2' => 'zh', 'code3' => 'zho-sim', 'flag' => 'cn', 'flag_ids' => [347, 466, 415]), // Mainland China, Singapore, Malaysia
     370        727 => array('language_id' => 727, 'title_en' => 'French', 'title' => 'Français', 'code2' => 'fr', 'code3' => 'fre', 'flag' => 'fr', 'flag_ids' => [371, 342]), // France, Canada
     371        768 => array('language_id' => 768, 'title_en' => 'Portuguese', 'title' => 'Português', 'code2' => 'pt', 'code3' => 'por', 'flag' => 'br', 'flag_ids' => [336, 450]), // Brazil, Portugal
     372        777 => array('language_id' => 777, 'title_en' => 'Spanish', 'title' => 'Español', 'code2' => 'es', 'code3' => 'spa', 'flag' => 'es', 'flag_ids' => [422, 318, 348, 474]), // Mexico, Argentina, Colombia, Spain
     373        796 => array('language_id' => 796, 'title_en' => 'Chinese (Traditional)', 'title' => '繁體', 'code2' => 'zh-tw', 'code3' => 'zho-tra', 'flag' => 'cn', 'flag_ids' => [482, 508]), // Taiwan, Hong Kong, Macau
    326374    );
    327375
    328376    public $matchingLanguageToFlag = array(
    329         703 => array(498, 497, 342),
    330         768 => array(336, 450),
    331         777 => array(422, 318, 348, 474)
     377        703 => array(498, 497, 342, 320), // US, UK, Canada, Australia
     378        704 => array(471, 314, 345, 349, 359, 363, 366, 408, 420, 428, 470, 476, 490), // South Africa + North Africa
     379        707 => array(324, 390, 396, 401, 405, 441, 451, 461, 481, 496, 505, 314, 345, 349, 359, 363, 366, 408, 420, 428, 470, 476, 490), // All Arabic countries
     380        719 => array(347, 466, 415), // China, Singapore, Malaysia
     381        727 => array(371, 342), // France, Canada
     382        768 => array(336, 450), // Brazil, Portugal
     383        777 => array(422, 318, 348, 474), // Mexico, Argentina, Colombia, Spain
     384        796 => array(482, 508) // Taiwan, Hong Kong
    332385    );
    333386
  • conveythis-translate/trunk/app/views/page/widget-style.php

    r3410103 r3454136  
    7171                    <div class="col-md-6">
    7272                        <div class="ui fluid search selection dropdown change_language">
     73                            <input type="hidden" name="style_change_language[]" value="">
    7374                            <i class="dropdown icon"></i>
    7475                            <div class="default text"><?php echo  esc_html(__( 'Select language', 'conveythis-translate' )); ?></div>
     
    8889                    <div class="col-md-6">
    8990                        <div class="ui fluid search selection dropdown change_flag">
     91                            <input type="hidden" name="style_change_flag[]" value="">
    9092                            <i class="dropdown icon"></i>
    9193                            <div class="default text"><?php echo  esc_html(__( 'Select Flag', 'conveythis-translate' )); ?></div>
  • conveythis-translate/trunk/app/widget/js/settings.js

    r3441289 r3454136  
    106106        prepareSettingsBeforeSave();
    107107
    108         const data = Object.fromEntries(new FormData(form[0]));
    109         console.log(data);
    110         console.log(conveythis_plugin_ajax.ajax_url);
     108        // Properly handle array inputs from FormData
     109        const formData = new FormData(form[0]);
     110        const data = {};
     111       
     112        // Fields that should be preserved as JSON strings (not parsed as arrays)
     113        // CRITICAL: These fields must NEVER be converted to arrays
     114        const jsonStringFields = ['glossary', 'exclusions', 'exclusion_blocks', 'target_languages_translations', 'custom_css_json'];
     115       
     116        // Convert FormData to object, handling arrays properly
     117        for (let [key, value] of formData.entries()) {
     118            // SAFETY CHECK: Never process JSON string fields as arrays, even if they somehow have []
     119            if (jsonStringFields.includes(key)) {
     120                // This field is a JSON string - preserve it as-is, never convert to array
     121                data[key] = value;
     122                continue; // Skip array processing for this field
     123            }
     124           
     125            // Handle array inputs (fields ending with [])
     126            // IMPORTANT: Only process fields ending with [] as arrays
     127            // JSON string fields like 'glossary' should NOT end with []
     128            if (key.endsWith('[]')) {
     129                const arrayKey = key.slice(0, -2); // Remove '[]'
     130               
     131                // EXTRA SAFETY: Double-check this isn't a JSON string field
     132                if (jsonStringFields.includes(arrayKey)) {
     133                    console.error('[SAFETY ERROR] Attempted to process JSON field as array: ' + arrayKey);
     134                    // Preserve as string instead
     135                    data[arrayKey] = value;
     136                    continue;
     137                }
     138               
     139                // Only create array if key doesn't exist (prevents overwriting existing values)
     140                if (!data[arrayKey]) {
     141                    data[arrayKey] = [];
     142                }
     143                // Only add non-empty values
     144                if (value && value.trim() !== '') {
     145                    data[arrayKey].push(value);
     146                }
     147            } else {
     148                // Regular field - preserve as-is (including JSON strings)
     149                data[key] = value;
     150            }
     151        }
     152       
     153       
    111154        $.post(conveythis_plugin_ajax.ajax_url, {
    112155            action: 'conveythis_save_all_settings',
     
    116159            $('.conveythis-overlay').remove();
    117160            $btn.prop('disabled', false).val('Save Settings');
    118             console.log(response)
    119161            if (response.success) {
    120162                toastr.success('Settings saved successfully');
     
    278320            'code3': 'eng',
    279321            'flag': 'R04',
    280             'flag_codes': {'us': 'United States of America', 'gb': ' United Kingdom', 'ca': 'Canada'}
     322            'flag_codes': {'us': 'United States of America', 'gb': ' United Kingdom', 'ca': 'Canada', 'au': 'Australia'}
    281323        },
    282         704: {'title_en': 'Afrikaans', 'title': 'Afrikaans', 'code2': 'af', 'code3': 'afr', 'flag': '7xS'},
     324        704: {
     325            'title_en': 'Afrikaans',
     326            'title': 'Afrikaans',
     327            'code2': 'af',
     328            'code3': 'afr',
     329            'flag': '7xS',
     330            'flag_codes': {
     331                'za': 'South Africa',
     332                'dz': 'Algeria',
     333                'td': 'Chad',
     334                'km': 'Comoros',
     335                'dj': 'Djibouti',
     336                'eg': 'Egypt',
     337                'er': 'Eritrea',
     338                'ly': 'Libya',
     339                'mr': 'Mauritania',
     340                'ma': 'Morocco',
     341                'so': 'Somalia',
     342                'sd': 'Sudan',
     343                'tn': 'Tunisia'
     344            }
     345        },
    283346        705: {'title_en': 'Albanian', 'title': 'Shqip', 'code2': 'sq', 'code3': 'sqi', 'flag': '5iM'},
    284347        706: {'title_en': 'Amharic', 'title': 'አማርኛ', 'code2': 'am', 'code3': 'amh', 'flag': 'ZH1'},
    285         707: {'title_en': 'Arabic', 'title': 'العربية', 'code2': 'ar', 'code3': 'ara', 'flag': 'J06'},
     348        707: {
     349            'title_en': 'Arabic',
     350            'title': 'العربية',
     351            'code2': 'ar',
     352            'code3': 'ara',
     353            'flag': 'J06',
     354            'flag_codes': {
     355                // Middle East
     356                'bh': 'Bahrain',
     357                'iq': 'Iraq',
     358                'jo': 'Jordan',
     359                'kw': 'Kuwait',
     360                'lb': 'Lebanon',
     361                'om': 'Oman',
     362                'ps': 'Palestine',
     363                'qa': 'Qatar',
     364                'sa': 'Saudi Arabia',
     365                'sy': 'Syria',
     366                'ae': 'United Arab Emirates',
     367                'ye': 'Yemen',
     368                // North Africa
     369                'dz': 'Algeria',
     370                'td': 'Chad',
     371                'km': 'Comoros',
     372                'dj': 'Djibouti',
     373                'eg': 'Egypt',
     374                'er': 'Eritrea',
     375                'ly': 'Libya',
     376                'mr': 'Mauritania',
     377                'ma': 'Morocco',
     378                'so': 'Somalia',
     379                'sd': 'Sudan',
     380                'tn': 'Tunisia'
     381            }
     382        },
    286383        708: {'title_en': 'Armenian', 'title': 'Հայերեն', 'code2': 'hy', 'code3': 'hye', 'flag': 'q9U'},
    287384        709: {'title_en': 'Azerbaijan', 'title': 'Azərbaycanca', 'code2': 'az', 'code3': 'aze', 'flag': 'Wg1'},
     
    295392        717: {'title_en': 'Catalan', 'title': 'Català', 'code2': 'ca', 'code3': 'cat', 'flag': 'Pw6'},
    296393        718: {'title_en': 'Cebuano', 'title': 'Cebuano', 'code2': 'ceb', 'code3': 'ceb', 'flag': ''},
    297         719: {'title_en': 'Chinese (Simplified)', 'title': '简体', 'code2': 'zh', 'code3': 'zh-sim', 'flag': 'Z1v'},
    298         796: {'title_en': 'Chinese (Traditional)', 'title': '繁體', 'code2': 'zh-tw', 'code3': 'zh-tra', 'flag': 'Z1v'},
     394        719: {
     395            'title_en': 'Chinese (Simplified)',
     396            'title': '简体',
     397            'code2': 'zh',
     398            'code3': 'zh-sim',
     399            'flag': 'Z1v',
     400            'flag_codes': {
     401                'cn': 'Mainland China',
     402                'sg': 'Singapore',
     403                'my': 'Malaysia'
     404            }
     405        },
     406        796: {
     407            'title_en': 'Chinese (Traditional)',
     408            'title': '繁體',
     409            'code2': 'zh-tw',
     410            'code3': 'zh-tra',
     411            'flag': 'Z1v',
     412            'flag_codes': {
     413                'tw': 'Taiwan',
     414                'hk': 'Hong Kong',
     415                'mo': 'Macau'
     416            }
     417        },
    299418        720: {'title_en': 'Croatian', 'title': 'Hrvatski', 'code2': 'hr', 'code3': 'hrv', 'flag': '7KQ'},
    300419        721: {'title_en': 'Czech', 'title': 'Čeština', 'code2': 'cs', 'code3': 'cze', 'flag': '1ZY'},
     
    311430            'flag': 'nM4'
    312431        },
    313         727: {'title_en': 'French', 'title': 'Français', 'code2': 'fr', 'code3': 'fre', 'flag': 'E77'},
     432        727: {
     433            'title_en': 'French',
     434            'title': 'Français',
     435            'code2': 'fr',
     436            'code3': 'fre',
     437            'flag': 'E77',
     438            'flag_codes': {'fr': 'France', 'ca': 'Canada'}
     439        },
    314440        728: {'title_en': 'Galician', 'title': 'Galego', 'code2': 'gl', 'code3': 'glg', 'flag': 'A5d'},
    315441        729: {'title_en': 'Georgian', 'title': 'ქართული', 'code2': 'ka', 'code3': 'kat', 'flag': '8Ou'},
     
    506632    $('.conveythis-delete-page').on('click', function (e) {
    507633        //e.preventDefault();
    508         $(this).parent().remove();
     634        let $rowToDelete = $(this).closest('.style-language');
     635        if ($rowToDelete.length) {
     636            // This is a flag style row - update availability after deletion
     637            $rowToDelete.remove();
     638            updateLanguageDropdownAvailability();
     639        } else {
     640            // Other type of row (glossary, exclusion, etc.)
     641            $(this).parent().remove();
     642        }
    509643        //  $(".autoSave").click();
    510644    });
     
    555689        e.preventDefault();
    556690
    557         if ($(".style-language").length == 4) {
     691        if ($(".style-language").length == 6) { // 1 cloned template + 5 actual rows = 6 total
    558692            $('#add_flag_style').prop("disable", true);
    559693            return;
     
    563697
    564698        $rule_style.removeClass('cloned')
    565         $rule_style.find('.change_language').prepend('<input type="hidden" name="style_change_language[]" value="">')
    566         $rule_style.find('.change_flag').prepend('<input type="hidden" name="style_change_flag[]"  value="">')
     699        $rule_style.find('input[name="style_change_language[]"]').val('');
     700        $rule_style.find('input[name="style_change_flag[]"]').val('');
    567701        $("#flag-style_wrapper").append($rule_style);
    568702
    569703        $(document).find('.conveythis-delete-page').on('click', function (e) {
    570704            e.preventDefault();
    571             $(this).parent().remove();
     705            let $rowToDelete = $(this).closest('.style-language');
     706            $rowToDelete.remove();
     707           
     708            // Update language availability after row deletion
     709            updateLanguageDropdownAvailability();
    572710        });
    573711
    574712        $('.ui.dropdown').dropdown();
     713        // Re-initialize handlers for all dropdowns (including the new one)
    575714        sortFlagsByLanguage();
    576     });
    577 
     715        // Initialize only the newly added row
     716        initializeFlagDropdowns();
     717    });
     718
     719    // Initialize on page load
    578720    sortFlagsByLanguage();
     721    initializeFlagDropdowns();
    579722
    580723    $('#add_glossary').on('click', function (e) {
     
    10951238        $('input[name="glossary"]').val(JSON.stringify(glossary));
    10961239
     1240        // Prepare style_change_language and style_change_flag arrays
     1241        // CRITICAL: Sync dropdown values to hidden inputs before collecting
     1242       
     1243        // First, ensure all dropdown values are synced to hidden inputs
     1244        $('.style-language').each(function () {
     1245            let $row = $(this);
     1246            let $languageDropdown = $row.find('.ui.dropdown.change_language');
     1247            let $flagDropdown = $row.find('.ui.dropdown.change_flag');
     1248            let $languageInput = $row.find('input[name="style_change_language[]"]');
     1249            let $flagInput = $row.find('input[name="style_change_flag[]"]');
     1250           
     1251            // Get current dropdown values
     1252            let langValue = $languageDropdown.dropdown('get value');
     1253            let flagValue = $flagDropdown.dropdown('get value');
     1254           
     1255            // Update hidden inputs with current dropdown values
     1256            if ($languageInput.length && langValue) {
     1257                $languageInput.val(langValue);
     1258            }
     1259            if ($flagInput.length && flagValue) {
     1260                $flagInput.val(flagValue);
     1261            }
     1262        });
     1263       
     1264        // Now collect from hidden inputs
     1265        let style_change_language = [];
     1266        let style_change_flag = [];
     1267       
     1268        $('.style-language').each(function () {
     1269            let $row = $(this);
     1270            let $languageInput = $row.find('input[name="style_change_language[]"]');
     1271            let $flagInput = $row.find('input[name="style_change_flag[]"]');
     1272           
     1273            // Get values from hidden inputs
     1274            let langValue = $languageInput.length ? $languageInput.val() : '';
     1275            let flagValue = $flagInput.length ? $flagInput.val() : '';
     1276           
     1277            // Only add if language is set
     1278            if (langValue && langValue.trim() !== '') {
     1279                style_change_language.push(langValue.trim());
     1280                style_change_flag.push(flagValue ? flagValue.trim() : '');
     1281            }
     1282        });
     1283       
     1284
    10971285        let exclusion_blocks = [];
    10981286        $('div.exclusion_block').each(function () {
     
    11621350    }
    11631351
     1352    // Function to update language dropdowns to disable already-selected languages
     1353    function updateLanguageDropdownAvailability() {
     1354
     1355        // Get all currently selected language IDs (excluding empty values)
     1356        let selectedLanguages = [];
     1357        $('.style-language').each(function() {
     1358            let $languageInput = $(this).find('input[name="style_change_language[]"]');
     1359            let langValue = $languageInput.length ? $languageInput.val() : '';
     1360            if (langValue && langValue.trim() !== '') {
     1361                selectedLanguages.push(langValue.trim());
     1362            }
     1363        });
     1364       
     1365        // Update all language dropdowns
     1366        $('.ui.dropdown.change_language').each(function() {
     1367            let $currentDropdown = $(this);
     1368            let $currentRow = $currentDropdown.closest('.style-language');
     1369            let $currentInput = $currentRow.find('input[name="style_change_language[]"]');
     1370            let currentValue = $currentInput.length ? $currentInput.val() : '';
     1371           
     1372            // Enable/disable items in this dropdown
     1373            $currentDropdown.find('.menu .item').each(function() {
     1374                let $item = $(this);
     1375                let itemValue = $item.attr('data-value');
     1376               
     1377                // If this language is selected in another row, disable it (unless it's the current row's selection)
     1378                if (selectedLanguages.includes(itemValue) && itemValue !== currentValue) {
     1379                    $item.addClass('disabled');
     1380                } else {
     1381                    $item.removeClass('disabled');
     1382                }
     1383            });
     1384        });
     1385    }
     1386
    11641387    // Sort flags by languages
    11651388    function sortFlagsByLanguage() {
     
    11671390            onChange: function (value) {
    11681391
     1392                // Update the hidden input for language
     1393                let $languageInput = $(this).closest('.row').find('input[name="style_change_language[]"]');
     1394                if ($languageInput.length) {
     1395                    $languageInput.val(value);
     1396                }
     1397
     1398                // Update availability of languages in all dropdowns
     1399                updateLanguageDropdownAvailability();
     1400
    11691401                let $dropdown = $(this).closest('.row').find('.ui.dropdown.change_flag');
    1170                 let flagCodes = languages[value]['flag_codes'];
    1171 
    1172                 $dropdown.find('.menu').empty();
    1173                 $dropdown.find('.text').empty();
    1174 
    1175                 $.each(flagCodes, function (code, title) {
    1176 
    1177                     let newItem = $('<div class="item" data-value="' + code + '">\
    1178                                         <div class="ui image" style="height: 28px; width: 30px; background-position: 50% 50%;\
    1179                                                                     background-size: contain; background-repeat: no-repeat;\
    1180                                                                     background-image: url(\'//cdn.conveythis.com/images/flags/svg/' + code + '.svg\')"></div>\
    1181                                         ' + title + '\
    1182                                     </div>');
    1183 
    1184                     $dropdown.find('.menu').append(newItem);
    1185 
    1186                 });
     1402               
     1403                // Check if flag_codes exists for this language
     1404                if (languages[value] && languages[value]['flag_codes']) {
     1405                    let flagCodes = languages[value]['flag_codes'];
     1406
     1407                    // Clear existing menu items and text
     1408                    $dropdown.find('.menu').empty();
     1409                    $dropdown.find('.text').text('Select Flag');
     1410                    $dropdown.find('input[type="hidden"]').val('');
     1411
     1412                    // Populate with new flag options
     1413                    $.each(flagCodes, function (code, title) {
     1414                        let newItem = $('<div class="item" data-value="' + code + '">\
     1415                                            <div class="ui image" style="height: 28px; width: 30px; background-position: 50% 50%;\
     1416                                                background-size: contain; background-repeat: no-repeat;\
     1417                                                background-image: url(\'//cdn.conveythis.com/images/flags/svg/' + code + '.svg\')"></div>\
     1418                                            ' + title + '\
     1419                                        </div>');
     1420                        $dropdown.find('.menu').append(newItem);
     1421                    });
     1422                   
     1423                    // Destroy and reinitialize dropdown to ensure it recognizes new items
     1424                    try {
     1425                        $dropdown.dropdown('destroy');
     1426                    } catch(e) {
     1427                        // Dropdown may not be initialized yet
     1428                    }
     1429                    $dropdown.dropdown();
     1430                } else {
     1431                    // If no flag_codes, clear the dropdown
     1432                    $dropdown.find('.menu').empty();
     1433                    $dropdown.find('.text').text('Select Flag');
     1434                    $dropdown.find('input[type="hidden"]').val('');
     1435                    try {
     1436                        $dropdown.dropdown('destroy');
     1437                    } catch(e) {
     1438                        // Dropdown may not be initialized yet
     1439                    }
     1440                    $dropdown.dropdown();
     1441                }
    11871442            },
    1188         });
     1443            onRemove: function (value) {
     1444                // When language is cleared/removed
     1445               
     1446                // Clear the hidden input
     1447                let $languageInput = $(this).closest('.row').find('input[name="style_change_language[]"]');
     1448                if ($languageInput.length) {
     1449                    $languageInput.val('');
     1450                }
     1451               
     1452                // Update availability - re-enable this language in other dropdowns
     1453                updateLanguageDropdownAvailability();
     1454            }
     1455        });
     1456
     1457        // Handle flag dropdown changes
     1458        $('.ui.dropdown.change_flag').dropdown({
     1459            onChange: function (value) {
     1460
     1461                // Update the hidden input for flag
     1462                let $flagInput = $(this).closest('.row').find('input[name="style_change_flag[]"]');
     1463                if ($flagInput.length) {
     1464                    $flagInput.val(value);
     1465                }
     1466            },
     1467        });
     1468    }
     1469
     1470    // Initialize dropdowns with saved values
     1471    function initializeFlagDropdowns() {
     1472        $('.style-language').each(function() {
     1473            let $row = $(this);
     1474            let $languageDropdown = $row.find('.ui.dropdown.change_language');
     1475            let $flagDropdown = $row.find('.ui.dropdown.change_flag');
     1476            let $languageInput = $row.find('input[name="style_change_language[]"]');
     1477            let $flagInput = $row.find('input[name="style_change_flag[]"]');
     1478
     1479            // Initialize language dropdown if value exists
     1480            if ($languageInput.length && $languageInput.val()) {
     1481                let languageValue = $languageInput.val();
     1482               
     1483                // Set the language dropdown value
     1484                if (languages[languageValue]) {
     1485                    $languageDropdown.dropdown('set selected', languageValue);
     1486                   
     1487                    // Populate flags if flag_codes exists
     1488                    if (languages[languageValue]['flag_codes']) {
     1489                        let flagCodes = languages[languageValue]['flag_codes'];
     1490                       
     1491                        // Clear existing menu items
     1492                        $flagDropdown.find('.menu').empty();
     1493                        $flagDropdown.find('.text').text('Select Flag');
     1494                       
     1495                        $.each(flagCodes, function (code, title) {
     1496                            let newItem = $('<div class="item" data-value="' + code + '">\
     1497                                <div class="ui image" style="height: 28px; width: 30px; background-position: 50% 50%;\
     1498                                    background-size: contain; background-repeat: no-repeat;\
     1499                                    background-image: url(\'//cdn.conveythis.com/images/flags/svg/' + code + '.svg\')"></div>\
     1500                                ' + title + '\
     1501                            </div>');
     1502                            $flagDropdown.find('.menu').append(newItem);
     1503                        });
     1504                       
     1505                        // Destroy and reinitialize dropdown to ensure it recognizes new items
     1506                        try {
     1507                            $flagDropdown.dropdown('destroy');
     1508                        } catch(e) {
     1509                            // Dropdown may not be initialized yet
     1510                        }
     1511                        $flagDropdown.dropdown();
     1512
     1513                        // Set flag value if it exists
     1514                        if ($flagInput.length && $flagInput.val()) {
     1515                            let flagValue = $flagInput.val();
     1516                            $flagDropdown.dropdown('set selected', flagValue);
     1517                        }
     1518                    }
     1519                }
     1520            }
     1521        });
     1522       
     1523        // Update language availability after initialization
     1524        updateLanguageDropdownAvailability();
    11891525    }
    11901526
  • conveythis-translate/trunk/changelog.txt

    r3441289 r3454136  
    11== Changelog ==
     2= 269.2 =
     3* Added per-language flag customization and fixed a Patchstack-reported security issue.
     4
    25= 269.1 =
    36* ConveyThis now supports 200+ languages
  • conveythis-translate/trunk/index.php

    r3441289 r3454136  
    44Plugin URI: https://www.conveythis.com/?utm_source=widget&utm_medium=wordpress
    55Description: Translate your WordPress site into over 100 languages using professional and instant machine translation technology. ConveyThis will help provide you with an SEO-friendy, multilingual website in minutes with no coding required.
    6 Version: 269.1
     6Version: 269.2
    77
    88Author: ConveyThis Translate Team
  • conveythis-translate/trunk/readme.txt

    r3441289 r3454136  
    66Tested up to: 6.9
    77
    8 Stable tag: 269.1
     8Stable tag: 269.2
    99
    1010License: GPLv2
     
    218218
    219219== Changelog ==
     220= 269.2 =
     221* Added per-language flag customization and fixed a Patchstack-reported security issue.
     222
    220223= 269.1 =
    221224* ConveyThis now supports 200+ languages
Note: See TracChangeset for help on using the changeset viewer.