Plugin Directory

Changeset 3491060


Ignore:
Timestamp:
03/25/2026 04:00:57 PM (45 hours ago)
Author:
mailerlite
Message:

Update to version 3.1.13 from GitHub

Location:
woo-mailerlite
Files:
10 added
30 edited
1 copied

Legend:

Unmodified
Added
Removed
  • woo-mailerlite/tags/3.1.13/README.txt

    r3484689 r3491060  
    66Tested up to: 6.8.2
    77Requires PHP: 7.2.5
    8 Stable tag: 3.1.12
     8Stable tag: 3.1.13
    99License: GPLv3 or later
    1010License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    8484
    8585== Changelog ==
     86= 3.1.13 (25th March 2026) =
     87* Bug fixes and performance improvements
     88
    8689= 3.1.12 (17th March 2026) =
    8790* Bug fixes
  • woo-mailerlite/tags/3.1.13/admin/WooMailerLiteAdmin.php

    r3415073 r3491060  
    2323    public function enqueueScripts($hook)
    2424    {
     25        if ($hook === 'edit.php' && isset($_GET['post_type']) && $_GET['post_type'] === 'product') {
     26            wp_enqueue_script('woo-mailerlite-quick-edit', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-quick-edit.js', ['jquery', 'inline-edit-post'], null, true);
     27            return;
     28        }
     29
    2530        if ($hook !== 'woocommerce_page_mailerlite') {
    2631            return;
    2732        }
     33
     34        wp_dequeue_script('select2');
     35        wp_deregister_script('select2');
     36
    2837        wp_enqueue_script('woo-mailerlite-vue-cdn', 'https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.prod.js', [], null, true);
    2938        wp_localize_script('woo-mailerlite-admin', 'woo_mailerlite_admin_data', array(
     
    3241        ));
    3342
    34         wp_enqueue_script('style2-script', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js');
    35         wp_enqueue_script('woo-mailerlite-admin', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-app.js', ['jquery', 'woo-mailerlite-vue-cdn'], null, true);
     43        wp_enqueue_script('woo-mailerlite-select2', plugin_dir_url(__FILE__) . 'assets/js/lib/select2.min.js', ['jquery'], WOO_MAILERLITE_VERSION, true);
     44        wp_enqueue_script('woo-mailerlite-admin', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-app.js', ['jquery', 'woo-mailerlite-vue-cdn', 'woo-mailerlite-select2'], null, true);
    3645
    3746    }
     
    4150            return;
    4251        }
     52
     53        wp_dequeue_style('select2');
     54        wp_deregister_style('select2');
     55
    4356        wp_enqueue_style('woo-mailerlite-admin-css', plugin_dir_url( __FILE__ ) . '../admin/assets/css/admin.css', false, WOO_MAILERLITE_VERSION);
    44         wp_enqueue_style('style2-style', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css');
    45         wp_enqueue_style('style2-mailerlite-style', plugin_dir_url( __FILE__ ) . '../public/css/mailerlite-select2.css',false, WOO_MAILERLITE_VERSION);
     57        wp_enqueue_style('woo-mailerlite-select2-css', plugin_dir_url( __FILE__ ) . 'assets/css/lib/select2.min.css', false, WOO_MAILERLITE_VERSION);
     58        wp_enqueue_style('woo-mailerlite-select2-theme', plugin_dir_url( __FILE__ ) . '../public/css/mailerlite-select2.css', ['woo-mailerlite-select2-css'], WOO_MAILERLITE_VERSION);
     59    }
     60
     61    public function removeConflictingSelect2($hook)
     62    {
     63        if ($hook !== 'woocommerce_page_mailerlite') {
     64            return;
     65        }
     66
     67        global $wp_scripts, $wp_styles;
     68
     69        $scriptPatterns = ['select2', 'selectWoo', 'wc-enhanced-select'];
     70
     71        foreach ($wp_scripts->registered as $handle => $script) {
     72            if ($handle === 'woo-mailerlite-select2') {
     73                continue;
     74            }
     75            foreach ($scriptPatterns as $pattern) {
     76                if (strpos($script->src, $pattern) !== false) {
     77                    wp_dequeue_script($handle);
     78                    wp_deregister_script($handle);
     79                    break;
     80                }
     81            }
     82        }
     83
     84        $stylePatterns = ['select2', 'selectWoo'];
     85
     86        foreach ($wp_styles->registered as $handle => $style) {
     87            if ($handle === 'woo-mailerlite-select2-css' || $handle === 'woo-mailerlite-select2-theme') {
     88                continue;
     89            }
     90            foreach ($stylePatterns as $pattern) {
     91                if (strpos($style->src, $pattern) !== false) {
     92                    wp_dequeue_style($handle);
     93                    wp_deregister_style($handle);
     94                    break;
     95                }
     96            }
     97        }
    4698    }
    4799
     
    115167    public function populateIgnoreProductBlock($column, $post_id)
    116168    {
    117         $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
    118169
    119170        switch ($column) {
    120171            case 'name' :
     172                $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
    121173                ?>
    122174                <div class="hidden ml_ignore_product_inline" id="ml_ignore_product_inline_<?=intval($post_id) ?>">
    123                     <div id="_ml_ignore_product"><?php echo array_key_exists($post_id, $ignoredProducts) ? 'yes' : 'no' ?></div>
     175                    <div class="_ml_ignore_product"><?php echo array_key_exists($post_id, $ignoredProducts) ? 'yes' : 'no' ?></div>
    124176                </div>
    125177                <?php
  • woo-mailerlite/tags/3.1.13/admin/assets/js/components/Forms/CustomSelect.js

    r3338878 r3491060  
    22import eventBus from "../eventBus.js";
    33const template = `
    4     <select ref="wooMlSubGroup" class="wc-enhanced-select">
     4    <select ref="wooMlSubGroup" class="woo-ml-group-select">
    55        <option value="" disabled >{{ placeholder }}</option>
    66    </select>
  • woo-mailerlite/tags/3.1.13/admin/assets/js/components/Forms/SyncFields.js

    r3338878 r3491060  
    11const template = `
    2     <select v-model="setSyncFields"/>
     2    <select v-model="setSyncFields" data-testid="sync-fields-select"/>
    33`;
    44
  • woo-mailerlite/tags/3.1.13/admin/assets/js/components/Modals/Modals.js

    r3338878 r3491060  
    55const template = `
    66<!-- Create Group Modal -->
    7 <div v-if="openCreateGroupModal" class="woo-ml-wizard-modal" id="wooMlWizardCreateGroupModal" role="dialog">
     7<div v-if="openCreateGroupModal" class="woo-ml-wizard-modal" id="wooMlWizardCreateGroupModal" role="dialog" data-testid="create-group-modal">
    88    <div class="woo-ml-wizard-modal-parent">
    99        <div class="woo-ml-wizard-modal-container">
     
    1515                <div class="woo-ml-wizard-modal-body">
    1616                    <div class="create-group-input">
    17                         <input ref="wooMlCreateGroup" type="text" name="createGroup" placeholder="Enter group name" v-model="createGroupName" class="">
     17                        <input ref="wooMlCreateGroup" type="text" name="createGroup" placeholder="Enter group name" v-model="createGroupName" class="" data-testid="create-group-name-input">
    1818                    </div>
    1919                    <div class="modal-button-ml">
    2020                        <button @click="openCreateGroupModal = false" type="button" class="btn-secondary-ml woo-ml-close" style="margin-right: 12px;">Close</button>
    21                         <button @click="createGroup" ref="createGroup" type="button" class="btn-primary-ml"><span class="woo-ml-button-text">Create group</span></button>
     21                        <button @click="createGroup" ref="createGroup" type="button" class="btn-primary-ml" data-testid="create-group-submit-btn"><span class="woo-ml-button-text">Create group</span></button>
    2222                    </div>
    2323                </div>
  • woo-mailerlite/tags/3.1.13/admin/assets/js/components/Views/Settings.js

    r3377455 r3491060  
    66
    77const template = `
    8 <div v-if="syncInProgress || asyncSyncInProgress" class="woo-ml-sync-loading-container">
     8<div v-if="syncInProgress || asyncSyncInProgress" class="woo-ml-sync-loading-container" data-testid="sync-in-progress">
    99    <strong>Sync in progress...</strong>
    1010    <p>This will continue as long as you stay on this page.
     
    2020                    <label for="wooMlSubGroup" class="settings-label mb-3-ml">Subscriber group</label>
    2121                    <label class="input-mailerlite mb-2-ml" style="display: flex;">
    22                         <custom-select
    23                             id="wooMlSubGroup"
    24                             class="wc-enhanced-select"
    25                             name="subscriber-group"
     22                        <custom-select
     23                            id="wooMlSubGroup"
     24                            name="subscriber-group"
    2625                            :options="groups"
    2726                            v-model="selectedGroup"
     
    4342                    </label>
    4443                    <label v-if="ignoredProducts.length !== 0" class="input-mailerlite" style="cursor: default;">
    45                         <div multiple class="wc-enhanced-select" name="ignore_product_list"
     44                        <div multiple name="ignore_product_list"
    4645                             style="min-height: 26px; padding-left: 8px; display: flex; flex-direction: row; overflow: hidden; flex-wrap: wrap; padding: 4px; padding-top: 0; border: 1px solid #d1d5db; border-radius: 0.25rem;">
    47                             <option v-for="(item, index) in ignoredProducts" :key="index" style="background-color: #e5e7eb; padding: 2px 5px; border-radius: 2px; font-size: 13px; margin-right: 4px; margin-top: 4px;">{{ item }}</option>
     46                            <span v-for="(item, index) in ignoredProducts" :key="index" style="background-color: #e5e7eb; padding: 2px 5px; border-radius: 2px; font-size: 13px; margin-right: 4px; margin-top: 4px;">{{ item }}</span>
    4847                        </div>
    4948                    </label>
     
    7675                        </div>
    7776                    </label>
    78                     <label class="input-mailerlite">
    79                         <sync-fields
    80                         id="sync_fields"
    81                         multiple="multiple"
    82                         data-placeholder="Click to select fields you want to sync"
    83                         class="wc-enhanced-select"
     77                    <label class="input-mailerlite" data-testid="settings-sync-fields">
     78                        <sync-fields
     79                        id="sync_fields"
     80                        multiple="multiple"
     81                        data-placeholder="Click to select fields you want to sync"
    8482                        style="width: 100%;"
    8583                        :options="syncFields"
     
    102100                    </button>
    103101                    <button @click.prevent="startSync" v-if="totalUntrackedResources && !syncInProgress" type="button" class="btn btn-secondary-ml flex-start-ml"
    104                             data-woo-ml-reset-resources-sync="true" ref="startSync"><span class="woo-ml-button-text">Synchronize {{ totalUntrackedResources }} untracked resources</span>
     102                            data-woo-ml-reset-resources-sync="true" ref="startSync" data-testid="sync-untracked-resources-btn"><span class="woo-ml-button-text">Synchronize {{ totalUntrackedResources }} untracked resources</span>
    105103                    </button>
    106104                </div>
     
    119117                               value="yes"
    120118                               id="subscribe_checkout_checkbox"
     119                               data-testid="subscribe-checkout-checkbox"
    121120                        />
    122121                        <label for="subscribe_checkout_checkbox" class="settings-label-medium">Enable list subscription via checkout page.</label>
     
    140139                        <label for="wooMlSubGroup" class="settings-label mb-3-ml">Subscribe checkbox position</label>
    141140                        <label class="input-mailerlite">
    142                             <select 
    143                               class="wc-enhanced-select"
    144                               name="checkout_position" 
     141                            <select
     142                              class="woo-ml-native-select"
     143                              name="checkout_position"
    145144                              :disabled="!settings.subscribeOnCheckout"
    146145                              v-model="settings.selectedCheckoutPosition"
     
    269268
    270269        <div style="display: flex; justify-content: flex-end; margin-top: 2rem;">
    271             <button @click.prevent="updateSettings" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="submit" class="btn-primary-ml" style="margin-top: 2rem;" id="updateSettingsBtn"><span class="woo-ml-button-text">Save changes</span></button>
     270            <button @click.prevent="updateSettings" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="submit" class="btn-primary-ml" style="margin-top: 2rem;" id="updateSettingsBtn" data-testid="update-settings-btn"><span class="woo-ml-button-text">Save changes</span></button>
    272271        </div>
    273272     <div class="settings-block">
  • woo-mailerlite/tags/3.1.13/admin/assets/js/components/Views/Wizard.js

    r3338878 r3491060  
    2828            <label for="wooMlApiKey" class="settings-label mb-3-ml">API key</label>
    2929            <div class="api-key-input">
    30                 <input ref="wooMlApiKey" type="text" name="api-key" placeholder="Enter your MailerLite API key" v-model="wooMlApiKey" :disabled="isLoading">
    31                 <button @click="connectAccount" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="button" id="wooMlWizardApiKeyBtn" class="btn-primary-ml"><span class="woo-ml-button-text">Connect account</span></button>
     30                <input ref="wooMlApiKey" type="text" name="api-key" placeholder="Enter your MailerLite API key" v-model="wooMlApiKey" :disabled="isLoading" data-testid="wizard-api-key">
     31                <button @click="connectAccount" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="button" id="wooMlWizardApiKeyBtn" class="btn-primary-ml" data-testid="wizard-connect-account-btn"><span class="woo-ml-button-text">Connect account</span></button>
    3232            </div>
    3333            <div class="signup-link-ml">
     
    6161            <label for="wooMlSubGroup" class="settings-label mb-3-ml">Group</label>
    6262            <label class="input-mailerlite mb-2-ml" style="display: flex;">
    63                 <custom-select
    64                     ref="wooMlSubGroupComponent"
    65                     class="wc-enhanced-select"
    66                     name="subscriber-group"
     63                <custom-select
     64                    ref="wooMlSubGroupComponent"
     65                    name="subscriber-group"
    6766                    style="width: 100%;"
    6867                    :options="groups"
     
    7069                    placeholder="Select group"
    7170                ></custom-select>
    72                 <button @click="createGroup" id="createGroupModal" type="button" class="btn-secondary-ml" style="margin-left: 0.5rem; white-space: nowrap;">Create group</button>
     71                <button @click="createGroup" id="createGroupModal" type="button" class="btn-secondary-ml" style="margin-left: 0.5rem; white-space: nowrap;" data-testid="wizard-create-group-btn">Create group</button>
    7372            </label>
    7473           
     
    109108                </div>
    110109            </label>
    111             <label class="input-mailerlite">
     110            <label class="input-mailerlite" data-testid="wizard-sync-fields">
    112111                <sync-fields
    113112                    id="sync_fields"
     
    115114                    v-model="selectedSyncFields"
    116115                    placeholder="Click to select fields you want to sync"
    117                     class="wc-enhanced-select" style="width: 100%;"
     116                    style="width: 100%;"
    118117                    :options="syncFields"
    119118                    :model-value="selectedSyncFields"
     
    123122
    124123        <div class="settings-block" style="display: flex; justify-content: space-between; padding-top: 2rem;">
    125             <button @click="startImport" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" id="startImport" type="button" class="btn-primary-ml"><span class="woo-ml-button-text">Next</span></button>
     124            <button @click="startImport" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" id="startImport" type="button" class="btn-primary-ml" data-testid="wizard-start-import-btn"><span class="woo-ml-button-text">Next</span></button>
    126125        </div>
    127126    </div>
  • woo-mailerlite/tags/3.1.13/admin/controllers/WooMailerLiteAdminSettingsController.php

    r3470352 r3491060  
    1818
    1919        $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
     20
     21        $wasIgnored = $product->ignored;
     22
    2023        if ($this->request('ml_ignore_product')) {
    2124            $product->ignored = true;
     
    3437
    3538        if ($this->apiClient()->isClassic()) {
    36             $this->apiClient()->setConsumerData([
    37                 'store'           => home_url(),
    38                 'currency'        => get_option('woocommerce_currency'),
    39                 'ignore_list'     => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
    40                 'consumer_key'    => WooMailerLiteOptions::get('consumerKey', null),
    41                 'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
    42                 'group_id'        => WooMailerLiteOptions::get('group.id'),
    43                 'resubscribe'     => WooMailerLiteOptions::get('settings.resubscribe'),
    44                 'create_segments' => false
    45             ]);
     39            if ((bool)$wasIgnored !== (bool)$product->ignored) {
     40                $this->apiClient()->setConsumerData([
     41                    'store' => home_url(),
     42                    'currency' => get_option('woocommerce_currency'),
     43                    'ignore_list' => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
     44                    'consumer_key' => WooMailerLiteOptions::get('consumerKey', null),
     45                    'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
     46                    'group_id' => WooMailerLiteOptions::get('group.id'),
     47                    'resubscribe' => WooMailerLiteOptions::get('settings.resubscribe'),
     48                    'create_segments' => false
     49                ]);
     50            }
    4651        } else {
    4752            $product->exclude_from_automations = $product->ignored;
     
    97102
    98103                $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
     104
     105                $wasIgnored = $product->ignored;
     106
    99107                if ($this->request('ml_ignore_product')) {
    100108                    $product->ignored = true;
     
    109117                WooMailerLiteOptions::update('ignored_products', $ignoredProducts);
    110118                if ($this->apiClient()->isClassic()) {
    111                     $this->apiClient()->setConsumerData([
    112                         'store'           => home_url(),
    113                         'currency'        => get_option('woocommerce_currency'),
    114                         'ignore_list'     => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
    115                         'consumer_key'    => WooMailerLiteOptions::get('consumerKey', null),
    116                         'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
    117                         'group_id'        => WooMailerLiteOptions::get('group.id'),
    118                         'resubscribe'     => WooMailerLiteOptions::get('settings.resubscribe'),
    119                         'create_segments' => false
    120                     ]);
     119                    if ((bool)$wasIgnored !== (bool)$product->ignored) {
     120                        $this->apiClient()->setConsumerData([
     121                            'store' => home_url(),
     122                            'currency' => get_option('woocommerce_currency'),
     123                            'ignore_list' => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
     124                            'consumer_key' => WooMailerLiteOptions::get('consumerKey', null),
     125                            'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
     126                            'group_id' => WooMailerLiteOptions::get('group.id'),
     127                            'resubscribe' => WooMailerLiteOptions::get('settings.resubscribe'),
     128                            'create_segments' => false
     129                        ]);
     130                    }
    121131                } else {
    122132                    $product->exclude_from_automations = $product->ignored;
  • woo-mailerlite/tags/3.1.13/includes/WooMailerLite.php

    r3462363 r3491060  
    115115        $this->loader->add_action('woocommerce_product_bulk_and_quick_edit', WooMailerLiteAdminSettingsController::instance(), 'updateIgnoreProductsBulkAndQuickEdit', 10, 2);
    116116        $this->loader->add_filter('script_loader_tag', $pluginAdmin, 'addModuleTypeScript', 10, 3);
    117         $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueScripts');
    118         $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueStyles');
     117        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueScripts', 20);
     118        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueStyles', 20);
     119        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'removeConflictingSelect2', 9999);
    119120        $this->loader->add_action('admin_menu', $pluginAdmin, 'addPluginAdminMenu', 71);
    120121        $this->loader->add_action('wp_ajax_woo_mailerlite_handle_connect_account', WooMailerLiteAdminWizardController::instance(), 'handleConnectAccount');
  • woo-mailerlite/tags/3.1.13/includes/api/WooMailerLiteRewriteApi.php

    r3484689 r3491060  
    33class WooMailerLiteRewriteApi extends WooMailerLiteApi
    44{
    5     const BASE_URL = 'https://connect.mailerlite.com/api';
     5    const DEFAULT_BASE_URL = 'https://connect.mailerlite.com/api';
    66
    77    public function __construct()
    88    {
    9         parent::__construct(self::BASE_URL);
     9        $baseUrl = $this->getBaseUrl();
     10        parent::__construct($baseUrl);
     11    }
     12
     13    /**
     14     * Base URL for the Rewrite API. Override for tests via MAILERLITE_API_URL env var
     15     * or by defining MAILERLITE_API_URL in wp-config (e.g. wp-env config in .wp-env.json).
     16     *
     17     * @return string
     18     */
     19    private function getBaseUrl()
     20    {
     21        if (defined('MAILERLITE_API_URL')) {
     22            return MAILERLITE_API_URL;
     23        }
     24        return self::DEFAULT_BASE_URL;
    1025    }
    1126
  • woo-mailerlite/tags/3.1.13/includes/common/WooMailerLiteOptions.php

    r3484689 r3491060  
    5959
    6060                if ($key === self::$apiKey && !empty($options[$key])) {
    61                     $decrypted = WooMailerLiteEncryption::instance()->decrypt($options[$key]);
    62                     if ($decrypted !== false) {
    63                         return $decrypted;
    64                     } else {
    65                         self::update($key, WooMailerLiteEncryption::instance()->encrypt($options[$key]));
    66                     }
     61                    return self::handleApiKeyRetrieval($options[$key]);
    6762                }
    6863                return $options[$key];
     
    7671    public static function update($key, $value)
    7772    {
     73        if ($key === self::$apiKey && !empty($value) && !self::isEncryptedData($value)) {
     74            $encrypted = WooMailerLiteEncryption::instance()->encrypt($value);
     75            if ($encrypted !== false) {
     76                $value = $encrypted;
     77            } else {
     78                WooMailerLiteLog()->error('WooMailerLite: Encryption failed, storing as plain text temporarily.');
     79            }
     80        }
     81
    7882        $options =  get_option(self::$key, []);
    7983        $options[$key] = $value;
     
    125129        return in_array($status, self::PENDING_ORDER_STATUSES);
    126130    }
     131
     132    private static function isEncryptedData($value) {
     133        return is_string($value)
     134            && strlen($value) > 50
     135            && preg_match('/^[A-Za-z0-9+\/]+=*$/', $value)
     136            && base64_decode($value, true) !== false;
     137    }
     138
     139    private static function handleApiKeyRetrieval($value)
     140    {
     141        if (self::isEncryptedData($value)) {
     142            $decrypted = WooMailerLiteEncryption::instance()->decrypt($value);
     143            if ($decrypted !== false) {
     144                return $decrypted;
     145            } else {
     146                WooMailerLiteLog()->error('WooMailerLite: API key decryption failed. Re-enter API key.');
     147                return null;
     148            }
     149        } else {
     150            self::update(self::$apiKey, $value);
     151            return $value;
     152        }
     153    }
    127154}
  • woo-mailerlite/tags/3.1.13/includes/controllers/WooMailerLiteController.php

    r3421835 r3491060  
    8686                        }
    8787
    88                         if (in_array('int', $validation) && !ctype_digit(strval($this->request[$key]))) {
    89                             throw new Exception("The $key field must be an integer.");
     88                        if (in_array('int', $validation) && (!ctype_digit(strval($this->request[$key])) || (int)$this->request[$key] <= 0)) {
     89                            throw new Exception("The $key field must be a positive integer.");
    9090                        }
    9191
  • woo-mailerlite/tags/3.1.13/includes/jobs/WooMailerLiteAbstractJob.php

    r3421835 r3491060  
    2424    public static function dispatch(array $data = []): void
    2525    {
     26        $data['attempts'] = $data['attempts'] ?? 0;
     27
     28        // In local/test (e.g. wp-env), run sync immediately so jobs don't stay pending.
     29        if (!isset($data['selfMechanism']['sync']) && defined('WOO_MAILERLITE_SYNC_IMMEDIATE') && WOO_MAILERLITE_SYNC_IMMEDIATE) {
     30            $data['selfMechanism']['sync'] = true;
     31        }
     32
    2633        $jobClass = static::class;
    2734        $objectId = 0;
    28 
    29         $data['attempts'] = $data['attempts'] ?? 0;
    3035
    3136        if ((!isset($data['selfMechanism']['sync']) || !$data['selfMechanism']['sync']) && function_exists('as_enqueue_async_action')) {
  • woo-mailerlite/tags/3.1.13/public/css/mailerlite-select2.css

    r3338878 r3491060  
    55    padding-left: 0;
    66    border-radius: 0.25rem;}
    7 .select2-dropdown {
     7.select2-container--mailerlite .select2-dropdown {
    88    border-color: #d1d5db;
    99}
     
    5252    background-color: #eee;
    5353    cursor: default; }
    54 .select2-container--mailerlite.select2-container--disabled .select2-selection--single .select2-selection__clear, .select2-container--default .select2-selection--multiple .select2-selection__clear {
     54.select2-container--mailerlite.select2-container--disabled .select2-selection--single .select2-selection__clear {
    5555    display: none; }
    5656
     
    5959    border-width: 0 4px 5px 4px; }
    6060
    61 .select2-container--mailerlite .select2-selection--multiple,.select2-container--default .select2-selection--multiple {
     61.select2-container--mailerlite .select2-selection--multiple {
    6262    background-color: white;
    6363    border: 1px solid #d1d5db;
     
    6767    padding-right: 5px;
    6868    position: relative; }
    69 .select2-container--default.select2-container--focus .select2-selection--multiple {
     69.select2-container--mailerlite.select2-container--focus .select2-selection--multiple {
    7070    border-color: #d1d5db;
    7171}
     
    7878}
    7979.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice {
    80     background-color: #e4e4e4;
    81     border: 1px solid #d1d5db;
    82     border-radius: 4px;
     80    display: inline-flex;
     81    align-items: center;
     82    background-color: #e5e7eb;
     83    border: 0;
     84    border-radius: 2px;
    8385    box-sizing: border-box;
    84     display: inline-block;
     86    font-size: 13px;
    8587    margin-left: 5px;
    8688    margin-top: 5px;
    87     padding: 0;
    88     padding-left: 20px;
    89     position: relative;
    9089    max-width: 100%;
    9190    overflow: hidden;
     91    padding: 2px 5px;
     92    position: relative;
     93    vertical-align: bottom;
     94    white-space: nowrap;
     95}
     96.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__display {
     97    color: rgba(0,0,0,0.75);
     98    cursor: default;
     99    min-width: 0;
     100    order: 0;
     101    overflow: hidden;
     102    padding-right: 4px;
    92103    text-overflow: ellipsis;
    93     vertical-align: bottom;
    94     white-space: nowrap; }
    95 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__display {
    96     cursor: default;
    97     color: rgba(0,0,0,0.75);
    98     padding-left: 2px;
    99     padding-right: 5px;
    100104}
    101105.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove {
    102106    background-color: transparent;
    103107    border: none;
    104     border-right: 1px solid #aaa;
    105     border-top-left-radius: 4px;
    106     border-bottom-left-radius: 4px;
    107108    color: #999;
    108109    cursor: pointer;
    109     font-size: 1em;
    110     font-weight: bold;
    111     padding: 0 4px;
    112     position: absolute;
    113     left: 0;
    114     top: 0; }
    115 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover, .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:focus {
    116     background-color: #f1f1f1;
    117     color: #333;
    118     outline: none; }
     110    flex-shrink: 0;
     111    font-size: 0.875rem;
     112    font-weight: normal;
     113    line-height: 1;
     114    order: 1;
     115    padding: 0 2px;
     116    position: static;
     117}
     118.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover,
     119.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:focus {
     120    color: #555;
     121    background-color: transparent;
     122    outline: none;
     123}
    119124
    120125.select2-container--mailerlite[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
     
    150155    display: none; }
    151156
    152 .select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--single, .select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--multiple {
     157.select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--single,
     158.select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--multiple {
    153159    border-top-left-radius: 0;
    154160    border-top-right-radius: 0; }
    155161
    156 .select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--single, .select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--multiple {
     162.select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--single,
     163.select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--multiple {
    157164    border-bottom-left-radius: 0;
    158165    border-bottom-right-radius: 0; }
     
    212219    padding: 6px; }
    213220
    214 .select2-results__option {
     221.select2-container--mailerlite .select2-results__option {
    215222    padding-right: 20px;
    216223    vertical-align: middle;
    217224}
    218225
    219 .select2-results__option:before {
     226.select2-container--mailerlite .select2-results__option:before {
    220227    content: "";
    221228    display: inline-block;
     
    251258    border-width: 2px;
    252259}
    253 .select2-container--open .select2-dropdown--below {
     260.select2-container--mailerlite.select2-container--open .select2-dropdown--below {
    254261    border-color: transparent;
    255262    border-radius: 6px;
     
    258265}
    259266
    260 .select2-selection .select2-selection--multiple:after {
    261     content: 'hhghgh';
    262 }
    263 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice {
    264     background-color: #e5e7eb;
    265     border: 0;
    266     border-radius: 2px;
    267     font-size: 13px;
    268     margin-bottom: 0;
    269     padding-top: 2px;
    270     padding-bottom: 2px;
    271     padding-left: 5px;
    272     padding-right: 14px;
    273 }
    274 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove {
    275     border-right: 0;
    276     color: rgba(0,0,0,0.75);
    277     font-weight: normal;
    278     font-size: 1rem;
    279     padding-right: 5px;
    280     right: 0;
    281     left: auto;
    282 }
    283 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover {
    284     color: #9b9b9b;
    285     background-color: #e5e7eb;
    286 }
    287 .select2-container--mailerlite .select2-selection--multiple, .select2-container--default.select2-container--focus .select2-selection--multiple {
     267.select2-container--mailerlite .select2-selection--multiple,
     268.select2-container--mailerlite.select2-container--focus .select2-selection--multiple {
    288269    border-width: 1px !important;
    289270    border-color: #d1d5db !important;
    290271}
    291 .select2-results__option.select2-results__option--selected:before {
     272.select2-container--mailerlite .select2-results__option.select2-results__option--selected:before {
    292273    color: #fff;
    293274    background-color: #09c269;
    294275    border: 0;
    295276    display: inline-block;
    296     content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E");
     277    content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C%2Fsvg%3E");
    297278    padding-left: 0;
    298279    width: 14px;
    299280    height: 14px;
    300281}
    301 .select2-results__option.select2-results__message:before {
     282.select2-container--mailerlite .select2-results__option.select2-results__message:before {
    302283    content: none;
    303284}
     
    312293    position: absolute;
    313294}
    314 .wc-wp-version-gte-53 .select2-container.select2-container--open .select2-selection--multiple {
     295.woo-ml-native-select {
     296    border: 1px solid #d1d5db;
     297    border-radius: 0.25rem;
     298    height: 38px;
     299    width: 100%;
     300    padding: 0 12px;
     301    font-size: 14px;
     302    color: rgba(0,0,0,0.75);
     303    background-color: #fff;
     304    appearance: none;
     305    background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E");
     306    background-repeat: no-repeat;
     307    background-position: right 12px center;
     308    background-size: 14px 14px;
     309    cursor: pointer;
     310}
     311.woo-ml-native-select:focus {
     312    outline: none;
     313    border-color: #000;
     314}
     315.woo-ml-native-select:disabled {
     316    background-color: #eee;
     317    cursor: default;
     318}
     319
     320.wc-wp-version-gte-53 .select2-container--mailerlite.select2-container--open .select2-selection--multiple {
    315321    border-width: 1px;
    316322    border-color: #d1d5db;
  • woo-mailerlite/tags/3.1.13/woo-mailerlite.php

    r3484689 r3491060  
    1616 * Plugin URI:        https://mailerlite.com
    1717 * Description:       Official MailerLite integration for WooCommerce. Track sales and campaign ROI, import products details, automate emails based on purchases and seamlessly add your customers to your email marketing lists via WooCommerce's checkout process.
    18  * Version:           3.1.12
     18 * Version:           3.1.13
    1919 * Author:            MailerLite
    2020 * Author URI:        https://mailerlite.com
     
    4040 * Update when you release new versions.
    4141 */
    42 define( 'WOO_MAILERLITE_VERSION', '3.1.12' );
     42define( 'WOO_MAILERLITE_VERSION', '3.1.13' );
    4343
    4444define('WOO_MAILERLITE_ASYNC_JOBS', false);
  • woo-mailerlite/trunk/README.txt

    r3484689 r3491060  
    66Tested up to: 6.8.2
    77Requires PHP: 7.2.5
    8 Stable tag: 3.1.12
     8Stable tag: 3.1.13
    99License: GPLv3 or later
    1010License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    8484
    8585== Changelog ==
     86= 3.1.13 (25th March 2026) =
     87* Bug fixes and performance improvements
     88
    8689= 3.1.12 (17th March 2026) =
    8790* Bug fixes
  • woo-mailerlite/trunk/admin/WooMailerLiteAdmin.php

    r3415073 r3491060  
    2323    public function enqueueScripts($hook)
    2424    {
     25        if ($hook === 'edit.php' && isset($_GET['post_type']) && $_GET['post_type'] === 'product') {
     26            wp_enqueue_script('woo-mailerlite-quick-edit', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-quick-edit.js', ['jquery', 'inline-edit-post'], null, true);
     27            return;
     28        }
     29
    2530        if ($hook !== 'woocommerce_page_mailerlite') {
    2631            return;
    2732        }
     33
     34        wp_dequeue_script('select2');
     35        wp_deregister_script('select2');
     36
    2837        wp_enqueue_script('woo-mailerlite-vue-cdn', 'https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.prod.js', [], null, true);
    2938        wp_localize_script('woo-mailerlite-admin', 'woo_mailerlite_admin_data', array(
     
    3241        ));
    3342
    34         wp_enqueue_script('style2-script', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js');
    35         wp_enqueue_script('woo-mailerlite-admin', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-app.js', ['jquery', 'woo-mailerlite-vue-cdn'], null, true);
     43        wp_enqueue_script('woo-mailerlite-select2', plugin_dir_url(__FILE__) . 'assets/js/lib/select2.min.js', ['jquery'], WOO_MAILERLITE_VERSION, true);
     44        wp_enqueue_script('woo-mailerlite-admin', plugin_dir_url(__FILE__) . '../admin/assets/js/ml-app.js', ['jquery', 'woo-mailerlite-vue-cdn', 'woo-mailerlite-select2'], null, true);
    3645
    3746    }
     
    4150            return;
    4251        }
     52
     53        wp_dequeue_style('select2');
     54        wp_deregister_style('select2');
     55
    4356        wp_enqueue_style('woo-mailerlite-admin-css', plugin_dir_url( __FILE__ ) . '../admin/assets/css/admin.css', false, WOO_MAILERLITE_VERSION);
    44         wp_enqueue_style('style2-style', 'https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css');
    45         wp_enqueue_style('style2-mailerlite-style', plugin_dir_url( __FILE__ ) . '../public/css/mailerlite-select2.css',false, WOO_MAILERLITE_VERSION);
     57        wp_enqueue_style('woo-mailerlite-select2-css', plugin_dir_url( __FILE__ ) . 'assets/css/lib/select2.min.css', false, WOO_MAILERLITE_VERSION);
     58        wp_enqueue_style('woo-mailerlite-select2-theme', plugin_dir_url( __FILE__ ) . '../public/css/mailerlite-select2.css', ['woo-mailerlite-select2-css'], WOO_MAILERLITE_VERSION);
     59    }
     60
     61    public function removeConflictingSelect2($hook)
     62    {
     63        if ($hook !== 'woocommerce_page_mailerlite') {
     64            return;
     65        }
     66
     67        global $wp_scripts, $wp_styles;
     68
     69        $scriptPatterns = ['select2', 'selectWoo', 'wc-enhanced-select'];
     70
     71        foreach ($wp_scripts->registered as $handle => $script) {
     72            if ($handle === 'woo-mailerlite-select2') {
     73                continue;
     74            }
     75            foreach ($scriptPatterns as $pattern) {
     76                if (strpos($script->src, $pattern) !== false) {
     77                    wp_dequeue_script($handle);
     78                    wp_deregister_script($handle);
     79                    break;
     80                }
     81            }
     82        }
     83
     84        $stylePatterns = ['select2', 'selectWoo'];
     85
     86        foreach ($wp_styles->registered as $handle => $style) {
     87            if ($handle === 'woo-mailerlite-select2-css' || $handle === 'woo-mailerlite-select2-theme') {
     88                continue;
     89            }
     90            foreach ($stylePatterns as $pattern) {
     91                if (strpos($style->src, $pattern) !== false) {
     92                    wp_dequeue_style($handle);
     93                    wp_deregister_style($handle);
     94                    break;
     95                }
     96            }
     97        }
    4698    }
    4799
     
    115167    public function populateIgnoreProductBlock($column, $post_id)
    116168    {
    117         $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
    118169
    119170        switch ($column) {
    120171            case 'name' :
     172                $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
    121173                ?>
    122174                <div class="hidden ml_ignore_product_inline" id="ml_ignore_product_inline_<?=intval($post_id) ?>">
    123                     <div id="_ml_ignore_product"><?php echo array_key_exists($post_id, $ignoredProducts) ? 'yes' : 'no' ?></div>
     175                    <div class="_ml_ignore_product"><?php echo array_key_exists($post_id, $ignoredProducts) ? 'yes' : 'no' ?></div>
    124176                </div>
    125177                <?php
  • woo-mailerlite/trunk/admin/assets/js/components/Forms/CustomSelect.js

    r3338878 r3491060  
    22import eventBus from "../eventBus.js";
    33const template = `
    4     <select ref="wooMlSubGroup" class="wc-enhanced-select">
     4    <select ref="wooMlSubGroup" class="woo-ml-group-select">
    55        <option value="" disabled >{{ placeholder }}</option>
    66    </select>
  • woo-mailerlite/trunk/admin/assets/js/components/Forms/SyncFields.js

    r3338878 r3491060  
    11const template = `
    2     <select v-model="setSyncFields"/>
     2    <select v-model="setSyncFields" data-testid="sync-fields-select"/>
    33`;
    44
  • woo-mailerlite/trunk/admin/assets/js/components/Modals/Modals.js

    r3338878 r3491060  
    55const template = `
    66<!-- Create Group Modal -->
    7 <div v-if="openCreateGroupModal" class="woo-ml-wizard-modal" id="wooMlWizardCreateGroupModal" role="dialog">
     7<div v-if="openCreateGroupModal" class="woo-ml-wizard-modal" id="wooMlWizardCreateGroupModal" role="dialog" data-testid="create-group-modal">
    88    <div class="woo-ml-wizard-modal-parent">
    99        <div class="woo-ml-wizard-modal-container">
     
    1515                <div class="woo-ml-wizard-modal-body">
    1616                    <div class="create-group-input">
    17                         <input ref="wooMlCreateGroup" type="text" name="createGroup" placeholder="Enter group name" v-model="createGroupName" class="">
     17                        <input ref="wooMlCreateGroup" type="text" name="createGroup" placeholder="Enter group name" v-model="createGroupName" class="" data-testid="create-group-name-input">
    1818                    </div>
    1919                    <div class="modal-button-ml">
    2020                        <button @click="openCreateGroupModal = false" type="button" class="btn-secondary-ml woo-ml-close" style="margin-right: 12px;">Close</button>
    21                         <button @click="createGroup" ref="createGroup" type="button" class="btn-primary-ml"><span class="woo-ml-button-text">Create group</span></button>
     21                        <button @click="createGroup" ref="createGroup" type="button" class="btn-primary-ml" data-testid="create-group-submit-btn"><span class="woo-ml-button-text">Create group</span></button>
    2222                    </div>
    2323                </div>
  • woo-mailerlite/trunk/admin/assets/js/components/Views/Settings.js

    r3377455 r3491060  
    66
    77const template = `
    8 <div v-if="syncInProgress || asyncSyncInProgress" class="woo-ml-sync-loading-container">
     8<div v-if="syncInProgress || asyncSyncInProgress" class="woo-ml-sync-loading-container" data-testid="sync-in-progress">
    99    <strong>Sync in progress...</strong>
    1010    <p>This will continue as long as you stay on this page.
     
    2020                    <label for="wooMlSubGroup" class="settings-label mb-3-ml">Subscriber group</label>
    2121                    <label class="input-mailerlite mb-2-ml" style="display: flex;">
    22                         <custom-select
    23                             id="wooMlSubGroup"
    24                             class="wc-enhanced-select"
    25                             name="subscriber-group"
     22                        <custom-select
     23                            id="wooMlSubGroup"
     24                            name="subscriber-group"
    2625                            :options="groups"
    2726                            v-model="selectedGroup"
     
    4342                    </label>
    4443                    <label v-if="ignoredProducts.length !== 0" class="input-mailerlite" style="cursor: default;">
    45                         <div multiple class="wc-enhanced-select" name="ignore_product_list"
     44                        <div multiple name="ignore_product_list"
    4645                             style="min-height: 26px; padding-left: 8px; display: flex; flex-direction: row; overflow: hidden; flex-wrap: wrap; padding: 4px; padding-top: 0; border: 1px solid #d1d5db; border-radius: 0.25rem;">
    47                             <option v-for="(item, index) in ignoredProducts" :key="index" style="background-color: #e5e7eb; padding: 2px 5px; border-radius: 2px; font-size: 13px; margin-right: 4px; margin-top: 4px;">{{ item }}</option>
     46                            <span v-for="(item, index) in ignoredProducts" :key="index" style="background-color: #e5e7eb; padding: 2px 5px; border-radius: 2px; font-size: 13px; margin-right: 4px; margin-top: 4px;">{{ item }}</span>
    4847                        </div>
    4948                    </label>
     
    7675                        </div>
    7776                    </label>
    78                     <label class="input-mailerlite">
    79                         <sync-fields
    80                         id="sync_fields"
    81                         multiple="multiple"
    82                         data-placeholder="Click to select fields you want to sync"
    83                         class="wc-enhanced-select"
     77                    <label class="input-mailerlite" data-testid="settings-sync-fields">
     78                        <sync-fields
     79                        id="sync_fields"
     80                        multiple="multiple"
     81                        data-placeholder="Click to select fields you want to sync"
    8482                        style="width: 100%;"
    8583                        :options="syncFields"
     
    102100                    </button>
    103101                    <button @click.prevent="startSync" v-if="totalUntrackedResources && !syncInProgress" type="button" class="btn btn-secondary-ml flex-start-ml"
    104                             data-woo-ml-reset-resources-sync="true" ref="startSync"><span class="woo-ml-button-text">Synchronize {{ totalUntrackedResources }} untracked resources</span>
     102                            data-woo-ml-reset-resources-sync="true" ref="startSync" data-testid="sync-untracked-resources-btn"><span class="woo-ml-button-text">Synchronize {{ totalUntrackedResources }} untracked resources</span>
    105103                    </button>
    106104                </div>
     
    119117                               value="yes"
    120118                               id="subscribe_checkout_checkbox"
     119                               data-testid="subscribe-checkout-checkbox"
    121120                        />
    122121                        <label for="subscribe_checkout_checkbox" class="settings-label-medium">Enable list subscription via checkout page.</label>
     
    140139                        <label for="wooMlSubGroup" class="settings-label mb-3-ml">Subscribe checkbox position</label>
    141140                        <label class="input-mailerlite">
    142                             <select 
    143                               class="wc-enhanced-select"
    144                               name="checkout_position" 
     141                            <select
     142                              class="woo-ml-native-select"
     143                              name="checkout_position"
    145144                              :disabled="!settings.subscribeOnCheckout"
    146145                              v-model="settings.selectedCheckoutPosition"
     
    269268
    270269        <div style="display: flex; justify-content: flex-end; margin-top: 2rem;">
    271             <button @click.prevent="updateSettings" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="submit" class="btn-primary-ml" style="margin-top: 2rem;" id="updateSettingsBtn"><span class="woo-ml-button-text">Save changes</span></button>
     270            <button @click.prevent="updateSettings" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="submit" class="btn-primary-ml" style="margin-top: 2rem;" id="updateSettingsBtn" data-testid="update-settings-btn"><span class="woo-ml-button-text">Save changes</span></button>
    272271        </div>
    273272     <div class="settings-block">
  • woo-mailerlite/trunk/admin/assets/js/components/Views/Wizard.js

    r3338878 r3491060  
    2828            <label for="wooMlApiKey" class="settings-label mb-3-ml">API key</label>
    2929            <div class="api-key-input">
    30                 <input ref="wooMlApiKey" type="text" name="api-key" placeholder="Enter your MailerLite API key" v-model="wooMlApiKey" :disabled="isLoading">
    31                 <button @click="connectAccount" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="button" id="wooMlWizardApiKeyBtn" class="btn-primary-ml"><span class="woo-ml-button-text">Connect account</span></button>
     30                <input ref="wooMlApiKey" type="text" name="api-key" placeholder="Enter your MailerLite API key" v-model="wooMlApiKey" :disabled="isLoading" data-testid="wizard-api-key">
     31                <button @click="connectAccount" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" type="button" id="wooMlWizardApiKeyBtn" class="btn-primary-ml" data-testid="wizard-connect-account-btn"><span class="woo-ml-button-text">Connect account</span></button>
    3232            </div>
    3333            <div class="signup-link-ml">
     
    6161            <label for="wooMlSubGroup" class="settings-label mb-3-ml">Group</label>
    6262            <label class="input-mailerlite mb-2-ml" style="display: flex;">
    63                 <custom-select
    64                     ref="wooMlSubGroupComponent"
    65                     class="wc-enhanced-select"
    66                     name="subscriber-group"
     63                <custom-select
     64                    ref="wooMlSubGroupComponent"
     65                    name="subscriber-group"
    6766                    style="width: 100%;"
    6867                    :options="groups"
     
    7069                    placeholder="Select group"
    7170                ></custom-select>
    72                 <button @click="createGroup" id="createGroupModal" type="button" class="btn-secondary-ml" style="margin-left: 0.5rem; white-space: nowrap;">Create group</button>
     71                <button @click="createGroup" id="createGroupModal" type="button" class="btn-secondary-ml" style="margin-left: 0.5rem; white-space: nowrap;" data-testid="wizard-create-group-btn">Create group</button>
    7372            </label>
    7473           
     
    109108                </div>
    110109            </label>
    111             <label class="input-mailerlite">
     110            <label class="input-mailerlite" data-testid="wizard-sync-fields">
    112111                <sync-fields
    113112                    id="sync_fields"
     
    115114                    v-model="selectedSyncFields"
    116115                    placeholder="Click to select fields you want to sync"
    117                     class="wc-enhanced-select" style="width: 100%;"
     116                    style="width: 100%;"
    118117                    :options="syncFields"
    119118                    :model-value="selectedSyncFields"
     
    123122
    124123        <div class="settings-block" style="display: flex; justify-content: space-between; padding-top: 2rem;">
    125             <button @click="startImport" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" id="startImport" type="button" class="btn-primary-ml"><span class="woo-ml-button-text">Next</span></button>
     124            <button @click="startImport" :class="{ 'woo-ml-button-loading': isLoading }" :disabled="isLoading" id="startImport" type="button" class="btn-primary-ml" data-testid="wizard-start-import-btn"><span class="woo-ml-button-text">Next</span></button>
    126125        </div>
    127126    </div>
  • woo-mailerlite/trunk/admin/controllers/WooMailerLiteAdminSettingsController.php

    r3470352 r3491060  
    1818
    1919        $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
     20
     21        $wasIgnored = $product->ignored;
     22
    2023        if ($this->request('ml_ignore_product')) {
    2124            $product->ignored = true;
     
    3437
    3538        if ($this->apiClient()->isClassic()) {
    36             $this->apiClient()->setConsumerData([
    37                 'store'           => home_url(),
    38                 'currency'        => get_option('woocommerce_currency'),
    39                 'ignore_list'     => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
    40                 'consumer_key'    => WooMailerLiteOptions::get('consumerKey', null),
    41                 'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
    42                 'group_id'        => WooMailerLiteOptions::get('group.id'),
    43                 'resubscribe'     => WooMailerLiteOptions::get('settings.resubscribe'),
    44                 'create_segments' => false
    45             ]);
     39            if ((bool)$wasIgnored !== (bool)$product->ignored) {
     40                $this->apiClient()->setConsumerData([
     41                    'store' => home_url(),
     42                    'currency' => get_option('woocommerce_currency'),
     43                    'ignore_list' => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
     44                    'consumer_key' => WooMailerLiteOptions::get('consumerKey', null),
     45                    'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
     46                    'group_id' => WooMailerLiteOptions::get('group.id'),
     47                    'resubscribe' => WooMailerLiteOptions::get('settings.resubscribe'),
     48                    'create_segments' => false
     49                ]);
     50            }
    4651        } else {
    4752            $product->exclude_from_automations = $product->ignored;
     
    97102
    98103                $ignoredProducts = WooMailerLiteOptions::get('ignored_products', []);
     104
     105                $wasIgnored = $product->ignored;
     106
    99107                if ($this->request('ml_ignore_product')) {
    100108                    $product->ignored = true;
     
    109117                WooMailerLiteOptions::update('ignored_products', $ignoredProducts);
    110118                if ($this->apiClient()->isClassic()) {
    111                     $this->apiClient()->setConsumerData([
    112                         'store'           => home_url(),
    113                         'currency'        => get_option('woocommerce_currency'),
    114                         'ignore_list'     => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
    115                         'consumer_key'    => WooMailerLiteOptions::get('consumerKey', null),
    116                         'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
    117                         'group_id'        => WooMailerLiteOptions::get('group.id'),
    118                         'resubscribe'     => WooMailerLiteOptions::get('settings.resubscribe'),
    119                         'create_segments' => false
    120                     ]);
     119                    if ((bool)$wasIgnored !== (bool)$product->ignored) {
     120                        $this->apiClient()->setConsumerData([
     121                            'store' => home_url(),
     122                            'currency' => get_option('woocommerce_currency'),
     123                            'ignore_list' => array_map('strval', array_keys(WooMailerLiteOptions::get('ignored_products', []))),
     124                            'consumer_key' => WooMailerLiteOptions::get('consumerKey', null),
     125                            'consumer_secret' => WooMailerLiteOptions::get('consumerSecret', null),
     126                            'group_id' => WooMailerLiteOptions::get('group.id'),
     127                            'resubscribe' => WooMailerLiteOptions::get('settings.resubscribe'),
     128                            'create_segments' => false
     129                        ]);
     130                    }
    121131                } else {
    122132                    $product->exclude_from_automations = $product->ignored;
  • woo-mailerlite/trunk/includes/WooMailerLite.php

    r3462363 r3491060  
    115115        $this->loader->add_action('woocommerce_product_bulk_and_quick_edit', WooMailerLiteAdminSettingsController::instance(), 'updateIgnoreProductsBulkAndQuickEdit', 10, 2);
    116116        $this->loader->add_filter('script_loader_tag', $pluginAdmin, 'addModuleTypeScript', 10, 3);
    117         $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueScripts');
    118         $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueStyles');
     117        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueScripts', 20);
     118        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'enqueueStyles', 20);
     119        $this->loader->add_action('admin_enqueue_scripts', $pluginAdmin, 'removeConflictingSelect2', 9999);
    119120        $this->loader->add_action('admin_menu', $pluginAdmin, 'addPluginAdminMenu', 71);
    120121        $this->loader->add_action('wp_ajax_woo_mailerlite_handle_connect_account', WooMailerLiteAdminWizardController::instance(), 'handleConnectAccount');
  • woo-mailerlite/trunk/includes/api/WooMailerLiteRewriteApi.php

    r3484689 r3491060  
    33class WooMailerLiteRewriteApi extends WooMailerLiteApi
    44{
    5     const BASE_URL = 'https://connect.mailerlite.com/api';
     5    const DEFAULT_BASE_URL = 'https://connect.mailerlite.com/api';
    66
    77    public function __construct()
    88    {
    9         parent::__construct(self::BASE_URL);
     9        $baseUrl = $this->getBaseUrl();
     10        parent::__construct($baseUrl);
     11    }
     12
     13    /**
     14     * Base URL for the Rewrite API. Override for tests via MAILERLITE_API_URL env var
     15     * or by defining MAILERLITE_API_URL in wp-config (e.g. wp-env config in .wp-env.json).
     16     *
     17     * @return string
     18     */
     19    private function getBaseUrl()
     20    {
     21        if (defined('MAILERLITE_API_URL')) {
     22            return MAILERLITE_API_URL;
     23        }
     24        return self::DEFAULT_BASE_URL;
    1025    }
    1126
  • woo-mailerlite/trunk/includes/common/WooMailerLiteOptions.php

    r3484689 r3491060  
    5959
    6060                if ($key === self::$apiKey && !empty($options[$key])) {
    61                     $decrypted = WooMailerLiteEncryption::instance()->decrypt($options[$key]);
    62                     if ($decrypted !== false) {
    63                         return $decrypted;
    64                     } else {
    65                         self::update($key, WooMailerLiteEncryption::instance()->encrypt($options[$key]));
    66                     }
     61                    return self::handleApiKeyRetrieval($options[$key]);
    6762                }
    6863                return $options[$key];
     
    7671    public static function update($key, $value)
    7772    {
     73        if ($key === self::$apiKey && !empty($value) && !self::isEncryptedData($value)) {
     74            $encrypted = WooMailerLiteEncryption::instance()->encrypt($value);
     75            if ($encrypted !== false) {
     76                $value = $encrypted;
     77            } else {
     78                WooMailerLiteLog()->error('WooMailerLite: Encryption failed, storing as plain text temporarily.');
     79            }
     80        }
     81
    7882        $options =  get_option(self::$key, []);
    7983        $options[$key] = $value;
     
    125129        return in_array($status, self::PENDING_ORDER_STATUSES);
    126130    }
     131
     132    private static function isEncryptedData($value) {
     133        return is_string($value)
     134            && strlen($value) > 50
     135            && preg_match('/^[A-Za-z0-9+\/]+=*$/', $value)
     136            && base64_decode($value, true) !== false;
     137    }
     138
     139    private static function handleApiKeyRetrieval($value)
     140    {
     141        if (self::isEncryptedData($value)) {
     142            $decrypted = WooMailerLiteEncryption::instance()->decrypt($value);
     143            if ($decrypted !== false) {
     144                return $decrypted;
     145            } else {
     146                WooMailerLiteLog()->error('WooMailerLite: API key decryption failed. Re-enter API key.');
     147                return null;
     148            }
     149        } else {
     150            self::update(self::$apiKey, $value);
     151            return $value;
     152        }
     153    }
    127154}
  • woo-mailerlite/trunk/includes/controllers/WooMailerLiteController.php

    r3421835 r3491060  
    8686                        }
    8787
    88                         if (in_array('int', $validation) && !ctype_digit(strval($this->request[$key]))) {
    89                             throw new Exception("The $key field must be an integer.");
     88                        if (in_array('int', $validation) && (!ctype_digit(strval($this->request[$key])) || (int)$this->request[$key] <= 0)) {
     89                            throw new Exception("The $key field must be a positive integer.");
    9090                        }
    9191
  • woo-mailerlite/trunk/includes/jobs/WooMailerLiteAbstractJob.php

    r3421835 r3491060  
    2424    public static function dispatch(array $data = []): void
    2525    {
     26        $data['attempts'] = $data['attempts'] ?? 0;
     27
     28        // In local/test (e.g. wp-env), run sync immediately so jobs don't stay pending.
     29        if (!isset($data['selfMechanism']['sync']) && defined('WOO_MAILERLITE_SYNC_IMMEDIATE') && WOO_MAILERLITE_SYNC_IMMEDIATE) {
     30            $data['selfMechanism']['sync'] = true;
     31        }
     32
    2633        $jobClass = static::class;
    2734        $objectId = 0;
    28 
    29         $data['attempts'] = $data['attempts'] ?? 0;
    3035
    3136        if ((!isset($data['selfMechanism']['sync']) || !$data['selfMechanism']['sync']) && function_exists('as_enqueue_async_action')) {
  • woo-mailerlite/trunk/public/css/mailerlite-select2.css

    r3338878 r3491060  
    55    padding-left: 0;
    66    border-radius: 0.25rem;}
    7 .select2-dropdown {
     7.select2-container--mailerlite .select2-dropdown {
    88    border-color: #d1d5db;
    99}
     
    5252    background-color: #eee;
    5353    cursor: default; }
    54 .select2-container--mailerlite.select2-container--disabled .select2-selection--single .select2-selection__clear, .select2-container--default .select2-selection--multiple .select2-selection__clear {
     54.select2-container--mailerlite.select2-container--disabled .select2-selection--single .select2-selection__clear {
    5555    display: none; }
    5656
     
    5959    border-width: 0 4px 5px 4px; }
    6060
    61 .select2-container--mailerlite .select2-selection--multiple,.select2-container--default .select2-selection--multiple {
     61.select2-container--mailerlite .select2-selection--multiple {
    6262    background-color: white;
    6363    border: 1px solid #d1d5db;
     
    6767    padding-right: 5px;
    6868    position: relative; }
    69 .select2-container--default.select2-container--focus .select2-selection--multiple {
     69.select2-container--mailerlite.select2-container--focus .select2-selection--multiple {
    7070    border-color: #d1d5db;
    7171}
     
    7878}
    7979.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice {
    80     background-color: #e4e4e4;
    81     border: 1px solid #d1d5db;
    82     border-radius: 4px;
     80    display: inline-flex;
     81    align-items: center;
     82    background-color: #e5e7eb;
     83    border: 0;
     84    border-radius: 2px;
    8385    box-sizing: border-box;
    84     display: inline-block;
     86    font-size: 13px;
    8587    margin-left: 5px;
    8688    margin-top: 5px;
    87     padding: 0;
    88     padding-left: 20px;
    89     position: relative;
    9089    max-width: 100%;
    9190    overflow: hidden;
     91    padding: 2px 5px;
     92    position: relative;
     93    vertical-align: bottom;
     94    white-space: nowrap;
     95}
     96.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__display {
     97    color: rgba(0,0,0,0.75);
     98    cursor: default;
     99    min-width: 0;
     100    order: 0;
     101    overflow: hidden;
     102    padding-right: 4px;
    92103    text-overflow: ellipsis;
    93     vertical-align: bottom;
    94     white-space: nowrap; }
    95 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__display {
    96     cursor: default;
    97     color: rgba(0,0,0,0.75);
    98     padding-left: 2px;
    99     padding-right: 5px;
    100104}
    101105.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove {
    102106    background-color: transparent;
    103107    border: none;
    104     border-right: 1px solid #aaa;
    105     border-top-left-radius: 4px;
    106     border-bottom-left-radius: 4px;
    107108    color: #999;
    108109    cursor: pointer;
    109     font-size: 1em;
    110     font-weight: bold;
    111     padding: 0 4px;
    112     position: absolute;
    113     left: 0;
    114     top: 0; }
    115 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover, .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:focus {
    116     background-color: #f1f1f1;
    117     color: #333;
    118     outline: none; }
     110    flex-shrink: 0;
     111    font-size: 0.875rem;
     112    font-weight: normal;
     113    line-height: 1;
     114    order: 1;
     115    padding: 0 2px;
     116    position: static;
     117}
     118.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover,
     119.select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:focus {
     120    color: #555;
     121    background-color: transparent;
     122    outline: none;
     123}
    119124
    120125.select2-container--mailerlite[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
     
    150155    display: none; }
    151156
    152 .select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--single, .select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--multiple {
     157.select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--single,
     158.select2-container--mailerlite.select2-container--open.select2-container--above .select2-selection--multiple {
    153159    border-top-left-radius: 0;
    154160    border-top-right-radius: 0; }
    155161
    156 .select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--single, .select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--multiple {
     162.select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--single,
     163.select2-container--mailerlite.select2-container--open.select2-container--below .select2-selection--multiple {
    157164    border-bottom-left-radius: 0;
    158165    border-bottom-right-radius: 0; }
     
    212219    padding: 6px; }
    213220
    214 .select2-results__option {
     221.select2-container--mailerlite .select2-results__option {
    215222    padding-right: 20px;
    216223    vertical-align: middle;
    217224}
    218225
    219 .select2-results__option:before {
     226.select2-container--mailerlite .select2-results__option:before {
    220227    content: "";
    221228    display: inline-block;
     
    251258    border-width: 2px;
    252259}
    253 .select2-container--open .select2-dropdown--below {
     260.select2-container--mailerlite.select2-container--open .select2-dropdown--below {
    254261    border-color: transparent;
    255262    border-radius: 6px;
     
    258265}
    259266
    260 .select2-selection .select2-selection--multiple:after {
    261     content: 'hhghgh';
    262 }
    263 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice {
    264     background-color: #e5e7eb;
    265     border: 0;
    266     border-radius: 2px;
    267     font-size: 13px;
    268     margin-bottom: 0;
    269     padding-top: 2px;
    270     padding-bottom: 2px;
    271     padding-left: 5px;
    272     padding-right: 14px;
    273 }
    274 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove {
    275     border-right: 0;
    276     color: rgba(0,0,0,0.75);
    277     font-weight: normal;
    278     font-size: 1rem;
    279     padding-right: 5px;
    280     right: 0;
    281     left: auto;
    282 }
    283 .select2-container--mailerlite .select2-selection--multiple .select2-selection__choice__remove:hover {
    284     color: #9b9b9b;
    285     background-color: #e5e7eb;
    286 }
    287 .select2-container--mailerlite .select2-selection--multiple, .select2-container--default.select2-container--focus .select2-selection--multiple {
     267.select2-container--mailerlite .select2-selection--multiple,
     268.select2-container--mailerlite.select2-container--focus .select2-selection--multiple {
    288269    border-width: 1px !important;
    289270    border-color: #d1d5db !important;
    290271}
    291 .select2-results__option.select2-results__option--selected:before {
     272.select2-container--mailerlite .select2-results__option.select2-results__option--selected:before {
    292273    color: #fff;
    293274    background-color: #09c269;
    294275    border: 0;
    295276    display: inline-block;
    296     content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E");
     277    content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C%2Fsvg%3E");
    297278    padding-left: 0;
    298279    width: 14px;
    299280    height: 14px;
    300281}
    301 .select2-results__option.select2-results__message:before {
     282.select2-container--mailerlite .select2-results__option.select2-results__message:before {
    302283    content: none;
    303284}
     
    312293    position: absolute;
    313294}
    314 .wc-wp-version-gte-53 .select2-container.select2-container--open .select2-selection--multiple {
     295.woo-ml-native-select {
     296    border: 1px solid #d1d5db;
     297    border-radius: 0.25rem;
     298    height: 38px;
     299    width: 100%;
     300    padding: 0 12px;
     301    font-size: 14px;
     302    color: rgba(0,0,0,0.75);
     303    background-color: #fff;
     304    appearance: none;
     305    background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E");
     306    background-repeat: no-repeat;
     307    background-position: right 12px center;
     308    background-size: 14px 14px;
     309    cursor: pointer;
     310}
     311.woo-ml-native-select:focus {
     312    outline: none;
     313    border-color: #000;
     314}
     315.woo-ml-native-select:disabled {
     316    background-color: #eee;
     317    cursor: default;
     318}
     319
     320.wc-wp-version-gte-53 .select2-container--mailerlite.select2-container--open .select2-selection--multiple {
    315321    border-width: 1px;
    316322    border-color: #d1d5db;
  • woo-mailerlite/trunk/woo-mailerlite.php

    r3484689 r3491060  
    1616 * Plugin URI:        https://mailerlite.com
    1717 * Description:       Official MailerLite integration for WooCommerce. Track sales and campaign ROI, import products details, automate emails based on purchases and seamlessly add your customers to your email marketing lists via WooCommerce's checkout process.
    18  * Version:           3.1.12
     18 * Version:           3.1.13
    1919 * Author:            MailerLite
    2020 * Author URI:        https://mailerlite.com
     
    4040 * Update when you release new versions.
    4141 */
    42 define( 'WOO_MAILERLITE_VERSION', '3.1.12' );
     42define( 'WOO_MAILERLITE_VERSION', '3.1.13' );
    4343
    4444define('WOO_MAILERLITE_ASYNC_JOBS', false);
Note: See TracChangeset for help on using the changeset viewer.