Plugin Directory

Changeset 3354997


Ignore:
Timestamp:
09/03/2025 03:20:28 AM (7 months ago)
Author:
linguise
Message:

Updating to version 2.1.67

Location:
linguise
Files:
4 added
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • linguise/tags/2.1.67/linguise.php

    r3350018 r3354997  
    55 * Plugin URI: https://www.linguise.com/
    66 * Description: Linguise translation plugin
    7  * Version:2.1.66
     7 * Version:2.1.67
    88 * Text Domain: linguise
    99 * Domain Path: /languages
  • linguise/tags/2.1.67/readme.txt

    r3350018 r3354997  
    44Requires at least: 4.0
    55Tested up to: 6.8
    6 Stable tag:2.1.66
     6Stable tag:2.1.67
    77Requires PHP: 7.0
    88License: GPLv2 or later
     
    104104
    105105== Changelog ==
     106= 2.1.67 =
     107- Fix: WooCommerce Stripe Gateway Issue: the credit card form not showing up
     108
    106109= 2.1.66 =
    107110- Fix: Double slash in redirect URL
  • linguise/tags/2.1.67/src/FragmentHandler.php

    r3339453 r3354997  
    206206            'key' => 'paymentMethodData.*?stripe\.plugin_url',
    207207            'mode' => 'regex_full',
     208            'kind' => 'deny',
     209        ],
     210        [
     211            'key' => 'currency',
     212            'mode' => 'exact',
    208213            'kind' => 'deny',
    209214        ],
     
    12921297
    12931298            $replaced_json = $json_data->getJson();
     1299
     1300            if (function_exists('apply_filters')) {
     1301                $replaced_json = apply_filters('linguise_after_apply_translated_fragments_override', $fragment_name, $replaced_json);
     1302            }
     1303
    12941304            if ($should_encode) {
    12951305                $replaced_json = rawurlencode($replaced_json);
     
    13921402
    13931403                $replaced_json = self::applyTranslatedFragmentsForAuto(json_decode('{' . $html_matches[3] . '}', true), $fragment_list['fragments']);
     1404
     1405                if (function_exists('apply_filters')) {
     1406                    $replaced_json = apply_filters('linguise_after_apply_translated_fragments_auto', $fragment_name, $replaced_json);
     1407                }
     1408
    13941409                if ($replaced_json === false) {
    13951410                    throw new \LogicException('FragmentHandler -> Injection -> ' . $fragment_name . '/' . $fragment_param . ' -> JSON data is empty!');
  • linguise/tags/2.1.67/src/constants.php

    r3350018 r3354997  
    11<?php
    22if (!defined('LINGUISE_SCRIPT_TRANSLATION_VERSION')) {
    3     define('LINGUISE_SCRIPT_TRANSLATION_VERSION', 'wordpress_plugin/2.1.66');
     3    define('LINGUISE_SCRIPT_TRANSLATION_VERSION', 'wordpress_plugin/2.1.67');
    44}
    55
    66if (!defined('LINGUISE_VERSION')) {
    7     define('LINGUISE_VERSION', '2.1.66');
     7    define('LINGUISE_VERSION', '2.1.67');
    88}
  • linguise/tags/2.1.67/src/thirdparty/wc/gateway-stripe.php

    r3291510 r3354997  
    2222
    2323    /**
     24     * A collection of fragment keys that will be translated
     25     *
     26     * @var array{key:string, mode:'exact'|'path'|'wildcard'|'regex'|'regex_full',kind:'allow'|'deny'}
     27     */
     28    protected static $fragment_keys = [
     29        [
     30            'key' => 'allowed_shipping_countries\..*',
     31            'mode' => 'regex_full',
     32            'kind' => 'deny',
     33        ],
     34        [
     35            'key' => 'checkout\.((?:country|currency)_code|needs_shipping)$',
     36            'mode' => 'regex_full',
     37            'kind' => 'deny',
     38        ],
     39        [
     40            'key' => 'baseLocation\.(country|state)$',
     41            'mode' => 'regex_full',
     42            'kind' => 'deny',
     43        ],
     44        [
     45            'key' => 'blocksAppearance\.rules\..*',
     46            'mode' => 'regex_full',
     47            'kind' => 'deny',
     48        ]
     49    ];
     50
     51    /**
    2452     * Decides if the integration should be loaded.
    2553     *
     
    4270        // Block checkout
    4371        add_filter('wc_stripe_params', [$this, 'hookTranslateParams'], 10, 1);
     72
     73        add_filter('linguise_after_apply_translated_fragments_auto', [$this, 'restoreOriginalConfigClassicCheckout'], 10, 2);
     74        add_filter('linguise_after_apply_translated_fragments_override', [$this, 'restoreOriginalConfigBlockCheckout'], 10, 2);
    4475    }
    4576
     
    5586        // Block checkout
    5687        remove_filter('wc_stripe_params', [$this, 'hookTranslateParams'], 10);
     88
     89        remove_filter('linguise_after_apply_translated_fragments_auto', [$this, 'restoreOriginalConfigClassicCheckout'], 10, 2);
     90        remove_filter('linguise_after_apply_translated_fragments_override', [$this, 'restoreOriginalConfigBlockCheckout'], 10, 2);
    5791    }
    5892
     
    149183        return $params;
    150184    }
     185
     186    /**
     187     * Restore the original block appearance structure that was accidentally
     188     * changed into an array
     189     *
     190     * @param string $fragment_name The name of the fragment being translated.
     191     * @param array  $replaced_json The fragment being translated.
     192     *
     193     * @return array The original block appearance structure.
     194     */
     195    public function restoreOriginalConfigClassicCheckout($fragment_name, $replaced_json)
     196    {
     197        if ($fragment_name === 'wc-stripe-upe-classic') {
     198            if (array_key_exists('blocksAppearance', $replaced_json)) {
     199                $replaced_json['blocksAppearance'] = $this->arrayToObject($replaced_json['blocksAppearance']);
     200            }
     201        }
     202
     203        return $replaced_json;
     204    }
     205
     206    /**
     207     * Restore the original block appearance structure that was accidentally
     208     * changed into an array
     209     *
     210     * @param string $fragment_name The name of the fragment being translated.
     211     * @param string $replaced_json The fragment being translated.
     212     *
     213     * @return array The original block appearance structure.
     214     */
     215    public function restoreOriginalConfigBlockCheckout($fragment_name, $replaced_json)
     216    {
     217        $replaced_json = json_decode($replaced_json);
     218        if ($fragment_name === 'wc-settings-encoded') {
     219            if (isset($replaced_json->paymentMethodData->stripe->blocksAppearance)) {
     220                $rules = $replaced_json->paymentMethodData->stripe->blocksAppearance->rules;
     221                $replaced_json->paymentMethodData->stripe->blocksAppearance->rules = $this->arrayToObject($rules);
     222            }
     223        }
     224
     225        return json_encode($replaced_json);
     226    }
     227
     228    /**
     229     * Recursive convert an array into an object.
     230     *
     231     * @param array $data The data to convert.
     232     *
     233     * @return object The converted object.
     234     */
     235    public function arrayToObject($data)
     236    {
     237        if (is_array($data)) {
     238            if (empty($data)) {
     239                return new \stdClass(); // convert empty array to object
     240            }
     241            foreach ($data as $key => $value) {
     242                $data[$key] = $this->arrayToObject($value);
     243            }
     244            return (object) $data;
     245        } elseif ($data instanceof \stdClass) {
     246            foreach ($data as $key => $value) {
     247                $data->$key = $this->arrayToObject($value);
     248            }
     249            return $data;
     250        }
     251
     252        return $data;
     253    }
    151254}
  • linguise/tags/2.1.67/vendor/composer/autoload_classmap.php

    r3316163 r3354997  
    3939    'Linguise\\Vendor\\Linguise\\Script\\Core\\Helper' => $vendorDir . '/linguise/script-php/src/Helper.php',
    4040    'Linguise\\Vendor\\Linguise\\Script\\Core\\Hook' => $vendorDir . '/linguise/script-php/src/Hook.php',
     41    'Linguise\\Vendor\\Linguise\\Script\\Core\\HttpResponse' => $vendorDir . '/linguise/script-php/src/HttpResponse.php',
    4142    'Linguise\\Vendor\\Linguise\\Script\\Core\\JsonWalker' => $vendorDir . '/linguise/script-php/src/JsonWalker.php',
    4243    'Linguise\\Vendor\\Linguise\\Script\\Core\\Management' => $vendorDir . '/linguise/script-php/src/Management.php',
     44    'Linguise\\Vendor\\Linguise\\Script\\Core\\OobeManager' => $vendorDir . '/linguise/script-php/src/OobeManager.php',
    4345    'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\OpenCart' => $vendorDir . '/linguise/script-php/src/Platforms/OpenCart.php',
    4446    'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\PrestaShop' => $vendorDir . '/linguise/script-php/src/Platforms/PrestaShop.php',
  • linguise/tags/2.1.67/vendor/composer/autoload_static.php

    r3321566 r3354997  
    107107        'Linguise\\Vendor\\Linguise\\Script\\Core\\Helper' => __DIR__ . '/..' . '/linguise/script-php/src/Helper.php',
    108108        'Linguise\\Vendor\\Linguise\\Script\\Core\\Hook' => __DIR__ . '/..' . '/linguise/script-php/src/Hook.php',
     109        'Linguise\\Vendor\\Linguise\\Script\\Core\\HttpResponse' => __DIR__ . '/..' . '/linguise/script-php/src/HttpResponse.php',
    109110        'Linguise\\Vendor\\Linguise\\Script\\Core\\JsonWalker' => __DIR__ . '/..' . '/linguise/script-php/src/JsonWalker.php',
    110111        'Linguise\\Vendor\\Linguise\\Script\\Core\\Management' => __DIR__ . '/..' . '/linguise/script-php/src/Management.php',
     112        'Linguise\\Vendor\\Linguise\\Script\\Core\\OobeManager' => __DIR__ . '/..' . '/linguise/script-php/src/OobeManager.php',
    111113        'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\OpenCart' => __DIR__ . '/..' . '/linguise/script-php/src/Platforms/OpenCart.php',
    112114        'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\PrestaShop' => __DIR__ . '/..' . '/linguise/script-php/src/Platforms/PrestaShop.php',
  • linguise/tags/2.1.67/vendor/composer/installed.json

    r3349428 r3354997  
    5757        {
    5858            "name": "linguise/script-php",
    59             "version": "v1.3.32",
    60             "version_normalized": "1.3.32.0",
     59            "version": "v1.3.33",
     60            "version_normalized": "1.3.33.0",
    6161            "source": {
    6262                "type": "git",
    6363                "url": "git@bitbucket.org:linguise/script-php.git",
    64                 "reference": "031e108bbc9e337b0a36a813404a35217b556cac"
     64                "reference": "9e65bd71800e1ae003c6929b23c2d24de167f7c5"
    6565            },
    6666            "require": {
     
    7373                "phpunit/phpunit": "^9"
    7474            },
    75             "time": "2025-07-29T08:11:46+00:00",
     75            "time": "2025-08-27T05:12:16+00:00",
    7676            "type": "library",
    7777            "installation-source": "source",
  • linguise/tags/2.1.67/vendor/composer/installed.php

    r3350018 r3354997  
    44        'pretty_version' => 'dev-master',
    55        'version' => 'dev-master',
    6         'reference' => '2b6bbeb52204d2861b41ccaef60b7dcdce8e6193',
     6        'reference' => 'a2c6891d602a1aa5ffa31d073b38f968aa1199d0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'linguise/script-php' => array(
    23             'pretty_version' => 'v1.3.32',
    24             'version' => '1.3.32.0',
    25             'reference' => '031e108bbc9e337b0a36a813404a35217b556cac',
     23            'pretty_version' => 'v1.3.33',
     24            'version' => '1.3.33.0',
     25            'reference' => '9e65bd71800e1ae003c6929b23c2d24de167f7c5',
    2626            'type' => 'library',
    2727            'install_path' => __DIR__ . '/../linguise/script-php',
     
    3232            'pretty_version' => 'dev-master',
    3333            'version' => 'dev-master',
    34             'reference' => '2b6bbeb52204d2861b41ccaef60b7dcdce8e6193',
     34            'reference' => 'a2c6891d602a1aa5ffa31d073b38f968aa1199d0',
    3535            'type' => 'library',
    3636            'install_path' => __DIR__ . '/../../',
  • linguise/tags/2.1.67/vendor/linguise/script-php/.version

    r3339453 r3354997  
    1 1.3.32
     11.3.33
  • linguise/tags/2.1.67/vendor/linguise/script-php/src/Helper.php

    r3324083 r3354997  
    243243     */
    244244    public static function transformToLocalConfig($local_config, $remote_config) {
     245        /**
     246         * If API-JS return language setting null, just return local config
     247         */
     248        if (!isset($remote_config['language_settings']) || is_null($remote_config['language_settings']) || empty($remote_config['language_settings'])) {
     249            return $local_config;
     250        }
     251
    245252        $language_settings = $remote_config['language_settings'];
    246253        $local_config['flag_display_type'] = $language_settings['display'];
     
    282289        return $local_config;
    283290    }
     291
     292    /**
     293     * Convert a boolean value to an integer (0 or 1)
     294     *
     295     * @param mixed $value The value to convert
     296     *
     297     * @return mixed The converted value (0 or 1), or original
     298     */
     299    static function boolInt($value)
     300    {
     301        if (is_bool($value)) {
     302            return $value ? 1 : 0;
     303        }
     304
     305        return $value;
     306    }
     307
     308    /**
     309     * Convert an integer (0 or 1) to a boolean value
     310     *
     311     * @param mixed $value The value to convert
     312     *
     313     * @return mixed The converted value (true or false), or original
     314     */
     315    static function intBool($value)
     316    {
     317        if (is_bool($value)) {
     318            return $value;
     319        }
     320        if (is_int($value)) {
     321            return $value === 1;
     322        }
     323
     324        return $value;
     325    }
     326
     327    static function defineConstants($is_logged_in = false)
     328    {
     329        if (!defined('LINGUISE_MANAGEMENT')) {
     330            define('LINGUISE_MANAGEMENT', 1);
     331        }
     332
     333        if ($is_logged_in && !defined('LINGUISE_AUTHORIZED')) {
     334            define('LINGUISE_AUTHORIZED', 1);
     335        }
     336
     337        // Silent debug noises
     338        $request = Request::getInstance(true);
     339        $base_dir = rtrim($request->getBaseDir(), '/');
     340        if (!defined('LINGUISE_BASE_URL')) {
     341            define('LINGUISE_BASE_URL', $base_dir . '/linguise');
     342        }
     343    }
    284344}
  • linguise/tags/2.1.67/vendor/linguise/script-php/src/Management.php

    r3339453 r3354997  
    44
    55defined('LINGUISE_SCRIPT_TRANSLATION') or die();
     6use Linguise\Vendor\Linguise\Script\Core\HttpResponse;
    67
    78class Management {
     
    1213
    1314    /**
    14      * @var string
    15      */
    16     private static $token_key = 'linguise_token';
    17     /**
    1815     * Mapping data from the API to the local config
    1916     *
    2017     * @var array
    2118     */
    22     private static $flag_options_map = [
     19    public static $flag_options_map = [
    2320        'display' => 'flag_display_type',
    2421        'position' => 'display_position',
     
    4946    }
    5047
    51     public function run($html_message = \null, $api_web_errors = []) {
    52         // Start session
    53         $sess = Session::getInstance()->start();
    54         // Set our CSRF token, always overrides
    55         $sess->generateCsrfToken();
    56 
    57         // We define this constant so user can't do direct access to the template
    58         $this->defineConstants(false);
    59 
    60         if (isset($_GET['linguise_action'])) {
    61             switch ($_GET['linguise_action']) {
    62                 case 'download-debug':
    63                     $this->downloadDebug();
    64                     break;
    65                 case 'update-config':
    66                     break;
    67                 default:
    68                     if (empty($_SESSION[self::$token_key])) {
    69                         $this->rejectGET();
    70                     } else {
    71                         $this->unknownGETAction();
    72                     }
    73                     break;
    74             }
    75         }
    76 
    77         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'Helper.php';
    78         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
    79 
    80         if (!$sess->hasSession()) {
    81             // Render login page
    82             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    83             if (!empty($html_message)) {
    84                 echo $html_message;
    85             }
    86             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'oobe.php';
    87         } else {
    88             // Logged in? we send it!
    89             $this->defineConstants(true);
    90             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    91             if (!empty($html_message)) {
    92                 echo $html_message;
    93             }
    94             $view_mode = isset($_GET['ling_mode']) ? $_GET['ling_mode'] : 'default';
    95             if ($view_mode === 'expert') {
    96                 require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'management-expert.php';
    97             } else {
    98                 require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'management.php';
    99             }
    100         }
    101         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'footer.php';
    102         die();
    103     }
    104 
    105     public function oobeRun($html_message = \null) {
    106         // Start session
    107         $sess = Session::getInstance()->start();
    108         // Set our CSRF token, always overrides
    109         $sess->generateCsrfToken();
    110 
    111         $this->defineConstants(false);
    112 
    113         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
    114         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    115         if (!empty($html_message)) {
    116             echo $html_message;
    117         }
    118         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'oobe.php';
    119         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'footer.php';
    120     }
    121 
    12248    public function editorRun() {
    123         $this->defineConstants(false);
     49        Helper::defineConstants(false);
    12450
    12551        require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
     
    13965        if (!$sess->hasSession()) {
    14066            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Not authorized</div>';
    141             $this->run($message, $api_web_errors);
     67            $oobe = OobeManager::getInstance();
     68            $oobe->run($message, $api_web_errors);
    14269        }
    14370        if (!isset($_POST['_token'])) {
    144             $this->errorJSON('Missing CSRF token', 400);
     71            HttpResponse::errorJSON('Missing CSRF token', 400);
    14572            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Missing CSRF token</div>';
    146             $this->run($message, $api_web_errors);
     73            $oobe = OobeManager::getInstance();
     74            $oobe->run($message, $api_web_errors);
    14775        }
    14876        if (!$sess->verifyCsrfToken('linguise_config', $_POST['_token'])) {
    14977            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid CSRF token</div>';
    150             $this->run($message, $api_web_errors);
     78            $oobe = OobeManager::getInstance();
     79            $oobe->run($message, $api_web_errors);
    15180        }
    15281
     
    187116                    $dynamic_translations['public_key'] = $api_result['data']['public_key'];
    188117                    $linguise_options = Helper::transformToLocalConfig($linguise_options, $api_result['data']);
    189                     $dynamic_translations['enabled'] = $this->boolInt($api_result['data']['dynamic_translations']['enabled']);
     118                    $dynamic_translations['enabled'] = Helper::boolInt($api_result['data']['dynamic_translations']['enabled']);
    190119                    $token_changed = true;
    191120                } else if ($api_result !== false && isset($api_result['status_code'])) {
     
    234163                    if ($token_changed) {
    235164                        // If token changed, we update enabled state
    236                         $dynamic_translations['enabled'] = $this->boolInt($api_result['data']['dynamic_translations']['enabled']);
     165                        $dynamic_translations['enabled'] = Helper::boolInt($api_result['data']['dynamic_translations']['enabled']);
    237166                    }
    238167                } else if ($api_result !== false && isset($api_result['status_code'])) {
     
    277206                'display_position' => isset($linguise_options['display_position']) ? $linguise_options['display_position'] : 'no',
    278207                'flag_display_type' => isset($linguise_options['flag_display_type']) ? $linguise_options['flag_display_type'] : 'popup',
    279                 'enable_flag' => $this->boolInt($enable_flag),
    280                 'enable_language_name' => $this->boolInt($enable_language_name),
    281                 'enable_language_name_popup' => $this->boolInt($enable_language_name_popup),
    282                 'enable_language_short_name' => $this->boolInt($enable_language_short_name),
     208                'enable_flag' => Helper::boolInt($enable_flag),
     209                'enable_language_name' => Helper::boolInt($enable_language_name),
     210                'enable_language_name_popup' => Helper::boolInt($enable_language_name_popup),
     211                'enable_language_short_name' => Helper::boolInt($enable_language_short_name),
    283212                'language_name_display' => isset($linguise_options['language_name_display']) ? $linguise_options['language_name_display'] : 'en',
    284213                'flag_shape' => isset($linguise_options['flag_shape']) ? $linguise_options['flag_shape'] : 'rounded',
     
    321250                    'language' => $default_language,
    322251                    'allowed_languages' => $translate_languages,
    323                     'dynamic_translations' => $this->intBool($dynamic_translations['enabled']),
     252                    'dynamic_translations' => Helper::intBool($dynamic_translations['enabled']),
    324253                    'language_settings' => [
    325254                        // Flag related
    326255                        'display' => $linguise_options['flag_display_type'],
    327256                        'position' => $linguise_options['display_position'],
    328                         'enabled_flag' => $this->intBool($enable_flag),
    329                         'enabled_lang_name' => $this->intBool($enable_language_name),
    330                         'enabled_lang_name_popup' => $this->intBool($enable_language_name_popup),
    331                         'enabled_lang_short_name' => $this->intBool($enable_language_short_name),
     257                        'enabled_flag' => Helper::intBool($enable_flag),
     258                        'enabled_lang_name' => Helper::intBool($enable_language_name),
     259                        'enabled_lang_name_popup' => Helper::intBool($enable_language_name_popup),
     260                        'enabled_lang_short_name' => Helper::intBool($enable_language_short_name),
    332261                        'lang_name_display' => $linguise_options['language_name_display'],
    333262                        'flag_shape' => $linguise_options['flag_shape'],
     
    410339
    411340        // Re-render
    412         $this->run($notification_popup_msg, $api_web_errors);
     341        $oobe = OobeManager::getInstance();
     342        $oobe->run($notification_popup_msg, $api_web_errors);
    413343    }
    414344
     
    418348        $sess = Session::getInstance()->start();
    419349        if (!$sess->hasSession()) {
    420             $this->errorJSON('Unauthorized', 401);
     350            HttpResponse::errorJSON('Unauthorized', 401);
    421351        }
    422352        if (!isset($_POST['nonce'])) {
    423             $this->errorJSON('Missing nonce token', 400);
     353            HttpResponse::errorJSON('Missing nonce token', 400);
    424354        }
    425355        if (!$sess->verifyCsrfToken('linguise_config_iframe', $_POST['nonce'])) {
    426             $this->errorJSON('Invalid nonce token', 403);
     356            HttpResponse::errorJSON('Invalid nonce token', 403);
    427357        }
    428358
     
    441371            if (!isset($data[$field])) {
    442372                // response with error
    443                 $this->errorJSON('Missing required field: ' . $field, 400);
     373                HttpResponse::errorJSON('Missing required field: ' . $field, 400);
    444374            }
    445375        }
     
    475405
    476406        $db->saveOtherParam('linguise_options', $options);
    477         $this->successJSON(true, 'Configuration updated successfully', 200);
     407        HttpResponse::successJSON(true, 'Configuration updated successfully', 200);
    478408    }
    479409
     
    484414        $jwt_token = isset($_SERVER['HTTP_X_LINGUISE_HASH']) ? $_SERVER['HTTP_X_LINGUISE_HASH'] : null;
    485415        if (empty($jwt_token)) {
    486             $this->errorJSON('Missing Hash header', 400);
     416            HttpResponse::errorJSON('Missing Hash header', 400);
    487417        }
    488418
     
    490420        $input_data = file_get_contents('php://input');
    491421        if (empty($input_data)) {
    492             $this->errorJSON('Invalid request', 400);
     422            HttpResponse::errorJSON('Invalid request', 400);
    493423        }
    494424
    495425        $input_data = json_decode($input_data, true);
    496426        if (json_last_error() !== JSON_ERROR_NONE) {
    497             $this->errorJSON('Invalid JSON data', 400);
     427            HttpResponse::errorJSON('Invalid JSON data', 400);
    498428        }
    499429
     
    503433
    504434        if ($input_data['token'] !== $options['token']) {
    505             $this->errorJSON('Invalid token', 401);
     435            HttpResponse::errorJSON('Invalid token', 401);
    506436        }
    507437
     
    525455                    $flag_real_key = self::$flag_options_map[$flag_key];
    526456                }
    527                 $options[$flag_real_key] = $this->boolInt($flag_value);
     457                $options[$flag_real_key] = Helper::boolInt($flag_value);
    528458            }
    529459        }
     
    531461        // Save
    532462        $db->saveOtherParam('linguise_options', $options);
    533         $this->successJSON(true, 'Configuration updated successfully', 200);
    534     }
    535 
    536     public function login()
    537     {
    538         if (!isset($_POST['_token'])) {
    539             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Missing CSRF token</div>';
    540             $this->oobeRun($message);
    541         }
    542         $sess = Session::getInstance()->start();
    543         if (!$sess->verifyCsrfToken('linguise_oobe_login', $_POST['_token'])) {
    544             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid CSRF token</div>';
    545             $this->oobeRun($message);
    546         }
    547 
    548         // Authenticate ourself, get the token or password
    549         if ($sess->oobeComplete() && !empty($_POST['password'])) {
    550             $existing_password = Database::getInstance()->ensureConnection()->retrieveOtherParam('linguise_password');
    551             if ($existing_password && password_verify($_POST['password'], $existing_password)) {
    552                 // Set session
    553                 $sess->setSession($existing_password, true);
    554                 $this->run(\null, []);
    555             } else {
    556                 $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid password</div>';
    557                 $this->oobeRun($message);
    558             }
    559         } else {
    560             $sess->unsetSession();
    561             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid request</div>';
    562             $this->oobeRun($message);
    563         }
    564     }
    565 
    566     public function mergeConfig($skip_missing = \false)
    567     {
    568         /**
    569          * @disregard P1011 - already checked
    570          */
    571         $use_mysql = defined('LINGUISE_OOBE_MYSQL') && LINGUISE_OOBE_MYSQL;
    572 
    573         // Each config for the mysql, LINGUISE_OOBE_MYSQL_DB_{HOST|USER|PASSWORD|NAME|PREFIX|PORT}
    574         $oobe_config = [
    575             'DB_HOST',
    576             'DB_USER',
    577             'DB_PASSWORD',
    578             'DB_NAME',
    579             'DB_PREFIX',
    580             'DB_PORT',
    581             'DB_FLAGS',
    582         ];
    583 
    584         if ($use_mysql) {
    585             foreach ($oobe_config as $config) {
    586                 if (defined('LINGUISE_OOBE_MYSQL_' . $config)) {
    587                     Configuration::getInstance()->set(strtolower($config), constant('LINGUISE_OOBE_MYSQL_' . $config));
    588                 }
    589             }
    590         }
    591 
    592         // Connect to database and install options
    593         Helper::prepareDataDir();
    594         $db = Database::getInstance(true);
    595 
    596         if (!LINGUISE_OOBE_DONE) {
    597             // Not yet ready
    598             return;
    599         }
    600 
    601         $db->ensureConnection()->installOptions();
    602 
    603         // Get metadata
    604         $existing_options = $db->retrieveOtherParam('linguise_options');
    605         if (!empty($existing_options)) {
    606             // Merge existing options
    607             Configuration::getInstance()->set('token', $existing_options['token']);
    608             Configuration::getInstance()->set('cache_enabled', $existing_options['cache_enabled']);
    609             Configuration::getInstance()->set('cache_max_size', $existing_options['cache_max_size']);
    610             Configuration::getInstance()->set('search_translations', $existing_options['search_translations']);
    611             Configuration::getInstance()->set('debug', $existing_options['debug']);
    612 
    613             foreach ($existing_options['expert_mode'] as $key => $value) {
    614                 Configuration::getInstance()->set($key, $value);
    615             }
    616         } else {
    617             if ($skip_missing) {
    618                 // Skip if missing
    619                 return;
    620             }
    621 
    622             // We create new options
    623             $current_token = Configuration::getInstance()->get('token');
    624             if ($current_token === 'REPLACE_BY_YOUR_TOKEN') {
    625                 $current_token = '';
    626             }
    627             $this->createOptionsWithToken($current_token);
    628         }
    629     }
    630 
    631     private function oobeRunError($message_str, $status_code = 200)
    632     {
    633         $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>' . $message_str . '</div>';
    634         http_response_code($status_code);
    635         $this->oobeRun($message);
    636         die();
    637     }
    638 
    639     public function activateLinguise()
    640     {
    641         // Check if OOBE
    642         if (defined('LINGUISE_OOBE_DONE') && LINGUISE_OOBE_DONE) {
    643             $this->oobeRunError('Not allowed', 403);
    644         } elseif (!defined('LINGUISE_OOBE_DONE')) {
    645             // Missing data
    646             $this->oobeRunError('Unknown status', 500);
    647         }
    648 
    649         if (!isset($_POST['_token'])) {
    650             $this->oobeRunError('Missing CSRF token', 400);
    651         }
    652         $sess = Session::getInstance()->start();
    653         if (!$sess->verifyCsrfToken('linguise_oobe_register', $_POST['_token'])) {
    654             $this->oobeRunError('Invalid CSRF token', 403);
    655         }
    656 
    657         $new_pass = isset($_POST['password']) ? $_POST['password'] : null;
    658         if (empty($new_pass)) {
    659             $this->oobeRunError('Missing password', 400);
    660         }
    661 
    662         // == Password check ==
    663         // Must be at least 10 characters long
    664         if (strlen($new_pass) < 10) {
    665             $this->oobeRunError('Password must be at least 10 characters long', 400);
    666         }
    667 
    668         if ($sess->hasSession()) {
    669             $this->oobeRunError('Already activated', 403);
    670         }
    671 
    672         $is_token = defined('LINGUISE_OOBE_TOKEN_EXIST') && LINGUISE_OOBE_TOKEN_EXIST;
    673         if ($is_token) {
    674             if (!isset($_POST['token'])) {
    675                 $this->oobeRunError('Missing token', 400);
    676             }
    677 
    678             $token = $_POST['token'];
    679             if ($token !== Configuration::getInstance()->get('token')) {
    680                 $this->oobeRunError('Invalid token provided in session', 401);
    681             }
    682 
    683             // This will automatically return error response
    684             $database_store = $this->storeDatabaseConnection(false);
    685             // set configuration
    686             if ($database_store['MYSQL']) {
    687                 Configuration::getInstance()->set('db_host', $database_store['MYSQL_DB_HOST']);
    688                 Configuration::getInstance()->set('db_user', $database_store['MYSQL_DB_USER']);
    689                 Configuration::getInstance()->set('db_password', $database_store['MYSQL_DB_PASSWORD']);
    690                 Configuration::getInstance()->set('db_name', $database_store['MYSQL_DB_NAME']);
    691                 Configuration::getInstance()->set('db_prefix', $database_store['MYSQL_DB_PREFIX']);
    692                 Configuration::getInstance()->set('db_port', $database_store['MYSQL_DB_PORT']);
    693             } else {
    694                 // SQLite
    695                 Configuration::getInstance()->set('db_host', '');
    696                 Configuration::getInstance()->set('db_user', '');
    697                 Configuration::getInstance()->set('db_password', '');
    698                 Configuration::getInstance()->set('db_name', '');
    699                 Configuration::getInstance()->set('db_prefix', '');
    700 
    701                 $sqlite_test = $this->prepareRootDatabaseSQLite();
    702                 if ($sqlite_test !== true) {
    703                     $this->oobeRunError($sqlite_test, 500);
    704                 }
    705             }
    706 
    707             // Init database
    708             Helper::prepareDataDir();
    709             $db = Database::getInstance(true, true)->ensureConnection();
    710             $db->installOptions();
    711 
    712             // Valid, then let's set the password
    713             $hashed_pass = $this->hashPassword($new_pass);
    714             $db->saveOtherParam('linguise_password', $hashed_pass);
    715             // Create the options
    716             $db_options = $this->createOptionsWithToken($token);
    717 
    718             // Write OOBE config
    719             $this->writeOOBE($database_store);
    720 
    721             $api_result = $this->getRemoteData($token);
    722             if ($api_result !== false && isset($api_result['data'])) {
    723                 $default_language = Helper::sanitizeKey($api_result['data']['language']);
    724 
    725                 $translation_languages = $api_result['data']['languages'];
    726                 $translate_languages = [];
    727                 if (!empty($translation_languages)) {
    728                     foreach ($translation_languages as $translation_language) {
    729                         $translate_languages[] = Helper::sanitizeKey($translation_language['code']);
    730                     }
    731                 }
    732 
    733                 $db_options['enabled_languages'] = $translate_languages;
    734                 $db_options['default_language'] = $default_language;
    735                 $db_options['dynamic_translations']['enabled'] = $this->intBool($api_result['data']['dynamic_translations']['enabled']);
    736                 $db_options['dynamic_translations']['public_key'] = $api_result['data']['public_key'];
    737 
    738                 if (!empty($api_result['data']['language_settings'])) {
    739                     foreach ($api_result['data']['language_settings'] as $flag_key => $flag_value) {
    740                         $flag_real_key = $flag_key;
    741                         if (isset(self::$flag_options_map[$flag_key])) {
    742                             $flag_real_key = self::$flag_options_map[$flag_key];
    743                         }
    744                         $db_options[$flag_real_key] = $this->boolInt($flag_value);
    745                     }
    746                 }
    747 
    748                 // Re-save with API data
    749                 $db->saveOtherParam('linguise_options', $db_options);
    750             }
    751 
    752             // Set session, and login the user.
    753             $sess->setOobeForced();
    754             $sess->setSession($hashed_pass, true); // Set session with password mode
    755             $this->run();
    756             die();
    757         } else {
    758             // This will automatically return error response
    759             $database_store = $this->storeDatabaseConnection(false);
    760             // set configuration
    761             if ($database_store['MYSQL']) {
    762                 Configuration::getInstance()->set('db_host', $database_store['MYSQL_DB_HOST']);
    763                 Configuration::getInstance()->set('db_user', $database_store['MYSQL_DB_USER']);
    764                 Configuration::getInstance()->set('db_password', $database_store['MYSQL_DB_PASSWORD']);
    765                 Configuration::getInstance()->set('db_name', $database_store['MYSQL_DB_NAME']);
    766                 Configuration::getInstance()->set('db_prefix', $database_store['MYSQL_DB_PREFIX']);
    767                 Configuration::getInstance()->set('db_port', $database_store['MYSQL_DB_PORT']);
    768             } else {
    769                 // SQLite
    770                 Configuration::getInstance()->set('db_host', '');
    771                 Configuration::getInstance()->set('db_user', '');
    772                 Configuration::getInstance()->set('db_password', '');
    773                 Configuration::getInstance()->set('db_name', '');
    774                 Configuration::getInstance()->set('db_prefix', '');
    775 
    776                 $sqlite_test = $this->prepareRootDatabaseSQLite();
    777                 if ($sqlite_test !== true) {
    778                     $this->oobeRunError($sqlite_test, 500);
    779                 }
    780             }
    781 
    782             // Init database
    783             Helper::prepareDataDir();
    784             $db = Database::getInstance(true, true)->ensureConnection();
    785             $db->installOptions();
    786 
    787             // No token, we just set it immediately
    788             $existing_password = $db->retrieveOtherParam('linguise_password');
    789             if ($existing_password) {
    790                 $this->oobeRunError('Password already set', 400);
    791             }
    792 
    793             // Set the password
    794             $hashed_pass = $this->hashPassword($new_pass);
    795 
    796             // Create
    797             $db->saveOtherParam('linguise_password', $hashed_pass);
    798             // Create the options
    799             $this->createOptionsWithToken(''); // Empty token since no token provided yet.
    800 
    801             // Write OOBE config
    802             $this->writeOOBE($database_store);
    803 
    804 
    805             // Set session, and login the user.
    806             $sess->setOobeForced();
    807             $sess->setSession($hashed_pass, true); // Set session with password mode
    808             $this->run();
    809             die();
    810         }
    811     }
    812 
    813     public function storeDatabaseConnection($testMode = \false)
    814     {
    815         if ($testMode) {
    816             if (!isset($_POST['_token'])) {
    817                 $this->errorJSON('Missing CSRF token', 400);
    818             }
    819             $sess = Session::getInstance()->start();
    820             if (!$sess->verifyCsrfToken('linguise_oobe_register', $_POST['_token'])) {
    821                 $this->errorJSON('Invalid CSRF token', 403);
    822             }
    823         }
    824 
    825         // Get from POST data
    826         $mode = $_POST['db_mode'] ?? null;
    827         $host = $_POST['db_host'] ?? null;
    828         $user = $_POST['db_user'] ?? null;
    829         $password = $_POST['db_password'] ?? null;
    830         $name = $_POST['db_name'] ?? null;
    831         $port = $_POST['db_port'] ?? 3306;
    832         $prefix = $_POST['db_prefix'] ?? null;
    833 
    834         if (empty($mode)) {
    835             $this->errorJSON('Missing `db_mode` data', 400);
    836         }
    837 
    838         switch ($mode) {
    839             case 'mysql':
    840                 if (empty($host) || empty($user) || empty($name)) {
    841                     $this->errorJSON('Missing `db_host`, `db_user` or `db_name` data', 400);
    842                 }
    843                 if (!$testMode && (empty($prefix))) {
    844                     $this->errorJSON('Missing `db_prefix` data', 400);
    845                 }
    846 
    847                 if ($testMode) {
    848                     $result = $this->testMySQL($host, $user, $password, $name, $port, $prefix);
    849                     if ($result !== true) {
    850                         $this->errorJSON($result, 500);
    851                     }
    852                     $this->successJSON(true, 'MySQL connection test successful', 200);
    853                 }
    854 
    855                 return [
    856                     'MYSQL' => true,
    857                     'MYSQL_DB_HOST' => $host,
    858                     'MYSQL_DB_USER' => $user,
    859                     'MYSQL_DB_PASSWORD' => $password,
    860                     'MYSQL_DB_NAME' => $name,
    861                     'MYSQL_DB_PORT' => $port,
    862                     'MYSQL_DB_PREFIX' => $prefix,
    863                 ];
    864             case 'sqlite':
    865                 // Check if SQLite3 is enabled
    866                 if ($testMode) {
    867                     $result = $this->testSqlite();
    868                     if ($result !== true) {
    869                         $this->errorJSON($result, 500);
    870                     }
    871                     $this->successJSON(true, 'SQLite connection test successful', 200);
    872                 }
    873 
    874                 // Store the SQLite connection
    875                 return [
    876                     'MYSQL' => false,
    877                 ];
    878             default:
    879                 $this->errorJSON('Invalid `db_mode` data', 400);
    880         }
    881     }
    882 
    883     private function testMySQL($host, $user, $password, $name, $port = 3306, $prefix = '')
    884     {
    885         // Check if MySQLi is enabled
    886         if (!extension_loaded('mysqli')) {
    887             return 'MySQLi extension not loaded';
    888         }
    889 
    890         // Check if MySQLi class exist
    891         if (!class_exists('mysqli')) {
    892             return 'MySQLi class not found';
    893         }
    894 
    895         // Attempt to connect to the database
    896         $connection = \null;
    897         try {
    898             $connection = new \mysqli($host, $user, $password, $name, $port);
    899         } catch (\mysqli_sql_exception $e) {
    900             return 'Connection failed: ' . $e->getMessage();
    901         }
    902 
    903         if (empty($connection)) {
    904             return 'Connection failed: No connection object created';
    905         }
    906 
    907         if ($connection->connect_error) {
    908             return 'Connection failed: ' . $connection->connect_error;
    909         }
    910 
    911         // Close the connection
    912         $connection->close();
    913 
    914         return true;
    915     }
    916 
    917     private function testSqlite()
    918     {
    919         // Check if SQLite3 is enabled
    920         if (!extension_loaded('sqlite3')) {
    921             return 'SQLite3 extension not loaded';
    922         }
    923 
    924         // Check if SQLite3 class exist
    925         if (!class_exists('SQLite3')) {
    926             return 'SQLite3 class not found';
    927         }
    928 
    929         // Check if we can write in LINGUISE_BASE_DIR / .linguise-main.db
    930         $sqlite_test = $this->prepareRootDatabaseSQLite();
    931         if ($sqlite_test !== true) {
    932             return $sqlite_test;
    933         }
    934 
    935         if (!Helper::checkDataDirAvailable()) {
    936             return 'Cannot write to data directory';
    937         }
    938 
    939         return true;
    940     }
    941 
    942     private function prepareRootDatabaseSQLite()
    943     {
    944         $databases_dir = LINGUISE_BASE_DIR . '.databases' . DIRECTORY_SEPARATOR;
    945         if (!file_exists($databases_dir)) {
    946             if (!mkdir($databases_dir, 0766, true)) {
    947                 return 'Cannot create database directory: ' . $databases_dir;
    948             }
    949         }
    950 
    951         $htaccess_file = $databases_dir . '.htaccess';
    952         if (!file_exists($htaccess_file)) {
    953             $written = file_put_contents($htaccess_file, 'deny from all');
    954             if ($written === false) {
    955                 return 'Cannot write to database directory: ' . $databases_dir;
    956             }
    957         }
    958 
    959         $db_path = $databases_dir . 'linguise-main.db';
    960         if (!file_exists($db_path)) {
    961             // touch the file
    962             $db_touch = touch($db_path);
    963             if (!$db_touch) {
    964                 return 'Cannot create database file: ' . $db_path;
    965             }
    966             unlink($db_path);
    967         } else {
    968             if (!is_writable($db_path)) {
    969                 return 'Cannot write database to ' . $db_path;
    970             }
    971         }
    972 
    973         return true;
    974     }
    975 
    976     private function createOptionsWithToken($token)
    977     {
    978         $db = Database::getInstance()->ensureConnection();
    979         $options = [
    980             'token' => $token,
    981             'cache_enabled' => Configuration::getInstance()->get('cache_enabled'),
    982             'cache_max_size' => Configuration::getInstance()->get('cache_max_size'),
    983             'search_translations' => Configuration::getInstance()->get('search_translations'),
    984             'debug' => Configuration::getInstance()->get('debug'),
    985             'dynamic_translations' => [
    986                 'enabled' => 0,
    987                 'public_key' => null,
    988             ],
    989 
    990             // Languages related
    991             'default_language' => 'en',
    992             'enabled_languages' => [],
    993 
    994             // Flag related
    995             'display_position' => 'bottom_right',
    996             'flag_display_type' => 'popup',
    997             'enable_flag' => 1,
    998             'enable_language_name' => 1,
    999             'enable_language_short_name' => 0,
    1000             'language_name_display' => 'en',
    1001             'flag_shape' => 'round',
    1002             'flag_en_type' => 'en-us',
    1003             'flag_de_type' => 'de',
    1004             'flag_es_type' => 'es',
    1005             'flag_pt_type' => 'pt',
    1006             'flag_tw_type' => 'zh-tw',
    1007             'flag_border_radius' => 0,
    1008             'flag_width' => 24,
    1009             'pre_text' => '',
    1010             'post_text' => '',
    1011             'custom_css' => '',
    1012             'language_name_color' => '#222',
    1013             'language_name_hover_color' => '#222',
    1014             'popup_language_name_color' => '#222',
    1015             'popup_language_name_hover_color' => '#222',
    1016             'flag_shadow_h' => 2,
    1017             'flag_shadow_v' => 2,
    1018             'flag_shadow_blur' => 12,
    1019             'flag_shadow_spread' => 0,
    1020             'flag_shadow_color' => '#eee',
    1021             'flag_shadow_color_alpha' => (float)1.0, // we use 100% scaling, 0.0-1.0
    1022             'flag_hover_shadow_h' => 3,
    1023             'flag_hover_shadow_v' => 3,
    1024             'flag_hover_shadow_blur' => 6,
    1025             'flag_hover_shadow_spread' => 0,
    1026             'flag_hover_shadow_color' => '#bfbfbf',
    1027             'flag_hover_shadow_color_alpha' => (float)1.0,
    1028 
    1029             'expert_mode' => [],
    1030         ];
    1031 
    1032         $db->saveOtherParam('linguise_options', $options);
    1033 
    1034         return $options;
    1035     }
    1036 
    1037     private function errorJSON($message, $code = 500)
    1038     {
    1039         header('Content-Type: application/json; charset=utf-8');
    1040         http_response_code($code);
    1041         echo json_encode([
    1042             'error' => true,
    1043             'message' => $message
    1044         ]);
    1045         exit;
    1046     }
    1047 
    1048     private function successJSON($data, $message = '', $code = 200)
    1049     {
    1050         header('Content-Type: application/json; charset=utf-8');
    1051         http_response_code($code);
    1052         echo json_encode([
    1053             'error' => false,
    1054             'message' => $message,
    1055             'data' => $data
    1056         ]);
    1057         exit;
    1058     }
    1059 
    1060     private function hashPassword($input)
    1061     {
    1062         $hashed = password_hash($input, PASSWORD_BCRYPT, [
    1063             'cost' => 12,
    1064         ]);
    1065 
    1066         return $hashed;
    1067     }
    1068 
    1069     private function writeOOBE($database_store)
    1070     {
    1071         // Modify OOBE status in ui-config.php
    1072         $ui_config = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..') . DIRECTORY_SEPARATOR . 'ui-config.php';
    1073         $content = file_get_contents($ui_config);
    1074 
    1075         $replaced_content = preg_replace('/define\([\'"]LINGUISE_OOBE_DONE[\'"], .*\);/m', 'define(\'LINGUISE_OOBE_DONE\', true);', $content);
    1076         if (empty($replaced_content)) {
    1077             $this->errorJSON('Failed to update ui-config.php', 500);
    1078         }
    1079 
    1080         // Update the database connection
    1081         foreach ($database_store as $key => $value) {
    1082             // Push content
    1083             $value_wrap = json_encode($value);
    1084             $replaced_content .= "\ndefine('LINGUISE_OOBE_" . $key . "', " . $value_wrap . ");\n";
    1085         }
    1086 
    1087         file_put_contents($ui_config, $replaced_content);
    1088     }
    1089 
    1090     public function clearDebug()
    1091     {
    1092         // Verify session
    1093         $sess = Session::getInstance()->start();
    1094         if (!$sess->hasSession()) {
    1095             $this->errorJSON('Unauthorized', 401);
    1096         }
    1097         if (!isset($_GET['nonce'])) {
    1098             $this->errorJSON('Missing nonce token', 400);
    1099         }
    1100         if (!$sess->verifyCsrfToken('linguise_clear_debug', $_GET['nonce'])) {
    1101             $this->errorJSON('Invalid nonce token', 403);
    1102         }
    1103 
    1104         // Clear the debug file
    1105         $debug_file = LINGUISE_BASE_DIR . 'debug.php';
    1106         $last_errors_file = LINGUISE_BASE_DIR . 'errors.php';
    1107         if (file_exists($debug_file)) {
    1108             file_put_contents($debug_file, "<?php die(); ?>" . PHP_EOL);
    1109         }
    1110         if (file_exists($last_errors_file)) {
    1111             file_put_contents($last_errors_file, "<?php die(); ?>" . PHP_EOL);
    1112         }
    1113 
    1114         $this->successJSON(true, 'Log truncated!', 200);
    1115     }
    1116 
    1117     public function clearCache()
    1118     {
    1119         // Verify session
    1120         $sess = Session::getInstance()->start();
    1121         if (!$sess->hasSession()) {
    1122             $this->errorJSON('Unauthorized', 401);
    1123         }
    1124         if (!isset($_GET['nonce'])) {
    1125             $this->errorJSON('Missing nonce token', 400);
    1126         }
    1127         if (!$sess->verifyCsrfToken('linguise_clear_cache', $_GET['nonce'])) {
    1128             $this->errorJSON('Invalid nonce token', 403);
    1129         }
    1130 
    1131         // Clear the cache
    1132         Cache::getInstance()->clearAll();
    1133     }
    1134 
    1135     private function downloadDebug()
    1136     {
    1137         // Verify session
    1138         $sess = Session::getInstance()->start();
    1139         if (!$sess->hasSession()) {
    1140             die('Unauthorized');
    1141         }
    1142 
    1143         $debug_file = LINGUISE_BASE_DIR . 'debug.php';
    1144         if (file_exists($debug_file)) {
    1145             header('Content-Description: File Transfer');
    1146             header('Content-Type: application/octet-stream');
    1147             header('Content-Disposition: attachment; filename="debug.txt"');
    1148             header('Content-Transfer-Encoding: binary');
    1149             header('Expires: 0');
    1150             header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    1151             header('Pragma: public');
    1152             header('Content-Length: ' . filesize($debug_file));
    1153             ob_clean();
    1154             ob_end_flush();
    1155             $handle = fopen($debug_file, 'rb');
    1156             while (!feof($handle)) {
    1157                 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    1158                 echo fread($handle, 1000);
    1159             }
    1160 
    1161             die();
    1162         } else {
    1163             die('No debug file found');
    1164         }
    1165     }
    1166 
    1167     private function defineConstants($is_logged_in = false)
    1168     {
    1169         if (!defined('LINGUISE_MANAGEMENT')) {
    1170             define('LINGUISE_MANAGEMENT', 1);
    1171         }
    1172 
    1173         if ($is_logged_in && !defined('LINGUISE_AUTHORIZED')) {
    1174             define('LINGUISE_AUTHORIZED', 1);
    1175         }
    1176 
    1177         // Silent debug noises
    1178         $request = Request::getInstance(true);
    1179         $base_dir = rtrim($request->getBaseDir(), '/');
    1180         if (!defined('LINGUISE_BASE_URL')) {
    1181             define('LINGUISE_BASE_URL', $base_dir . '/linguise');
    1182         }
     463        HttpResponse::successJSON(true, 'Configuration updated successfully', 200);
    1183464    }
    1184465
     
    1226507
    1227508        if ($response_code !== 200) {
    1228             $this->errorJSON('Invalid JWT verification token: ' . print_r($response_code, true), 403);
     509            HttpResponse::errorJSON('Invalid JWT verification token: ' . print_r($response_code, true), 403);
    1229510        }
    1230511
     
    1233514    }
    1234515
    1235     private function getRemoteData($token)
     516    public function getRemoteData($token)
    1236517    {
    1237518        // Config get is api.linguise.com/api/config
     
    1333614    }
    1334615
    1335     public function logout()
    1336     {
    1337         // Verify session
    1338         $sess = Session::getInstance()->start();
    1339         // Verify session
    1340         if ($sess->hasSession()) {
    1341             // Unset session
    1342             $sess->unsetSession();
    1343             $this->successJSON(true, 'Logout successful', 200);
    1344         } else {
    1345             // No session, we just redirect to login page
    1346             $this->successJSON(true, 'No session found', 200);
    1347             exit;
    1348         }
    1349     }
    1350 
    1351616    private function getApiRoot()
    1352617    {
     
    1369634        return $protocol . '://' . $api_host . (in_array($api_port, $api_port_base) ? '' : ':' . $api_port) . '';
    1370635    }
    1371 
    1372     public function rejectGET()
    1373     {
    1374         header('Content-Type: text/html; charset=utf-8');
    1375         http_response_code(403);
    1376         echo '<h1>403 Forbidden</h1>';
    1377         echo '<p>You are not allowed to access this page.</p>';
    1378         die();
    1379     }
    1380 
    1381     private function unknownGETAction()
    1382     {
    1383         header('Content-Type: text/html; charset=utf-8');
    1384         http_response_code(400);
    1385         echo '<h1>400 Bad Request</h1>';
    1386         echo '<p>Unknown action.</p>';
    1387         die();
    1388     }
    1389 
    1390     /**
    1391      * Convert a boolean value to an integer (0 or 1)
    1392      *
    1393      * @param mixed $value The value to convert
    1394      *
    1395      * @return mixed The converted value (0 or 1), or original
    1396      */
    1397     private function boolInt($value)
    1398     {
    1399         if (is_bool($value)) {
    1400             return $value ? 1 : 0;
    1401         }
    1402 
    1403         return $value;
    1404     }
    1405 
    1406     /**
    1407      * Convert an integer (0 or 1) to a boolean value
    1408      *
    1409      * @param mixed $value The value to convert
    1410      *
    1411      * @return mixed The converted value (true or false), or original
    1412      */
    1413     private function intBool($value)
    1414     {
    1415         if (is_bool($value)) {
    1416             return $value;
    1417         }
    1418         if (is_int($value)) {
    1419             return $value === 1;
    1420         }
    1421 
    1422         return $value;
    1423     }
    1424636}
  • linguise/trunk/linguise.php

    r3350018 r3354997  
    55 * Plugin URI: https://www.linguise.com/
    66 * Description: Linguise translation plugin
    7  * Version:2.1.66
     7 * Version:2.1.67
    88 * Text Domain: linguise
    99 * Domain Path: /languages
  • linguise/trunk/readme.txt

    r3350018 r3354997  
    44Requires at least: 4.0
    55Tested up to: 6.8
    6 Stable tag:2.1.66
     6Stable tag:2.1.67
    77Requires PHP: 7.0
    88License: GPLv2 or later
     
    104104
    105105== Changelog ==
     106= 2.1.67 =
     107- Fix: WooCommerce Stripe Gateway Issue: the credit card form not showing up
     108
    106109= 2.1.66 =
    107110- Fix: Double slash in redirect URL
  • linguise/trunk/src/FragmentHandler.php

    r3339453 r3354997  
    206206            'key' => 'paymentMethodData.*?stripe\.plugin_url',
    207207            'mode' => 'regex_full',
     208            'kind' => 'deny',
     209        ],
     210        [
     211            'key' => 'currency',
     212            'mode' => 'exact',
    208213            'kind' => 'deny',
    209214        ],
     
    12921297
    12931298            $replaced_json = $json_data->getJson();
     1299
     1300            if (function_exists('apply_filters')) {
     1301                $replaced_json = apply_filters('linguise_after_apply_translated_fragments_override', $fragment_name, $replaced_json);
     1302            }
     1303
    12941304            if ($should_encode) {
    12951305                $replaced_json = rawurlencode($replaced_json);
     
    13921402
    13931403                $replaced_json = self::applyTranslatedFragmentsForAuto(json_decode('{' . $html_matches[3] . '}', true), $fragment_list['fragments']);
     1404
     1405                if (function_exists('apply_filters')) {
     1406                    $replaced_json = apply_filters('linguise_after_apply_translated_fragments_auto', $fragment_name, $replaced_json);
     1407                }
     1408
    13941409                if ($replaced_json === false) {
    13951410                    throw new \LogicException('FragmentHandler -> Injection -> ' . $fragment_name . '/' . $fragment_param . ' -> JSON data is empty!');
  • linguise/trunk/src/constants.php

    r3350018 r3354997  
    11<?php
    22if (!defined('LINGUISE_SCRIPT_TRANSLATION_VERSION')) {
    3     define('LINGUISE_SCRIPT_TRANSLATION_VERSION', 'wordpress_plugin/2.1.66');
     3    define('LINGUISE_SCRIPT_TRANSLATION_VERSION', 'wordpress_plugin/2.1.67');
    44}
    55
    66if (!defined('LINGUISE_VERSION')) {
    7     define('LINGUISE_VERSION', '2.1.66');
     7    define('LINGUISE_VERSION', '2.1.67');
    88}
  • linguise/trunk/src/thirdparty/wc/gateway-stripe.php

    r3291510 r3354997  
    2222
    2323    /**
     24     * A collection of fragment keys that will be translated
     25     *
     26     * @var array{key:string, mode:'exact'|'path'|'wildcard'|'regex'|'regex_full',kind:'allow'|'deny'}
     27     */
     28    protected static $fragment_keys = [
     29        [
     30            'key' => 'allowed_shipping_countries\..*',
     31            'mode' => 'regex_full',
     32            'kind' => 'deny',
     33        ],
     34        [
     35            'key' => 'checkout\.((?:country|currency)_code|needs_shipping)$',
     36            'mode' => 'regex_full',
     37            'kind' => 'deny',
     38        ],
     39        [
     40            'key' => 'baseLocation\.(country|state)$',
     41            'mode' => 'regex_full',
     42            'kind' => 'deny',
     43        ],
     44        [
     45            'key' => 'blocksAppearance\.rules\..*',
     46            'mode' => 'regex_full',
     47            'kind' => 'deny',
     48        ]
     49    ];
     50
     51    /**
    2452     * Decides if the integration should be loaded.
    2553     *
     
    4270        // Block checkout
    4371        add_filter('wc_stripe_params', [$this, 'hookTranslateParams'], 10, 1);
     72
     73        add_filter('linguise_after_apply_translated_fragments_auto', [$this, 'restoreOriginalConfigClassicCheckout'], 10, 2);
     74        add_filter('linguise_after_apply_translated_fragments_override', [$this, 'restoreOriginalConfigBlockCheckout'], 10, 2);
    4475    }
    4576
     
    5586        // Block checkout
    5687        remove_filter('wc_stripe_params', [$this, 'hookTranslateParams'], 10);
     88
     89        remove_filter('linguise_after_apply_translated_fragments_auto', [$this, 'restoreOriginalConfigClassicCheckout'], 10, 2);
     90        remove_filter('linguise_after_apply_translated_fragments_override', [$this, 'restoreOriginalConfigBlockCheckout'], 10, 2);
    5791    }
    5892
     
    149183        return $params;
    150184    }
     185
     186    /**
     187     * Restore the original block appearance structure that was accidentally
     188     * changed into an array
     189     *
     190     * @param string $fragment_name The name of the fragment being translated.
     191     * @param array  $replaced_json The fragment being translated.
     192     *
     193     * @return array The original block appearance structure.
     194     */
     195    public function restoreOriginalConfigClassicCheckout($fragment_name, $replaced_json)
     196    {
     197        if ($fragment_name === 'wc-stripe-upe-classic') {
     198            if (array_key_exists('blocksAppearance', $replaced_json)) {
     199                $replaced_json['blocksAppearance'] = $this->arrayToObject($replaced_json['blocksAppearance']);
     200            }
     201        }
     202
     203        return $replaced_json;
     204    }
     205
     206    /**
     207     * Restore the original block appearance structure that was accidentally
     208     * changed into an array
     209     *
     210     * @param string $fragment_name The name of the fragment being translated.
     211     * @param string $replaced_json The fragment being translated.
     212     *
     213     * @return array The original block appearance structure.
     214     */
     215    public function restoreOriginalConfigBlockCheckout($fragment_name, $replaced_json)
     216    {
     217        $replaced_json = json_decode($replaced_json);
     218        if ($fragment_name === 'wc-settings-encoded') {
     219            if (isset($replaced_json->paymentMethodData->stripe->blocksAppearance)) {
     220                $rules = $replaced_json->paymentMethodData->stripe->blocksAppearance->rules;
     221                $replaced_json->paymentMethodData->stripe->blocksAppearance->rules = $this->arrayToObject($rules);
     222            }
     223        }
     224
     225        return json_encode($replaced_json);
     226    }
     227
     228    /**
     229     * Recursive convert an array into an object.
     230     *
     231     * @param array $data The data to convert.
     232     *
     233     * @return object The converted object.
     234     */
     235    public function arrayToObject($data)
     236    {
     237        if (is_array($data)) {
     238            if (empty($data)) {
     239                return new \stdClass(); // convert empty array to object
     240            }
     241            foreach ($data as $key => $value) {
     242                $data[$key] = $this->arrayToObject($value);
     243            }
     244            return (object) $data;
     245        } elseif ($data instanceof \stdClass) {
     246            foreach ($data as $key => $value) {
     247                $data->$key = $this->arrayToObject($value);
     248            }
     249            return $data;
     250        }
     251
     252        return $data;
     253    }
    151254}
  • linguise/trunk/vendor/composer/autoload_classmap.php

    r3316163 r3354997  
    3939    'Linguise\\Vendor\\Linguise\\Script\\Core\\Helper' => $vendorDir . '/linguise/script-php/src/Helper.php',
    4040    'Linguise\\Vendor\\Linguise\\Script\\Core\\Hook' => $vendorDir . '/linguise/script-php/src/Hook.php',
     41    'Linguise\\Vendor\\Linguise\\Script\\Core\\HttpResponse' => $vendorDir . '/linguise/script-php/src/HttpResponse.php',
    4142    'Linguise\\Vendor\\Linguise\\Script\\Core\\JsonWalker' => $vendorDir . '/linguise/script-php/src/JsonWalker.php',
    4243    'Linguise\\Vendor\\Linguise\\Script\\Core\\Management' => $vendorDir . '/linguise/script-php/src/Management.php',
     44    'Linguise\\Vendor\\Linguise\\Script\\Core\\OobeManager' => $vendorDir . '/linguise/script-php/src/OobeManager.php',
    4345    'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\OpenCart' => $vendorDir . '/linguise/script-php/src/Platforms/OpenCart.php',
    4446    'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\PrestaShop' => $vendorDir . '/linguise/script-php/src/Platforms/PrestaShop.php',
  • linguise/trunk/vendor/composer/autoload_static.php

    r3321566 r3354997  
    107107        'Linguise\\Vendor\\Linguise\\Script\\Core\\Helper' => __DIR__ . '/..' . '/linguise/script-php/src/Helper.php',
    108108        'Linguise\\Vendor\\Linguise\\Script\\Core\\Hook' => __DIR__ . '/..' . '/linguise/script-php/src/Hook.php',
     109        'Linguise\\Vendor\\Linguise\\Script\\Core\\HttpResponse' => __DIR__ . '/..' . '/linguise/script-php/src/HttpResponse.php',
    109110        'Linguise\\Vendor\\Linguise\\Script\\Core\\JsonWalker' => __DIR__ . '/..' . '/linguise/script-php/src/JsonWalker.php',
    110111        'Linguise\\Vendor\\Linguise\\Script\\Core\\Management' => __DIR__ . '/..' . '/linguise/script-php/src/Management.php',
     112        'Linguise\\Vendor\\Linguise\\Script\\Core\\OobeManager' => __DIR__ . '/..' . '/linguise/script-php/src/OobeManager.php',
    111113        'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\OpenCart' => __DIR__ . '/..' . '/linguise/script-php/src/Platforms/OpenCart.php',
    112114        'Linguise\\Vendor\\Linguise\\Script\\Core\\Platforms\\PrestaShop' => __DIR__ . '/..' . '/linguise/script-php/src/Platforms/PrestaShop.php',
  • linguise/trunk/vendor/composer/installed.json

    r3349428 r3354997  
    5757        {
    5858            "name": "linguise/script-php",
    59             "version": "v1.3.32",
    60             "version_normalized": "1.3.32.0",
     59            "version": "v1.3.33",
     60            "version_normalized": "1.3.33.0",
    6161            "source": {
    6262                "type": "git",
    6363                "url": "git@bitbucket.org:linguise/script-php.git",
    64                 "reference": "031e108bbc9e337b0a36a813404a35217b556cac"
     64                "reference": "9e65bd71800e1ae003c6929b23c2d24de167f7c5"
    6565            },
    6666            "require": {
     
    7373                "phpunit/phpunit": "^9"
    7474            },
    75             "time": "2025-07-29T08:11:46+00:00",
     75            "time": "2025-08-27T05:12:16+00:00",
    7676            "type": "library",
    7777            "installation-source": "source",
  • linguise/trunk/vendor/composer/installed.php

    r3350018 r3354997  
    44        'pretty_version' => 'dev-master',
    55        'version' => 'dev-master',
    6         'reference' => '2b6bbeb52204d2861b41ccaef60b7dcdce8e6193',
     6        'reference' => 'a2c6891d602a1aa5ffa31d073b38f968aa1199d0',
    77        'type' => 'library',
    88        'install_path' => __DIR__ . '/../../',
     
    2121        ),
    2222        'linguise/script-php' => array(
    23             'pretty_version' => 'v1.3.32',
    24             'version' => '1.3.32.0',
    25             'reference' => '031e108bbc9e337b0a36a813404a35217b556cac',
     23            'pretty_version' => 'v1.3.33',
     24            'version' => '1.3.33.0',
     25            'reference' => '9e65bd71800e1ae003c6929b23c2d24de167f7c5',
    2626            'type' => 'library',
    2727            'install_path' => __DIR__ . '/../linguise/script-php',
     
    3232            'pretty_version' => 'dev-master',
    3333            'version' => 'dev-master',
    34             'reference' => '2b6bbeb52204d2861b41ccaef60b7dcdce8e6193',
     34            'reference' => 'a2c6891d602a1aa5ffa31d073b38f968aa1199d0',
    3535            'type' => 'library',
    3636            'install_path' => __DIR__ . '/../../',
  • linguise/trunk/vendor/linguise/script-php/.version

    r3339453 r3354997  
    1 1.3.32
     11.3.33
  • linguise/trunk/vendor/linguise/script-php/src/Helper.php

    r3324083 r3354997  
    243243     */
    244244    public static function transformToLocalConfig($local_config, $remote_config) {
     245        /**
     246         * If API-JS return language setting null, just return local config
     247         */
     248        if (!isset($remote_config['language_settings']) || is_null($remote_config['language_settings']) || empty($remote_config['language_settings'])) {
     249            return $local_config;
     250        }
     251
    245252        $language_settings = $remote_config['language_settings'];
    246253        $local_config['flag_display_type'] = $language_settings['display'];
     
    282289        return $local_config;
    283290    }
     291
     292    /**
     293     * Convert a boolean value to an integer (0 or 1)
     294     *
     295     * @param mixed $value The value to convert
     296     *
     297     * @return mixed The converted value (0 or 1), or original
     298     */
     299    static function boolInt($value)
     300    {
     301        if (is_bool($value)) {
     302            return $value ? 1 : 0;
     303        }
     304
     305        return $value;
     306    }
     307
     308    /**
     309     * Convert an integer (0 or 1) to a boolean value
     310     *
     311     * @param mixed $value The value to convert
     312     *
     313     * @return mixed The converted value (true or false), or original
     314     */
     315    static function intBool($value)
     316    {
     317        if (is_bool($value)) {
     318            return $value;
     319        }
     320        if (is_int($value)) {
     321            return $value === 1;
     322        }
     323
     324        return $value;
     325    }
     326
     327    static function defineConstants($is_logged_in = false)
     328    {
     329        if (!defined('LINGUISE_MANAGEMENT')) {
     330            define('LINGUISE_MANAGEMENT', 1);
     331        }
     332
     333        if ($is_logged_in && !defined('LINGUISE_AUTHORIZED')) {
     334            define('LINGUISE_AUTHORIZED', 1);
     335        }
     336
     337        // Silent debug noises
     338        $request = Request::getInstance(true);
     339        $base_dir = rtrim($request->getBaseDir(), '/');
     340        if (!defined('LINGUISE_BASE_URL')) {
     341            define('LINGUISE_BASE_URL', $base_dir . '/linguise');
     342        }
     343    }
    284344}
  • linguise/trunk/vendor/linguise/script-php/src/Management.php

    r3339453 r3354997  
    44
    55defined('LINGUISE_SCRIPT_TRANSLATION') or die();
     6use Linguise\Vendor\Linguise\Script\Core\HttpResponse;
    67
    78class Management {
     
    1213
    1314    /**
    14      * @var string
    15      */
    16     private static $token_key = 'linguise_token';
    17     /**
    1815     * Mapping data from the API to the local config
    1916     *
    2017     * @var array
    2118     */
    22     private static $flag_options_map = [
     19    public static $flag_options_map = [
    2320        'display' => 'flag_display_type',
    2421        'position' => 'display_position',
     
    4946    }
    5047
    51     public function run($html_message = \null, $api_web_errors = []) {
    52         // Start session
    53         $sess = Session::getInstance()->start();
    54         // Set our CSRF token, always overrides
    55         $sess->generateCsrfToken();
    56 
    57         // We define this constant so user can't do direct access to the template
    58         $this->defineConstants(false);
    59 
    60         if (isset($_GET['linguise_action'])) {
    61             switch ($_GET['linguise_action']) {
    62                 case 'download-debug':
    63                     $this->downloadDebug();
    64                     break;
    65                 case 'update-config':
    66                     break;
    67                 default:
    68                     if (empty($_SESSION[self::$token_key])) {
    69                         $this->rejectGET();
    70                     } else {
    71                         $this->unknownGETAction();
    72                     }
    73                     break;
    74             }
    75         }
    76 
    77         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'Helper.php';
    78         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
    79 
    80         if (!$sess->hasSession()) {
    81             // Render login page
    82             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    83             if (!empty($html_message)) {
    84                 echo $html_message;
    85             }
    86             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'oobe.php';
    87         } else {
    88             // Logged in? we send it!
    89             $this->defineConstants(true);
    90             require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    91             if (!empty($html_message)) {
    92                 echo $html_message;
    93             }
    94             $view_mode = isset($_GET['ling_mode']) ? $_GET['ling_mode'] : 'default';
    95             if ($view_mode === 'expert') {
    96                 require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'management-expert.php';
    97             } else {
    98                 require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'management.php';
    99             }
    100         }
    101         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'footer.php';
    102         die();
    103     }
    104 
    105     public function oobeRun($html_message = \null) {
    106         // Start session
    107         $sess = Session::getInstance()->start();
    108         // Set our CSRF token, always overrides
    109         $sess->generateCsrfToken();
    110 
    111         $this->defineConstants(false);
    112 
    113         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
    114         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'header.php';
    115         if (!empty($html_message)) {
    116             echo $html_message;
    117         }
    118         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'oobe.php';
    119         require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'footer.php';
    120     }
    121 
    12248    public function editorRun() {
    123         $this->defineConstants(false);
     49        Helper::defineConstants(false);
    12450
    12551        require_once LINGUISE_BASE_DIR . 'src' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'stubs.php';
     
    13965        if (!$sess->hasSession()) {
    14066            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Not authorized</div>';
    141             $this->run($message, $api_web_errors);
     67            $oobe = OobeManager::getInstance();
     68            $oobe->run($message, $api_web_errors);
    14269        }
    14370        if (!isset($_POST['_token'])) {
    144             $this->errorJSON('Missing CSRF token', 400);
     71            HttpResponse::errorJSON('Missing CSRF token', 400);
    14572            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Missing CSRF token</div>';
    146             $this->run($message, $api_web_errors);
     73            $oobe = OobeManager::getInstance();
     74            $oobe->run($message, $api_web_errors);
    14775        }
    14876        if (!$sess->verifyCsrfToken('linguise_config', $_POST['_token'])) {
    14977            $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid CSRF token</div>';
    150             $this->run($message, $api_web_errors);
     78            $oobe = OobeManager::getInstance();
     79            $oobe->run($message, $api_web_errors);
    15180        }
    15281
     
    187116                    $dynamic_translations['public_key'] = $api_result['data']['public_key'];
    188117                    $linguise_options = Helper::transformToLocalConfig($linguise_options, $api_result['data']);
    189                     $dynamic_translations['enabled'] = $this->boolInt($api_result['data']['dynamic_translations']['enabled']);
     118                    $dynamic_translations['enabled'] = Helper::boolInt($api_result['data']['dynamic_translations']['enabled']);
    190119                    $token_changed = true;
    191120                } else if ($api_result !== false && isset($api_result['status_code'])) {
     
    234163                    if ($token_changed) {
    235164                        // If token changed, we update enabled state
    236                         $dynamic_translations['enabled'] = $this->boolInt($api_result['data']['dynamic_translations']['enabled']);
     165                        $dynamic_translations['enabled'] = Helper::boolInt($api_result['data']['dynamic_translations']['enabled']);
    237166                    }
    238167                } else if ($api_result !== false && isset($api_result['status_code'])) {
     
    277206                'display_position' => isset($linguise_options['display_position']) ? $linguise_options['display_position'] : 'no',
    278207                'flag_display_type' => isset($linguise_options['flag_display_type']) ? $linguise_options['flag_display_type'] : 'popup',
    279                 'enable_flag' => $this->boolInt($enable_flag),
    280                 'enable_language_name' => $this->boolInt($enable_language_name),
    281                 'enable_language_name_popup' => $this->boolInt($enable_language_name_popup),
    282                 'enable_language_short_name' => $this->boolInt($enable_language_short_name),
     208                'enable_flag' => Helper::boolInt($enable_flag),
     209                'enable_language_name' => Helper::boolInt($enable_language_name),
     210                'enable_language_name_popup' => Helper::boolInt($enable_language_name_popup),
     211                'enable_language_short_name' => Helper::boolInt($enable_language_short_name),
    283212                'language_name_display' => isset($linguise_options['language_name_display']) ? $linguise_options['language_name_display'] : 'en',
    284213                'flag_shape' => isset($linguise_options['flag_shape']) ? $linguise_options['flag_shape'] : 'rounded',
     
    321250                    'language' => $default_language,
    322251                    'allowed_languages' => $translate_languages,
    323                     'dynamic_translations' => $this->intBool($dynamic_translations['enabled']),
     252                    'dynamic_translations' => Helper::intBool($dynamic_translations['enabled']),
    324253                    'language_settings' => [
    325254                        // Flag related
    326255                        'display' => $linguise_options['flag_display_type'],
    327256                        'position' => $linguise_options['display_position'],
    328                         'enabled_flag' => $this->intBool($enable_flag),
    329                         'enabled_lang_name' => $this->intBool($enable_language_name),
    330                         'enabled_lang_name_popup' => $this->intBool($enable_language_name_popup),
    331                         'enabled_lang_short_name' => $this->intBool($enable_language_short_name),
     257                        'enabled_flag' => Helper::intBool($enable_flag),
     258                        'enabled_lang_name' => Helper::intBool($enable_language_name),
     259                        'enabled_lang_name_popup' => Helper::intBool($enable_language_name_popup),
     260                        'enabled_lang_short_name' => Helper::intBool($enable_language_short_name),
    332261                        'lang_name_display' => $linguise_options['language_name_display'],
    333262                        'flag_shape' => $linguise_options['flag_shape'],
     
    410339
    411340        // Re-render
    412         $this->run($notification_popup_msg, $api_web_errors);
     341        $oobe = OobeManager::getInstance();
     342        $oobe->run($notification_popup_msg, $api_web_errors);
    413343    }
    414344
     
    418348        $sess = Session::getInstance()->start();
    419349        if (!$sess->hasSession()) {
    420             $this->errorJSON('Unauthorized', 401);
     350            HttpResponse::errorJSON('Unauthorized', 401);
    421351        }
    422352        if (!isset($_POST['nonce'])) {
    423             $this->errorJSON('Missing nonce token', 400);
     353            HttpResponse::errorJSON('Missing nonce token', 400);
    424354        }
    425355        if (!$sess->verifyCsrfToken('linguise_config_iframe', $_POST['nonce'])) {
    426             $this->errorJSON('Invalid nonce token', 403);
     356            HttpResponse::errorJSON('Invalid nonce token', 403);
    427357        }
    428358
     
    441371            if (!isset($data[$field])) {
    442372                // response with error
    443                 $this->errorJSON('Missing required field: ' . $field, 400);
     373                HttpResponse::errorJSON('Missing required field: ' . $field, 400);
    444374            }
    445375        }
     
    475405
    476406        $db->saveOtherParam('linguise_options', $options);
    477         $this->successJSON(true, 'Configuration updated successfully', 200);
     407        HttpResponse::successJSON(true, 'Configuration updated successfully', 200);
    478408    }
    479409
     
    484414        $jwt_token = isset($_SERVER['HTTP_X_LINGUISE_HASH']) ? $_SERVER['HTTP_X_LINGUISE_HASH'] : null;
    485415        if (empty($jwt_token)) {
    486             $this->errorJSON('Missing Hash header', 400);
     416            HttpResponse::errorJSON('Missing Hash header', 400);
    487417        }
    488418
     
    490420        $input_data = file_get_contents('php://input');
    491421        if (empty($input_data)) {
    492             $this->errorJSON('Invalid request', 400);
     422            HttpResponse::errorJSON('Invalid request', 400);
    493423        }
    494424
    495425        $input_data = json_decode($input_data, true);
    496426        if (json_last_error() !== JSON_ERROR_NONE) {
    497             $this->errorJSON('Invalid JSON data', 400);
     427            HttpResponse::errorJSON('Invalid JSON data', 400);
    498428        }
    499429
     
    503433
    504434        if ($input_data['token'] !== $options['token']) {
    505             $this->errorJSON('Invalid token', 401);
     435            HttpResponse::errorJSON('Invalid token', 401);
    506436        }
    507437
     
    525455                    $flag_real_key = self::$flag_options_map[$flag_key];
    526456                }
    527                 $options[$flag_real_key] = $this->boolInt($flag_value);
     457                $options[$flag_real_key] = Helper::boolInt($flag_value);
    528458            }
    529459        }
     
    531461        // Save
    532462        $db->saveOtherParam('linguise_options', $options);
    533         $this->successJSON(true, 'Configuration updated successfully', 200);
    534     }
    535 
    536     public function login()
    537     {
    538         if (!isset($_POST['_token'])) {
    539             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Missing CSRF token</div>';
    540             $this->oobeRun($message);
    541         }
    542         $sess = Session::getInstance()->start();
    543         if (!$sess->verifyCsrfToken('linguise_oobe_login', $_POST['_token'])) {
    544             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid CSRF token</div>';
    545             $this->oobeRun($message);
    546         }
    547 
    548         // Authenticate ourself, get the token or password
    549         if ($sess->oobeComplete() && !empty($_POST['password'])) {
    550             $existing_password = Database::getInstance()->ensureConnection()->retrieveOtherParam('linguise_password');
    551             if ($existing_password && password_verify($_POST['password'], $existing_password)) {
    552                 // Set session
    553                 $sess->setSession($existing_password, true);
    554                 $this->run(\null, []);
    555             } else {
    556                 $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid password</div>';
    557                 $this->oobeRun($message);
    558             }
    559         } else {
    560             $sess->unsetSession();
    561             $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>Invalid request</div>';
    562             $this->oobeRun($message);
    563         }
    564     }
    565 
    566     public function mergeConfig($skip_missing = \false)
    567     {
    568         /**
    569          * @disregard P1011 - already checked
    570          */
    571         $use_mysql = defined('LINGUISE_OOBE_MYSQL') && LINGUISE_OOBE_MYSQL;
    572 
    573         // Each config for the mysql, LINGUISE_OOBE_MYSQL_DB_{HOST|USER|PASSWORD|NAME|PREFIX|PORT}
    574         $oobe_config = [
    575             'DB_HOST',
    576             'DB_USER',
    577             'DB_PASSWORD',
    578             'DB_NAME',
    579             'DB_PREFIX',
    580             'DB_PORT',
    581             'DB_FLAGS',
    582         ];
    583 
    584         if ($use_mysql) {
    585             foreach ($oobe_config as $config) {
    586                 if (defined('LINGUISE_OOBE_MYSQL_' . $config)) {
    587                     Configuration::getInstance()->set(strtolower($config), constant('LINGUISE_OOBE_MYSQL_' . $config));
    588                 }
    589             }
    590         }
    591 
    592         // Connect to database and install options
    593         Helper::prepareDataDir();
    594         $db = Database::getInstance(true);
    595 
    596         if (!LINGUISE_OOBE_DONE) {
    597             // Not yet ready
    598             return;
    599         }
    600 
    601         $db->ensureConnection()->installOptions();
    602 
    603         // Get metadata
    604         $existing_options = $db->retrieveOtherParam('linguise_options');
    605         if (!empty($existing_options)) {
    606             // Merge existing options
    607             Configuration::getInstance()->set('token', $existing_options['token']);
    608             Configuration::getInstance()->set('cache_enabled', $existing_options['cache_enabled']);
    609             Configuration::getInstance()->set('cache_max_size', $existing_options['cache_max_size']);
    610             Configuration::getInstance()->set('search_translations', $existing_options['search_translations']);
    611             Configuration::getInstance()->set('debug', $existing_options['debug']);
    612 
    613             foreach ($existing_options['expert_mode'] as $key => $value) {
    614                 Configuration::getInstance()->set($key, $value);
    615             }
    616         } else {
    617             if ($skip_missing) {
    618                 // Skip if missing
    619                 return;
    620             }
    621 
    622             // We create new options
    623             $current_token = Configuration::getInstance()->get('token');
    624             if ($current_token === 'REPLACE_BY_YOUR_TOKEN') {
    625                 $current_token = '';
    626             }
    627             $this->createOptionsWithToken($current_token);
    628         }
    629     }
    630 
    631     private function oobeRunError($message_str, $status_code = 200)
    632     {
    633         $message = '<div class="linguise-notification-popup"><span class="material-icons fail">check</span>' . $message_str . '</div>';
    634         http_response_code($status_code);
    635         $this->oobeRun($message);
    636         die();
    637     }
    638 
    639     public function activateLinguise()
    640     {
    641         // Check if OOBE
    642         if (defined('LINGUISE_OOBE_DONE') && LINGUISE_OOBE_DONE) {
    643             $this->oobeRunError('Not allowed', 403);
    644         } elseif (!defined('LINGUISE_OOBE_DONE')) {
    645             // Missing data
    646             $this->oobeRunError('Unknown status', 500);
    647         }
    648 
    649         if (!isset($_POST['_token'])) {
    650             $this->oobeRunError('Missing CSRF token', 400);
    651         }
    652         $sess = Session::getInstance()->start();
    653         if (!$sess->verifyCsrfToken('linguise_oobe_register', $_POST['_token'])) {
    654             $this->oobeRunError('Invalid CSRF token', 403);
    655         }
    656 
    657         $new_pass = isset($_POST['password']) ? $_POST['password'] : null;
    658         if (empty($new_pass)) {
    659             $this->oobeRunError('Missing password', 400);
    660         }
    661 
    662         // == Password check ==
    663         // Must be at least 10 characters long
    664         if (strlen($new_pass) < 10) {
    665             $this->oobeRunError('Password must be at least 10 characters long', 400);
    666         }
    667 
    668         if ($sess->hasSession()) {
    669             $this->oobeRunError('Already activated', 403);
    670         }
    671 
    672         $is_token = defined('LINGUISE_OOBE_TOKEN_EXIST') && LINGUISE_OOBE_TOKEN_EXIST;
    673         if ($is_token) {
    674             if (!isset($_POST['token'])) {
    675                 $this->oobeRunError('Missing token', 400);
    676             }
    677 
    678             $token = $_POST['token'];
    679             if ($token !== Configuration::getInstance()->get('token')) {
    680                 $this->oobeRunError('Invalid token provided in session', 401);
    681             }
    682 
    683             // This will automatically return error response
    684             $database_store = $this->storeDatabaseConnection(false);
    685             // set configuration
    686             if ($database_store['MYSQL']) {
    687                 Configuration::getInstance()->set('db_host', $database_store['MYSQL_DB_HOST']);
    688                 Configuration::getInstance()->set('db_user', $database_store['MYSQL_DB_USER']);
    689                 Configuration::getInstance()->set('db_password', $database_store['MYSQL_DB_PASSWORD']);
    690                 Configuration::getInstance()->set('db_name', $database_store['MYSQL_DB_NAME']);
    691                 Configuration::getInstance()->set('db_prefix', $database_store['MYSQL_DB_PREFIX']);
    692                 Configuration::getInstance()->set('db_port', $database_store['MYSQL_DB_PORT']);
    693             } else {
    694                 // SQLite
    695                 Configuration::getInstance()->set('db_host', '');
    696                 Configuration::getInstance()->set('db_user', '');
    697                 Configuration::getInstance()->set('db_password', '');
    698                 Configuration::getInstance()->set('db_name', '');
    699                 Configuration::getInstance()->set('db_prefix', '');
    700 
    701                 $sqlite_test = $this->prepareRootDatabaseSQLite();
    702                 if ($sqlite_test !== true) {
    703                     $this->oobeRunError($sqlite_test, 500);
    704                 }
    705             }
    706 
    707             // Init database
    708             Helper::prepareDataDir();
    709             $db = Database::getInstance(true, true)->ensureConnection();
    710             $db->installOptions();
    711 
    712             // Valid, then let's set the password
    713             $hashed_pass = $this->hashPassword($new_pass);
    714             $db->saveOtherParam('linguise_password', $hashed_pass);
    715             // Create the options
    716             $db_options = $this->createOptionsWithToken($token);
    717 
    718             // Write OOBE config
    719             $this->writeOOBE($database_store);
    720 
    721             $api_result = $this->getRemoteData($token);
    722             if ($api_result !== false && isset($api_result['data'])) {
    723                 $default_language = Helper::sanitizeKey($api_result['data']['language']);
    724 
    725                 $translation_languages = $api_result['data']['languages'];
    726                 $translate_languages = [];
    727                 if (!empty($translation_languages)) {
    728                     foreach ($translation_languages as $translation_language) {
    729                         $translate_languages[] = Helper::sanitizeKey($translation_language['code']);
    730                     }
    731                 }
    732 
    733                 $db_options['enabled_languages'] = $translate_languages;
    734                 $db_options['default_language'] = $default_language;
    735                 $db_options['dynamic_translations']['enabled'] = $this->intBool($api_result['data']['dynamic_translations']['enabled']);
    736                 $db_options['dynamic_translations']['public_key'] = $api_result['data']['public_key'];
    737 
    738                 if (!empty($api_result['data']['language_settings'])) {
    739                     foreach ($api_result['data']['language_settings'] as $flag_key => $flag_value) {
    740                         $flag_real_key = $flag_key;
    741                         if (isset(self::$flag_options_map[$flag_key])) {
    742                             $flag_real_key = self::$flag_options_map[$flag_key];
    743                         }
    744                         $db_options[$flag_real_key] = $this->boolInt($flag_value);
    745                     }
    746                 }
    747 
    748                 // Re-save with API data
    749                 $db->saveOtherParam('linguise_options', $db_options);
    750             }
    751 
    752             // Set session, and login the user.
    753             $sess->setOobeForced();
    754             $sess->setSession($hashed_pass, true); // Set session with password mode
    755             $this->run();
    756             die();
    757         } else {
    758             // This will automatically return error response
    759             $database_store = $this->storeDatabaseConnection(false);
    760             // set configuration
    761             if ($database_store['MYSQL']) {
    762                 Configuration::getInstance()->set('db_host', $database_store['MYSQL_DB_HOST']);
    763                 Configuration::getInstance()->set('db_user', $database_store['MYSQL_DB_USER']);
    764                 Configuration::getInstance()->set('db_password', $database_store['MYSQL_DB_PASSWORD']);
    765                 Configuration::getInstance()->set('db_name', $database_store['MYSQL_DB_NAME']);
    766                 Configuration::getInstance()->set('db_prefix', $database_store['MYSQL_DB_PREFIX']);
    767                 Configuration::getInstance()->set('db_port', $database_store['MYSQL_DB_PORT']);
    768             } else {
    769                 // SQLite
    770                 Configuration::getInstance()->set('db_host', '');
    771                 Configuration::getInstance()->set('db_user', '');
    772                 Configuration::getInstance()->set('db_password', '');
    773                 Configuration::getInstance()->set('db_name', '');
    774                 Configuration::getInstance()->set('db_prefix', '');
    775 
    776                 $sqlite_test = $this->prepareRootDatabaseSQLite();
    777                 if ($sqlite_test !== true) {
    778                     $this->oobeRunError($sqlite_test, 500);
    779                 }
    780             }
    781 
    782             // Init database
    783             Helper::prepareDataDir();
    784             $db = Database::getInstance(true, true)->ensureConnection();
    785             $db->installOptions();
    786 
    787             // No token, we just set it immediately
    788             $existing_password = $db->retrieveOtherParam('linguise_password');
    789             if ($existing_password) {
    790                 $this->oobeRunError('Password already set', 400);
    791             }
    792 
    793             // Set the password
    794             $hashed_pass = $this->hashPassword($new_pass);
    795 
    796             // Create
    797             $db->saveOtherParam('linguise_password', $hashed_pass);
    798             // Create the options
    799             $this->createOptionsWithToken(''); // Empty token since no token provided yet.
    800 
    801             // Write OOBE config
    802             $this->writeOOBE($database_store);
    803 
    804 
    805             // Set session, and login the user.
    806             $sess->setOobeForced();
    807             $sess->setSession($hashed_pass, true); // Set session with password mode
    808             $this->run();
    809             die();
    810         }
    811     }
    812 
    813     public function storeDatabaseConnection($testMode = \false)
    814     {
    815         if ($testMode) {
    816             if (!isset($_POST['_token'])) {
    817                 $this->errorJSON('Missing CSRF token', 400);
    818             }
    819             $sess = Session::getInstance()->start();
    820             if (!$sess->verifyCsrfToken('linguise_oobe_register', $_POST['_token'])) {
    821                 $this->errorJSON('Invalid CSRF token', 403);
    822             }
    823         }
    824 
    825         // Get from POST data
    826         $mode = $_POST['db_mode'] ?? null;
    827         $host = $_POST['db_host'] ?? null;
    828         $user = $_POST['db_user'] ?? null;
    829         $password = $_POST['db_password'] ?? null;
    830         $name = $_POST['db_name'] ?? null;
    831         $port = $_POST['db_port'] ?? 3306;
    832         $prefix = $_POST['db_prefix'] ?? null;
    833 
    834         if (empty($mode)) {
    835             $this->errorJSON('Missing `db_mode` data', 400);
    836         }
    837 
    838         switch ($mode) {
    839             case 'mysql':
    840                 if (empty($host) || empty($user) || empty($name)) {
    841                     $this->errorJSON('Missing `db_host`, `db_user` or `db_name` data', 400);
    842                 }
    843                 if (!$testMode && (empty($prefix))) {
    844                     $this->errorJSON('Missing `db_prefix` data', 400);
    845                 }
    846 
    847                 if ($testMode) {
    848                     $result = $this->testMySQL($host, $user, $password, $name, $port, $prefix);
    849                     if ($result !== true) {
    850                         $this->errorJSON($result, 500);
    851                     }
    852                     $this->successJSON(true, 'MySQL connection test successful', 200);
    853                 }
    854 
    855                 return [
    856                     'MYSQL' => true,
    857                     'MYSQL_DB_HOST' => $host,
    858                     'MYSQL_DB_USER' => $user,
    859                     'MYSQL_DB_PASSWORD' => $password,
    860                     'MYSQL_DB_NAME' => $name,
    861                     'MYSQL_DB_PORT' => $port,
    862                     'MYSQL_DB_PREFIX' => $prefix,
    863                 ];
    864             case 'sqlite':
    865                 // Check if SQLite3 is enabled
    866                 if ($testMode) {
    867                     $result = $this->testSqlite();
    868                     if ($result !== true) {
    869                         $this->errorJSON($result, 500);
    870                     }
    871                     $this->successJSON(true, 'SQLite connection test successful', 200);
    872                 }
    873 
    874                 // Store the SQLite connection
    875                 return [
    876                     'MYSQL' => false,
    877                 ];
    878             default:
    879                 $this->errorJSON('Invalid `db_mode` data', 400);
    880         }
    881     }
    882 
    883     private function testMySQL($host, $user, $password, $name, $port = 3306, $prefix = '')
    884     {
    885         // Check if MySQLi is enabled
    886         if (!extension_loaded('mysqli')) {
    887             return 'MySQLi extension not loaded';
    888         }
    889 
    890         // Check if MySQLi class exist
    891         if (!class_exists('mysqli')) {
    892             return 'MySQLi class not found';
    893         }
    894 
    895         // Attempt to connect to the database
    896         $connection = \null;
    897         try {
    898             $connection = new \mysqli($host, $user, $password, $name, $port);
    899         } catch (\mysqli_sql_exception $e) {
    900             return 'Connection failed: ' . $e->getMessage();
    901         }
    902 
    903         if (empty($connection)) {
    904             return 'Connection failed: No connection object created';
    905         }
    906 
    907         if ($connection->connect_error) {
    908             return 'Connection failed: ' . $connection->connect_error;
    909         }
    910 
    911         // Close the connection
    912         $connection->close();
    913 
    914         return true;
    915     }
    916 
    917     private function testSqlite()
    918     {
    919         // Check if SQLite3 is enabled
    920         if (!extension_loaded('sqlite3')) {
    921             return 'SQLite3 extension not loaded';
    922         }
    923 
    924         // Check if SQLite3 class exist
    925         if (!class_exists('SQLite3')) {
    926             return 'SQLite3 class not found';
    927         }
    928 
    929         // Check if we can write in LINGUISE_BASE_DIR / .linguise-main.db
    930         $sqlite_test = $this->prepareRootDatabaseSQLite();
    931         if ($sqlite_test !== true) {
    932             return $sqlite_test;
    933         }
    934 
    935         if (!Helper::checkDataDirAvailable()) {
    936             return 'Cannot write to data directory';
    937         }
    938 
    939         return true;
    940     }
    941 
    942     private function prepareRootDatabaseSQLite()
    943     {
    944         $databases_dir = LINGUISE_BASE_DIR . '.databases' . DIRECTORY_SEPARATOR;
    945         if (!file_exists($databases_dir)) {
    946             if (!mkdir($databases_dir, 0766, true)) {
    947                 return 'Cannot create database directory: ' . $databases_dir;
    948             }
    949         }
    950 
    951         $htaccess_file = $databases_dir . '.htaccess';
    952         if (!file_exists($htaccess_file)) {
    953             $written = file_put_contents($htaccess_file, 'deny from all');
    954             if ($written === false) {
    955                 return 'Cannot write to database directory: ' . $databases_dir;
    956             }
    957         }
    958 
    959         $db_path = $databases_dir . 'linguise-main.db';
    960         if (!file_exists($db_path)) {
    961             // touch the file
    962             $db_touch = touch($db_path);
    963             if (!$db_touch) {
    964                 return 'Cannot create database file: ' . $db_path;
    965             }
    966             unlink($db_path);
    967         } else {
    968             if (!is_writable($db_path)) {
    969                 return 'Cannot write database to ' . $db_path;
    970             }
    971         }
    972 
    973         return true;
    974     }
    975 
    976     private function createOptionsWithToken($token)
    977     {
    978         $db = Database::getInstance()->ensureConnection();
    979         $options = [
    980             'token' => $token,
    981             'cache_enabled' => Configuration::getInstance()->get('cache_enabled'),
    982             'cache_max_size' => Configuration::getInstance()->get('cache_max_size'),
    983             'search_translations' => Configuration::getInstance()->get('search_translations'),
    984             'debug' => Configuration::getInstance()->get('debug'),
    985             'dynamic_translations' => [
    986                 'enabled' => 0,
    987                 'public_key' => null,
    988             ],
    989 
    990             // Languages related
    991             'default_language' => 'en',
    992             'enabled_languages' => [],
    993 
    994             // Flag related
    995             'display_position' => 'bottom_right',
    996             'flag_display_type' => 'popup',
    997             'enable_flag' => 1,
    998             'enable_language_name' => 1,
    999             'enable_language_short_name' => 0,
    1000             'language_name_display' => 'en',
    1001             'flag_shape' => 'round',
    1002             'flag_en_type' => 'en-us',
    1003             'flag_de_type' => 'de',
    1004             'flag_es_type' => 'es',
    1005             'flag_pt_type' => 'pt',
    1006             'flag_tw_type' => 'zh-tw',
    1007             'flag_border_radius' => 0,
    1008             'flag_width' => 24,
    1009             'pre_text' => '',
    1010             'post_text' => '',
    1011             'custom_css' => '',
    1012             'language_name_color' => '#222',
    1013             'language_name_hover_color' => '#222',
    1014             'popup_language_name_color' => '#222',
    1015             'popup_language_name_hover_color' => '#222',
    1016             'flag_shadow_h' => 2,
    1017             'flag_shadow_v' => 2,
    1018             'flag_shadow_blur' => 12,
    1019             'flag_shadow_spread' => 0,
    1020             'flag_shadow_color' => '#eee',
    1021             'flag_shadow_color_alpha' => (float)1.0, // we use 100% scaling, 0.0-1.0
    1022             'flag_hover_shadow_h' => 3,
    1023             'flag_hover_shadow_v' => 3,
    1024             'flag_hover_shadow_blur' => 6,
    1025             'flag_hover_shadow_spread' => 0,
    1026             'flag_hover_shadow_color' => '#bfbfbf',
    1027             'flag_hover_shadow_color_alpha' => (float)1.0,
    1028 
    1029             'expert_mode' => [],
    1030         ];
    1031 
    1032         $db->saveOtherParam('linguise_options', $options);
    1033 
    1034         return $options;
    1035     }
    1036 
    1037     private function errorJSON($message, $code = 500)
    1038     {
    1039         header('Content-Type: application/json; charset=utf-8');
    1040         http_response_code($code);
    1041         echo json_encode([
    1042             'error' => true,
    1043             'message' => $message
    1044         ]);
    1045         exit;
    1046     }
    1047 
    1048     private function successJSON($data, $message = '', $code = 200)
    1049     {
    1050         header('Content-Type: application/json; charset=utf-8');
    1051         http_response_code($code);
    1052         echo json_encode([
    1053             'error' => false,
    1054             'message' => $message,
    1055             'data' => $data
    1056         ]);
    1057         exit;
    1058     }
    1059 
    1060     private function hashPassword($input)
    1061     {
    1062         $hashed = password_hash($input, PASSWORD_BCRYPT, [
    1063             'cost' => 12,
    1064         ]);
    1065 
    1066         return $hashed;
    1067     }
    1068 
    1069     private function writeOOBE($database_store)
    1070     {
    1071         // Modify OOBE status in ui-config.php
    1072         $ui_config = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..') . DIRECTORY_SEPARATOR . 'ui-config.php';
    1073         $content = file_get_contents($ui_config);
    1074 
    1075         $replaced_content = preg_replace('/define\([\'"]LINGUISE_OOBE_DONE[\'"], .*\);/m', 'define(\'LINGUISE_OOBE_DONE\', true);', $content);
    1076         if (empty($replaced_content)) {
    1077             $this->errorJSON('Failed to update ui-config.php', 500);
    1078         }
    1079 
    1080         // Update the database connection
    1081         foreach ($database_store as $key => $value) {
    1082             // Push content
    1083             $value_wrap = json_encode($value);
    1084             $replaced_content .= "\ndefine('LINGUISE_OOBE_" . $key . "', " . $value_wrap . ");\n";
    1085         }
    1086 
    1087         file_put_contents($ui_config, $replaced_content);
    1088     }
    1089 
    1090     public function clearDebug()
    1091     {
    1092         // Verify session
    1093         $sess = Session::getInstance()->start();
    1094         if (!$sess->hasSession()) {
    1095             $this->errorJSON('Unauthorized', 401);
    1096         }
    1097         if (!isset($_GET['nonce'])) {
    1098             $this->errorJSON('Missing nonce token', 400);
    1099         }
    1100         if (!$sess->verifyCsrfToken('linguise_clear_debug', $_GET['nonce'])) {
    1101             $this->errorJSON('Invalid nonce token', 403);
    1102         }
    1103 
    1104         // Clear the debug file
    1105         $debug_file = LINGUISE_BASE_DIR . 'debug.php';
    1106         $last_errors_file = LINGUISE_BASE_DIR . 'errors.php';
    1107         if (file_exists($debug_file)) {
    1108             file_put_contents($debug_file, "<?php die(); ?>" . PHP_EOL);
    1109         }
    1110         if (file_exists($last_errors_file)) {
    1111             file_put_contents($last_errors_file, "<?php die(); ?>" . PHP_EOL);
    1112         }
    1113 
    1114         $this->successJSON(true, 'Log truncated!', 200);
    1115     }
    1116 
    1117     public function clearCache()
    1118     {
    1119         // Verify session
    1120         $sess = Session::getInstance()->start();
    1121         if (!$sess->hasSession()) {
    1122             $this->errorJSON('Unauthorized', 401);
    1123         }
    1124         if (!isset($_GET['nonce'])) {
    1125             $this->errorJSON('Missing nonce token', 400);
    1126         }
    1127         if (!$sess->verifyCsrfToken('linguise_clear_cache', $_GET['nonce'])) {
    1128             $this->errorJSON('Invalid nonce token', 403);
    1129         }
    1130 
    1131         // Clear the cache
    1132         Cache::getInstance()->clearAll();
    1133     }
    1134 
    1135     private function downloadDebug()
    1136     {
    1137         // Verify session
    1138         $sess = Session::getInstance()->start();
    1139         if (!$sess->hasSession()) {
    1140             die('Unauthorized');
    1141         }
    1142 
    1143         $debug_file = LINGUISE_BASE_DIR . 'debug.php';
    1144         if (file_exists($debug_file)) {
    1145             header('Content-Description: File Transfer');
    1146             header('Content-Type: application/octet-stream');
    1147             header('Content-Disposition: attachment; filename="debug.txt"');
    1148             header('Content-Transfer-Encoding: binary');
    1149             header('Expires: 0');
    1150             header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    1151             header('Pragma: public');
    1152             header('Content-Length: ' . filesize($debug_file));
    1153             ob_clean();
    1154             ob_end_flush();
    1155             $handle = fopen($debug_file, 'rb');
    1156             while (!feof($handle)) {
    1157                 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    1158                 echo fread($handle, 1000);
    1159             }
    1160 
    1161             die();
    1162         } else {
    1163             die('No debug file found');
    1164         }
    1165     }
    1166 
    1167     private function defineConstants($is_logged_in = false)
    1168     {
    1169         if (!defined('LINGUISE_MANAGEMENT')) {
    1170             define('LINGUISE_MANAGEMENT', 1);
    1171         }
    1172 
    1173         if ($is_logged_in && !defined('LINGUISE_AUTHORIZED')) {
    1174             define('LINGUISE_AUTHORIZED', 1);
    1175         }
    1176 
    1177         // Silent debug noises
    1178         $request = Request::getInstance(true);
    1179         $base_dir = rtrim($request->getBaseDir(), '/');
    1180         if (!defined('LINGUISE_BASE_URL')) {
    1181             define('LINGUISE_BASE_URL', $base_dir . '/linguise');
    1182         }
     463        HttpResponse::successJSON(true, 'Configuration updated successfully', 200);
    1183464    }
    1184465
     
    1226507
    1227508        if ($response_code !== 200) {
    1228             $this->errorJSON('Invalid JWT verification token: ' . print_r($response_code, true), 403);
     509            HttpResponse::errorJSON('Invalid JWT verification token: ' . print_r($response_code, true), 403);
    1229510        }
    1230511
     
    1233514    }
    1234515
    1235     private function getRemoteData($token)
     516    public function getRemoteData($token)
    1236517    {
    1237518        // Config get is api.linguise.com/api/config
     
    1333614    }
    1334615
    1335     public function logout()
    1336     {
    1337         // Verify session
    1338         $sess = Session::getInstance()->start();
    1339         // Verify session
    1340         if ($sess->hasSession()) {
    1341             // Unset session
    1342             $sess->unsetSession();
    1343             $this->successJSON(true, 'Logout successful', 200);
    1344         } else {
    1345             // No session, we just redirect to login page
    1346             $this->successJSON(true, 'No session found', 200);
    1347             exit;
    1348         }
    1349     }
    1350 
    1351616    private function getApiRoot()
    1352617    {
     
    1369634        return $protocol . '://' . $api_host . (in_array($api_port, $api_port_base) ? '' : ':' . $api_port) . '';
    1370635    }
    1371 
    1372     public function rejectGET()
    1373     {
    1374         header('Content-Type: text/html; charset=utf-8');
    1375         http_response_code(403);
    1376         echo '<h1>403 Forbidden</h1>';
    1377         echo '<p>You are not allowed to access this page.</p>';
    1378         die();
    1379     }
    1380 
    1381     private function unknownGETAction()
    1382     {
    1383         header('Content-Type: text/html; charset=utf-8');
    1384         http_response_code(400);
    1385         echo '<h1>400 Bad Request</h1>';
    1386         echo '<p>Unknown action.</p>';
    1387         die();
    1388     }
    1389 
    1390     /**
    1391      * Convert a boolean value to an integer (0 or 1)
    1392      *
    1393      * @param mixed $value The value to convert
    1394      *
    1395      * @return mixed The converted value (0 or 1), or original
    1396      */
    1397     private function boolInt($value)
    1398     {
    1399         if (is_bool($value)) {
    1400             return $value ? 1 : 0;
    1401         }
    1402 
    1403         return $value;
    1404     }
    1405 
    1406     /**
    1407      * Convert an integer (0 or 1) to a boolean value
    1408      *
    1409      * @param mixed $value The value to convert
    1410      *
    1411      * @return mixed The converted value (true or false), or original
    1412      */
    1413     private function intBool($value)
    1414     {
    1415         if (is_bool($value)) {
    1416             return $value;
    1417         }
    1418         if (is_int($value)) {
    1419             return $value === 1;
    1420         }
    1421 
    1422         return $value;
    1423     }
    1424636}
Note: See TracChangeset for help on using the changeset viewer.