PluginProbe
AliNext – WooCommerce Dropshipping Plugin for AliExpress / trunk
AliNext – WooCommerce Dropshipping Plugin for AliExpress vtrunk
ali2woo-lite / includes / classes / model / Override.php

Override.php in AliNext – WooCommerce Dropshipping Plugin for AliExpress trunk, at includes/classes/model/Override.php

307 lines 13.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Description of Override
4 *
5 * @author Ali2Woo Team
6 */
7 namespace AliNext_Lite;;
8
9 use Automattic\WooCommerce\Utilities\OrderUtil;
10
11 class Override
12 {
13
14 public function __construct()
15 {}
16
17 public function has_override($product_id): bool
18 {
19 global $wpdb;
20
21 if ($product_id) {
22 return !!$wpdb->get_var("SELECT 1 FROM $wpdb->options WHERE option_name like '%a2wl_product%' AND option_value like '%\"override_product_id\";i:" . intval($product_id) . "%'");
23 } else {
24 return false;
25 }
26 }
27
28 public function override($product_id, $external_id, $change_supplier = false, $override_images = false, $override_title_description = false, $variations = array())
29 {
30 global $wpdb;
31
32 $result = array("state" => "error", "message" => "Product not found.");
33
34 if ($this->has_override($product_id)) {
35 $result = array(
36 "state" => "error",
37 "message" => esc_html__("You've already selected to override this product. Check your import list and confirm the override to continue.", "ali2woo"),
38 );
39 } else {
40 $override_product = $wpdb->get_row($wpdb->prepare("SELECT p.ID, p.post_title FROM $wpdb->posts p WHERE p.ID=%d", $product_id), ARRAY_A);
41 if ($override_product) {
42 $product_import_model = new ProductImport();
43 $product = $product_import_model->get_product($external_id);
44 if ($product) {
45 $product['override_product_id'] = intval($product_id);
46 $product['override_product_title'] = $override_product['post_title'];
47 $product['override_supplier'] = $change_supplier;
48 $product['override_images'] = $override_images;
49 $product['override_title_description'] = $override_title_description;
50 $product['override_variations'] = $variations;
51
52 $product_import_model->upd_product($product);
53
54 $result = array(
55 'state' => 'ok',
56 'product_id' => $product_id, 'external_id' => $external_id,
57 'html' => $this->override_message($product_id, $product['override_product_title']),
58 );
59 }
60 }
61 }
62
63 return $result;
64 }
65
66 public function find_orders($product_id)
67 {
68 global $wpdb;
69
70 // Define the default WooCommerce statuses that mean an order is fulfilled
71 $fulfilled_statuses = ['wc-completed', 'wc-cancelled', 'wc-refunded'];
72
73 // Get the custom "delivered" status from plugin settings
74 $delivered_order_status = get_setting('delivered_order_status');
75
76 if ($delivered_order_status && !in_array($delivered_order_status, $fulfilled_statuses, true)) {
77 $fulfilled_statuses[] = $delivered_order_status;
78 }
79
80 $statuses_sql = "'" . implode("','", array_map('esc_sql', $fulfilled_statuses)) . "'";
81
82 if (OrderUtil::custom_orders_table_usage_is_enabled()) {
83 $query = "
84 SELECT variation_id, max(variation_attributes) as variation_attributes, max(thumbnail) as thumbnail, count(order_id) as cnt
85 FROM (
86 SELECT wi.order_id as order_id, wim2.meta_value as variation_id,
87 group_concat(t1.name SEPARATOR '#') as variation_attributes,
88 max(p2.guid) as thumbnail
89 FROM {$wpdb->prefix}woocommerce_order_items wi
90 INNER JOIN {$wpdb->prefix}wc_orders p1
91 ON (p1.ID=wi.order_id AND NOT p1.status IN ($statuses_sql))
92 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta wim1
93 ON (wi.order_item_id=wim1.order_item_id AND wim1.meta_key='_product_id' AND wim1.meta_value=%d)
94 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta wim2
95 ON (wi.order_item_id=wim2.order_item_id AND wim2.meta_key='_variation_id')
96 INNER JOIN {$wpdb->postmeta} pm1
97 ON (pm1.post_id=wim2.meta_value AND pm1.meta_key LIKE 'attribute_%')
98 LEFT JOIN {$wpdb->postmeta} pm2
99 ON (pm2.post_id=wim2.meta_value AND pm2.meta_key LIKE '_thumbnail_id')
100 LEFT JOIN {$wpdb->posts} p2
101 ON (pm2.meta_value=p2.ID)
102 INNER JOIN {$wpdb->term_taxonomy} tt1
103 ON (tt1.taxonomy=SUBSTRING(pm1.meta_key, 11))
104 INNER JOIN {$wpdb->terms} t1
105 ON (t1.term_id=tt1.term_id AND t1.slug=pm1.meta_value)
106 GROUP BY order_id, variation_id
107 ) as q
108 GROUP BY variation_id";
109 } else {
110 // Otherwise, fall back to using wp_posts table for order statuses
111 $query = "
112 SELECT variation_id, max(variation_attributes) as variation_attributes, max(thumbnail) as thumbnail, count(order_id) as cnt
113 FROM (
114 SELECT wi.order_id as order_id, wim2.meta_value as variation_id,
115 group_concat(t1.name SEPARATOR '#') as variation_attributes,
116 max(p2.guid) as thumbnail
117 FROM {$wpdb->prefix}woocommerce_order_items wi
118 INNER JOIN {$wpdb->posts} p1
119 ON (p1.ID=wi.order_id AND NOT p1.post_status IN ($statuses_sql))
120 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta wim1
121 ON (wi.order_item_id=wim1.order_item_id AND wim1.meta_key='_product_id' AND wim1.meta_value=%d)
122 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta wim2
123 ON (wi.order_item_id=wim2.order_item_id AND wim2.meta_key='_variation_id')
124 INNER JOIN {$wpdb->postmeta} pm1
125 ON (pm1.post_id=wim2.meta_value AND pm1.meta_key LIKE 'attribute_%')
126 LEFT JOIN {$wpdb->postmeta} pm2
127 ON (pm2.post_id=wim2.meta_value AND pm2.meta_key LIKE '_thumbnail_id')
128 LEFT JOIN {$wpdb->posts} p2
129 ON (pm2.meta_value=p2.ID)
130 INNER JOIN {$wpdb->term_taxonomy} tt1
131 ON (tt1.taxonomy=SUBSTRING(pm1.meta_key, 11))
132 INNER JOIN {$wpdb->terms} t1
133 ON (t1.term_id=tt1.term_id AND t1.slug=pm1.meta_value)
134 GROUP BY order_id, variation_id
135 ) as q
136 GROUP BY variation_id";
137 }
138
139 return $wpdb->get_results(
140 $wpdb->prepare(
141 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
142 $query,
143 $product_id
144 ),
145 ARRAY_A
146 );
147 }
148
149
150
151 /**
152 * Update order items when product variations are overridden.
153 *
154 * @param int $product_id
155 * @param array $override_variations
156 *
157 * NOTE:
158 * Before this function call we have already updated the original product with the new override-product in the database.
159 * Here we check if the product still has variants, and if so, update related order items.
160 * BUT: if the override-product is a simple product and the original was variable,
161 * then the query below will not return any results and related order items will not be updated.
162 * This means order items may still reference old '_variation_id' values that no longer exist.
163 * Perhaps this is acceptable, but for correct order fulfillment each order item should have valid data.
164 * In the future, if users report issues, we may need to extend this logic:
165 * - detect when the override-product is simple,
166 * - and remove '_variation_id' from each order item meta to avoid broken references.
167 */
168 public static function updateOrderItemsOnOverride($product_id, $override_variations): void
169 {
170 global $wpdb;
171
172 // Build mapping external_variation_id > variation_id
173 $variations_to_override = [];
174 foreach ($override_variations as $v) {
175 $variations_to_override[$v['external_variation_id']] = $v['variation_id'];
176 }
177
178 if (empty($variations_to_override)) {
179 return;
180 }
181
182 $in_data = implode(",", array_map(function ($v) {
183 global $wpdb;
184 return "'" . $wpdb->_real_escape($v) . "'";
185 }, array_keys($variations_to_override)));
186
187 // Build fulfilled statuses SQL string (includes custom Delivered if set)
188 $fulfilled_statuses = ['wc-completed', 'wc-cancelled', 'wc-refunded'];
189 $delivered_order_status = get_setting('delivered_order_status');
190 if ($delivered_order_status && !in_array($delivered_order_status, $fulfilled_statuses, true)) {
191 $fulfilled_statuses[] = $delivered_order_status;
192 }
193 $statuses_sql = "'" . implode("','", array_map('esc_sql', $fulfilled_statuses)) . "'";
194
195 // Find new variations
196 $new_variations_query = "SELECT pm.post_id as variation_id, pm.meta_value as external_variation_id
197 FROM {$wpdb->postmeta} pm
198 INNER JOIN {$wpdb->posts} p on (p.ID=pm.post_id)
199 WHERE p.post_parent=%d and pm.meta_key='external_variation_id' and pm.meta_value in ($in_data)";
200
201 $new_variations = $wpdb->get_results(
202 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
203 $wpdb->prepare($new_variations_query, $product_id), ARRAY_A
204 );
205
206 foreach ($new_variations as $v) {
207 if (isset($variations_to_override[$v['external_variation_id']])) {
208 if (OrderUtil::custom_orders_table_usage_is_enabled()) {
209 $update_query = "UPDATE {$wpdb->prefix}woocommerce_order_itemmeta oim
210 INNER JOIN {$wpdb->prefix}woocommerce_order_items oi ON (oi.order_item_id=oim.order_item_id)
211 INNER JOIN {$wpdb->prefix}wc_orders p ON (p.ID=oi.order_id)
212 SET oim.meta_value=%d
213 WHERE oim.meta_key='_variation_id' AND oim.meta_value=%d
214 AND NOT p.status IN ($statuses_sql)";
215 } else {
216 $update_query = "UPDATE {$wpdb->prefix}woocommerce_order_itemmeta oim
217 INNER JOIN {$wpdb->prefix}woocommerce_order_items oi ON (oi.order_item_id=oim.order_item_id)
218 INNER JOIN {$wpdb->posts} p ON (p.ID=oi.order_id)
219 SET oim.meta_value=%d
220 WHERE oim.meta_key='_variation_id' AND oim.meta_value=%d
221 AND NOT p.post_status IN ($statuses_sql)";
222 }
223
224 $wpdb->query(
225 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
226 $wpdb->prepare(
227 $update_query,
228 $v['variation_id'],
229 $variations_to_override[$v['external_variation_id']]
230 )
231 );
232 }
233 }
234 }
235
236 public function find_variations($product_id)
237 {
238 global $wpdb;
239 $query = "SELECT p.id AS variation_id, group_concat(t1.name SEPARATOR '#') AS variation_attributes, " .
240 "max(p2.guid) thumbnail FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm1 " .
241 "ON (pm1.post_id=p.ID AND pm1.meta_key like'attribute_%') LEFT JOIN {$wpdb->postmeta} pm2 " .
242 "ON (pm2.post_id=p.ID AND pm2.meta_key like'_thumbnail_id') LEFT JOIN {$wpdb->posts} p2 " .
243 "ON (pm2.meta_value=p2.ID) INNER JOIN {$wpdb->term_taxonomy} tt1 " .
244 "ON (tt1.taxonomy=substring(pm1.meta_key, 11)) INNER JOIN {$wpdb->terms} t1 " .
245 "ON (t1.term_id=tt1.term_id and t1.slug=pm1.meta_value) " .
246 "WHERE p.post_type='product_variation' AND p.post_parent=%d GROUP BY variation_id";
247
248 return $wpdb->get_results(
249 $wpdb->prepare(
250 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
251 $query,
252 $product_id
253 ),
254 ARRAY_A
255 );
256 }
257
258 public function cancel_override($external_id)
259 {
260 $result = array("state" => "error", "message" => "Product not found.");
261
262 $product_import_model = new ProductImport();
263
264 $product = $product_import_model->get_product($external_id);
265
266 if ($product) {
267 unset($product['override_product_id']);
268 unset($product['override_product_title']);
269 unset($product['override_supplier']);
270 unset($product['override_images']);
271 unset($product['override_title_description']);
272 unset($product['override_variations']);
273
274 $product_import_model->upd_product($product, false);
275
276 $result = array('state' => 'ok');
277 }
278
279 return $result;
280 }
281
282 public function override_message(int $product_id, string $product_title): string
283 {
284 $url = esc_url(admin_url('post.php?post=' . $product_id . '&action=edit'));
285 $title = esc_html($product_title);
286
287 /* translators: %s is product title */
288 $msg_text = sprintf(
289 __('This product will override %s. Click "Override" to proceed.', 'ali2woo'),
290 $title
291 );
292
293 $msg = str_replace($title, sprintf('<a href="%s">%s</a>', $url, $title), $msg_text);
294
295 $btn_text = esc_html__('Cancel Override', 'ali2woo');
296 $btn = sprintf(
297 '<button class="btn btn-default cancel-override" type="button">%s</button>',
298 $btn_text
299 );
300
301 return sprintf(
302 '<div><div style="padding-bottom:8px;">%s</div>%s</div>', $msg, $btn
303 );
304 }
305
306 }
307