Plugin Directory

Changeset 3452753


Ignore:
Timestamp:
02/03/2026 10:21:20 AM (2 months ago)
Author:
clonable
Message:

version 2.9.0

Location:
clonable
Files:
142 added
21 edited

Legend:

Unmodified
Added
Removed
  • clonable/trunk/clonable-wp.php

    r3434441 r3452753  
    55Description: Official plugin for improving your clones made with Clonable.
    66Plugin URI: https://kb.clonable.net/en/introduction/getting-started/wordpress#de-clonable-plug-in-downloaden
    7 Version: 2.8.2
     7Version: 2.9.0
    88Author: Clonable BV
    99Author URI: https://www.clonable.net
     
    2222use Clonable\Services\AllowedHostsService;
    2323use Clonable\Services\ClonableWooCommerceService;
     24use Clonable\Services\EmailTranslationsService;
    2425use Clonable\Services\LanguageSwitcherService;
    2526use Clonable\Helpers\UrlTranslator;
     
    4445include_once "models/ClonedSite.php";
    4546include_once "models/Settings.php";
     47include_once "models/EmailTranslations.php";
    4648
    4749// custom objects
     
    5355include_once "objects/Notification.php";
    5456include_once "objects/CircuitBreaker.php";
     57
    5558
    5659// include views
     
    6366include_once "views/SettingsView.php";
    6467include_once "views/OnboardingView.php";
     68include_once "views/EmailTranslationsView.php";
    6569
    6670// include controllers
     
    7276include_once "controllers/SettingController.php";
    7377include_once "controllers/LandingPageController.php";
     78include_once "controllers/EmailTranslationsController.php";
    7479
    7580// include helper classes
     
    8186include_once "helpers/MultiCurrency.php";
    8287include_once "helpers/UrlTranslator.php";
     88include_once "helpers/TextTranslator.php";
    8389
    8490// middleware
     
    95101// include services
    96102include_once "services/LanguageSwitcherService.php";
    97 //include_once "services/WPLanguageSwitcherService.php";
    98103include_once "services/ApiService.php";
    99104include_once "services/SubfolderService.php";
     
    107112include_once "services/CacheService.php";
    108113include_once "services/AdminUIService.php"; // general admin UI service for additions, notices, etc.
    109 
     114include_once "services/EmailTranslationsService.php";
    110115
    111116// include service modules
     
    121126
    122127define('CLONABLE_NAME', 'Clonable');
    123 define('CLONABLE_VERSION', '2.8.2');
     128define('CLONABLE_VERSION', '2.9.0');
    124129
    125130if (defined('WP_CLI') && WP_CLI) {
     
    148153        $language_tag_service = new LanguageTagService();
    149154        add_action('wp_head', array($language_tag_service, 'clonable_echo_language_tags'), 5);
    150 
     155       
    151156        $urlTranslator = new UrlTranslator();
    152157        add_action('clonable_public_key_cron_hook', array($urlTranslator, 'clonable_get_public_key'), 10, 1);
     
    158163        error_log("[Clonable] Error while setting up language tags: {$exception->getMessage()}");
    159164    }
    160 
    161165
    162166    try {
     
    184188    } catch (Exception $exception) {
    185189        error_log("[Clonable] Error while setting up WooCommerce modules: {$exception->getMessage()}");
     190    }
     191
     192    try {
     193        $email_translations_service = new EmailTranslationsService();
     194    } catch (Exception $exception) {
     195        error_log("[Clonable] Error setting up email translations: {$exception->getMessage()}");
    186196    }
    187197
  • clonable/trunk/controllers/WoocommerceController.php

    r3422714 r3452753  
    9191    }
    9292
     93    public function clonable_woocommerce_enable_pdf_invoice_translation_validate($input) {
     94        return $this->validate_checkbox($input, 'clonable_woocommerce_enable_pdf_invoice_translation');
     95    }
     96
    9397    public function clonable_curcy_currency_override_validate($input)
    9498    {
     
    128132            return $old_input;
    129133        }
     134
    130135        foreach ($rows as $row) {
    131            
    132136            if (empty($row->domain)) {
    133137                add_settings_error('clonable_default_country_by_domain', 'err_empty_domain', 'Domain cannot be empty.');
     
    158162
    159163        }
     164
    160165        return Json::handle_output(json_encode($rows));
    161166    }
  • clonable/trunk/helpers/Functions.php

    r3422714 r3452753  
    44
    55use Clonable\Objects\ClonableConfig;
     6use Clonable\Helpers\TextTranslator;
    67
    78class Functions
     
    182183        // Build result rows for each domain
    183184        $rows = [];
    184 
    185185        foreach ($clone_urls as $domain) {
    186186            $rows[] = $saved_by_domain[$domain] ?? [
     
    194194        return $rows;
    195195    }
     196
     197    public static function minify_html($html = '', $active = true)
     198    {
     199        if (empty($html)) {
     200            return $html;
     201        }
     202        // Remove comments
     203        $html = preg_replace('/<!--(?!<!)[^\[>].*?-->/', '', $html);
     204        // Remove whitespace between tags
     205        $html = preg_replace('/>\s+</', '><', $html);
     206        // Remove leading and trailing whitespace
     207        $html = trim($html);
     208        // remove multiple spaces
     209        $html = preg_replace('/\s+/', ' ', $html);
     210
     211        // remove inline js and css comments
     212        $html = preg_replace_callback('/(<script\b[^>]*>)([\s\S]*?)(<\/script>)/', function ($matches) {
     213            $script_open = $matches[1];
     214            $script_content = $matches[2];
     215            $script_close = $matches[3];
     216
     217            // Remove JS comments
     218            $script_content = preg_replace('/\/\/.*(?=[\n\r])|\/\*[\s\S]*?\*\//', '', $script_content);
     219
     220            return $script_open . $script_content . $script_close;
     221        }, $html);
     222
     223
     224        return $html;
     225    }
     226
     227    public static function lower(string $value): string {
     228        return function_exists('mb_strtolower') ? mb_strtolower($value) : strtolower($value);
     229    }
     230
     231    public static function sanitize_text(string $text) {
     232        if (empty($text)) {
     233            return "";
     234        }
     235        $value = trim($text);
     236        if (function_exists('sanitize_text_field')) {
     237            return sanitize_text_field($value);
     238        }
     239
     240        // Fallback for non-WP contexts (static analysis / early load).
     241        $value = strip_tags($value);
     242        $value = preg_replace('/[\r\n\t\0\x0B]+/u' ,' ' ,$value);
     243
     244        return trim((string) $value);
     245    }
     246
     247    public static function sanitize_email(string $email) {
     248        if (empty($text)) {
     249            return "";
     250        }
     251        $value = trim($email);
     252        if (function_exists('sanitize_email')) {
     253            return sanitize_email($value);
     254        }
     255
     256        $sanitized = (string) filter_var($value ,FILTER_SANITIZE_EMAIL);
     257        return is_string($sanitized) ? $sanitized : '';
     258    }
    196259}
  • clonable/trunk/helpers/Html.php

    r3422714 r3452753  
    127127        self::include_cdn("Alpine-Tooltip.js", "https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js", false, true);
    128128        self::include_cdn("Tippy.css", "https://unpkg.com/tippy.js@6/dist/tippy.css", true);
     129        self::include_cdn("Collapse", "https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js", false, true);
    129130        self::include_cdn("Alpine.js", "https://unpkg.com/alpinejs", false, true);
    130131    }
  • clonable/trunk/helpers/UrlTranslator.php

    r3422714 r3452753  
    160160    public static function clonable_get_full_url($site)
    161161    {
     162        $domain = $site->domain;
    162163       
    163         if ($site && is_array($site)) {
    164             $domain = $site['domain'];
    165         }
    166         if ($site && is_object($site)) {
    167             $domain = $site->domain;
    168         }
    169 
    170         if (empty($domain)) {
    171             return '';
    172         }
    173        
    174164        // Use fallback '/' for original site data, since there is no notion of subfolders there
    175165        $original_subfolder = ($site->original_subfolder ?? '/');
  • clonable/trunk/objects/ClonableConfig.php

    r3378896 r3452753  
    33namespace Clonable\Objects;
    44
    5 use Clonable\Helpers\Functions;
    65use Clonable\Models\Site;
    76
    8 defined( 'ABSPATH' ) || exit;
     7defined('ABSPATH') || exit;
    98
    109/**
     
    1211 * the entire plugin.
    1312 */
    14 class ClonableConfig {
     13class ClonableConfig
     14{
    1515    const WOOCOMMERCE_QUERY_ID = "clonable-excluded-%s";
    1616    const WOOCOMMERCE_TAXONOMY = 'clonable_excluded_products';
    1717    const UT_API_ENDPOINT = 'ut.api.clonable.net';
     18    const TRANSLATIONS_API_ENDPOINT = 'translations.api.clonable.net';
    1819    const SERVER_IP = '89.41.171.180';
    1920    const SERVER_IPV6 = '2a01:7c8:e001:d9:0:0:0:e778';
     
    2627     * @return array
    2728     */
    28     public static function get_clones() {
     29    public static function get_clones()
     30    {
    2931        $site = self::get_site();
    3032        if (empty($site)) {
     
    3840     * @return Site|null
    3941     */
    40     public static function get_site() {
     42    public static function get_site()
     43    {
    4144        $response = get_option("clonable_site");
    4245        if (empty($response)) {
     
    5255     * or return the domain + subfolder for the clone.
    5356     */
    54     public static function current_clonable_domain() {
     57    public static function current_clonable_domain()
     58    {
    5559        // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    5660        $server_data = $_SERVER;
     
    6771        return $domain . $subfolder;
    6872    }
     73
     74    /**
     75     * Get the clone ID based on the current URL or refererrer URL.
     76     * @return string|null
     77     */
     78    public static function get_clone_id_by_current_url(): ?string {
     79        $clones = self::get_clones();
     80        if (empty($clones)) {
     81            return null;
     82        }
     83
     84        $host_header = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? ($_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] ?? ''));
     85        $sanitized_host = str_replace('www.', '', $host_header);
     86       
     87        // Get all clones for this domain.
     88        $domain_matches = [];
     89        foreach ($clones as $clone) {
     90            if ($clone['domain'] === $sanitized_host) {
     91                $domain_matches[] = $clone;
     92            }
     93        }
     94
     95        if (empty($domain_matches)) {
     96            return null;
     97        }
     98
     99        if (count($domain_matches) === 1) {
     100            return $domain_matches[0]['id'];
     101        }
     102
     103        // More than 1 domain match basically means that there are multiple subfolder clones.
     104        foreach ($domain_matches as $clone) {
     105            if ($clone['is_subfolder_clone']) {
     106                $subfolder = trim($clone['subfolder_clone'], '/');
     107                $request_uri = $_SERVER['HTTP_REFERER'] ?? '/';
     108                $request_path = parse_url($request_uri, PHP_URL_PATH);
     109                if (strpos($request_path, $subfolder) !== false) {
     110                    return $clone['id'];
     111                }
     112            }
     113        }
     114
     115        return null;
     116    }
    69117}
  • clonable/trunk/readme-da_DK.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-de_DE.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-es_ES.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-fr_FR.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-it_IT.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-nb_NO.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme-nl_NL.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35Nieuwe WooCommerce email vertalingen.
     36Configureer vertaalde email voor je clones, inclusief the facturen en pakbonnen
     37
    3438v2.8.2
    3539Probleem met default land opgelost
  • clonable/trunk/readme-sv_SE.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/readme.txt

    r3434441 r3452753  
    55Tested up to: 6.9
    66Requires PHP: 7.2
    7 Stable tag: 2.8.2
     7Stable tag: 2.9.0
    88License: GPL v2 or later
    99
     
    3232
    3333== Changelog ==
     34v2.9.0
     35New WooCommerce email translations.
     36Configure translated emails for your WooCommerce store, including the invoices.
     37
    3438v2.8.2
    3539Fixed bug with default country
  • clonable/trunk/routes/Router.php

    r3280987 r3452753  
    99use Clonable\Controllers\WooCommerceController;
    1010use Clonable\Controllers\DashboardController;
     11use Clonable\Controllers\EmailTranslationsController;
    1112use Clonable\MiddlewareHandler;
    1213use Clonable\Traits\PluginCheck;
     
    7475            'middleware' => ['auth', 'cloned_site'],
    7576        ];
     77
    7678        $routes['language-switcher'] = [
    7779            'name' => 'Language Switcher',
     
    8789            'type' => $this::PAGE_TYPE_TAB,
    8890            'middleware' => ['auth', 'cloned_site', 'woocommerce'],
     91        ];
     92
     93        // email translations tab
     94        $routes['email-translations'] = [
     95            'name' => 'Email Translations',
     96            'controller' => EmailTranslationsController::class,
     97            'type' => $this::PAGE_TYPE_TAB,
     98            'middleware' => ['auth', 'cloned_site'],
    8999        ];
    90100
  • clonable/trunk/services/ClonableWooCommerceService.php

    r3434441 r3452753  
    55use Clonable\Helpers\Functions;
    66use Clonable\Helpers\Json;
    7 use Clonable\Helpers\UrlTranslator;
    87use Clonable\Objects\ClonableConfig;
    98use Clonable\Services\Modules\DataPanelModule;
     
    1211use Clonable\Services\Modules\TaxonomyModule;
    1312use Clonable\Traits\PluginCheck;
     13use Clonable\Helpers\TextTranslator;
    1414
    1515use WC_Order;
     
    2020{
    2121    use PluginCheck;
     22
     23    private const PDF_REPLACEMENTS_BY_DOMAIN = 'clonable_wpo_replacements_by_domain';
     24    private const OPT_HEADERS_BY_DOMAIN = 'clonable_email_headers_by_domain';
    2225
    2326    public function __construct()
     
    6265        }
    6366
    64         add_filter('default_checkout_billing_country', array($this, 'get_default_checkout_country'));
    65         add_filter('default_checkout_shipping_country', function () {
    66             return $this->get_default_checkout_country('shipping');
     67        // Set default checkout country filters (only when user not logged in so we don't interfere with existing user data)
     68        add_action('plugins_loaded', function () {
     69            if (!is_user_logged_in()) {
     70                add_filter('default_checkout_billing_country', array($this, 'get_default_checkout_country'));
     71                add_filter('default_checkout_shipping_country', function () {
     72                    return $this->get_default_checkout_country('shipping');
     73                });
     74            }
    6775        });
    68     }
    69 
     76
     77        // Add a hidden field containing the current clone ID for use in woocommerce email translations
     78        add_action('woocommerce_after_order_notes', array($this, 'add_clone_id_field'), 10, 2);
     79        // Add the clone ID to the order meta when saving the order   
     80        add_action('woocommerce_checkout_update_order_meta', array($this, 'save_order_clone_id'));
     81
     82        // Support for WooCommerce PDF Invoices & Packing Slips plugin translation
     83        $pdf_translations_enabled = get_option('clonable_woocommerce_enable_pdf_invoice_translation', 'off') === 'on';
     84        if ($this->pdf_invoices_installed() && $pdf_translations_enabled) {
     85            add_action('plugins_loaded', [$this, 'register_pdf_translation_hooks'], 20, 0);
     86        }
     87    }
     88
     89    public function register_pdf_translation_hooks() {
     90        $filter_function_map = [
     91            "wpo_wcpdf_shop_phone_number_settings_text" => "shop_phone_number",
     92            "wpo_wcpdf_shop_email_address_settings_text" => "shop_email_address",
     93            "wpo_wcpdf_shop_name_settings_text" => "shop_name",
     94            "wpo_wcpdf_vat_number_settings_text" => "vat_number",
     95            "wpo_wcpdf_coc_number_settings_text" => "coc_number",
     96            "wpo_wcpdf_shop_address_line_1_settings_text" => "shop_address_line_1",
     97            "wpo_wcpdf_shop_address_line_2_settings_text" => "shop_address_line_2",
     98            "wpo_wcpdf_shop_address_country_settings_text" => "shop_address_country",
     99            "wpo_wcpdf_shop_address_state_settings_text" => "shop_address_state",
     100            "wpo_wcpdf_shop_address_city_settings_text" => "shop_address_city",
     101            "wpo_wcpdf_shop_address_postcode_settings_text" => "shop_address_postcode",
     102        ];
     103
     104        add_filter('wpo_wcpdf_get_html', function ($html, $document) {
     105            return $this->get_pdf_template_translation($html, $document);
     106        }, 10, 2);
     107
     108        foreach ($filter_function_map as $hook => $field_name) {
     109            add_filter($hook, function ($text, $document) use ($field_name) {
     110                return $this->get_pdf_field_replacement($field_name, $text, $document);
     111            }, 10, 2);
     112        }
     113
     114        // Support different logos per language in PDF documents (use multiple filter names for compatibility)
     115        add_filter('wpo_wcpdf_logo', [$this, 'pdf_logo_url'], 10, 2);
     116        add_filter('wpo_wcpdf_document_logo', [$this, 'pdf_logo_url'], 10, 2);
     117        add_filter('wpo_wcpdf_header_logo', [$this, 'pdf_logo_url'], 10, 2);
     118        add_filter('wpo_wcpdf_template_logo', [$this, 'pdf_logo_url'], 10, 2);
     119
     120        // logo ID filter
     121        add_filter('wpo_wcpdf_header_logo_id', [$this, 'pdf_logo_id'], 10, 2);
     122    }
     123
     124    /**
     125     * Get PDF replacements for a specific clone
     126     *
     127     * @param string $clone_id
     128     * @param string $field_name
     129     * @param string $default
     130     *
     131     * @return string field replacement value or default when not found
     132     */
     133    public function get_pdf_field_replacement(string $field_name, string $text, $document): string
     134    {
     135        $order = $document->order ?? null;
     136        $clone_id = $order ? $this->get_clone_id_from_order($order) : '';
     137
     138        if (!$clone_id) {
     139            // check if we can get clone ID from the $_POST request
     140            $clone_id = $clone_id ?? $this->get_clone_id_from_request();
     141            if (!$clone_id) {
     142                error_log('No clone ID found for PDF translation');
     143                return $text;
     144            }
     145        }
     146
     147        $pdf_replacements = $this->get_pdf_replacements_for_clone($clone_id);
     148
     149        if ($pdf_replacements === null) {
     150            return $text;
     151        }
     152
     153        if (isset($pdf_replacements[$field_name]) && is_string($pdf_replacements[$field_name]) && trim($pdf_replacements[$field_name]) !== '') {
     154            return $pdf_replacements[$field_name];
     155        }
     156
     157        return $text;
     158    }
     159
     160    public function get_pdf_template_translation($html, $document)
     161    {
     162        $order = $document->order ?? null;
     163        $clone_id = $order ? $this->get_clone_id_from_order($order) : '';
     164
     165        if (!$clone_id) {
     166            // check if we can get clone ID from the $_POST request
     167            $clone_id = $clone_id ?? $this->get_clone_id_from_request();
     168            if (!$clone_id) {
     169                error_log('No clone ID found for PDF translation');
     170                return $html;
     171            }
     172        }
     173
     174        // make sure element with class shop-info and addresses are not translated by adding notranslate class
     175        $html = str_replace('class="shop-info', 'class="shop-info notranslate', $html);
     176        $html = str_replace('class="address billing-address', 'class="address billing-address notranslate', $html);
     177        $html = str_replace('class="address shipping-address', 'class="address shipping-address notranslate', $html);
     178
     179        // Call translation API
     180        $translated_html = TextTranslator::translateText($html, (string) $clone_id, false) ?? $html;
     181        return $translated_html ?: $html;
     182    }
     183
     184    private function get_clone_id_from_order($orderObject)
     185    {
     186        if (!$orderObject || !($orderObject instanceof WC_Order)) {
     187            return '';
     188        }
     189        $clone_id = $orderObject->get_meta('clonable_clone_id');
     190        return $clone_id ? $clone_id : '';
     191    }
     192
     193    /**
     194     * Filter PDF logo URL based on Clone
     195     *
     196     * @param string $logo_url Original logo URL
     197     * @param mixed $document PDF document object
     198     * @return string Custom logo URL or original
     199     */
     200    public function pdf_logo_url($logo_url, $document)
     201    {
     202        $order = $document->order ?? null;
     203        $clone_id = $order ? $this->get_clone_id_from_order($order) : '';
     204        if (!$clone_id) {
     205            $clone_id = $this->get_clone_id_from_request();
     206        }
     207        if (!$clone_id) {
     208            return $logo_url;
     209        }
     210
     211        // get the logo URL for the current clone ID
     212        $headerInfo = $this->getHeaderInfoForClone($clone_id);
     213        if ($headerInfo === null) {
     214            return $logo_url;
     215        }
     216
     217        $custom_logo_url = isset($headerInfo['email_logo']) && is_string($headerInfo['email_logo'])
     218            ? trim($headerInfo['email_logo'])
     219            : '';
     220
     221        if ($custom_logo_url !== '') {
     222            return $custom_logo_url;
     223        }
     224
     225        return $logo_url;
     226    }
     227
     228    /**
     229     * Filter PDF logo ID based on Clone
     230     *
     231     * @param int $logo_id Original logo attachment ID
     232     * @param mixed $document PDF document object
     233     * @return int Custom logo attachment ID or original
     234     */
     235    public function pdf_logo_id($logo_id, $document)
     236    {
     237        $order = $document->order ?? null;
     238
     239        $clone_id = $order ? $this->get_clone_id_from_order($order) : '';
     240        if (!$clone_id) {
     241            $clone_id = $this->get_clone_id_from_request();
     242        }
     243        if (!$clone_id) {
     244            return $logo_id;
     245        }
     246
     247        // get the logo URL for the current clone ID
     248        $headerInfo = $this->getHeaderInfoForClone($clone_id);
     249        if ($headerInfo === null) {
     250            return $logo_id;
     251        }
     252
     253        $custom_logo_url = isset($headerInfo['email_logo']) && is_string($headerInfo['email_logo'])
     254            ? trim($headerInfo['email_logo'])
     255            : '';
     256
     257        $custom_logo_id = attachment_url_to_postid($custom_logo_url);
     258        if ($custom_logo_id) {
     259            return $custom_logo_id;
     260        }
     261
     262        return $logo_id;
     263    }
     264
     265    public function add_clone_id_field()
     266    {
     267        $clone_id = ClonableConfig::get_clone_id_by_current_url();
     268        echo '<input type="hidden" name="clonable_clone_id" value="' . $clone_id . '" />';
     269    }
    70270
    71271    /**
     
    73273     *
    74274     * @param string $type 'billing' or 'shipping'
    75      *
    76275     * @return string
    77276     */
     
    101300        return '';
    102301    }
    103    
    104     public function add_origin_field($checkout) {
    105         $value = parse_url( get_site_url(), PHP_URL_HOST );
     302
     303    public function add_origin_field($checkout)
     304    {
     305        $value = parse_url(get_site_url(), PHP_URL_HOST);
    106306        echo "<input type=\"hidden\" name=\"clonable_origin\" value=\"" . esc_html($value) . "\" />";
    107307    }
     
    109309    public function save_origin_field($order_id)
    110310    {
    111         // phpcs:disable WordPress.Security.NonceVerification.Missing
    112311        $post_data = wp_unslash($_POST);
    113312        if (!empty($post_data['clonable_origin'])) {
     
    118317            }
    119318        }
    120         // phpcs:enable WordPress.Security.NonceVerification.Missing
     319    }
     320
     321    public function save_order_clone_id($order_id)
     322    {
     323        $post_data = wp_unslash($_POST);
     324        if (!empty($post_data['clonable_clone_id'])) {
     325            $order = wc_get_order($order_id);
     326            $order->update_meta_data('clonable_clone_id', sanitize_text_field($post_data['clonable_clone_id']));
     327            $order->save();
     328        }
    121329    }
    122330
     
    132340    }
    133341
    134 
    135342    /** Change the return url to point to the clone site */
    136343    public function filter_return_url($return_url, $order)
     
    143350        // Check if order origin was recorded. No validation is needed here, as it is done when saving the attribute.
    144351        $order_origin = get_post_meta($order->get_id(), 'clonable_origin', true);
    145 
    146         // phpcs:disable WordPress.Security.NonceVerification.Missing
    147352        $server_data = $_SERVER;
    148         // phpcs:enable WordPress.Security.NonceVerification.Missing
    149353        $host = $server_data['HTTP_HOST'] ?? null;
    150354
     
    230434        }
    231435    }
     436
     437    private function get_clone_id_from_request(): string
     438    {
     439        return isset($_POST['clonable_clone_id']) ? (string) $_POST['clonable_clone_id'] : '';
     440    }
     441
     442    private function get_pdf_replacements_for_clone(string $clone_id): ?array
     443    {
     444        foreach ($this->get_pdf_replacements_by_domain() as $domain_pdf_replacements) {
     445            if (!is_array($domain_pdf_replacements)) {
     446                continue;
     447            }
     448
     449            $db_clone_id = isset($domain_pdf_replacements['clone_id']) ? \trim((string) $domain_pdf_replacements['clone_id']) : '';
     450            if ($db_clone_id && $clone_id === $db_clone_id) {
     451                return $domain_pdf_replacements;
     452            }
     453        }
     454
     455        return null;
     456    }
     457
     458    private function get_pdf_replacements_by_domain(): array
     459    {
     460        $raw = get_option(self::PDF_REPLACEMENTS_BY_DOMAIN, '[]');
     461        $decoded = json_decode((string) $raw, true);
     462        return is_array($decoded) ? $decoded : [];
     463    }
     464
     465
     466    private function getHeadersByDomain(): array
     467    {
     468        $raw = get_option(self::OPT_HEADERS_BY_DOMAIN, '[]');
     469        $decoded = json_decode((string) $raw, true);
     470        return is_array($decoded) ? $decoded : [];
     471    }
     472
     473    private function getHeaderInfoForClone(string $clone_id): ?array
     474    {
     475        foreach ($this->getHeadersByDomain() as $header_info) {
     476            if (!is_array($header_info)) {
     477                continue;
     478            }
     479
     480            $header_clone_id = isset($header_info['clone_id']) ? trim((string) $header_info['clone_id']) : '';
     481            if ($header_clone_id !== '' && $header_clone_id === $clone_id) {
     482                return $header_info;
     483            }
     484        }
     485
     486        return null;
     487    }
    232488}
  • clonable/trunk/traits/PluginCheck.php

    r3280987 r3452753  
    2424    }
    2525
     26    /**
     27    * Check if Select PDF Invoices & Packing Slips for WooCommerce is installed
     28    * @return bool
     29    */
     30    public function pdf_invoices_installed() {
     31        return $this->check_plugin_by_file('woocommerce-pdf-invoices-packing-slips/woocommerce-pdf-invoices-packingslips.php');
     32    }   
     33
    2634    /**
    2735     * Check if the WooCommerce Multi Currency plugin is installed
  • clonable/trunk/traits/Validation.php

    r3234826 r3452753  
    122122        return $input;
    123123    }
     124
     125    public function validate_email_strict($email): bool {
     126        if ($email === '') {
     127            return false;
     128        }
     129
     130        // WP's is_email() may not be available in some contexts (e.g. static analysis / early load).
     131        if (function_exists('is_email')) {
     132            return is_email($email) !== false;
     133        } else {
     134            return filter_var($email ,FILTER_VALIDATE_EMAIL) !== false;
     135        }
     136    }
    124137}
  • clonable/trunk/views/WoocommerceView.php

    r3422714 r3452753  
    1010use Clonable\Models\ClonableWooCommerce;
    1111use Clonable\Traits\Forms;
    12 use Clonable\Objects\ClonableConfig;
    1312
    1413class WoocommerceView implements ViewInterface
     
    206205        $default_countries_per_domain = Functions::get_default_countries_per_domain();
    207206
    208         if ($default_countries_per_domain)
     207        if ($default_countries_per_domain) {
    209208            $output = esc_js(wp_json_encode($default_countries_per_domain));
     209        }
    210210
    211211        // Build country list
     
    216216        }
    217217
    218     ?> 
     218    ?>
    219219        <p style="max-width: 75%">
    220220            Configure per domain which default billing and shipping country should be preselected during WooCommerce checkout.
  • clonable/trunk/views/css/clonable-language-switcher.css

    r3422714 r3452753  
    6565    width: var(--clonable-language-switcher-flag-size);
    6666    height: var(--clonable-language-switcher-flag-size);
     67    border-radius: var(--clonable-language-switcher-flag-radius);
    6768}
    6869
    6970.cl-language-switcher-lan-name {
    70     font-size: var(--clonable-language-switcher-font-size);
     71    font-size: inherit;
    7172    white-space: nowrap;
     73}
     74
     75.clonable-language-switcher i.fflag.ff-md {
     76    margin-right: var(--clonable-language-switcher-fflag-margin-right);
     77    width: var(--clonable-language-switcher-fflag-width);
     78    height: var(--clonable-language-switcher-fflag-height);
    7279}
    7380
Note: See TracChangeset for help on using the changeset viewer.