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

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

1,062 lines 43.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Description of Utils
5 *
6 * @author Ali2Woo Team
7 */
8
9 namespace AliNext_Lite;;
10
11 use function mb_convert_encoding;
12
13 class Utils
14 {
15
16 public static function sanitizeExternalText(string $text): string
17 {
18 // Remove invisible characters like non-breaking spaces
19 $text = str_replace("\u{00A0}", ' ', $text);
20
21 // Collapse multiple whitespace
22 $text = preg_replace('/\s+/u', ' ', $text);
23
24 // Ensure UTF-8 consistency
25 $text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
26
27 // Decode HTML entities if present
28 $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
29
30 return trim($text);
31 }
32
33 public static function wcae_strack_active()
34 {
35 return is_plugin_active('woocommerce_aliexpress_shipment_tracking/index.php');
36 }
37
38 public static function update_post_terms_count($post_id)
39 {
40 global $wpdb;
41 $update_taxonomies = array();
42 $res = $wpdb->get_results("SELECT tt.taxonomy,tt.term_taxonomy_id,tt.term_id FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id=tt.term_taxonomy_id) WHERE tr.object_id=" . absint($post_id), ARRAY_A);
43 foreach ($res as $row) {
44 if (!isset($update_taxonomies[$row['taxonomy']])) {
45 $update_taxonomies[$row['taxonomy']] = array();
46 }
47 $update_taxonomies[$row['taxonomy']][] = in_array($row['taxonomy'], array('product_cat', 'product_tag')) ? $row['term_taxonomy_id'] : $row['term_id'];
48 }
49
50 foreach ($update_taxonomies as $taxonomy => $terms) {
51 wp_update_term_count_now($terms, $taxonomy);
52 }
53 }
54
55 public static function clear_url($url)
56 {
57 if ($url) {
58 $parts = wp_parse_url($url);
59 $res = '';
60 if (isset($parts['scheme'])) {
61 $res .= $parts['scheme'] . '://';
62 } else {
63 $res .= '//';
64 }
65 if (isset($parts['host'])) {
66 $res .= $parts['host'];
67 }
68 if (isset($parts['path'])) {
69 $res .= $parts['path'];
70 }
71 return $res;
72 }
73 return '';
74 }
75
76 public static function checkVariationIdHasAllParts(string $variationId, array $parts): bool
77 {
78 // Split the input variation id by the '-' delimiter
79 $segments = explode('-', $variationId);
80
81 foreach ($parts as $part) {
82 if (!is_string($part) || !in_array($part, $segments, true)) {
83 return false;
84 }
85 }
86
87 return true;
88 }
89
90 /**
91 * Get size information for all currently-registered image sizes.
92 * List available image sizes with width and height following
93 *
94 * @return array $sizes Data for all currently-registered image sizes.
95 * @uses get_intermediate_image_sizes()
96 * @global $_wp_additional_image_sizes
97 */
98 public function get_image_sizes()
99 {
100 global $_wp_additional_image_sizes;
101
102 $sizes = array();
103
104 foreach (get_intermediate_image_sizes() as $_size) {
105 if (in_array($_size, array('thumbnail', 'medium', 'medium_large', 'large'))) {
106 $sizes[$_size]['width'] = get_option("{$_size}_size_w");
107 $sizes[$_size]['height'] = get_option("{$_size}_size_h");
108 $sizes[$_size]['crop'] = (bool)get_option("{$_size}_crop");
109 } elseif (isset($_wp_additional_image_sizes[$_size])) {
110 $sizes[$_size] = array(
111 'width' => $_wp_additional_image_sizes[$_size]['width'],
112 'height' => $_wp_additional_image_sizes[$_size]['height'],
113 'crop' => $_wp_additional_image_sizes[$_size]['crop'],
114 );
115 }
116 }
117
118 return $sizes;
119 }
120
121 /**
122 * Get size information for a specific image size.
123 *
124 * @param string $size The image size for which to retrieve data.
125 * @return bool|array $size Size data about an image size or false if the size doesn't exist.
126 * @uses get_image_sizes()
127 */
128 public function get_image_size($size)
129 {
130 $sizes = get_image_sizes();
131
132 if (isset($sizes[$size])) {
133 return $sizes[$size];
134 }
135
136 return false;
137 }
138
139 /**
140 * Get the width of a specific image size.
141 *
142 * @param string $size The image size for which to retrieve data.
143 * @return bool|string $size Width of an image size or false if the size doesn't exist.
144 * @uses get_image_size()
145 */
146 public function get_image_width($size)
147 {
148 if (!$size = get_image_size($size)) {
149 return false;
150 }
151
152 if (isset($size['width'])) {
153 return $size['width'];
154 }
155
156 return false;
157 }
158
159 /**
160 * Get the height of a specific image size.
161 *
162 * @param string $size The image size for which to retrieve data.
163 * @return bool|string $size Height of an image size or false if the size doesn't exist.
164 * @uses get_image_size()
165 */
166 public function get_image_height($size)
167 {
168 if (!$size = get_image_size($size)) {
169 return false;
170 }
171
172 if (isset($size['height'])) {
173 return $size['height'];
174 }
175
176 return false;
177 }
178
179 public static function delete_post_images($post_id)
180 {
181 global $wpdb;
182 $external_id = get_post_meta($post_id, '_a2w_external_id', true);
183 $post_type = get_post_type($post_id);
184 if ($external_id || $post_type == 'product_variation') {
185 $childrens = $wpdb->get_col($wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_parent = %d and post_type='attachment'", $post_id));
186 if ($childrens) {
187 foreach ($childrens as $attachment_id) {
188 Utils::delete_attachment($attachment_id, true);
189 }
190 }
191 $thumbnail_id = get_post_meta($post_id, '_thumbnail_id', true);
192 if ($thumbnail_id && $post_type != 'product_variation') {
193 Utils::delete_attachment($thumbnail_id, true);
194 }
195 }
196 }
197
198 // cloned wp_delete_attachment function, use for overload sites
199 // 2021.11.20 By default will use default function (wp_delete_attachment)
200 public static function delete_attachment($post_id, $force_delete = false)
201 {
202 if (!a2wl_check_defined('A2WL_USE_CUSTOM_DELETE_ATTACHMENT')) {
203 return wp_delete_attachment($post_id, $force_delete);
204 }
205
206 global $wpdb;
207
208 $post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id));
209
210 if (!$post) {
211 return $post;
212 }
213
214 $post = get_post($post);
215
216 if ('attachment' !== $post->post_type) {
217 return false;
218 }
219
220 if (!$force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status) {
221 return wp_trash_post($post_id);
222 }
223
224 delete_post_meta($post_id, '_wp_trash_meta_status');
225 delete_post_meta($post_id, '_wp_trash_meta_time');
226
227 $meta = wp_get_attachment_metadata($post_id);
228 $backup_sizes = get_post_meta($post->ID, '_wp_attachment_backup_sizes', true);
229 $file = get_attached_file($post_id);
230
231 if (is_multisite()) {
232 delete_transient('dirsize_cache');
233 }
234
235 /**
236 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
237 *
238 * @param int $post_id Attachment ID.
239 * @since 2.0.0
240 *
241 */
242 do_action('delete_attachment', $post_id);
243
244 wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
245 wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
246
247 // Delete all for any posts.
248 // A2W disable this call!
249 // delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
250
251 wp_defer_comment_counting(true);
252
253 $comment_ids = $wpdb->get_col($wpdb->prepare("SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id));
254 foreach ($comment_ids as $comment_id) {
255 wp_delete_comment($comment_id, true);
256 }
257
258 wp_defer_comment_counting(false);
259
260 $post_meta_ids = $wpdb->get_col($wpdb->prepare("SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id));
261 foreach ($post_meta_ids as $mid) {
262 delete_metadata_by_mid('post', $mid);
263 }
264
265 /** This action is documented in wp-includes/post.php */
266 do_action('delete_post', $post_id);
267 $result = $wpdb->delete($wpdb->posts, array('ID' => $post_id));
268 if (!$result) {
269 return false;
270 }
271 /** This action is documented in wp-includes/post.php */
272 do_action('deleted_post', $post_id);
273
274 wp_delete_attachment_files($post_id, $meta, $backup_sizes, $file);
275
276 clean_post_cache($post);
277
278 return $post;
279 }
280
281 public static function clear_image_url($img_url, $param_str = '')
282 {
283 //$img_url = str_replace("http:", "https:", $img_url);
284 if (substr($img_url, 0, 4) !== "http") {
285 $new_src = "https:" . $img_url;
286 } else {
287 $new_src = $img_url;
288 }
289
290 $parsed_url = wp_parse_url($img_url);
291
292 if (!empty($parsed_url['scheme']) && !empty($parsed_url['host']) && !empty($parsed_url['path'])) {
293 $new_src = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'] . $param_str;
294 } else if (empty($parsed_url['scheme']) && empty($parsed_url['host']) && !empty($parsed_url['path'])) {
295 $new_src = $parsed_url['path'] . $param_str;
296 }
297 // remove _640x640.jpg from image url filename.jpg_640x640.jpg
298 $new_src = preg_replace("/(.+?)(.jpg|.jpeg)(.*)/", "$1$2", $new_src);
299
300 return $new_src;
301 }
302
303 public static function buildImageIdFromPath(string $imagePath): string
304 {
305 $imagePath = self::clear_image_url($imagePath);
306
307 return md5($imagePath);
308 }
309
310 public static function buildIdFromUrl(string $url): string
311 {
312 $url = self::clear_url($url);
313
314 return md5($url);
315 }
316
317 public static function get_all_images_from_product(
318 $product, $skip = false, $product_images = true, $description_images = true
319 )
320 {
321 $tmp_all_images = [];
322
323 if ($product_images) {
324 foreach ($product['images'] as $img) {
325 if (!is_string($img)) {
326 continue;
327 }
328 $img_id = self::buildImageIdFromPath($img);
329 if (!isset($tmp_all_images[$img_id])) {
330 $tmp_all_images[$img_id] = [
331 'image' => $img,
332 'type' => 'gallery'
333 ];
334 }
335 }
336 }
337
338 if (!empty($product['sku_products']['variations'])) {
339 foreach ($product['sku_products']['variations'] as $var) {
340 if (isset($var['image'])) {
341 if (!is_string($var['image'])) {
342 continue;
343 }
344 $img_id = self::buildImageIdFromPath($var['image']);
345 if (!isset($tmp_all_images[$img_id])) {
346 $tmp_all_images[$img_id] = [
347 'image' => $var['image'],
348 'type' => 'variant'
349 ];
350 }
351 }
352 }
353 }
354
355 if ($description_images) {
356 $connector = AliexpressDefaultConnector::getInstance();
357 $desc_images = $connector::get_images_from_description($product);
358
359 //add description image only if it's not already available as image of other type
360 foreach ($desc_images as $img_id => $img) {
361 if (!isset($tmp_all_images[$img_id])) {
362 if (!is_string($img)) {
363 continue;
364 }
365 $tmp_all_images[$img_id] = [
366 'image' => $img,
367 'type' => 'description'
368 ];
369 }
370 }
371 }
372
373 if (!empty($product['sku_products']['attributes'])) {
374 foreach ($product['sku_products']['attributes'] as $attribute) {
375 foreach ($attribute['value'] as $attr_value) {
376 $has_variation = false;
377 foreach ($product['sku_products']['variations'] as $variation) {
378 if (!empty($product['skip_vars']) && !in_array($variation['id'], $product['skip_vars'])) {
379 foreach ($variation['attributes'] as $va) {
380 if ($va == $attr_value['id']) {
381 $has_variation = true;
382 }
383 }
384 }
385 }
386
387 if ($has_variation) {
388 if (get_setting('use_external_image_urls')) {
389 if (!empty($attr_value['thumb'])) {
390 if (!is_string($attr_value['thumb'])) {
391 continue;
392 }
393 $img_id = self::buildImageIdFromPath($attr_value['thumb']);
394 if (!isset($tmp_all_images[$img_id])) {
395 $tmp_all_images[$img_id] = [
396 'image' => $attr_value['thumb'],
397 'type' => 'attribute'
398 ];
399 }
400 } else if (!empty($attr_value['image'])) {
401 if (!is_string($attr_value['thumb'])) {
402 continue;
403 }
404 $img_id = self::buildImageIdFromPath($attr_value['image']);
405 if (!isset($tmp_all_images[$img_id])) {
406 $tmp_all_images[$img_id] = [
407 'image' => $attr_value['image'],
408 'type' => 'attribute'
409 ];
410 }
411 }
412 } else {
413 if (!empty($attr_value['image'])) {
414 if (!is_string($attr_value['image'])) {
415 continue;
416 }
417 $img_id = self::buildImageIdFromPath($attr_value['image']);
418 if (!isset($tmp_all_images[$img_id])) {
419 $tmp_all_images[$img_id] = [
420 'image' => $attr_value['image'],
421 'type' => 'attribute'
422 ];
423 }
424 } else if (!empty($attr_value['thumb'])) {
425 if (!is_string($attr_value['thumb'])) {
426 continue;
427 }
428 $img_id = self::buildImageIdFromPath($attr_value['thumb']);
429 if (!isset($tmp_all_images[$img_id])) {
430 $tmp_all_images[$img_id] = [
431 'image' => $attr_value['thumb'],
432 'type' => 'attribute'
433 ];
434 }
435 }
436 }
437 }
438 }
439 }
440 }
441
442 if ($skip) {
443 foreach ($product['skip_images'] as $img_id) {
444 unset($tmp_all_images[$img_id]);
445 }
446 }
447
448 return $tmp_all_images;
449 }
450
451 public static function getProductVideoUrl(array $product): string
452 {
453 if (empty($product['video'])) {
454 return '';
455 }
456
457 if (!empty($product['video']['web_url'])) {
458 return $product['video']['web_url'];
459 }
460
461 if (!empty($product['video']['android_url'])) {
462 return $product['video']['android_url'];
463 }
464
465 if (!empty($product['video']['iphone_url'])) {
466 return $product['video']['iphone_url'];
467 }
468
469 return '';
470 }
471
472 public static function getProductVideoPoster(array $product): ?string
473 {
474 if (empty($product['video'])) {
475 return null;
476 }
477
478 if (!empty($product['video']['video_thumb'])) {
479 return $product['video']['video_thumb'];
480 }
481
482 return null;
483 }
484
485 /*
486 public static function get_images_from_description($description)
487 {
488 $src_result = array();
489
490 if ($description && class_exists('DOMDocument')) {
491 $description = htmlspecialchars_decode(utf8_decode(htmlentities($description, ENT_COMPAT, 'UTF-8', false)));
492
493 if (function_exists('libxml_use_internal_errors')) {
494 libxml_use_internal_errors(true);
495 }
496 $dom = new \DOMDocument();
497 @$dom->loadHTML($description);
498 $dom->formatOutput = true;
499 $tags = $dom->getElementsByTagName('img');
500
501 foreach ($tags as $tag) {
502 $src_result[self::buildImageIdFromPath($tag->getAttribute('src'))] = $tag->getAttribute('src');
503 }
504 }
505
506 return $src_result;
507 }
508 */
509
510 public static function normalizeChars($s)
511 {
512 $replace = array(
513 'ъ' => '-', 'Ь' => '-', 'Ъ' => '-', 'ь' => '-',
514 'Ă' => 'A', 'Ą' => 'A', 'À' => 'A', 'Ã' => 'A', 'Á' => 'A', 'Æ' => 'A', 'Â' => 'A', '�
515 ' => 'A', 'Ä' => 'Ae',
516 'Þ' => 'B',
517 'Ć' => 'C', 'ץ' => 'C', 'Ç' => 'C',
518 'È' => 'E', 'Ę' => 'E', 'É' => 'E', 'Ë' => 'E', 'Ê' => 'E',
519 'Ğ' => 'G',
520 'İ' => 'I', 'Ï' => 'I', 'Î' => 'I', 'Í' => 'I', 'Ì' => 'I',
521 'Ł' => 'L',
522 'Ñ' => 'N', 'Ń' => 'N',
523 'Ø' => 'O', 'Ó' => 'O', 'Ò' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'Oe',
524 'Ş' => 'S', 'Ś' => 'S', 'Ș' => 'S', 'Š' => 'S',
525 'Ț' => 'T',
526 'Ù' => 'U', 'Û' => 'U', 'Ú' => 'U', 'Ü' => 'Ue',
527 'Ý' => 'Y',
528 'Ź' => 'Z', 'Ž' => 'Z', 'Ż' => 'Z',
529 'â' => 'a', 'ǎ' => 'a', '�
530 ' => 'a', 'á' => 'a', 'ă' => 'a', 'ã' => 'a', 'Ǎ' => 'a', 'а' => 'a', 'А' => 'a', 'å' => 'a', 'à' => 'a', 'א' => 'a', 'Ǻ' => 'a', 'Ā' => 'a', 'ǻ' => 'a', 'ā' => 'a', 'ä' => 'ae', 'æ' => 'ae', 'Ǽ' => 'ae', 'ǽ' => 'ae',
531 'б' => 'b', 'ב' => 'b', 'Б' => 'b', 'þ' => 'b',
532 'ĉ' => 'c', 'Ĉ' => 'c', 'Ċ' => 'c', 'ć' => 'c', 'ç' => 'c', 'ц' => 'c', 'צ' => 'c', 'ċ' => 'c', 'Ц' => 'c', 'Č' => 'c', 'č' => 'c', 'Ч' => 'ch', 'ч' => 'ch',
533 'ד' => 'd', 'ď' => 'd', 'Đ' => 'd', 'Ď' => 'd', 'đ' => 'd', 'д' => 'd', 'Д' => 'D', 'ð' => 'd',
534 'є' => 'e', 'ע' => 'e', 'е' => 'e', 'Е' => 'e', 'Ə' => 'e', 'ę' => 'e', 'ĕ' => 'e', 'ē' => 'e', 'Ē' => 'e', 'Ė' => 'e', 'ė' => 'e', 'ě' => 'e', 'Ě' => 'e', 'Є' => 'e', 'Ĕ' => 'e', 'ê' => 'e', 'ə' => 'e', 'è' => 'e', 'ë' => 'e', 'é' => 'e',
535 'ф' => 'f', 'ƒ' => 'f', 'Ф' => 'f',
536 'ġ' => 'g', 'Ģ' => 'g', 'Ġ' => 'g', 'Ĝ' => 'g', 'Г' => 'g', 'г' => 'g', 'ĝ' => 'g', 'ğ' => 'g', 'ג' => 'g', 'Ґ' => 'g', 'ґ' => 'g', 'ģ' => 'g',
537 'ח' => 'h', 'ħ' => 'h', 'Х' => 'h', 'Ħ' => 'h', 'Ĥ' => 'h', 'ĥ' => 'h', '�
538 ' => 'h', 'ה' => 'h',
539 'î' => 'i', 'ï' => 'i', 'í' => 'i', 'ì' => 'i', 'į' => 'i', 'ĭ' => 'i', 'ı' => 'i', 'Ĭ' => 'i', 'И' => 'i', 'ĩ' => 'i', 'ǐ' => 'i', 'Ĩ' => 'i', 'Ǐ' => 'i', 'и' => 'i', 'Į' => 'i', 'י' => 'i', 'Ї' => 'i', 'Ī' => 'i', 'І' => 'i', 'ї' => 'i', 'і' => 'i', 'ī' => 'i', 'ij' => 'ij', 'IJ' => 'ij',
540 'й' => 'j', 'Й' => 'j', 'Ĵ' => 'j', 'ĵ' => 'j', 'я' => 'ja', 'Я' => 'ja', 'Э' => 'je', 'э' => 'je', 'ё' => 'jo', 'Ё' => 'jo', 'ю' => 'ju', 'Ю' => 'ju',
541 'ĸ' => 'k', 'כ' => 'k', 'Ķ' => 'k', 'К' => 'k', 'к' => 'k', 'ķ' => 'k', 'ך' => 'k',
542 'Ŀ' => 'l', 'ŀ' => 'l', 'Л' => 'l', 'ł' => 'l', 'ļ' => 'l', 'ĺ' => 'l', 'Ĺ' => 'l', 'Ļ' => 'l', 'л' => 'l', 'Ľ' => 'l', 'ľ' => 'l', 'ל' => 'l',
543 'מ' => 'm', 'М' => 'm', 'ם' => 'm', 'м' => 'm',
544 'ñ' => 'n', 'н' => 'n', '�
545 ' => 'n', 'ן' => 'n', 'ŋ' => 'n', 'נ' => 'n', 'Н' => 'n', 'ń' => 'n', 'Ŋ' => 'n', 'ņ' => 'n', 'ʼn' => 'n', 'Ň' => 'n', 'ň' => 'n',
546 'о' => 'o', 'О' => 'o', 'ő' => 'o', 'õ' => 'o', 'ô' => 'o', 'Ő' => 'o', 'ŏ' => 'o', 'Ŏ' => 'o', 'Ō' => 'o', 'ō' => 'o', 'ø' => 'o', 'ǿ' => 'o', 'ǒ' => 'o', 'ò' => 'o', 'Ǿ' => 'o', 'Ǒ' => 'o', 'ơ' => 'o', 'ó' => 'o', 'Ơ' => 'o', 'œ' => 'oe', 'Œ' => 'oe', 'ö' => 'oe',
547 'פ' => 'p', 'ף' => 'p', 'п' => 'p', 'П' => 'p',
548 'ק' => 'q',
549 'ŕ' => 'r', 'ř' => 'r', 'Ř' => 'r', 'ŗ' => 'r', 'Ŗ' => 'r', 'ר' => 'r', 'Ŕ' => 'r', 'Р' => 'r', 'р' => 'r',
550 'ș' => 's', 'с' => 's', 'Ŝ' => 's', 'š' => 's', 'ś' => 's', 'ס' => 's', 'ş' => 's', 'С' => 's', 'ŝ' => 's', 'Щ' => 'sch', 'щ' => 'sch', 'ш' => 'sh', 'Ш' => 'sh', 'ß' => 'ss',
551 'т' => 't', 'ט' => 't', 'ŧ' => 't', 'ת' => 't', 'ť' => 't', 'ţ' => 't', 'Ţ' => 't', 'Т' => 't', 'ț' => 't', 'Ŧ' => 't', 'Ť' => 't', '' => 'tm',
552 'ū' => 'u', 'у' => 'u', 'Ũ' => 'u', 'ũ' => 'u', 'Ư' => 'u', 'ư' => 'u', 'Ū' => 'u', 'Ǔ' => 'u', 'ų' => 'u', 'Ų' => 'u', 'ŭ' => 'u', 'Ŭ' => 'u', 'Ů' => 'u', 'ů' => 'u', 'ű' => 'u', 'Ű' => 'u', 'Ǖ' => 'u', 'ǔ' => 'u', 'Ǜ' => 'u', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'У' => 'u', 'ǚ' => 'u', 'ǜ' => 'u', 'Ǚ' => 'u', 'Ǘ' => 'u', 'ǖ' => 'u', 'ǘ' => 'u', 'ü' => 'ue',
553 'в' => 'v', 'ו' => 'v', 'В' => 'v',
554 'ש' => 'w', 'ŵ' => 'w', 'Ŵ' => 'w',
555 'ы' => 'y', 'ŷ' => 'y', 'ý' => 'y', 'ÿ' => 'y', 'Ÿ' => 'y', 'Ŷ' => 'y',
556 'Ы' => 'y', 'ž' => 'z', 'З' => 'z', 'з' => 'z', 'ź' => 'z', 'ז' => 'z', 'ż' => 'z', 'ſ' => 'z', 'Ж' => 'zh', 'ж' => 'zh',
557 );
558 return strtr($s, $replace);
559 }
560
561 public static function safeTransliterate($text)
562 {
563
564 //for cyrilic first:
565 $cyr = array(
566 'ж', 'ч', 'щ', 'ш', 'ю', 'а', 'б', 'в', 'г', 'д', 'е', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', '�
567 ', 'ц', 'ъ', 'ь', 'я',
568 'Ж', 'Ч', 'Щ', 'Ш', 'Ю', 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ъ', 'Ь', 'Я');
569 $lat = array(
570 'zh', 'ch', 'sht', 'sh', 'yu', 'a', 'b', 'v', 'g', 'd', 'e', 'z', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'f', 'h', 'c', 'y', 'x', 'q',
571 'Zh', 'Ch', 'Sht', 'Sh', 'Yu', 'A', 'B', 'V', 'G', 'D', 'E', 'Z', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F', 'H', 'c', 'Y', 'X', 'Q');
572 $text = str_replace($cyr, $lat, $text);
573
574 //for Brasilian and Arabic languages:
575
576 //if available, this function uses PHP5.4's transliterate, which is capable of converting arabic, hebrew, greek,
577 //chinese, japanese and more into ASCII! however, we use our manual (and crude) fallback *first* instead because
578 //we will take the liberty of transliterating some things into more readable ASCII-friendly forms,
579 //e.g. "100℃" > "100degc" instead of "100oc"
580
581 /* manual transliteration list:
582 -------------------------------------------------------------------------------------------------------------- */
583 /* this list is supposed to be practical, not comprehensive, representing:
584 1. the most common accents and special letters that get typed, and
585 2. the most practical transliterations for readability;
586
587 this data was produced with the help of:
588 http://www.unicode.org/charts/normalization/
589 http://www.yuiblog.com/sandbox/yui/3.3.0pr3/api/text-data-accentfold.js.html
590 http://www.utf8-chartable.de/
591 */
592 static $translit = array(
593 'a' => '/[ÀÁÂẦẤẪẨÃĀĂẰẮẴȦẲǠẢ�
594
595 ǺǍȀȂẠẬẶḀĄẚàáâầấẫẩãāăằắẵẳȧǡảåǻǎȁȃạậặḁ�
596 ]/u',
597 'b' => '/[ḂḄḆḃ�
598 ḇ]/u', 'c' => '/[ÇĆĈĊČḈçćĉċčḉ]/u',
599 'd' => '/[ÐĎḊḌḎḐḒďḋḍḏḑḓð]/u',
600 'e' => '/[ÈËĒĔĖĘĚȄȆȨḔḖḘḚḜẸẺẼẾỀỂỄỆèëēĕėęě�
601 ȇȩḕḗḙḛḝẹẻẽếềể�
602 ]/u',
603 'f' => '/[Ḟḟ]/u', 'g' => '/[ĜĞĠĢǦǴḠĝğġģǧǵḡ]/u',
604 'h' => '/[ĤȞḢḤḦḨḪĥȟḣḥḧḩḫẖ]/u', 'i' => '/[ÌÏĨĪĬĮİǏȈȊḬḮỈỊiìïĩīĭįǐȉȋḭḯỉị]/u',
605 'j' => '/[Ĵĵǰ]/u', 'k' => '/[ĶǨḰḲḴKķǩḱḳḵ]/u',
606 'l' => '/[ĹĻĽĿḶḸḺḼĺļľŀḷḹḻḽ]/u', 'm' => '/[ḾṀṂḿṁṃ]/u',
607 'n' => '/[ÑŃ�
608 ŇǸṄṆṈṊñńņňǹ�
609 ṇṉṋ]/u',
610 'o' => '/[ÒÖŌŎŐƠǑǪǬȌȎȪȬȮȰṌṎṐṒỌỎỐỒỔỖỘỚỜỞỠỢØǾòöōŏőơǒǫǭȍȏȫȭȯȱṍṏṑṓọỏốồổỗộớờởỡợøǿ]/u',
611 'p' => '/[ṔṖṕṗ]/u', 'r' => '/[ŔŖŘȐȒṘṚṜṞŕŗřȑȓṙṛṝṟ]/u',
612 's' => '/[ŚŜŞŠȘṠṢṤṦṨſśŝşšșṡṣṥṧṩ]/u', 'ss' => '/[ß]/u',
613 't' => '/[ŢŤȚṪṬṮṰţťțṫṭṯṱẗ]/u', 'th' => '/[Þþ]/u',
614 'u' => '/[ÙŨŪŬŮŰŲƯǓȔȖṲṴṶṸṺỤỦỨỪỬỮỰùũūŭůűųưǔȕȗṳṵṷṹṻụủứừửữựµ]/u',
615 'v' => '/[ṼṾṽṿ]/u', 'w' => '/[ŴẀẂẄẆẈŵẁẃ�
616 ẇẉẘ]/u',
617 'x' => '/[ẊẌẋẍ×]/u', 'y' => '/[ÝŶŸȲẎỲỴỶỸýÿŷȳẏẙỳỵỷỹ]/u',
618 'z' => '/[ŹŻŽẐẒẔźżžẑẓẕ]/u',
619 //combined letters and ligatures:
620 'ae' => '/[ÄǞÆǼǢäǟæǽǣ]/u', 'oe' => '/[Œœ]/u',
621 'dz' => '/[D�
622 DZDzdždz]/u',
623 'ff' => '/[]/u', 'fi' => '/[ffifi]/u', 'ffl' => '/[fflfl]/u',
624 'ij' => '/[IJij]/u', 'lj' => '/[LJLjlj]/u', 'nj' => '/[NJNjnj]/u',
625 'st' => '/[�
626 ]/u', 'ue' => '/[ÜǕǗǙǛüǖǘǚǜ]/u',
627 //currencies:
628 'eur' => '/[]/u', 'cents' => '/[¢]/u', 'lira' => '/[]/u', 'dollars' => '/[$]/u',
629 'won' => '/[]/u', 'rs' => '/[]/u', 'yen' => '/[¥]/u', 'pounds' => '/[£]/u',
630 'pts' => '/[]/u',
631 //misc:
632 'degc' => '/[]/u', 'degf' => '/[]/u',
633 'no' => '/[]/u', 'tm' => '/[]/u',
634 );
635 //do the manual transliteration first
636 $text = preg_replace(array_values($translit), array_keys($translit), $text);
637
638 //flatten the text down to just a-z0-9 and dash, with underscores instead of spaces
639 $text = preg_replace(
640 //remove punctuation //replace non a-z //deduplicate //trim underscores from start & end
641 array( /*'/\p{P}/u',*//*'/[^_a-z0-9-]/i',*/'/_{2,}/', '/^_|_$/'),
642 array( /*'',*//* '_', */'_', ''),
643
644 //attempt transliteration with PHP5.4's transliteration engine (best):
645 //(this method can handle near anything, including converting chinese and arabic letters to ASCII.
646 // requires the 'intl' extension to be enabled)
647 function_exists('transliterator_transliterate') ? transliterator_transliterate(
648 //split unicode accents and symbols, e.g. "�
649 " > "":
650 'NFKD; ' .
651 //convert everything to the Latin charset e.g. "" > "ma":
652 //(splitting the unicode before transliterating catches some complex cases,
653 // such as: "" >NFKD> "20" >Latin> "20ri")
654 'Latin; ' .
655 //because the Latin unicode table still contains a large number of non-pure-A-Z glyphs (e.g. "œ"),
656 //convert what remains to an even stricter set of characters, the US-ASCII set:
657 //(we must do this because "Latin/US-ASCII" alone is not able to transliterate non-Latin characters
658 // such as "". this two-stage method also means we catch awkward characters such as:
659 // "" >Latin> "" >Latin/US-ASCII> "kO")
660 'Latin/US-ASCII; ' .
661 //remove the now stand-alone diacritics from the string
662 '[:Nonspacing Mark:] Remove; ',
663 $text)
664
665 //attempt transliteration with iconv: <php.net/manual/en/function.iconv.php>
666 : (function_exists('iconv') ? str_replace(array("'", '"', '`', '^', '~'), '',
667 //note: results of this are different depending on iconv version,
668 // sometimes the diacritics are written to the side e.g. "ñ" = "~n", which are removed
669 iconv('UTF-8', 'US-ASCII//IGNORE//TRANSLIT', $text)
670 ) : $text)
671 );
672
673 //old iconv versions and certain inputs may cause a nullstring. don't allow a blank response
674 return !$text ? '_' : $text;
675 }
676
677 public static function remove_ship_from($product, $country_from = 'CN')
678 {
679 $default_country = 'CN';
680 $ship_from_attr_value = array();
681 $ship_from_attr_name = "";
682 foreach ($product['sku_products']['attributes'] as $attr) {
683 foreach ($attr['value'] as $attr_val) {
684 if (isset($attr_val[ImportedProductService::FIELD_COUNTRY_CODE])) {
685 $ship_from_attr_name = $attr['name'];
686 if ($attr_val[ImportedProductService::FIELD_COUNTRY_CODE] === $default_country) {
687 $ship_from_attr_value[$default_country] = $attr_val['id'];
688 } else if ($attr_val[ImportedProductService::FIELD_COUNTRY_CODE] === $country_from) {
689 $ship_from_attr_value[$country_from] = $attr_val['id'];
690 }
691 }
692 }
693 }
694
695 $ship_from_attr_value = empty($ship_from_attr_value) ? false : ($ship_from_attr_value[$country_from] ?? $ship_from_attr_value[$default_country]);
696
697 if ($ship_from_attr_name && $ship_from_attr_value) {
698 $product['disable_add_new_variants'] = true;
699 $product['skip_vars'] = array();
700 $product['skip_attr'] = array($ship_from_attr_name);
701 foreach ($product['sku_products']['variations'] as $v) {
702 if (!in_array($ship_from_attr_value, $v['attributes'])) {
703 $product['skip_vars'][] = $v['id'];
704 }
705 }
706 }
707
708 return $product;
709 }
710
711 public static function get_aliexpress_shipping_options(){
712 return
713 [
714 [
715 'value' => "CAINIAO_FULFILLMENT_STD",
716 'label' => "AliExpress Selection Standard"
717 ],
718 [
719 'value' => "CAINIAO_FULFILLMENT_STD_PRE_SG",
720 'label' => "Choice Special Cargo Standard PRE"
721 ],
722 [
723 'value' => "CAINIAO_FULFILLMENT_PRE",
724 'label' => "AliExpress Selection Premium"
725 ],
726 [
727 'value' => "ECONOMIC139",
728 'label' => "139Express"
729 ], [
730 'value' => "AE_360LION_STANDARD",
731 'label' => "360 Lion Standard Packet"
732 ], [
733 'value' => "FOURPX_RM",
734 'label' => "4PX RM"
735 ], [
736 'value' => "SGP_OMP",
737 'label' => "4PX Singapore Post OM Pro"
738 ], [
739 'value' => "CAINIAO_CONSOLIDATION_SA",
740 'label' => "Aliexpress Direct (Saudi Arabia)"
741 ], [
742 'value' => "CAINIAO_CONSOLIDATION_AE",
743 'label' => "Aliexpress Direct (UAE)"
744 ], [
745 'value' => "CAINIAO_CONSOLIDATION_BR",
746 'label' => "Aliexpress Direct (Brazil)"
747 ], [
748 'value' => "CAINIAO_PREMIUM",
749 'label' => "AliExpress Premium Shipping"
750 ], [
751 'value' => "CAINIAO_ECONOMY",
752 'label' => "AliExpress Saver Shipping"
753 ], [
754 'value' => "CAINIAO_STANDARD",
755 'label' => "AliExpress Standard Shipping"
756 ], [
757 'value' => "ARAMEX",
758 'label' => "ARAMEX"
759 ], [
760 'value' => "AUSPOST",
761 'label' => "Australia Post"
762 ], [
763 'value' => "BSC_ECONOMY_SG",
764 'label' => "BSC Special Economy"
765 ], [
766 'value' => "BSC_STANDARD_SG",
767 'label' => "BSC Special Standard"
768 ], [
769 'value' => "CAINIAO_EXPEDITED_ECONOMY",
770 'label' => "Cainiao Expedited Economy"
771 ], [
772 'value' => "AE_CAINIAO_STANDARD",
773 'label' => "Cainiao Expedited Standard"
774 ], [
775 'value' => "CAINIAO_STANDARD_HEAVY",
776 'label' => "Cainiao Heavy Parcel Line"
777 ], [
778 'value' => "CAINIAO_ECONOMY_SG",
779 'label' => "Cainiao Saver Shipping For Special Goods"
780 ], [
781 'value' => "CAINIAO_STANDARD_SG",
782 'label' => "Cainiao Standard For Special Goods"
783 ], [
784 'value' => "CAINIAO_SUPER_ECONOMY",
785 'label' => "Cainiao Super Economy"
786 ], [
787 'value' => "CAINIAO_SUPER_ECONOMY_SG",
788 'label' => "Cainiao Super Economy for Special Goods"
789 ], [
790 'value' => "AE_CN_SUPER_ECONOMY_G",
791 'label' => "Cainiao Super Economy Global"
792 ], [
793 'value' => "CAINIAO_OVERSEAS_WH_EXPPL",
794 'label' => "Cainiao Warehouse Express Shipping"
795 ], [
796 'value' => "CDEK_RU",
797 'label' => "CDEK"
798 ], [
799 'value' => "CPAP",
800 'label' => "China Post Air Parcel"
801 ], [
802 'value' => "YANWEN_JYT",
803 'label' => "China Post Ordinary Small Packet Plus"
804 ], [
805 'value' => "CPAM",
806 'label' => "China Post Registered Air Mail"
807 ], [
808 'value' => "CHOICE",
809 'label' => "CHOICE Logistics"
810 ], [
811 'value' => "CJ",
812 'label' => "CJ Logistics"
813 ], [
814 'value' => "CKE",
815 'label' => "CKE Express"
816 ], [
817 'value' => "CNE",
818 'label' => "CNE Express"
819 ], [
820 'value' => "CORREIOS_BR",
821 'label' => "Correios Brazil"
822 ], [
823 'value' => "DEUTSCHE_POST",
824 'label' => "Deutsche Post"
825 ], [
826 'value' => "DHL",
827 'label' => "DHL"
828 ], [
829 'value' => "DHLECOM",
830 'label' => "DHL e-commerce"
831 ], [
832 'value' => "TOLL",
833 'label' => "DPEX"
834 ], [
835 'value' => "EMS",
836 'label' => "EMS"
837 ], [
838 'value' => "E_EMS",
839 'label' => "e-EMS"
840 ], [
841 'value' => "EMS_ZX_ZX_US",
842 'label' => "ePacket"
843 ], [
844 'value' => "eTotal",
845 'label' => "eTotal"
846 ], [
847 'value' => "FEDEX",
848 'label' => "FedEx"
849 ], [
850 'value' => "FEDEX_IE",
851 'label' => "Fedex IE"
852 ], [
853 'value' => "FLYT",
854 'label' => "Flyt Express"
855 ], [
856 'value' => "FLYT_ECONOMY_SG",
857 'label' => "Flyt Special Economy"
858 ], [
859 'value' => "GATI",
860 'label' => "GATI"
861 ], [
862 'value' => "GES",
863 'label' => "GES Express"
864 ], [
865 'value' => "GLS_FR",
866 'label' => "GLS France"
867 ], [
868 'value' => "GLS_ES",
869 'label' => "GLS Spain"
870 ], [
871 'value' => "CTR_LAND_PICKUP",
872 'label' => "J-NET"
873 ], [
874 'value' => "JCEX",
875 'label' => "JCEX Express"
876 ], [
877 'value' => "LAPOSTE",
878 'label' => "La Poste"
879 ], [
880 'value' => "MEEST",
881 'label' => "Meest"
882 ], [
883 'value' => "POSTKR",
884 'label' => "POSTKR"
885 ], [
886 'value' => "POST_NL",
887 'label' => "PostNL"
888 ], [
889 'value' => "RUSSIAN_POST",
890 'label' => "Russian Post"
891 ], [
892 'value' => "SF_EPARCEL_OM",
893 'label' => "SF Economic Air Mail"
894 ], [
895 'value' => "SF_EPARCEL",
896 'label' => "SF eParcel"
897 ], [
898 'value' => "SF",
899 'label' => "SF Express"
900 ], [
901 'value' => "SGP",
902 'label' => "Singapore Post"
903 ], [
904 'value' => "SUNYOU_RM",
905 'label' => "SunYou"
906 ], [
907 'value' => "SUNYOU_ECONOMY",
908 'label' => "SunYou Economic Air Mail"
909 ], [
910 'value' => "SUNYOU_ECONOMY_SG",
911 'label' => "SunYou Special Economy"
912 ], [
913 'value' => "SHUNYOU_STANDARD_SG",
914 'label' => "SunYou Special Standard"
915 ], [
916 'value' => "CHP",
917 'label' => "Swiss Post"
918 ], [
919 'value' => "TNT",
920 'label' => "TNT"
921 ], [
922 'value' => "LAOPOST",
923 'label' => "TOPYOU"
924 ], [
925 'value' => "TOPYOU_ECONOMY_SG",
926 'label' => "TOPYOU Special Economy"
927 ], [
928 'value' => "PTT",
929 'label' => "Turkey Post"
930 ], [
931 'value' => "UBI",
932 'label' => "UBI"
933 ], [
934 'value' => "UPS",
935 'label' => "UPS"
936 ], [
937 'value' => "UPSE",
938 'label' => "UPS Expedited"
939 ], [
940 'value' => "USPS",
941 'label' => "USPS"
942 ], [
943 'value' => "YANWEN_ECONOMY",
944 'label' => "Yanwen Economic Air Mail"
945 ], [
946 'value' => "YANWEN_ECONOMY_SG",
947 'label' => "Yanwen Special Economy"
948 ], [
949 'value' => "YANWEN_AM",
950 'label' => "Yanwen Special Line-YW"
951 ], [
952 'value' => "YANWEN_AM",
953 'label' => "Yanwen Special Standard"
954 ]
955 ];
956 }
957
958 /**
959 * Get phone country code or return list of all country phone codes
960 *
961 * @param string $country
962 *
963 * @return mixed|string|null
964 */
965 public static function get_phone_country_code($country = '')
966 {
967 $data = array();
968 $file = A2WL()->plugin_path() . '/assets/data/phone_country_code.json';
969 if (file_exists($file)) {
970 $data = json_decode(
971 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
972 file_get_contents($file),
973 true
974 );
975 }
976 if ($country) {
977 return $data[$country] ?? '';
978 } else {
979 return $data;
980 }
981 }
982
983 public static function sanitize_phone_number($phone): ?string
984 {
985 return preg_replace('/[^\d]/', '', $phone);
986 }
987
988 public static function wp_kses_post($content)
989 {
990 $allowed_html = wp_kses_allowed_html('post');
991 $allowed_html = array_merge($allowed_html, array(
992 'input' => array(
993 'type' => 1,
994 'id' => 1,
995 'name' => 1,
996 'class' => 1,
997 'placeholder' => 1,
998 'autocomplete' => 1,
999 'style' => 1,
1000 'value' => 1,
1001 'size' => 1,
1002 'checked' => 1,
1003 'disabled' => 1,
1004 'readonly' => 1,
1005 'data-*' => 1,
1006 ),
1007 'form' => array(
1008 'method' => 1,
1009 'id' => 1,
1010 'class' => 1,
1011 'action' => 1,
1012 'data-*' => 1,
1013 ),
1014 'select' => array(
1015 'id' => 1,
1016 'name' => 1,
1017 'class' => 1,
1018 'multiple' => 1,
1019 'data-*' => 1,
1020 ),
1021 'option' => array(
1022 'value' => 1,
1023 'selected' => 1,
1024 'data-*' => 1,
1025 ),
1026 )
1027 );
1028 foreach ($allowed_html as $key => $value) {
1029 if ($key === 'input') {
1030 $allowed_html[$key]['data-*'] = 1;
1031 $allowed_html[$key]['checked'] = 1;
1032 $allowed_html[$key]['disabled'] = 1;
1033 $allowed_html[$key]['readonly'] = 1;
1034 } elseif (in_array($key, array('div', 'span', 'a', 'form', 'select', 'option', 'tr', 'td'))) {
1035 $allowed_html[$key]['data-*'] = 1;
1036 }
1037 }
1038
1039 return wp_kses($content, $allowed_html);
1040 }
1041
1042 /**
1043 * Set cookie in WordPress admin with safe parameters
1044 *
1045 * @param string $name Cookie name
1046 * @param string $value Cookie value
1047 * @param int $expire Cookie expiration time (30 days by default)
1048 */
1049 public static function setAdminCookie(string $name, string $value, int $expire = 30 * DAY_IN_SECONDS): void
1050 {
1051 setcookie($name, $value, [
1052 'expires' => time() + $expire,
1053 'path' => COOKIEPATH,
1054 'domain' => COOKIE_DOMAIN,
1055 'secure' => is_ssl(),
1056 'httponly' => true,
1057 'samesite' => 'Lax', // or 'Strict' if neassery
1058 ]);
1059 }
1060
1061 }
1062