Plugin Directory

Changeset 3377455


Ignore:
Timestamp:
10/13/2025 11:05:39 AM (6 months ago)
Author:
mailerlite
Message:

updated to 3.1.0

Location:
woo-mailerlite/trunk
Files:
30 edited

Legend:

Unmodified
Added
Removed
  • woo-mailerlite/trunk/admin/WooMailerLiteAdmin.php

    r3361673 r3377455  
    4848    public function wooMailerLiteSettingsPageCallback()
    4949    {
    50         $falseApi = false;
    51         if (!WooMailerLiteCache::get('valid_api')) {
    52             $response = WooMailerLiteApi::client()->ping();
    53             if ($response->status === 401) {
    54                 $falseApi = true;
    55 //                WooMailerLiteOptions::deleteAll();
    56             } else {
    57                 WooMailerLiteCache::set('valid_api', true, 86400);
    58             }
    59         }
    60         $untrackedResources = WooMailerLiteCategory::getUntrackedCategoriesCount() + WooMailerLiteProduct::getUntrackedProductsCount() +  WooMailerLiteCustomer::getAll()->count();
     50       $falseApi = false;
     51//        if (!WooMailerLiteCache::get('valid_api')) {
     52//            $response = WooMailerLiteApi::client()->ping();
     53//            if ($response->status === 401) {
     54//                $falseApi = true;
     55////                WooMailerLiteOptions::deleteAll();
     56//            } else {
     57//                WooMailerLiteCache::set('valid_api', true, 86400);
     58//            }
     59//        }
     60        $untrackedResources = WooMailerLiteProduct::getUntrackedProductsCount() +  WooMailerLiteCategory::getUntrackedCategoriesCount() +  WooMailerLiteCustomer::getUntrackedCustomersCount();
    6161
    6262        wp_localize_script('woo-mailerlite-vue-cdn', 'woo_mailerlite_admin_data', [
     
    8181            'settings' => WooMailerLiteOptions::get('settings', []),
    8282            'syncFields' => WooMailerLiteOptions::get('syncFields'),
    83             'asyncSync' => class_exists('ActionScheduler'),
     83            // Need better solution for async sync, adding true for now and force sync (works)
     84            'asyncSync' => WooMailerLiteCache::get('scheduled_jobs') && $untrackedResources,
    8485            'falseApi' => $falseApi,
    8586            'debugMode' => WooMailerLiteOptions::get('debugMode')
  • woo-mailerlite/trunk/admin/assets/js/components/App.js

    r3338878 r3377455  
    3232            selectedFields: [],
    3333            startSync: false,
     34            manualSync: false,
    3435        }
    3536    },
     
    4344              body: new URLSearchParams({
    4445                  action: 'woo_mailerlite_sync_handler',
    45                   nonce: woo_mailerlite_admin_data.woo_mailerlite_admin_nonce
     46                  nonce: woo_mailerlite_admin_data.woo_mailerlite_admin_nonce,
     47                  async: !this.manualSync
    4648              }),
    4749          });
    4850
    49           if (response.status === 200) {
     51          if ([200, 203].includes(response.status)) {
    5052              eventBus.emit('sync-completed', true);
    5153              clearInterval(this.interval);
     
    5759            window.location.reload()
    5860        }
    59         if (this.startSync || ((parseInt(woo_mailerlite_admin_data.currentStep) === 2) && (woo_mailerlite_admin_data.asyncSync && woo_mailerlite_admin_data.sync.syncInProgress))) {
     61        if (woo_mailerlite_admin_data.asyncSync || this.startSync || ((parseInt(woo_mailerlite_admin_data.currentStep) === 2) && woo_mailerlite_admin_data.sync.syncInProgress)) {
    6062            this.syncProcess();
     63            if (!woo_mailerlite_admin_data.asyncSync) {
     64                this.interval = setInterval(() => {
     65                    woo_mailerlite_admin_data.sync.syncInProgress = true
     66                    this.syncProcess();
     67                }, 8000);
     68            }
    6169        }
    6270
     
    6674        });
    6775        eventBus.on('start-sync', (data) => {
     76            this.manualSync = true;
    6877            this.syncProcess();
    6978            this.interval = setInterval(() => {
  • woo-mailerlite/trunk/admin/assets/js/components/Views/Settings.js

    r3338878 r3377455  
    66
    77const template = `
    8 <div v-if="syncInProgress" class="woo-ml-sync-loading-container">
     8<div v-if="syncInProgress || asyncSyncInProgress" class="woo-ml-sync-loading-container">
    99    <strong>Sync in progress...</strong>
    1010    <p>This will continue as long as you stay on this page.
     
    398398          isLoading: false,
    399399          selectedSyncFields: [],
     400          asyncSyncInProgress: woo_mailerlite_admin_data.asyncSync,
    400401      }
    401402    },
  • woo-mailerlite/trunk/admin/controllers/WooMailerLiteAdminSettingsController.php

    r3346776 r3377455  
    4242        } else {
    4343            $product->exclude_from_automations = $product->ignored;
    44             $this->apiClient()->syncProduct(WooMailerLiteOptions::get('shopId'), $product->toArray());
     44            $this->apiClient()->syncProduct(WooMailerLiteOptions::get('shopId'), $product->toArray(), true);
    4545        }
    4646        return true;
     
    7777                $product->exclude_from_automations = $product->ignored ? 1 : 0;
    7878                $product->categories = $product->category_ids;
    79                 $response = $this->apiClient()->syncProduct($shopId, $product->toArray());
     79                $response = $this->apiClient()->syncProduct($shopId, $product->toArray(), true);
    8080                if ($response->success) {
    8181                    $product->tracked = true;
     
    107107                } else {
    108108                    $product->exclude_from_automations = $product->ignored;
    109                     $this->apiClient()->syncProduct(WooMailerLiteOptions::get('shopId'), $product->toArray());
     109                    $this->apiClient()->syncProduct(WooMailerLiteOptions::get('shopId'), $product->toArray(), true);
    110110                }
    111111            }
     
    148148
    149149        $currentStatus = $this->apiClient()->getDoubleOptin();
    150         $currentStatus->data->double_optin = $currentStatus->data->double_optin ?? $currentStatus->data->enabled;
     150        $currentStatus->data->double_optin = $currentStatus->data->double_optin ?? $currentStatus->data->enabled ?? false;
    151151
    152152        if ($this->request('settings.doubleOptIn')) {
  • woo-mailerlite/trunk/admin/controllers/WooMailerLiteAdminSyncController.php

    r3338878 r3377455  
    1010            WooMailerLiteCache::set('manual_sync', true, 18000);
    1111        }
    12         $totalUntrackedResources = WooMailerLiteCategory::getUntrackedCategoriesCount() + WooMailerLiteProduct::getUntrackedProductsCount() +  WooMailerLiteCustomer::getAll()->count();
     12
     13        // save count in cache
     14        $untrackedCategories = WooMailerLiteCategory::getUntrackedCategoriesCount();
     15        $untrackedProducts = WooMailerLiteProduct::getUntrackedProductsCount();
     16        $untrackedCustomers = WooMailerLiteCustomer::getUntrackedCustomersCount();
     17        WooMailerLiteCache::set('resource_sync_counts', [
     18            'categories' => $untrackedCategories,
     19            'products' => $untrackedProducts,
     20            'customers' => $untrackedCustomers,
     21        ], 18000);
     22        $totalUntrackedResources = $untrackedCategories + $untrackedProducts + $untrackedCustomers;
    1323        if ($totalUntrackedResources == 0) {
    1424            WooMailerLiteCache::delete('manual_sync');
     
    1626        }
    1727
    18         if ($alreadyStarted) {
    19             WooMailerLiteProductSyncJob::dispatch();
    20         } else {
    21             WooMailerLiteProductSyncJob::dispatchSync();
    22         }
     28        WooMailerLiteCategorySyncJob::dispatch();
    2329
    24         return $this->response('sync started', 202);
     30        return $this->response([
     31            'message' => 'Sync in progress',
     32            'data' => [
     33                'totalUntrackedResources' => $totalUntrackedResources
     34            ]
     35        ], function_exists('as_enqueue_async_action') ? 203 : 202);
    2536    }
    2637
     
    3142        WooMailerLiteProductSyncResetJob::dispatchSync();
    3243        return $this->response(['message' => 'reset sync completed', 'data' => [
    33             'totalUntrackedResources' => WooMailerLiteCategory::getUntrackedCategoriesCount() + WooMailerLiteProduct::getUntrackedProductsCount() +  WooMailerLiteCustomer::getAll()->count()
     44            'totalUntrackedResources' => WooMailerLiteCategory::getUntrackedCategoriesCount() + WooMailerLiteProduct::getUntrackedProductsCount() +  WooMailerLiteCustomer::getUntrackedCustomersCount()
    3445        ]], 202);
    3546    }
  • woo-mailerlite/trunk/admin/controllers/WooMailerLiteAdminWizardController.php

    r3361673 r3377455  
    4343        ];
    4444
    45         if ($this->apiClient()->isRewrite()) {
    46             if ($this->requestHas('page') && ($this->request['page'] !== '1')) {
    47                 $params['offset'] = ($this->request['page'] - 1)  * $params['limit'];
    48             }
     45        if ($this->requestHas('page') && ($this->request['page'] !== '1')) {
     46            $params['offset'] = ($this->request['page'] - 1)  * $params['limit'];
    4947        }
    5048
     
    6058        $groups = [];
    6159        if ($response->success) {
    62             if (isset($response->data)) {
     60            if (isset($response->data) && is_array($response->data)) {
    6361                foreach ($response->data as $group) {
    6462                    $groups['data'][] = [
     
    7270                    'next' => (bool)$response->links->next
    7371                ];
    74             } elseif ($this->apiClient()->isClassic() && (count($groups['data']) > 0)) {
     72            } elseif ($this->apiClient()->isClassic() && (isset($groups['data']) && count($groups['data']) > 0)) {
    7573                $groups['pagination'] = [
    76                     'next' => WooMailerLiteApi::CLASSIC_API
     74                    'next' => !empty($groups)
    7775                ];
    7876            }
     
    157155                'syncFields' => $this->validated['syncFields'],
    158156                'enabled' => true,
    159                 'consumerKey' => $this->validated['consumerKey'],
    160                 'consumerSecret' => $this->validated['consumerSecret'],
     157                'consumerKey' => $this->validated['consumerKey'] ?? null,
     158                'consumerSecret' => $this->validated['consumerSecret'] ?? null,
    161159            ]);
    162160            if (!$response->data->group) {
  • woo-mailerlite/trunk/includes/WooMailerLite.php

    r3361673 r3377455  
    110110
    111111
    112         function on_product_category_saved($term_id, $tt_id) {
    113             $term = get_term($term_id, 'product_cat');
    114 
    115             // Your logic here
    116             error_log("Category saved: {$term->name} (ID: $term_id)");
    117         }
    118112        $this->loader->add_filter('woocommerce_product_data_tabs', $pluginAdmin, 'woo_ml_product_data_tab');
    119113        $this->loader->add_filter('woocommerce_product_data_store_cpt_get_products_query', $pluginAdmin, 'handleCustomProductQuery', 10, 2);
     
    327321        if (get_option('woo_ml_key', false) && (get_option('woo_ml_wizard_setup', 0) == 2)) {
    328322            $settings = get_option('woocommerce_mailerlite_settings', []);
     323            if (!(get_option('woo_ml_key') == WooMailerLiteOptions::get('apiKey'))) {
     324                return false;
     325            }
    329326            if (!empty($settings)) {
    330327                WooMailerLiteOptions::updateMultiple([
  • woo-mailerlite/trunk/includes/WooMailerLiteService.php

    r3340793 r3377455  
    2626    /**
    2727     * Triggered when the cart is created/updated
    28      * @return void
     28     * @return true
    2929     */
    3030    public function handleCartUpdated()
     
    5454            ]);
    5555        }
     56        return true;
    5657    }
    5758
  • woo-mailerlite/trunk/includes/WooMailerLiteSession.php

    r3338878 r3377455  
    3535    public static function getMLCartHash()
    3636    {
    37         return WC()->session->get('woo_mailerlite_cart_hash');
     37        if (WC()->session) {
     38            return WC()->session->get('woo_mailerlite_cart_hash');
     39        }
     40        return null;
    3841    }
    3942
  • woo-mailerlite/trunk/includes/api/WooMailerLiteClassicApi.php

    r3348600 r3377455  
    7070    }
    7171
     72    public function syncCustomers($data)
     73    {
     74       return $this->post('/woocommerce/sync_customer', $data);
     75    }
     76
     77    public function importCategories()
     78    {
     79        return $this->successResponse();
     80    }
     81
     82    public function importProducts()
     83    {
     84        return $this->successResponse();
     85    }
     86
    7287    public function createField($params)
    7388    {
     
    116131        return $this->get('/subscribers/' . $email);
    117132    }
    118 
    119     public function syncCustomers($data)
    120     {
    121         return $this->successResponse();
    122     }
    123 
    124     public function importCategories()
    125     {
    126         return $this->successResponse();
    127     }
    128 
    129     public function importProducts()
    130     {
    131         return $this->successResponse();
    132     }
    133133}
  • woo-mailerlite/trunk/includes/api/WooMailerLiteRewriteApi.php

    r3338878 r3377455  
    5757    }
    5858
    59     public function syncProduct($shopId, $data)
     59    public function syncProduct($shopId, $data, $replaceCategories = false)
    6060    {
    61         return $this->post('/ecommerce/shops/' . $shopId . '/products?with_resource_id',
     61        $response = $this->post('/ecommerce/shops/' . $shopId . '/products?with_resource_id',
    6262            $data);
     63        if ($replaceCategories && isset($data['resource_id']) && isset($data['category_ids'])) {
     64            $response = $this->put('/ecommerce/shops/' . $shopId . '/products/' . $data['resource_id'] . '/categories/multiple?with_resource_id',
     65                [
     66                    'replace'    => true,
     67                    'categories' => $data['category_ids']
     68                ]);
     69        }
     70        return $response;
    6371    }
    6472
  • woo-mailerlite/trunk/includes/common/WooMailerLiteDBConnection.php

    r3338878 r3377455  
    4343    protected $hasWhere = false;
    4444
     45    protected $countOnly = false;
     46
     47
     48    private $columnsOnly = false;
     49
    4550    /**
    4651     * @var string|null $table
     
    6368            $result = dbDelta($this->query);
    6469        } else {
    65             $result = $this->db()->get_results($this->query);
     70            if ($this->columnsOnly) {
     71                $result = $this->db()->get_col($this->query);
     72            } else {
     73                $result = $this->db()->get_results($this->query);
     74            }
    6675        }
    6776        return $result;
     
    109118    public function count()
    110119    {
     120        $this->countOnly = true;
    111121        $data = $this->get();
     122        $this->countOnly = false;
    112123        if ($data instanceof WooMailerLiteCollection) {
    113124            return $data->count();
    114125        } else {
    115             return is_array($data) ? count($data) : 0;
     126            return is_array($data) ? count($data) : (is_int($data) ? $data : 0);
    116127        }
    117128    }
     
    127138        return static::db_connection_instance();
    128139    }
     140
     141    public function columnsOnly()
     142    {
     143        $this->columnsOnly = true;
     144        return $this;
     145    }
    129146}
  • woo-mailerlite/trunk/includes/common/WooMailerLiteQueryBuilder.php

    r3348600 r3377455  
    88    private $select = "*";
    99
     10    protected $andWhere = false;
     11
     12    protected $withoutPrefix = false;
     13
    1014    public function __construct($model)
    1115    {
     
    3842    public function get(int $count = -1)
    3943    {
     44        if ($count == -1 && (get_class($this->model) === 'WooMailerLiteCustomer')) {
     45            return $this->buildQuery($count)->executeQuery();
     46        }
    4047        if ($this->model->isResource() || ((get_class($this->model) === 'WooMailerLiteCustomer') && !$this->customTableEnabled())) {
    4148
     
    4653
    4754        $data = $this->buildQuery($count)->executeQuery();
     55        if ($this->countOnly && (get_class($this->model) === 'WooMailerLiteProduct')) {
     56            return $data;
     57        }
    4858        foreach ($data as $item) {
    4959            if ((get_class($this->model) === 'WooMailerLiteCustomer') && empty($item->email)) {
    5060                continue;
    5161            }
     62            if (get_class($this->model) === 'WooMailerLiteProduct' && !$this->model->isResource()) {
     63                $item = wc_get_product($item);
     64                if (!$item) {
     65                    continue;
     66                }
     67                $itemData = $item->get_data();
     68                $this->prepareResourceData(get_class($this->model), $itemData, $item->last_order_id ?? $item);
     69                $item = $itemData;
     70            }
    5271            $model = new $this->model();
    5372
     
    6281            if (!empty($this->model->getFormatArray())) {
    6382                foreach ($this->model->getFormatArray() as $key => $format) {
     83                    if (!isset($model->attributes[$key])) {
     84                        continue;
     85                    }
    6486                    switch ($format) {
    6587                        case 'array':
    66                             $model->attributes[$key] = json_decode($model->attributes[$key], true);
     88                            if (is_string($model->attributes[$key])){
     89                                $model->attributes[$key] = json_decode($model->attributes[$key], true);
     90                            }
    6791                            break;
    6892                        case 'boolean':
    6993                            $model->attributes[$key] = (bool) $model->attributes[$key];
    7094                            break;
    71                     }
    72                 }
    73             }
     95                        case 'string':
     96                            $model->attributes[$key] = (string) $model->attributes[$key];
     97                            break;
     98                    }
     99                }
     100            }
     101            if (!empty($this->model->getRemoveEmptyArray())) {
     102                foreach ($this->model->getRemoveEmptyArray() as $key) {
     103                    if (isset($model->attributes[$key])) {
     104                        if (empty($model->attributes[$key]) || (is_string($model->attributes[$key]) && ctype_space($model->attributes[$key]))) {
     105                            unset($model->attributes[$key]);
     106                        }
     107                    }
     108                }
     109            }
     110
    74111            $collection->collect($model);
    75112        }
     
    121158    }
    122159
    123     public function join(string $table, string $tableLeft, string $tableRight)
    124     {
     160    public function join($table, $tableLeft = null, $tableRight = null, $alias = null)
     161    {
     162        if ($table instanceof WooMailerLiteQueryBuilder) {
     163            $alias = $this->db()->prefix . ($alias ?? 'subquery');
     164            $this->query .= " INNER JOIN (" . rtrim($table->buildQuery()->query, ';') . ") AS " . ($alias ?? 'subquery') . " ON " . $this->db()->prefix . $tableLeft . " = " . $this->db()->prefix  . $tableRight;
     165            return $this;
     166        }
     167
     168        if (!$tableRight && is_array($tableLeft)) {
     169            $operator = null;
     170            foreach ($tableLeft as $key => $value) {
     171                if (is_string($value)) {
     172                    if (!strpos($value, '.')) {
     173                        $value = "'{$value}'";
     174                    } else {
     175                        $value = $this->db()->prefix . $value;
     176                    }
     177                }
     178
     179                if (is_array($value) && is_string(array_keys($value)[0])) {
     180                    $operator = array_keys($value)[0];
     181                    $findin = "'" . implode("','", $value[$operator]) . "'";
     182                    $value = " {$operator} ({$findin})";
     183                }
     184                $joins[] = $this->db()->prefix . $key . ($operator ? '' : ' = ') . $value;
     185            }
     186            $this->query .= " INNER JOIN " . $this->db()->prefix . $table . " ON " . implode(' AND ', $joins);
     187            return $this;
     188        }
    125189        $table = $this->db()->prefix . $table;
    126190        $tableLeft = $this->db()->prefix . $tableLeft;
     
    130194    }
    131195
     196    public function from($table)
     197    {
     198        $this->model->setTable($table);
     199        return $this;
     200    }
     201
     202    public function leftJoin(string $table, $tableLeft, string $tableRight = '')
     203    {
     204        if (!$tableRight && is_array($tableLeft)) {
     205            // this condition is for join on key = value and another key = value
     206            $joins = [];
     207            foreach ($tableLeft as $key => $value) {
     208                if (!strpos($value, '.')) {
     209                    $value = "'{$value}'";
     210                } else {
     211                    $value = $this->db()->prefix . $value;
     212                }
     213                $joins[] = $this->db()->prefix . $key . ' = ' . $value;
     214            }
     215            $this->query .= " LEFT JOIN " . $this->db()->prefix . $table . " ON " . implode(' AND ', $joins);
     216            return $this;
     217        }
     218        $table = $this->db()->prefix . $table;
     219        $tableLeft = $this->db()->prefix . $tableLeft;
     220        $tableRight = $this->db()->prefix . $tableRight;
     221        $this->query .= " LEFT JOIN {$table} ON {$tableLeft} = {$tableRight}";
     222        return $this;
     223    }
     224
    132225    public function andWhere($column, $operation, $value)
    133226    {
     
    136229            $operation = '=';
    137230        }
     231        if ($this->andWhere) {
     232            $this->andWhere = false;
     233            $this->query .= " {$column} {$operation} '{$value}'";
     234            return $this;
     235        }
    138236        $this->query .= " AND {$column} {$operation} '{$value}'";
     237        return $this;
     238    }
     239
     240    public function orWhere($column, $operation = '=', $value = null)
     241    {
     242        $prefix = $this->db()->prefix;
     243        if ($value === null) {
     244            $value = $operation;
     245            $operation = '=';
     246        }
     247        if ($value === null) {
     248            $value = 'IS NULL';
     249            $operation = '';
     250        } else {
     251            $value = "'{$value}'";
     252        }
     253        if (!$this->withoutPrefix) {
     254            $column = $prefix . $column;
     255        }
     256        $this->query .= " OR {$column} {$operation} {$value}";
     257        return $this;
     258    }
     259
     260    public function withoutPrefix($callback)
     261    {
     262        $this->withoutPrefix = true;
     263        $callback($this);
     264        $this->withoutPrefix = false;
     265        return $this;
     266    }
     267
     268    public function andCombine($callback)
     269    {
     270        $this->andWhere = true;
     271        $this->query .= ' AND (';
     272        $callback($this);
     273        $this->query .= ')';
    139274        return $this;
    140275    }
     
    185320        $exists = $this->where(array_key_first($where), $where[array_key_first($where)])->first();
    186321        if ($exists) {
     322            return $exists;
     323        }
     324        $this->create(array_merge($where, $data));
     325        $this->query = '';
     326        $this->hasWhere = false;
     327        return $this->where(array_key_first($where), $where[array_key_first($where)])->first();
     328    }
     329
     330    public function updateOrCreate($where, $data)
     331    {
     332        $exists = $this->where(array_key_first($where), $where[array_key_first($where)])->first();
     333        if ($exists) {
     334            $this->query = '';
     335            $this->hasWhere = false;
     336            $this->model = $exists;
     337            $this->update($data);
    187338            return $exists;
    188339        }
  • woo-mailerlite/trunk/includes/common/traits/WooMailerLiteResources.php

    r3361673 r3377455  
    4141    /**
    4242     * Get all resources
    43      * @return void|WooMailerLiteCollection
     43     * @return void|WooMailerLiteCollection|int
    4444     */
    4545    public function resource_all($count = 0)
     
    5757                ], $this->args);
    5858
     59                if ($this->args['limit'] == -1) {
     60                    $this->args = array_merge($this->args, [
     61                        'paginate' => true,
     62                        'return' => 'ids',
     63                        'status' => 'publish',
     64                    ]);
     65                    return (int) (new WC_Product_Query($this->args))->get_products()->total;
     66                }
    5967                $items = (new WC_Product_Query($this->args))->get_products();
    6068                break;
     
    186194    {
    187195        $this->args = array_merge($this->args, $args);
     196        $prefix = $this->db()->prefix;
     197        $this->select = "{$prefix}posts.ID";
     198        if (isset($args['meta_query'][0]['key']) && ($args['meta_query'][0]['key'] !== '_woo_ml_category_tracked')) {
     199            return $this;
     200        }
     201        if (isset($args['metaQuery'][0]['value']) && ($args['metaQuery'][0]['value'] === true) && ($args['metaQuery'][0]['key'] === '_woo_ml_product_tracked')) {
     202            $this->leftJoin('postmeta', [
     203                'posts.id' => "postmeta.post_id",
     204                'postmeta.meta_key' => '_woo_ml_product_tracked'
     205            ])
     206            ->where('posts.post_type', 'product')
     207            ->where('posts.post_status', 'publish')
     208                ->andCombine(function($query) {
     209                    $query->where('postmeta.meta_value', '1')
     210                        ->orWhere('postmeta.meta_value', true);
     211                })
     212            ->columnsOnly();
     213            return $this;
     214        }
     215        $this->leftJoin('postmeta', [
     216            'posts.id' => "postmeta.post_id",
     217            'postmeta.meta_key' => '_woo_ml_product_tracked'
     218            ])
     219            ->where('posts.post_type', 'product')
     220            ->where('posts.post_status', 'publish')
     221            ->andCombine(function($query) {
     222                $query->where('postmeta.meta_value', '0')
     223                      ->orWhere('postmeta.meta_value', null)
     224                      ->orWhere('postmeta.meta_value', false);
     225            })
     226            ->columnsOnly();
    188227        return $this;
    189228    }
     
    193232        switch ($resource) {
    194233            case 'WooMailerLiteProduct':
    195                 $data['resource_id'] = (string) $item->get_id();
     234                $variableProduct = $item->is_type('variation') ?? false;
     235                $data['resource_id'] = (string) ($variableProduct ? $item->get_parent_id() : $item->get_id());
    196236                $data['url'] = get_permalink($item->get_id());
     237                $data['name'] = $item->get_name();
     238                $data['price'] = floatval($item->get_price());
     239                $data['url'] = get_permalink( $item->get_id() );
     240                $data['category_ids'] = $item->get_category_ids();
    197241                $data['image'] = (string) wp_get_attachment_image_url($item->get_image_id(), 'full');
     242                $data['description'] = $item->get_description();
     243                $data['short_description'] = $item->get_short_description();
    198244                $data['ignored'] = get_post_meta($data['resource_id'], '_woo_ml_product_ignored', true);
    199245                $data['tracked'] = get_post_meta($data['resource_id'], '_woo_ml_product_tracked', true);
     
    201247            case 'WooMailerLiteCategory':
    202248                $data['resource_id'] = (string) $item->term_id;
     249                $data['name'] = $item->name;
    203250                $data['tracked'] = get_term_meta($item->term_id, '_woo_ml_category_tracked', true);
    204251                break;
     
    252299                $this->customerResourceCount[$email] = [
    253300                    'customer_id' => $data['customer_id'] ?? $this->counter,
    254                     'resource_id' => $item->get_customer_id(),
     301                    'resource_id' => $data['customer_id'] ?? $this->counter,
    255302                    'email' => $item->get_billing_email(),
    256303                    'name' => $item->get_billing_first_name(),
  • woo-mailerlite/trunk/includes/controllers/WooMailerLiteController.php

    r3338878 r3377455  
    3636            $keysToUnset = [];
    3737
    38             foreach ($validations as $key => $validation) {
     38            if (is_array($validations)) {
     39                foreach ($validations as $key => $validation) {
    3940
    40                 if (is_array($validation)) {
    41                     if ($skipSometimes === $key) {
    42                         continue;
     41                    if (is_array($validation)) {
     42                        if ($skipSometimes === $key) {
     43                            continue;
     44                        }
     45
     46                        if (strpos($key, '.') !== false) {
     47                            $needle = explode('.', $key);
     48                            if (!is_array($this->request[$needle[0]])) {
     49                                $this->sanitizeRequestKey($needle[0]);
     50                            }
     51
     52                            if (isset($this->request[$needle[0]][$needle[1]])) {
     53                                $this->request[$key] = $this->request[$needle[0]][$needle[1]];
     54                                $keysToUnset[] = $key;
     55                            }
     56                        }
     57
     58                        if (in_array('required', $validation) && empty($this->request[$key])) {
     59                            throw new Exception("The $key field is required.");
     60                        }
     61
     62                        if (in_array('sometimes', $validation)) {
     63                            $skipSometimes = $key;
     64                            if (isset($this->request[$key]) && empty($this->request[$key])) {
     65                                $this->validated[$key] = $this->request[$key];
     66                            }
     67                            continue;
     68                        }
     69
     70                        if (in_array('string', $validation) && !is_string($this->request[$key])) {
     71                            throw new Exception("The $key field must be a string.");
     72                        }
     73
     74                        if (in_array('int', $validation) && !ctype_digit(strval($this->request[$key]))) {
     75                            throw new Exception("The $key field must be an integer.");
     76                        }
     77
     78                        if (in_array('bool', $validation) && !is_bool(filter_var($this->request[$key], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE))) {
     79                            throw new Exception("The $key field must be a boolean.");
     80                        }
     81                    }
     82                    if ($validation === 'nonce') {
     83                        check_ajax_referer('woo_mailerlite_admin', 'nonce');
    4384                    }
    4485
    45                     if (strpos($key, '.') !== false) {
    46                         $needle = explode('.', $key);
    47                         if (!is_array($this->request[$needle[0]])) {
    48                             $this->sanitizeRequestKey($needle[0]);
    49                         }
    50 
    51                         if (isset($this->request[$needle[0]][$needle[1]])) {
    52                             $this->request[$key] = $this->request[$needle[0]][$needle[1]];
    53                             $keysToUnset[] = $key;
    54                         }
     86                    if (isset($this->request[$key])) {
     87                        $this->validated[$key] = $this->request[$key];
    5588                    }
    56 
    57                     if (in_array('required', $validation) && empty($this->request[$key])) {
    58                         throw new Exception("The $key field is required.");
    59                     }
    60 
    61                     if (in_array('sometimes', $validation)) {
    62                         $skipSometimes = $key;
    63                         $this->validated[$key] = $this->request[$key];
    64                         continue;
    65                     }
    66 
    67                     if (in_array('string', $validation) && !is_string($this->request[$key])) {
    68                         throw new Exception("The $key field must be a string.");
    69                     }
    70 
    71                     if (in_array('int', $validation) && !ctype_digit(strval($this->request[$key]))) {
    72                         throw new Exception("The $key field must be an integer.");
    73                     }
    74 
    75                     if (in_array('bool', $validation) && !is_bool(filter_var($this->request[$key], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE))) {
    76                         throw new Exception("The $key field must be a boolean.");
    77                     }
    78                 }
    79 
    80                 if (isset($this->request[$key])) {
    81                     $this->validated[$key] = $this->request[$key];
    8289                }
    8390            }
  • woo-mailerlite/trunk/includes/controllers/WooMailerLiteOrderController.php

    r3361673 r3377455  
    77    {
    88        try {
     9
    910            if (WooMailerLiteCache::get('order_sent:'.$orderId)) {
    1011                return true;
    1112            }
    1213            $order = wc_get_order($orderId);
    13 
     14            if (WooMailerLiteSession::getMLCartHash()) {
     15                $order->add_meta_data('_woo_ml_cart_hash', WooMailerLiteSession::getMLCartHash());
     16            }
    1417            $customer = WooMailerLiteCustomer::selectAll(false)->where('email', $order->get_billing_email())->first();
    1518            if (!$customer) {
     
    2225            $cart = WooMailerLiteCart::where('email', $order->get_billing_email())->first();
    2326            if (!$cart) {
    24                 $cart = WooMailerLiteCart::where('hash', WooMailerLiteSession::getMLCartHash())->first();
     27                $cart = WooMailerLiteCart::where('hash', WooMailerLiteSession::getMLCartHash())
     28                ->withoutPrefix(function($query) use ($order) {
     29                    $query->orWhere('hash', $order->get_meta('_woo_ml_cart_hash'));
     30                })->first();
    2531                if ($cart instanceof WooMailerLiteCart) {
    2632                    $cart->update([
     
    6268            $customerFields = array_intersect_key($filteredCustomerData, array_flip($syncFields));
    6369            $subscribe = false;
    64             if (isset($cart->subscribe)) {
     70            if ($cart && isset($cart->subscribe)) {
    6571                $subscribe = $cart->subscribe;
    6672            }
     
    96102            ];
    97103            if ($this->apiClient()->isClassic()) {
     104                if (!$cart) {
     105                    return true;
     106                }
    98107                $orderData['order'] = $order->get_data();
    99108                $orderData['checkout_id'] = $cart->data['checkout_id'];
     
    112121                    'shop_url' => home_url(),
    113122                    'order_url' => home_url() . "/wp-admin/post.php?post=" . $orderId . "&action=edit",
    114                     'checkout_data' => WooMailerLiteCheckoutDataService::getCheckoutData()
     123                    'checkout_data' => WooMailerLiteCheckoutDataService::getCheckoutData($order->get_billing_email())
    115124                ];
    116125                $this->apiClient()->sendOrderProcessing($data);
     
    140149                if (in_array($order->get_status(), ['wc-completed', 'wc-processing','completed','processing']) && !empty($cart)) {
    141150                    if ($cart instanceof WooMailerLiteCart) {
    142                         $cart->delete();
     151                        if ($this->apiClient()->isRewrite()) {
     152                            $this->apiClient()->deleteOrder($cart->data['checkout_id']);
     153                        }
     154                       $cart->delete();
    143155                    }
    144156                }
  • woo-mailerlite/trunk/includes/controllers/WooMailerLitePluginController.php

    r3338878 r3377455  
    7979        if ($this->requestHas('ml_checkout')) {
    8080            $cart = WooMailerLiteCart::where('data', 'like', '%'.$this->request['ml_checkout'].'%')->first();
    81             if ($cart->exists()) {
     81            if ($cart && $cart->exists()) {
    8282                WC()->session->set('cart', $cart->data);
    8383            }
  • woo-mailerlite/trunk/includes/jobs/WooMailerLiteAbstractJob.php

    r3339434 r3377455  
    33abstract class WooMailerLiteAbstractJob
    44{
    5     /**
    6      * Whether the job runs in serial mode (not used directly here).
    7      */
    85    private $serial = true;
    96
    10     /**
    11      * Delay in seconds before executing the job.
    12      */
    137    protected static $delay = 0;
    148
    15     /**
    16      * Holds the job record model from the database.
    17      */
    189    public static $jobModel;
    1910
    20     /**
    21      * Used to skip retry logic if needed.
    22      */
    2311    protected $retryDelay = 10;
    2412
    25     /**
    26      * Max retry attempts.
    27      */
    28     protected $maxRetries = 0;
     13    protected $maxRetries = 3;
    2914
    3015    protected $resourceLimit = 100;
    3116
    32     /**
    33      * Each job must implement this method.
    34      */
    3517    abstract public function handle($data = []);
    3618
    37     /**
    38      * Returns a new instance of the job.
    39      */
    4019    public static function getInstance()
    4120    {
     
    4322    }
    4423
    45     /**
    46      * Dispatch the job to Action Scheduler or run it synchronously.
    47      */
    4824    public static function dispatch(array $data = []): void
    4925    {
     
    5127        $objectId = 0;
    5228
    53         if ((isset($data['selfMechanism']['sync']) && !$data['selfMechanism']['sync']) && class_exists('ActionScheduler')) {
    54             if (!as_has_scheduled_action($jobClass)) {
    55                 $objectId = as_enqueue_async_action($jobClass, $data);
    56             }
     29        $data['attempts'] = $data['attempts'] ?? 0;
     30
     31        if ((!isset($data['selfMechanism']['sync']) || !$data['selfMechanism']['sync']) && function_exists('as_enqueue_async_action')) {
     32            $objectId = as_enqueue_async_action($jobClass, [$data]);
     33            WooMailerLiteCache::set('scheduled_jobs', true, 300);
    5734        }
    5835
    59         static::$jobModel = WooMailerLiteJob::firstOrCreate(
     36        static::$jobModel = WooMailerLiteJob::updateOrCreate(
    6037            ['job' => $jobClass],
    6138            ['object_id' => $objectId, 'data' => $data]
     
    6744    }
    6845
    69     /**
    70      * Force synchronous execution.
    71      */
    7246    public static function dispatchSync(array $data = []): void
    7347    {
     
    8256                static::$jobModel = WooMailerLiteJob::where('job', static::class)->first();
    8357            }
     58
    8459            $this->handle($data);
     60
    8561            if (static::$jobModel) {
    8662                static::$jobModel->delete();
    8763            }
     64            WooMailerLiteCache::delete('scheduled_jobs');
    8865            return true;
     66
    8967        } catch (Throwable $th) {
    90             WooMailerLiteLog()->error("Failed Job " . static::class, [$th->getMessage()]);
     68            WooMailerLiteCache::delete('scheduled_jobs');
     69            WooMailerLiteLog()->error("Failed Job: " . static::class, [
     70                'error' => $th->getMessage(),
     71                'trace' => $th->getTraceAsString()
     72            ]);
    9173
    92             // retry mechanism
    93             $attempts = static::$jobModel->data['attempts'] ?? 0;
    94             static::$jobModel->update([
    95                 'data' => [
    96                     'status' => 'failed',
    97                     'error' => $th->getMessage(),
    98                     'attempts' => $attempts + 1,
    99                 ]
    100             ]);
     74            $attempts = $data['attempts'] ?? 0;
     75            $attempts++;
     76
     77            if (static::$jobModel) {
     78                static::$jobModel->update([
     79                    'data' => array_merge($data, [
     80                        'status' => 'failed',
     81                        'error' => $th->getMessage(),
     82                        'attempts' => $attempts,
     83                    ])
     84                ]);
     85            }
     86
    10187            if ($attempts < $this->maxRetries) {
    102                 if (!isset($data['selfMechanism']['sync']) && class_exists('ActionScheduler')) {
    103                     as_enqueue_async_action(static::class, $data);
     88                $data['attempts'] = $attempts;
     89
     90                if (!isset($data['selfMechanism']['sync']) && function_exists('as_enqueue_async_action')) {
     91                    as_enqueue_async_action(static::class, [$data]);
    10492                }
     93            } else {
     94                WooMailerLiteLog()->error("Job " . static::class . " failed after max retries.");
    10595            }
    10696        }
     97        return true;
    10798    }
    10899
    109     /**
    110      * Set a delay before job runs.
    111      */
    112100    public static function delay(int $delay)
    113101    {
  • woo-mailerlite/trunk/includes/jobs/WooMailerLiteCategorySyncJob.php

    r3338878 r3377455  
    66    {
    77        $categories = WooMailerLiteCategory::untracked()->get(100);
    8 
    98        if (!$categories->hasItems()) {
    10             self::$jobModel->delete();
    11             WooMailerLiteCustomerSyncJob::dispatch($data);
     9            WooMailerLiteProductSyncJob::dispatch($data);
    1210            return;
     11        }
     12        $countInCache = WooMailerLiteCache::get('resource_sync_counts', false);
     13        if (isset($countInCache['categories'])) {
     14            $countInCache = $countInCache['categories'];
    1315        }
    1416
     
    1618
    1719        foreach ($categories->items as $category) {
    18             error_log('Category: ' . $category->name);
    19 
    2020            $importCategories[] = [
    2121                'name' => $category->name,
     
    2929        if (!empty($importCategories)) {
    3030            WooMailerLiteApi::client()->importCategories($importCategories);
    31         }
    32 
    33         if (static::$jobModel) {
    34             static::$jobModel->delete();
    35         }
    36 
    37         if (WooMailerLiteCategory::getUntrackedCategoriesCount()) {
    38             static::dispatch($data);
     31            if (WooMailerLiteCategory::getUntrackedCategoriesCount() < $countInCache) {
     32                static::dispatch($data);
     33            }
    3934        } else {
    40             WooMailerLiteCustomerSyncJob::dispatch($data);
     35            WooMailerLiteProductSyncJob::dispatch($data);
    4136        }
    4237    }
  • woo-mailerlite/trunk/includes/jobs/WooMailerLiteCustomerSyncJob.php

    r3338878 r3377455  
    66    {
    77        $customers = WooMailerLiteCustomer::getAll(100);
     8        $processed = false;
    89        if ($customers->hasItems()) {
     10            $countInCache = WooMailerLiteCache::get('resource_sync_counts', false);
     11            if (isset($countInCache['customers'])) {
     12                $countInCache = $countInCache['customers'];
     13            } else {
     14                $countInCache = 0;
     15            }
    916            foreach ($customers->items as $customer) {
    1017                $customer->markTracked();
     18                $processed = true;
     19                if (WooMailerLiteApi::client()->isClassic()) {
     20                    $this->syncToClassic($customer);
     21                }
    1122            }
    12             $response = WooMailerLiteApi::client()->syncCustomers($customers->items);
     23            if (WooMailerLiteApi::client()->isClassic()) {
     24                static::$jobModel->delete();
     25            } else {
     26                $originalCustomers = $customers->toArray();
    1327
    14             if ($response->success) {
    15                 static::$jobModel->delete();
     28                $transformedCustomers = array_map(function ($data) {
     29                    $rootKeys = [
     30                        'resource_id',
     31                        'email',
     32                        'create_subscriber',
     33                        'accepts_marketing',
     34                        'total_spent',
     35                        'orders_count',
     36                        'last_order_id',
     37                        'last_order'
     38                    ];
     39                    $flippedRootKeys = array_flip($rootKeys);
     40                    $subscriberFields = array_diff_key($data, $flippedRootKeys);
     41                    $rootFields = array_intersect_key($data, $flippedRootKeys);
     42
     43                    return array_merge($rootFields, ['subscriber_fields' => $this->prepareCustomerFieldsForSync($subscriberFields)]);
     44                }, $originalCustomers);
     45
     46                $response = WooMailerLiteApi::client()->syncCustomers($transformedCustomers);
     47
     48                if ($response->success) {
     49                    static::$jobModel->delete();
     50                }
    1651            }
    17             if (WooMailerLiteCustomer::getAll()) {
     52
     53            if ($processed && (WooMailerLiteCustomer::getAll() < $countInCache)) {
    1854                self::dispatch($data);
    19             } else {
    20                 WooMailerLiteCache::delete('manual_sync');
    21                 self::$jobModel->delete();
    2255            }
    2356        }
    24         self::$jobModel->delete();
     57    }
     58
     59    protected function syncToClassic($customer)
     60    {
     61        try {
     62            $customerFields = $this->prepareCustomerFieldsForSync($customer->toArray());
     63            $customerFields['woo_orders_count'] = $customer['orders_count'] ?? 0;
     64            $customerFields['woo_total_spent'] = $customer['total_spent'] ?? 0;
     65            $customerFields['woo_last_order'] = $customer['last_order'] ?? null;
     66            $customerFields['woo_last_order_id'] = $customer['last_order_id'] ?? null;
     67            WooMailerLiteApi::client()->syncCustomers([
     68                'email' => $customer['email'],
     69                'subscriber_fields' => $customerFields,
     70                'shop' => home_url()
     71            ]);
     72            return true;
     73        } catch(\Throwable $e) {
     74            return true;
     75        }
     76    }
     77
     78    protected function prepareCustomerFieldsForSync($customer)
     79    {
     80        $syncFields = WooMailerLiteOptions::get('syncFields', []);
     81        if (empty($syncFields)) {
     82            $syncFields = [
     83                'name',
     84                'email',
     85                'company',
     86                'city',
     87                'zip',
     88                'state',
     89                'country',
     90                'phone'
     91            ];
     92            WooMailerLiteOptions::update('syncFields', $syncFields);
     93        }
     94        $syncFields[] = 'last_name';
     95        if (!in_array('name', $syncFields)) {
     96            $syncFields[] = 'name';
     97        }
     98        return array_intersect_key($customer, array_flip($syncFields));
    2599    }
    26100}
  • woo-mailerlite/trunk/includes/jobs/WooMailerLiteProductSyncJob.php

    r3344677 r3377455  
    33class WooMailerLiteProductSyncJob extends WooMailerLiteAbstractJob
    44{
    5     protected $maxRetries = 10;
    6     protected $retryDelay = 10;
    7 
    85    public function handle($data = [])
    96    {
    107        $products = WooMailerLiteProduct::untracked()->get(100);
    118        $syncProducts = [];
     9        if (!$products->hasItems()) {
     10            WooMailerLiteCustomerSyncJob::dispatch($data);
     11            return;
     12        }
    1213
    13         if (!$products->hasItems()) {
    14             self::$jobModel->delete();
    15             WooMailerLiteCategorySyncJob::dispatch($data);
    16             return;
     14        $countInCache = WooMailerLiteCache::get('resource_sync_counts', false);
     15        if (isset($countInCache['products'])) {
     16            $countInCache = $countInCache['products'];
    1717        }
    1818
     
    3333                $product->price = $productObj->get_price();
    3434            }
     35            if (!is_string($product->description)) {
     36                $product->description = '';
     37            }
     38            if ($product->description !== '' && ctype_space($product->description)) {
     39                $product->description = '';
     40            }
     41
    3542
    3643            $syncProducts[] = array_filter([
     
    5259        if (!empty($syncProducts)) {
    5360            WooMailerLiteApi::client()->importProducts($syncProducts);
    54         }
    55 
    56         if (WooMailerLiteProduct::getUntrackedProductsCount()) {
    57             static::dispatch($data);
     61            if (WooMailerLiteProduct::getUntrackedProductsCount() < $countInCache) {
     62                static::dispatch($data);
     63            }
    5864        } else {
    59             WooMailerLiteCategorySyncJob::dispatch($data);
     65            WooMailerLiteCustomerSyncJob::dispatch($data);
    6066        }
    6167    }
  • woo-mailerlite/trunk/includes/models/WooMailerLiteCategory.php

    r3338878 r3377455  
    3131                [
    3232                    'key'     => '_woo_ml_category_tracked',
    33                     'value'   => false,
    34                     'compare' => '=',
     33                    'value'   => ['1', 'true', 'yes'],
     34                    'compare' => 'NOT IN',
    3535                ],
    3636                [
     
    3838                    'compare' => 'NOT EXISTS',
    3939                ],
    40             ]
     40            ],
     41            'number' => -1
    4142        ]);
    4243    }
  • woo-mailerlite/trunk/includes/models/WooMailerLiteCustomer.php

    r3361673 r3377455  
    1818        'name',
    1919        'last_name',
     20        'company',
    2021        'city',
    2122        'state',
     
    2425        'last_order_id',
    2526        'last_order',
     27    ];
     28
     29    protected $format = [
     30        'accepts_marketing' => 'boolean',
     31        'create_subscriber' => 'boolean',
     32        'resource_id' => 'string',
    2633    ];
    2734
     
    3441        $prefix = db()->prefix;
    3542
    36         $query = static::builder()->select("
    37         {$prefix}wc_customer_lookup.customer_id,
    38         {$prefix}wc_customer_lookup.email,
    39         max({$prefix}wc_order_stats.order_id) AS last_order_id,
    40         max({$prefix}wc_order_stats.date_created) AS last_order,
    41         count(DISTINCT {$prefix}wc_order_stats.order_id) AS orders_count,
    42         sum({$prefix}wc_order_stats.total_sales) AS total_spent,
    43         CASE WHEN (
    44                         SELECT
    45                             wpm.meta_value
    46                         FROM
    47                             {$prefix}postmeta wpm
    48                         WHERE
    49                             wpm.meta_key = '_woo_ml_subscribe'
    50                             AND wpm.post_id = max({$prefix}wc_order_stats.order_id)
    51                         LIMIT 1) THEN
    52                         TRUE
    53                     ELSE
    54                         FALSE
    55                     END AS create_subscriber,
    56                     max({$prefix}wc_order_stats.order_id) AS last_order_id,
    57                     max({$prefix}wc_order_stats.date_created) AS last_order,
    58                     count(DISTINCT ({$prefix}wc_order_stats.order_id)) AS orders_count,
    59                     sum(({$prefix}wc_order_stats.total_sales)) AS total_spent")
    60             ->join('wc_order_stats', 'wc_order_stats.customer_id', 'wc_customer_lookup.customer_id')
    61             ->whereIn('wc_order_stats.status', ['wc-processing', 'wc-completed']);
     43        $statesQuery = static::builder()->select("
     44            {$prefix}wc_order_stats.customer_id,
     45            {$prefix}wc_order_stats.customer_id as resource_id,
     46            MAX({$prefix}wc_order_stats.order_id) AS last_order_id,
     47            MAX({$prefix}wc_order_stats.date_created) AS last_order,
     48            COUNT({$prefix}wc_order_stats.order_id) AS orders_count,
     49            SUM({$prefix}wc_order_stats.total_sales) AS total_spent
     50        ")
     51            ->from("wc_order_stats")
     52            ->whereIn("status", ['wc-processing','wc-completed'])
     53            ->groupBy("wc_order_stats.customer_id");
    6254
    63         if (self::builder()->customTableEnabled()) {
    64             $query->where('wc_order_stats.customer_id', '>', WooMailerLiteOptions::get('lastSyncedCustomer', 0));
    65         }
     55        return static::builder()->select("
     56            {$prefix}wc_customer_lookup.customer_id,
     57            {$prefix}wc_customer_lookup.customer_id AS resource_id,
     58            {$prefix}wc_customer_lookup.email,
     59            CASE WHEN {$prefix}postmeta.meta_value IS NOT NULL THEN TRUE ELSE FALSE END AS create_subscriber,
     60            CASE WHEN {$prefix}postmeta.meta_value IS NOT NULL THEN TRUE ELSE FALSE END AS accepts_marketing,
     61            {$prefix}order_agg.orders_count,
     62            {$prefix}order_agg.total_spent,
     63            {$prefix}wc_customer_lookup.first_name AS name,
     64            {$prefix}wc_customer_lookup.last_name,
     65            {$prefix}wc_customer_lookup.city,
     66            {$prefix}wc_customer_lookup.state,
     67            {$prefix}wc_customer_lookup.country,
     68            {$prefix}wc_customer_lookup.postcode AS zip,
     69            COALESCE({$prefix}usermeta.meta_value, '') AS company,
     70            {$prefix}order_agg.last_order_id,
     71            {$prefix}order_agg.last_order,
     72            CASE WHEN {$prefix}postmeta.meta_value IS NOT NULL THEN TRUE ELSE FALSE END AS create_subscriber
     73        ")
     74            ->join($statesQuery, 'order_agg.customer_id', 'wc_customer_lookup.customer_id', 'order_agg')
     75            ->leftJoin("postmeta", [
     76                'postmeta.post_id' => 'order_agg.last_order_id',
     77                'postmeta.meta_key' => '_woo_ml_subscribe'
     78            ])
     79            ->leftJoin("usermeta", [
     80                'usermeta.user_id' => 'wc_customer_lookup.user_id',
     81                'usermeta.meta_key' => 'billing_company'
     82            ])
     83            ->where('order_agg.customer_id', '>', WooMailerLiteOptions::get('lastSyncedCustomer', 0))
     84            ->orderBy('order_agg.customer_id')->get($limit);
     85    }
    6686
    67         return $query->groupBy('wc_order_stats.customer_id')
    68         ->orderBy('wc_order_stats.customer_id')
    69         ->get($limit);
     87    public static function getUntrackedCustomersCount()
     88    {
     89        $prefix = db()->prefix;
     90        return static::builder()->select("{$prefix}wc_customer_lookup.customer_id")
     91            ->from("wc_customer_lookup")
     92            ->join("wc_order_stats", [
     93                'wc_order_stats.customer_id' => 'wc_customer_lookup.customer_id',
     94                'wc_order_stats.status' => [
     95                    'in' => [
     96                        'wc-processing', 'wc-completed'
     97                    ]
     98                ]
     99            ])
     100        ->where("wc_order_stats.customer_id", ">", WooMailerLiteOptions::get('lastSyncedCustomer', 0))
     101        ->groupBy("wc_customer_lookup.customer_id")
     102        ->orderBy('wc_customer_lookup.customer_id')
     103        ->count();
    70104    }
    71105
     
    73107    {
    74108        $prefix = db()->prefix;
    75         $query  = static::builder()->select("*, 
     109        $query  = static::builder()->select("*,
    76110        CASE WHEN (
    77111                        SELECT
     
    104138    public function markTracked()
    105139    {
    106         if (!$this->queryBuilder()->customTableEnabled()) {
    107             WooMailerLiteOptions::update('lastSyncedOrder', $this->last_order_id);
    108         } else {
    109             WooMailerLiteOptions::update('lastSyncedCustomer', $this->customer_id);
    110         }
     140        WooMailerLiteOptions::update('lastSyncedOrder', $this->last_order_id);
     141        WooMailerLiteOptions::update('lastSyncedCustomer', $this->customer_id);
    111142    }
    112143
    113144    public static function martUntracked()
    114145    {
    115         if (!self::builder()->customTableEnabled()) {
    116             WooMailerLiteOptions::update('lastSyncedOrder', 0);
    117         } else {
    118             WooMailerLiteOptions::update('lastSyncedCustomer', 0);
    119         }
     146        WooMailerLiteOptions::update('lastSyncedOrder', 0);
     147        WooMailerLiteOptions::update('lastSyncedCustomer', 0);
    120148    }
    121149}
  • woo-mailerlite/trunk/includes/models/WooMailerLiteModel.php

    r3348600 r3377455  
    152152        $this->isResource = true;
    153153    }
     154
     155    public function setTable($table)
     156    {
     157        $this->table = $table;
     158    }
    154159}
  • woo-mailerlite/trunk/includes/models/WooMailerLiteProduct.php

    r3338878 r3377455  
    1919    ];
    2020
    21     protected $isResource = true;
     21    protected $isResource = false;
     22
     23    protected $table = 'posts';
    2224
    2325    protected $format = [
    2426        'tracked' => 'boolean',
    2527        'image' => 'string',
     28        'resource_id' => 'string',
    2629    ];
    2730
     
    3134      'image',
    3235    ];
     36
     37    const QUERYLIMIT = 1000;
     38
     39    public function __construct($attributes = [])
     40    {
     41        parent::__construct($attributes);
     42    }
    3343
    3444    public static function tracked()
  • woo-mailerlite/trunk/includes/services/WooMailerLiteCheckoutDataService.php

    r3346224 r3377455  
    33class WooMailerLiteCheckoutDataService
    44{
    5     public static function getCheckoutData()
     5    public static function getCheckoutData($email = null)
    66    {
    77        if (empty(WC()->cart)) {
     
    1515        $cartItems = $cart->get_cart();
    1616        $customer = $cart->get_customer();
    17         $cartFromDb = WooMailerLiteCart::where('hash', WooMailerLiteSession::getMLCartHash())->first();
     17        if ($email) {
     18            $customerEmail = $email;
     19        } else {
     20            $customerEmail = $customer->get_email();
     21        }
     22        $cartFromDb = WooMailerLiteCart::where('hash', WooMailerLiteSession::getMLCartHash())
     23        ->withoutPrefix(function($query) use ($customerEmail) {
     24            $query->orWhere('email', $customerEmail);
     25        })->first();
     26        if (!$cartFromDb) {
     27            return true;
     28        }
    1829        $cartData = $cartFromDb->data;
    19         $customerEmail = $customer->get_email();
    2030
    21         if ( !$customerEmail) {
    22             $customerEmail = $cartFromDb->email ?? "";
     31        if (!$customerEmail) {
     32            $customerEmail = $cartFromDb->email;
    2333        }
    2434
  • woo-mailerlite/trunk/public/WooMailerLitePublic.php

    r3340793 r3377455  
    4444
    4545        if (!WooMailerLiteOptions::get('enabled')) {
     46            return true;
     47        }
     48        if (is_plugin_active("official-mailerlite-sign-up-forms/mailerlite.php")) {
    4649            return true;
    4750        }
  • woo-mailerlite/trunk/public/js/woo-mailerlite-public.js

    r3361673 r3377455  
    11jQuery(document).ready(function(a) {
    2     const allowedInputs = ['billing_email', 'billing_first_name', 'email', 'billing_last_name', 'woo_ml_subscribe', 'billing-first_name', 'billing-last_name', 'shipping-first_name', 'shipping-last_name'];
     2    const allowedInputs = [
     3        'billing_email',
     4        'billing_first_name',
     5        'email',
     6        'billing_last_name',
     7        'woo_ml_subscribe',
     8        'billing-first_name',
     9        'billing-last_name',
     10        'shipping-first_name',
     11        'shipping-last_name'
     12    ];
    313    let email = null;
    414    let firstName = null;
     
    818    let mailerLiteCheckoutBlockActive = null;
    919    let listeningEvents = false;
    10 
     20    let iteratorInterrupt = 0;
    1121
    1222    // temporarily forcefully picking this js not blocks one
    1323    window.mailerlitePublicJsCaptured = true;
    14     // if (document.querySelector('[data-block-name="woocommerce/checkout"]')) {
    15     //     window.mailerlitePublicJsCaptured = false;
    16     // } else {
    17     //     window.mailerlitePublicJsCaptured = true;
    18     // }
    1924
    2025    if (wooMailerLitePublicData.checkboxSettings.enabled && document.body.classList.contains('woocommerce-checkout')) {
     
    4550
    4651    function triggerAddEvents() {
    47 
     52        iteratorInterrupt++;
     53        if (iteratorInterrupt >= 5) {
     54            return false;
     55        }
    4856        const mailerLiteCheckoutBlockWrapper = document.querySelector('[data-block-name="mailerlite-block/woo-mailerlite"]');
    4957
    50         if(mailerLiteCheckoutBlockWrapper && mailerLiteCheckoutBlockWrapper.querySelector('#woo_ml_subscribe')) {
     58        if (mailerLiteCheckoutBlockWrapper && mailerLiteCheckoutBlockWrapper.querySelector('#woo_ml_subscribe')) {
    5159            mailerLiteCheckoutBlockActive = true;
    5260        }
     
    5664        }
    5765
    58         allowedInputs.forEach((val, key) => {
     66        allowedInputs.forEach((val) => {
    5967            if (!foundEmail && val.match('email')) {
    6068                email = document.querySelector('#' + val)
     
    7482                }
    7583            }
    76         })
     84        });
    7785
    7886        let signup = document.getElementById('woo_ml_subscribe');
     
    8593            wooMlCheckoutCheckbox.setAttribute('id', 'woo_ml_subscribe');
    8694            wooMlCheckoutCheckbox.setAttribute('type', 'checkbox');
     95            wooMlCheckoutCheckbox.setAttribute('name', 'woo_ml_subscribe');
    8796            wooMlCheckoutCheckbox.setAttribute('value', wooMailerLitePublicData.checkboxSettings.preselect ? 1 : 0);
    88             wooMlCheckoutCheckbox.setAttribute('checked', wooMailerLitePublicData.checkboxSettings.preselect ? 'checked' : '');
    89 
    90             if (!wooMailerLitePublicData.checkboxSettings.hidden) {
    91                 const label = document.createElement('label');
    92 
    93                 label.style.cursor = 'pointer';
    94                 label.style.display = 'inline-flex';
    95                 label.style.alignItems = 'center';
    96                 label.style.gap = '0.5rem';
    97                 label.htmlFor = 'woo_ml_subscribe';
    98 
    99                 // Create text span for the label
    100                 const labelText = document.createElement('span');
    101                 labelText.textContent = wooMailerLitePublicData.checkboxSettings.label ?? 'Yes, I want to receive your newsletter.';
    102 
    103                 // Append checkbox first, then text
    104                 label.appendChild(wooMlCheckoutCheckbox);
    105                 label.appendChild(labelText);
    106                 checkboxWrapper.appendChild(label);
    107                 // Insert the container after the email field’s wrapper
    108                 // email.closest('p').insertAdjacentElement('afterend', container);
     97            if (wooMailerLitePublicData.checkboxSettings.preselect) {
     98                wooMlCheckoutCheckbox.setAttribute('checked', 'checked');
     99            }
     100
     101            const label = document.createElement('label');
     102            label.style.cursor = 'pointer';
     103            label.style.display = 'inline-flex';
     104            label.style.alignItems = 'center';
     105            label.style.gap = '0.5rem';
     106            label.htmlFor = 'woo_ml_subscribe';
     107            if (wooMailerLitePublicData.checkboxSettings.hidden) {
     108                label.style.display = 'none';
     109            }
     110
     111            const labelText = document.createElement('span');
     112            labelText.textContent = wooMailerLitePublicData.checkboxSettings.label ?? 'Yes, I want to receive your newsletter.';
     113
     114            label.appendChild(wooMlCheckoutCheckbox);
     115            label.appendChild(labelText);
     116            checkboxWrapper.appendChild(label);
     117            if (wooMailerLitePublicData.checkboxSettings.hidden) {
     118                wooMlCheckoutCheckbox.setAttribute('type', 'hidden');
    109119            }
    110120
    111121            const wrapper = email.closest('div') ?? email;
    112122            wrapper.parentNode.insertBefore(checkboxWrapper, wrapper.nextSibling);
    113             // email.insertAdjacentElement('afterend', wooMlCheckoutCheckbox);
    114123            signup = document.getElementById('woo_ml_subscribe');
    115124            checkboxAdded = true;
    116             triggerAddEvents();
    117         }
    118 
    119         if (email !== null) {
     125        }
     126
     127        if (email !== null && !listeningEvents) {
    120128            listeningEvents = true;
     129
     130            document.addEventListener('change', (event) => {
     131                if (event.target && event.target.matches('input[name="billing_email"]')) {
     132                    validateMLSub(event);
     133                }
     134            });
     135
    121136            email.addEventListener('change', (event) => {
    122137                validateMLSub(event);
    123138            });
    124         }
    125 
    126         if (firstName !== null) {
    127             firstName.addEventListener('change', (event) => {
    128 
    129                 if(firstName.value.length > 0) {
    130                     validateMLSub(event);
    131                 }
    132             });
    133         }
    134 
    135         if (lastName !== null) {
    136             lastName.addEventListener('change', (event) => {
    137                 if(lastName.value.length > 0) {
    138                     validateMLSub(event);
    139                 }
    140             });
    141         }
    142 
    143         if (signup !== null) {
    144             a(document).on('change', signup, function(event) {
    145                 if (event.target.id === 'woo_ml_subscribe') {
    146                     validateMLSub(event);
    147                 }
    148             });
     139
     140            if (firstName !== null) {
     141                firstName.addEventListener('change', (event) => {
     142                    if (firstName.value.length > 0) {
     143                        validateMLSub(event);
     144                    }
     145                });
     146            }
     147
     148            if (lastName !== null) {
     149                lastName.addEventListener('change', (event) => {
     150                    if (lastName.value.length > 0) {
     151                        validateMLSub(event);
     152                    }
     153                });
     154            }
     155
     156            if (signup !== null) {
     157                a(document).on('change', signup, function(event) {
     158                    if (event.target.id === 'woo_ml_subscribe') {
     159                        validateMLSub(event);
     160                    }
     161                });
     162            }
    149163        }
    150164    }
    151165
    152166    function validateMLSub(e) {
    153         if(email !== null && email.value.length > 0) {
     167        if (email !== null && email.value.length > 0) {
    154168            checkoutMLSub(e);
    155169        }
     
    157171
    158172    function checkoutMLSub(e) {
    159 
    160173        clearTimeout(execute);
    161174        execute = setTimeout(() => {
     
    163176                return false;
    164177            }
    165             /** set cookie before sending request to server
    166              * since multiple checkout update requests can be sent
    167              * and server cookies won't get updated, so send the saved
    168              * cookie as a request parameter
    169              **/
    170178
    171179            if (!getCookie('mailerlite_checkout_token')) {
     
    187195                    name: firstName?.value ?? '',
    188196                    last_name: lastName?.value ?? '',
    189                     cookie_mailerlite_checkout_token:getCookie('mailerlite_checkout_token')
     197                    cookie_mailerlite_checkout_token: getCookie('mailerlite_checkout_token')
    190198                }
    191199            });
  • woo-mailerlite/trunk/readme.txt

    r3361673 r3377455  
    66Tested up to: 6.8.2
    77Requires PHP: 7.2.5
    8 Stable tag: 3.0.8
     8Stable tag: 3.1.0
    99License: GPLv3 or later
    1010License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    8585== Changelog ==
    8686
     87= 3.1.0 (13th October 2025) =
     88* Improved data and asynchronous resource synchronization
     89* Bug fixes and performance improvements
     90
    8791= 3.0.8 (15th September 2025) =
    8892* Improved cart synchronization and updates
  • woo-mailerlite/trunk/woo-mailerlite.php

    r3361673 r3377455  
    1616 * Plugin URI:        https://wordpress.org/plugins/woo-mailerlite/
    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.0.8
     18 * Version:           3.1.0
    1919 * Author:            MailerLite
    2020 * Author URI:        https://mailerlite.com
     
    4040 * Update when you release new versions.
    4141 */
    42 define( 'WOO_MAILERLITE_VERSION', '3.0.8' );
     42define( 'WOO_MAILERLITE_VERSION', '3.1.0' );
    4343
    4444define('WOO_MAILERLITE_ASYNC_JOBS', false);
     
    9797
    9898add_filter('auto_update_plugin', function ($update, $item) {
    99     if ($item->plugin === 'woo-mailerlite/woo-mailerlite.php') {
     99    if (isset($item->plugin) && $item->plugin === 'woo-mailerlite/woo-mailerlite.php') {
    100100        return WooMailerLiteOptions::get('settings.autoUpdatePlugin');
    101101    }
Note: See TracChangeset for help on using the changeset viewer.